From 0c53a0dc41b7daf5740230b14daf4fa9543e6a9d Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 24 Oct 2023 12:02:22 -0700 Subject: [PATCH] rewrite text encoding to handle non-English properly --- CMakeLists.txt | 5 +- README.md | 5 +- src/BMLArchive.cc | 4 +- src/CatSession.cc | 1 + src/Channel.cc | 5 + src/Channel.hh | 3 + src/ChatCommands.cc | 638 +++++++-------- src/ChatCommands.hh | 4 +- src/Client.cc | 3 +- src/Client.hh | 4 +- src/CommandFormats.hh | 548 ++++++------- src/Episode3/BattleRecord.cc | 2 +- src/Episode3/Card.cc | 14 +- src/Episode3/CardSpecial.cc | 29 +- src/Episode3/DataIndexes.cc | 73 +- src/Episode3/DataIndexes.hh | 56 +- src/Episode3/DeckState.cc | 2 +- src/Episode3/DeckState.hh | 2 +- src/Episode3/Server.cc | 53 +- src/Episode3/Server.hh | 2 +- src/Episode3/Tournament.cc | 4 +- src/FunctionCompiler.cc | 14 +- src/GSLArchive.cc | 6 +- src/GVMEncoder.cc | 4 +- src/ItemParameterTable.hh | 2 + src/License.cc | 16 +- src/Lobby.cc | 20 +- src/Lobby.hh | 6 +- src/Main.cc | 34 +- src/Menu.cc | 10 +- src/Menu.hh | 16 +- src/PSOEncryption.cc | 57 -- src/PSOEncryption.hh | 29 - src/Player.cc | 6 +- src/Player.hh | 10 +- src/PlayerSubordinates.cc | 69 +- src/PlayerSubordinates.hh | 97 ++- src/ProxyCommands.cc | 234 +++--- src/ProxyServer.cc | 70 +- src/ProxyServer.hh | 11 +- src/Quest.cc | 52 +- src/Quest.hh | 14 +- src/QuestScript.cc | 68 +- src/QuestScript.hh | 27 +- src/ReceiveCommands.cc | 761 +++++++++--------- src/ReceiveCommands.hh | 8 +- src/ReceiveSubcommands.cc | 32 +- src/ReplaySession.cc | 83 +- src/SaveFileFormats.hh | 48 +- src/SendCommands.cc | 551 +++++++------ src/SendCommands.hh | 57 +- src/Server.cc | 3 +- src/ServerShell.cc | 28 +- src/ServerState.cc | 54 +- src/ServerState.hh | 12 +- src/StaticGameData.cc | 40 - src/StaticGameData.hh | 10 - src/Text.cc | 376 ++++++--- src/Text.hh | 754 +++++++---------- src/UnicodeTextSet.cc | 14 +- src/UnicodeTextSet.hh | 4 +- tests/DCv2-GameSmokeTest.test.txt | 2 +- tests/GC-Episode3BattleWithSpectator.test.txt | 34 +- tests/GC-ForestGame.test.txt | 12 +- tests/PC-BasicGame.test.txt | 2 +- 65 files changed, 2483 insertions(+), 2731 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 41216371..903631b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ set (LIBEVENT_LIBRARIES ${LIBEVENT_CORE}) find_package(phosg REQUIRED) +find_package(Iconv REQUIRED) find_package(resource_file QUIET) @@ -109,8 +110,8 @@ add_executable(newserv src/Version.cc src/WordSelectTable.cc ) -target_include_directories(newserv PUBLIC ${LIBEVENT_INCLUDE_DIR}) -target_link_libraries(newserv phosg ${LIBEVENT_LIBRARIES} pthread) +target_include_directories(newserv PUBLIC ${LIBEVENT_INCLUDE_DIR} ${Iconv_INCLUDE_DIRS}) +target_link_libraries(newserv phosg ${LIBEVENT_LIBRARIES} ${Iconv_LIBRARIES} pthread) if(resource_file_FOUND) target_compile_definitions(newserv PUBLIC HAVE_RESOURCE_FILE) diff --git a/README.md b/README.md index 255682f2..81b376d6 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,8 @@ There is a fairly recent macOS ARM64 release on the newserv GitHub repository. Y There is a fairly recent Windows release on the newserv GitHub repository also. It's built with Cygwin, and all the necessary DLL files should be included. That said, I've only tested it on my own machine and there is no CI for Windows builds like there is for macOS and Linux, so if it doesn't work for you, please open a GitHub issue to let me know. If you're not using a release from the GitHub repository, do this to build newserv: -1. If you're on Windows, install Cygwin. While doing so, install the `cmake`, `gcc-core`, `gcc-g++`, `git`, `libevent2.1_7`, `make`, and `zlib` packages. Do the rest of these steps inside a Cygwin shell (not a Windows cmd shell or PowerShell). -2. Make sure you have CMake and libevent installed. (On macOS, `brew install cmake libevent`; on most Linuxes, `sudo apt-get install cmake libevent-dev`; on Windows, you already did this in step 1.) +1. If you're on Windows, install Cygwin. While doing so, install the `cmake`, `gcc-core`, `gcc-g++`, `git`, `libevent2.1_7`, `make`, `libiconv`, and `zlib` packages. Do the rest of these steps inside a Cygwin shell (not a Windows cmd shell or PowerShell). +2. Make sure you have CMake, libevent, and libiconv installed. (On macOS, `brew install cmake libevent libiconv`; on most Linuxes, `sudo apt-get install cmake libevent-dev`; on Windows, you already did this in step 1.) 3. Build and install phosg (https://github.com/fuzziqersoftware/phosg). 4. Optionally, install resource_dasm (https://github.com/fuzziqersoftware/resource_dasm). This will enable newserv to send memory patches and load DOL files on PSO GC clients. PSO GC clients can play PSO normally on newserv without this. 5. Run `cmake . && make` in the newserv directory. @@ -395,7 +395,6 @@ newserv has many CLI options, which can be used to access functionality other th * Convert a PSO GC or Episode 3 snapshot file to a BMP image (`decode-gci-snapshot`) * Find the likely round1 or round2 seed for a corrupt save file (`salvage-gci`) * Run a brute-force search for a decryption seed (`find-decryption-seed`) -* Decode Shift-JIS text to UTF-16 (`decode-sjis`) * Convert quests in .gci, .vms, .dlq, or .qst format to .bin/.dat format (`decode-gci`, `decode-vms`, `decode-dlq`, `decode-qst`) * Convert quests in .bin/.dat to .qst format (`encode-qst`) * Convert text archives (e.g. TextEnglish.pr2) to JSON and vice versa (`decode-text-archive`, `encode-text-archive`) diff --git a/src/BMLArchive.cc b/src/BMLArchive.cc index 81508215..06e3a915 100644 --- a/src/BMLArchive.cc +++ b/src/BMLArchive.cc @@ -21,7 +21,7 @@ template struct BMLHeaderEntry { using U32T = typename std::conditional::type; - ptext filename; + pstring filename; U32T compressed_size; parray unknown_a1; U32T decompressed_size; @@ -52,7 +52,7 @@ void BMLArchive::load_t() { size_t gvm_offset = offset; offset = (offset + entry.compressed_gvm_size + 0x1F) & (~0x1F); - this->entries.emplace(entry.filename, Entry{data_offset, entry.compressed_size, gvm_offset, entry.compressed_gvm_size}); + this->entries.emplace(entry.filename.decode(), Entry{data_offset, entry.compressed_size, gvm_offset, entry.compressed_gvm_size}); } } diff --git a/src/CatSession.cc b/src/CatSession.cc index b8d9927e..9de9ea14 100644 --- a/src/CatSession.cc +++ b/src/CatSession.cc @@ -40,6 +40,7 @@ CatSession::CatSession( log("[CatSession] ", proxy_server_log.min_level), channel( version, + 1, CatSession::dispatch_on_channel_input, CatSession::dispatch_on_channel_error, this, diff --git a/src/Channel.cc b/src/Channel.cc index aad39595..38e79fff 100644 --- a/src/Channel.cc +++ b/src/Channel.cc @@ -24,6 +24,7 @@ static void flush_and_free_bufferevent(struct bufferevent* bev) { Channel::Channel( GameVersion version, + uint8_t language, on_command_received_t on_command_received, on_error_t on_error, void* context_obj, @@ -32,6 +33,7 @@ Channel::Channel( TerminalFormat terminal_recv_color) : bev(nullptr, flush_and_free_bufferevent), version(version), + language(language), name(name), terminal_send_color(terminal_send_color), terminal_recv_color(terminal_recv_color), @@ -43,6 +45,7 @@ Channel::Channel( Channel::Channel( struct bufferevent* bev, GameVersion version, + uint8_t language, on_command_received_t on_command_received, on_error_t on_error, void* context_obj, @@ -51,6 +54,7 @@ Channel::Channel( TerminalFormat terminal_recv_color) : bev(nullptr, flush_and_free_bufferevent), version(version), + language(language), name(name), terminal_send_color(terminal_send_color), terminal_recv_color(terminal_recv_color), @@ -71,6 +75,7 @@ void Channel::replace_with( this->remote_addr = other.remote_addr; this->is_virtual_connection = other.is_virtual_connection; this->version = other.version; + this->language = other.language; this->crypt_in = other.crypt_in; this->crypt_out = other.crypt_out; this->name = name; diff --git a/src/Channel.hh b/src/Channel.hh index db458ca4..ce562928 100644 --- a/src/Channel.hh +++ b/src/Channel.hh @@ -16,6 +16,7 @@ struct Channel { bool is_virtual_connection; GameVersion version; + uint8_t language; std::shared_ptr crypt_in; std::shared_ptr crypt_out; @@ -39,6 +40,7 @@ struct Channel { // Creates an unconnected channel Channel( GameVersion version, + uint8_t language, on_command_received_t on_command_received, on_error_t on_error, void* context_obj, @@ -49,6 +51,7 @@ struct Channel { Channel( struct bufferevent* bev, GameVersion version, + uint8_t language, on_command_received_t on_command_received, on_error_t on_error, void* context_obj, diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index b6e6c13f..ec539573 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -26,69 +26,69 @@ using namespace std; class precondition_failed { public: - precondition_failed(const std::u16string& user_msg) : user_msg(user_msg) {} + precondition_failed(const std::string& user_msg) : user_msg(user_msg) {} ~precondition_failed() = default; - const std::u16string& what() const { + const std::string& what() const { return this->user_msg; } private: - std::u16string user_msg; + std::string user_msg; }; static void check_license_flags(shared_ptr c, uint32_t mask) { if (!c->license) { - throw precondition_failed(u"$C6You are not\nlogged in."); + throw precondition_failed("$C6You are not\nlogged in."); } if ((c->license->flags & mask) != mask) { - throw precondition_failed(u"$C6You do not have\npermission to\nrun this command."); + throw precondition_failed("$C6You do not have\npermission to\nrun this command."); } } static void check_version(shared_ptr c, GameVersion version) { if (c->version() != version) { - throw precondition_failed(u"$C6This command cannot\nbe used for your\nversion of PSO."); + throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO."); } } static void check_not_version(shared_ptr c, GameVersion version) { if (c->version() == version) { - throw precondition_failed(u"$C6This command cannot\nbe used for your\nversion of PSO."); + throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO."); } } static void check_is_game(shared_ptr l, bool is_game) { if (l->is_game() != is_game) { - throw precondition_failed(is_game ? u"$C6This command cannot\nbe used in lobbies." : u"$C6This command cannot\nbe used in games."); + throw precondition_failed(is_game ? "$C6This command cannot\nbe used in lobbies." : "$C6This command cannot\nbe used in games."); } } static void check_is_ep3(shared_ptr c, bool is_ep3) { if (!!(c->flags & Client::Flag::IS_EPISODE_3) != is_ep3) { - throw precondition_failed(is_ep3 ? u"$C6This command can only\nbe used in Episode 3." : u"$C6This command cannot\nbe used in Episode 3."); + throw precondition_failed(is_ep3 ? "$C6This command can only\nbe used in Episode 3." : "$C6This command cannot\nbe used in Episode 3."); } } static void check_cheats_enabled(shared_ptr s, shared_ptr l = nullptr) { if (s->cheat_mode_behavior == ServerState::CheatModeBehavior::OFF) { - throw precondition_failed(u"$C6Cheats are disabled."); + throw precondition_failed("$C6Cheats are disabled."); } if (l && !(l->flags & Lobby::Flag::CHEATS_ENABLED)) { - throw precondition_failed(u"$C6This command can\nonly be used in\ncheat mode."); + throw precondition_failed("$C6This command can\nonly be used in\ncheat mode."); } } static void check_is_leader(shared_ptr l, shared_ptr c) { if (l->leader_id != c->lobby_client_id) { - throw precondition_failed(u"$C6This command can\nonly be used by\nthe game leader."); + throw precondition_failed("$C6This command can\nonly be used by\nthe game leader."); } } //////////////////////////////////////////////////////////////////////////////// // Message commands -static void server_command_lobby_info(shared_ptr c, const std::u16string&) { +static void server_command_lobby_info(shared_ptr c, const std::string&) { vector lines; auto l = c->lobby.lock(); @@ -138,10 +138,10 @@ static void server_command_lobby_info(shared_ptr c, const std::u16string lines.emplace_back(std::move(slots_str)); } - send_text_message(c, decode_sjis(join(lines, "\n"))); + send_text_message(c, join(lines, "\n")); } -static void proxy_command_lobby_info(shared_ptr ses, const std::u16string&) { +static void proxy_command_lobby_info(shared_ptr ses, const std::string&) { string msg; // On non-masked-GC sessions (BB), there is no remote Guild Card number, so we // don't show it. (The user can see it in the pause menu, unlike in masked-GC @@ -202,41 +202,40 @@ static void proxy_command_lobby_info(shared_ptr ses, msg += name_for_section_id(ses->options.override_section_id); } - send_text_message(ses->client_channel, decode_sjis(msg)); + send_text_message(ses->client_channel, msg); } -static void server_command_ax(shared_ptr c, const std::u16string& args) { +static void server_command_ax(shared_ptr c, const std::string& args) { check_license_flags(c, License::Flag::ANNOUNCE); - string message = encode_sjis(args); - ax_messages_log.info("%s", message.c_str()); + ax_messages_log.info("%s", args.c_str()); } -static void server_command_announce(shared_ptr c, const std::u16string& args) { +static void server_command_announce(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); check_license_flags(c, License::Flag::ANNOUNCE); send_text_message(s, args); } -static void server_command_arrow(shared_ptr c, const std::u16string& args) { +static void server_command_arrow(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); - c->lobby_arrow_color = stoull(encode_sjis(args), nullptr, 0); + c->lobby_arrow_color = stoull(args, nullptr, 0); if (!l->is_game()) { send_arrow_update(l); } } -static void proxy_command_arrow(shared_ptr ses, const std::u16string& args) { - ses->server_channel.send(0x89, stoull(encode_sjis(args), nullptr, 0)); +static void proxy_command_arrow(shared_ptr ses, const std::string& args) { + ses->server_channel.send(0x89, stoull(args, nullptr, 0)); } -static void server_command_debug(shared_ptr c, const std::u16string&) { +static void server_command_debug(shared_ptr c, const std::string&) { check_license_flags(c, License::Flag::DEBUG); c->options.debug = !c->options.debug; send_text_message_printf(c, "Debug %s", c->options.debug ? "enabled" : "disabled"); } -static void server_command_show_material_counts(shared_ptr c, const std::u16string&) { +static void server_command_show_material_counts(shared_ptr c, const std::string&) { auto p = c->game_data.player(); send_text_message_printf(c, "%hhu HP, %hhu TP, %hhu POW\n%hhu MIND, %hhu EVADE\n%hhu DEF, %hhu LUCK", p->get_material_usage(SavedPlayerDataBB::MaterialType::HP), @@ -248,7 +247,7 @@ static void server_command_show_material_counts(shared_ptr c, const std: p->get_material_usage(SavedPlayerDataBB::MaterialType::LUCK)); } -static void server_command_auction(shared_ptr c, const std::u16string&) { +static void server_command_auction(shared_ptr c, const std::string&) { check_license_flags(c, License::Flag::DEBUG); auto l = c->require_lobby(); if (l->is_game() && l->is_ep3()) { @@ -258,16 +257,15 @@ static void server_command_auction(shared_ptr c, const std::u16string&) } } -static void proxy_command_auction(shared_ptr ses, const std::u16string&) { +static void proxy_command_auction(shared_ptr ses, const std::string&) { G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd; cmd.header.sender_client_id = ses->lobby_client_id; ses->client_channel.send(0xC9, 0x00, &cmd, sizeof(cmd)); ses->server_channel.send(0xC9, 0x00, &cmd, sizeof(cmd)); } -static void server_command_patch(shared_ptr c, const std::u16string& args) { - string basename = encode_sjis(args); - prepare_client_for_patches(c, [wc = weak_ptr(c), basename]() { +static void server_command_patch(shared_ptr c, const std::string& args) { + prepare_client_for_patches(c, [wc = weak_ptr(c), args]() { auto c = wc.lock(); if (!c) { return; @@ -277,21 +275,19 @@ static void server_command_patch(shared_ptr c, const std::u16string& arg // Note: We can't look this up outside of the closure because // c->specific_version can change during prepare_client_for_patches auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at( - string_printf("%s-%08" PRIX32, basename.c_str(), c->specific_version)); + string_printf("%s-%08" PRIX32, args.c_str(), c->specific_version)); send_function_call(c, fn); c->function_call_response_queue.emplace_back(empty_function_call_response_handler); } catch (const out_of_range&) { - send_text_message(c, u"Invalid patch name"); + send_text_message(c, "Invalid patch name"); } }); } static void empty_patch_return_handler(uint32_t, uint32_t) {} -static void proxy_command_patch(shared_ptr ses, const std::u16string& args) { - - string basename = encode_sjis(args); - auto send_call = [basename, ses](uint32_t specific_version, uint32_t) { +static void proxy_command_patch(shared_ptr ses, const std::string& args) { + auto send_call = [args, ses](uint32_t specific_version, uint32_t) { try { if (ses->newserv_client_config.cfg.specific_version != specific_version) { ses->newserv_client_config.cfg.specific_version = specific_version; @@ -299,18 +295,18 @@ static void proxy_command_patch(shared_ptr ses, cons } auto s = ses->require_server_state(); auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at( - string_printf("%s-%08" PRIX32, basename.c_str(), ses->newserv_client_config.cfg.specific_version)); + string_printf("%s-%08" PRIX32, args.c_str(), ses->newserv_client_config.cfg.specific_version)); send_function_call( ses->client_channel, ses->newserv_client_config.cfg.flags, fn); // Don't forward the patch response to the server ses->function_call_return_handler_queue.emplace_back(empty_patch_return_handler); } catch (const out_of_range&) { - send_text_message(ses->client_channel, u"Invalid patch name"); + send_text_message(ses->client_channel, "Invalid patch name"); } }; - auto send_version_detect_or_send_call = [basename, ses, send_call]() { - if (ses->version == GameVersion::GC && + auto send_version_detect_or_send_call = [args, ses, send_call]() { + if (ses->version() == GameVersion::GC && ses->newserv_client_config.cfg.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { auto s = ses->require_server_state(); send_function_call( @@ -341,11 +337,11 @@ static void proxy_command_patch(shared_ptr ses, cons } } -static void server_command_persist(shared_ptr c, const std::u16string&) { +static void server_command_persist(shared_ptr c, const std::string&) { check_license_flags(c, License::Flag::DEBUG); auto l = c->require_lobby(); if (l->flags & Lobby::Flag::DEFAULT) { - send_text_message(c, u"$C6Default lobbies\ncannot be marked\ntemporary"); + send_text_message(c, "$C6Default lobbies\ncannot be marked\ntemporary"); } else { l->flags ^= Lobby::Flag::PERSISTENT; send_text_message_printf(c, "Lobby persistence\n%s", @@ -353,7 +349,7 @@ static void server_command_persist(shared_ptr c, const std::u16string&) } } -static void server_command_exit(shared_ptr c, const std::u16string&) { +static void server_command_exit(shared_ptr c, const std::string&) { auto l = c->require_lobby(); if (l->is_game()) { if (c->flags & Client::Flag::IS_EPISODE_3) { @@ -363,12 +359,12 @@ static void server_command_exit(shared_ptr c, const std::u16string&) { c->channel.send(0x60, 0x00, cmd); c->area = 0; } else { - send_text_message(c, u"$C6You must return to\nthe lobby first"); + send_text_message(c, "$C6You must return to\nthe lobby first"); } } else { send_self_leave_notification(c); if (!(c->flags & Client::Flag::NO_D6)) { - send_message_box(c, u""); + send_message_box(c, ""); } const auto& port_name = version_to_login_port_name.at(static_cast(c->version())); @@ -377,7 +373,7 @@ static void server_command_exit(shared_ptr c, const std::u16string&) { } } -static void proxy_command_exit(shared_ptr ses, const std::u16string&) { +static void proxy_command_exit(shared_ptr ses, const std::string&) { if (ses->is_in_game) { if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) { ses->client_channel.send(0xED, 0x00); @@ -385,7 +381,7 @@ static void proxy_command_exit(shared_ptr ses, const G_UnusedHeader cmd = {0x73, 0x01, 0x0000}; ses->client_channel.send(0x60, 0x00, cmd); } else { - send_text_message(ses->client_channel, u"$C6You must return to\nthe lobby first"); + send_text_message(ses->client_channel, "$C6You must return to\nthe lobby first"); } } else { ses->disconnect_action = ProxyServer::LinkedSession::DisconnectAction::CLOSE_IMMEDIATELY; @@ -393,34 +389,33 @@ static void proxy_command_exit(shared_ptr ses, const } } -static void server_command_call(shared_ptr c, const std::u16string& args) { +static void server_command_call(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); if (l->is_game() && l->quest) { - send_quest_function_call(c, stoul(encode_sjis(args), nullptr, 0)); + send_quest_function_call(c, stoul(args, nullptr, 0)); } else { - send_text_message(c, u"$C6You must be in\nquest to use this\ncommand"); + send_text_message(c, "$C6You must be in\nquest to use this\ncommand"); } } -static void proxy_command_call(shared_ptr ses, const std::u16string& args) { +static void proxy_command_call(shared_ptr ses, const std::string& args) { if (ses->is_in_game && ses->is_in_quest) { - send_quest_function_call(ses->client_channel, stoul(encode_sjis(args), nullptr, 0)); + send_quest_function_call(ses->client_channel, stoul(args, nullptr, 0)); } else { - send_text_message(ses->client_channel, u"$C6You must be in\nquest to use this\ncommand"); + send_text_message(ses->client_channel, "$C6You must be in\nquest to use this\ncommand"); } } -static void server_command_get_self_card(shared_ptr c, const std::u16string&) { +static void server_command_get_self_card(shared_ptr c, const std::string&) { send_guild_card(c, c); } -static void proxy_command_get_player_card(shared_ptr ses, const std::u16string& u16args) { - string args = encode_sjis(u16args); +static void proxy_command_get_player_card(shared_ptr ses, const std::string& args) { bool any_card_sent = false; for (const auto& p : ses->lobby_players) { if (!p.name.empty() && args == p.name) { - send_guild_card(ses->client_channel, p.guild_card_number, decode_sjis(p.name), u"", u"", p.section_id, p.char_class); + send_guild_card(ses->client_channel, p.guild_card_number, p.name, "", "", p.language, p.section_id, p.char_class); any_card_sent = true; } } @@ -430,7 +425,7 @@ static void proxy_command_get_player_card(shared_ptr size_t index = stoull(args, nullptr, 0); const auto& p = ses->lobby_players.at(index); if (!p.name.empty()) { - send_guild_card(ses->client_channel, p.guild_card_number, decode_sjis(p.name), u"", u"", p.section_id, p.char_class); + send_guild_card(ses->client_channel, p.guild_card_number, p.name, "", "", p.language, p.section_id, p.char_class); } } catch (const exception& e) { send_text_message_printf(ses->client_channel, "Error: %s", e.what()); @@ -438,20 +433,20 @@ static void proxy_command_get_player_card(shared_ptr } } -static void server_command_send_client(shared_ptr c, const std::u16string& args) { - string data = parse_data_string(encode_sjis(args)); +static void server_command_send_client(shared_ptr c, const std::string& args) { + string data = parse_data_string(args); data.resize((data.size() + 3) & (~3)); c->channel.send(data); } -static void proxy_command_send_client(shared_ptr ses, const std::u16string& args) { - string data = parse_data_string(encode_sjis(args)); +static void proxy_command_send_client(shared_ptr ses, const std::string& args) { + string data = parse_data_string(args); data.resize((data.size() + 3) & (~3)); ses->client_channel.send(data); } -static void proxy_command_send_server(shared_ptr ses, const std::u16string& args) { - string data = parse_data_string(encode_sjis(args)); +static void proxy_command_send_server(shared_ptr ses, const std::string& args) { + string data = parse_data_string(args); data.resize((data.size() + 3) & (~3)); ses->server_channel.send(data); } @@ -459,20 +454,19 @@ static void proxy_command_send_server(shared_ptr ses //////////////////////////////////////////////////////////////////////////////// // Lobby commands -static void server_command_cheat(shared_ptr c, const std::u16string&) { +static void server_command_cheat(shared_ptr c, const std::string&) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); if (s->cheat_mode_behavior == ServerState::CheatModeBehavior::OFF) { - send_text_message(c, u"$C6Cheat mode cannot\nbe enabled on this\nserver"); + send_text_message(c, "$C6Cheat mode cannot\nbe enabled on this\nserver"); return; } l->flags ^= Lobby::Flag::CHEATS_ENABLED; - send_text_message_printf(l, "Cheat mode %s", - (l->flags & Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); + send_text_message_printf(l, "Cheat mode %s", (l->flags & Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); // If cheat mode was disabled, turn off all the cheat features that were on if (!(l->flags & Lobby::Flag::CHEATS_ENABLED)) { @@ -488,14 +482,14 @@ static void server_command_cheat(shared_ptr c, const std::u16string&) { } } -static void server_command_lobby_event(shared_ptr c, const std::u16string& args) { +static void server_command_lobby_event(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, false); check_license_flags(c, License::Flag::CHANGE_EVENT); uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { - send_text_message(c, u"$C6No such lobby event."); + send_text_message(c, "$C6No such lobby event."); return; } @@ -503,31 +497,31 @@ static void server_command_lobby_event(shared_ptr c, const std::u16strin send_change_event(l, l->event); } -static void proxy_command_lobby_event(shared_ptr ses, const std::u16string& args) { +static void proxy_command_lobby_event(shared_ptr ses, const std::string& args) { if (args.empty()) { ses->options.override_lobby_event = -1; } else { uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { - send_text_message(ses->client_channel, u"$C6No such lobby event."); + send_text_message(ses->client_channel, "$C6No such lobby event."); } else { ses->options.override_lobby_event = new_event; // This command is supported on all V3 versions except Ep1&2 Trial - if ((ses->version == GameVersion::GC && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || - (ses->version == GameVersion::XB) || - (ses->version == GameVersion::BB)) { + if ((ses->version() == GameVersion::GC && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || + (ses->version() == GameVersion::XB) || + (ses->version() == GameVersion::BB)) { ses->client_channel.send(0xDA, ses->options.override_lobby_event); } } } } -static void server_command_lobby_event_all(shared_ptr c, const std::u16string& args) { +static void server_command_lobby_event_all(shared_ptr c, const std::string& args) { check_license_flags(c, License::Flag::CHANGE_EVENT); uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { - send_text_message(c, u"$C6No such lobby event."); + send_text_message(c, "$C6No such lobby event."); return; } @@ -542,13 +536,13 @@ static void server_command_lobby_event_all(shared_ptr c, const std::u16s } } -static void server_command_lobby_type(shared_ptr c, const std::u16string& args) { +static void server_command_lobby_type(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, false); uint8_t new_type = args.empty() ? 0 : lobby_type_for_name(args); if (new_type == 0x80) { - send_text_message(c, u"$C6No such lobby type"); + send_text_message(c, "$C6No such lobby type"); return; } @@ -557,10 +551,10 @@ static void server_command_lobby_type(shared_ptr c, const std::u16string send_join_lobby(c, l); } -static void proxy_command_lobby_type(shared_ptr ses, const std::u16string& args) { +static void proxy_command_lobby_type(shared_ptr ses, const std::string& args) { uint8_t new_type = args.empty() ? 0 : lobby_type_for_name(args); if (new_type == 0x80) { - send_text_message(ses->client_channel, u"$C6No such lobby type"); + send_text_message(ses->client_channel, "$C6No such lobby type"); return; } @@ -568,31 +562,30 @@ static void proxy_command_lobby_type(shared_ptr ses, ses->options.override_lobby_number = (new_type < max_standard_type) ? -1 : new_type; } -static string file_path_for_recording(const std::u16string& args, uint32_t serial_number) { - string filename = encode_sjis(args); - for (char ch : filename) { +static string file_path_for_recording(const std::string& args, uint32_t serial_number) { + for (char ch : args) { if (ch <= 0x20 || ch > 0x7E || ch == '/') { throw runtime_error("invalid recording name"); } } - return string_printf("system/ep3/battle-records/%010" PRIu32 "_%s.mzrd", serial_number, filename.c_str()); + return string_printf("system/ep3/battle-records/%010" PRIu32 "_%s.mzrd", serial_number, args.c_str()); } -static void server_command_saverec(shared_ptr c, const std::u16string& args) { +static void server_command_saverec(shared_ptr c, const std::string& args) { if (!c->ep3_prev_battle_record) { - send_text_message(c, u"$C4No finished\nrecording is\npresent"); + send_text_message(c, "$C4No finished\nrecording is\npresent"); return; } string file_path = file_path_for_recording(args, c->license->serial_number); string data = c->ep3_prev_battle_record->serialize(); save_file(file_path, data); - send_text_message(c, u"$C7Recording saved"); + send_text_message(c, "$C7Recording saved"); c->ep3_prev_battle_record.reset(); } -static void server_command_playrec(shared_ptr c, const std::u16string& args) { +static void server_command_playrec(shared_ptr c, const std::string& args) { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { - send_text_message(c, u"$C4This command can\nonly be used on\nEpisode 3"); + send_text_message(c, "$C4This command can\nonly be used on\nEpisode 3"); return; } @@ -604,7 +597,7 @@ static void server_command_playrec(shared_ptr c, const std::u16string& a auto s = c->require_server_state(); uint32_t flags = Lobby::Flag::IS_SPECTATOR_TEAM; - string filename = encode_sjis(args); + string filename = args; if (filename[0] == '!') { flags |= Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY; filename = filename.substr(1); @@ -614,31 +607,31 @@ static void server_command_playrec(shared_ptr c, const std::u16string& a try { data = load_file(file_path); } catch (const cannot_open_file&) { - send_text_message(c, u"$C4The recording does\nnot exist"); + send_text_message(c, "$C4The recording does\nnot exist"); return; } shared_ptr record(new Episode3::BattleRecord(data)); shared_ptr battle_player( new Episode3::BattleRecordPlayer(record, s->game_server->get_base())); auto game = create_game_generic( - s, c, args, u"", Episode::EP3, GameMode::NORMAL, 0, flags, false, nullptr, battle_player); + s, c, args, "", Episode::EP3, GameMode::NORMAL, 0, flags, false, nullptr, battle_player); if (game) { s->change_client_lobby(c, game); c->flags |= Client::Flag::LOADING; } } else { - send_text_message(c, u"$C4This command cannot\nbe used in a game"); + send_text_message(c, "$C4This command cannot\nbe used in a game"); } } -static void server_command_meseta(shared_ptr c, const std::u16string& args) { +static void server_command_meseta(shared_ptr c, const std::string& args) { check_is_ep3(c, true); if (!c->options.debug) { - send_text_message(c, u"$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); + send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); return; } - uint32_t amount = stoul(encode_sjis(args), nullptr, 0); + uint32_t amount = stoul(args, nullptr, 0); c->license->ep3_current_meseta += amount; c->license->ep3_total_meseta_earned += amount; c->license->save(); @@ -649,17 +642,17 @@ static void server_command_meseta(shared_ptr c, const std::u16string& ar //////////////////////////////////////////////////////////////////////////////// // Game commands -static void server_command_secid(shared_ptr c, const std::u16string& args) { +static void server_command_secid(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, false); if (!args[0]) { c->options.override_section_id = -1; - send_text_message(c, u"$C6Override section ID\nremoved"); + send_text_message(c, "$C6Override section ID\nremoved"); } else { uint8_t new_secid = section_id_for_name(args); if (new_secid == 0xFF) { - send_text_message(c, u"$C6Invalid section ID"); + send_text_message(c, "$C6Invalid section ID"); } else { c->options.override_section_id = new_secid; string name = name_for_section_id(new_secid); @@ -668,63 +661,61 @@ static void server_command_secid(shared_ptr c, const std::u16string& arg } } -static void proxy_command_secid(shared_ptr ses, const std::u16string& args) { +static void proxy_command_secid(shared_ptr ses, const std::string& args) { if (!args[0]) { ses->options.override_section_id = -1; - send_text_message(ses->client_channel, u"$C6Override section ID\nremoved"); + send_text_message(ses->client_channel, "$C6Override section ID\nremoved"); } else { uint8_t new_secid = section_id_for_name(args); if (new_secid == 0xFF) { - send_text_message(ses->client_channel, u"$C6Invalid section ID"); + send_text_message(ses->client_channel, "$C6Invalid section ID"); } else { ses->options.override_section_id = new_secid; string name = name_for_section_id(new_secid); - send_text_message_printf(ses->client_channel, "$C6Override section ID\nset to %s", name.c_str()); + send_text_message(ses->client_channel, "$C6Override section ID\nset to " + name); } } } -static void server_command_rand(shared_ptr c, const std::u16string& args) { +static void server_command_rand(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, false); if (!args[0]) { c->options.override_random_seed = -1; - send_text_message(c, u"$C6Override seed\nremoved"); + send_text_message(c, "$C6Override seed\nremoved"); } else { - c->options.override_random_seed = stoul(encode_sjis(args), 0, 16); - send_text_message(c, u"$C6Override seed\nset"); + c->options.override_random_seed = stoul(args, 0, 16); + send_text_message(c, "$C6Override seed\nset"); } } -static void proxy_command_rand(shared_ptr ses, const std::u16string& args) { +static void proxy_command_rand(shared_ptr ses, const std::string& args) { if (!args[0]) { ses->options.override_random_seed = -1; - send_text_message(ses->client_channel, u"$C6Override seed\nremoved"); + send_text_message(ses->client_channel, "$C6Override seed\nremoved"); } else { - ses->options.override_random_seed = stoul(encode_sjis(args), 0, 16); - send_text_message(ses->client_channel, u"$C6Override seed\nset"); + ses->options.override_random_seed = stoul(args, 0, 16); + send_text_message(ses->client_channel, "$C6Override seed\nset"); } } -static void server_command_password(shared_ptr c, const std::u16string& args) { +static void server_command_password(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); if (!args[0]) { l->password[0] = 0; - send_text_message(l, u"$C6Game unlocked"); + send_text_message(l, "$C6Game unlocked"); } else { l->password = args; - auto encoded = encode_sjis(l->password); - send_text_message_printf(l, "$C6Game password:\n%s", - encoded.c_str()); + send_text_message_printf(l, "$C6Game password:\n%s", l->password.c_str()); } } -static void server_command_toggle_spectator_flag(shared_ptr c, const std::u16string&) { +static void server_command_toggle_spectator_flag(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); @@ -740,12 +731,12 @@ static void server_command_toggle_spectator_flag(shared_ptr c, const std } if (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { - send_text_message(c, u"$C6This command cannot\nbe used in a spectator\nteam"); + send_text_message(c, "$C6This command cannot\nbe used in a spectator\nteam"); } if (l->flags & Lobby::Flag::SPECTATORS_FORBIDDEN) { l->flags &= ~Lobby::Flag::SPECTATORS_FORBIDDEN; - send_text_message(l, u"$C6Spectators allowed"); + send_text_message(l, "$C6Spectators allowed"); } else { l->flags |= Lobby::Flag::SPECTATORS_FORBIDDEN; @@ -753,33 +744,31 @@ static void server_command_toggle_spectator_flag(shared_ptr c, const std send_command(watcher_l, 0xED, 0x00); } l->watcher_lobbies.clear(); - send_text_message(l, u"$C6Spectators forbidden"); + send_text_message(l, "$C6Spectators forbidden"); } } -static void server_command_min_level(shared_ptr c, const std::u16string& args) { +static void server_command_min_level(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); - u16string buffer; - l->min_level = stoull(encode_sjis(args)) - 1; - send_text_message_printf(l, "$C6Minimum level set to %" PRIu32, - l->min_level + 1); + l->min_level = stoull(args) - 1; + send_text_message_printf(l, "$C6Minimum level set to %" PRIu32, l->min_level + 1); } -static void server_command_max_level(shared_ptr c, const std::u16string& args) { +static void server_command_max_level(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); - l->max_level = stoull(encode_sjis(args)) - 1; + l->max_level = stoull(args) - 1; if (l->max_level >= 200) { l->max_level = 0xFFFFFFFF; } if (l->max_level == 0xFFFFFFFF) { - send_text_message(l, u"$C6Maximum level set to unlimited"); + send_text_message(l, "$C6Maximum level set to unlimited"); } else { send_text_message_printf(l, "$C6Maximum level set to %" PRIu32, l->max_level + 1); } @@ -788,13 +777,13 @@ static void server_command_max_level(shared_ptr c, const std::u16string& //////////////////////////////////////////////////////////////////////////////// // Character commands -static void server_command_edit(shared_ptr c, const std::u16string& args) { +static void server_command_edit(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, false); check_version(c, GameVersion::BB); - string encoded_args = tolower(encode_sjis(args)); + string encoded_args = tolower(args); vector tokens = split(encoded_args, ' '); try { @@ -824,23 +813,23 @@ static void server_command_edit(shared_ptr c, const std::u16string& args sscanf(tokens.at(1).c_str(), "%8X", &new_color); p->disp.visual.name_color = new_color; } else if (tokens.at(0) == "secid") { - uint8_t secid = section_id_for_name(decode_sjis(tokens.at(1))); + uint8_t secid = section_id_for_name(tokens.at(1)); if (secid == 0xFF) { - send_text_message(c, u"$C6No such section ID"); + send_text_message(c, "$C6No such section ID"); return; } else { p->disp.visual.section_id = secid; } } else if (tokens.at(0) == "name") { - p->disp.name = add_language_marker(tokens.at(1), 'J'); + p->disp.name.encode(tokens.at(1), p->inventory.language); } else if (tokens.at(0) == "npc") { if (tokens.at(1) == "none") { p->disp.visual.extra_model = 0; p->disp.visual.validation_flags &= 0xFD; } else { - uint8_t npc = npc_for_name(decode_sjis(tokens.at(1))); + uint8_t npc = npc_for_name(tokens.at(1)); if (npc == 0xFF) { - send_text_message(c, u"$C6No such NPC"); + send_text_message(c, "$C6No such NPC"); return; } p->disp.visual.extra_model = npc; @@ -853,24 +842,24 @@ static void server_command_edit(shared_ptr c, const std::u16string& args p->set_technique_level(x, level); } } else { - uint8_t tech_id = technique_for_name(decode_sjis(tokens.at(1))); + uint8_t tech_id = technique_for_name(tokens.at(1)); if (tech_id == 0xFF) { - send_text_message(c, u"$C6No such technique"); + send_text_message(c, "$C6No such technique"); return; } try { p->set_technique_level(tech_id, level); } catch (const out_of_range&) { - send_text_message(c, u"$C6Invalid technique"); + send_text_message(c, "$C6Invalid technique"); return; } } } else { - send_text_message(c, u"$C6Unknown field"); + send_text_message(c, "$C6Unknown field"); return; } } catch (const out_of_range&) { - send_text_message(c, u"$C6Not enough arguments"); + send_text_message(c, "$C6Not enough arguments"); return; } @@ -881,28 +870,28 @@ static void server_command_edit(shared_ptr c, const std::u16string& args } // TODO: implement this (and make sure the bank name is filesystem-safe) -/* static void server_command_change_bank(shared_ptr c, const std::u16string&) { +/* static void server_command_change_bank(shared_ptr c, const std::string&) { check_version(c, GameVersion::BB); ... } */ // TODO: This can be implemented on the proxy server too. -static void server_command_convert_char_to_bb(shared_ptr c, const std::u16string& args) { +static void server_command_convert_char_to_bb(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, false); check_not_version(c, GameVersion::BB); - vector tokens = split(encode_sjis(args), L' '); + vector tokens = split(args, ' '); if (tokens.size() != 3) { - send_text_message(c, u"$C6Incorrect argument count"); + send_text_message(c, "$C6Incorrect argument count"); return; } // username/password are tokens[0] and [1] c->pending_bb_save_player_index = stoul(tokens[2]) - 1; if (c->pending_bb_save_player_index > 3) { - send_text_message(c, u"$C6Player index must be 1-4"); + send_text_message(c, "$C6Player index must be 1-4"); return; } @@ -926,7 +915,7 @@ static void server_command_convert_char_to_bb(shared_ptr c, const std::u static string name_for_client(shared_ptr c) { auto player = c->game_data.player(false); if (player.get()) { - return encode_sjis(player->disp.name); + return player->disp.name.decode(player->inventory.language); } if (c->license.get()) { @@ -936,7 +925,7 @@ static string name_for_client(shared_ptr c) { return "Player"; } -static void server_command_silence(shared_ptr c, const std::u16string& args) { +static void server_command_silence(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_license_flags(c, License::Flag::SILENCE_USER); @@ -944,12 +933,12 @@ static void server_command_silence(shared_ptr c, const std::u16string& a auto target = s->find_client(&args); if (!target->license) { // this should be impossible, but I'll bet it's not actually - send_text_message(c, u"$C6Client not logged in"); + send_text_message(c, "$C6Client not logged in"); return; } if (target->license->flags & License::Flag::MODERATOR) { - send_text_message(c, u"$C6You do not have\nsufficient privileges."); + send_text_message(c, "$C6You do not have\nsufficient privileges."); return; } @@ -959,7 +948,7 @@ static void server_command_silence(shared_ptr c, const std::u16string& a target->can_chat ? "un" : ""); } -static void server_command_kick(shared_ptr c, const std::u16string& args) { +static void server_command_kick(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_license_flags(c, License::Flag::KICK_USER); @@ -967,47 +956,46 @@ static void server_command_kick(shared_ptr c, const std::u16string& args auto target = s->find_client(&args); if (!target->license) { // This should be impossible, but I'll bet it's not actually - send_text_message(c, u"$C6Client not logged in"); + send_text_message(c, "$C6Client not logged in"); return; } if (target->license->flags & License::Flag::MODERATOR) { - send_text_message(c, u"$C6You do not have\nsufficient privileges."); + send_text_message(c, "$C6You do not have\nsufficient privileges."); return; } - send_message_box(target, u"$C6You were kicked off by a moderator."); + send_message_box(target, "$C6You were kicked off by a moderator."); target->should_disconnect = true; string target_name = name_for_client(target); send_text_message_printf(l, "$C6%s kicked off", target_name.c_str()); } -static void server_command_ban(shared_ptr c, const std::u16string& args) { +static void server_command_ban(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_license_flags(c, License::Flag::BAN_USER); - u16string args_str(args); - size_t space_pos = args_str.find(L' '); + size_t space_pos = args.find(' '); if (space_pos == string::npos) { - send_text_message(c, u"$C6Incorrect argument count"); + send_text_message(c, "$C6Incorrect argument count"); return; } - u16string identifier = args_str.substr(space_pos + 1); + string identifier = args.substr(space_pos + 1); auto target = s->find_client(&identifier); if (!target->license) { // This should be impossible, but I'll bet it's not actually - send_text_message(c, u"$C6Client not logged in"); + send_text_message(c, "$C6Client not logged in"); return; } if (target->license->flags & License::Flag::BAN_USER) { - send_text_message(c, u"$C6You do not have\nsufficient privileges."); + send_text_message(c, "$C6You do not have\nsufficient privileges."); return; } - uint64_t usecs = stoull(encode_sjis(args), nullptr, 0) * 1000000; + uint64_t usecs = stoull(args, nullptr, 0) * 1000000; size_t unit_offset = 0; for (; isdigit(args[unit_offset]); unit_offset++) @@ -1028,7 +1016,7 @@ static void server_command_ban(shared_ptr c, const std::u16string& args) target->license->ban_end_time = now() + usecs; target->license->save(); - send_message_box(target, u"$C6You were banned by a moderator."); + send_message_box(target, "$C6You were banned by a moderator."); target->should_disconnect = true; string target_name = name_for_client(target); send_text_message_printf(l, "$C6%s banned", target_name.c_str()); @@ -1037,13 +1025,13 @@ static void server_command_ban(shared_ptr c, const std::u16string& args) //////////////////////////////////////////////////////////////////////////////// // Cheat commands -static void server_command_warp(shared_ptr c, const std::u16string& args, bool is_warpall) { +static void server_command_warp(shared_ptr c, const std::string& args, bool is_warpall) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); check_cheats_enabled(s, l); - uint32_t area = stoul(encode_sjis(args), nullptr, 0); + uint32_t area = stoul(args, nullptr, 0); if (c->area == area) { return; } @@ -1063,22 +1051,22 @@ static void server_command_warp(shared_ptr c, const std::u16string& args } } -static void server_command_warpme(shared_ptr c, const std::u16string& args) { +static void server_command_warpme(shared_ptr c, const std::string& args) { server_command_warp(c, args, false); } -static void server_command_warpall(shared_ptr c, const std::u16string& args) { +static void server_command_warpall(shared_ptr c, const std::string& args) { server_command_warp(c, args, true); } -static void proxy_command_warp(shared_ptr ses, const std::u16string& args, bool is_warpall) { +static void proxy_command_warp(shared_ptr ses, const std::string& args, bool is_warpall) { auto s = ses->require_server_state(); check_cheats_enabled(s); if (!ses->is_in_game) { - send_text_message(ses->client_channel, u"$C6You must be in a\ngame to use this\ncommand"); + send_text_message(ses->client_channel, "$C6You must be in a\ngame to use this\ncommand"); return; } - uint32_t area = stoul(encode_sjis(args), nullptr, 0); + uint32_t area = stoul(args, nullptr, 0); send_warp(ses->client_channel, ses->lobby_client_id, area, !is_warpall); if (is_warpall) { send_warp(ses->server_channel, ses->lobby_client_id, area, false); @@ -1086,15 +1074,15 @@ static void proxy_command_warp(shared_ptr ses, const ses->area = area; } -static void proxy_command_warpme(shared_ptr ses, const std::u16string& args) { +static void proxy_command_warpme(shared_ptr ses, const std::string& args) { proxy_command_warp(ses, args, false); } -static void proxy_command_warpall(shared_ptr ses, const std::u16string& args) { +static void proxy_command_warpall(shared_ptr ses, const std::string& args) { proxy_command_warp(ses, args, true); } -static void server_command_next(shared_ptr c, const std::u16string&) { +static void server_command_next(shared_ptr c, const std::string&) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); @@ -1107,11 +1095,11 @@ static void server_command_next(shared_ptr c, const std::u16string&) { send_warp(c, (c->area + 1) % limit, true); } -static void proxy_command_next(shared_ptr ses, const std::u16string&) { +static void proxy_command_next(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); check_cheats_enabled(s); if (!ses->is_in_game) { - send_text_message(ses->client_channel, u"$C6You must be in a\ngame to use this\ncommand"); + send_text_message(ses->client_channel, "$C6You must be in a\ngame to use this\ncommand"); return; } @@ -1119,7 +1107,7 @@ static void proxy_command_next(shared_ptr ses, const send_warp(ses->client_channel, ses->lobby_client_id, ses->area, true); } -static void server_command_what(shared_ptr c, const std::u16string&) { +static void server_command_what(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); @@ -1127,7 +1115,7 @@ static void server_command_what(shared_ptr c, const std::u16string&) { return; } if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { - send_text_message(c, u"$C4Item tracking is\nnot available"); + send_text_message(c, "$C4Item tracking is\nnot available"); } else { float min_dist2 = 0.0f; uint32_t nearest_item_id = 0xFFFFFFFF; @@ -1145,24 +1133,24 @@ static void server_command_what(shared_ptr c, const std::u16string&) { } if (nearest_item_id == 0xFFFFFFFF) { - send_text_message(c, u"$C4No items are near you"); + send_text_message(c, "$C4No items are near you"); } else { const auto& item = l->item_id_to_floor_item.at(nearest_item_id); string name = item.data.name(true); - send_text_message(c, decode_sjis(name)); + send_text_message(c, name); } } } -static void server_command_song(shared_ptr c, const std::u16string& args) { +static void server_command_song(shared_ptr c, const std::string& args) { check_is_ep3(c, true); - uint32_t song = stoul(encode_sjis(args), nullptr, 0); + uint32_t song = stoul(args, nullptr, 0); send_ep3_change_music(c->channel, song); } -static void proxy_command_song(shared_ptr ses, const std::u16string& args) { - int32_t song = stol(encode_sjis(args), nullptr, 0); +static void proxy_command_song(shared_ptr ses, const std::string& args) { + int32_t song = stol(args, nullptr, 0); if (song < 0) { song = -song; send_ep3_change_music(ses->server_channel, song); @@ -1170,26 +1158,24 @@ static void proxy_command_song(shared_ptr ses, const send_ep3_change_music(ses->client_channel, song); } -static void server_command_infinite_hp(shared_ptr c, const std::u16string&) { +static void server_command_infinite_hp(shared_ptr c, const std::string&) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); check_cheats_enabled(s, l); c->options.infinite_hp = !c->options.infinite_hp; - send_text_message_printf(c, "$C6Infinite HP %s", - c->options.infinite_hp ? "enabled" : "disabled"); + send_text_message_printf(c, "$C6Infinite HP %s", c->options.infinite_hp ? "enabled" : "disabled"); } -static void proxy_command_infinite_hp(shared_ptr ses, const std::u16string&) { +static void proxy_command_infinite_hp(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); check_cheats_enabled(s); ses->options.infinite_hp = !ses->options.infinite_hp; - send_text_message_printf(ses->client_channel, "$C6Infinite HP %s", - ses->options.infinite_hp ? "enabled" : "disabled"); + send_text_message_printf(ses->client_channel, "$C6Infinite HP %s", ses->options.infinite_hp ? "enabled" : "disabled"); } -static void server_command_infinite_tp(shared_ptr c, const std::u16string&) { +static void server_command_infinite_tp(shared_ptr c, const std::string&) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); @@ -1200,7 +1186,7 @@ static void server_command_infinite_tp(shared_ptr c, const std::u16strin c->options.infinite_tp ? "enabled" : "disabled"); } -static void proxy_command_infinite_tp(shared_ptr ses, const std::u16string&) { +static void proxy_command_infinite_tp(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); check_cheats_enabled(s); ses->options.infinite_tp = !ses->options.infinite_tp; @@ -1208,7 +1194,7 @@ static void proxy_command_infinite_tp(shared_ptr ses ses->options.infinite_tp ? "enabled" : "disabled"); } -static void server_command_switch_assist(shared_ptr c, const std::u16string&) { +static void server_command_switch_assist(shared_ptr c, const std::string&) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); @@ -1219,7 +1205,7 @@ static void server_command_switch_assist(shared_ptr c, const std::u16str c->options.switch_assist ? "enabled" : "disabled"); } -static void proxy_command_switch_assist(shared_ptr ses, const std::u16string&) { +static void proxy_command_switch_assist(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); check_cheats_enabled(s); ses->options.switch_assist = !ses->options.switch_assist; @@ -1227,7 +1213,7 @@ static void proxy_command_switch_assist(shared_ptr s ses->options.switch_assist ? "enabled" : "disabled"); } -static void server_command_drop(shared_ptr c, const std::u16string&) { +static void server_command_drop(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); @@ -1235,7 +1221,7 @@ static void server_command_drop(shared_ptr c, const std::u16string&) { send_text_message_printf(l, "Drops %s", (l->flags & Lobby::Flag::DROPS_ENABLED) ? "enabled" : "disabled"); } -static void server_command_raretable(shared_ptr c, const std::u16string&) { +static void server_command_raretable(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); @@ -1250,64 +1236,61 @@ static void server_command_raretable(shared_ptr c, const std::u16string& } } -static void server_command_item(shared_ptr c, const std::u16string& args) { +static void server_command_item(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); check_cheats_enabled(s, l); - ItemData item(encode_sjis(args)); + ItemData item(args); item.id = l->generate_item_id(c->lobby_client_id); l->add_item(item, c->area, c->x, c->z); send_drop_stacked_item(l, item, c->area, c->x, c->z); string name = item.name(true); - send_text_message(c, u"$C7Item created:\n" + decode_sjis(name)); + send_text_message(c, "$C7Item created:\n" + name); } -static void proxy_command_item(shared_ptr ses, const std::u16string& args) { +static void proxy_command_item(shared_ptr ses, const std::string& args) { auto s = ses->require_server_state(); check_cheats_enabled(s); - if (ses->version == GameVersion::BB) { - send_text_message(ses->client_channel, - u"$C6This command cannot\nbe used on the proxy\nserver in BB games"); + if (ses->version() == GameVersion::BB) { + send_text_message(ses->client_channel, "$C6This command cannot\nbe used on the proxy\nserver in BB games"); return; } if (!ses->is_in_game) { - send_text_message(ses->client_channel, - u"$C6You must be in\na game to use this\ncommand"); + send_text_message(ses->client_channel, "$C6You must be in\na game to use this\ncommand"); return; } if (ses->lobby_client_id != ses->leader_client_id) { - send_text_message(ses->client_channel, - u"$C6You must be the\nleader to use this\ncommand"); + send_text_message(ses->client_channel, "$C6You must be the\nleader to use this\ncommand"); return; } - bool set_drop = (!args.empty() && (args[0] == u'!')); + bool set_drop = (!args.empty() && (args[0] == '!')); - ItemData item(encode_sjis(set_drop ? args.substr(1) : args)); + ItemData item(set_drop ? args.substr(1) : args); item.id = random_object(); if (set_drop) { ses->next_drop_item = item; string name = ses->next_drop_item.name(true); - send_text_message(ses->client_channel, u"$C7Next drop:\n" + decode_sjis(name)); + send_text_message(ses->client_channel, "$C7Next drop:\n" + name); } else { send_drop_stacked_item(ses->client_channel, item, ses->area, ses->x, ses->z); send_drop_stacked_item(ses->server_channel, item, ses->area, ses->x, ses->z); string name = item.name(true); - send_text_message(ses->client_channel, u"$C7Item created:\n" + decode_sjis(name)); + send_text_message(ses->client_channel, "$C7Item created:\n" + name); } } -static void server_command_enable_ep3_battle_debug_menu(shared_ptr c, const std::u16string& args) { +static void server_command_enable_ep3_battle_debug_menu(shared_ptr c, const std::string& args) { if (!c->options.debug) { - send_text_message(c, u"$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); + send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); return; } @@ -1318,23 +1301,23 @@ static void server_command_enable_ep3_battle_debug_menu(shared_ptr c, co throw logic_error("non-Ep3 client in Ep3 game"); } if (!l->ep3_server) { - send_text_message(c, u"$C6Episode 3 server\nis not initialized"); + send_text_message(c, "$C6Episode 3 server\nis not initialized"); return; } if (!args.empty()) { - l->ep3_server->override_environment_number = stoul(encode_sjis(args), nullptr, 16); + l->ep3_server->override_environment_number = stoul(args, nullptr, 16); send_text_message_printf(l, "$C6Override environment\nnumber set to %02hhX", l->ep3_server->override_environment_number); } else if (l->ep3_server->override_environment_number == 0xFF) { l->ep3_server->override_environment_number = 0x1A; - send_text_message(l, u"$C6Battle setup debug\nmenu enabled"); + send_text_message(l, "$C6Battle setup debug\nmenu enabled"); } else { l->ep3_server->override_environment_number = 0xFF; - send_text_message(l, u"$C6Battle setup debug\nmenu disabled"); + send_text_message(l, "$C6Battle setup debug\nmenu disabled"); } } -static void server_command_ep3_infinite_time(shared_ptr c, const std::u16string&) { +static void server_command_ep3_infinite_time(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); @@ -1344,20 +1327,20 @@ static void server_command_ep3_infinite_time(shared_ptr c, const std::u1 throw logic_error("non-Ep3 client in Ep3 game"); } if (!l->ep3_server) { - send_text_message(c, u"$C6Episode 3 server\nis not initialized"); + send_text_message(c, "$C6Episode 3 server\nis not initialized"); return; } if (l->ep3_server->setup_phase != Episode3::SetupPhase::REGISTRATION) { - send_text_message(c, u"$C6Battle is already\nin progress"); + send_text_message(c, "$C6Battle is already\nin progress"); return; } l->ep3_server->options.behavior_flags ^= Episode3::BehaviorFlag::DISABLE_TIME_LIMITS; bool infinite_time_enabled = (l->ep3_server->options.behavior_flags & Episode3::BehaviorFlag::DISABLE_TIME_LIMITS); - send_text_message(l, infinite_time_enabled ? u"$C6Infinite time enabled" : u"$C6Infinite time disabled"); + send_text_message(l, infinite_time_enabled ? "$C6Infinite time enabled" : "$C6Infinite time disabled"); } -static void server_command_ep3_set_def_dice_range(shared_ptr c, const std::u16string& args) { +static void server_command_ep3_set_def_dice_range(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); @@ -1367,11 +1350,11 @@ static void server_command_ep3_set_def_dice_range(shared_ptr c, const st throw logic_error("non-Ep3 client in Ep3 game"); } if (!l->ep3_server) { - send_text_message(c, u"$C6Episode 3 server\nis not initialized"); + send_text_message(c, "$C6Episode 3 server\nis not initialized"); return; } if (l->ep3_server->setup_phase != Episode3::SetupPhase::REGISTRATION) { - send_text_message(c, u"$C6Battle is already\nin progress"); + send_text_message(c, "$C6Battle is already\nin progress"); return; } @@ -1380,7 +1363,7 @@ static void server_command_ep3_set_def_dice_range(shared_ptr c, const st send_text_message_printf(l, "$C6DEF dice range\nset to default"); } else { uint8_t min_dice, max_dice; - auto tokens = split(encode_sjis(args), '-'); + auto tokens = split(args, '-'); if (tokens.size() == 1) { min_dice = stoul(tokens[0]); max_dice = min_dice; @@ -1388,11 +1371,11 @@ static void server_command_ep3_set_def_dice_range(shared_ptr c, const st min_dice = stoul(tokens[0]); max_dice = stoul(tokens[1]); } else { - send_text_message(c, u"$C6Specify DEF dice\nrange as MIN-MAX"); + send_text_message(c, "$C6Specify DEF dice\nrange as MIN-MAX"); return; } if (min_dice == 0 || min_dice > 9 || max_dice == 0 || max_dice > 9) { - send_text_message(c, u"$C6DEF dice must be\nin range 1-9"); + send_text_message(c, "$C6DEF dice must be\nin range 1-9"); return; } if (min_dice > max_dice) { @@ -1405,7 +1388,7 @@ static void server_command_ep3_set_def_dice_range(shared_ptr c, const st } } -static void server_command_ep3_unset_field_character(shared_ptr c, const std::u16string& args) { +static void server_command_ep3_unset_field_character(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); check_is_game(l, true); @@ -1416,19 +1399,19 @@ static void server_command_ep3_unset_field_character(shared_ptr c, const throw logic_error("non-Ep3 client in Ep3 game"); } if (!l->ep3_server) { - send_text_message(c, u"$C6Episode 3 server\nis not initialized"); + send_text_message(c, "$C6Episode 3 server\nis not initialized"); return; } if (l->ep3_server->setup_phase != Episode3::SetupPhase::MAIN_BATTLE) { - send_text_message(c, u"$C6Battle has not\nyet begun"); + send_text_message(c, "$C6Battle has not\nyet begun"); return; } - size_t index = stoull(encode_sjis(args)) - 1; + size_t index = stoull(args) - 1; l->ep3_server->force_destroy_field_character(c->lobby_client_id, index); } -static void server_command_surrender(shared_ptr c, const std::u16string&) { +static void server_command_surrender(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); @@ -1436,14 +1419,14 @@ static void server_command_surrender(shared_ptr c, const std::u16string& throw logic_error("non-Ep3 client in Ep3 game"); } if (!l->ep3_server) { - send_text_message(c, u"$C6Episode 3 server\nis not initialized"); + send_text_message(c, "$C6Episode 3 server\nis not initialized"); return; } if (l->ep3_server->setup_phase != Episode3::SetupPhase::MAIN_BATTLE) { - send_text_message(c, u"$C6Battle has not\nyet started"); + send_text_message(c, "$C6Battle has not\nyet started"); return; } - string name = encode_sjis(c->game_data.player()->disp.name); + const string& name = c->game_data.player()->disp.name.decode(c->language()); send_text_message_printf(l, "$C6%s has\nsurrendered", name.c_str()); for (const auto& watcher_l : l->watcher_lobbies) { send_text_message_printf(watcher_l, "$C6%s has\nsurrendered", name.c_str()); @@ -1451,7 +1434,7 @@ static void server_command_surrender(shared_ptr c, const std::u16string& l->ep3_server->force_battle_result(c->lobby_client_id, false); } -static void server_command_get_ep3_battle_stat(shared_ptr c, const std::u16string& args) { +static void server_command_get_ep3_battle_stat(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); @@ -1459,11 +1442,11 @@ static void server_command_get_ep3_battle_stat(shared_ptr c, const std:: throw logic_error("non-Ep3 client in Ep3 game"); } if (!l->ep3_server) { - send_text_message(c, u"$C6Episode 3 server\nis not initialized"); + send_text_message(c, "$C6Episode 3 server\nis not initialized"); return; } if (l->ep3_server->setup_phase != Episode3::SetupPhase::MAIN_BATTLE) { - send_text_message(c, u"$C6Battle has not\nyet started"); + send_text_message(c, "$C6Battle has not\nyet started"); return; } if (c->lobby_client_id >= 4) { @@ -1471,7 +1454,7 @@ static void server_command_get_ep3_battle_stat(shared_ptr c, const std:: } auto ps = l->ep3_server->player_states[c->lobby_client_id]; if (!ps) { - send_text_message(c, u"$C6Player is missing"); + send_text_message(c, "$C6Player is missing"); return; } uint8_t team_id = ps->get_team_id(); @@ -1479,131 +1462,130 @@ static void server_command_get_ep3_battle_stat(shared_ptr c, const std:: throw logic_error("team ID is incorrect"); } - string what = encode_sjis(args); - if (what == "rank") { + if (args == "rank") { float score = ps->stats.score(l->ep3_server->get_round_num()); uint8_t rank = ps->stats.rank_for_score(score); const char* rank_name = ps->stats.name_for_rank(rank); send_text_message_printf(c, "$C7Score: %g\nRank: %hhu (%s)", score, rank, rank_name); - } else if (what == "duration") { + } else if (args == "duration") { string s = format_duration(now() - l->ep3_server->battle_start_usecs); send_text_message_printf(c, "$C7Duration: %s", s.c_str()); - } else if (what == "fcs-destroyed") { + } else if (args == "fcs-destroyed") { send_text_message_printf(c, "$C7Team FCs destroyed:\n%" PRIu32, l->ep3_server->team_num_ally_fcs_destroyed[team_id]); - } else if (what == "cards-destroyed") { + } else if (args == "cards-destroyed") { send_text_message_printf(c, "$C7Team cards destroyed:\n%" PRIu32, l->ep3_server->team_num_cards_destroyed[team_id]); - } else if (what == "damage-given") { + } else if (args == "damage-given") { send_text_message_printf(c, "$C7Damage given: %hu", ps->stats.damage_given.load()); - } else if (what == "damage-taken") { + } else if (args == "damage-taken") { send_text_message_printf(c, "$C7Damage taken: %hu", ps->stats.damage_taken.load()); - } else if (what == "opp-cards-destroyed") { + } else if (args == "opp-cards-destroyed") { send_text_message_printf(c, "$C7Opp. cards destroyed:\n%hu", ps->stats.num_opponent_cards_destroyed.load()); - } else if (what == "own-cards-destroyed") { + } else if (args == "own-cards-destroyed") { send_text_message_printf(c, "$C7Own cards destroyed:\n%hu", ps->stats.num_owned_cards_destroyed.load()); - } else if (what == "move-distance") { + } else if (args == "move-distance") { send_text_message_printf(c, "$C7Move distance: %hu", ps->stats.total_move_distance.load()); - } else if (what == "cards-set") { + } else if (args == "cards-set") { send_text_message_printf(c, "$C7Cards set: %hu", ps->stats.num_cards_set.load()); - } else if (what == "fcs-set") { + } else if (args == "fcs-set") { send_text_message_printf(c, "$C7FC cards set: %hu", ps->stats.num_item_or_creature_cards_set.load()); - } else if (what == "attack-actions-set") { + } else if (args == "attack-actions-set") { send_text_message_printf(c, "$C7Attack actions set:\n%hu", ps->stats.num_attack_actions_set.load()); - } else if (what == "techs-set") { + } else if (args == "techs-set") { send_text_message_printf(c, "$C7Techs set: %hu", ps->stats.num_tech_cards_set.load()); - } else if (what == "assists-set") { + } else if (args == "assists-set") { send_text_message_printf(c, "$C7Assists set: %hu", ps->stats.num_assist_cards_set.load()); - } else if (what == "defenses-self") { + } else if (args == "defenses-self") { send_text_message_printf(c, "$C7Defenses on self:\n%hu", ps->stats.defense_actions_set_on_self.load()); - } else if (what == "defenses-ally") { + } else if (args == "defenses-ally") { send_text_message_printf(c, "$C7Defenses on ally:\n%hu", ps->stats.defense_actions_set_on_ally.load()); - } else if (what == "cards-drawn") { + } else if (args == "cards-drawn") { send_text_message_printf(c, "$C7Cards drawn: %hu", ps->stats.num_cards_drawn.load()); - } else if (what == "max-attack-damage") { + } else if (args == "max-attack-damage") { send_text_message_printf(c, "$C7Maximum attack damage:\n%hu", ps->stats.max_attack_damage.load()); - } else if (what == "max-combo") { + } else if (args == "max-combo") { send_text_message_printf(c, "$C7Longest combo: %hu", ps->stats.max_attack_combo_size.load()); - } else if (what == "attacks-given") { + } else if (args == "attacks-given") { send_text_message_printf(c, "$C7Attacks given: %hu", ps->stats.num_attacks_given.load()); - } else if (what == "attacks-taken") { + } else if (args == "attacks-taken") { send_text_message_printf(c, "$C7Attacks taken: %hu", ps->stats.num_attacks_taken.load()); - } else if (what == "sc-damage") { + } else if (args == "sc-damage") { send_text_message_printf(c, "$C7SC damage taken: %hu", ps->stats.sc_damage_taken.load()); - } else if (what == "damage-defended") { + } else if (args == "damage-defended") { send_text_message_printf(c, "$C7Damage defended: %hu", ps->stats.action_card_negated_damage.load()); } else { - send_text_message(c, u"$C6Unknown statistic"); + send_text_message(c, "$C6Unknown statistic"); } } //////////////////////////////////////////////////////////////////////////////// -typedef void (*server_handler_t)(shared_ptr c, const std::u16string& args); -typedef void (*proxy_handler_t)(shared_ptr ses, const std::u16string& args); +typedef void (*server_handler_t)(shared_ptr c, const std::string& args); +typedef void (*proxy_handler_t)(shared_ptr ses, const std::string& args); struct ChatCommandDefinition { server_handler_t server_handler; proxy_handler_t proxy_handler; }; -static const unordered_map chat_commands({ - {u"$allevent", {server_command_lobby_event_all, nullptr}}, - {u"$ann", {server_command_announce, nullptr}}, - {u"$arrow", {server_command_arrow, proxy_command_arrow}}, - {u"$auction", {server_command_auction, proxy_command_auction}}, - {u"$ax", {server_command_ax, nullptr}}, - {u"$ban", {server_command_ban, nullptr}}, - {u"$bbchar", {server_command_convert_char_to_bb, nullptr}}, - {u"$call", {server_command_call, proxy_command_call}}, - {u"$cheat", {server_command_cheat, nullptr}}, - {u"$debug", {server_command_debug, nullptr}}, - {u"$defrange", {server_command_ep3_set_def_dice_range, nullptr}}, - {u"$drop", {server_command_drop, nullptr}}, - {u"$edit", {server_command_edit, nullptr}}, - {u"$event", {server_command_lobby_event, proxy_command_lobby_event}}, - {u"$exit", {server_command_exit, proxy_command_exit}}, - {u"$gc", {server_command_get_self_card, proxy_command_get_player_card}}, - {u"$infhp", {server_command_infinite_hp, proxy_command_infinite_hp}}, - {u"$inftime", {server_command_ep3_infinite_time, nullptr}}, - {u"$inftp", {server_command_infinite_tp, proxy_command_infinite_tp}}, - {u"$item", {server_command_item, proxy_command_item}}, - {u"$i", {server_command_item, proxy_command_item}}, - {u"$kick", {server_command_kick, nullptr}}, - {u"$li", {server_command_lobby_info, proxy_command_lobby_info}}, - {u"$ln", {server_command_lobby_type, proxy_command_lobby_type}}, - {u"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}}, - {u"$matcount", {server_command_show_material_counts, nullptr}}, - {u"$maxlevel", {server_command_max_level, nullptr}}, - {u"$meseta", {server_command_meseta, nullptr}}, - {u"$minlevel", {server_command_min_level, nullptr}}, - {u"$next", {server_command_next, proxy_command_next}}, - {u"$password", {server_command_password, nullptr}}, - {u"$patch", {server_command_patch, proxy_command_patch}}, - {u"$persist", {server_command_persist, nullptr}}, - {u"$playrec", {server_command_playrec, nullptr}}, - {u"$rand", {server_command_rand, proxy_command_rand}}, - {u"$raretable", {server_command_raretable, nullptr}}, - {u"$saverec", {server_command_saverec, nullptr}}, - {u"$sc", {server_command_send_client, proxy_command_send_client}}, - {u"$secid", {server_command_secid, proxy_command_secid}}, - {u"$silence", {server_command_silence, nullptr}}, - {u"$song", {server_command_song, proxy_command_song}}, - {u"$spec", {server_command_toggle_spectator_flag, nullptr}}, - {u"$ss", {nullptr, proxy_command_send_server}}, - {u"$stat", {server_command_get_ep3_battle_stat, nullptr}}, - {u"$surrender", {server_command_surrender, nullptr}}, - {u"$swa", {server_command_switch_assist, proxy_command_switch_assist}}, - {u"$unset", {server_command_ep3_unset_field_character, nullptr}}, - {u"$warp", {server_command_warpme, proxy_command_warpme}}, - {u"$warpme", {server_command_warpme, proxy_command_warpme}}, - {u"$warpall", {server_command_warpall, proxy_command_warpall}}, - {u"$what", {server_command_what, nullptr}}, +static const unordered_map chat_commands({ + {"$allevent", {server_command_lobby_event_all, nullptr}}, + {"$ann", {server_command_announce, nullptr}}, + {"$arrow", {server_command_arrow, proxy_command_arrow}}, + {"$auction", {server_command_auction, proxy_command_auction}}, + {"$ax", {server_command_ax, nullptr}}, + {"$ban", {server_command_ban, nullptr}}, + {"$bbchar", {server_command_convert_char_to_bb, nullptr}}, + {"$call", {server_command_call, proxy_command_call}}, + {"$cheat", {server_command_cheat, nullptr}}, + {"$debug", {server_command_debug, nullptr}}, + {"$defrange", {server_command_ep3_set_def_dice_range, nullptr}}, + {"$drop", {server_command_drop, nullptr}}, + {"$edit", {server_command_edit, nullptr}}, + {"$event", {server_command_lobby_event, proxy_command_lobby_event}}, + {"$exit", {server_command_exit, proxy_command_exit}}, + {"$gc", {server_command_get_self_card, proxy_command_get_player_card}}, + {"$infhp", {server_command_infinite_hp, proxy_command_infinite_hp}}, + {"$inftime", {server_command_ep3_infinite_time, nullptr}}, + {"$inftp", {server_command_infinite_tp, proxy_command_infinite_tp}}, + {"$item", {server_command_item, proxy_command_item}}, + {"$i", {server_command_item, proxy_command_item}}, + {"$kick", {server_command_kick, nullptr}}, + {"$li", {server_command_lobby_info, proxy_command_lobby_info}}, + {"$ln", {server_command_lobby_type, proxy_command_lobby_type}}, + {"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}}, + {"$matcount", {server_command_show_material_counts, nullptr}}, + {"$maxlevel", {server_command_max_level, nullptr}}, + {"$meseta", {server_command_meseta, nullptr}}, + {"$minlevel", {server_command_min_level, nullptr}}, + {"$next", {server_command_next, proxy_command_next}}, + {"$password", {server_command_password, nullptr}}, + {"$patch", {server_command_patch, proxy_command_patch}}, + {"$persist", {server_command_persist, nullptr}}, + {"$playrec", {server_command_playrec, nullptr}}, + {"$rand", {server_command_rand, proxy_command_rand}}, + {"$raretable", {server_command_raretable, nullptr}}, + {"$saverec", {server_command_saverec, nullptr}}, + {"$sc", {server_command_send_client, proxy_command_send_client}}, + {"$secid", {server_command_secid, proxy_command_secid}}, + {"$silence", {server_command_silence, nullptr}}, + {"$song", {server_command_song, proxy_command_song}}, + {"$spec", {server_command_toggle_spectator_flag, nullptr}}, + {"$ss", {nullptr, proxy_command_send_server}}, + {"$stat", {server_command_get_ep3_battle_stat, nullptr}}, + {"$surrender", {server_command_surrender, nullptr}}, + {"$swa", {server_command_switch_assist, proxy_command_switch_assist}}, + {"$unset", {server_command_ep3_unset_field_character, nullptr}}, + {"$warp", {server_command_warpme, proxy_command_warpme}}, + {"$warpme", {server_command_warpme, proxy_command_warpme}}, + {"$warpall", {server_command_warpall, proxy_command_warpall}}, + {"$what", {server_command_what, nullptr}}, }); struct SplitCommand { - u16string name; - u16string args; + string name; + string args; - SplitCommand(const u16string& text) { - size_t space_pos = text.find(u' '); + SplitCommand(const string& text) { + size_t space_pos = text.find(' '); if (space_pos != string::npos) { this->name = text.substr(0, space_pos); this->args = text.substr(space_pos + 1); @@ -1616,19 +1598,19 @@ struct SplitCommand { // This function is called every time any player sends a chat beginning with a // dollar sign. It is this function's responsibility to see if the chat is a // command, and to execute the command and block the chat if it is. -void on_chat_command(std::shared_ptr c, const std::u16string& text) { +void on_chat_command(std::shared_ptr c, const std::string& text) { SplitCommand cmd(text); const ChatCommandDefinition* def = nullptr; try { def = &chat_commands.at(cmd.name); } catch (const out_of_range&) { - send_text_message(c, u"$C6Unknown command"); + send_text_message(c, "$C6Unknown command"); return; } if (!def->server_handler) { - send_text_message(c, u"$C6Command not available\non game server"); + send_text_message(c, "$C6Command not available\non game server"); } else { try { def->server_handler(c, cmd.args); @@ -1640,19 +1622,19 @@ void on_chat_command(std::shared_ptr c, const std::u16string& text) { } } -void on_chat_command(shared_ptr ses, const std::u16string& text) { +void on_chat_command(shared_ptr ses, const std::string& text) { SplitCommand cmd(text); const ChatCommandDefinition* def = nullptr; try { def = &chat_commands.at(cmd.name); } catch (const out_of_range&) { - send_text_message(ses->client_channel, u"$C6Unknown command"); + send_text_message(ses->client_channel, "$C6Unknown command"); return; } if (!def->proxy_handler) { - send_text_message(ses->client_channel, u"$C6Command not available\non proxy server"); + send_text_message(ses->client_channel, "$C6Command not available\non proxy server"); } else { try { def->proxy_handler(ses, cmd.args); diff --git a/src/ChatCommands.hh b/src/ChatCommands.hh index 8b1f9504..dd13c8ad 100644 --- a/src/ChatCommands.hh +++ b/src/ChatCommands.hh @@ -10,5 +10,5 @@ #include "ProxyServer.hh" #include "ServerState.hh" -void on_chat_command(std::shared_ptr c, const std::u16string& text); -void on_chat_command(std::shared_ptr ses, const std::u16string& text); +void on_chat_command(std::shared_ptr c, const std::string& text); +void on_chat_command(std::shared_ptr ses, const std::string& text); diff --git a/src/Client.cc b/src/Client.cc index 83f67ce1..4fcde953 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -54,14 +54,13 @@ Client::Client( bb_game_state(0), flags(flags_for_version(version, -1)), specific_version(default_specific_version_for_version(version, -1)), - channel(bev, version, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN), + channel(bev, version, 1, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN), server_behavior(server_behavior), should_disconnect(false), should_send_to_lobby_server(false), should_send_to_proxy_server(false), proxy_destination_address(0), proxy_destination_port(0), - language(1), x(0.0f), z(0.0f), area(0), diff --git a/src/Client.hh b/src/Client.hh index a76bb871..0a0d20e3 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -150,7 +150,6 @@ struct Client : public std::enable_shared_from_this { // Lobby/positioning ClientOptions options; - uint8_t language; float x; float z; uint32_t area; @@ -195,6 +194,9 @@ struct Client : public std::enable_shared_from_this { inline GameVersion version() const { return this->channel.version; } + inline uint8_t language() const { + return this->channel.language; + } QuestScriptVersion quest_version() const; void set_license(std::shared_ptr l); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index aa8aa57a..91917367 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -19,9 +19,13 @@ // For the unfamiliar, the le_uint and be_uint types (from phosg/Encoding.hh) // are the same as normal uint types, but are explicitly little-endian or -// big-endian. The parray and ptext types (from Text.hh) are the same as -// standard arrays, but have various safety and convenience features so we don't -// have to use easy-to-mess-up functions like memset/memcpy and strncpy. +// big-endian. The parray type (from Text.hh) is the same as a standard array, +// but has various safety and convenience features so we don't have to use +// easy-to-mess-up functions like memset/memcpy and strncpy. The pstring types +// (also from Text.hh) are like std::strings, but have an explicit encoding. +// They can be implicitly converted to and from std::strings, and will encode +// or decode their specified encoding when doing so. (The default encoding is +// UTF-8 everywhere in the server code.) // Struct names are like [S|C|SC]_CommandName_[Versions]_Numbers // S/C denotes who sends the command (S = server, C = client, SC = both) @@ -179,7 +183,7 @@ struct ClientConfigBB { // "Patch Server. Copyright SonicTeam, LTD. 2001" struct S_ServerInit_Patch_02 { - ptext copyright; + pstring copyright; le_uint32_t server_key = 0; // Key for commands sent by server le_uint32_t client_key = 0; // Key for commands sent by client // The client rejects the command if it's larger than this size, so we can't @@ -201,9 +205,9 @@ struct S_ServerInit_Patch_02 { struct C_Login_Patch_04 { parray unused; - ptext username; - ptext password; - ptext email; + pstring username; + pstring password; + pstring email; } __packed__; // 05 (S->C): Disconnect @@ -216,7 +220,7 @@ struct C_Login_Patch_04 { struct S_OpenFile_Patch_06 { le_uint32_t unknown_a1 = 0; le_uint32_t size = 0; - ptext filename; + pstring filename; } __packed__; // 07 (S->C): Write file @@ -246,7 +250,7 @@ struct S_CloseCurrentFile_Patch_08 { // 09 (S->C): Enter directory struct S_EnterDirectory_Patch_09 { - ptext name; + pstring name; } __packed__; // 0A (S->C): Exit directory @@ -259,7 +263,7 @@ struct S_EnterDirectory_Patch_09 { struct S_FileChecksumRequest_Patch_0C { le_uint32_t request_id = 0; - ptext filename; + pstring filename; } __packed__; // 0D (S->C): End of file checksum requests @@ -338,7 +342,7 @@ struct S_Reconnect_Patch_14 : S_Reconnect { struct SC_TextHeader_01_06_11_B0_EE { le_uint32_t unused = 0; le_uint32_t guild_card_number = 0; - // Text immediately follows here (char[] on DC/V3, char16_t[] on PC/BB) + // Text immediately follows here } __packed__; // 02 (S->C): Start encryption (except on BB) @@ -359,7 +363,7 @@ struct SC_TextHeader_01_06_11_B0_EE { // those versions that don't run on the DreamCast.) struct S_ServerInitDefault_DC_PC_V3_02_17_91_9B { - ptext copyright; + pstring copyright; le_uint32_t server_key = 0; // Key for data sent by server le_uint32_t client_key = 0; // Key for data sent by client } __packed__; @@ -369,7 +373,7 @@ struct S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B { S_ServerInitDefault_DC_PC_V3_02_17_91_9B basic_cmd; // This field is not part of SEGA's implementation; the client ignores it. // newserv sends a message here disavowing the preceding copyright notice. - ptext after_message; + pstring after_message; } __packed__; // 03 (C->S): Legacy register (non-BB) @@ -385,8 +389,8 @@ struct C_LegacyLogin_PC_V3_03 { // as the corresponding fields in 9D/9E. (Even though serial_number and // serial_number2 have the same contents in 9E, they do not come from the same // field on the client's connection context object.) - ptext serial_number2; - ptext access_key2; + pstring serial_number2; + pstring access_key2; } __packed__; // 03 (S->C): Legacy register result (non-BB) @@ -409,7 +413,7 @@ struct C_LegacyLogin_PC_V3_03 { // "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM." struct S_ServerInitDefault_BB_03_9B { - ptext copyright; + pstring copyright; parray server_key; parray client_key; } __packed__; @@ -418,7 +422,7 @@ template struct S_ServerInitWithAfterMessage_BB_03_9B { S_ServerInitDefault_BB_03_9B basic_cmd; // As in 02, this field is not part of SEGA's implementation. - ptext after_message; + pstring after_message; } __packed__; // 04 (C->S): Legacy login @@ -435,14 +439,14 @@ struct C_LegacyLogin_PC_V3_04 { uint8_t is_extended = 0; uint8_t language = 0; le_uint16_t unknown_a2 = 0; - ptext serial_number; - ptext access_key; + pstring serial_number; + pstring access_key; } __packed__; struct C_LegacyLogin_BB_04 { parray unknown_a1; - ptext username; - ptext password; + pstring username; + pstring password; } __packed__; // 04 (S->C): Set guild card number and update client config ("security data") @@ -508,13 +512,12 @@ struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig { // 06: Chat // Internal name: RcvChat and SndChat -// Server->client format is same as 01 command. The maximum size of the message -// is 0x200 bytes. -// Client->server format is very similar; we include a zero-length array in this -// struct to make parsing easier. +// Server->client format is the same as the 01 command; guild_card_number +// is unused and set to zero. The maximum size of the message is 0x200 bytes. +// Client->server format is the same as the 01 command also. // When sent by the client, the text field includes only the message. When sent // by the server, the text field includes the origin player's name, followed by -// a tab character, followed by the message. +// a tab character (\x09), followed by the message. // During Episode 3 battles, the first byte of an inbound 06 command's message // is interpreted differently. It should be treated as a bit field, with the low // 4 bits intended as masks for who can see the message. If the low bit (1) is @@ -526,14 +529,6 @@ struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig { // the chat string accidentally.) We call this byte private_flags in the places // where newserv uses it. -struct C_Chat_06 { - parray unused; - union { - char dcv3[0]; - char16_t pcbb[0]; - } __packed__ text; -} __packed__; - // 07 (S->C): Ship select menu // Internal name: RcvDirList // This command triggers a general form of blocking menu, which was used for @@ -547,16 +542,16 @@ struct C_Chat_06 { // Command is a list of these; header.flag is the entry count. The first entry // is not included in the count and does not appear on the client. The text of // the first entry becomes the ship name when the client joins a lobby. -template +template struct S_MenuEntry { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; le_uint16_t flags = 0x0F04; // Should be this value, apparently - ptext text; + pstring text; } __packed__; -struct S_MenuEntry_PC_BB_07_1F : S_MenuEntry { +struct S_MenuEntry_PC_BB_07_1F : S_MenuEntry { } __packed__; -struct S_MenuEntry_DC_V3_07_1F : S_MenuEntry { +struct S_MenuEntry_DC_V3_07_1F : S_MenuEntry { } __packed__; // 08 (C->S): Request game list @@ -569,7 +564,7 @@ struct S_MenuEntry_DC_V3_07_1F : S_MenuEntry { // Command is a list of these; header.flag is the entry count. The first entry // is not included in the count and does not appear on the client. -template +template struct S_GameMenuEntry { le_uint32_t menu_id = 0; le_uint32_t game_id = 0; @@ -577,7 +572,7 @@ struct S_GameMenuEntry { // difficulty + 0x22 (so 0x25 means Ultimate, for example) uint8_t difficulty_tag = 0; uint8_t num_players = 0; - ptext name; + pstring name; // The episode field is used differently by different versions: // - On DCv1, PC, and GC Episode 3, the value is ignored. // - On DCv2, 1 means v1 players can't join the game, and 0 means they can. @@ -593,9 +588,9 @@ struct S_GameMenuEntry { // 20 = Is challenge mode uint8_t flags = 0; } __packed__; -struct S_GameMenuEntry_PC_BB_08 : S_GameMenuEntry { +struct S_GameMenuEntry_PC_BB_08 : S_GameMenuEntry { } __packed__; -struct S_GameMenuEntry_DC_V3_08_Ep3_E6 : S_GameMenuEntry { +struct S_GameMenuEntry_DC_V3_08_Ep3_E6 : S_GameMenuEntry { } __packed__; // 09 (C->S): Menu item info request @@ -634,7 +629,7 @@ struct C_MenuItemInfoRequest_09 { // command does not softlock, but instead does nothing because the 0E // second-phase handler is missing. -template +template struct SC_MeetUserExtension { struct LobbyReference { le_uint32_t menu_id = 0; @@ -642,14 +637,14 @@ struct SC_MeetUserExtension { } __packed__; parray lobby_refs; le_uint32_t unknown_a2 = 0; - ptext player_name; + pstring player_name; } __packed__; struct S_LegacyJoinGame_PC_0E { struct LobbyData { le_uint32_t player_tag; le_uint32_t guild_card_number; - parray name; + pstring name; } __packed__; parray lobby_data; parray unknown_a3; @@ -657,7 +652,7 @@ struct S_LegacyJoinGame_PC_0E { struct S_LegacyJoinGame_GC_0E { parray lobby_data; - SC_MeetUserExtension meet_user_extension; + SC_MeetUserExtension meet_user_extension; parray unknown_a3; } __packed__; @@ -665,10 +660,10 @@ struct S_LegacyJoinGame_XB_0E { struct LobbyData { le_uint32_t player_tag; le_uint32_t guild_card_number; - parray name; + pstring name; } __packed__; parray lobby_data; - SC_MeetUserExtension meet_user_extension; + SC_MeetUserExtension meet_user_extension; parray unknown_a3; } __packed__; @@ -689,35 +684,35 @@ struct C_MenuSelection_10_Flag00 { le_uint32_t item_id = 0; } __packed__; -template +template struct C_MenuSelection_10_Flag01 { C_MenuSelection_10_Flag00 basic_cmd; - ptext unknown_a1; + pstring unknown_a1; } __packed__; -struct C_MenuSelection_DC_V3_10_Flag01 : C_MenuSelection_10_Flag01 { +struct C_MenuSelection_DC_V3_10_Flag01 : C_MenuSelection_10_Flag01 { } __packed__; -struct C_MenuSelection_PC_BB_10_Flag01 : C_MenuSelection_10_Flag01 { +struct C_MenuSelection_PC_BB_10_Flag01 : C_MenuSelection_10_Flag01 { } __packed__; -template +template struct C_MenuSelection_10_Flag02 { C_MenuSelection_10_Flag00 basic_cmd; - ptext password; + pstring password; } __packed__; -struct C_MenuSelection_DC_V3_10_Flag02 : C_MenuSelection_10_Flag02 { +struct C_MenuSelection_DC_V3_10_Flag02 : C_MenuSelection_10_Flag02 { } __packed__; -struct C_MenuSelection_PC_BB_10_Flag02 : C_MenuSelection_10_Flag02 { +struct C_MenuSelection_PC_BB_10_Flag02 : C_MenuSelection_10_Flag02 { } __packed__; -template +template struct C_MenuSelection_10_Flag03 { C_MenuSelection_10_Flag00 basic_cmd; - ptext unknown_a1; - ptext password; + pstring unknown_a1; + pstring password; } __packed__; -struct C_MenuSelection_DC_V3_10_Flag03 : C_MenuSelection_10_Flag03 { +struct C_MenuSelection_DC_V3_10_Flag03 : C_MenuSelection_10_Flag03 { } __packed__; -struct C_MenuSelection_PC_BB_10_Flag03 : C_MenuSelection_10_Flag03 { +struct C_MenuSelection_PC_BB_10_Flag03 : C_MenuSelection_10_Flag03 { } __packed__; // 11 (S->C): Ship info @@ -744,7 +739,7 @@ struct C_MenuSelection_PC_BB_10_Flag03 : C_MenuSelection_10_Flag03 { // header.flag = file chunk index (start offset / 0x400) struct S_WriteFile_13_A7 { - ptext filename; + pstring filename; parray data; le_uint32_t data_size = 0; } __packed__; @@ -755,7 +750,7 @@ struct S_WriteFile_13_A7 { // header.flag = file chunk index (same as in the 13/A7 sent by the server) struct C_WriteFileConfirmation_V3_BB_13_A7 { - ptext filename; + pstring filename; } __packed__; // 14 (S->C): Valid but ignored (all versions) @@ -818,11 +813,11 @@ struct S_ReconnectSplit_19 { // Internal name: RcvText // On V3, client will sometimes respond with a D6 command (see D6 for more // information). -// Contents are plain text (char on DC/V3, char16_t on PC/BB). There must be at -// least one null character ('\0') before the end of the command data. -// There is a bug in V3 (and possibly all versions) where if this command is -// sent after the client has joined a lobby, the chat log window contents will -// appear in the message box, prepended to the message text from the command. +// Contents are plain text. There must be at least one null character ('\0') +// before the end of the command data. There is a bug in V3 (and possibly all +// versions) where if this command is sent after the client has joined a lobby, +// the chat log window contents will appear in the message box, prepended to +// the message text from the command. // The maximum length of the message is 0x400 bytes. This is the only // difference between this command and the D5 command. @@ -950,7 +945,7 @@ struct C_GuildCardSearch_40 { // 41 (S->C): Guild card search result // Internal name: RcvUserAns -template +template struct S_GuildCardSearchResult { le_uint32_t player_tag = 0x00010000; le_uint32_t searcher_guild_card_number = 0; @@ -961,21 +956,21 @@ struct S_GuildCardSearchResult { // player is not in a game, GAME-NAME should be the lobby name - for standard // lobbies this is "BLOCK-"; for CARD lobbies this is // "BLOCK-C". - ptext location_string; + pstring location_string; // If the player chooses to meet the user, this extension data is sent in the // login command (9D/9E) after connecting to the server designated in // reconnect_command. When processing the 9D/9E, newserv uses only the - // lobby_id field within, but it fills in all fields when sengind a 41. - SC_MeetUserExtension extension; + // lobby_id field within, but it fills in all fields when sending a 41. + SC_MeetUserExtension extension; } __packed__; struct S_GuildCardSearchResult_PC_41 - : S_GuildCardSearchResult { + : S_GuildCardSearchResult { } __packed__; struct S_GuildCardSearchResult_DC_V3_41 - : S_GuildCardSearchResult { + : S_GuildCardSearchResult { } __packed__; struct S_GuildCardSearchResult_BB_41 - : S_GuildCardSearchResult { + : S_GuildCardSearchResult { } __packed__; // 42: Invalid command @@ -992,7 +987,7 @@ struct S_GuildCardSearchResult_BB_41 // though it doesn't make sense for an XB client to receive such a file. struct S_OpenFile_DC_44_A6 { - ptext name; // Should begin with "PSO/" + pstring name; // Should begin with "PSO/" // The type field is only used for download quests (A6); it is ignored for // online quests (44). The following values are valid for A6: // 0 = download quest (client expects .bin and .dat files) @@ -1005,14 +1000,14 @@ struct S_OpenFile_DC_44_A6 { // but I haven't verified this. Generally the server should send all files for // a given piece of content with the same type in each file's A6 command. uint8_t type = 0; - ptext filename; + pstring filename; le_uint32_t file_size = 0; } __packed__; struct S_OpenFile_PC_GC_44_A6 { - ptext name; // Should begin with "PSO/" + pstring name; // Should begin with "PSO/" le_uint16_t type = 0; - ptext filename; + pstring filename; le_uint32_t file_size = 0; } __packed__; @@ -1026,9 +1021,9 @@ struct S_OpenFile_XB_44_A6 : S_OpenFile_PC_GC_44_A6 { struct S_OpenFile_BB_44_A6 { parray unused; le_uint16_t type = 0; - ptext filename; + pstring filename; le_uint32_t file_size = 0; - ptext name; + pstring name; } __packed__; // 44 (C->S): Confirm open file (V3/BB) @@ -1038,7 +1033,7 @@ struct S_OpenFile_BB_44_A6 { // whatever the server sent in its header.flag field. Also quest numbers can be // > 0xFF so the flag is essentially meaningless) struct C_OpenFileConfirmation_44_A6 { - ptext filename; + pstring filename; } __packed__; // 45: Invalid command @@ -1144,7 +1139,7 @@ struct C_CharacterData_PC_61_98 { // The auto-reply message can be up to 0x200 characters. If it's shorter than // that, the client truncates the command after the first null value (rounded // up to the next 4-byte boundary). - /* 05A4 */ char16_t auto_reply[0]; + /* 05A4 */ // uint16_t auto_reply[...EOF]; } __attribute__((packed)); struct C_CharacterData_V3_61_98 { @@ -1152,13 +1147,13 @@ struct C_CharacterData_V3_61_98 { /* 034C */ PlayerDispDataDCPCV3 disp; /* 041C */ PlayerRecordsEntry_V3 records; /* 0538 */ ChoiceSearchConfig choice_search_config; - /* 0550 */ ptext info_board; + /* 0550 */ pstring info_board; /* 05FC */ parray blocked_senders; /* 0674 */ le_uint32_t auto_reply_enabled; // The auto-reply message can be up to 0x200 bytes. If it's shorter than that, // the client truncates the command after the first zero byte (rounded up to // the next 4-byte boundary). - /* 0678 */ char auto_reply[0]; + /* 0678 */ // char auto_reply[...EOF]; } __attribute__((packed)); struct C_CharacterData_GC_Ep3_61_98 { @@ -1166,10 +1161,10 @@ struct C_CharacterData_GC_Ep3_61_98 { /* 034C */ PlayerDispDataDCPCV3 disp; /* 041C */ PlayerRecordsEntry_V3 records; /* 0538 */ ChoiceSearchConfig choice_search_config; - /* 0550 */ ptext info_board; + /* 0550 */ pstring info_board; /* 05FC */ parray blocked_senders; /* 0674 */ le_uint32_t auto_reply_enabled; - /* 0678 */ ptext auto_reply; + /* 0678 */ pstring auto_reply; /* 0724 */ Episode3::PlayerConfig ep3_config; /* 2A74 */ } __attribute__((packed)); @@ -1179,12 +1174,12 @@ struct C_CharacterData_BB_61_98 { /* 034C */ PlayerDispDataBB disp; /* 04DC */ PlayerRecordsEntry_BB records; /* 0638 */ ChoiceSearchConfig choice_search_config; - /* 0650 */ ptext info_board; + /* 0650 */ pstring info_board; /* 07A8 */ parray blocked_senders; /* 0820 */ le_uint32_t auto_reply_enabled; // Like on V3, the client truncates the command if the auto reply message is // shorter than 0x200 bytes. - /* 082C */ char16_t auto_reply[0]; + /* 082C */ // uint16_t auto_reply[...EOF]; } __attribute__((packed)); // 62: Target command @@ -1451,29 +1446,29 @@ 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 +template struct SC_SimpleMail_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; - ptext from_name; + pstring from_name; le_uint32_t to_guild_card_number = 0; - ptext text; + pstring text; } __packed__; -struct SC_SimpleMail_PC_81 : SC_SimpleMail_81 { +struct SC_SimpleMail_PC_81 : SC_SimpleMail_81 { } __packed__; -struct SC_SimpleMail_DC_V3_81 : SC_SimpleMail_81 { +struct SC_SimpleMail_DC_V3_81 : SC_SimpleMail_81 { } __packed__; struct SC_SimpleMail_BB_81 { le_uint32_t player_tag = 0x00010000; le_uint32_t from_guild_card_number = 0; - ptext from_name; + pstring from_name; le_uint32_t to_guild_card_number = 0; - ptext received_date; - ptext text; + pstring received_date; + pstring text; } __packed__; // 82: Invalid command @@ -1517,8 +1512,8 @@ struct C_LobbySelection_84 { // The server should respond with an 88 command. struct C_Login_DCNTE_88 { - ptext serial_number; - ptext access_key; + pstring serial_number; + pstring access_key; } __packed__; // 88 (S->C): License check result (DC NTE only) @@ -1567,12 +1562,12 @@ struct S_ArrowUpdateEntry_88 { // The server should respond with an 8A command. struct C_ConnectionInfo_DCNTE_8A { - ptext hardware_id; + pstring hardware_id; le_uint32_t sub_version = 0x20; le_uint32_t unknown_a1 = 0; - ptext username; - ptext password; - ptext email_address; // From Sylverant documentation + pstring username; + pstring password; + pstring email_address; // From Sylverant documentation } __packed__; // 8A (S->C): Connection information result (DC NTE only) @@ -1583,11 +1578,11 @@ struct C_ConnectionInfo_DCNTE_8A { // No arguments // 8A (S->C): Lobby/game name (except DC NTE) -// Contents is a string (char16_t on PC/BB, char on DC/V3) containing the lobby -// or game name. The client generally only sends this immediately after joining -// a game, but Sega's servers also replied to it if it was sent in a lobby. They -// would return a string like "LOBBY01" in that case even though this would -// never be used under normal circumstances. +// Contents is a string containing the lobby or game name. The client generally +// only sends this immediately after joining a game, but Sega's servers also +// replied to it if it was sent in a lobby. They would return a string like +// "LOBBY01" in that case even though this would never be used under normal +// circumstances. // 8B: Log in (DC NTE only) @@ -1599,16 +1594,16 @@ struct C_Login_DCNTE_8B { uint8_t is_extended = 0; uint8_t language = 0; parray unused1; - ptext serial_number; - ptext access_key; - ptext username; - ptext password; - ptext name; + pstring serial_number; + pstring access_key; + pstring username; + pstring password; + pstring name; parray unused; } __packed__; struct C_LoginExtended_DCNTE_8B : C_Login_DCNTE_8B { - SC_MeetUserExtension extension; + SC_MeetUserExtension extension; } __packed__; // 8C: Invalid command @@ -1630,8 +1625,8 @@ struct C_LoginExtended_DCNTE_8B : C_Login_DCNTE_8B { // will be blank (all zeroes). struct C_LoginV1_DC_PC_V3_90 { - ptext serial_number; - ptext access_key; + pstring serial_number; + pstring access_key; // Note: There is a bug in the Japanese and prototype versions of DCv1 that // cause the client to send this command despite its size not being a // multiple of 4. This is fixed in later versions, so we handle both cases in @@ -1659,9 +1654,9 @@ struct C_RegisterV1_DC_92 { uint8_t is_extended = 0; // TODO: This is a guess uint8_t language = 0; // TODO: This is a guess; verify it parray unknown_a3; - ptext hardware_id; + pstring hardware_id; parray unused1; - ptext email; // According to Sylverant documentation + pstring email; // According to Sylverant documentation parray unused2; } __packed__; @@ -1680,16 +1675,16 @@ struct C_LoginV1_DC_93 { uint8_t is_extended = 0; uint8_t language = 0; parray unused1; - ptext serial_number; - ptext access_key; - ptext hardware_id; - ptext unknown_a3; - ptext name; + pstring serial_number; + pstring access_key; + pstring hardware_id; + pstring unknown_a3; + pstring name; parray unused2; } __packed__; struct C_LoginExtendedV1_DC_93 : C_LoginV1_DC_93 { - SC_MeetUserExtension extension; + SC_MeetUserExtension extension; } __packed__; // 93 (C->S): Log in (BB) @@ -1697,10 +1692,10 @@ struct C_LoginExtendedV1_DC_93 : C_LoginV1_DC_93 { struct C_Login_BB_93 { le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; - ptext unused; + pstring unused; le_uint32_t team_id = 0; - ptext username; - ptext password; + pstring username; + pstring password; // These fields map to the same fields in SC_MeetUserExtension. There is no // equivalent of the name field from that structure on BB (though newserv @@ -1717,7 +1712,7 @@ struct C_Login_BB_93 { union VariableLengthSection { union ClientConfigFields { ClientConfigBB cfg; - ptext version_string; + pstring version_string; parray as_u32; } __packed__; @@ -1793,16 +1788,16 @@ struct C_CharSaveInfo_DCv2_PC_V3_BB_96 { // Not used on DCv1 - that version uses 90 instead. struct C_Login_DC_PC_V3_9A { - ptext v1_serial_number; - ptext v1_access_key; - ptext serial_number; - ptext access_key; + pstring v1_serial_number; + pstring v1_access_key; + pstring serial_number; + pstring access_key; le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; le_uint32_t sub_version = 0; - ptext serial_number2; // On DCv2, this is the hardware ID - ptext access_key2; - ptext email_address; + pstring serial_number2; // On DCv2, this is the hardware ID + pstring access_key2; + pstring email_address; } __packed__; // 9A (S->C): License verification result @@ -1851,9 +1846,9 @@ struct C_Register_DC_PC_V3_9C { uint8_t unused1 = 0; uint8_t language = 0; parray unused2; - ptext serial_number; // On XB, this is the XBL gamertag - ptext access_key; // On XB, this is the XBL user ID - ptext password; // On XB, this contains "xbox-pso" + pstring serial_number; // On XB, this is the XBL gamertag + pstring access_key; // On XB, this is the XBL user ID + pstring password; // On XB, this contains "xbox-pso" } __packed__; struct C_Register_BB_9C { @@ -1861,9 +1856,9 @@ struct C_Register_BB_9C { uint8_t unused1 = 0; uint8_t language = 0; parray unused2; - ptext username; - ptext password; - ptext game_tag; // "psopc2" on BB + pstring username; + pstring password; + pstring game_tag; // "psopc2" on BB } __packed__; // 9C (S->C): Register result @@ -1891,19 +1886,19 @@ struct C_Login_DC_PC_GC_9D { uint8_t is_extended = 0; // If 1, structure has extended format uint8_t language = 0; // 0 = JP, 1 = EN, 2 = DE, 3 = FR, 4 = ES parray unused3; // Always zeroes - ptext v1_serial_number; - ptext v1_access_key; - ptext serial_number; // On XB, this is the XBL gamertag - ptext access_key; // On XB, this is the XBL user ID - ptext serial_number2; // On XB, this is the XBL gamertag - ptext access_key2; // On XB, this is the XBL user ID - ptext name; + pstring v1_serial_number; + pstring v1_access_key; + pstring serial_number; // On XB, this is the XBL gamertag + pstring access_key; // On XB, this is the XBL user ID + pstring serial_number2; // On XB, this is the XBL gamertag + pstring access_key2; // On XB, this is the XBL user ID + pstring name; } __packed__; struct C_LoginExtended_DC_GC_9D : C_Login_DC_PC_GC_9D { - SC_MeetUserExtension extension; + SC_MeetUserExtension extension; } __packed__; struct C_LoginExtended_PC_9D : C_Login_DC_PC_GC_9D { - SC_MeetUserExtension extension; + SC_MeetUserExtension extension; } __packed__; // 9E (C->S): Log in with client config (V3/BB) @@ -1920,7 +1915,7 @@ struct C_Login_GC_9E : C_Login_DC_PC_GC_9D { } __packed__ client_config; } __packed__; struct C_LoginExtended_GC_9E : C_Login_GC_9E { - SC_MeetUserExtension extension; + SC_MeetUserExtension extension; } __packed__; struct C_Login_XB_9E : C_Login_GC_9E { @@ -1928,7 +1923,7 @@ struct C_Login_XB_9E : C_Login_GC_9E { parray unknown_a1; } __packed__; struct C_LoginExtended_XB_9E : C_Login_XB_9E { - SC_MeetUserExtension extension; + SC_MeetUserExtension extension; } __packed__; struct C_LoginExtended_BB_9E { @@ -1937,15 +1932,15 @@ struct C_LoginExtended_BB_9E { le_uint32_t sub_version = 0; le_uint32_t unknown_a1 = 0; le_uint32_t unknown_a2 = 0; - ptext unknown_a3; // Always blank? - ptext unknown_a4; // == "?" - ptext unknown_a5; // Always blank? - ptext unknown_a6; // Always blank? - ptext username; - ptext password; - ptext guild_card_number_str; + pstring unknown_a3; // Always blank? + pstring unknown_a4; // == "?" + pstring unknown_a5; // Always blank? + pstring unknown_a6; // Always blank? + pstring username; + pstring password; + pstring guild_card_number_str; parray unknown_a7; - SC_MeetUserExtension extension; + SC_MeetUserExtension extension; } __packed__; // 9F (S->C): Request client config / security data (V3/BB) @@ -1989,20 +1984,20 @@ struct C_ChangeShipOrBlock_A0_A1 { // should send another quest menu (if a category was chosen), or send the quest // data with 44/13 commands; for A9, the server does not need to respond. -template +template struct S_QuestMenuEntry { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; - ptext name; - ptext short_description; + pstring name; + pstring short_description; } __packed__; -struct S_QuestMenuEntry_PC_A2_A4 : S_QuestMenuEntry { +struct S_QuestMenuEntry_PC_A2_A4 : S_QuestMenuEntry { } __packed__; -struct S_QuestMenuEntry_DC_GC_A2_A4 : S_QuestMenuEntry { +struct S_QuestMenuEntry_DC_GC_A2_A4 : S_QuestMenuEntry { } __packed__; -struct S_QuestMenuEntry_XB_A2_A4 : S_QuestMenuEntry { +struct S_QuestMenuEntry_XB_A2_A4 : S_QuestMenuEntry { } __packed__; -struct S_QuestMenuEntry_BB_A2_A4 : S_QuestMenuEntry { +struct S_QuestMenuEntry_BB_A2_A4 : S_QuestMenuEntry { } __packed__; // A3 (S->C): Quest information @@ -2207,7 +2202,7 @@ struct S_RankUpdate_GC_Ep3_B7 { // truncated to 11 characters. If rank is zero, the client uses rank_text // without modifying it. le_uint32_t rank = 0; - ptext rank_text; // Encrypted (with encrypt_challenge_rank_text) + pstring rank_text; le_uint32_t current_meseta = 0; le_uint32_t total_meseta_earned = 0; le_uint32_t unlocked_jukebox_songs = 0xFFFFFFFF; @@ -2334,17 +2329,17 @@ struct S_MesetaTransaction_GC_Ep3_BA { // header.flag is the number of valid match entries. struct S_TournamentMatchInformation_GC_Ep3_BB { - ptext tournament_name; + pstring tournament_name; struct TeamEntry { le_uint16_t win_count = 0; le_uint16_t is_active = 0; - ptext name; + pstring name; } __packed__; parray team_entries; le_uint16_t num_teams = 0; le_uint16_t unknown_a3 = 0; // Probably actually unused struct MatchEntry { - ptext name; + pstring name; uint8_t locked = 0; uint8_t count = 0; uint8_t max_count = 0; @@ -2367,19 +2362,19 @@ struct S_TournamentMatchInformation_GC_Ep3_BB { // Internal name: RcvChoiceList // Command is a list of these; header.flag is the entry count (incl. top-level). -template +template struct S_ChoiceSearchEntry { // Category IDs are nonzero; if the high byte of the ID is nonzero then the // category can be set by the user at any time; otherwise it can't. ItemIDT parent_category_id = 0; // 0 for top-level categories ItemIDT category_id = 0; - ptext text; + pstring text; } __packed__; -struct S_ChoiceSearchEntry_DC_C0 : S_ChoiceSearchEntry { +struct S_ChoiceSearchEntry_DC_C0 : S_ChoiceSearchEntry { } __packed__; -struct S_ChoiceSearchEntry_V3_C0 : S_ChoiceSearchEntry { +struct S_ChoiceSearchEntry_V3_C0 : S_ChoiceSearchEntry { } __packed__; -struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry { +struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry { } __packed__; // Top-level categories are things like "Level", "Class", etc. @@ -2398,18 +2393,18 @@ struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry // C1 (C->S): Create game (DCv2 and later versions) // Internal name: SndCreateGame -template +template struct C_CreateGame_DCNTE { // menu_id and item_id are only used for the E7 (create spectator team) form // of this command le_uint32_t menu_id; le_uint32_t item_id; - ptext name; - ptext password; + pstring name; + pstring password; } __packed__; -template -struct C_CreateGame : C_CreateGame_DCNTE { +template +struct C_CreateGame : C_CreateGame_DCNTE { uint8_t difficulty = 0; // 0-3 (always 0 on Episode 3) uint8_t battle_mode = 0; // 0 or 1 (always 0 on Episode 3) // Note: Episode 3 uses the challenge mode flag for view battle permissions. @@ -2420,12 +2415,12 @@ struct C_CreateGame : C_CreateGame_DCNTE { // players; if set to 1, it's v2-only. uint8_t episode = 0; // 1-4 on V3+ (3 on Episode 3); unused on DC/PC } __packed__; -struct C_CreateGame_DC_V3_0C_C1_Ep3_EC : C_CreateGame { +struct C_CreateGame_DC_V3_0C_C1_Ep3_EC : C_CreateGame { } __packed__; -struct C_CreateGame_PC_C1 : C_CreateGame { +struct C_CreateGame_PC_C1 : C_CreateGame { } __packed__; -struct C_CreateGame_BB_C1 : C_CreateGame { +struct C_CreateGame_BB_C1 : C_CreateGame { uint8_t solo_mode = 0; parray unused2; } __packed__; @@ -2451,12 +2446,12 @@ struct C_ChoiceSearchSelections_PC_V3_BB_C2_C3 : ChoiceSearchConfig // Command is a list of these; header.flag is the entry count struct S_ChoiceSearchResultEntry_V3_C4 { le_uint32_t guild_card_number = 0; - ptext name; // No language marker, as usual on V3 - ptext info_string; // Usually something like " Lvl " + pstring name; // No language marker, as usual on V3 + pstring 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" - ptext locator_string; + pstring locator_string; // Server IP and port for "meet user" option le_uint32_t server_ip = 0; le_uint16_t server_port = 0; @@ -2533,17 +2528,17 @@ struct C_SetBlockedSenders_BB_C6 : C_SetBlockedSenders_C6<28> { // disabled and the Status item has only two panes. struct S_ConfirmTournamentEntry_GC_Ep3_CC { - ptext tournament_name; + pstring tournament_name; le_uint16_t num_teams = 0; le_uint16_t players_per_team = 0; le_uint16_t unknown_a2 = 0; le_uint16_t unknown_a3 = 0; - ptext server_name; - ptext start_time; // e.g. "15:09:30" or "13:03 PST" + pstring server_name; + pstring start_time; // e.g. "15:09:30" or "13:03 PST" struct TeamEntry { le_uint16_t win_count = 0; le_uint16_t is_active = 0; - ptext name; + pstring name; } __packed__; parray team_entries; } __packed__; @@ -2628,7 +2623,7 @@ struct SC_TradeItems_D0_D3 { // D0 when sent by client, D3 when sent by server // client. struct C_GBAGameRequest_V3_D7 { - ptext filename; + pstring filename; } __packed__; // D7 (S->C): GBA file not found (V3/BB) @@ -2648,14 +2643,14 @@ struct C_GBAGameRequest_V3_D7 { // Command is a list of these; header.flag is the entry count. There should be // one entry for each player in the current lobby/game. -template +template struct S_InfoBoardEntry_D8 { - ptext name; - ptext message; + 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 { +struct S_InfoBoardEntry_V3_D8 : S_InfoBoardEntry_D8 { } __packed__; // D9 (C->S): Write info board (V3/BB) @@ -2670,28 +2665,28 @@ struct S_InfoBoardEntry_V3_D8 : S_InfoBoardEntry_D8 { // Server should respond with a 9A command. struct C_VerifyLicense_V3_DB { - ptext unused; - ptext serial_number; // On XB, this is the XBL gamertag - ptext access_key; // On XB, this is the XBL user ID - ptext unused2; + pstring unused; + pstring serial_number; // On XB, this is the XBL gamertag + pstring access_key; // On XB, this is the XBL user ID + pstring unused2; le_uint32_t sub_version = 0; - ptext serial_number2; // On XB, this is the XBL gamertag - ptext access_key2; // On XB, this is the XBL user ID - ptext password; // On XB, this contains "xbox-pso" + pstring serial_number2; // On XB, this is the XBL gamertag + pstring access_key2; // On XB, this is the XBL user ID + pstring password; // On XB, this contains "xbox-pso" } __packed__; // Note: This login pathway generally isn't used on BB (and isn't supported at // all during the data server phase). All current servers use 03/93 instead. struct C_VerifyLicense_BB_DB { // Note: These four fields are likely the same as those used in BB's 9E - ptext unknown_a3; // Always blank? - ptext unknown_a4; // == "?" - ptext unknown_a5; // Always blank? - ptext unknown_a6; // Always blank? + pstring unknown_a3; // Always blank? + pstring unknown_a4; // == "?" + pstring unknown_a5; // Always blank? + pstring unknown_a6; // Always blank? le_uint32_t sub_version = 0; - ptext username; - ptext password; - ptext game_tag; // "psopc2" + pstring username; + pstring password; + pstring game_tag; // "psopc2" } __packed__; // DC: Player menu state (Episode 3) @@ -2757,7 +2752,7 @@ struct C_Unknown_BB_04DF { struct C_Unknown_BB_05DF { le_uint32_t unknown_a1 = 0; - ptext unknown_a2; + pstring unknown_a2; } __packed__; struct C_Unknown_BB_06DF { @@ -2801,7 +2796,7 @@ struct S_TournamentList_GC_Ep3_E0 { uint8_t state = 0; uint8_t unknown_a2 = 0; le_uint32_t start_time = 0; // In seconds since Unix epoch - ptext name; + pstring name; le_uint16_t num_teams = 0; le_uint16_t max_teams = 0; le_uint16_t unknown_a3 = 0; @@ -2822,13 +2817,13 @@ struct S_TournamentList_GC_Ep3_E0 { // flag value means. struct S_GameInformation_GC_Ep3_E1 { - /* 0004 */ ptext game_name; + /* 0004 */ pstring game_name; struct PlayerEntry { - ptext name; // From disp.name - ptext description; // Usually something like "FOmarl CLv30 J" + pstring name; // From disp.name + pstring description; // Usually something like "FOmarl CLv30 J" } __packed__; /* 0024 */ parray player_entries; - /* 00E4 */ ptext map_name; + /* 00E4 */ pstring map_name; /* 0104 */ Episode3::Rules rules; /* 0118 */ parray spectator_entries; /* 0298 */ @@ -2890,7 +2885,7 @@ struct S_TournamentEntryList_GC_Ep3_E2 { // but cannot be selected at all (the menu cursor simply skips over it). uint8_t state = 0; uint8_t unknown_a2 = 0; - ptext name; + pstring name; } __packed__; parray entries; } __packed__; @@ -2922,15 +2917,15 @@ struct S_TournamentEntryList_GC_Ep3_E2 { struct S_TournamentGameDetails_GC_Ep3_E3 { // These fields are used only if the Rules pane is shown - /* 0004 */ ptext name; - /* 0024 */ ptext map_name; + /* 0004 */ pstring name; + /* 0024 */ pstring map_name; /* 0044 */ Episode3::Rules rules; // This field is used only if the bracket pane is shown struct BracketEntry { le_uint16_t win_count = 0; le_uint16_t is_active = 0; - ptext team_name; + pstring team_name; parray unused; } __packed__; /* 0058 */ parray bracket_entries; @@ -2939,11 +2934,11 @@ struct S_TournamentGameDetails_GC_Ep3_E3 { // is 2, all fields are shown; if player_per_team is 1, team_name and // players[1] is ignored (only players[0] is shown). struct PlayerEntry { - ptext name; - ptext description; // Usually something like "RAmarl CLv24 E" + pstring name; + pstring description; // Usually something like "RAmarl CLv24 E" } __packed__; struct TeamEntry { - ptext team_name; + pstring team_name; parray players; } __packed__; /* 04D8 */ parray team_entries; @@ -3058,8 +3053,8 @@ struct S_ClientInit_BB_00E6 { struct C_CreateSpectatorTeam_GC_Ep3_E7 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; - ptext name; - ptext password; + pstring name; + pstring password; le_uint32_t unused = 0; } __packed__; @@ -3083,8 +3078,8 @@ struct SC_SyncCharacterSaveFile_BB_00E7 { /* 1AC8 */ le_uint32_t unknown_a3; /* 1ACC */ parray symbol_chats; // account /* 1FAC */ parray shortcuts; // account - /* 29EC */ ptext auto_reply; // player - /* 2B44 */ ptext info_board; // player + /* 29EC */ pstring auto_reply; // player + /* 2B44 */ pstring info_board; // player /* 2C9C */ PlayerRecords_Battle battle_records; /* 2CB4 */ parray unknown_a4; /* 2CB8 */ PlayerRecordsBB_Challenge challenge_records; @@ -3128,8 +3123,8 @@ struct S_JoinSpectatorTeam_GC_Ep3_E8 { // for, but the client also completely ignores it. /* 00 */ le_uint32_t player_tag = 0; /* 04 */ le_uint32_t guild_card_number = 0; - /* 08 */ ptext name; - /* 18 */ ptext unused1; + /* 08 */ pstring name; + /* 18 */ pstring unused1; /* 28 */ uint8_t present = 0; /* 29 */ uint8_t unused2 = 0; /* 2A */ le_uint16_t level = 0; @@ -3142,7 +3137,7 @@ struct S_JoinSpectatorTeam_GC_Ep3_E8 { // battle - they appear in the first positions. Presumably the first 4 are // always for battlers, and the last 8 are always for spectators. /* 1184 */ parray entries; - /* 1424 */ ptext spectator_team_name; + /* 1424 */ pstring spectator_team_name; // This field doesn't appear to be actually used by the game, but some servers // send it anyway (and the game ignores it) /* 1444 */ parray spectator_players; @@ -3199,7 +3194,7 @@ struct C_DeleteGuildCard_BB_05E8_08E8 { struct C_WriteGuildCardComment_BB_09E8 { le_uint32_t guild_card_number = 0; - ptext comment; + pstring comment; } __packed__; // 0AE8 (C->S): Set guild card position in list @@ -3234,7 +3229,7 @@ struct S_TimedMessageBoxHeader_GC_Ep3_EA { // 01EA (C->S): Create team struct C_CreateTeam_BB_01EA { - ptext name; + pstring name; } __packed__; // 02EA (S->C): Unknown @@ -3258,10 +3253,10 @@ struct C_AddOrRemoveTeamMember_BB_03EA_05EA { // 07EA: Team chat struct SC_TeamChat_BB_07EA { - parray sender_name; + pstring sender_name; // It seems there are no real limits on the message length, other than the // overall command length limit of 0x7C00 bytes. - char16_t message[0]; + // Text follows here } __packed__; // 08EA (C->S): Team admin @@ -3277,16 +3272,17 @@ struct S_Unknown_BB_09EA { le_uint32_t value; le_uint32_t color; // 0x10 or 0x20 = green, 0x30 = blue, 0x40 = red, anything else = white le_uint32_t unknown_a1; - parray message; + pstring message; } __packed__; - Entry entries[0]; // [entry_count] actually + // Variable-length field: + // Entry entries[entry_count]; } __packed__; // 0CEA (S->C): Unknown struct S_Unknown_BB_0CEA { parray unknown_a1; - char16_t unknown_a2[0]; + // Text follows here } __packed__; // 0DEA (C->S): Unknown @@ -3296,7 +3292,7 @@ struct S_Unknown_BB_0CEA { struct S_Unknown_BB_0EEA { parray unused; - char16_t unknown_a2[0]; + // Text follows here } __packed__; // 0EEA (S->C): Unknown @@ -3331,7 +3327,7 @@ struct S_TeamMembershipInformation_BB_12EA { le_uint32_t unknown_a4; le_uint32_t privilege_level; le_uint32_t unknown_a6; - parray team_name; + pstring team_name; } __packed__; // 13EA: Unknown @@ -3343,10 +3339,10 @@ struct S_Unknown_BB_13EA_15EA_Entry { le_uint32_t unknown_a3; le_uint32_t unknown_a4; le_uint32_t privilege_level; - parray team_name; + pstring team_name; le_uint32_t guild_card_number2; le_uint32_t lobby_client_id; - parray player_name; + pstring player_name; parray team_flag; } __packed__; @@ -3381,7 +3377,7 @@ struct S_Unknown_BB_13EA_15EA_Entry { // header.flag is used, but it's unknown what the value means. struct C_Unknown_BB_1EEA { - ptext unknown_a1; + pstring unknown_a1; } __packed__; // 1FEA (S->C): Unknown @@ -3408,7 +3404,7 @@ struct S_StreamFileIndexEntry_BB_01EB { le_uint32_t size = 0; le_uint32_t checksum = 0; // CRC32 of file data le_uint32_t offset = 0; // offset in stream (== sum of all previous files' sizes) - ptext filename; + pstring filename; } __packed__; // 02EB (S->C): Send stream file chunk (BB) @@ -3533,7 +3529,7 @@ struct S_StartCardAuction_GC_Ep3_EF { // client, so newserv's proxy unconditionally blocks this command. struct S_SetShutdownCommand_BB_01EF { - ptext command; + pstring command; } __packed__; // F0 (S->C): Force update player lobby data (BB) @@ -3675,38 +3671,25 @@ struct G_SwitchStateChanged_6x05 { // 6x06: Send guild card -template -struct G_SendGuildCard_DC_PC_V3 { +struct G_SendGuildCard_DC_6x06 { G_UnusedHeader header; - le_uint32_t player_tag; - le_uint32_t guild_card_number; - ptext name; - ptext description; - parray unused2; - uint8_t present; - uint8_t present2; - uint8_t section_id; - uint8_t char_class; + GuildCardDC guild_card; + parray unused; } __packed__; -struct G_SendGuildCard_DC_6x06 : G_SendGuildCard_DC_PC_V3 { - parray unused3; +struct G_SendGuildCard_PC_6x06 { + G_UnusedHeader header; + GuildCardPC guild_card; } __packed__; -struct G_SendGuildCard_PC_6x06 : G_SendGuildCard_DC_PC_V3 { -} __packed__; -struct G_SendGuildCard_V3_6x06 : G_SendGuildCard_DC_PC_V3 { + +struct G_SendGuildCard_V3_6x06 { + G_UnusedHeader header; + GuildCardV3 guild_card; } __packed__; struct G_SendGuildCard_BB_6x06 { G_UnusedHeader header; - le_uint32_t guild_card_number; - ptext name; - ptext team_name; - ptext description; - uint8_t present; - uint8_t present2; - uint8_t section_id; - uint8_t char_class; + GuildCardBB guild_card; } __packed__; // 6x07: Symbol chat @@ -4467,7 +4450,7 @@ struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E { G_ExtendedHeader header; le_uint32_t decompressed_size; le_uint32_t compressed_size; // Must be <= subcommand_size - 0x10 - uint8_t data[0]; // BC0-compressed data follows here (see bc0_decompress) + // BC0-compressed data follows here (see bc0_decompress) } __packed__; // Decompressed format is a list of these @@ -4521,7 +4504,8 @@ struct G_SyncItemState_6x6D_Decompressed { le_uint16_t drop_number; ItemData item_data; } __packed__; - FloorItem items[0]; // sum(floor_item_count_per_area) of them, actually + // Variable-length field: + // FloorItem items[sum(floor_item_count_per_area)]; } __packed__; // 6x6E: Sync flag state (used while loading into game) @@ -6122,7 +6106,7 @@ struct G_SetDeckInBattleSetupMenu_GC_Ep3_6xB5x2F { parray unknown_a1; parray unknown_a2; - ptext deck_name; + pstring deck_name; parray unknown_a3; le_uint16_t unknown_a4 = 0; parray card_ids; @@ -6278,8 +6262,8 @@ struct G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D { Episode3::Rules rules; struct Entry { uint8_t type = 0; // 0 = no player, 1 = human, 2 = COM - ptext player_name; - ptext deck_name; // Only be used for COM players + pstring player_name; + pstring deck_name; // Only be used for COM players parray unknown_a1; parray card_ids; // Can be blank for human players uint8_t client_id = 0; // Unused for COMs @@ -6411,12 +6395,12 @@ struct G_ServerVersionStrings_GC_Ep3_6xB4x46 { // Presumably if any logs exist from before 7 March 2007, they would have a // different date_str1, but the unchanged version_signature likely means that // Sega never made any code changes on the server side. - ptext version_signature; - ptext date_str1; // Probably card definitions revision date + pstring version_signature; + pstring date_str1; // Probably card definitions revision date // In Sega's implementation, it seems this field is blank when starting a // battle, and contains the current time (in the format "YYYY/MM/DD hh:mm:ss") // when ending a battle. This may have been used for identifying debug logs. - ptext date_str2; + pstring date_str2; // It seems Sega used to send 0 here when starting a battle, and 0x04157580 // when ending a battle. Since the field is unused by the client, it's not // clear what that value means, if anything. This behavior may be another @@ -6569,10 +6553,10 @@ struct G_SetTrapTileLocations_GC_Ep3_6xB4x50 { struct G_TournamentMatchResult_GC_Ep3_6xB4x51 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_TournamentMatchResult_GC_Ep3_6xB4x51) / 4, 0, 0x51, 0, 0, 0}; - ptext match_description; + pstring match_description; struct NamesEntry { - ptext team_name; - parray, 2> player_names; + pstring team_name; + parray, 2> player_names; } __packed__; parray names_entries; le_uint16_t unused1 = 0; @@ -6586,7 +6570,7 @@ struct G_TournamentMatchResult_GC_Ep3_6xB4x51 { // This field apparently is supposed to contain a %s token (as for printf) // which is replaced with meseta_amount. The results screen animates this text // counting up from 0 to meseta_amount. - ptext meseta_reward_text; + pstring meseta_reward_text; } __packed__; // 6xB4x52: Set game metadata @@ -6617,7 +6601,7 @@ struct G_SetGameMetadata_GC_Ep3_6xB4x52 { // If text_size is not zero, the text is shown in the top bar instead of the // usual message ("Viewing Battle", "Time left: XX:XX", and the like). le_uint16_t text_size = 0; - ptext text; + pstring text; } __packed__; // 6xB4x53: Reject battle start request diff --git a/src/Episode3/BattleRecord.cc b/src/Episode3/BattleRecord.cc index 5937868c..519dfa14 100644 --- a/src/Episode3/BattleRecord.cc +++ b/src/Episode3/BattleRecord.cc @@ -354,7 +354,7 @@ void BattleRecordPlayer::schedule_events() { send_command(l, 0xC9, 0x00, ev.data); break; case BattleRecord::Event::Type::CHAT_MESSAGE: - send_chat_message(l, ev.guild_card_number, decode_sjis(ev.data)); + send_prepared_chat_message(l, ev.guild_card_number, ev.data); break; } this->event_it++; diff --git a/src/Episode3/Card.cc b/src/Episode3/Card.cc index a7e304d5..7b93f46e 100644 --- a/src/Episode3/Card.cc +++ b/src/Episode3/Card.cc @@ -166,10 +166,12 @@ ssize_t Card::apply_abnormal_condition( cond.value8 = value + existing_cond_value; cond.random_percent = random_percent; - switch (eff.arg1[0]) { - case 'a': - cond.a_arg_value = atoi(&eff.arg1[1]); + switch (eff.arg1.at(0)) { + case 'a': { + string s = eff.arg1.decode(); + cond.a_arg_value = atoi(s.c_str() + 1); break; + } case 'e': cond.remaining_turns = 99; break; @@ -179,8 +181,10 @@ ssize_t Card::apply_abnormal_condition( case 'r': cond.remaining_turns = 102; break; - case 't': - cond.remaining_turns = atoi(&eff.arg1[1]); + case 't': { + string s = eff.arg1.decode(); + cond.remaining_turns = atoi(s.c_str() + 1); + } } string cond_str = cond.str(); diff --git a/src/Episode3/CardSpecial.cc b/src/Episode3/CardSpecial.cc index f4830d3a..1a99aab6 100644 --- a/src/Episode3/CardSpecial.cc +++ b/src/Episode3/CardSpecial.cc @@ -358,7 +358,7 @@ bool CardSpecial::apply_defense_condition( defense_state, defender_card, dice_roll, defender_cond->card_ref, defender_cond->condition_giver_card_ref); - string expr = orig_eff->expr; + string expr = orig_eff->expr.decode(); int16_t expr_value = this->evaluate_effect_expr(astats, expr.c_str(), dice_roll); this->execute_effect( *defender_cond, defender_card, expr_value, defender_cond->value, @@ -1397,9 +1397,9 @@ bool CardSpecial::evaluate_effect_arg2_condition( if (ce->def.effects[cond_index].type == ConditionType::NONE) { break; } - uint8_t arg2_command = ce->def.effects[cond_index].arg2[0]; + uint8_t arg2_command = ce->def.effects[cond_index].arg2.at(0); if ((arg2_command == 'c') || (arg2_command == 'C')) { - uint8_t other_ch1 = ce->def.effects[cond_index].arg2[1] - 0x30; + uint8_t other_ch1 = ce->def.effects[cond_index].arg2.at(1) - 0x30; if ((other_ch1 > 9)) { return false; } @@ -2007,8 +2007,9 @@ bool CardSpecial::execute_effect( case ConditionType::BONUS_FROM_LEADER: if (unknown_p7 & 1) { - clamped_unknown_p5 = this->count_cards_with_card_id_except_card_ref(expr_value, 0xFFFF) + (card->action_chain).chain.ap_effect_bonus; - (card->action_chain).chain.ap_effect_bonus = clamp(clamped_unknown_p5, -99, 99); + size_t leader_count = this->count_cards_with_card_id_except_card_ref(expr_value, 0xFFFF); + card->action_chain.chain.ap_effect_bonus = clamp( + leader_count + card->action_chain.chain.ap_effect_bonus, -99, 99); } return true; @@ -3192,12 +3193,16 @@ bool CardSpecial::is_card_targeted_by_condition( } if (cond.remaining_turns == 102) { if (sc_card && ((sc_card == card) || !(sc_card->card_flags & 2))) { + string arg3_s = ce->def.effects[cond.card_definition_effect_index].arg3.decode(); + if (arg3_s.size() < 1) { + throw runtime_error("card definition arg3 is missing"); + } auto target_cards = this->get_targeted_cards_for_condition( cond.card_ref, cond.card_definition_effect_index, cond.condition_giver_card_ref, as, - atoi(&ce->def.effects[cond.card_definition_effect_index].arg3[1]), + atoi(arg3_s.c_str() + 1), 0); for (auto c : target_cards) { if (c == card) { @@ -3686,7 +3691,11 @@ void CardSpecial::evaluate_and_apply_effects( continue; } - int16_t arg3_value = atoi(&card_effect.arg3[1]); + string arg3_s = card_effect.arg3.decode(); + if (arg3_s.size() < 1) { + throw runtime_error("card effect arg3 is missing"); + } + int16_t arg3_value = atoi(arg3_s.c_str() + 1); effect_log.debug("arg3_value=%hd", arg3_value); auto targeted_cards = this->get_targeted_cards_for_condition( set_card_ref, def_effect_index, sc_card_ref, as, arg3_value, 1); @@ -3701,7 +3710,7 @@ void CardSpecial::evaluate_and_apply_effects( size_t count = 0; for (size_t z = 0; z < targeted_cards.size(); z++) { dice_roll.value_used_in_expr = false; - string arg2_text = card_effect.arg2; + string arg2_text = card_effect.arg2.decode(); if (this->evaluate_effect_arg2_condition( as, targeted_cards[z], arg2_text.c_str(), dice_roll, set_card_ref, sc_card_ref, random_percent, when)) { @@ -3731,7 +3740,7 @@ void CardSpecial::evaluate_and_apply_effects( for (size_t z = 0; z < targeted_cards.size(); z++) { auto target_log = effect_log.sub(string_printf("(target:@%04hX) ", targeted_cards[z]->get_card_ref())); dice_roll.value_used_in_expr = false; - string arg2_str = card_effect.arg2; + string arg2_str = card_effect.arg2.decode(); target_log.debug("arg2_str = %s", arg2_str.c_str()); if (all_targets_matched || this->evaluate_effect_arg2_condition( @@ -3739,7 +3748,7 @@ void CardSpecial::evaluate_and_apply_effects( target_log.debug("arg2 condition passed"); auto env_stats = this->compute_attack_env_stats( as, targeted_cards[z], dice_roll, set_card_ref, sc_card_ref); - string expr_str = card_effect.expr; + string expr_str = card_effect.expr.decode(); int16_t value = this->evaluate_effect_expr(env_stats, expr_str.c_str(), dice_roll); target_log.debug("expr = %s, value = %hd", expr_str.c_str(), value); diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index c608883a..b2031748 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -702,11 +702,11 @@ string CardDefinition::Stat::str() const { bool CardDefinition::Effect::is_empty() const { return (this->effect_num == 0 && this->type == ConditionType::NONE && - this->expr.is_filled_with(0) && + this->expr.empty() && this->when == 0 && - this->arg1.is_filled_with(0) && - this->arg2.is_filled_with(0) && - this->arg3.is_filled_with(0) && + this->arg1.empty() && + this->arg2.empty() && + this->arg3.empty() && this->apply_criterion == CriterionCode::NONE && this->name_index == 0); } @@ -781,12 +781,12 @@ string CardDefinition::Effect::str(const char* separator, const TextArchive* tex tokens.emplace_back(std::move(cmd_str)); } if (!this->expr.empty()) { - tokens.emplace_back("expr=" + string(this->expr)); + tokens.emplace_back("expr=" + this->expr.decode()); } tokens.emplace_back(string_printf("when=%02hhX", this->when)); - tokens.emplace_back("arg1=" + this->str_for_arg(this->arg1)); - tokens.emplace_back("arg2=" + this->str_for_arg(this->arg2)); - tokens.emplace_back("arg3=" + this->str_for_arg(this->arg3)); + tokens.emplace_back("arg1=" + this->str_for_arg(this->arg1.decode())); + tokens.emplace_back("arg2=" + this->str_for_arg(this->arg2.decode())); + tokens.emplace_back("arg3=" + this->str_for_arg(this->arg3.decode())); { uint8_t type = static_cast(this->apply_criterion); string cond_str = string_printf("cond=%02hhX", type); @@ -1118,6 +1118,7 @@ string CardDefinition::str(bool single_line, const TextArchive* text_archive) co } } + string en_name_s = this->en_name.decode(); if (single_line) { string range_str = string_for_range(this->range); return string_printf( @@ -1126,7 +1127,7 @@ string CardDefinition::str(bool single_line, const TextArchive* text_archive) co "cannot_attack=%s cannot_drop=%s hp=%s ap=%s tp=%s mv=%s left=%s right=%s " "top=%s class=%s assist_ai_params=[target=%s priority=%hhu effect=%hhu] drop_rates=[%s, %s] effects=[%s]]", this->card_id.load(), - this->en_name.data(), + en_name_s.c_str(), type_str.c_str(), criterion_str.c_str(), rank_str.c_str(), @@ -1192,7 +1193,7 @@ Card: %04" PRIX32 " \"%s\"\n\ %s\n\ Effects:%s", this->card_id.load(), - this->en_name.data(), + en_name_s.c_str(), type_str.c_str(), card_class_str.c_str(), criterion_str.c_str(), @@ -1635,7 +1636,7 @@ string MapDefinition::CameraSpec::str() const { this->unknown_a2[1].load(), this->unknown_a2[2].load()); } -string MapDefinition::str(const CardIndex* card_index) const { +string MapDefinition::str(const CardIndex* card_index, uint8_t language) const { deque lines; auto add_map = [&](const parray, 0x10>& tiles) { for (size_t y = 0; y < this->height; y++) { @@ -1691,14 +1692,14 @@ string MapDefinition::str(const CardIndex* card_index) const { " a5[0x70:0x74]: %02hhX %02hhX %02hhX %02hhX", this->unknown_a5[0x70], this->unknown_a5[0x71], this->unknown_a5[0x72], this->unknown_a5[0x73])); lines.emplace_back(" default_rules: " + this->default_rules.str()); - lines.emplace_back(" name: " + format_data_string(this->name)); - lines.emplace_back(" location_name: " + format_data_string(this->location_name)); - lines.emplace_back(" quest_name: " + format_data_string(this->quest_name)); - lines.emplace_back(" description: " + format_data_string(this->description)); + lines.emplace_back(" name: " + format_data_string(this->name.decode(language))); + lines.emplace_back(" location_name: " + format_data_string(this->location_name.decode(language))); + lines.emplace_back(" quest_name: " + format_data_string(this->quest_name.decode(language))); + lines.emplace_back(" description: " + format_data_string(this->description.decode(language))); lines.emplace_back(string_printf(" map_xy: %hu %hu", this->map_x.load(), this->map_y.load())); for (size_t z = 0; z < 3; z++) { lines.emplace_back(string_printf(" npc_chars[%zu]:", z)); - lines.emplace_back(" name: " + format_data_string(this->npc_ai_params[z].name)); + lines.emplace_back(" name: " + format_data_string(this->npc_ai_params[z].name.decode(language))); lines.emplace_back(string_printf( " ai_params: (a1: %04hX %04hX, is_arkz: %02hhX, a2: %02hX %02hX %02hX)", this->npc_ai_params[z].unknown_a1[0].load(), this->npc_ai_params[z].unknown_a1[1].load(), @@ -1719,7 +1720,7 @@ string MapDefinition::str(const CardIndex* card_index) const { this->npc_ai_params[z].params[0x7A].load(), this->npc_ai_params[z].params[0x7B].load(), this->npc_ai_params[z].params[0x7C].load(), this->npc_ai_params[z].params[0x7D].load())); lines.emplace_back(string_printf(" npc_decks[%zu]:", z)); - lines.emplace_back(" name: " + format_data_string(this->npc_decks[z].name)); + lines.emplace_back(" name: " + format_data_string(this->npc_decks[z].name.decode(language))); for (size_t w = 0; w < 0x20; w++) { uint16_t card_id = this->npc_decks[z].card_ids[w]; shared_ptr entry; @@ -1730,7 +1731,7 @@ string MapDefinition::str(const CardIndex* card_index) const { } } if (entry) { - string name = entry->def.en_name; + string name = entry->def.en_name.decode(language); lines.emplace_back(string_printf(" cards[%02zu]: %04hX (%s)", w, card_id, name.c_str())); } else { lines.emplace_back(string_printf(" cards[%02zu]: %04hX", w, card_id)); @@ -1744,8 +1745,8 @@ string MapDefinition::str(const CardIndex* card_index) const { lines.emplace_back(string_printf(" npc_dialogue[%zu][%zu] (when: %04hX, chance: %hu%%):", z, x, set.when.load(), set.percent_chance.load())); for (size_t w = 0; w < 4; w++) { - if (set.strings[w][0] != 0 && static_cast(set.strings[w][0]) != 0xFF) { - string escaped = format_data_string(set.strings[w]); + if (!set.strings[w].empty() && set.strings[w].at(0) != 0xFF) { + string escaped = format_data_string(set.strings[w].decode(language)); lines.emplace_back(string_printf(" strings[%zu]: %s", w, escaped.c_str())); } } @@ -1754,14 +1755,14 @@ string MapDefinition::str(const CardIndex* card_index) const { lines.emplace_back(" a7: " + format_data_string(this->unknown_a7.data(), this->unknown_a7.bytes())); lines.emplace_back(string_printf(" npc_ai_params_entry_index: [%08" PRIX32 ", %08" PRIX32 ", %08" PRIX32 "]", this->npc_ai_params_entry_index[0].load(), this->npc_ai_params_entry_index[1].load(), this->npc_ai_params_entry_index[2].load())); - if (this->before_message[0]) { - lines.emplace_back(" before_message: " + format_data_string(this->before_message)); + if (!this->before_message.empty()) { + lines.emplace_back(" before_message: " + format_data_string(this->before_message.decode(language))); } - if (this->after_message[0]) { - lines.emplace_back(" after_message: " + format_data_string(this->after_message)); + if (!this->after_message.empty()) { + lines.emplace_back(" after_message: " + format_data_string(this->after_message.decode(language))); } - if (this->dispatch_message[0]) { - lines.emplace_back(" dispatch_message: " + format_data_string(this->dispatch_message)); + if (!this->dispatch_message.empty()) { + lines.emplace_back(" dispatch_message: " + format_data_string(this->dispatch_message.decode(language))); } for (size_t z = 0; z < 0x10; z++) { uint16_t card_id = this->reward_card_ids[z]; @@ -1773,7 +1774,7 @@ string MapDefinition::str(const CardIndex* card_index) const { } } if (entry) { - string name = entry->def.en_name; + string name = entry->def.en_name.decode(language); lines.emplace_back(string_printf(" reward_cards[%02zu]: %04hX (%s)", z, card_id, name.c_str())); } else { lines.emplace_back(string_printf(" reward_cards[%02zu]: %04hX", z, card_id)); @@ -1955,7 +1956,7 @@ MapDefinitionTrial::operator MapDefinition() const { ret.dialogue_sets[z][x].percent_chance = 0xFFFF; for (size_t w = 0; w < 4; w++) { ret.dialogue_sets[z][x].strings[w].clear(0xFF); - ret.dialogue_sets[z][x].strings[w][0] = 0x00; + ret.dialogue_sets[z][x].strings[w].set_byte(0, 0); } } } @@ -1972,7 +1973,7 @@ MapDefinitionTrial::operator MapDefinition() const { // guess and fill in the field appropriately here. size_t num_npc_decks = 0; for (size_t z = 0; z < ret.npc_decks.size(); z++) { - if (ret.npc_decks[z].name[0]) { + if (!ret.npc_decks[z].name.empty()) { num_npc_decks++; } } @@ -2270,7 +2271,7 @@ CardIndex::CardIndex( // Some cards intentionally have the same name, so we just leave them // unindexed (they can still be looked up by ID, of course) - string name = entry->def.en_name; + string name = entry->def.en_name.decode(1); this->card_definitions_by_name.emplace(name, entry); this->card_definitions_by_name_normalized.emplace(this->normalize_card_name(name), entry); @@ -2510,7 +2511,7 @@ MapIndex::MapIndex(const string& directory) { throw runtime_error("unknown map file format"); } - string name = format_data_string(vm->map->name); + string name = format_data_string(vm->map->name.decode(vm->language)); auto map_it = this->maps.find(vm->map->map_number); if (map_it == this->maps.end()) { map_it = this->maps.emplace(vm->map->map_number, new Map(vm)).first; @@ -2529,7 +2530,7 @@ MapIndex::MapIndex(const string& directory) { vm->map->is_quest() ? "quest" : "free", name.c_str()); } - this->maps_by_name.emplace(vm->map->name, map_it->second); + this->maps_by_name.emplace(vm->map->name.decode(vm->language), map_it->second); } catch (const exception& e) { static_game_data_log.warning("Failed to index Episode 3 map %s: %s", @@ -2579,16 +2580,16 @@ const string& MapIndex::get_compressed_list(size_t num_players, uint8_t language e.modification_tiles = vm->map->modification_tiles; e.name_offset = strings_w.size(); - strings_w.write(vm->map->name.data(), vm->map->name.len()); + strings_w.write(vm->map->name.data, vm->map->name.used_bytes_8()); strings_w.put_u8(0); e.location_name_offset = strings_w.size(); - strings_w.write(vm->map->location_name.data(), vm->map->location_name.len()); + strings_w.write(vm->map->location_name.data, vm->map->location_name.used_bytes_8()); strings_w.put_u8(0); e.quest_name_offset = strings_w.size(); - strings_w.write(vm->map->quest_name.data(), vm->map->quest_name.len()); + strings_w.write(vm->map->quest_name.data, vm->map->quest_name.used_bytes_8()); strings_w.put_u8(0); e.description_offset = strings_w.size(); - strings_w.write(vm->map->description.data(), vm->map->description.len()); + strings_w.write(vm->map->description.data, vm->map->description.used_bytes_8()); strings_w.put_u8(0); e.map_category = vm->map->map_category; diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index 79c54f66..79face53 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -492,16 +492,16 @@ struct CardDefinition { // operators to perform basic computations on them. Operators are evaluated // left-to-right in the expression, and there are no operator precedence // rules; for example, the expression "4+4//2" results in 4, not 6. - /* 02 */ ptext expr; + /* 02 */ pstring expr; // when specifies in which phase the effect should activate. // TODO: There are many values that can be used here; document them. /* 11 */ uint8_t when; // arg1 generally specifies how long the effect activates for. - /* 12 */ ptext arg1; + /* 12 */ pstring arg1; // arg2 generally specifies a condition for when the effect activates. - /* 16 */ ptext arg2; + /* 16 */ pstring arg2; // arg3 generally specifies who is targeted by the effect. - /* 1A */ ptext arg3; + /* 1A */ pstring arg3; // apply_criterion can be used to apply an additional condition for when the // effect should activate. For example, it can be used to make the effect // only activate if the target is not a Story Character. @@ -764,9 +764,9 @@ struct CardDefinition { // enormous comment? That's what this array stores. /* 009C */ parray drop_rates; - /* 00A0 */ ptext en_name; - /* 00B4 */ ptext jp_short_name; - /* 00BF */ ptext en_short_name; + /* 00A0 */ pstring en_name; + /* 00B4 */ pstring jp_short_name; + /* 00BF */ pstring en_short_name; // These effects modify the card's behavior in various situations. Only // effects for which effect_num is not zero are used. /* 00C7 */ parray effects; @@ -800,7 +800,7 @@ struct CardDefinitionsFooter { } __attribute__((packed)); struct DeckDefinition { - /* 00 */ ptext name; + /* 00 */ pstring name; /* 10 */ be_uint32_t client_id; // 0-3 // List of card IDs. The card count is the number of nonzero entries here // before a zero entry (or 50 if no entries are nonzero). The first card ID is @@ -823,7 +823,7 @@ struct PlayerConfig { // The game splits this internally into two structures. The first column of // offsets is relative to the start of the first structure; the second column // is relative to the start of the second structure. - /* 0000:---- */ ptext rank_text; // From B7 command + /* 0000:---- */ pstring rank_text; // From B7 command /* 000C:---- */ parray unknown_a1; /* 0028:---- */ parray tech_menu_shortcut_entries; /* 0050:---- */ parray choice_search_config; @@ -885,7 +885,7 @@ struct PlayerConfig { /* 2124:1FD0 */ be_uint32_t online_clv_exp; // CLvOn = this / 100 struct PlayerReference { /* 00 */ be_uint32_t guild_card_number; - /* 04 */ ptext player_name; + /* 04 */ pstring player_name; } __attribute__((packed)); // This array is updated when a battle is started (via a 6xB4x05 command). The // client adds the opposing players' info to ths first two entries here if the @@ -1198,10 +1198,10 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 1D68 */ parray unknown_a5; /* 1DDC */ Rules default_rules; - /* 1DF0 */ ptext name; - /* 1E04 */ ptext location_name; - /* 1E18 */ ptext quest_name; // == location_name if not a quest - /* 1E54 */ ptext description; + /* 1DF0 */ pstring name; + /* 1E04 */ pstring location_name; + /* 1E18 */ pstring quest_name; // == location_name if not a quest + /* 1E54 */ pstring description; // These fields describe where the map cursor on the preview screen should // scroll to @@ -1209,7 +1209,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 1FE6 */ be_uint16_t map_y; struct NPCDeck { - /* 00 */ ptext name; + /* 00 */ pstring name; /* 18 */ parray card_ids; // Last one appears to always be FFFF /* 58 */ } __attribute__((packed)); @@ -1223,7 +1223,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 0000 */ parray unknown_a1; /* 0004 */ uint8_t is_arkz; /* 0005 */ parray unknown_a2; - /* 0008 */ ptext name; + /* 0008 */ pstring name; // TODO: Figure out exactly how these are used and document here. /* 0018 */ parray params; /* 0114 */ @@ -1257,9 +1257,9 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // appears after the battle if it's not blank. dispatch_message appears right // before the player chooses a deck if it's not blank; usually it says // something like "You can only dispatch ". - /* 2440 */ ptext before_message; - /* 25D0 */ ptext after_message; - /* 2760 */ ptext dispatch_message; + /* 2440 */ pstring before_message; + /* 25D0 */ pstring after_message; + /* 2760 */ pstring dispatch_message; struct DialogueSet { // Dialogue sets specify lines that COMs can say at certain points during @@ -1277,7 +1277,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 0002 */ be_uint16_t percent_chance; // 0-100, or FFFF if unused // If the dialogue set activates, the game randomly chooses one of these // strings, excluding any that are empty or begin with the character '^'. - /* 0004 */ parray, 4> strings; + /* 0004 */ parray, 4> strings; /* 0104 */ } __attribute__((packed)); @@ -1375,7 +1375,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // text may differ. void assert_semantically_equivalent(const MapDefinition& other) const; - std::string str(const CardIndex* card_index = nullptr) const; + std::string str(const CardIndex* card_index, uint8_t language) const; } __attribute__((packed)); struct MapDefinitionTrial { @@ -1396,19 +1396,19 @@ struct MapDefinitionTrial { /* 1C68 */ parray, 0x10> modification_tiles; /* 1D68 */ parray unknown_a5; /* 1DD4 */ RulesTrial default_rules; - /* 1DE8 */ ptext name; - /* 1DFC */ ptext location_name; - /* 1E10 */ ptext quest_name; - /* 1E4C */ ptext description; + /* 1DE8 */ pstring name; + /* 1DFC */ pstring location_name; + /* 1E10 */ pstring quest_name; + /* 1E4C */ pstring description; /* 1FDC */ be_uint16_t map_x; /* 1FDE */ be_uint16_t map_y; /* 1FE0 */ parray npc_decks; /* 20E8 */ parray npc_ai_params; /* 2424 */ parray unknown_a7; /* 242C */ parray npc_ai_params_entry_index; - /* 2438 */ ptext before_message; - /* 25C8 */ ptext after_message; - /* 2758 */ ptext dispatch_message; + /* 2438 */ pstring before_message; + /* 25C8 */ pstring after_message; + /* 2758 */ pstring dispatch_message; /* 28E8 */ parray, 3> dialogue_sets; /* 4148 */ parray reward_card_ids; /* 4168 */ be_int32_t win_level_override; diff --git a/src/Episode3/DeckState.cc b/src/Episode3/DeckState.cc index a54ba754..7d18515c 100644 --- a/src/Episode3/DeckState.cc +++ b/src/Episode3/DeckState.cc @@ -305,7 +305,7 @@ void DeckState::print(FILE* stream, std::shared_ptr card_index) } } if (ce) { - string name = ce->def.en_name; + string name = ce->def.en_name.decode(1); fprintf(stream, " (%02zu) index=%02hhX ref=@%04hX card_id=#%04hX \"%s\" %s\n", z, e.deck_index, this->card_refs[z], e.card_id, name.c_str(), name_for_card_state(e.state)); } else { diff --git a/src/Episode3/DeckState.hh b/src/Episode3/DeckState.hh index d2f27774..710f5ec3 100644 --- a/src/Episode3/DeckState.hh +++ b/src/Episode3/DeckState.hh @@ -22,7 +22,7 @@ struct NameEntry { } __attribute__((packed)); struct DeckEntry { - ptext name; + pstring name; le_uint32_t team_id; parray card_ids; // If the following flag is not set to 3, then the God Whim assist effect can diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index b3208c8a..2dd68912 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -228,8 +228,8 @@ void Server::send_6xB4x46() const { } G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46; - cmd46.version_signature = VERSION_SIGNATURE; - cmd46.date_str1 = format_time(this->options.card_index->definitions_mtime() * 1000000); + cmd46.version_signature.encode(VERSION_SIGNATURE, 1); + cmd46.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1); string date_str2 = string_printf( "Lobby:%08" PRIX32 " Random:%08" PRIX32 "+%08" PRIX32, l->lobby_id, @@ -238,7 +238,7 @@ void Server::send_6xB4x46() const { if (this->last_chosen_map) { date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map_number); } - cmd46.date_str2 = date_str2; + cmd46.date_str2.encode(date_str2, 1); this->send(cmd46); } @@ -255,7 +255,7 @@ string Server::prepare_6xB6x41_map_definition( return std::move(w.str()); } -void Server::send_commands_for_joining_spectator(Channel& c, uint8_t language, bool is_trial) const { +void Server::send_commands_for_joining_spectator(Channel& ch, bool is_trial) const { bool should_send_state = true; if (this->setup_phase == SetupPhase::REGISTRATION) { // If registration is still in progress, we only need to send the map data @@ -267,38 +267,38 @@ void Server::send_commands_for_joining_spectator(Channel& c, uint8_t language, b } if (this->last_chosen_map) { - string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, language, is_trial); - this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(language), this->last_chosen_map->map_number); - c.send(0x6C, 0x00, data); + string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch.language, is_trial); + this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(ch.language), this->last_chosen_map->map_number); + ch.send(0x6C, 0x00, data); } if (should_send_state) { - c.send(0xC9, 0x00, this->prepare_6xB4x03()); + ch.send(0xC9, 0x00, this->prepare_6xB4x03()); for (uint8_t client_id = 0; client_id < 4; client_id++) { auto ps = this->player_states[client_id]; if (ps) { - c.send(0xC9, 0x00, ps->prepare_6xB4x02()); - c.send(0xC9, 0x00, ps->prepare_6xB4x04()); + ch.send(0xC9, 0x00, ps->prepare_6xB4x02()); + ch.send(0xC9, 0x00, ps->prepare_6xB4x04()); } } { G_UpdateMap_GC_Ep3_6xB4x05 cmd_05; cmd_05.state = *this->map_and_rules; - this->send(cmd_05); + ch.send(0xC9, 0x00, cmd_05); } // TODO: Sega does something like this; do we have to do this too? // for (uint8_t client_id = 0; client_id < 4; client_id++) { // (send 6xB4x4E, 6xB4x4C, 6xB4x4D for each set card) // (send 6xB4x4F for client_id) // } - c.send(0xC9, 0x00, this->prepare_6xB4x07_decks_update()); + ch.send(0xC9, 0x00, this->prepare_6xB4x07_decks_update()); // TODO: Sega sends 6xB4x05 here again; why? Is that necessary? They also // send 6xB4x02 again for each player after that (but not 6xB4x04) - c.send(0xC9, 0x00, this->prepare_6xB4x1C_names_update()); - c.send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations()); + ch.send(0xC9, 0x00, this->prepare_6xB4x1C_names_update()); + ch.send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations()); { G_LoadCurrentEnvironment_GC_Ep3_6xB4x3B cmd_3B; - c.send(0xC9, 0x00, &cmd_3B, sizeof(cmd_3B)); + ch.send(0xC9, 0x00, &cmd_3B, sizeof(cmd_3B)); } } } @@ -310,8 +310,7 @@ __attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(con va_start(va, fmt); std::string buf = string_vprintf(fmt, va); va_end(va); - std::u16string decoded = decode_sjis(buf); - send_text_message(l, decoded.c_str()); + send_text_message(l, buf); } } @@ -322,8 +321,7 @@ __attribute__((format(printf, 2, 3))) void Server::send_info_message_printf(cons va_start(va, fmt); std::string buf = string_vprintf(fmt, va); va_end(va); - std::u16string decoded = decode_sjis(buf); - send_text_message(l, decoded.c_str()); + send_text_message(l, buf); } } @@ -2317,8 +2315,7 @@ void Server::handle_CAx40_map_list_request(shared_ptr sender_c, const st throw runtime_error("lobby is deleted"); } - const auto& list_data = this->options.map_index->get_compressed_list( - l->count_clients(), sender_c->language); + const auto& list_data = this->options.map_index->get_compressed_list(l->count_clients(), sender_c->language()); StringWriter w; uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3); @@ -2347,15 +2344,15 @@ void Server::send_6xB6x41_to_all_clients() const { if (!c) { return; } - if (map_commands_by_language.size() <= c->language) { - map_commands_by_language.resize(c->language + 1); + if (map_commands_by_language.size() <= c->language()) { + map_commands_by_language.resize(c->language() + 1); } - if (map_commands_by_language[c->language].empty()) { - map_commands_by_language[c->language] = this->prepare_6xB6x41_map_definition( - this->last_chosen_map, c->language, l->flags & Lobby::Flag::IS_EP3_TRIAL); + if (map_commands_by_language[c->language()].empty()) { + map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition( + this->last_chosen_map, c->language(), l->flags & Lobby::Flag::IS_EP3_TRIAL); } - this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language), this->last_chosen_map->map_number); - send_command(c, 0x6C, 0x00, map_commands_by_language[c->language]); + this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language()), this->last_chosen_map->map_number); + send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]); }; for (const auto& c : l->clients) { send_to_client(c); diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 18fd40f4..b2d6d9a2 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -112,7 +112,7 @@ public: this->send(&cmd, cmd.header.size * 4); } void send(const void* data, size_t size) const; - void send_commands_for_joining_spectator(Channel& ch, uint8_t language, bool is_trial) const; + void send_commands_for_joining_spectator(Channel& ch, bool is_trial) const; void force_battle_result(uint8_t surrendered_client_id, bool set_winner); void force_destroy_field_character(uint8_t client_id, size_t set_index); diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index 5c49b2e5..f2a767f8 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -16,7 +16,7 @@ Tournament::PlayerEntry::PlayerEntry(uint32_t serial_number, const string& playe Tournament::PlayerEntry::PlayerEntry(shared_ptr c) : serial_number(c->license->serial_number), client(c), - player_name(encode_sjis(c->game_data.player()->disp.name)) {} + player_name(c->game_data.player()->disp.name.decode(c->language())) {} Tournament::PlayerEntry::PlayerEntry( shared_ptr com_deck) @@ -743,7 +743,7 @@ void Tournament::print_bracket(FILE* stream) const { fprintf(stream, "Tournament \"%s\"\n", this->name.c_str()); auto en_vm = this->map->version(1); if (en_vm) { - string map_name = en_vm->map->name; + string map_name = en_vm->map->name.decode(en_vm->language); fprintf(stream, " Map: %08" PRIX32 " (%s)\n", this->map->map_number, map_name.c_str()); } else { fprintf(stream, " Map: %08" PRIX32 "\n", this->map->map_number); diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc index c3eefacc..b2292b5e 100644 --- a/src/FunctionCompiler.cc +++ b/src/FunctionCompiler.cc @@ -236,13 +236,12 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { shared_ptr FunctionCodeIndex::patch_menu(uint32_t specific_version) const { auto suffix = string_printf("-%08" PRIX32, specific_version); - shared_ptr ret(new Menu(MenuID::PATCHES, u"Patches")); - ret->items.emplace_back(PatchesMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0); + shared_ptr ret(new Menu(MenuID::PATCHES, "Patches")); + ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0); for (const auto& it : this->name_and_specific_version_to_patch_function) { const auto& fn = it.second; if (!fn->hide_from_patches_menu && ends_with(it.first, suffix)) { - ret->items.emplace_back(fn->menu_item_id, decode_sjis(fn->patch_name), u"", - MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); + ret->items.emplace_back(fn->menu_item_id, fn->patch_name, "", MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); } } return ret; @@ -267,9 +266,9 @@ DOLFileIndex::DOLFileIndex(const string& directory) { return; } - shared_ptr menu(new Menu(MenuID::PROGRAMS, u"Programs")); + shared_ptr menu(new Menu(MenuID::PROGRAMS, "Programs")); this->menu = menu; - menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0); + menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0); uint32_t next_menu_item_id = 0; for (const auto& filename : list_directory_sorted(directory)) { @@ -326,8 +325,7 @@ DOLFileIndex::DOLFileIndex(const string& directory) { this->name_to_file.emplace(dol->name, dol); this->item_id_to_file.emplace_back(dol); - menu->items.emplace_back(dol->menu_item_id, decode_sjis(dol->name), - decode_sjis(description), MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); + menu->items.emplace_back(dol->menu_item_id, dol->name, description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); } catch (const exception& e) { function_compiler_log.warning("Failed to load DOL file %s: %s", filename.c_str(), e.what()); diff --git a/src/GSLArchive.cc b/src/GSLArchive.cc index ce40efde..318bbe69 100644 --- a/src/GSLArchive.cc +++ b/src/GSLArchive.cc @@ -12,7 +12,7 @@ template struct GSLHeaderEntry { using U32T = typename std::conditional::type; - ptext filename; + pstring filename; U32T offset; // In pages, so actual offset is this * 0x800 U32T size; uint64_t unused; @@ -24,14 +24,14 @@ void GSLArchive::load_t() { uint64_t min_data_offset = 0xFFFFFFFFFFFFFFFF; while (r.where() < min_data_offset) { const auto& entry = r.get>(); - if (entry.filename.len() == 0) { + if (entry.filename.empty()) { break; } uint64_t offset = static_cast(entry.offset) * 0x800; if (offset + entry.size > this->data->size()) { throw runtime_error("GSL entry extends beyond end of data"); } - this->entries.emplace(entry.filename, Entry{offset, entry.size}); + this->entries.emplace(entry.filename.decode(), Entry{offset, entry.size}); } } diff --git a/src/GVMEncoder.cc b/src/GVMEncoder.cc index 309813a7..0012413f 100644 --- a/src/GVMEncoder.cc +++ b/src/GVMEncoder.cc @@ -26,7 +26,7 @@ static uint32_t encode_argb8888(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { struct GVMFileEntry { be_uint16_t file_num; - ptext name; + pstring name; parray unknown_a1; } __attribute__((packed)); @@ -78,7 +78,7 @@ string encode_gvm(const Image& img, GVRDataFormat data_format) { w.put({.magic = 0x47564D48, .header_size = 0x48, .flags = 0x010F, .num_files = 1}); GVMFileEntry file_entry; file_entry.file_num = 0; - file_entry.name = "img"; + file_entry.name.encode("img", 1); file_entry.unknown_a1.clear(0); w.put(file_entry); w.extend_to(0x50, 0x00); diff --git a/src/ItemParameterTable.hh b/src/ItemParameterTable.hh index 46c7f59b..22f46acd 100644 --- a/src/ItemParameterTable.hh +++ b/src/ItemParameterTable.hh @@ -19,6 +19,8 @@ public: } __attribute__((packed)); struct ItemBase { + // id specifies several things; notably, it doubles as the index of the + // item's name in the text archive (e.g. TextEnglish) collection 0. le_uint32_t id; le_uint16_t type; le_uint16_t skin; diff --git a/src/License.cc b/src/License.cc index 4e4084b0..cad96223 100644 --- a/src/License.cc +++ b/src/License.cc @@ -82,11 +82,11 @@ string License::str() const { } struct BinaryLicense { - ptext username; // BB username (max. 16 chars; should technically be Unicode) - ptext bb_password; // BB password (max. 16 chars) + pstring username; // BB username (max. 16 chars; should technically be Unicode) + pstring 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. - 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 + pstring 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) + pstring gc_password; // GC password uint32_t privileges; // privilege level uint64_t ban_end_time; // end time of ban (zero = not banned) } __attribute__((packed)); @@ -107,10 +107,10 @@ LicenseIndex::LicenseIndex() { } catch (const missing_license&) { License license; license.serial_number = bin_license.serial_number; - license.access_key = bin_license.access_key; - license.gc_password = bin_license.gc_password; - license.bb_username = bin_license.username; - license.bb_password = bin_license.bb_password; + license.access_key = bin_license.access_key.decode(); + license.gc_password = bin_license.gc_password.decode(); + license.bb_username = bin_license.username.decode(); + license.bb_password = bin_license.bb_password.decode(); license.flags = bin_license.privileges; license.ban_end_time = bin_license.ban_end_time; license.ep3_current_meseta = 0; diff --git a/src/Lobby.cc b/src/Lobby.cc index be353cd9..b199b90d 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -191,11 +191,11 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { PlayerLobbyDataDCGC lobby_data; lobby_data.player_tag = 0x00010000; lobby_data.guild_card = c->license->serial_number; - lobby_data.name = encode_sjis(p->disp.name); + lobby_data.name.encode(p->disp.name.decode(c->language()), c->language()); this->battle_record->add_player( lobby_data, p->inventory, - p->disp.to_dcpcv3(), + p->disp.to_dcpcv3(c->language(), c->language()), c->game_data.ep3_config ? (c->game_data.ep3_config->online_clv_exp / 100) : 0); } @@ -276,18 +276,18 @@ void Lobby::move_client_to_lobby( dest_lobby->add_client(c, required_client_id); } -shared_ptr Lobby::find_client( - const u16string* identifier, uint64_t serial_number) { +shared_ptr Lobby::find_client(const string* identifier, uint64_t serial_number) { for (size_t x = 0; x < this->max_clients; x++) { - if (!this->clients[x]) { + auto lc = this->clients[x]; + if (!lc) { continue; } - if (serial_number && this->clients[x]->license && - (this->clients[x]->license->serial_number == serial_number)) { - return this->clients[x]; + if (serial_number && lc->license && + (lc->license->serial_number == serial_number)) { + return lc; } - if (identifier && (this->clients[x]->game_data.player()->disp.name == *identifier)) { - return this->clients[x]; + if (identifier && (lc->game_data.player()->disp.name.eq(*identifier, lc->language()))) { + return lc; } } diff --git a/src/Lobby.hh b/src/Lobby.hh index 0e16bd8f..3eabbe66 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -80,8 +80,8 @@ struct Lobby : public std::enable_shared_from_this { GameMode mode; uint8_t difficulty; // 0-3 uint16_t exp_multiplier; - std::u16string password; - std::u16string name; + std::string password; + std::string name; // This seed is also sent to the client for rare enemy generation uint32_t random_seed; std::shared_ptr random_crypt; @@ -154,7 +154,7 @@ struct Lobby : public std::enable_shared_from_this { ssize_t required_client_id = -1); std::shared_ptr find_client( - const std::u16string* identifier = nullptr, + const std::string* identifier = nullptr, uint64_t serial_number = 0); void add_item(const ItemData& item, uint8_t area, float x, float z); diff --git a/src/Main.cc b/src/Main.cc index 812055cb..a9c0adbe 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -312,7 +312,6 @@ enum class Behavior { DECODE_QUEST_FILE, ENCODE_QST, DISASSEMBLE_QUEST_SCRIPT, - DECODE_SJIS, EXTRACT_AFS, EXTRACT_GSL, EXTRACT_BML, @@ -368,7 +367,6 @@ static bool behavior_takes_input_filename(Behavior b) { (b == Behavior::DECODE_QUEST_FILE) || (b == Behavior::ENCODE_QST) || (b == Behavior::DISASSEMBLE_QUEST_SCRIPT) || - (b == Behavior::DECODE_SJIS) || (b == Behavior::FORMAT_RARE_ITEM_SET) || (b == Behavior::CONVERT_ITEMRT_REL_TO_JSON) || (b == Behavior::CONVERT_ITEMRT_GSL_TO_JSON) || @@ -415,7 +413,6 @@ static bool behavior_takes_output_filename(Behavior b) { (b == Behavior::CONVERT_ITEMRT_REL_TO_JSON) || (b == Behavior::CONVERT_ITEMRT_GSL_TO_JSON) || (b == Behavior::CONVERT_ITEMRT_AFS_TO_JSON) || - (b == Behavior::DECODE_SJIS) || (b == Behavior::EXTRACT_AFS) || (b == Behavior::EXTRACT_GSL) || (b == Behavior::EXTRACT_BML) || @@ -611,8 +608,6 @@ int main(int argc, char** argv) { behavior = Behavior::FIND_DECRYPTION_SEED; } else if (!strcmp(argv[x], "salvage-gci")) { behavior = Behavior::SALVAGE_GCI; - } else if (!strcmp(argv[x], "decode-sjis")) { - behavior = Behavior::DECODE_SJIS; } else if (!strcmp(argv[x], "decode-gci")) { behavior = Behavior::DECODE_QUEST_FILE; quest_file_type = QuestFileFormat::BIN_DAT_GCI; @@ -1000,10 +995,12 @@ int main(int argc, char** argv) { case Behavior::ENCRYPT_CHALLENGE_DATA: case Behavior::DECRYPT_CHALLENGE_DATA: { string data = read_input_data(); - string result = (behavior == Behavior::DECRYPT_CHALLENGE_DATA) - ? decrypt_challenge_rank_text(data) - : encrypt_challenge_rank_text(data); - write_output_data(result.data(), result.size()); + if (behavior == Behavior::DECRYPT_CHALLENGE_DATA) { + decrypt_challenge_rank_text_t(data.data(), data.size()); + } else { + encrypt_challenge_rank_text_t(data.data(), data.size()); + } + write_output_data(data.data(), data.size()); break; } @@ -1414,18 +1411,11 @@ int main(int argc, char** argv) { if (!expect_decompressed) { data = prs_decompress(data); } - string result = disassemble_quest_script(data.data(), data.size(), cli_quest_version); + string result = disassemble_quest_script(data.data(), data.size(), cli_quest_version, 1); write_output_data(result.data(), result.size()); break; } - case Behavior::DECODE_SJIS: { - string data = read_input_data(); - auto decoded = decode_sjis(data); - write_output_data(decoded.data(), decoded.size() * sizeof(decoded[0])); - break; - } - case Behavior::EXTRACT_AFS: case Behavior::EXTRACT_GSL: case Behavior::EXTRACT_BML: { @@ -1516,8 +1506,8 @@ int main(int argc, char** argv) { case Behavior::DECODE_UNICODE_TEXT_SET: { auto strings = parse_unicode_text_set(read_input_data()); JSON j = JSON::list(); - for (const u16string& s : strings) { - j.emplace_back(encode_sjis(s)); + for (const string& s : strings) { + j.emplace_back(s); } string out_data = j.serialize(JSON::SerializeOption::FORMAT); write_output_data(out_data.data(), out_data.size()); @@ -1525,9 +1515,9 @@ int main(int argc, char** argv) { } case Behavior::ENCODE_UNICODE_TEXT_SET: { auto json = JSON::parse(read_input_data()); - vector strings; + vector strings; for (const auto& s_json : json.as_list()) { - strings.emplace_back(decode_sjis(s_json->as_string())); + strings.emplace_back(s_json->as_string()); } string encoded = serialize_unicode_text_set(strings); write_output_data(encoded.data(), encoded.size()); @@ -1982,7 +1972,7 @@ int main(int argc, char** argv) { if (!vms[language]) { continue; } - string s = vms[language]->map->str(&card_index); + string s = vms[language]->map->str(&card_index, language); fprintf(stdout, "(%c) %s\n", char_for_language_code(language), s.c_str()); } } diff --git a/src/Menu.cc b/src/Menu.cc index 5d1c6bb2..df3e199f 100644 --- a/src/Menu.cc +++ b/src/Menu.cc @@ -2,24 +2,20 @@ using namespace std; -MenuItem::MenuItem( - uint32_t item_id, const u16string& name, - const u16string& description, uint32_t flags) +MenuItem::MenuItem(uint32_t item_id, const string& name, const string& description, uint32_t flags) : item_id(item_id), name(name), description(description), get_description(nullptr), flags(flags) {} -MenuItem::MenuItem( - uint32_t item_id, const u16string& name, - std::function get_description, uint32_t flags) +MenuItem::MenuItem(uint32_t item_id, const string& name, std::function get_description, uint32_t flags) : item_id(item_id), name(name), description(), get_description(std::move(get_description)), flags(flags) {} -Menu::Menu(uint32_t menu_id, const std::u16string& name) +Menu::Menu(uint32_t menu_id, const std::string& name) : menu_id(menu_id), name(name) {} diff --git a/src/Menu.hh b/src/Menu.hh index d5baad0f..042ac75f 100644 --- a/src/Menu.hh +++ b/src/Menu.hh @@ -106,22 +106,20 @@ struct MenuItem { }; uint32_t item_id; - std::u16string name; - std::u16string description; - std::function get_description; + std::string name; + std::string description; + std::function get_description; uint32_t flags; - MenuItem(uint32_t item_id, const std::u16string& name, - const std::u16string& description, uint32_t flags); - MenuItem(uint32_t item_id, const std::u16string& name, - std::function get_description, uint32_t flags); + MenuItem(uint32_t item_id, const std::string& name, const std::string& description, uint32_t flags); + MenuItem(uint32_t item_id, const std::string& name, std::function get_description, uint32_t flags); }; struct Menu { uint32_t menu_id; - std::u16string name; + std::string name; std::vector items; Menu() = delete; - Menu(uint32_t menu_id, const std::u16string& name); + Menu(uint32_t menu_id, const std::string& name); }; diff --git a/src/PSOEncryption.cc b/src/PSOEncryption.cc index 2511c4c1..b6d73f18 100644 --- a/src/PSOEncryption.cc +++ b/src/PSOEncryption.cc @@ -932,63 +932,6 @@ uint16_t decrypt_challenge_time(uint32_t value) { : ((mask ^ value) & 0xFFFF); } -template -StringT crypt_challenge_rank_text(const void* src, size_t count) { - if (count == 0) { - return StringT(); - } - - StringT ret; - StringReader r(src, count * sizeof(typename StringT::value_type)); - typename StringT::value_type prev = 0; - while (!r.eof()) { - typename StringT::value_type ch = r.get(); - if (ch == 0) { - break; - } - if (ret.empty()) { - ret.push_back(ch ^ 0x7F); - } else { - ret.push_back((IsEncrypt ? ((ch - prev) ^ 0x7F) : ((ch ^ 0x7F) + ret.back())) & 0xFF); - } - prev = ch; - } - - return ret; -} - -string encrypt_challenge_rank_text(const char* src, size_t count) { - return crypt_challenge_rank_text(src, count); -} - -string decrypt_challenge_rank_text(const char* src, size_t count) { - return crypt_challenge_rank_text(src, count); -} - -u16string encrypt_challenge_rank_text(const char16_t* src, size_t count) { - return crypt_challenge_rank_text(src, count); -} - -u16string decrypt_challenge_rank_text(const char16_t* src, size_t count) { - return crypt_challenge_rank_text(src, count); -} - -std::string decrypt_challenge_rank_text(const std::string& data) { - return decrypt_challenge_rank_text(data.data(), data.size()); -} - -std::string encrypt_challenge_rank_text(const std::string& data) { - return encrypt_challenge_rank_text(data.data(), data.size()); -} - -std::u16string decrypt_challenge_rank_text(const std::u16string& data) { - return decrypt_challenge_rank_text(data.data(), data.size()); -} - -std::u16string encrypt_challenge_rank_text(const std::u16string& data) { - return encrypt_challenge_rank_text(data.data(), data.size()); -} - string decrypt_v2_registry_value(const void* data, size_t size) { string ret(reinterpret_cast(data), size); PSOV2Encryption crypt(0x66); diff --git a/src/PSOEncryption.hh b/src/PSOEncryption.hh index 50819478..8bec3046 100644 --- a/src/PSOEncryption.hh +++ b/src/PSOEncryption.hh @@ -250,35 +250,6 @@ void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis); uint32_t encrypt_challenge_time(uint16_t value); uint16_t decrypt_challenge_time(uint32_t value); -std::string decrypt_challenge_rank_text(const char* data, size_t count); -std::string decrypt_challenge_rank_text(const std::string& data); -std::string encrypt_challenge_rank_text(const char* data, size_t count); -std::string encrypt_challenge_rank_text(const std::string& data); -std::u16string decrypt_challenge_rank_text(const char16_t* data, size_t count); -std::u16string decrypt_challenge_rank_text(const std::u16string& data); -std::u16string encrypt_challenge_rank_text(const char16_t* data, size_t count); -std::u16string encrypt_challenge_rank_text(const std::u16string& data); - -template -std::string decrypt_challenge_rank_text(const ptext& data) { - return decrypt_challenge_rank_text(data.data(), data.size()); -} - -template -std::u16string decrypt_challenge_rank_text(const ptext& data) { - return decrypt_challenge_rank_text(data.data(), data.size()); -} - -template -std::string encrypt_challenge_rank_text(const ptext& data) { - return encrypt_challenge_rank_text(data.data(), data.size()); -} - -template -std::u16string encrypt_challenge_rank_text(const ptext& data) { - return encrypt_challenge_rank_text(data.data(), data.size()); -} - std::string decrypt_v2_registry_value(const void* data, size_t size); struct DecryptedPR2 { diff --git a/src/Player.cc b/src/Player.cc index f0b34305..dbbd5208 100644 --- a/src/Player.cc +++ b/src/Player.cc @@ -138,7 +138,7 @@ shared_ptr ClientGameData::account(bool allow_load) { if (!this->account_data.get() && allow_load) { if (this->bb_username.empty()) { this->account_data.reset(new SavedAccountDataBB()); - this->account_data->signature = ACCOUNT_FILE_SIGNATURE; + this->account_data->signature.encode(ACCOUNT_FILE_SIGNATURE); } else { this->load_account_data(); } @@ -223,7 +223,7 @@ void ClientGameData::load_account_data() { try { data.reset(new SavedAccountDataBB( player_files_cache.get_obj_or_load(filename).obj)); - if (data->signature != ACCOUNT_FILE_SIGNATURE) { + if (!data->signature.eq(ACCOUNT_FILE_SIGNATURE)) { throw runtime_error("account data header is incorrect"); } player_data_log.info("Loaded account data file %s", filename.c_str()); @@ -236,7 +236,7 @@ void ClientGameData::load_account_data() { player_files_cache.get_obj_or_load( "system/players/default.nsa") .obj)); - if (data->signature != ACCOUNT_FILE_SIGNATURE) { + if (!data->signature.eq(ACCOUNT_FILE_SIGNATURE)) { throw runtime_error("default account data header is incorrect"); } player_data_log.info("Loaded default account data file"); diff --git a/src/Player.hh b/src/Player.hh index f6e1dbdc..88dcc5cd 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -36,12 +36,12 @@ struct SavedPlayerDataBB { // .nsc file format /* 0008 */ parray unused; /* 0028 */ PlayerRecords_Battle battle_records; /* 0040 */ PlayerDispDataBBPreview preview; - /* 00BC */ ptext auto_reply; + /* 00BC */ pstring auto_reply; /* 0214 */ PlayerBank bank; /* 14DC */ PlayerRecordsBB_Challenge challenge_records; /* 161C */ PlayerDispDataBB disp; - /* 17AC */ ptext guild_card_description; - /* 185C */ ptext info_board; + /* 17AC */ pstring guild_card_description; + /* 185C */ pstring info_board; /* 19B4 */ PlayerInventory inventory; /* 1D00 */ parray quest_data1; /* 1F08 */ parray quest_data2; @@ -80,7 +80,7 @@ enum AccountFlag { }; struct SavedAccountDataBB { // .nsa file format - ptext signature; + pstring signature; parray blocked_senders; GuildCardFileBB guild_cards; KeyAndTeamConfigBB key_config; @@ -88,7 +88,7 @@ struct SavedAccountDataBB { // .nsa file format le_uint32_t option_flags; parray shortcuts; parray symbol_chats; - ptext team_name; + pstring team_name; } __attribute__((packed)); class ClientGameData { diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 73d6b16c..9eb7d53d 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -134,29 +134,38 @@ void PlayerDispDataDCPCV3::enforce_lobby_join_limits(GameVersion target_version) this->visual.compute_name_color_checksum(); this->visual.class_flags = class_flags_for_class(this->visual.char_class); + + if (this->visual.name.at(0) == '\t' && (this->visual.name.at(1) == 'J' || this->visual.name.at(1) == 'E')) { + this->visual.name.encode(this->visual.name.decode().substr(2)); + } } -void PlayerDispDataBB::enforce_lobby_join_limits(GameVersion) { +void PlayerDispDataBB::enforce_lobby_join_limits(GameVersion version) { + if (version != GameVersion::BB) { + 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() const { +PlayerDispDataBB PlayerDispDataDCPCV3::to_bb(uint8_t to_language, uint8_t from_language) const { PlayerDispDataBB bb; bb.stats = this->stats; bb.visual = this->visual; - bb.visual.name = " 0"; - bb.name = this->visual.name; + bb.visual.name.encode(" 0"); + bb.name.encode(this->visual.name.decode(from_language), to_language); bb.config = this->config; bb.technique_levels_v1 = this->technique_levels_v1; return bb; } -PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3() const { +PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3(uint8_t to_language, uint8_t from_language) const { PlayerDispDataDCPCV3 ret; ret.stats = this->stats; ret.visual = this->visual; - ret.visual.name = this->name; - remove_language_marker_inplace(ret.visual.name); + ret.visual.name.encode(this->name.decode(from_language), to_language); ret.config = this->config; ret.technique_levels_v1 = this->technique_levels_v1; return ret; @@ -203,9 +212,9 @@ void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) { void GuildCardBB::clear() { this->guild_card_number = 0; - this->name.clear(0); - this->team_name.clear(0); - this->description.clear(0); + this->name.clear(); + this->team_name.clear(); + this->description.clear(); this->present = 0; this->language = 0; this->section_id = 0; @@ -240,7 +249,7 @@ void PlayerLobbyDataPC::clear() { this->guild_card = 0; this->ip_address = 0; this->client_id = 0; - ptext name; + this->name.clear(); } void PlayerLobbyDataDCGC::clear() { @@ -248,7 +257,7 @@ void PlayerLobbyDataDCGC::clear() { this->guild_card = 0; this->ip_address = 0; this->client_id = 0; - ptext name; + this->name.clear(); } void XBNetworkLocation::clear() { @@ -266,7 +275,7 @@ void PlayerLobbyDataXB::clear() { this->guild_card = 0; this->netloc.clear(); this->client_id = 0; - this->name.clear(0); + this->name.clear(); } void PlayerLobbyDataBB::clear() { @@ -275,7 +284,7 @@ void PlayerLobbyDataBB::clear() { this->ip_address = 0; this->unknown_a1.clear(0); this->client_id = 0; - this->name.clear(0); + this->name.clear(); this->unknown_a2 = 0; } @@ -289,11 +298,11 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Chall grave_deaths(rec.grave_deaths), unknown_u4(0), grave_coords_time(rec.grave_coords_time), - grave_team(rec.grave_team), - grave_message(rec.grave_message), + grave_team(rec.grave_team.decode(), 1), + grave_message(rec.grave_message.decode(), 1), unknown_m5(0), unknown_t6(0), - rank_title(encrypt_challenge_rank_text(decode_sjis(decrypt_challenge_rank_text(rec.rank_title)))), + rank_title(rec.rank_title.decode(), 1), unknown_l7(0) {} PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Challenge& rec) @@ -306,8 +315,8 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Chall grave_deaths(rec.grave_deaths), unknown_u4(0), grave_coords_time(rec.grave_coords_time), - grave_team(rec.grave_team), - grave_message(rec.grave_message), + grave_team(rec.grave_team.decode(), 1), + grave_message(rec.grave_message.decode(), 1), unknown_m5(0), unknown_t6(0), rank_title(rec.rank_title), @@ -323,24 +332,24 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsV3_Chall grave_deaths(rec.stats.grave_deaths), unknown_u4(rec.stats.unknown_u4), grave_coords_time(rec.stats.grave_coords_time), - grave_team(rec.stats.grave_team), - grave_message(rec.stats.grave_message), + grave_team(rec.stats.grave_team.decode(), 1), + grave_message(rec.stats.grave_message.decode(), 1), unknown_m5(rec.stats.unknown_m5), unknown_t6(rec.stats.unknown_t6), - rank_title(encrypt_challenge_rank_text(decode_sjis(decrypt_challenge_rank_text(rec.rank_title)))), + rank_title(rec.rank_title.decode(), 1), unknown_l7(rec.unknown_l7) {} PlayerRecordsBB_Challenge::operator PlayerRecordsDC_Challenge() const { PlayerRecordsDC_Challenge ret; ret.title_color = this->title_color; ret.unknown_u0 = this->unknown_u0; - ret.rank_title = encrypt_challenge_rank_text(encode_sjis(decrypt_challenge_rank_text(this->rank_title))); + ret.rank_title.encode(this->rank_title.decode()); ret.times_ep1_online = this->times_ep1_online; ret.unknown_g3 = 0; ret.grave_deaths = this->grave_deaths; ret.grave_coords_time = this->grave_coords_time; - ret.grave_team = this->grave_team; - ret.grave_message = this->grave_message; + ret.grave_team.encode(this->grave_team.decode()); + ret.grave_message.encode(this->grave_message.decode()); ret.times_ep1_offline = this->times_ep1_offline; ret.unknown_l4.clear(0); return ret; @@ -355,8 +364,8 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const { ret.unknown_g3 = 0; ret.grave_deaths = this->grave_deaths; ret.grave_coords_time = this->grave_coords_time; - ret.grave_team = this->grave_team; - ret.grave_message = this->grave_message; + ret.grave_team.encode(this->grave_team.decode()); + ret.grave_message.encode(this->grave_message.decode()); ret.times_ep1_offline = this->times_ep1_offline; ret.unknown_l4.clear(0); return ret; @@ -373,11 +382,11 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsV3_Challenge() const { ret.stats.grave_deaths = this->grave_deaths; ret.stats.unknown_u4 = this->unknown_u4; ret.stats.grave_coords_time = this->grave_coords_time; - ret.stats.grave_team = this->grave_team; - ret.stats.grave_message = this->grave_message; + ret.stats.grave_team.encode(this->grave_team.decode()); + ret.stats.grave_message.encode(this->grave_message.decode()); ret.stats.unknown_m5 = this->unknown_m5; ret.stats.unknown_t6 = this->unknown_t6; - ret.rank_title = encrypt_challenge_rank_text(encode_sjis(decrypt_challenge_rank_text(this->rank_title))); + ret.rank_title.encode(this->rank_title.decode()); ret.unknown_l7 = this->unknown_l7; return ret; } diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index cc2a3717..22002161 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -102,7 +102,7 @@ struct PlayerBank { struct PlayerDispDataBB; struct PlayerVisualConfig { - /* 00 */ ptext name; + /* 00 */ pstring name; /* 10 */ parray unknown_a2; /* 18 */ le_uint32_t name_color = 0x00000000; // ARGB /* 1C */ uint8_t extra_model = 0; @@ -149,7 +149,7 @@ struct PlayerDispDataDCPCV3 { /* BC */ parray technique_levels_v1; /* D0 */ void enforce_lobby_join_limits(GameVersion target_version); - PlayerDispDataBB to_bb() const; + PlayerDispDataBB to_bb(uint8_t to_language, uint8_t from_language) const; } __attribute__((packed)); struct PlayerDispDataBBPreview { @@ -158,7 +158,7 @@ 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 */ ptext name; + /* 58 */ pstring name; /* 78 */ uint32_t play_time = 0; /* 7C */ } __attribute__((packed)); @@ -167,7 +167,7 @@ struct PlayerDispDataBBPreview { struct PlayerDispDataBB { /* 0000 */ PlayerStats stats; /* 0024 */ PlayerVisualConfig visual; - /* 0074 */ ptext name; + /* 0074 */ pstring name; /* 008C */ le_uint32_t play_time = 0; /* 0090 */ uint32_t unknown_a3 = 0; /* 0094 */ parray config; @@ -175,18 +175,31 @@ struct PlayerDispDataBB { /* 0190 */ void enforce_lobby_join_limits(GameVersion target_version); - PlayerDispDataDCPCV3 to_dcpcv3() const; + 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)); +struct GuildCardDC { + /* 00 */ le_uint32_t player_tag = 0; + /* 04 */ le_uint32_t guild_card_number = 0; + /* 08 */ pstring name; + /* 20 */ pstring description; + /* 68 */ parray unused2; + /* 79 */ uint8_t present = 0; + /* 7A */ uint8_t language = 0; + /* 7B */ uint8_t section_id = 0; + /* 7C */ uint8_t char_class = 0; + /* 7D */ +} __attribute__((packed)); + struct GuildCardPC { /* 00 */ le_uint32_t player_tag = 0; /* 04 */ le_uint32_t guild_card_number = 0; // TODO: Is the length of the name field correct here? - /* 08 */ ptext name; - /* 38 */ ptext description; + /* 08 */ pstring name; + /* 38 */ pstring description; /* EC */ uint8_t present = 0; /* ED */ uint8_t language = 0; /* EE */ uint8_t section_id = 0; @@ -199,8 +212,8 @@ struct GuildCardPC { struct GuildCardV3 { /* 00 */ le_uint32_t player_tag = 0; /* 04 */ le_uint32_t guild_card_number = 0; - /* 08 */ ptext name; - /* 20 */ ptext description; + /* 08 */ pstring name; + /* 20 */ pstring description; /* 8C */ uint8_t present = 0; /* 8D */ uint8_t language = 0; /* 8E */ uint8_t section_id = 0; @@ -208,12 +221,11 @@ struct GuildCardV3 { /* 90 */ } __attribute__((packed)); -// BB guild card format struct GuildCardBB { /* 0000 */ le_uint32_t guild_card_number = 0; - /* 0004 */ ptext name; - /* 0034 */ ptext team_name; - /* 0054 */ ptext description; + /* 0004 */ pstring name; + /* 0034 */ pstring team_name; + /* 0054 */ pstring description; /* 0104 */ uint8_t present = 0; /* 0105 */ uint8_t language = 0; /* 0106 */ uint8_t section_id = 0; @@ -226,7 +238,7 @@ struct GuildCardBB { // an entry in the BB guild card file struct GuildCardEntryBB { GuildCardBB data; - ptext comment; + pstring comment; parray unknown_a1; void clear(); @@ -251,7 +263,7 @@ struct KeyAndTeamConfigBB { le_uint64_t team_info = 0; // 02C0 le_uint16_t team_privilege_level = 0; // 02C8 le_uint16_t reserved = 0; // 02CA - ptext team_name; // 02CC + pstring team_name; // 02CC parray team_flag; // 02EC le_uint32_t team_rewards = 0; // 0AEC } __attribute__((packed)); @@ -266,7 +278,7 @@ struct PlayerLobbyDataPC { // nonzero on all other versions too. be_uint32_t ip_address = 0x7F000001; le_uint32_t client_id = 0; - ptext name; + pstring name; void clear(); } __attribute__((packed)); @@ -276,7 +288,7 @@ struct PlayerLobbyDataDCGC { le_uint32_t guild_card = 0; be_uint32_t ip_address = 0x7F000001; le_uint32_t client_id = 0; - ptext name; + pstring name; void clear(); } __attribute__((packed)); @@ -298,7 +310,7 @@ struct PlayerLobbyDataXB { le_uint32_t guild_card = 0; XBNetworkLocation netloc; le_uint32_t client_id = 0; - ptext name; + pstring name; void clear(); } __attribute__((packed)); @@ -311,34 +323,32 @@ struct PlayerLobbyDataBB { be_uint32_t ip_address = 0x7F000001; parray unknown_a1; le_uint32_t client_id = 0; - ptext name; + pstring name; le_uint32_t unknown_a2 = 0; void clear(); } __attribute__((packed)); -template +template struct PlayerRecordsDCPC_Challenge { - using CharT = typename std::conditional::type; - /* 00 */ le_uint16_t title_color = 0x7FFF; /* 02 */ parray unknown_u0; - /* 04 */ ptext rank_title; // Encrypted; see decrypt_challenge_rank_text + /* 04 */ pstring rank_title; /* 10 */ parray times_ep1_online; // Encrypted; see decrypt_challenge_time. TODO: This might be offline times /* 34 */ le_uint16_t unknown_g3 = 0; /* 36 */ le_uint16_t grave_deaths = 0; /* 38 */ parray grave_coords_time; - /* 4C */ ptext grave_team; - /* 60 */ ptext grave_message; + /* 4C */ pstring grave_team; + /* 60 */ pstring grave_message; /* 78 */ parray times_ep1_offline; // Encrypted; see decrypt_challenge_time. TODO: This might be online times /* 9C */ parray unknown_l4; /* A0 */ } __attribute__((packed)); -struct PlayerRecordsDC_Challenge : PlayerRecordsDCPC_Challenge { +struct PlayerRecordsDC_Challenge : PlayerRecordsDCPC_Challenge { } __attribute__((packed)); -struct PlayerRecordsPC_Challenge : PlayerRecordsDCPC_Challenge { +struct PlayerRecordsPC_Challenge : PlayerRecordsDCPC_Challenge { } __attribute__((packed)); template @@ -358,22 +368,21 @@ struct PlayerRecordsV3_Challenge { /* 64:80 */ U16T grave_deaths = 0; /* 66:82 */ parray unknown_u4; /* 68:84 */ parray grave_coords_time; - /* 7C:98 */ ptext grave_team; - /* 90:AC */ ptext grave_message; + /* 7C:98 */ pstring grave_team; + /* 90:AC */ pstring grave_message; /* B0:CC */ parray unknown_m5; /* B4:D0 */ parray unknown_t6; /* D8:F4 */ } __attribute__((packed)); /* 0000:001C */ Stats stats; // On Episode 3, there are special cases that apply to this field - if the - // text ends with certain strings (after decrypt_challenge_rank_text), the - // player will have particle effects emanate from their character in the - // lobby every 2 seconds. These effects are: + // text ends with certain strings, the player will have particle effects + // emanate from their character in the lobby every 2 seconds. The effects are: // Ends with ":GOD" => blue circle // Ends with ":KING" => white particles // Ends with ":LORD" => rising yellow sparkles // Ends with ":CHAMP" => green circle - /* 00D8:00F4 */ ptext rank_title; + /* 00D8:00F4 */ pstring rank_title; /* 00E4:0100 */ parray unknown_l7; /* 0100:011C */ } __attribute__((packed)); @@ -388,11 +397,11 @@ struct PlayerRecordsBB_Challenge { /* 0064 */ le_uint16_t grave_deaths = 0; /* 0066 */ parray unknown_u4; /* 0068 */ parray grave_coords_time; - /* 007C */ ptext grave_team; - /* 00A4 */ ptext grave_message; + /* 007C */ pstring grave_team; + /* 00A4 */ pstring grave_message; /* 00E4 */ parray unknown_m5; /* 00E8 */ parray unknown_t6; - /* 010C */ ptext rank_title; // Encrypted; see decrypt_challenge_rank_text + /* 010C */ pstring rank_title; // Encrypted; see decrypt_challenge_rank_text /* 0124 */ parray unknown_l7; /* 0140 */ @@ -432,32 +441,32 @@ struct ChoiceSearchConfig { } __attribute__((packed)); template -DestT convert_player_disp_data(const SrcT&) { +DestT convert_player_disp_data(const SrcT&, uint8_t, uint8_t) { static_assert(always_false::v, - "unspecialized strcpy_t should never be called"); + "unspecialized convert_player_disp_data should never be called"); } template <> inline PlayerDispDataDCPCV3 convert_player_disp_data( - const PlayerDispDataDCPCV3& src) { + const PlayerDispDataDCPCV3& src, uint8_t, uint8_t) { return src; } template <> inline PlayerDispDataDCPCV3 convert_player_disp_data( - const PlayerDispDataBB& src) { - return src.to_dcpcv3(); + const PlayerDispDataBB& src, uint8_t to_language, uint8_t from_language) { + return src.to_dcpcv3(to_language, from_language); } template <> inline PlayerDispDataBB convert_player_disp_data( - const PlayerDispDataDCPCV3& src) { - return src.to_bb(); + const PlayerDispDataDCPCV3& src, uint8_t to_language, uint8_t from_language) { + return src.to_bb(to_language, from_language); } template <> inline PlayerDispDataBB convert_player_disp_data( - const PlayerDispDataBB& src) { + const PlayerDispDataBB& src, uint8_t, uint8_t) { return src; } diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index de2ff02b..0a5ba43b 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -107,7 +107,7 @@ static HandlerResult default_handler(shared_ptr, uin static HandlerResult S_invalid(shared_ptr ses, uint16_t command, uint32_t flag, string&) { ses->log.error("Server sent invalid command"); - string error_str = (ses->version == GameVersion::BB) + string error_str = (ses->version() == GameVersion::BB) ? string_printf("Server sent invalid\ncommand: %04hX %08" PRIX32, command, flag) : string_printf("Server sent invalid\ncommand: %02hX %02" PRIX32, command, flag); ses->send_to_game_server(error_str.c_str()); @@ -115,7 +115,7 @@ static HandlerResult S_invalid(shared_ptr ses, uint1 } static HandlerResult C_05(shared_ptr ses, uint16_t, uint32_t, string&) { - ses->disconnect_action = ses->version == GameVersion::BB + ses->disconnect_action = ses->version() == GameVersion::BB ? ProxyServer::LinkedSession::DisconnectAction::MEDIUM_TIMEOUT : ProxyServer::LinkedSession::DisconnectAction::SHORT_TIMEOUT; return HandlerResult::Type::FORWARD; @@ -187,15 +187,15 @@ static HandlerResult S_G_9A(shared_ptr ses, uint16_t cmd.unused2 = 0; cmd.sub_version = ses->sub_version; cmd.is_extended = (ses->remote_guild_card_number < 0) ? 1 : 0; - cmd.language = ses->language; - cmd.serial_number = string_printf("%08" PRIX32 "", ses->license->serial_number); - cmd.access_key = ses->license->access_key; + cmd.language = ses->language(); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; if (ses->options.blank_name) { - cmd.name = " "; + cmd.name.encode(" ", ses->language()); } else { - cmd.name = ses->character_name; + cmd.name.encode(ses->character_name, ses->language()); } cmd.client_config.data = ses->remote_client_config_data; @@ -213,7 +213,7 @@ static HandlerResult S_V123P_02_17( uint16_t command, uint32_t flag, string& data) { - if (ses->version == GameVersion::PATCH && command == 0x17) { + if (ses->version() == GameVersion::PATCH && command == 0x17) { throw invalid_argument("patch server sent 17 server init"); } @@ -228,8 +228,8 @@ static HandlerResult S_V123P_02_17( // client will be able to understand it. forward_command(ses, false, command, flag, data); - if ((ses->version == GameVersion::GC) || - (ses->version == GameVersion::XB)) { + if ((ses->version() == GameVersion::GC) || + (ses->version() == GameVersion::XB)) { ses->server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); ses->server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); ses->client_channel.crypt_in.reset(new PSOV3Encryption(cmd.client_key)); @@ -247,7 +247,7 @@ static HandlerResult S_V123P_02_17( ses->log.info("Existing license in linked session"); // This isn't forwarded to the client, so don't recreate the client's crypts - switch (ses->version) { + switch (ses->version()) { case GameVersion::DC: case GameVersion::PC: case GameVersion::PATCH: @@ -267,18 +267,16 @@ static HandlerResult S_V123P_02_17( // because it believes it already did (when it was in an unlinked session, or // in the patch server case, during the current session due to a hidden // redirect). - if (ses->version == GameVersion::PATCH) { + if (ses->version() == GameVersion::PATCH) { ses->server_channel.send(0x02); return HandlerResult::Type::SUPPRESS; - } else if ((ses->version == GameVersion::DC) || - (ses->version == GameVersion::PC)) { + } else if ((ses->version() == GameVersion::DC) || (ses->version() == GameVersion::PC)) { if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_DC_V1) { if (command == 0x17) { C_LoginV1_DC_PC_V3_90 cmd; - cmd.serial_number = string_printf("%08" PRIX32 "", - ses->license->serial_number); - cmd.access_key = ses->license->access_key; + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); cmd.access_key.clear_after(8); ses->server_channel.send(0x90, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; @@ -295,13 +293,12 @@ static HandlerResult S_V123P_02_17( cmd.unknown_a2 = 0; cmd.sub_version = ses->sub_version; cmd.is_extended = 0; - cmd.language = ses->language; - cmd.serial_number = string_printf("%08" PRIX32 "", - ses->license->serial_number); - cmd.access_key = ses->license->access_key; + cmd.language = ses->language(); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); cmd.access_key.clear_after(8); - cmd.hardware_id = ses->hardware_id; - cmd.name = ses->character_name; + cmd.hardware_id.encode(ses->hardware_id); + cmd.name.encode(ses->character_name); ses->server_channel.send(0x93, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } @@ -316,9 +313,8 @@ static HandlerResult S_V123P_02_17( cmd.guild_card_number = ses->remote_guild_card_number; } cmd.sub_version = ses->sub_version; - cmd.serial_number = string_printf("%08" PRIX32 "", - ses->license->serial_number); - cmd.access_key = ses->license->access_key; + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); cmd.access_key.clear_after(8); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; @@ -340,33 +336,31 @@ static HandlerResult S_V123P_02_17( cmd.unused2 = 0; cmd.sub_version = ses->sub_version; cmd.is_extended = 0; - cmd.language = ses->language; - cmd.serial_number = string_printf("%08" PRIX32 "", - ses->license->serial_number); - cmd.access_key = ses->license->access_key; + cmd.language = ses->language(); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); cmd.access_key.clear_after(8); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; if (ses->options.blank_name) { - cmd.name = " "; + cmd.name.encode(" ", ses->language()); } else { - cmd.name = ses->character_name; + cmd.name.encode(ses->character_name); } ses->server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } } - } else if (ses->version == GameVersion::GC) { + } else if (ses->version() == GameVersion::GC) { if (command == 0x17) { C_VerifyLicense_V3_DB cmd; - cmd.serial_number = string_printf("%08" PRIX32 "", - ses->license->serial_number); - cmd.access_key = ses->license->access_key; + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); + cmd.access_key.encode(ses->license->access_key); cmd.sub_version = ses->sub_version; cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - cmd.password = ses->license->gc_password; + cmd.password.encode(ses->license->gc_password); ses->server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; @@ -394,15 +388,15 @@ static HandlerResult S_V123P_02_17( cmd.unused2 = 0; cmd.sub_version = ses->sub_version; cmd.is_extended = 0; - cmd.language = ses->language; - cmd.serial_number = string_printf("%08" PRIX32, fake_serial_number); - cmd.access_key = fake_access_key_str; + cmd.language = ses->language(); + cmd.serial_number.encode(string_printf("%08" PRIX32, fake_serial_number)); + cmd.access_key.encode(fake_access_key_str); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; if (ses->options.blank_name) { - cmd.name = " "; + cmd.name.encode(" ", ses->language()); } else { - cmd.name = ses->character_name; + cmd.name.encode(ses->character_name, ses->language()); } cmd.client_config.data = ses->remote_client_config_data; ses->server_channel.send(0x9E, 0x01, &cmd, sizeof(C_Login_GC_9E)); @@ -413,7 +407,7 @@ static HandlerResult S_V123P_02_17( return S_G_9A(ses, command, flag, data); } - } else if (ses->version == GameVersion::XB) { + } else if (ses->version() == GameVersion::XB) { throw runtime_error("xbox licenses are not implemented"); } else { @@ -504,7 +498,7 @@ static HandlerResult S_V123_04(shared_ptr ses, uint1 string message = string_printf( "The remote server\nhas assigned your\nGuild Card number:\n\tC6%" PRId64, ses->remote_guild_card_number); - send_ship_info(ses->client_channel, decode_sjis(message)); + send_ship_info(ses->client_channel, message); } if (ses->license) { cmd.guild_card_number = ses->license->serial_number; @@ -621,7 +615,7 @@ static HandlerResult S_B2(shared_ptr ses, uint16_t, if (ses->newserv_client_config.cfg.flags & Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL) { StringReader r(code); - bool is_big_endian = (ses->version == GameVersion::GC || ses->version == GameVersion::DC); + bool is_big_endian = (ses->version() == GameVersion::GC || ses->version() == GameVersion::DC); uint32_t decompressed_size = is_big_endian ? r.get_u32b() : r.get_u32l(); uint32_t key = is_big_endian ? r.get_u32b() : r.get_u32l(); @@ -657,7 +651,7 @@ static HandlerResult S_B2(shared_ptr ses, uint16_t, ses->log.info("Wrote code from server to file %s", output_filename.c_str()); #ifdef HAVE_RESOURCE_FILE - if (ses->version == GameVersion::GC) { + if (ses->version() == GameVersion::GC) { try { if (code.size() < sizeof(S_ExecuteCode_Footer_GC_B2)) { throw runtime_error("code section is too small"); @@ -817,7 +811,7 @@ static HandlerResult S_19_P_14(shared_ptr ses, uint1 struct sockaddr_in* sin = reinterpret_cast( &ses->next_destination); sin->sin_family = AF_INET; - if (ses->version == GameVersion::PATCH) { + if (ses->version() == GameVersion::PATCH) { auto& cmd = check_size_t(data); sin->sin_addr.s_addr = cmd.address.load_raw(); // Already big-endian sin->sin_port = htons(cmd.port); @@ -833,7 +827,7 @@ static HandlerResult S_19_P_14(shared_ptr ses, uint1 ses->log.warning("Received reconnect command with no destination present"); return HandlerResult::Type::SUPPRESS; - } else if (ses->version != GameVersion::BB) { + } else if (ses->version() != GameVersion::BB) { // Hide redirects from the client completely. The new destination server // will presumably send a new encryption init command, which the handlers // will appropriately respond to. @@ -861,7 +855,7 @@ static HandlerResult S_V3_1A_D5(shared_ptr ses, uint // If the client is a version that sends close confirmations and the client // has the no-close-confirmation flag set in its newserv client config, send a // fake confirmation to the remote server immediately. - if (((ses->version == GameVersion::GC) || (ses->version == GameVersion::XB)) && + if (((ses->version() == GameVersion::GC) || (ses->version() == GameVersion::XB)) && (ses->newserv_client_config.cfg.flags & Client::Flag::NO_D6)) { ses->server_channel.send(0xD6); } @@ -870,7 +864,7 @@ static HandlerResult S_V3_1A_D5(shared_ptr ses, uint static HandlerResult S_V3_BB_DA(shared_ptr ses, uint16_t, uint32_t flag, string&) { // This command is supported on all V3 versions except Ep1&2 Trial - if ((ses->version == GameVersion::GC) && + if ((ses->version() == GameVersion::GC) && (ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) { return HandlerResult::Type::SUPPRESS; } else if ((ses->options.override_lobby_event >= 0) && @@ -883,7 +877,7 @@ static HandlerResult S_V3_BB_DA(shared_ptr ses, uint static HandlerResult S_6x(shared_ptr ses, uint16_t, uint32_t, string& data) { if (ses->options.save_files) { - if ((ses->version == GameVersion::GC) && (data.size() >= 0x14)) { + if ((ses->version() == GameVersion::GC) && (data.size() >= 0x14)) { if (static_cast(data[0]) == 0xB6) { const auto& header = check_size_t(data, 0xFFFF); if (header.subsubcommand == 0x00000041) { @@ -907,7 +901,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, bool modified = false; if (!data.empty()) { // Unmask any masked Episode 3 commands from the server - if ((ses->version == GameVersion::GC) && (data.size() > 8) && + if ((ses->version() == GameVersion::GC) && (data.size() > 8) && ((static_cast(data[0]) == 0xB3) || (static_cast(data[0]) == 0xB4) || (static_cast(data[0]) == 0xB5))) { @@ -964,7 +958,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, return HandlerResult::Type::SUPPRESS; } - } else if ((data[0] == 0x60) && ses->next_drop_item.data1d[0] && (ses->version != GameVersion::BB)) { + } else if ((data[0] == 0x60) && ses->next_drop_item.data1d[0] && (ses->version() != GameVersion::BB)) { const auto& cmd = check_size_t( data, sizeof(G_StandardDropItemRequest_PC_V3_BB_6x60)); ses->next_drop_item.id = ses->next_item_id++; @@ -977,7 +971,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, // the comparison is always false (which even happens in some environments // if we use -0x5E... apparently char is unsigned on some systems, or // std::string's char_type isn't char??) - } else if ((static_cast(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && (ses->version != GameVersion::BB)) { + } else if ((static_cast(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && (ses->version() != GameVersion::BB)) { const auto& cmd = check_size_t(data); ses->next_drop_item.id = ses->next_item_id++; send_drop_item(ses->server_channel, ses->next_drop_item, false, cmd.area, cmd.x, cmd.z, cmd.entity_id); @@ -986,7 +980,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, return HandlerResult::Type::SUPPRESS; } else if ((static_cast(data[0]) == 0xB5) && - (ses->version == GameVersion::GC) && + (ses->version() == GameVersion::GC) && (data.size() > 4)) { if (data[4] == 0x1A) { return HandlerResult::Type::SUPPRESS; @@ -1007,13 +1001,13 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 // TODO: We should check if the info board text was actually modified and // return MODIFIED if so. - if (ses->version == GameVersion::BB) { + if (ses->version() == GameVersion::BB) { auto& pd = check_size_t(data, 0xFFFF); if (ses->options.enable_chat_filter) { - add_color_inplace(pd.info_board.data(), pd.info_board.size()); + pd.info_board.encode(add_color(pd.info_board.decode(ses->language())), ses->language()); } if (ses->options.blank_name) { - pd.disp.name = " "; + pd.disp.name.encode(" ", ses->language()); modified = true; } if (ses->options.red_name && pd.disp.visual.name_color != 0xFFFF0000) { @@ -1026,7 +1020,7 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 } if (!ses->challenge_rank_title_override.empty()) { pd.records.challenge.title_color = encode_xrgb1555(ses->challenge_rank_color_override); - pd.records.challenge.rank_title = encrypt_challenge_rank_text(ses->challenge_rank_title_override); + pd.records.challenge.rank_title.encode(ses->challenge_rank_title_override, ses->language()); } } else { @@ -1047,10 +1041,10 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 pd = &check_size_t(data, 0xFFFF); } if (ses->options.enable_chat_filter) { - add_color_inplace(pd->info_board.data(), pd->info_board.size()); + pd->info_board.encode(add_color(pd->info_board.decode(ses->language())), ses->language()); } if (ses->options.blank_name) { - pd->disp.visual.name = " "; + pd->disp.visual.name.encode(" ", ses->language()); modified = true; } if (ses->options.red_name && pd->disp.visual.name_color != 0xFFFF0000) { @@ -1063,7 +1057,7 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 } if (!ses->challenge_rank_title_override.empty()) { pd->records.challenge.stats.title_color = encode_xrgb1555(ses->challenge_rank_color_override); - pd->records.challenge.rank_title = encrypt_challenge_rank_text(ses->challenge_rank_title_override); + pd->records.challenge.rank_title.encode(ses->challenge_rank_title_override, ses->language()); } } @@ -1072,7 +1066,7 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 static HandlerResult C_GX_D9(shared_ptr ses, uint16_t, uint32_t, string& data) { if (ses->options.enable_chat_filter) { - add_color_inplace(data.data(), data.size()); + data = add_color(data); // TODO: We should check if the info board text was actually modified and // return MODIFIED if so. } @@ -1081,8 +1075,13 @@ static HandlerResult C_GX_D9(shared_ptr ses, uint16_ static HandlerResult C_B_D9(shared_ptr ses, uint16_t, uint32_t, string& data) { if (ses->options.enable_chat_filter) { - char16_t* text = reinterpret_cast(data.data()); - add_color_inplace(text, data.size() / sizeof(char16_t)); + try { + string decoded = tt_utf16_to_utf8(data.data(), data.size()); + add_color_inplace(decoded); + data = tt_utf8_to_utf16(data.data(), data.size()); + } catch (const runtime_error& e) { + ses->log.warning("Failed to replace escape characters in D9 command: %s", e.what()); + } // TODO: We should check if the info board text was actually modified and // return HandlerResult::MODIFIED if so. } @@ -1093,7 +1092,7 @@ template static HandlerResult S_44_A6(shared_ptr ses, uint16_t command, uint32_t, string& data) { const auto& cmd = check_size_t(data); - string filename = cmd.filename; + string filename = cmd.filename.decode(); string output_filename; bool is_download = (command == 0xA6); if (ses->options.save_files) { @@ -1126,8 +1125,8 @@ static HandlerResult S_44_A6(shared_ptr ses, uint16_ // Episode 3 download quests aren't DLQ-encoded bool decode_dlq = is_download && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3); - ProxyServer::LinkedSession::SavingFile sf(cmd.filename, output_filename, cmd.file_size, decode_dlq); - ses->saving_files.emplace(cmd.filename, std::move(sf)); + ProxyServer::LinkedSession::SavingFile sf(filename, output_filename, cmd.file_size, decode_dlq); + ses->saving_files.emplace(filename, std::move(sf)); if (ses->options.save_files) { ses->log.info("Saving %s from server to %s", filename.c_str(), output_filename.c_str()); } else { @@ -1148,9 +1147,9 @@ static HandlerResult S_13_A7(shared_ptr ses, uint16_ ProxyServer::LinkedSession::SavingFile* sf = nullptr; try { - sf = &ses->saving_files.at(cmd.filename); + sf = &ses->saving_files.at(cmd.filename.decode()); } catch (const out_of_range&) { - string filename = cmd.filename; + string filename = cmd.filename.decode(); ses->log.warning("Received data for non-open file %s", filename.c_str()); return HandlerResult::Type::FORWARD; } @@ -1181,7 +1180,7 @@ static HandlerResult S_13_A7(shared_ptr ses, uint16_ } else { ses->log.info("Download complete for file %s", sf->basename.c_str()); } - ses->saving_files.erase(cmd.filename); + ses->saving_files.erase(cmd.filename.decode()); } return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; @@ -1301,7 +1300,7 @@ static void update_leader_id(shared_ptr ses, uint8_t ses->leader_client_id = leader_id; ses->log.info("Changed room leader to %zu", ses->leader_client_id); if (ses->options.enable_player_notifications && (ses->leader_client_id == ses->lobby_client_id)) { - send_text_message(ses->client_channel, u"$C6You are now the leader"); + send_text_message(ses->client_channel, "$C6You are now the leader"); } } } @@ -1332,25 +1331,27 @@ static HandlerResult S_65_67_68_EB(shared_ptr ses, u ses->lobby_client_id = cmd.lobby_flags.client_id; update_leader_id(ses, cmd.lobby_flags.leader_id); for (size_t x = 0; x < flag; x++) { - size_t index = cmd.entries[x].lobby_data.client_id; + auto& entry = cmd.entries[x]; + size_t index = entry.lobby_data.client_id; if (index >= ses->lobby_players.size()) { ses->log.warning("Ignoring invalid player index %zu at position %zu", index, x); } else { - string name = encode_sjis(cmd.entries[x].disp.visual.name); + string name = entry.disp.visual.name.decode(entry.inventory.language); - if (ses->license && (cmd.entries[x].lobby_data.guild_card == ses->remote_guild_card_number)) { - cmd.entries[x].lobby_data.guild_card = ses->license->serial_number; + if (ses->license && (entry.lobby_data.guild_card == ses->remote_guild_card_number)) { + entry.lobby_data.guild_card = ses->license->serial_number; num_replacements++; modified = true; } else if (ses->options.enable_player_notifications && command != 0x67) { send_text_message_printf(ses->client_channel, "$C6Join: %zu/%" PRIu32 "\n%s", - index, cmd.entries[x].lobby_data.guild_card.load(), name.c_str()); + index, entry.lobby_data.guild_card.load(), name.c_str()); } auto& p = ses->lobby_players[index]; - p.guild_card_number = cmd.entries[x].lobby_data.guild_card; + p.guild_card_number = entry.lobby_data.guild_card; p.name = name; - p.section_id = cmd.entries[x].disp.visual.section_id; - p.char_class = cmd.entries[x].disp.visual.char_class; + p.language = entry.inventory.language; + p.section_id = entry.disp.visual.section_id; + p.char_class = entry.disp.visual.char_class; ses->log.info("Added lobby player: (%zu) %" PRIu32 " %s", index, p.guild_card_number, p.name.c_str()); } @@ -1404,10 +1405,11 @@ static HandlerResult S_64(shared_ptr ses, uint16_t, auto& p = ses->lobby_players[x]; p.guild_card_number = cmd->lobby_data[x].guild_card; if (cmd_ep3) { - ptext name = cmd_ep3->players_ep3[x].disp.visual.name; - p.name = name; - p.section_id = cmd_ep3->players_ep3[x].disp.visual.section_id; - p.char_class = cmd_ep3->players_ep3[x].disp.visual.char_class; + const auto& p_ep3 = cmd_ep3->players_ep3[x]; + p.language = p_ep3.inventory.language; + p.name = p_ep3.disp.visual.name.decode(p.language); + p.section_id = p_ep3.disp.visual.section_id; + p.char_class = p_ep3.disp.visual.char_class; } else { p.name.clear(); } @@ -1465,8 +1467,8 @@ static HandlerResult S_E8(shared_ptr ses, uint16_t, auto& p = ses->lobby_players[x]; p.guild_card_number = player_entry.lobby_data.guild_card; - ptext name = player_entry.disp.visual.name; - p.name = name; + p.language = player_entry.inventory.language; + p.name = player_entry.disp.visual.name.decode(p.language); p.section_id = player_entry.disp.visual.section_id; p.char_class = player_entry.disp.visual.char_class; ses->log.info("Added lobby player: (%zu) %" PRIu32 " %s", @@ -1521,9 +1523,9 @@ static HandlerResult C_98(shared_ptr ses, uint16_t c ses->area = 0x0F; ses->is_in_game = false; ses->is_in_quest = false; - if (ses->version == GameVersion::GC || - ses->version == GameVersion::XB || - ses->version == GameVersion::BB) { + if (ses->version() == GameVersion::GC || + ses->version() == GameVersion::XB || + ses->version() == GameVersion::BB) { return C_GXB_61(ses, command, flag, data); } else { return HandlerResult::Type::FORWARD; @@ -1532,24 +1534,26 @@ static HandlerResult C_98(shared_ptr ses, uint16_t c static HandlerResult C_06(shared_ptr ses, uint16_t, uint32_t, string& data) { if (data.size() >= 12) { - const auto& cmd = check_size_t(data, 0xFFFF); - - u16string text; - uint8_t private_flags = 0; - if (ses->version == GameVersion::PC || ses->version == GameVersion::BB) { - text = u16string(cmd.text.pcbb, (data.size() - sizeof(C_Chat_06)) / sizeof(char16_t)); - - } else if ((cmd.text.dcv3[0] != '\t') && - (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { - private_flags = cmd.text.dcv3[0]; - text = decode_sjis(cmd.text.dcv3 + 1, data.size() - sizeof(C_Chat_06)); - - } else { - text = decode_sjis(cmd.text.dcv3, data.size() - sizeof(C_Chat_06)); - } + const auto& cmd = check_size_t(data, 0xFFFF); + string text = data.substr(sizeof(cmd)); strip_trailing_zeroes(text); + uint8_t private_flags = 0; + if (ses->version() == GameVersion::PC || ses->version() == GameVersion::BB) { + if (text.size() & 1) { + text.push_back(0); + } + text = tt_decode_marked(text, ses->language(), true); + } else if (!text.empty() && + (text[0] != '\t') && + (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { + private_flags = text[0]; + text = tt_decode_marked(text.substr(1), ses->language(), false); + } else { + text = tt_decode_marked(text, ses->language(), false); + } + if (text.empty()) { return HandlerResult::Type::SUPPRESS; } @@ -1561,7 +1565,11 @@ static HandlerResult C_06(shared_ptr ses, uint16_t, offset += (text[offset] == '$') ? 0 : 2; text = text.substr(offset); if (text.size() >= 2 && text[1] == '$') { - send_chat_message(ses->server_channel, text.substr(1), private_flags); + if (ses->options.enable_chat_filter) { + send_chat_message_from_client(ses->server_channel, add_color(text.substr(1)), private_flags); + } else { + send_chat_message_from_client(ses->server_channel, text.substr(1), private_flags); + } return HandlerResult::Type::SUPPRESS; } else { on_chat_command(ses, text); @@ -1569,10 +1577,8 @@ static HandlerResult C_06(shared_ptr ses, uint16_t, } } else if (ses->options.enable_chat_filter) { - add_color_inplace(data.data() + 8, data.size() - 8); - // TODO: We should return MODIFIED here if the message was changed by - // the add_color_inplace call - return HandlerResult::Type::FORWARD; + send_chat_message_from_client(ses->server_channel, add_color(text), private_flags); + return HandlerResult::Type::SUPPRESS; } else { return HandlerResult::Type::FORWARD; @@ -1611,7 +1617,7 @@ static HandlerResult C_81(shared_ptr ses, uint16_t, } } // GC clients send uninitialized memory here; don't forward it - cmd.text.clear_after(cmd.text.len()); + cmd.text.clear_after(cmd.text.used_bytes_8()); return HandlerResult::Type::MODIFIED; } @@ -1631,10 +1637,10 @@ static HandlerResult C_6x(shared_ptr ses, uint16_t c if (ses->license && !data.empty()) { // On BB, the 6x06 command is blank - the server generates the actual Guild // Card contents and sends it to the target client. - if (data[0] == 0x06 && ses->version != GameVersion::BB) { + if (data[0] == 0x06 && ses->version() != GameVersion::BB) { auto& cmd = check_size_t(data); - if (cmd.guild_card_number == ses->license->serial_number) { - cmd.guild_card_number = ses->remote_guild_card_number; + if (cmd.guild_card.guild_card_number == ses->license->serial_number) { + cmd.guild_card.guild_card_number = ses->remote_guild_card_number; } } } @@ -1996,7 +2002,7 @@ void on_proxy_command( uint32_t flag, string& data) { try { - auto fn = get_handler(ses->version, from_server, command); + auto fn = get_handler(ses->version(), from_server, command); auto res = fn(ses, command, flag, data); if (res.type == HandlerResult::Type::FORWARD) { forward_command(ses, !from_server, command, flag, data, false); diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 876b6511..2959ae9b 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -152,7 +152,7 @@ void ProxyServer::on_client_connect( auto ses = emplace_ret.first->second; ses->log.info("Opened linked session"); - Channel ch(bev, version, nullptr, nullptr, ses.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN); + Channel ch(bev, version, 1, nullptr, nullptr, ses.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN); ses->resume(std::move(ch)); // If no default destination exists, or the client is not a patch client, @@ -229,6 +229,7 @@ ProxyServer::UnlinkedSession::UnlinkedSession( channel( bev, version, + 1, ProxyServer::UnlinkedSession::on_input, ProxyServer::UnlinkedSession::on_error, this, @@ -260,7 +261,6 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 bool should_close_unlinked_session = false; shared_ptr license; uint32_t sub_version = 0; - uint8_t language = 1; // Default = English string character_name; ClientConfigBB client_config; string login_command_bb; @@ -272,20 +272,18 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 // anything else, disconnect if (command == 0x93) { const auto& cmd = check_size_t(data); - license = s->license_index->verify_v1_v2( - stoul(cmd.serial_number, nullptr, 16), cmd.access_key); + license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); sub_version = cmd.sub_version; - language = cmd.language; - character_name = cmd.name; - hardware_id = cmd.hardware_id; + ses->channel.language = cmd.language; + character_name = cmd.name.decode(ses->channel.language); + hardware_id = cmd.hardware_id.decode(); client_config.cfg.flags |= Client::Flag::IS_DC_V1; } else if (command == 0x9D) { const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DC_GC_9D)); - license = s->license_index->verify_v1_v2( - stoul(cmd.serial_number, nullptr, 16), cmd.access_key); + license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); sub_version = cmd.sub_version; - language = cmd.language; - character_name = cmd.name; + ses->channel.language = cmd.language; + character_name = cmd.name.decode(ses->channel.language); } else { throw runtime_error("command is not 93 or 9D"); } @@ -297,11 +295,10 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 throw runtime_error("command is not 9D"); } const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_PC_9D)); - license = s->license_index->verify_v1_v2( - stoul(cmd.serial_number, nullptr, 16), cmd.access_key); + license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); sub_version = cmd.sub_version; - language = cmd.language; - character_name = cmd.name; + ses->channel.language = cmd.language; + character_name = cmd.name.decode(ses->channel.language); } else if (ses->version == GameVersion::GC) { // We should only get a 9E while the session is unlinked; if we get @@ -311,11 +308,10 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 throw runtime_error("command is not 9E"); } const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_GC_9E)); - license = s->license_index->verify_gc( - stoul(cmd.serial_number, nullptr, 16), cmd.access_key); + license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); sub_version = cmd.sub_version; - language = cmd.language; - character_name = cmd.name; + ses->channel.language = cmd.language; + character_name = cmd.name.decode(ses->channel.language); client_config.cfg = cmd.client_config.cfg; } else if (ses->version == GameVersion::XB) { @@ -329,16 +325,15 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 } const auto& cmd = check_size_t(data); try { - license = s->license_index->verify_bb( - cmd.username, cmd.password); + license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); } catch (const LicenseIndex::missing_license&) { if (!s->allow_unregistered_users) { throw; } shared_ptr l(new License()); - l->serial_number = fnv1a32(cmd.username) & 0x7FFFFFFF; - l->bb_username = cmd.username; - l->bb_password = cmd.password; + l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF; + l->bb_username = cmd.username.decode(); + l->bb_password = cmd.password.decode(); s->license_index->add(l); license = l; string l_str = l->str(); @@ -392,7 +387,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 if (linked_ses.get()) { server->id_to_session.emplace(license->serial_number, linked_ses); - if (linked_ses->version != ses->version) { + if (linked_ses->version() != ses->version) { linked_ses->log.error("Linked session has different game version"); } else { // Resume the linked session using the unlinked session @@ -407,7 +402,6 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 std::move(ses->channel), ses->detector_crypt, sub_version, - language, character_name, hardware_id); } @@ -449,6 +443,7 @@ ProxyServer::LinkedSession::LinkedSession( license(nullptr), client_channel( version, + 1, nullptr, nullptr, this, @@ -457,6 +452,7 @@ ProxyServer::LinkedSession::LinkedSession( TerminalFormat::FG_GREEN), server_channel( version, + 1, nullptr, nullptr, this, @@ -467,9 +463,7 @@ ProxyServer::LinkedSession::LinkedSession( disconnect_action(DisconnectAction::LONG_TIMEOUT), remote_ip_crc(0), enable_remote_ip_crc_patch(false), - version(version), sub_version(0), // This is set during resume() - language(1), // Default = English. This is also set during resume() remote_guild_card_number(-1), next_item_id(0x0F000000), lobby_players(12), @@ -537,11 +531,9 @@ void ProxyServer::LinkedSession::resume( Channel&& client_channel, shared_ptr detector_crypt, uint32_t sub_version, - uint8_t language, const string& character_name, const string& hardware_id) { this->sub_version = sub_version; - this->language = language; this->character_name = character_name; this->hardware_id = hardware_id; this->resume_inner(std::move(client_channel), detector_crypt); @@ -557,7 +549,6 @@ void ProxyServer::LinkedSession::resume( void ProxyServer::LinkedSession::resume(Channel&& client_channel) { this->sub_version = 0; - this->language = 1; this->character_name.clear(); this->resume_inner(std::move(client_channel), nullptr); } @@ -578,6 +569,7 @@ void ProxyServer::LinkedSession::resume_inner( ProxyServer::LinkedSession::on_error, this, string_printf("LinkedSession:%08" PRIX64 ":client", this->id)); + this->server_channel.language = this->client_channel.language; this->detector_crypt = detector_crypt; this->server_channel.disconnect(); @@ -648,9 +640,9 @@ void ProxyServer::LinkedSession::on_error(Channel& ch, short events) { ses->log.info("%s channel connected", is_server_stream ? "Server" : "Client"); if (is_server_stream && (ses->options.override_lobby_event >= 0) && - (((ses->version == GameVersion::GC) && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || - (ses->version == GameVersion::XB) || - (ses->version == GameVersion::BB))) { + (((ses->version() == GameVersion::GC) && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || + (ses->version() == GameVersion::XB) || + (ses->version() == GameVersion::BB))) { ses->client_channel.send(0xDA, ses->options.override_lobby_event); } } @@ -687,7 +679,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) } // On BB, do nothing - we can't return to the game server since the remote // server likely sent different game data than what newserv would have sent - if (this->version == GameVersion::BB) { + if (this->version() == GameVersion::BB) { this->disconnect(); return; } @@ -704,13 +696,12 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) } auto s = this->require_server_state(); - string encoded_name = encode_sjis(s->name); if (this->is_in_game) { - send_ship_info(this->client_channel, decode_sjis(string_printf("You cannot return\nto $C6%s$C7\nwhile in a game.\n\n%s", encoded_name.c_str(), error_message ? error_message : ""))); + send_ship_info(this->client_channel, string_printf("You cannot return\nto $C6%s$C7\nwhile in a game.\n\n%s", s->name.c_str(), error_message ? error_message : "")); this->disconnect(); } else { - send_ship_info(this->client_channel, decode_sjis(string_printf("You\'ve returned to\n\tC6%s$C7\n\n%s", encoded_name.c_str(), error_message ? error_message : ""))); + 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 : "")); // Restore newserv_client_config, so the login server gets the client flags S_UpdateClientConfig_DC_PC_V3_04 update_client_config_cmd; @@ -719,8 +710,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) update_client_config_cmd.cfg = this->newserv_client_config.cfg; this->client_channel.send(0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd)); - const auto& port_name = version_to_login_port_name.at(static_cast( - this->version)); + const auto& port_name = version_to_login_port_name.at(static_cast(this->version())); S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}}; diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 4fbbabd3..e73681d4 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -57,9 +57,7 @@ public: uint32_t remote_ip_crc; bool enable_remote_ip_crc_patch; - GameVersion version; uint32_t sub_version; - uint8_t language; std::string character_name; std::string hardware_id; // Only used for DC sessions std::string login_command_bb; @@ -79,6 +77,7 @@ public: struct LobbyPlayer { uint32_t guild_card_number = 0; std::string name; + uint8_t language; uint8_t section_id = 0; uint8_t char_class = 0; }; @@ -138,11 +137,17 @@ public: std::shared_ptr require_server() const; std::shared_ptr require_server_state() const; + inline GameVersion version() const { + return this->client_channel.version; + } + inline uint8_t language() const { + return this->client_channel.language; + } + void resume( Channel&& client_channel, std::shared_ptr detector_crypt, uint32_t sub_version, - uint8_t language, const std::string& character_name, const std::string& hardware_id); void resume( diff --git a/src/Quest.cc b/src/Quest.cc index a1f70a86..0d141175 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -26,8 +26,8 @@ QuestCategoryIndex::Category::Category(uint32_t category_id, const JSON& json) this->flags = json.get_int(0); this->type = json.get_string(1).at(0); this->short_token = json.get_string(2); - this->name = decode_sjis(json.get_string(3)); - this->description = decode_sjis(json.get_string(4)); + this->name = json.get_string(3); + this->description = json.get_string(4); } bool QuestCategoryIndex::Category::matches_flags(uint8_t request) const { @@ -252,9 +252,9 @@ VersionedQuest::VersionedQuest( if (this->quest_number == 0xFFFFFFFF) { this->quest_number = header->quest_number; } - this->name = decode_sjis(header->name); - this->short_description = decode_sjis(header->short_description); - this->long_description = decode_sjis(header->long_description); + this->name = header->name.decode(this->language); + this->short_description = header->short_description.decode(this->language); + this->long_description = header->long_description.decode(this->language); break; } @@ -268,9 +268,9 @@ VersionedQuest::VersionedQuest( if (this->quest_number == 0xFFFFFFFF) { this->quest_number = header->quest_number; } - this->name = header->name; - this->short_description = header->short_description; - this->long_description = header->long_description; + this->name = header->name.decode(this->language); + this->short_description = header->short_description.decode(this->language); + this->long_description = header->long_description.decode(this->language); break; } @@ -291,9 +291,9 @@ VersionedQuest::VersionedQuest( if (this->quest_number == 0xFFFFFFFF) { this->quest_number = map->map_number; } - this->name = decode_sjis(map->name); - this->short_description = decode_sjis(map->quest_name); - this->long_description = decode_sjis(map->description); + this->name = map->name.decode(this->language); + this->short_description = map->quest_name.decode(this->language); + this->long_description = map->description.decode(this->language); break; } @@ -309,9 +309,9 @@ VersionedQuest::VersionedQuest( if (this->quest_number == 0xFFFFFFFF) { this->quest_number = header->quest_number; } - this->name = decode_sjis(header->name); - this->short_description = decode_sjis(header->short_description); - this->long_description = decode_sjis(header->long_description); + this->name = header->name.decode(this->language); + this->short_description = header->short_description.decode(this->language); + this->long_description = header->long_description.decode(this->language); break; } @@ -337,9 +337,9 @@ VersionedQuest::VersionedQuest( if (this->quest_number == 0xFFFFFFFF) { this->quest_number = header->quest_number; } - this->name = header->name; - this->short_description = header->short_description; - this->long_description = header->long_description; + this->name = header->name.decode(this->language); + this->short_description = header->short_description.decode(this->language); + this->long_description = header->long_description.decode(this->language); break; } @@ -675,8 +675,8 @@ QuestIndex::QuestIndex( shared_ptr vq(new VersionedQuest( quest_number, category_id, version, language, bin_contents, dat_contents, battle_rules, challenge_template_index)); - string ascii_name = format_data_string(encode_sjis(vq->name)); - auto category_name = encode_sjis(this->category_index->at(vq->category_id).name); + string ascii_name = format_data_string(vq->name); + auto category_name = this->category_index->at(vq->category_id).name; string dat_str = dat_filename.empty() ? "" : (" with layout " + dat_filename); string battle_rules_str = battle_rules ? (" with battle rules from " + json_filename) : ""; @@ -1029,7 +1029,7 @@ static pair decode_qst_data_t(const string& data) { throw runtime_error("qst open file command has incorrect size"); } const auto& cmd = r.get(); - string internal_filename = cmd.filename; + string internal_filename = cmd.filename.decode(); if (ends_with(internal_filename, ".bin")) { if (internal_bin_filename.empty()) { @@ -1059,7 +1059,7 @@ static pair decode_qst_data_t(const string& data) { throw runtime_error("qst write file command has incorrect size"); } const auto& cmd = r.get(); - string filename = cmd.filename; + string filename = cmd.filename.decode(); string* dest = nullptr; if (filename == internal_bin_filename) { @@ -1134,11 +1134,11 @@ void add_command_header( } template -void add_open_file_command(StringWriter& w, const std::u16string& name, const std::string& filename, size_t file_size, bool is_download) { +void add_open_file_command(StringWriter& w, const std::string& name, const std::string& filename, size_t file_size, bool is_download) { add_command_header(w, is_download ? 0xA6 : 0x44, 0x00, sizeof(CmdT)); CmdT cmd; - cmd.name = "PSO/" + encode_sjis(name); - cmd.filename = filename; + cmd.name.assign_raw("PSO/" + name); + cmd.filename.encode(filename); cmd.type = 0; cmd.file_size = file_size; // TODO: It'd be nice to have something like w.emplace(...) to avoid copying @@ -1157,7 +1157,7 @@ void add_write_file_commands( size_t chunk_size = min(data.size() - z, 0x400); add_command_header(w, is_download ? 0xA7 : 0x13, z >> 10, sizeof(S_WriteFile_13_A7)); S_WriteFile_13_A7 cmd; - cmd.filename = filename; + cmd.filename.encode(filename); memcpy(cmd.data.data(), &data[z], chunk_size); cmd.data_size = chunk_size; w.put(cmd); @@ -1173,7 +1173,7 @@ void add_write_file_commands( string encode_qst_file( const string& bin_data, const string& dat_data, - const u16string& name, + const string& name, uint32_t quest_number, QuestScriptVersion version, bool is_dlq_encoded) { diff --git a/src/Quest.hh b/src/Quest.hh index 16240815..1347cae2 100644 --- a/src/Quest.hh +++ b/src/Quest.hh @@ -37,8 +37,8 @@ struct QuestCategoryIndex { uint8_t flags; char type; std::string short_token; - std::u16string name; - std::u16string description; + std::string name; + std::string description; explicit Category(uint32_t category_id, const JSON& json); @@ -58,12 +58,12 @@ struct VersionedQuest { uint32_t category_id; Episode episode; bool joinable; - std::u16string name; + std::string name; QuestScriptVersion version; uint8_t language; bool is_dlq_encoded; - std::u16string short_description; - std::u16string long_description; + std::string short_description; + std::string long_description; std::shared_ptr bin_contents; std::shared_ptr dat_contents; std::shared_ptr battle_rules; @@ -105,7 +105,7 @@ public: uint32_t category_id; Episode episode; bool joinable; - std::u16string name; + std::string name; std::shared_ptr battle_rules; ssize_t challenge_template_index; std::map> versions; @@ -147,7 +147,7 @@ std::pair decode_qst_data(const std::string& data); std::string encode_qst_file( const std::string& bin_data, const std::string& dat_data, - const std::u16string& name, + const std::string& name, uint32_t quest_number, QuestScriptVersion version, bool is_dlq_encoded); diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 21474c75..8b34337d 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -78,19 +78,6 @@ static string format_and_indent_data(const void* data, size_t size, uint64_t sta return ret; } -static string dasm_u16string(const char16_t* data, size_t size) { - try { - return format_data_string(encode_sjis(data, size)); - } catch (const runtime_error& e) { - return "/* undecodable */ " + format_data_string(data, size * sizeof(char16_t)); - } -} - -template -static string dasm_u16string(const parray& data) { - return dasm_u16string(data.data(), data.size()); -} - struct ResistData { le_int16_t evp_bonus; le_uint16_t unknown_a1; @@ -867,7 +854,7 @@ opcodes_for_version(QuestScriptVersion v) { return index; } -std::string disassemble_quest_script(const void* data, size_t size, QuestScriptVersion version) { +std::string disassemble_quest_script(const void* data, size_t size, QuestScriptVersion version, uint8_t language) { StringReader r(data, size); deque lines; @@ -885,9 +872,9 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV if (header.is_download) { lines.emplace_back(string_printf(".is_download_quest")); } - lines.emplace_back(".name " + format_data_string(header.name.data(), header.name.len())); - lines.emplace_back(".short_desc " + format_data_string(header.short_description.data(), header.short_description.len())); - lines.emplace_back(".long_desc " + format_data_string(header.long_description.data(), header.long_description.len())); + lines.emplace_back(".name " + JSON(header.name.decode(language)).serialize()); + lines.emplace_back(".short_desc " + JSON(header.short_description.decode(language)).serialize()); + lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize()); break; } case QuestScriptVersion::PC_V2: { @@ -899,9 +886,9 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV if (header.is_download) { lines.emplace_back(string_printf(".is_download_quest")); } - lines.emplace_back(".name " + dasm_u16string(header.name)); - lines.emplace_back(".short_desc " + dasm_u16string(header.short_description)); - lines.emplace_back(".long_desc " + dasm_u16string(header.long_description)); + lines.emplace_back(".name " + JSON(header.name.decode(language)).serialize()); + lines.emplace_back(".short_desc " + JSON(header.short_description.decode(language)).serialize()); + lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize()); break; } case QuestScriptVersion::GC_NTE: @@ -916,9 +903,9 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV lines.emplace_back(string_printf(".is_download_quest")); } lines.emplace_back(string_printf(".episode %hhu", header.episode)); - lines.emplace_back(".name " + format_data_string(header.name.data(), header.name.len())); - lines.emplace_back(".short_desc " + format_data_string(header.short_description.data(), header.short_description.len())); - lines.emplace_back(".long_desc " + format_data_string(header.long_description.data(), header.long_description.len())); + lines.emplace_back(".name " + JSON(header.name.decode(language)).serialize()); + lines.emplace_back(".short_desc " + JSON(header.short_description.decode(language)).serialize()); + lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize()); break; } case QuestScriptVersion::BB_V4: { @@ -932,9 +919,9 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV if (header.joinable_in_progress) { lines.emplace_back(".joinable_in_progress"); } - lines.emplace_back(".name " + dasm_u16string(header.name)); - lines.emplace_back(".short_desc " + dasm_u16string(header.short_description)); - lines.emplace_back(".long_desc " + dasm_u16string(header.long_description)); + lines.emplace_back(".name " + JSON(header.name.decode(language)).serialize()); + lines.emplace_back(".short_desc " + JSON(header.short_description.decode(language)).serialize()); + lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize()); break; } default: @@ -1168,20 +1155,20 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV } case Type::CSTRING: if (use_wstrs) { - u16string s; - for (char16_t ch = cmd_r.get_u16l(); ch; ch = cmd_r.get_u16l()) { - s.push_back(ch); + StringWriter w; + for (uint16_t ch = cmd_r.get_u16l(); ch; ch = cmd_r.get_u16l()) { + w.put_u16l(ch); } if (def->flags & F_PASS) { - arg_stack_values.emplace_back(encode_sjis(s)); + arg_stack_values.emplace_back(tt_utf16_to_utf8(w.str())); } - dasm_arg = dasm_u16string(s.data(), s.size()); + dasm_arg = JSON(w.str()).serialize(); } else { string s = cmd_r.get_cstr(); if (def->flags & F_PASS) { arg_stack_values.emplace_back(s); } - dasm_arg = format_data_string(s); + dasm_arg = JSON(s).serialize(); } break; default: @@ -1383,21 +1370,20 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV } if (l->has_data_type(Arg::DataType::CSTRING)) { lines.emplace_back(string_printf(" // As C string (0x%zX bytes)", size)); - string data; + string str_data = cmd_r.pread(l->offset, size); + strip_trailing_zeroes(str_data); if (use_wstrs) { - u16string wdata(reinterpret_cast(cmd_r.pgetv(l->offset, size)), size >> 1); - strip_trailing_zeroes(wdata); - data = encode_sjis(wdata); - } else { - data = cmd_r.pread(l->offset, size); - strip_trailing_zeroes(data); + if (str_data.size() & 1) { + str_data.push_back(0); + } + str_data = tt_utf16_to_utf8(str_data); } - string formatted = format_data_string(data); + string formatted = format_data_string(str_data); lines.emplace_back(string_printf(" %04" PRIX32 " %s", l->offset, formatted.c_str())); } print_as_struct.template operator()([&](const PlayerVisualConfig& visual) -> void { lines.emplace_back(" // As PlayerVisualConfig"); - string name = format_data_string(visual.name); + string name = format_data_string(visual.name.decode(language)); lines.emplace_back(string_printf(" %04zX name %s", l->offset + offsetof(PlayerVisualConfig, name), name.c_str())); lines.emplace_back(string_printf(" %04zX name_color %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, name_color), visual.name_color.load())); string a2_str = format_data_string(visual.unknown_a2.data(), sizeof(visual.unknown_a2)); diff --git a/src/QuestScript.hh b/src/QuestScript.hh index fa6dcac2..f9d57b2d 100644 --- a/src/QuestScript.hh +++ b/src/QuestScript.hh @@ -32,9 +32,9 @@ struct PSOQuestHeaderDC { // Same format for DC v1 and v2 /* 0010 */ uint8_t is_download; /* 0011 */ uint8_t unknown1; /* 0012 */ le_uint16_t quest_number; // 0xFFFF for challenge quests - /* 0014 */ ptext name; - /* 0034 */ ptext short_description; - /* 00B4 */ ptext long_description; + /* 0014 */ pstring name; + /* 0034 */ pstring short_description; + /* 00B4 */ pstring long_description; /* 01D4 */ } __attribute__((packed)); @@ -46,9 +46,9 @@ struct PSOQuestHeaderPC { /* 0010 */ uint8_t is_download; /* 0011 */ uint8_t unknown1; /* 0012 */ le_uint16_t quest_number; // 0xFFFF for challenge quests - /* 0014 */ ptext name; - /* 0054 */ ptext short_description; - /* 0154 */ ptext long_description; + /* 0014 */ pstring name; + /* 0054 */ pstring short_description; + /* 0154 */ pstring long_description; /* 0394 */ } __attribute__((packed)); @@ -63,9 +63,9 @@ struct PSOQuestHeaderGC { /* 0011 */ uint8_t unknown1; /* 0012 */ uint8_t quest_number; /* 0013 */ uint8_t episode; // 1 = Ep2. Apparently some quests have 0xFF here, which means ep1 (?) - /* 0014 */ ptext name; - /* 0034 */ ptext short_description; - /* 00B4 */ ptext long_description; + /* 0014 */ pstring name; + /* 0034 */ pstring short_description; + /* 00B4 */ pstring long_description; /* 01D4 */ } __attribute__((packed)); @@ -80,10 +80,11 @@ struct PSOQuestHeaderBB { /* 0015 */ uint8_t max_players; /* 0016 */ uint8_t joinable_in_progress; /* 0017 */ uint8_t unknown; - /* 0018 */ ptext name; - /* 0058 */ ptext short_description; - /* 0158 */ ptext long_description; + /* 0018 */ pstring name; + /* 0058 */ pstring short_description; + /* 0158 */ pstring long_description; /* 0398 */ } __attribute__((packed)); -std::string disassemble_quest_script(const void* data, size_t size, QuestScriptVersion version); +std::string disassemble_quest_script( + const void* data, size_t size, QuestScriptVersion version, uint8_t language); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 0799978f..2ffa86a1 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -33,56 +33,56 @@ const char* ADD_NEXT_CLIENT_DISCONNECT_HOOK_NAME = "add_next_game_client"; static shared_ptr proxy_options_menu_for_client(shared_ptr c) { auto s = c->require_server_state(); - shared_ptr ret(new Menu(MenuID::PROXY_OPTIONS, u"Proxy options")); - ret->items.emplace_back(ProxyOptionsMenuItemID::GO_BACK, u"Go back", u"Return to the\nProxy Server menu", 0); + shared_ptr ret(new Menu(MenuID::PROXY_OPTIONS, "Proxy options")); + ret->items.emplace_back(ProxyOptionsMenuItemID::GO_BACK, "Go back", "Return to the\nProxy Server menu", 0); - auto add_option = [&](uint32_t item_id, bool is_enabled, const char16_t* text, const char16_t* description) -> void { - u16string option = is_enabled ? u"* " : u"- "; + auto add_option = [&](uint32_t item_id, bool is_enabled, const char* text, const char* description) -> void { + string option = is_enabled ? "* " : "- "; option += text; ret->items.emplace_back(item_id, option, description, 0); }; add_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, c->options.enable_chat_commands, - u"Chat commands", u"Enable chat\ncommands"); + "Chat commands", "Enable chat\ncommands"); add_option(ProxyOptionsMenuItemID::CHAT_FILTER, c->options.enable_chat_filter, - u"Chat filter", u"Enable escape\nsequences in\nchat messages\nand info board"); + "Chat filter", "Enable escape\nsequences in\nchat messages\nand info board"); add_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, c->options.enable_player_notifications, - u"Player notifs", u"Show a message\nwhen other players\njoin or leave"); + "Player notifs", "Show a message\nwhen other players\njoin or leave"); add_option(ProxyOptionsMenuItemID::BLOCK_PINGS, c->options.suppress_client_pings, - u"Block pings", u"Block ping commands\nsent by the client"); + "Block pings", "Block ping commands\nsent by the client"); if (s->cheat_mode_behavior != ServerState::CheatModeBehavior::OFF) { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp, - u"Infinite HP", u"Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"); + "Infinite HP", "Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"); add_option(ProxyOptionsMenuItemID::INFINITE_TP, c->options.infinite_tp, - u"Infinite TP", u"Enable automatic TP\nrestoration when\nyou cast any\ntechnique"); + "Infinite TP", "Enable automatic TP\nrestoration when\nyou cast any\ntechnique"); add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, c->options.switch_assist, - u"Switch assist", u"Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"); + "Switch assist", "Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"); } else { // Note: This option's text is the maximum possible length for any menu item add_option(ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, c->options.ep3_infinite_meseta, - u"Infinite Meseta", u"Fix Meseta value\nat 1,000,000"); + "Infinite Meseta", "Fix Meseta value\nat 1,000,000"); add_option(ProxyOptionsMenuItemID::EP3_INFINITE_TIME, c->options.ep3_infinite_time, - u"Infinite time", u"Disable overall and\nper-phase time limits\nin battle"); + "Infinite time", "Disable overall and\nper-phase time limits\nin battle"); } } add_option(ProxyOptionsMenuItemID::BLOCK_EVENTS, (c->options.override_lobby_event >= 0), - u"Block events", u"Disable seasonal\nevents in the lobby\nand in games"); + "Block events", "Disable seasonal\nevents in the lobby\nand in games"); add_option(ProxyOptionsMenuItemID::BLOCK_PATCHES, (c->options.function_call_return_value >= 0), - u"Block patches", u"Disable patches sent\nby the remote server"); + "Block patches", "Disable patches sent\nby the remote server"); if (s->proxy_allow_save_files) { add_option(ProxyOptionsMenuItemID::SAVE_FILES, c->options.save_files, - u"Save files", u"Save local copies of\nfiles from the\nremote server\n(quests, etc.)"); + "Save files", "Save local copies of\nfiles from the\nremote server\n(quests, etc.)"); } if (s->proxy_enable_login_options) { add_option(ProxyOptionsMenuItemID::RED_NAME, c->options.red_name, - u"Red name", u"Set the colors\nof your name and\nChallenge Mode\nrank to red"); + "Red name", "Set the colors\nof your name and\nChallenge Mode\nrank to red"); add_option(ProxyOptionsMenuItemID::BLANK_NAME, c->options.blank_name, - u"Blank name", u"Suppress your\ncharacter name\nduring login"); + "Blank name", "Suppress your\ncharacter name\nduring login"); add_option(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, c->options.suppress_remote_login, - u"Skip login", u"Use an alternate\nlogin sequence"); + "Skip login", "Use an alternate\nlogin sequence"); add_option(ProxyOptionsMenuItemID::SKIP_CARD, c->options.zero_remote_guild_card, - u"Skip card", u"Use an alternate\nvalue for your initial\nGuild Card"); + "Skip card", "Use an alternate\nvalue for your initial\nGuild Card"); } return ret; @@ -178,11 +178,11 @@ static void send_main_menu(shared_ptr c) { shared_ptr main_menu(new Menu(MenuID::MAIN, s->name)); main_menu->items.emplace_back( - MainMenuItemID::GO_TO_LOBBY, u"Go to lobby", - [s, wc = weak_ptr(c)]() -> u16string { + MainMenuItemID::GO_TO_LOBBY, "Go to lobby", + [s, wc = weak_ptr(c)]() -> string { auto c = wc.lock(); if (!c) { - return u""; + return ""; } size_t num_players = 0; @@ -203,14 +203,13 @@ static void send_main_menu(shared_ptr c) { } } } - string info = string_printf( + return string_printf( "$C6%zu$C7 players online\n$C6%zu$C7 games\n$C6%zu$C7 compatible games", num_players, num_games, num_compatible_games); - return decode_sjis(info); }, 0); - main_menu->items.emplace_back(MainMenuItemID::INFORMATION, u"Information", - u"View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::REQUIRES_MESSAGE_BOXES); + main_menu->items.emplace_back(MainMenuItemID::INFORMATION, "Information", + "View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::REQUIRES_MESSAGE_BOXES); uint32_t proxy_destinations_menu_item_flags = // DCNTE doesn't support multiple ship select menus without changing @@ -221,8 +220,8 @@ static void send_main_menu(shared_ptr c) { (s->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) | (s->proxy_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) | MenuItem::Flag::INVISIBLE_ON_BB; - main_menu->items.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server", - u"Connect to another\nserver through the\nproxy", proxy_destinations_menu_item_flags); + main_menu->items.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, "Proxy server", + "Connect to another\nserver through the\nproxy", proxy_destinations_menu_item_flags); // If the client is on a virtual connection, redirecting will not work // properly (they'll just be reconencted to newserv), so hide the option @@ -236,26 +235,26 @@ static void send_main_menu(shared_ptr c) { (s->redirect_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) | (s->redirect_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) | MenuItem::Flag::INVISIBLE_ON_BB; - main_menu->items.emplace_back(MainMenuItemID::REDIRECT_DESTINATIONS, u"Other servers", - u"Connect to another\nserver directly", redirect_destinations_menu_item_flags); + main_menu->items.emplace_back(MainMenuItemID::REDIRECT_DESTINATIONS, "Other servers", + "Connect to another\nserver directly", redirect_destinations_menu_item_flags); } - main_menu->items.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, u"Download quests", - u"Download quests", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB); + main_menu->items.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, "Download quests", + "Download quests", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB); if (!s->is_replay) { if (!s->function_code_index->patch_menu_empty(c->specific_version)) { - main_menu->items.emplace_back(MainMenuItemID::PATCHES, u"Patches", - u"Change game\nbehaviors", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); + main_menu->items.emplace_back(MainMenuItemID::PATCHES, "Patches", + "Change game\nbehaviors", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); } if (!s->dol_file_index->empty()) { - main_menu->items.emplace_back(MainMenuItemID::PROGRAMS, u"Programs", - u"Run GameCube\nprograms", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL | MenuItem::Flag::REQUIRES_SAVE_DISABLED); + main_menu->items.emplace_back(MainMenuItemID::PROGRAMS, "Programs", + "Run GameCube\nprograms", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL | MenuItem::Flag::REQUIRES_SAVE_DISABLED); } } - main_menu->items.emplace_back(MainMenuItemID::DISCONNECT, u"Disconnect", - u"Disconnect", 0); - main_menu->items.emplace_back(MainMenuItemID::CLEAR_LICENSE, u"Clear license", - u"Disconnect with an\ninvalid license error\nso you can enter a\ndifferent serial\nnumber, access key,\nor password", + main_menu->items.emplace_back(MainMenuItemID::DISCONNECT, "Disconnect", + "Disconnect", 0); + main_menu->items.emplace_back(MainMenuItemID::CLEAR_LICENSE, "Clear license", + "Disconnect with an\ninvalid license error\nso you can enter a\ndifferent serial\nnumber, access key,\nor password", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB); send_menu(c, main_menu); @@ -345,7 +344,7 @@ static void set_console_client_flags(shared_ptr c, uint32_t sub_version) } } -static void on_DB_V3(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_DB_V3(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); @@ -354,9 +353,9 @@ static void on_DB_V3(shared_ptr c, uint16_t, uint32_t, const string& dat } set_console_client_flags(c, cmd.sub_version); - uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - auto l = s->license_index->verify_gc(serial_number, cmd.access_key, cmd.password); + auto l = s->license_index->verify_gc(serial_number, cmd.access_key.decode(), cmd.password.decode()); c->set_license(l); send_command(c, 0x9A, 0x02); @@ -377,8 +376,8 @@ static void on_DB_V3(shared_ptr c, uint16_t, uint32_t, const string& dat } else { shared_ptr l(new License()); l->serial_number = serial_number; - l->access_key = cmd.access_key; - l->gc_password = cmd.password; + l->access_key = cmd.access_key.decode(); + l->gc_password = cmd.password.decode(); s->license_index->add(l); c->set_license(l); string l_str = l->str(); @@ -388,7 +387,7 @@ static void on_DB_V3(shared_ptr c, uint16_t, uint32_t, const string& dat } } -static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); @@ -396,24 +395,24 @@ static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, const string& c->flags |= flags_for_version(c->version(), -1); c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_DC_TRIAL_EDITION; - uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key); + shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); c->set_license(l); send_command(c, 0x88, 0x00); } catch (const LicenseIndex::incorrect_access_key& e) { - send_message_box(c, u"Incorrect access key"); + send_message_box(c, "Incorrect access key"); c->should_disconnect = true; } catch (const LicenseIndex::missing_license& e) { if (!s->allow_unregistered_users) { - send_message_box(c, u"Incorrect serial number"); + send_message_box(c, "Incorrect serial number"); c->should_disconnect = true; } else { shared_ptr l(new License()); l->serial_number = serial_number; - l->access_key = cmd.access_key; + l->access_key = cmd.access_key.decode(); s->license_index->add(l); c->set_license(l); string l_str = l->str(); @@ -423,33 +422,32 @@ static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, const string& } } -static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DCNTE_8B)); auto s = c->require_server_state(); c->channel.version = GameVersion::DC; + c->channel.language = cmd.language; c->flags |= flags_for_version(c->version(), -1); c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_DC_TRIAL_EDITION; - c->language = cmd.language; - uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - shared_ptr l = s->license_index->verify_v1_v2( - serial_number, cmd.access_key); + shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); c->set_license(l); } catch (const LicenseIndex::incorrect_access_key& e) { - send_message_box(c, u"Incorrect access key"); + send_message_box(c, "Incorrect access key"); c->should_disconnect = true; } catch (const LicenseIndex::missing_license& e) { if (!s->allow_unregistered_users) { - send_message_box(c, u"Incorrect serial number"); + send_message_box(c, "Incorrect serial number"); c->should_disconnect = true; } else { shared_ptr l(new License()); l->serial_number = serial_number; - l->access_key = cmd.access_key; + l->access_key = cmd.access_key.decode(); s->license_index->add(l); c->set_license(l); string l_str = l->str(); @@ -470,7 +468,7 @@ static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, const string& } } -static void on_90_DC(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_90_DC(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data, 0xFFFF); auto s = c->require_server_state(); @@ -478,9 +476,9 @@ static void on_90_DC(shared_ptr c, uint16_t, uint32_t, const string& dat c->flags |= flags_for_version(c->version(), -1); c->flags |= Client::Flag::IS_DC_V1; - uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key); + shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); c->set_license(l); send_command(c, 0x90, 0x02); @@ -495,7 +493,7 @@ static void on_90_DC(shared_ptr c, uint16_t, uint32_t, const string& dat } else { shared_ptr l(new License()); l->serial_number = serial_number; - l->access_key = cmd.access_key; + l->access_key = cmd.access_key.decode(); s->license_index->add(l); c->set_license(l); string l_str = l->str(); @@ -505,9 +503,9 @@ static void on_90_DC(shared_ptr c, uint16_t, uint32_t, const string& dat } } -static void on_92_DC(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_92_DC(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); - c->language = cmd.language; + c->channel.language = cmd.language; // It appears that in response to 90 01, the DCv1 prototype sends 93 rather // than 92, so we use the presence of a 92 command to determine that the // client is actually DCv1 and not the prototype. @@ -515,32 +513,32 @@ static void on_92_DC(shared_ptr c, uint16_t, uint32_t, const string& dat send_command(c, 0x92, 0x01); } -static void on_93_DC(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_93_DC(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data, sizeof(C_LoginExtendedV1_DC_93)); auto s = c->require_server_state(); + c->channel.language = cmd.language; set_console_client_flags(c, cmd.sub_version); - c->language = cmd.language; - uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key); + shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); c->set_license(l); } catch (const LicenseIndex::incorrect_access_key& e) { - send_message_box(c, u"Incorrect access key"); + send_message_box(c, "Incorrect access key"); c->should_disconnect = true; return; } catch (const LicenseIndex::missing_license& e) { if (!s->allow_unregistered_users) { - send_message_box(c, u"Incorrect serial number"); + send_message_box(c, "Incorrect serial number"); c->should_disconnect = true; return; } else { shared_ptr l(new License()); l->serial_number = serial_number; - l->access_key = cmd.access_key; + l->access_key = cmd.access_key.decode(); s->license_index->add(l); c->set_license(l); string l_str = l->str(); @@ -571,22 +569,22 @@ static void on_93_DC(shared_ptr c, uint16_t, uint32_t, const string& dat } } -static void on_9A(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_9A(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); set_console_client_flags(c, cmd.sub_version); - uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { shared_ptr l; switch (c->version()) { case GameVersion::DC: case GameVersion::PC: - l = s->license_index->verify_v1_v2(serial_number, cmd.access_key); + l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); break; case GameVersion::GC: - l = s->license_index->verify_gc(serial_number, cmd.access_key); + l = s->license_index->verify_gc(serial_number, cmd.access_key.decode()); break; case GameVersion::XB: throw runtime_error("xbox licenses are not implemented"); @@ -619,7 +617,7 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, const string& data) } else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) { shared_ptr l(new License()); l->serial_number = serial_number; - l->access_key = cmd.access_key; + l->access_key = cmd.access_key.decode(); s->license_index->add(l); c->set_license(l); string l_str = l->str(); @@ -631,24 +629,23 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, const string& data) } } -static void on_9C(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_9C(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); + c->channel.language = cmd.language; set_console_client_flags(c, cmd.sub_version); - c->language = cmd.language; - uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { shared_ptr l; switch (c->version()) { case GameVersion::DC: case GameVersion::PC: - l = s->license_index->verify_v1_v2(serial_number, cmd.access_key); + l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); break; case GameVersion::GC: - l = s->license_index->verify_gc(serial_number, cmd.access_key, - cmd.password); + l = s->license_index->verify_gc(serial_number, cmd.access_key.decode(), cmd.password.decode()); break; case GameVersion::XB: throw runtime_error("xbox licenses are not implemented"); @@ -672,9 +669,9 @@ static void on_9C(shared_ptr c, uint16_t, uint32_t, const string& data) } else { shared_ptr l(new License()); l->serial_number = serial_number; - l->access_key = cmd.access_key; + l->access_key = cmd.access_key.decode(); if (c->version() == GameVersion::GC) { - l->gc_password = cmd.password; + l->gc_password = cmd.password.decode(); } s->license_index->add(l); c->set_license(l); @@ -685,7 +682,7 @@ static void on_9C(shared_ptr c, uint16_t, uint32_t, const string& data) } } -static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, const string& data) { +static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& data) { const C_Login_DC_PC_GC_9D* base_cmd; auto s = c->require_server_state(); @@ -743,8 +740,8 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, const str throw logic_error("9D/9E handler called for incorrect command"); } + c->channel.language = base_cmd->language; set_console_client_flags(c, base_cmd->sub_version); - c->language = base_cmd->language; // See system/ppc/Episode3USAQuestBufferOverflow.s for where this value gets // set. We use this to determine if the client has already run the code or @@ -758,16 +755,16 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, const str c->flags |= Client::Flag::NO_SEND_FUNCTION_CALL; } - uint32_t serial_number = stoul(base_cmd->serial_number, nullptr, 16); + uint32_t serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); try { shared_ptr l; switch (c->version()) { case GameVersion::DC: case GameVersion::PC: - l = s->license_index->verify_v1_v2(serial_number, base_cmd->access_key); + l = s->license_index->verify_v1_v2(serial_number, base_cmd->access_key.decode()); break; case GameVersion::GC: - l = s->license_index->verify_gc(serial_number, base_cmd->access_key); + l = s->license_index->verify_gc(serial_number, base_cmd->access_key.decode()); break; case GameVersion::XB: throw runtime_error("xbox licenses are not implemented"); @@ -800,7 +797,7 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, const str } else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) { shared_ptr l(new License()); l->serial_number = serial_number; - l->access_key = base_cmd->access_key; + l->access_key = base_cmd->access_key.decode(); s->license_index->add(l); c->set_license(l); string l_str = l->str(); @@ -814,7 +811,7 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, const str on_login_complete(c); } -static void on_93_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_93_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data, sizeof(C_Login_BB_93) - 8, sizeof(C_Login_BB_93)); auto s = c->require_server_state(); @@ -830,26 +827,24 @@ static void on_93_BB(shared_ptr c, uint16_t, uint32_t, const string& dat c->flags |= flags_for_version(c->version(), -1); try { - auto l = s->license_index->verify_bb(cmd.username, cmd.password); + auto l = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); c->set_license(l); } catch (const LicenseIndex::incorrect_password& e) { - u16string message = u"Login failed: " + decode_sjis(e.what()); - send_message_box(c, message.c_str()); + send_message_box(c, string_printf("Login failed: %s", e.what())); c->should_disconnect = true; return; } catch (const LicenseIndex::missing_license& e) { if (!s->allow_unregistered_users) { - u16string message = u"Login failed: " + decode_sjis(e.what()); - send_message_box(c, message.c_str()); + send_message_box(c, string_printf("Login failed: %s", e.what())); c->should_disconnect = true; return; } else { shared_ptr l(new License()); - l->serial_number = fnv1a32(cmd.username) & 0x7FFFFFFF; - l->bb_username = cmd.username; - l->bb_password = cmd.password; + l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF; + l->bb_username = cmd.username.decode(); + l->bb_password = cmd.password.decode(); s->license_index->add(l); c->set_license(l); string l_str = l->str(); @@ -903,7 +898,7 @@ static void on_93_BB(shared_ptr c, uint16_t, uint32_t, const string& dat } } -static void on_9F_V3(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_9F_V3(shared_ptr c, uint16_t, uint32_t, string& data) { if (c->version() == GameVersion::BB) { const auto& cfg = check_size_t(data); c->import_config(cfg); @@ -913,12 +908,12 @@ static void on_9F_V3(shared_ptr c, uint16_t, uint32_t, const string& dat } } -static void on_96(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_96(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_t(data); send_server_time(c); } -static void on_B1(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_B1(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); send_server_time(c); // The B1 command is sent in response to a 97 command, which is normally part @@ -937,7 +932,7 @@ static void on_B1(shared_ptr c, uint16_t, uint32_t, const string& data) } } -static void on_BA_Ep3(shared_ptr c, uint16_t command, uint32_t, const string& data) { +static void on_BA_Ep3(shared_ptr c, uint16_t command, uint32_t, string& data) { const auto& in_cmd = check_size_t(data); auto s = c->require_server_state(); auto l = c->lobby.lock(); @@ -1150,8 +1145,8 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr l, int16_t ta auto c = game_clients.begin()->second; auto s = c->require_server_state(); - u16string name = tourn ? decode_sjis(tourn->get_name()) : u""; - auto game = create_game_generic(s, c, name, u"", Episode::EP3); + string name = tourn ? tourn->get_name() : ""; + auto game = create_game_generic(s, c, name, "", Episode::EP3); if (!game) { return false; } @@ -1181,13 +1176,13 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr l, int16_t ta for (const auto& it : game_clients) { auto other_c = it.second; send_self_leave_notification(other_c); - u16string message; + string message; if (tourn) { - message = decode_sjis(string_printf( + message = string_printf( "$C7Waiting to begin match in tournament\n$C6%s$C7...\n\n(Hold B+X+START to abort)", - tourn->get_name().c_str())); + tourn->get_name().c_str()); } else { - message = u"$C7Waiting to begin battle table match...\n\n(Hold B+X+START to abort)"; + message = "$C7Waiting to begin battle table match...\n\n(Hold B+X+START to abort)"; } send_message_box(other_c, message); } @@ -1205,7 +1200,7 @@ static void on_ep3_battle_table_state_updated(shared_ptr l, int16_t table start_ep3_battle_table_game_if_ready(l, table_number); } -static void on_E4_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { +static void on_E4_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& data) { const auto& cmd = check_size_t(data); auto l = c->require_lobby(); @@ -1246,7 +1241,7 @@ static void on_E4_Ep3(shared_ptr c, uint16_t, uint32_t flag, const strin } } -static void on_E5_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { +static void on_E5_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& data) { check_size_t(data); auto l = c->require_lobby(); if (l->is_game() || !l->is_ep3()) { @@ -1265,7 +1260,7 @@ static void on_E5_Ep3(shared_ptr c, uint16_t, uint32_t flag, const strin on_ep3_battle_table_state_updated(l, c->card_battle_table_number); } -static void on_DC_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { +static void on_DC_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& data) { check_size_v(data.size(), 0); auto l = c->lobby.lock(); @@ -1299,7 +1294,7 @@ static void on_tournament_bracket_updated( } } -static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { auto l = c->lobby.lock(); if (!l) { // In rare cases (e.g. when two players end a tournament's match results @@ -1348,21 +1343,21 @@ static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, const string& da if (existing_c) { auto existing_p = existing_c->game_data.player(); PlayerLobbyDataDCGC lobby_data; - lobby_data.name = encode_sjis(existing_p->disp.name); + lobby_data.name.encode(existing_p->disp.name.decode(existing_c->language()), c->language()); lobby_data.player_tag = 0x00010000; lobby_data.guild_card = existing_c->license->serial_number; l->battle_record->add_player( lobby_data, existing_p->inventory, - existing_p->disp.to_dcpcv3(), + existing_p->disp.to_dcpcv3(c->language(), c->language()), c->game_data.ep3_config ? (c->game_data.ep3_config->online_clv_exp / 100) : 0); } } if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { if (c->ep3_prev_battle_record) { - send_text_message(l, u"$C6Recording complete"); + send_text_message(l, "$C6Recording complete"); } - send_text_message(l, u"$C6Recording enabled"); + send_text_message(l, "$C6Recording enabled"); } } } @@ -1423,7 +1418,7 @@ static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, const string& da } } -static void on_E2_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string&) { +static void on_E2_Ep3(shared_ptr c, uint16_t, uint32_t flag, string&) { switch (flag) { case 0x00: // Request tournament list send_ep3_tournament_list(c, false); @@ -1435,10 +1430,10 @@ static void on_E2_Ep3(shared_ptr c, uint16_t, uint32_t flag, const strin if (tourn) { send_ep3_tournament_entry_list(c, tourn, false); } else { - send_lobby_message_box(c, u"$C6The tournament\nhas concluded."); + send_lobby_message_box(c, "$C6The tournament\nhas concluded."); } } else { - send_lobby_message_box(c, u"$C6You are not\nregistered in a\ntournament."); + send_lobby_message_box(c, "$C6You are not\nregistered in a\ntournament."); } break; } @@ -1460,14 +1455,14 @@ static void on_E2_Ep3(shared_ptr c, uint16_t, uint32_t flag, const strin } case 0x03: // Create tournament spectator team (get battle list) case 0x04: // Join tournament spectator team (get team list) - send_lobby_message_box(c, u"$C6Use View Regular\nBattle for this"); + send_lobby_message_box(c, "$C6Use View Regular\nBattle for this"); break; default: throw runtime_error("invalid tournament operation"); } } -static void on_D6_V3(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_D6_V3(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); if (c->flags & Client::Flag::IN_INFORMATION_MENU) { auto s = c->require_server_state(); @@ -1480,7 +1475,7 @@ static void on_D6_V3(shared_ptr c, uint16_t, uint32_t, const string& dat } } -static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); @@ -1494,15 +1489,15 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) bool is_download_quest = !c->lobby.lock(); auto quest_index = s->quest_index_for_client(c); if (!quest_index) { - send_quest_info(c, u"$C6Quests are not available.", is_download_quest); + send_quest_info(c, "$C6Quests are not available.", is_download_quest); } else { auto q = quest_index->get(cmd.item_id); if (!q) { - send_quest_info(c, u"$C4Quest does not\nexist.", is_download_quest); + send_quest_info(c, "$C4Quest does not\nexist.", is_download_quest); } else { - auto vq = q->version(c->quest_version(), c->language); + auto vq = q->version(c->quest_version(), c->language()); if (!vq) { - send_quest_info(c, u"$C4Quest does not\nexist for this game\nversion.", is_download_quest); + send_quest_info(c, "$C4Quest does not\nexist for this game\nversion.", is_download_quest); } else { send_quest_info(c, vq->long_description, is_download_quest); } @@ -1514,12 +1509,12 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) case MenuID::GAME: { auto game = s->find_lobby(cmd.item_id); if (!game) { - send_ship_info(c, u"$C4Game no longer\nexists."); + send_ship_info(c, "$C4Game no longer\nexists."); break; } if (!game->is_game()) { - send_ship_info(c, u"$C4Incorrect game ID"); + send_ship_info(c, "$C4Incorrect game ID"); } else if ((c->flags & Client::Flag::IS_EPISODE_3) && game->is_ep3()) { send_ep3_game_details(c, game); @@ -1530,7 +1525,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) const auto& game_c = game->clients[x]; if (game_c.get()) { auto player = game_c->game_data.player(); - auto name = encode_sjis(player->disp.name); + string name = player->disp.name.decode(game_c->language()); if (game->is_ep3()) { info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n", x + 1, name.c_str(), player->disp.stats.level + 1); @@ -1562,7 +1557,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) if (game->quest) { info += (game->flags & Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) ? "$C6Quest: " : "$C4Quest: "; - info += encode_sjis(game->quest->name); + info += game->quest->name; info += "\n"; } else if (game->flags & Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) { info += "$C6Quest in progress\n"; @@ -1577,7 +1572,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) } strip_trailing_whitespace(info); - send_ship_info(c, decode_sjis(info)); + send_ship_info(c, info); } break; } @@ -1585,7 +1580,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) case MenuID::TOURNAMENTS_FOR_SPEC: case MenuID::TOURNAMENTS: { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { - send_ship_info(c, u"Incorrect menu ID"); + send_ship_info(c, "Incorrect menu ID"); break; } auto tourn = s->ep3_tournament_index->get_tournament(cmd.item_id); @@ -1597,7 +1592,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) case MenuID::TOURNAMENT_ENTRIES: { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { - send_ship_info(c, u"Incorrect menu ID"); + send_ship_info(c, "Incorrect menu ID"); break; } uint16_t tourn_num = cmd.item_id >> 16; @@ -1634,19 +1629,19 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) message += string_printf("\n $C3%s \"%s\"$C7", player.com_deck->player_name.c_str(), player.com_deck->deck_name.c_str()); } } - send_ship_info(c, decode_sjis(message)); + send_ship_info(c, message); } else { - send_ship_info(c, u"$C7No such team"); + send_ship_info(c, "$C7No such team"); } } else { - send_ship_info(c, u"$C7No such tournament"); + send_ship_info(c, "$C7No such tournament"); } break; } default: if (!c->last_menu_sent || c->last_menu_sent->menu_id != cmd.menu_id) { - send_ship_info(c, u"Incorrect menu ID"); + send_ship_info(c, "Incorrect menu ID"); } else { for (const auto& item : c->last_menu_sent->items) { if (item.item_id == cmd.item_id) { @@ -1658,37 +1653,37 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) return; } } - send_ship_info(c, u"$C4Incorrect menu\nitem ID"); + send_ship_info(c, "$C4Incorrect menu\nitem ID"); } break; } } -static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { bool uses_unicode = ((c->version() == GameVersion::PC) || (c->version() == GameVersion::BB)); uint32_t menu_id; uint32_t item_id; - u16string team_name; - u16string password; + string team_name; + string password; if (data.size() > sizeof(C_MenuSelection_10_Flag00)) { if (uses_unicode) { // TODO: We can support the Flag03 variant here, but PC/BB probably never // actually use it. const auto& cmd = check_size_t(data); - password = cmd.password; + password = cmd.password.decode(c->language()); menu_id = cmd.basic_cmd.menu_id; item_id = cmd.basic_cmd.item_id; } else if (data.size() > sizeof(C_MenuSelection_DC_V3_10_Flag02)) { const auto& cmd = check_size_t(data); - team_name = decode_sjis(cmd.unknown_a1); - password = decode_sjis(cmd.password); + team_name = cmd.unknown_a1.decode(c->language()); + password = cmd.password.decode(c->language()); menu_id = cmd.basic_cmd.menu_id; item_id = cmd.basic_cmd.item_id; } else { const auto& cmd = check_size_t(data); - password = decode_sjis(cmd.password); + password = cmd.password.decode(c->language()); menu_id = cmd.basic_cmd.menu_id; item_id = cmd.basic_cmd.item_id; } @@ -1753,10 +1748,10 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) if (num_ep3_categories == 1) { auto quest_index = s->quest_index_for_client(c); if (quest_index) { - auto quests = quest_index->filter(ep3_category_id, c->quest_version(), c->language); + auto quests = quest_index->filter(ep3_category_id, c->quest_version(), c->language()); send_quest_menu(c, MenuID::QUEST, quests, true); } else { - send_lobby_message_box(c, u"$C6Quests are not available."); + send_lobby_message_box(c, "$C6Quests are not available."); } break; } @@ -1808,7 +1803,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) break; default: - send_message_box(c, u"Incorrect menu item ID."); + send_message_box(c, "Incorrect menu item ID."); break; } break; @@ -1823,7 +1818,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) try { send_message_box(c, c->require_server_state()->information_contents->at(item_id).c_str()); } catch (const out_of_range&) { - send_message_box(c, u"$C6No such information exists."); + send_message_box(c, "$C6No such information exists."); } } break; @@ -1893,7 +1888,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) send_menu(c, proxy_options_menu_for_client(c)); break; default: - send_message_box(c, u"Incorrect menu item ID."); + send_message_box(c, "Incorrect menu item ID."); break; } break; @@ -1915,7 +1910,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) } if (!dest) { - send_message_box(c, u"$C6No such destination exists."); + send_message_box(c, "$C6No such destination exists."); c->should_disconnect = true; } else { // Clear Check Tactics menu so client won't see newserv tournament @@ -1956,7 +1951,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) } if (!dest) { - send_message_box(c, u"$C6No such destination exists."); + send_message_box(c, "$C6No such destination exists."); c->should_disconnect = true; } else { // Clear Check Tactics menu so client won't see newserv tournament @@ -1974,52 +1969,52 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) auto s = c->require_server_state(); auto game = s->find_lobby(item_id); if (!game) { - send_lobby_message_box(c, u"$C6You cannot join this\ngame because it no\nlonger exists."); + send_lobby_message_box(c, "$C6You cannot join this\ngame because it no\nlonger exists."); break; } if (!game->is_game()) { - send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nnot a game."); + send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nnot a game."); break; } if (game->count_clients() >= game->max_clients) { - send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfull."); + send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfull."); break; } if (!game->version_is_allowed(c->quest_version()) || (!game->is_ep3() != !(c->flags & Client::Flag::IS_EPISODE_3)) || (!(game->flags & Lobby::Flag::IS_EP3_TRIAL) != !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION))) { - send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO."); + send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO."); break; } if (game->flags & Lobby::Flag::QUEST_IN_PROGRESS) { - send_lobby_message_box(c, u"$C6You cannot join this\ngame because a\nquest is already\nin progress."); + send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nquest is already\nin progress."); break; } if (game->flags & Lobby::Flag::BATTLE_IN_PROGRESS) { - send_lobby_message_box(c, u"$C6You cannot join this\ngame because a\nbattle is already\nin progress."); + send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nbattle is already\nin progress."); break; } if (game->any_client_loading()) { - send_lobby_message_box(c, u"$C6You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon."); + send_lobby_message_box(c, "$C6You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon."); break; } if (game->mode == GameMode::SOLO) { - send_lobby_message_box(c, u"$C6You cannot join this\n game because it is\na Solo Mode game."); + send_lobby_message_box(c, "$C6You cannot join this\n game because it is\na Solo Mode game."); break; } if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES)) { if (!game->password.empty() && (password != game->password)) { - send_lobby_message_box(c, u"$C6Incorrect password."); + send_lobby_message_box(c, "$C6Incorrect password."); break; } auto p = c->game_data.player(); if (p->disp.stats.level < game->min_level) { - send_lobby_message_box(c, u"$C6Your level is too\nlow to join this\ngame."); + send_lobby_message_box(c, "$C6Your level is too\nlow to join this\ngame."); break; } if (p->disp.stats.level > game->max_level) { - send_lobby_message_box(c, u"$C6Your level is too\nhigh to join this\ngame."); + send_lobby_message_box(c, "$C6Your level is too\nhigh to join this\ngame."); break; } } @@ -2035,11 +2030,11 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) auto s = c->require_server_state(); auto quest_index = s->quest_index_for_client(c); if (!quest_index) { - send_lobby_message_box(c, u"$C6Quests are not available."); + send_lobby_message_box(c, "$C6Quests are not available."); break; } shared_ptr l = c->lobby.lock(); - auto quests = quest_index->filter(item_id, c->quest_version(), c->language); + auto quests = quest_index->filter(item_id, c->quest_version(), c->language()); // Hack: Assume the menu to be sent is the download quest menu if the // client is not in any lobby @@ -2051,12 +2046,12 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) auto s = c->require_server_state(); auto quest_index = s->quest_index_for_client(c); if (!quest_index) { - send_lobby_message_box(c, u"$C6Quests are not\navailable."); + send_lobby_message_box(c, "$C6Quests are not\navailable."); break; } auto q = quest_index->get(item_id); if (!q) { - send_lobby_message_box(c, u"$C6Quest does not exist."); + send_lobby_message_box(c, "$C6Quest does not exist."); break; } @@ -2064,17 +2059,17 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) // Otherwise, they must be in a game to load a quest. auto l = c->lobby.lock(); if (l && !l->is_game()) { - send_lobby_message_box(c, u"$C6Quests cannot be\nloaded in lobbies."); + send_lobby_message_box(c, "$C6Quests cannot be\nloaded in lobbies."); break; } if (l) { if (q->episode == Episode::EP3) { - send_lobby_message_box(c, u"$C6Episode 3 quests\ncannot be loaded\nvia this interface."); + send_lobby_message_box(c, "$C6Episode 3 quests\ncannot be loaded\nvia this interface."); break; } if (l->quest) { - send_lobby_message_box(c, u"$C6A quest is already\nin progress."); + send_lobby_message_box(c, "$C6A quest is already\nin progress."); break; } if (q->joinable) { @@ -2095,9 +2090,9 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) continue; } - auto vq = q->version(lc->quest_version(), lc->language); + auto vq = q->version(lc->quest_version(), lc->language()); if (!vq) { - send_lobby_message_box(lc, u"$C6Quest does not exist\nfor this game version."); + send_lobby_message_box(lc, "$C6Quest does not exist\nfor this game version."); lc->should_disconnect = true; break; } @@ -2142,10 +2137,9 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) } } else { - string quest_name = encode_sjis(q->name); - auto vq = q->version(c->quest_version(), c->language); + auto vq = q->version(c->quest_version(), c->language()); if (!vq) { - send_lobby_message_box(c, u"$C6Quest does not exist\nfor this game version."); + send_lobby_message_box(c, "$C6Quest does not exist\nfor this game version."); break; } // Episode 3 uses the download quest commands (A6/A7) but does not @@ -2154,11 +2148,11 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) // TODO: This is not true for Episode 3 Trial Edition. We also would // have to convert the map to a MapDefinitionTrial, though. if (vq->version == QuestScriptVersion::GC_EP3) { - send_open_quest_file(c, quest_name, vq->bin_filename(), vq->bin_contents, QuestFileType::EPISODE_3); + send_open_quest_file(c, q->name, vq->bin_filename(), vq->bin_contents, QuestFileType::EPISODE_3); } else { vq = vq->create_download_quest(); - send_open_quest_file(c, quest_name, vq->bin_filename(), vq->bin_contents, QuestFileType::DOWNLOAD); - send_open_quest_file(c, quest_name, vq->dat_filename(), vq->dat_contents, QuestFileType::DOWNLOAD); + send_open_quest_file(c, q->name, vq->bin_filename(), vq->bin_contents, QuestFileType::DOWNLOAD); + send_open_quest_file(c, q->name, vq->dat_filename(), vq->dat_contents, QuestFileType::DOWNLOAD); } } break; @@ -2221,12 +2215,12 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) throw runtime_error("non-Episode 3 client attempted to join tournament"); } if (c->ep3_tournament_team.lock()) { - send_lobby_message_box(c, u"$C6You are registered\nin a different\ntournament already"); + send_lobby_message_box(c, "$C6You are registered\nin a different\ntournament already"); break; } if (team_name.empty()) { - team_name = c->game_data.player()->disp.name; - team_name += decode_sjis(string_printf("/%" PRIX32, c->license->serial_number)); + team_name = c->game_data.player()->disp.name.decode(c->language()); + team_name += string_printf("/%" PRIX32, c->license->serial_number); } auto s = c->require_server_state(); uint16_t tourn_num = item_id >> 16; @@ -2236,7 +2230,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) auto team = tourn->get_team(team_index); if (team) { try { - team->register_player(c, encode_sjis(team_name), encode_sjis(password)); + team->register_player(c, team_name, password); c->ep3_tournament_team = team; tourn->send_all_state_updates(); string message = string_printf("$C7You are registered in $C6%s$C7.\n\ @@ -2251,29 +2245,29 @@ partner (if any) and opponent(s).", } catch (const exception& e) { string message = string_printf("Cannot join team:\n%s", e.what()); - send_lobby_message_box(c, decode_sjis(message)); + send_lobby_message_box(c, message); } } else { - send_lobby_message_box(c, u"Team does not exist"); + send_lobby_message_box(c, "Team does not exist"); } } else { - send_lobby_message_box(c, u"Tournament does\nnot exist"); + send_lobby_message_box(c, "Tournament does\nnot exist"); } break; } default: - send_message_box(c, u"Incorrect menu ID"); + send_message_box(c, "Incorrect menu ID"); break; } } -static void on_84(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_84(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); if (cmd.menu_id != MenuID::LOBBY) { - send_message_box(c, u"Incorrect menu ID"); + send_message_box(c, "Incorrect menu ID"); return; } @@ -2288,38 +2282,38 @@ static void on_84(shared_ptr c, uint16_t, uint32_t, const string& data) } else { auto new_lobby = s->find_lobby(cmd.item_id); if (!new_lobby) { - send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The lobby does not\nexist."); + send_lobby_message_box(c, "$C6Can't change lobby\n\n$C7The lobby does not\nexist."); return; } if (new_lobby->is_game()) { - send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The specified lobby\nis a game."); + send_lobby_message_box(c, "$C6Can't change lobby\n\n$C7The specified lobby\nis a game."); return; } if (new_lobby->is_ep3() && !(c->flags & Client::Flag::IS_EPISODE_3)) { - send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The lobby is for\nEpisode 3 only."); + send_lobby_message_box(c, "$C6Can't change lobby\n\n$C7The lobby is for\nEpisode 3 only."); return; } if (!s->change_client_lobby(c, new_lobby)) { - send_lobby_message_box(c, u"$C6Can\'t change lobby\n\n$C7The lobby is full."); + send_lobby_message_box(c, "$C6Can\'t change lobby\n\n$C7The lobby is full."); } } } -static void on_08_E6(shared_ptr c, uint16_t command, uint32_t, const string& data) { +static void on_08_E6(shared_ptr c, uint16_t command, uint32_t, string& data) { check_size_v(data.size(), 0); send_game_menu(c, (command == 0xE6), false); } -static void on_1F(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_1F(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); auto s = c->require_server_state(); send_menu(c, s->information_menu_for_version(c->version()), true); } -static void on_A0(shared_ptr c, uint16_t, uint32_t, const string&) { +static void on_A0(shared_ptr c, uint16_t, uint32_t, string&) { // The client sends data in this command, but none of it is important. We // intentionally don't call check_size here, but just ignore the data. @@ -2334,7 +2328,7 @@ static void on_A0(shared_ptr c, uint16_t, uint32_t, const string&) { // to do this if we're not going to show the welcome message or information // menu (that is, if the client will not send a close confirmation). if (!(c->flags & Client::Flag::NO_D6)) { - send_message_box(c, u""); + send_message_box(c, ""); } const auto& port_name = version_to_login_port_name.at(static_cast(c->version())); @@ -2344,12 +2338,12 @@ static void on_A0(shared_ptr c, uint16_t, uint32_t, const string&) { s->name_to_port_config.at(port_name)->port); } -static void on_A1(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { +static void on_A1(shared_ptr c, uint16_t command, uint32_t flag, string& data) { // newserv doesn't have blocks; treat block change the same as ship change on_A0(c, command, flag, data); } -static void on_8E_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { +static void on_8E_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, string& data) { if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { on_A0(c, command, flag, data); } else { @@ -2357,7 +2351,7 @@ static void on_8E_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, c } } -static void on_8F_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { +static void on_8F_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, string& data) { if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { on_A1(c, command, flag, data); } else { @@ -2384,11 +2378,10 @@ static void send_dol_file_chunk(shared_ptr c, uint32_t start_addr) { send_function_call(c, fn, label_writes, data_to_send); size_t progress_percent = ((offset + bytes_to_send) * 100) / c->loading_dol_file->data.size(); - string info = string_printf("%zu%%%%", progress_percent); - send_ship_info(c, decode_sjis(info)); + send_ship_info(c, string_printf("%zu%%%%", progress_percent)); } -static void on_B3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { +static void on_B3(shared_ptr c, uint16_t, uint32_t flag, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); @@ -2421,20 +2414,20 @@ static void on_B3(shared_ptr c, uint16_t, uint32_t flag, const string& d } } -static void on_A2(shared_ptr c, uint16_t, uint32_t flag, const string& data) { +static void on_A2(shared_ptr c, uint16_t, uint32_t flag, string& data) { check_size_v(data.size(), 0); auto s = c->require_server_state(); auto l = c->lobby.lock(); if (!l || !l->is_game()) { - send_lobby_message_box(c, u"$C6Quests are not available\nin lobbies."); + send_lobby_message_box(c, "$C6Quests are not available\nin lobbies."); return; } // In Episode 3, there are no quest categories, so skip directly to the quest // filter menu. if (c->flags & Client::Flag::IS_EPISODE_3) { - send_lobby_message_box(c, u"$C6Episode 3 does not\nprovide online quests\nvia this interface."); + send_lobby_message_box(c, "$C6Episode 3 does not\nprovide online quests\nvia this interface."); } else { @@ -2467,7 +2460,7 @@ static void on_A2(shared_ptr c, uint16_t, uint32_t flag, const string& d } } -static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); auto l = c->require_lobby(); @@ -2497,7 +2490,7 @@ static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& (l->base_version == GameVersion::BB) && l->map && l->quest) { - auto dat_contents = prs_decompress(*l->quest->version(QuestScriptVersion::BB_V4, c->language)->dat_contents); + auto dat_contents = prs_decompress(*l->quest->version(QuestScriptVersion::BB_V4, c->language())->dat_contents); l->map->clear(); l->map->add_enemies_from_quest_data(l->episode, l->difficulty, l->event, dat_contents.data(), dat_contents.size()); c->log.info("Replaced enemies list with quest layout (%zu entries)", @@ -2510,7 +2503,7 @@ static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& } } -static void on_AA(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_AA(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { @@ -2529,7 +2522,7 @@ static void on_AA(shared_ptr c, uint16_t, uint32_t, const string& data) send_quest_function_call(c, cmd.function_id1); } -static void on_D7_GC(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_D7_GC(shared_ptr c, uint16_t, uint32_t, string& data) { string filename(data); strip_trailing_zeroes(filename); if (filename.find('/') != string::npos) { @@ -2570,17 +2563,17 @@ static void send_file_chunk( } } -static void on_44_A6_V3_BB(shared_ptr c, uint16_t command, uint32_t, const string& data) { +static void on_44_A6_V3_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { const auto& cmd = check_size_t(data); - send_file_chunk(c, cmd.filename, 0, (command == 0xA6)); + send_file_chunk(c, cmd.filename.decode(), 0, (command == 0xA6)); } -static void on_13_A7_V3_BB(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { +static void on_13_A7_V3_BB(shared_ptr c, uint16_t command, uint32_t flag, string& data) { const auto& cmd = check_size_t(data); - send_file_chunk(c, cmd.filename, flag + 1, (command == 0xA7)); + send_file_chunk(c, cmd.filename.decode(), flag + 1, (command == 0xA7)); } -static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { +static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, string& data) { auto s = c->require_server_state(); auto player = c->game_data.player(); auto account = c->game_data.account(); @@ -2590,11 +2583,11 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, cons if (c->flags & Client::Flag::IS_DC_V1) { const auto& pd = check_size_t(data); player->inventory = pd.inventory; - player->disp = pd.disp.to_bb(); + player->disp = pd.disp.to_bb(player->inventory.language, player->inventory.language); } else { const auto& pd = check_size_t(data, 0xFFFF); player->inventory = pd.inventory; - player->disp = pd.disp.to_bb(); + player->disp = pd.disp.to_bb(player->inventory.language, player->inventory.language); player->battle_records = pd.records.battle; player->challenge_records = pd.records.challenge; // TODO: Parse choice search config @@ -2604,15 +2597,20 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, cons case GameVersion::PC: { const auto& pd = check_size_t(data, 0xFFFF); player->inventory = pd.inventory; - player->disp = pd.disp.to_bb(); + player->disp = pd.disp.to_bb(player->inventory.language, player->inventory.language); player->battle_records = pd.records.battle; player->challenge_records = pd.records.challenge; // TODO: Parse choice search config account->blocked_senders = pd.blocked_senders; if (pd.auto_reply_enabled) { - player->auto_reply = pd.auto_reply; + string auto_reply = data.substr(sizeof(pd)); + strip_trailing_zeroes(auto_reply); + if (auto_reply.size() & 1) { + auto_reply.push_back(0); + } + player->auto_reply.encode(tt_utf16_to_utf8(auto_reply), player->inventory.language); } else { - player->auto_reply.clear(0); + player->auto_reply.clear(); } break; } @@ -2662,16 +2660,19 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, cons } player->inventory = cmd->inventory; - player->disp = cmd->disp.to_bb(); + player->disp = cmd->disp.to_bb(player->inventory.language, player->inventory.language); player->battle_records = cmd->records.battle; player->challenge_records = cmd->records.challenge; // TODO: Parse choice search config - player->info_board = cmd->info_board; + player->info_board.encode(cmd->info_board.decode(player->inventory.language), player->inventory.language); account->blocked_senders = cmd->blocked_senders; if (cmd->auto_reply_enabled) { - player->auto_reply = cmd->auto_reply; + string auto_reply = data.substr(sizeof(cmd), 0xAC); + strip_trailing_zeroes(auto_reply); + string encoded = tt_decode_marked(auto_reply, player->inventory.language, false); + player->auto_reply.encode(encoded, player->inventory.language); } else { - player->auto_reply.clear(0); + player->auto_reply.clear(); } break; } @@ -2685,9 +2686,14 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, cons player->info_board = cmd.info_board; account->blocked_senders = cmd.blocked_senders; if (cmd.auto_reply_enabled) { - player->auto_reply = cmd.auto_reply; + string auto_reply = data.substr(sizeof(cmd), 0xAC); + strip_trailing_zeroes(auto_reply); + if (auto_reply.size() & 1) { + auto_reply.push_back(0); + } + player->auto_reply.encode(tt_utf16_to_utf8(auto_reply), player->inventory.language); } else { - player->auto_reply.clear(0); + player->auto_reply.clear(); } break; } @@ -2695,9 +2701,9 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, cons throw logic_error("player data command not implemented for version"); } player->inventory.decode_mags(c->version()); - c->language = player->inventory.language; + c->channel.language = player->inventory.language; - string name_str = remove_language_marker(encode_sjis(player->disp.name)); + string name_str = player->disp.name.decode(c->language()); 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 @@ -2720,8 +2726,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, cons try { c->game_data.save_player_data(); } catch (const exception& e) { - u16string buffer = u"$C6PSOBB player data could\nnot be saved:\n" + decode_sjis(e.what()); - send_text_message(c, buffer.c_str()); + send_text_message_printf(c, "$C6PSOBB player data could\nnot be saved:\n%s", e.what()); failure = true; } @@ -2746,7 +2751,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, cons } } -static void on_6x_C9_CB(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { +static void on_6x_C9_CB(shared_ptr c, uint16_t command, uint32_t flag, string& data) { check_size_v(data.size(), 4, 0xFFFF); if ((data.size() > 0x400) && (command != 0x6C) && (command != 0x6D)) { throw runtime_error("non-extended game command data size is too large"); @@ -2754,88 +2759,75 @@ static void on_6x_C9_CB(shared_ptr c, uint16_t command, uint32_t flag, c on_subcommand_multi(c, command, flag, data); } -static void on_chat_generic(shared_ptr c, const u16string& text) { +static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { + const auto& cmd = check_size_t(data, 0xFFFF); + string text = data.substr(sizeof(cmd)); + strip_trailing_zeroes(text); + if (text.empty()) { + return; + } + bool is_w = (c->version() == GameVersion::PC || c->version() == GameVersion::BB); + if (is_w && (text.size() & 1)) { + text.push_back(0); + } + + auto l = c->lobby.lock(); + char private_flags = 0; + if ((c->version() == GameVersion::GC) && (c->flags & Client::Flag::IS_EPISODE_3) && l && l->is_ep3() && (text[0] != '\t')) { + private_flags = text[0]; + text = text.substr(1); + } + + text = tt_decode_marked(text, c->language(), is_w); if (text.empty()) { return; } - auto l = c->lobby.lock(); - if (!l) { - return; - } - - char private_flags = 0; - u16string processed_text; - if ((text[0] != '\t') && l->is_ep3()) { - private_flags = text[0]; - processed_text = remove_language_marker(text.substr(1)); - } else { - processed_text = remove_language_marker(text); - } - if (processed_text.empty()) { - return; - } - - if (processed_text[0] == L'$') { - if (processed_text[1] == L'$') { - processed_text = processed_text.substr(1); + if (text[0] == '$') { + if (text[1] == '$') { + text = text.substr(1); } else { - on_chat_command(c, processed_text); + on_chat_command(c, text); return; } } - if (!c->can_chat) { + if (!l || !c->can_chat) { return; } auto p = c->game_data.player(); - u16string from_name = p->disp.name; + 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); + } for (size_t x = 0; x < l->max_clients; x++) { if (l->clients[x]) { - send_chat_message(l->clients[x], c->license->serial_number, - from_name, processed_text.c_str(), private_flags); + send_chat_message(l->clients[x], c->license->serial_number, from_name, text, private_flags); } } for (const auto& watcher_l : l->watcher_lobbies) { for (size_t x = 0; x < watcher_l->max_clients; x++) { if (watcher_l->clients[x]) { - send_chat_message(watcher_l->clients[x], c->license->serial_number, - from_name, processed_text.c_str(), private_flags); + send_chat_message(watcher_l->clients[x], c->license->serial_number, from_name, text, private_flags); } } } if (l->battle_record && l->battle_record->battle_in_progress()) { - auto prepared_message = prepare_chat_message( - c->version(), p->disp.name.data(), - processed_text.c_str(), private_flags); - string prepared_message_sjis = encode_sjis(prepared_message); - l->battle_record->add_chat_message(c->license->serial_number, std::move(prepared_message_sjis)); + auto prepared_message = prepare_chat_data(c->version(), c->language(), p->disp.name.decode(c->language()), text, private_flags); + l->battle_record->add_chat_message(c->license->serial_number, std::move(prepared_message)); } } -static void on_06_PC_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { - const auto& cmd = check_size_t(data, 0xFFFF); - u16string text(cmd.text.pcbb, (data.size() - sizeof(C_Chat_06)) / sizeof(char16_t)); - strip_trailing_zeroes(text); - on_chat_generic(c, text); -} - -static void on_06_DC_V3(shared_ptr c, uint16_t, uint32_t, const string& data) { - const auto& cmd = check_size_t(data, 0xFFFF); - u16string decoded_s = decode_sjis(cmd.text.dcv3, data.size() - sizeof(C_Chat_06)); - on_chat_generic(c, decoded_s); -} - -static void on_00E0_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_00E0_BB(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); send_team_and_key_config_bb(c); c->game_data.account()->newserv_flags &= ~AccountFlag::IN_DRESSING_ROOM; c->log.info("Cleared dressing room flag for account"); } -static void on_00E3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_00E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); if (c->bb_game_state == ClientStateBB::CHOOSE_PLAYER) { @@ -2865,7 +2857,7 @@ static void on_00E3_BB(shared_ptr c, uint16_t, uint32_t, const string& d } } -static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, const string& data) { +static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { constexpr size_t max_count = sizeof(GuildCardFileBB::entries) / sizeof(GuildCardEntryBB); constexpr size_t max_blocked = sizeof(GuildCardFileBB::blocked) / sizeof(GuildCardBB); switch (command) { @@ -3003,14 +2995,14 @@ static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, const s } } -static void on_DC_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_DC_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); if (cmd.cont) { send_guild_card_chunk_bb(c, cmd.chunk_index); } } -static void on_xxEB_BB(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { +static void on_xxEB_BB(shared_ptr c, uint16_t command, uint32_t flag, string& data) { check_size_v(data.size(), 0); if (command == 0x04EB) { @@ -3022,7 +3014,7 @@ static void on_xxEB_BB(shared_ptr c, uint16_t command, uint32_t flag, co } } -static void on_00EC_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_00EC_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); if (cmd.reason == 2) { c->game_data.account()->newserv_flags |= AccountFlag::IN_DRESSING_ROOM; @@ -3033,11 +3025,11 @@ static void on_00EC_BB(shared_ptr c, uint16_t, uint32_t, const string& d } } -static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); if (!c->license) { - send_message_box(c, u"$C6You are not logged in."); + send_message_box(c, "$C6You are not logged in."); return; } @@ -3062,8 +3054,7 @@ static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, const string& d try { c->game_data.player()->disp.apply_dressing_room(cmd.preview); } catch (const exception& e) { - string message = string_printf("$C6Character could not be modified:\n%s", e.what()); - send_message_box(c, decode_sjis(message)); + send_message_box(c, string_printf("$C6Character could not be modified:\n%s", e.what())); return; } } else { @@ -3071,8 +3062,7 @@ static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, const string& d auto s = c->require_server_state(); c->game_data.create_player(cmd.preview, s->level_table); } catch (const exception& e) { - string message = string_printf("$C6New character could not be created:\n%s", e.what()); - send_message_box(c, decode_sjis(message)); + send_message_box(c, string_printf("$C6New character could not be created:\n%s", e.what())); return; } } @@ -3083,7 +3073,7 @@ static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, const string& d send_approve_player_choice_bb(c); } -static void on_xxED_BB(shared_ptr c, uint16_t command, uint32_t, const string& data) { +static void on_xxED_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { const auto* cmd = reinterpret_cast(data.data()); switch (command) { @@ -3120,7 +3110,7 @@ static void on_xxED_BB(shared_ptr c, uint16_t command, uint32_t, const s } } -static void on_00E7_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_00E7_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); // We only trust the player's quest data and challenge data. @@ -3134,14 +3124,14 @@ static void on_00E7_BB(shared_ptr c, uint16_t, uint32_t, const string& d p->quest_data2 = cmd.quest_data2; } -static void on_00E2_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_00E2_BB(shared_ptr c, uint16_t, uint32_t, string& data) { // Some clients have only a uint32_t at the end for team rewards auto& cmd = check_size_t(data, sizeof(KeyAndTeamConfigBB) - 4, sizeof(KeyAndTeamConfigBB)); c->game_data.account()->key_config = cmd; } -static void on_89(shared_ptr c, uint16_t, uint32_t flag, const string& data) { +static void on_89(shared_ptr c, uint16_t, uint32_t flag, string& data) { check_size_v(data.size(), 0); c->lobby_arrow_color = flag; @@ -3151,7 +3141,7 @@ static void on_89(shared_ptr c, uint16_t, uint32_t flag, const string& d } } -static void on_40(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_40(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); try { auto s = c->require_server_state(); @@ -3164,13 +3154,13 @@ static void on_40(shared_ptr c, uint16_t, uint32_t, const string& data) } } -static void on_C0(shared_ptr c, uint16_t, uint32_t, const string&) { +static void on_C0(shared_ptr c, uint16_t, uint32_t, string&) { // TODO: Implement choice search. - send_text_message(c, u"$C6Choice Search is\nnot supported"); + send_text_message(c, "$C6Choice Search is\nnot supported"); } -static void on_81(shared_ptr c, uint16_t, uint32_t, const string& data) { - u16string message; +static void on_81(shared_ptr c, uint16_t, uint32_t, string& data) { + string message; uint32_t to_guild_card_number; switch (c->version()) { case GameVersion::DC: @@ -3178,19 +3168,19 @@ static void on_81(shared_ptr c, uint16_t, uint32_t, const string& data) case GameVersion::XB: { const auto& cmd = check_size_t(data); to_guild_card_number = cmd.to_guild_card_number; - message = decode_sjis(cmd.text); + message = cmd.text.decode(c->language()); break; } case GameVersion::PC: { const auto& cmd = check_size_t(data); to_guild_card_number = cmd.to_guild_card_number; - message = cmd.text; + message = cmd.text.decode(c->language()); break; } case GameVersion::BB: { const auto& cmd = check_size_t(data); to_guild_card_number = cmd.to_guild_card_number; - message = cmd.text; + message = cmd.text.decode(c->language()); break; } default: @@ -3207,7 +3197,7 @@ static void on_81(shared_ptr c, uint16_t, uint32_t, const string& data) // TODO: We should store pending messages for accounts somewhere, and send // them when the player signs on again. We should also persist the player's // autoreply setting when they're offline and use it if appropriate here. - send_text_message(c, u"$C6Player is offline"); + send_text_message(c, "$C6Player is offline"); } else { // If the sender is blocked, don't forward the mail @@ -3221,59 +3211,51 @@ static void on_81(shared_ptr c, uint16_t, uint32_t, const string& data) // forward the message in this case. auto target_p = target->game_data.player(); if (!target_p->auto_reply.empty()) { - send_simple_mail(c, target->license->serial_number, target_p->disp.name, target_p->auto_reply); + send_simple_mail( + c, + target->license->serial_number, + target_p->disp.name.decode(target_p->inventory.language), + target_p->auto_reply.decode(target_p->inventory.language)); } // Forward the message send_simple_mail( target, c->license->serial_number, - c->game_data.player()->disp.name, + c->game_data.player()->disp.name.decode(c->language()), message); } } -static void on_D8(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_D8(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); send_info_board(c); } -template -void on_D9_t(shared_ptr c, const string& data) { - auto p = c->game_data.player(true, false); - check_size_v(data.size(), 0, p->info_board.size() * sizeof(CharT)); - p->info_board.assign(reinterpret_cast(data.data()), data.size() / sizeof(CharT)); +void on_D9(shared_ptr c, uint16_t, uint32_t, string& data) { + strip_trailing_zeroes(data); + bool is_w = (c->version() == GameVersion::PC || c->version() == GameVersion::BB); + if (is_w && (data.size() & 1)) { + data.push_back(0); + } + c->game_data.player(true, false)->info_board.encode(tt_decode_marked(data, c->language(), is_w), c->language()); } -void on_D9_a(shared_ptr c, uint16_t, uint32_t, const string& data) { - on_D9_t(c, data); -} -void on_D9_w(shared_ptr c, uint16_t, uint32_t, const string& data) { - on_D9_t(c, data); +void on_C7(shared_ptr c, uint16_t, uint32_t, string& data) { + strip_trailing_zeroes(data); + bool is_w = (c->version() == GameVersion::PC || c->version() == GameVersion::BB); + if (is_w && (data.size() & 1)) { + data.push_back(0); + } + c->game_data.player(true, false)->auto_reply.encode(tt_decode_marked(data, c->language(), is_w), c->language()); } -template -void on_C7_t(shared_ptr c, uint16_t, uint32_t, const string& data) { - auto p = c->game_data.player(true, false); - check_size_v(data.size(), 0, p->auto_reply.size() * sizeof(CharT)); - p->auto_reply.assign( - reinterpret_cast(data.data()), - data.size() / sizeof(CharT)); -} - -void on_C7_a(shared_ptr c, uint16_t cmd, uint32_t flag, const string& data) { - on_C7_t(c, cmd, flag, data); -} -void on_C7_w(shared_ptr c, uint16_t cmd, uint32_t flag, const string& data) { - on_C7_t(c, cmd, flag, data); -} - -static void on_C8(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_C8(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); - c->game_data.player(true, false)->auto_reply.clear(0); + c->game_data.player(true, false)->auto_reply.clear(); } -static void on_C6(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_C6(shared_ptr c, uint16_t, uint32_t, string& data) { if (c->version() == GameVersion::BB) { const auto& cmd = check_size_t(data); c->game_data.account()->blocked_senders = cmd.blocked_senders; @@ -3286,8 +3268,8 @@ static void on_C6(shared_ptr c, uint16_t, uint32_t, const string& data) shared_ptr create_game_generic( shared_ptr s, shared_ptr c, - const std::u16string& name, - const std::u16string& password, + const std::string& name, + const std::string& password, Episode episode, GameMode mode, uint8_t difficulty, @@ -3340,7 +3322,7 @@ shared_ptr create_game_generic( (min_level > p->disp.stats.level)) { // Note: We don't throw here because this is a situation players might // actually encounter while playing the game normally - send_lobby_message_box(c, u"Your level is too\nlow for this\ndifficulty"); + send_lobby_message_box(c, "Your level is too\nlow for this\ndifficulty"); return nullptr; } @@ -3505,7 +3487,7 @@ shared_ptr create_game_generic( return game; } -static void on_C1_PC(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_C1_PC(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); @@ -3515,22 +3497,20 @@ static void on_C1_PC(shared_ptr c, uint16_t, uint32_t, const string& dat } else if (cmd.challenge_mode) { mode = GameMode::CHALLENGE; } - auto game = create_game_generic(s, c, cmd.name, cmd.password, Episode::EP1, mode, cmd.difficulty); + auto game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), Episode::EP1, mode, cmd.difficulty); if (game) { s->change_client_lobby(c, game); c->flags |= Client::Flag::LOADING; } } -static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, const string& data) { +static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, string& data) { auto s = c->require_server_state(); shared_ptr game; if (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { - const auto& cmd = check_size_t>(data); - u16string name = decode_sjis(cmd.name); - u16string password = decode_sjis(cmd.password); - game = create_game_generic(s, c, name, password, Episode::EP1, GameMode::NORMAL, 0, 0, true); + const auto& cmd = check_size_t>(data); + game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), Episode::EP1, GameMode::NORMAL, 0, 0, true); } else { const auto& cmd = check_size_t(data); @@ -3553,9 +3533,6 @@ static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, con episode = cmd.episode == 2 ? Episode::EP2 : Episode::EP1; } - u16string name = decode_sjis(cmd.name); - u16string password = decode_sjis(cmd.password); - GameMode mode = GameMode::NORMAL; if (cmd.battle_mode) { mode = GameMode::BATTLE; @@ -3575,17 +3552,17 @@ static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, con } watched_lobby = s->find_lobby(cmd.item_id); if (!watched_lobby) { - send_lobby_message_box(c, u"$C6This game no longer\nexists"); + send_lobby_message_box(c, "$C6This game no longer\nexists"); return; } if (watched_lobby->flags & Lobby::Flag::SPECTATORS_FORBIDDEN) { - send_lobby_message_box(c, u"$C6This game does not\nallow spectators"); + send_lobby_message_box(c, "$C6This game does not\nallow spectators"); return; } flags |= Lobby::Flag::IS_SPECTATOR_TEAM; } - game = create_game_generic(s, c, name, password, episode, mode, cmd.difficulty, flags, allow_v1, watched_lobby); + game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), episode, mode, cmd.difficulty, flags, allow_v1, watched_lobby); if (game && (game->episode == Episode::EP3)) { game->ep3_ex_result_values = s->ep3_default_ex_values; } @@ -3597,7 +3574,7 @@ static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, con } } -static void on_C1_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_C1_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); @@ -3627,14 +3604,14 @@ static void on_C1_BB(shared_ptr c, uint16_t, uint32_t, const string& dat throw runtime_error("invalid episode number"); } - auto game = create_game_generic(s, c, cmd.name, cmd.password, episode, mode, cmd.difficulty); + auto game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), episode, mode, cmd.difficulty); if (game) { s->change_client_lobby(c, game); c->flags |= Client::Flag::LOADING; } } -static void on_8A(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_8A(shared_ptr c, uint16_t, uint32_t, string& data) { if ((c->version() == GameVersion::DC) && (c->flags & Client::Flag::IS_DC_TRIAL_EDITION)) { const auto& cmd = check_size_t(data); set_console_client_flags(c, cmd.sub_version); @@ -3647,7 +3624,7 @@ static void on_8A(shared_ptr c, uint16_t, uint32_t, const string& data) } } -static void on_6F(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_6F(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); auto l = c->require_lobby(); @@ -3670,7 +3647,7 @@ static void on_6F(shared_ptr c, uint16_t, uint32_t, const string& data) if (!l->quest) { throw runtime_error("JOINABLE_QUEST_IN_PROGRESS is set, but lobby has no quest"); } - auto vq = l->quest->version(c->quest_version(), c->language); + auto vq = l->quest->version(c->quest_version(), c->language()); if (!vq) { throw runtime_error("JOINABLE_QUEST_IN_PROGRESS is set, but lobby has no quest for client version"); } @@ -3692,7 +3669,7 @@ static void on_6F(shared_ptr c, uint16_t, uint32_t, const string& data) } else if (watched_lobby && watched_lobby->ep3_server) { if (!watched_lobby->ep3_server->battle_finished) { watched_lobby->ep3_server->send_commands_for_joining_spectator( - c->channel, c->language, c->flags & Client::Flag::IS_EP3_TRIAL_EDITION); + c->channel, c->flags & Client::Flag::IS_EP3_TRIAL_EDITION); } send_ep3_update_game_metadata(watched_lobby); } @@ -3702,7 +3679,7 @@ static void on_6F(shared_ptr c, uint16_t, uint32_t, const string& data) add_next_game_client(l); } -static void on_99_GC(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_99_GC(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); // This is an odd place to send 6xB4x52, but there's a reason for it. If the @@ -3722,7 +3699,7 @@ static void on_99_GC(shared_ptr c, uint16_t, uint32_t, const string& dat } } -static void on_D0_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_D0_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); if (c->game_data.pending_item_trade) { @@ -3762,7 +3739,7 @@ static void on_D0_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& } } -static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); if (!c->game_data.pending_item_trade) { @@ -3792,7 +3769,7 @@ static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& } } -static void on_D4_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_D4_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); // Annoyingly, if the other client disconnects at a certain point during the @@ -3822,7 +3799,7 @@ static void on_D4_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& send_command(target_c, 0xD4, 0x00); } -static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { +static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& data) { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Ep3 client sent card trade command"); } @@ -3924,7 +3901,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, const strin } } -static void on_EF_Ep3(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_EF_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); if (!(c->flags & Client::Flag::IS_EPISODE_3)) { @@ -3938,12 +3915,12 @@ static void on_EF_Ep3(shared_ptr c, uint16_t, uint32_t, const string& da send_ep3_card_auction(l); } -static void on_xxEA_BB(shared_ptr c, uint16_t command, uint32_t, const string&) { +static void on_xxEA_BB(shared_ptr c, uint16_t command, uint32_t, string&) { // TODO: Implement teams. This command has a very large number of subcommands // (up to 20EA!). if (command == 0x01EA) { - send_lobby_message_box(c, u"$C6Teams are not supported."); + send_lobby_message_box(c, "$C6Teams are not supported."); } else if (command == 0x14EA) { // Do nothing (for now) } else { @@ -3951,7 +3928,7 @@ static void on_xxEA_BB(shared_ptr c, uint16_t command, uint32_t, const s } } -static void on_02_P(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_02_P(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); send_command(c, 0x04, 0x00); // This requests the user's login information } @@ -3987,32 +3964,30 @@ static void change_to_directory_patch( } } -static void on_04_P(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_04_P(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); try { - auto l = s->license_index->verify_bb(cmd.username, cmd.password); + auto l = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); c->set_license(l); } catch (const LicenseIndex::incorrect_password& e) { - u16string message = u"Login failed: " + decode_sjis(e.what()); - send_message_box(c, message.c_str()); + send_message_box(c, string_printf("Login failed: %s", e.what())); c->should_disconnect = true; return; } catch (const LicenseIndex::missing_license& e) { if (!s->allow_unregistered_users) { - u16string message = u"Login failed: " + decode_sjis(e.what()); - send_message_box(c, message.c_str()); + send_message_box(c, string_printf("Login failed: %s", e.what())); c->should_disconnect = true; return; } else { shared_ptr l(new License()); - l->serial_number = fnv1a32(cmd.username) & 0x7FFFFFFF; - l->bb_username = cmd.username; - l->bb_password = cmd.password; + l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF; + l->bb_username = cmd.username.decode(); + l->bb_password = cmd.password.decode(); s->license_index->add(l); c->set_license(l); string l_str = l->str(); @@ -4022,7 +3997,7 @@ static void on_04_P(shared_ptr c, uint16_t, uint32_t, const string& data // On BB we can use colors and newlines should be \n; on PC we can't use // colors, the text is auto-word-wrapped, and newlines should be \r\n. - const u16string& message = (c->flags & Client::Flag::IS_BB_PATCH) + const string& message = (c->flags & Client::Flag::IS_BB_PATCH) ? s->bb_patch_server_message : s->pc_patch_server_message; if (!message.empty()) { @@ -4037,8 +4012,7 @@ static void on_04_P(shared_ptr c, uint16_t, uint32_t, const string& data for (const auto& file : index->all_files()) { change_to_directory_patch(c, path_directories, file->path_directories); - S_FileChecksumRequest_Patch_0C req = { - c->patch_file_checksum_requests.size(), file->name}; + S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}}; send_command_t(c, 0x0C, 0x00, req); c->patch_file_checksum_requests.emplace_back(file); } @@ -4059,7 +4033,7 @@ static void on_04_P(shared_ptr c, uint16_t, uint32_t, const string& data } } -static void on_0F_P(shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_0F_P(shared_ptr c, uint16_t, uint32_t, string& data) { auto& cmd = check_size_t(data); auto& req = c->patch_file_checksum_requests.at(cmd.request_id); req.crc32 = cmd.checksum; @@ -4067,7 +4041,7 @@ static void on_0F_P(shared_ptr c, uint16_t, uint32_t, const string& data req.response_received = true; } -static void on_10_P(shared_ptr c, uint16_t, uint32_t, const string&) { +static void on_10_P(shared_ptr c, uint16_t, uint32_t, string&) { S_StartFileDownloads_Patch_11 start_cmd = {0, 0}; for (const auto& req : c->patch_file_checksum_requests) { @@ -4099,16 +4073,16 @@ static void on_10_P(shared_ptr c, uint16_t, uint32_t, const string&) { send_command(c, 0x12, 0x00); } -static void on_ignored(shared_ptr, uint16_t, uint32_t, const string&) {} +static void on_ignored(shared_ptr, uint16_t, uint32_t, string&) {} static void on_unimplemented_command( - shared_ptr c, uint16_t command, uint32_t flag, const string& data) { + shared_ptr c, uint16_t command, uint32_t flag, string& data) { c->log.warning("Unknown command: size=%04zX command=%04hX flag=%08" PRIX32, data.size(), command, flag); throw invalid_argument("unimplemented command"); } -typedef void (*on_command_t)(shared_ptr c, uint16_t command, uint32_t flag, const string& data); +typedef void (*on_command_t)(shared_ptr c, uint16_t command, uint32_t flag, string& data); // Command handler table, indexed by command number and game version. Null // entries in this table cause on_unimplemented_command to be called, which @@ -4122,7 +4096,7 @@ static on_command_t handlers[0x100][6] = { /* 03 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 04 */ {on_04_P, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 05 */ {nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored}, - /* 06 */ {nullptr, on_06_DC_V3, on_06_PC_BB, on_06_DC_V3, on_06_DC_V3, on_06_PC_BB}, + /* 06 */ {nullptr, on_06, on_06, on_06, on_06, on_06}, /* 07 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 08 */ {nullptr, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6}, /* 09 */ {nullptr, on_09, on_09, on_09, on_09, on_09}, @@ -4321,7 +4295,7 @@ static on_command_t handlers[0x100][6] = { /* C4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* C5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* C6 */ {nullptr, nullptr, on_C6, on_C6, on_C6, on_C6}, - /* C7 */ {nullptr, nullptr, on_C7_w, on_C7_a, on_C7_a, on_C7_w}, + /* C7 */ {nullptr, nullptr, on_C7, on_C7, on_C7, on_C7}, /* C8 */ {nullptr, nullptr, on_C8, on_C8, on_C8, on_C8}, /* C9 */ {nullptr, nullptr, nullptr, on_6x_C9_CB, nullptr, nullptr}, /* CA */ {nullptr, nullptr, nullptr, on_CA_Ep3, nullptr, nullptr}, @@ -4339,7 +4313,7 @@ static on_command_t handlers[0x100][6] = { /* D6 */ {nullptr, nullptr, nullptr, on_D6_V3, on_D6_V3, nullptr}, /* D7 */ {nullptr, nullptr, nullptr, on_D7_GC, on_D7_GC, nullptr}, /* D8 */ {nullptr, nullptr, on_D8, on_D8, on_D8, on_D8}, - /* D9 */ {nullptr, nullptr, on_D9_w, on_D9_a, on_D9_a, on_D9_w}, + /* D9 */ {nullptr, nullptr, on_D9, on_D9, on_D9, on_D9}, /* DA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* DB */ {nullptr, nullptr, nullptr, on_DB_V3, on_DB_V3, nullptr}, /* DC */ {nullptr, nullptr, nullptr, on_DC_Ep3, nullptr, on_DC_BB}, @@ -4429,7 +4403,7 @@ void on_command( shared_ptr c, uint16_t command, uint32_t flag, - const string& data) { + string& data) { c->reschedule_ping_and_timeout_events(); // Most of the command handlers assume the client is registered, logged in, @@ -4448,24 +4422,27 @@ void on_command( } } -void on_command_with_header(shared_ptr c, string& data) { +void on_command_with_header(shared_ptr c, const string& data) { switch (c->version()) { case GameVersion::DC: case GameVersion::GC: case GameVersion::XB: { auto& header = check_size_t(data, 0xFFFF); - on_command(c, header.command, header.flag, data.substr(sizeof(header))); + string sub_data = data.substr(sizeof(header)); + on_command(c, header.command, header.flag, sub_data); break; } case GameVersion::PC: case GameVersion::PATCH: { auto& header = check_size_t(data, 0xFFFF); - on_command(c, header.command, header.flag, data.substr(sizeof(header))); + string sub_data = data.substr(sizeof(header)); + on_command(c, header.command, header.flag, sub_data); break; } case GameVersion::BB: { auto& header = check_size_t(data, 0xFFFF); - on_command(c, header.command, header.flag, data.substr(sizeof(header))); + string sub_data = data.substr(sizeof(header)); + on_command(c, header.command, header.flag, sub_data); break; } default: diff --git a/src/ReceiveCommands.hh b/src/ReceiveCommands.hh index 37d9b53f..7618f39f 100644 --- a/src/ReceiveCommands.hh +++ b/src/ReceiveCommands.hh @@ -7,8 +7,8 @@ std::shared_ptr create_game_generic( std::shared_ptr s, std::shared_ptr c, - const std::u16string& name, - const std::u16string& password = u"", + const std::string& name, + const std::string& password = "", Episode episode = Episode::EP1, GameMode mode = GameMode::NORMAL, uint8_t difficulty = 0, @@ -19,5 +19,5 @@ std::shared_ptr create_game_generic( void on_connect(std::shared_ptr c); void on_disconnect(std::shared_ptr c); -void on_command(std::shared_ptr c, uint16_t command, uint32_t flag, const std::string& data); -void on_command_with_header(std::shared_ptr c, std::string& data); +void on_command(std::shared_ptr c, uint16_t command, uint32_t flag, std::string& data); +void on_command_with_header(std::shared_ptr c, const std::string& data); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 80cbf141..9540f1de 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -144,7 +144,7 @@ static void on_forward_sync_joining_player_state(shared_ptr c, uint8_t c } if (c->options.debug) { - string decompressed = bc0_decompress(cmd.data, cmd.compressed_size); + string decompressed = bc0_decompress(reinterpret_cast(data) + sizeof(cmd), cmd.compressed_size); c->log.info("Decompressed sync data (%" PRIX32 " -> %zX bytes; expected %" PRIX32 "):", cmd.compressed_size.load(), decompressed.size(), cmd.decompressed_size.load()); print_data(stderr, decompressed); @@ -191,7 +191,7 @@ static void on_sync_joining_player_item_state(shared_ptr c, uint8_t comm throw runtime_error("compressed end offset is beyond end of command"); } - string decompressed = bc0_decompress(cmd.data, cmd.compressed_size); + string decompressed = bc0_decompress(reinterpret_cast(data) + sizeof(cmd), cmd.compressed_size); if (c->options.debug) { c->log.info("Decompressed item sync data (%" PRIX32 " -> %zX bytes; expected %" PRIX32 "):", cmd.compressed_size.load(), decompressed.size(), cmd.decompressed_size.load()); @@ -216,12 +216,14 @@ static void on_sync_joining_player_item_state(shared_ptr c, uint8_t comm "decompressed 6x6D data (0x%zX bytes) is too short for all items (0x%zX bytes)", decompressed.size(), required_size)); } + auto* floor_items = reinterpret_cast( + decompressed.data() + sizeof(G_SyncItemState_6x6D_Decompressed)); for (size_t z = 0; z < num_floor_items; z++) { // NOTE: If we use this codepath for non-V3 in the future, we'll need to // change this hardcoded version. This only works because GC's mag // encoding/decoding is symmetric (encode and decode do the same thing). - decompressed_cmd->items[z].item_data.decode_if_mag(GameVersion::GC); + floor_items[z].item_data.decode_if_mag(GameVersion::GC); } string out_compressed_data = bc0_compress(decompressed); @@ -408,18 +410,18 @@ static void on_send_guild_card(shared_ptr c, uint8_t command, uint8_t fl switch (c->version()) { case GameVersion::DC: { const auto& cmd = check_size_t(data, size); - c->game_data.player(true, false)->guild_card_description = cmd.description; + c->game_data.player(true, false)->guild_card_description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } case GameVersion::PC: { const auto& cmd = check_size_t(data, size); - c->game_data.player(true, false)->guild_card_description = cmd.description; + c->game_data.player(true, false)->guild_card_description = cmd.guild_card.description; break; } case GameVersion::GC: case GameVersion::XB: { const auto& cmd = check_size_t(data, size); - c->game_data.player(true, false)->guild_card_description = cmd.description; + c->game_data.player(true, false)->guild_card_description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } case GameVersion::BB: @@ -496,7 +498,7 @@ static void on_word_select_t(shared_ptr c, uint8_t command, uint8_t, con } } catch (const exception& e) { - string name = encode_sjis(c->game_data.player()->disp.name); + string name = c->game_data.player()->disp.name.decode(c->language()); lc->log.warning("Untranslatable Word Select message: %s", e.what()); send_text_message_printf(lc, "$C4Untranslatable Word\nSelect message from\n%s", name.c_str()); } @@ -523,8 +525,8 @@ static void on_set_player_visibility(shared_ptr c, uint8_t command, uint send_arrow_update(l); } if (!l->is_game() && (l->flags & Lobby::Flag::IS_OVERFLOW)) { - send_message_box(c, u"$C6All lobbies are full.\n\n$C7You are in a private lobby. You can use the\nteleporter to join other lobbies if there is space\navailable."); - send_lobby_message_box(c, u""); + send_message_box(c, "$C6All lobbies are full.\n\n$C7You are in a private lobby. You can use the\nteleporter to join other lobbies if there is space\navailable."); + send_lobby_message_box(c, ""); } } } @@ -632,7 +634,7 @@ static void on_switch_state_changed(shared_ptr c, uint8_t command, uint8 (c->last_switch_enabled_command.header.subcommand == 0x05)) { c->log.info("[Switch assist] Replaying previous enable command"); if (c->options.debug) { - send_text_message(c, u"$C5Switch assist"); + send_text_message(c, "$C5Switch assist"); } forward_subcommand(c, command, flag, &c->last_switch_enabled_command, sizeof(c->last_switch_enabled_command)); send_command_t(c, command, flag, c->last_switch_enabled_command); @@ -1278,12 +1280,14 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u return; } + if (!(l->flags & Lobby::Flag::DROPS_ENABLED)) { + return; + } + // If there is no item creator (that is, the game is BB or has server rare // tables disabled), then forward the request to the leader if (!l->item_creator) { - if (l->flags & Lobby::Flag::DROPS_ENABLED) { - forward_subcommand(c, command, flag, data, size); - } + forward_subcommand(c, command, flag, data, size); return; } @@ -1531,7 +1535,7 @@ static void on_enemy_killed_bb(shared_ptr c, uint8_t command, uint8_t fl throw runtime_error("game does not have a map loaded"); } if (cmd.enemy_id >= l->map->enemies.size()) { - send_text_message(c, u"$C6Missing enemy killed"); + send_text_message(c, "$C6Missing enemy killed"); return; } diff --git a/src/ReplaySession.cc b/src/ReplaySession.cc index e8bf0f13..82dc17e0 100644 --- a/src/ReplaySession.cc +++ b/src/ReplaySession.cc @@ -45,6 +45,7 @@ ReplaySession::Client::Client( version(version), channel( this->version, + 1, &ReplaySession::dispatch_on_command_received, &ReplaySession::dispatch_on_error, session, @@ -113,31 +114,31 @@ void ReplaySession::check_for_password(shared_ptr ev) const { case GameVersion::PATCH: { const auto& header = check_size_t(ev->data, 0xFFFF); if (header.command == 0x04) { - check_either(check_size_t(cmd_data, cmd_size).password); + check_either(check_size_t(cmd_data, cmd_size).password.decode()); } break; } case GameVersion::PC: { const auto& header = check_size_t(ev->data, 0xFFFF); if (header.command == 0x03) { - check_ak(check_size_t(cmd_data, cmd_size).access_key2); + check_ak(check_size_t(cmd_data, cmd_size).access_key2.decode()); } else if (header.command == 0x04) { - check_ak(check_size_t(cmd_data, cmd_size).access_key); + check_ak(check_size_t(cmd_data, cmd_size).access_key.decode()); } else if (header.command == 0x9A) { const auto& cmd = check_size_t(cmd_data, cmd_size); - check_ak(cmd.v1_access_key); - check_ak(cmd.access_key); - check_ak(cmd.access_key2); + check_ak(cmd.v1_access_key.decode()); + check_ak(cmd.access_key.decode()); + check_ak(cmd.access_key2.decode()); } else if (header.command == 0x9C) { const auto& cmd = check_size_t(cmd_data, cmd_size); - check_ak(cmd.access_key); - check_pw(cmd.password); + check_ak(cmd.access_key.decode()); + check_pw(cmd.password.decode()); } else if (header.command == 0x9D) { const auto& cmd = check_size_t( cmd_data, cmd_size, sizeof(C_LoginExtended_PC_9D)); - check_ak(cmd.v1_access_key); - check_ak(cmd.access_key); - check_ak(cmd.access_key2); + check_ak(cmd.v1_access_key.decode()); + check_ak(cmd.access_key.decode()); + check_ak(cmd.access_key2.decode()); } break; } @@ -146,62 +147,58 @@ void ReplaySession::check_for_password(shared_ptr ev) const { case GameVersion::XB: { const auto& header = check_size_t(ev->data, 0xFFFF); if (header.command == 0x03) { - check_ak(check_size_t(cmd_data, cmd_size).access_key2); + check_ak(check_size_t(cmd_data, cmd_size).access_key2.decode()); } else if (header.command == 0x04) { - check_ak(check_size_t(cmd_data, cmd_size).access_key); + check_ak(check_size_t(cmd_data, cmd_size).access_key.decode()); } else if (header.command == 0x90) { - check_ak(check_size_t(cmd_data, cmd_size, 0xFFFF).access_key); + check_ak(check_size_t(cmd_data, cmd_size, 0xFFFF).access_key.decode()); } else if (header.command == 0x93) { - const auto& cmd = check_size_t( - cmd_data, cmd_size, sizeof(C_LoginExtendedV1_DC_93)); - check_ak(cmd.access_key); + const auto& cmd = check_size_t(cmd_data, cmd_size, sizeof(C_LoginExtendedV1_DC_93)); + check_ak(cmd.access_key.decode()); } else if (header.command == 0x9A) { const auto& cmd = check_size_t(cmd_data, cmd_size); - check_ak(cmd.v1_access_key); - check_ak(cmd.access_key); - check_ak(cmd.access_key2); + check_ak(cmd.v1_access_key.decode()); + check_ak(cmd.access_key.decode()); + check_ak(cmd.access_key2.decode()); } else if (header.command == 0x9C) { const auto& cmd = check_size_t(cmd_data, cmd_size); - check_ak(cmd.access_key); - check_pw(cmd.password); + check_ak(cmd.access_key.decode()); + check_pw(cmd.password.decode()); } else if (header.command == 0x9D) { - const auto& cmd = check_size_t( - cmd_data, cmd_size, sizeof(C_LoginExtended_DC_GC_9D)); - check_ak(cmd.v1_access_key); - check_ak(cmd.access_key); - check_ak(cmd.access_key2); + const auto& cmd = check_size_t(cmd_data, cmd_size, sizeof(C_LoginExtended_DC_GC_9D)); + check_ak(cmd.v1_access_key.decode()); + check_ak(cmd.access_key.decode()); + check_ak(cmd.access_key2.decode()); } else if (header.command == 0x9E) { if (version == GameVersion::GC) { - const auto& cmd = check_size_t( - cmd_data, cmd_size, sizeof(C_LoginExtended_GC_9E)); - check_ak(cmd.access_key); - check_ak(cmd.access_key2); + const auto& cmd = check_size_t(cmd_data, cmd_size, sizeof(C_LoginExtended_GC_9E)); + check_ak(cmd.access_key.decode()); + check_ak(cmd.access_key2.decode()); } else { // XB - const auto& cmd = check_size_t( - cmd_data, cmd_size, sizeof(C_LoginExtended_XB_9E)); - check_ak(cmd.access_key); - check_ak(cmd.access_key2); + const auto& cmd = check_size_t(cmd_data, cmd_size, sizeof(C_LoginExtended_XB_9E)); + check_ak(cmd.access_key.decode()); + check_ak(cmd.access_key2.decode()); } } else if (header.command == 0xDB) { const auto& cmd = check_size_t(cmd_data, cmd_size); - check_ak(cmd.access_key); - check_ak(cmd.access_key2); - check_pw(cmd.password); + check_ak(cmd.access_key.decode()); + check_ak(cmd.access_key2.decode()); + check_pw(cmd.password.decode()); } break; } case GameVersion::BB: { const auto& header = check_size_t(ev->data, 0xFFFF); if (header.command == 0x04) { - check_pw(check_size_t(cmd_data, cmd_size).password); + check_pw(check_size_t(cmd_data, cmd_size).password.decode()); } else if (header.command == 0x93) { - check_pw(check_size_t(cmd_data, cmd_size).password); + check_pw(check_size_t(cmd_data, cmd_size).password.decode()); } else if (header.command == 0x9C) { - check_pw(check_size_t(cmd_data, cmd_size).password); + check_pw(check_size_t(cmd_data, cmd_size).password.decode()); } else if (header.command == 0x9E) { - check_pw(check_size_t(cmd_data, cmd_size).password); + check_pw(check_size_t(cmd_data, cmd_size).password.decode()); } else if (header.command == 0xDB) { - check_pw(check_size_t(cmd_data, cmd_size).password); + check_pw(check_size_t(cmd_data, cmd_size).password.decode()); } break; } diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index b56fb5ba..db966c30 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -26,9 +26,9 @@ struct ShuffleTables { }; struct PSOVMSFileHeader { - /* 0000 */ ptext short_desc; - /* 0010 */ ptext long_desc; - /* 0030 */ ptext creator_id; + /* 0000 */ pstring short_desc; + /* 0010 */ pstring long_desc; + /* 0030 */ pstring creator_id; /* 0040 */ le_uint16_t num_icons; /* 0042 */ le_uint16_t animation_speed; /* 0044 */ le_uint16_t eyecatch_type; @@ -54,7 +54,7 @@ struct PSOGCIFileHeader { // There is a structure for this part of the header, but we don't use it. /* 0006 */ uint8_t unused; /* 0007 */ uint8_t image_flags; - /* 0008 */ ptext internal_file_name; + /* 0008 */ pstring internal_file_name; /* 0028 */ be_uint32_t modification_time; /* 002C */ be_uint32_t image_data_offset; /* 0030 */ be_uint16_t icon_formats; @@ -67,9 +67,9 @@ struct PSOGCIFileHeader { /* 003C */ be_uint32_t comment_offset; // GCI header ends here (and memcard file data begins here) // game_name is e.g. "PSO EPISODE I & II" or "PSO EPISODE III" - /* 0040 */ ptext game_name; + /* 0040 */ pstring game_name; /* 005C */ be_uint32_t embedded_seed; // Used in some of Ralf's quest packs - /* 0060 */ ptext file_name; + /* 0060 */ pstring file_name; /* 0080 */ parray banner; /* 1880 */ parray icon; // data_size specifies the number of bytes remaining in the file. In all cases @@ -119,7 +119,7 @@ struct PSOGCEp3SystemFile { struct PSOGCSaveFileSymbolChatEntry { /* 00 */ be_uint32_t present; - /* 04 */ ptext name; + /* 04 */ pstring name; /* 1C */ be_uint16_t unused; /* 1E */ uint8_t flags; /* 1F */ uint8_t face_spec; @@ -140,7 +140,7 @@ struct PSOGCSaveFileSymbolChatEntry { struct PSOPCSaveFileSymbolChatEntry { /* 00 */ le_uint32_t present; - /* 04 */ ptext name; + /* 04 */ pstring name; /* 34 */ uint8_t face_spec; /* 35 */ uint8_t flags; /* 36 */ be_uint16_t unused; @@ -212,8 +212,8 @@ struct PSOGCCharacterFile { /* 192C:1510 */ GuildCardV3 guild_card; /* 19BC:15A0 */ parray symbol_chats; /* 1DDC:19C0 */ parray chat_shortcuts; - /* 246C:2050 */ ptext auto_reply; - /* 2518:20FC */ ptext info_board; + /* 246C:2050 */ pstring auto_reply; + /* 2518:20FC */ pstring info_board; /* 25C4:21A8 */ PlayerRecords_Battle battle_records; /* 25DC:21C0 */ parray unknown_a2; /* 25E0:21C4 */ PlayerRecordsV3_Challenge challenge_records; @@ -228,9 +228,9 @@ struct PSOGCCharacterFile { /* 2798:237C */ } __attribute__((packed)); /* 00004 */ parray characters; - /* 1152C */ ptext serial_number; // As %08X (not decimal) - /* 1153C */ ptext access_key; - /* 1154C */ ptext password; + /* 1152C */ pstring serial_number; // As %08X (not decimal) + /* 1153C */ pstring access_key; + /* 1154C */ pstring password; /* 1155C */ be_uint64_t bgm_test_songs_unlocked; /* 11564 */ be_uint32_t save_count; /* 11568 */ be_uint32_t round2_seed; @@ -269,8 +269,8 @@ struct PSOGCEp3CharacterFile { /* 08CC:04B0 */ GuildCardV3 guild_card; /* 095C:0540 */ parray symbol_chats; /* 0D7C:0960 */ parray chat_shortcuts; - /* 140C:0FF0 */ ptext auto_reply; - /* 14B8:109C */ ptext info_board; + /* 140C:0FF0 */ pstring auto_reply; + /* 14B8:109C */ pstring info_board; // In this struct, place_counts[0] is win_count and [1] is loss_count /* 1564:1148 */ PlayerRecords_Battle battle_records; /* 157C:1160 */ parray unknown_a10; @@ -282,9 +282,9 @@ struct PSOGCEp3CharacterFile { /* 39B4:3598 */ } __attribute__((packed)); /* 00004 */ parray characters; - /* 193F0 */ ptext serial_number; // As %08X (not decimal) - /* 19400 */ ptext access_key; // As 12 ASCII characters (decimal) - /* 19410 */ ptext password; + /* 193F0 */ pstring serial_number; // As %08X (not decimal) + /* 19400 */ pstring access_key; // As 12 ASCII characters (decimal) + /* 19410 */ pstring password; // In Episode 3, this field still exists, but is unused since BGM test was // removed from the options menu in favor of the jukebox. The jukebox is // accessible online only, and which songs are available there is controlled @@ -313,8 +313,8 @@ struct PSOGCGuildCardFile { // except for 32-bit fields, which are big-endian here. /* 0000 */ be_uint32_t player_tag; // == 0x00000001 (not 0x00010000) /* 0004 */ be_uint32_t guild_card_number; - /* 0008 */ ptext name; - /* 0020 */ ptext description; + /* 0008 */ pstring name; + /* 0020 */ pstring description; /* 008C */ uint8_t present; /* 008D */ uint8_t language; /* 008E */ uint8_t section_id; @@ -327,7 +327,7 @@ struct PSOGCGuildCardFile { /* 0091 */ uint8_t unknown_a2; /* 0092 */ uint8_t unknown_a3; /* 0093 */ uint8_t unknown_a4; - /* 0094 */ ptext comment; + /* 0094 */ pstring comment; /* 0100 */ } __attribute__((packed)); /* 00C4 */ parray entries; @@ -349,7 +349,7 @@ struct PSOGCSnapshotFile { /* 1800A */ be_int16_t max_players; /* 1800C */ parray players_present; /* 1803C */ parray player_levels; - /* 1806C */ parray, 12> player_names; + /* 1806C */ parray, 12> player_names; /* 1818C */ bool checksum_correct() const; @@ -592,8 +592,8 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD /* 123C */ parray unknown_a3; /* 1CDC */ parray tech_menu_shortcut_entries; /* 1D04 */ parray unknown_a4; - /* 1D30 */ ptext serial_number; // As %08X (not decimal) - /* 1D40 */ ptext access_key; // As decimal + /* 1D30 */ pstring serial_number; // As %08X (not decimal) + /* 1D40 */ pstring access_key; // As decimal /* 1D50 */ le_uint32_t round2_seed; /* 1D54 */ } __attribute__((packed)); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 7bc46cb6..8ceb6a44 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -140,12 +140,10 @@ prepare_server_init_contents_console( uint32_t server_key, uint32_t client_key, uint8_t flags) { bool initial_connection = (flags & SendServerInitFlag::IS_INITIAL_CONNECTION); S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xB4> cmd; - cmd.basic_cmd.copyright = initial_connection - ? dc_port_map_copyright - : dc_lobby_server_copyright; + cmd.basic_cmd.copyright.encode(initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright); cmd.basic_cmd.server_key = server_key; cmd.basic_cmd.client_key = client_key; - cmd.after_message = anti_copyright; + cmd.after_message.encode(anti_copyright); return cmd; } @@ -185,10 +183,10 @@ prepare_server_init_contents_bb( uint8_t flags) { bool use_secondary_message = (flags & SendServerInitFlag::USE_SECONDARY_MESSAGE); S_ServerInitWithAfterMessage_BB_03_9B<0xB4> cmd; - cmd.basic_cmd.copyright = use_secondary_message ? bb_pm_server_copyright : bb_game_server_copyright; + cmd.basic_cmd.copyright.encode(use_secondary_message ? bb_pm_server_copyright : bb_game_server_copyright); cmd.basic_cmd.server_key = server_key; cmd.basic_cmd.client_key = client_key; - cmd.after_message = anti_copyright; + cmd.after_message.encode(anti_copyright); return cmd; } @@ -219,7 +217,7 @@ void send_server_init_patch(shared_ptr c) { uint32_t client_key = random_object(); S_ServerInit_Patch_02 cmd; - cmd.copyright = patch_server_copyright; + cmd.copyright.encode(patch_server_copyright); cmd.server_key = server_key; cmd.client_key = client_key; send_command_t(c, 0x02, 0x00, cmd); @@ -267,29 +265,29 @@ void send_quest_open_file_t( switch (type) { case QuestFileType::ONLINE: command_num = 0x44; - cmd.name = "PSO/" + quest_name; + cmd.name.encode("PSO/" + quest_name); cmd.type = 0; break; case QuestFileType::GBA_DEMO: command_num = 0xA6; - cmd.name = "GBA Demo"; + cmd.name.encode("GBA Demo"); cmd.type = 2; break; case QuestFileType::DOWNLOAD: command_num = 0xA6; - cmd.name = "PSO/" + quest_name; + cmd.name.encode("PSO/" + quest_name); cmd.type = 0; break; case QuestFileType::EPISODE_3: command_num = 0xA6; - cmd.name = "PSO/" + quest_name; + cmd.name.encode("PSO/" + quest_name); cmd.type = 3; break; default: throw logic_error("invalid quest file type"); } cmd.file_size = file_size; - cmd.filename = filename.c_str(); + cmd.filename.encode(filename); send_command_t(c, command_num, 0x00, cmd); } @@ -307,7 +305,7 @@ void send_quest_buffer_overflow(shared_ptr c) { c, "BufferOverflow", filename, 0x18, QuestFileType::EPISODE_3); S_WriteFile_13_A7 cmd; - cmd.filename = filename; + cmd.filename.encode(filename); memcpy(cmd.data.data(), fn->code.data(), fn->code.size()); if (fn->code.size() < 0x400) { memset(&cmd.data[fn->code.size()], 0, 0x400 - fn->code.size()); @@ -546,7 +544,7 @@ void send_stream_file_index_bb(shared_ptr c) { le_uint32_t size; le_uint32_t checksum; // crc32 of file data le_uint32_t offset; // offset in stream (== sum of all previous files' sizes) - ptext filename; + pstring filename; }; vector entries; @@ -569,7 +567,7 @@ void send_stream_file_index_bb(shared_ptr c) { e.checksum = bb_stream_files_cache.get_obj(key + ".crc32", compute_checksum).obj; } e.offset = offset; - e.filename = filename; + e.filename.encode(filename); offset += e.size; } send_command_vt(c, 0x01EB, entries.size(), entries); @@ -655,12 +653,12 @@ void send_complete_player_bb(shared_ptr c) { // patch functions void send_enter_directory_patch(shared_ptr c, const string& dir) { - S_EnterDirectory_Patch_09 cmd = {dir}; + S_EnterDirectory_Patch_09 cmd = {{dir, 1}}; send_command_t(c, 0x09, 0x00, cmd); } void send_patch_file(shared_ptr c, shared_ptr f) { - S_OpenFile_Patch_06 open_cmd = {0, f->size, f->name}; + S_OpenFile_Patch_06 open_cmd = {0, f->size, {f->name, 1}}; send_command_t(c, 0x06, 0x00, open_cmd); for (size_t x = 0; x < f->chunk_crcs.size(); x++) { @@ -681,45 +679,34 @@ void send_patch_file(shared_ptr c, shared_ptr f) { //////////////////////////////////////////////////////////////////////////////// // message functions -void send_text(Channel& ch, StringWriter& w, uint16_t command, - const u16string& text, bool should_add_color) { - if ((ch.version == GameVersion::DC) || - (ch.version == GameVersion::GC) || - (ch.version == GameVersion::XB)) { - string data = encode_sjis(text); - if (should_add_color) { - add_color(w, data.c_str(), data.size()); - } else { - w.write(data); - } - w.put_u8(0); - } else { - if (should_add_color) { - add_color(w, text.c_str(), text.size()); - } else { - w.write(text.data(), text.size() * sizeof(char16_t)); - } +void send_text(Channel& ch, StringWriter& w, uint16_t command, const string& text, bool should_add_color) { + bool is_w = (ch.version == GameVersion::PC || ch.version == GameVersion::BB || ch.version == GameVersion::PATCH); + + w.write(tt_encode_marked_optional(should_add_color ? add_color(text) : text, ch.language, is_w)); + if (is_w) { w.put_u16(0); + } else { + w.put_u8(0); } + while (w.str().size() & 3) { w.put_u8(0); } ch.send(command, 0x00, w.str()); } -void send_text(Channel& ch, uint16_t command, const u16string& text, bool should_add_color) { +void send_text(Channel& ch, uint16_t command, const string& text, bool should_add_color) { StringWriter w; send_text(ch, w, command, text, should_add_color); } -void send_header_text(Channel& ch, uint16_t command, - uint32_t guild_card_number, const u16string& text, bool should_add_color) { +void send_header_text(Channel& ch, uint16_t command, uint32_t guild_card_number, const string& text, bool should_add_color) { StringWriter w; w.put(SC_TextHeader_01_06_11_B0_EE({0, guild_card_number})); send_text(ch, w, command, text, should_add_color); } -void send_message_box(shared_ptr c, const u16string& text) { +void send_message_box(shared_ptr c, const string& text) { uint16_t command; switch (c->version()) { case GameVersion::PATCH: @@ -741,9 +728,10 @@ void send_message_box(shared_ptr c, const u16string& text) { } void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const string& message) { + string encoded = tt_encode_marked(message, ch.language, false); StringWriter w; w.put({frames}); - add_color(w, message.data(), message.size()); + w.write(encoded); w.put_u8(0); while (w.size() & 3) { w.put_u8(0); @@ -751,36 +739,35 @@ void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const string& mess ch.send(0xEA, 0x00, w.str()); } -void send_lobby_name(shared_ptr c, const u16string& text) { +void send_lobby_name(shared_ptr c, const string& text) { send_text(c->channel, 0x8A, text, false); } -void send_quest_info(shared_ptr c, const u16string& text, - bool is_download_quest) { +void send_quest_info(shared_ptr c, const string& text, bool is_download_quest) { send_text(c->channel, is_download_quest ? 0xA5 : 0xA3, text, true); } -void send_lobby_message_box(shared_ptr c, const u16string& text) { +void send_lobby_message_box(shared_ptr c, const string& text) { send_header_text(c->channel, 0x01, 0, text, true); } -void send_ship_info(shared_ptr c, const u16string& text) { +void send_ship_info(shared_ptr c, const string& text) { send_header_text(c->channel, 0x11, 0, text, true); } -void send_ship_info(Channel& ch, const u16string& text) { +void send_ship_info(Channel& ch, const string& text) { send_header_text(ch, 0x11, 0, text, true); } -void send_text_message(Channel& ch, const u16string& text) { +void send_text_message(Channel& ch, const string& text) { send_header_text(ch, 0xB0, 0, text, true); } -void send_text_message(shared_ptr c, const u16string& text) { +void send_text_message(shared_ptr c, const string& text) { send_header_text(c->channel, 0xB0, 0, text, true); } -void send_text_message(shared_ptr l, const u16string& text) { +void send_text_message(shared_ptr l, const string& text) { for (size_t x = 0; x < l->max_clients; x++) { if (l->clients[x]) { send_text_message(l->clients[x], text); @@ -788,7 +775,7 @@ void send_text_message(shared_ptr l, const u16string& text) { } } -void send_text_message(shared_ptr s, const u16string& text) { +void send_text_message(shared_ptr s, const string& text) { // TODO: We should have a collection of all clients (even those not in any // lobby) and use that instead here for (auto& l : s->all_lobbies()) { @@ -796,48 +783,52 @@ void send_text_message(shared_ptr s, const u16string& text) { } } -__attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf( - shared_ptr s, const char* format, ...) { +__attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(shared_ptr s, const char* format, ...) { va_list va; va_start(va, format); string buf = string_vprintf(format, va); va_end(va); - u16string decoded = decode_sjis(buf); for (auto& it : s->id_to_lobby) { for (auto& c : it.second->clients) { if (c && (c->flags & Client::Flag::IS_EPISODE_3)) { - send_text_message(c, decoded); + send_text_message(c, buf); } } } } -u16string prepare_chat_message( - GameVersion version, - const u16string& from_name, - const u16string& text, - char private_flags) { - u16string data; - if (version == GameVersion::BB) { - data.append(u"\tJ"); +string prepare_chat_data(GameVersion version, uint8_t language, const string& from_name, const string& text, char private_flags) { + string data; + if ((version == GameVersion::BB) || (version == GameVersion::PC)) { + if (version == GameVersion::BB) { + data.append("\tJ"); + } + data.append(from_name); + data.append(1, '\t'); + if (private_flags) { + data.append(1, static_cast(private_flags)); + } + data.append(language ? "\tE" : "\tJ"); + data.append(text); + return tt_utf8_to_utf16(data); + } else { + data.append(from_name); + data.append(1, '\t'); + if (private_flags) { + data.append(1, static_cast(private_flags)); + } + data.append(tt_encode_marked(text, language, false)); + return data; } - data.append(remove_language_marker(from_name)); - data.append(1, u'\t'); - if (private_flags) { - data.append(1, static_cast(private_flags)); - } - data.append(u"\tJ"); - data.append(text); - return data; } -void send_chat_message(Channel& ch, const u16string& text, char private_flags) { +void send_chat_message_from_client(Channel& ch, const string& text, char private_flags) { if (private_flags != 0) { if (ch.version != GameVersion::GC) { throw runtime_error("nonzero private_flags in non-GC chat message"); } - u16string effective_text; - effective_text.push_back(static_cast(private_flags)); + string effective_text; + effective_text.push_back(private_flags); effective_text += text; send_header_text(ch, 0x06, 0, effective_text, false); } else { @@ -845,68 +836,63 @@ void send_chat_message(Channel& ch, const u16string& text, char private_flags) { } } -void send_chat_message(shared_ptr c, uint32_t from_guild_card_number, - const u16string& prepared_data) { - send_header_text(c->channel, 0x06, from_guild_card_number, prepared_data, false); +void send_prepared_chat_message(shared_ptr c, uint32_t from_guild_card_number, const string& prepared_data) { + StringWriter w; + w.put(SC_TextHeader_01_06_11_B0_EE{0, from_guild_card_number}); + w.write(prepared_data); + w.put_u8(0); + if ((c->version() == GameVersion::BB) || (c->version() == GameVersion::PC)) { + w.put_u8(0); + } + while (w.size() & 3) { + w.put_u8(0); + } + send_command(c, 0x06, 0x00, w.str()); } -void send_chat_message(shared_ptr l, uint32_t from_guild_card_number, - const u16string& prepared_data) { +void send_prepared_chat_message(shared_ptr l, uint32_t from_guild_card_number, const string& prepared_data) { for (auto c : l->clients) { if (c) { - send_header_text(c->channel, 0x06, from_guild_card_number, prepared_data, false); + send_prepared_chat_message(c, from_guild_card_number, prepared_data); } } } -void send_chat_message(shared_ptr c, uint32_t from_guild_card_number, - const u16string& from_name, const u16string& text, char private_flags) { - auto data = prepare_chat_message(c->version(), from_name, text, private_flags); - send_chat_message(c, from_guild_card_number, data); +void send_chat_message(shared_ptr c, uint32_t from_guild_card_number, const string& from_name, const string& text, char private_flags) { + send_prepared_chat_message(c, from_guild_card_number, prepare_chat_data(c->version(), c->language(), from_name, text, private_flags)); } template -void send_simple_mail_t( - shared_ptr c, - uint32_t from_guild_card_number, - const u16string& from_name, - const u16string& text) { +void send_simple_mail_t(shared_ptr c, uint32_t from_guild_card_number, const string& from_name, const string& text) { CmdT cmd; cmd.player_tag = 0x00010000; cmd.from_guild_card_number = from_guild_card_number; - cmd.from_name = from_name; + cmd.from_name.encode(from_name, c->language()); cmd.to_guild_card_number = c->license->serial_number; - cmd.text = text; + cmd.text.encode(text, c->language()); send_command_t(c, 0x81, 0x00, cmd); } -void send_simple_mail_bb( - shared_ptr c, - uint32_t from_guild_card_number, - const u16string& from_name, - const u16string& text) { +void send_simple_mail_bb(shared_ptr c, uint32_t from_guild_card_number, const string& from_name, const string& text) { SC_SimpleMail_BB_81 cmd; cmd.player_tag = 0x00010000; cmd.from_guild_card_number = from_guild_card_number; - cmd.from_name = from_name; + cmd.from_name.encode(from_name, c->language()); cmd.to_guild_card_number = c->license->serial_number; - cmd.received_date = decode_sjis(format_time(now())); - cmd.text = text; + cmd.received_date.encode(format_time(now()), c->language()); + cmd.text.encode(text, c->language()); send_command_t(c, 0x81, 0x00, cmd); } -void send_simple_mail(shared_ptr c, uint32_t from_guild_card_number, - const u16string& from_name, const u16string& text) { +void send_simple_mail(shared_ptr c, uint32_t from_guild_card_number, const string& from_name, const string& text) { switch (c->version()) { case GameVersion::DC: case GameVersion::GC: case GameVersion::XB: - send_simple_mail_t( - c, from_guild_card_number, from_name, text); + send_simple_mail_t(c, from_guild_card_number, from_name, text); break; case GameVersion::PC: - send_simple_mail_t( - c, from_guild_card_number, from_name, text); + send_simple_mail_t(c, from_guild_card_number, from_name, text); break; case GameVersion::BB: send_simple_mail_bb(c, from_guild_card_number, from_name, text); @@ -919,9 +905,9 @@ void send_simple_mail(shared_ptr c, uint32_t from_guild_card_number, //////////////////////////////////////////////////////////////////////////////// // info board -template +template void send_info_board_t(shared_ptr c) { - vector> entries; + vector> entries; auto l = c->require_lobby(); for (const auto& other_c : l->clients) { if (!other_c.get()) { @@ -929,9 +915,8 @@ void send_info_board_t(shared_ptr c) { } auto other_p = other_c->game_data.player(true, false); auto& e = entries.emplace_back(); - e.name = other_p->disp.name; - e.message = other_p->info_board; - add_color_inplace(e.message); + e.name.encode(other_p->disp.name.decode(other_p->inventory.language), c->language()); + e.message.encode(add_color(other_p->info_board.decode(other_p->inventory.language)), c->language()); } send_command_vt(c, 0xD8, entries.size(), entries); } @@ -940,13 +925,15 @@ void send_info_board(shared_ptr c) { if (c->version() == GameVersion::PC || c->version() == GameVersion::PATCH || c->version() == GameVersion::BB) { - send_info_board_t(c); + send_info_board_t(c); + } else if (c->language()) { + send_info_board_t(c); } else { - send_info_board_t(c); + send_info_board_t(c); } } -template +template void send_card_search_result_t( shared_ptr c, shared_ptr result, @@ -954,7 +941,7 @@ void send_card_search_result_t( auto s = c->require_server_state(); const auto& port_name = version_to_lobby_port_name.at(static_cast(c->version())); - S_GuildCardSearchResult cmd; + S_GuildCardSearchResult cmd; cmd.player_tag = 0x00010000; cmd.searcher_guild_card_number = c->license->serial_number; cmd.result_guild_card_number = result->license->serial_number; @@ -965,23 +952,19 @@ void send_card_search_result_t( cmd.reconnect_command.port = s->name_to_port_config.at(port_name)->port; 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); - location_string = string_printf("%s,BLOCK01,%s", - encoded_lobby_name.c_str(), encoded_server_name.c_str()); + location_string = string_printf("%s,BLOCK01,%s", result_lobby->name.c_str(), s->name.c_str()); } else if (result_lobby->is_ep3()) { - location_string = string_printf("BLOCK01-C%02" PRIu32 ",BLOCK01,%s", - result_lobby->lobby_id - 15, encoded_server_name.c_str()); + location_string = string_printf("BLOCK01-C%02" PRIu32 ",BLOCK01,%s", result_lobby->lobby_id - 15, s->name.c_str()); } else { - location_string = string_printf("BLOCK01-%02" PRIu32 ",BLOCK01,%s", - result_lobby->lobby_id, encoded_server_name.c_str()); + location_string = string_printf("BLOCK01-%02" PRIu32 ",BLOCK01,%s", result_lobby->lobby_id, s->name.c_str()); } - cmd.location_string = location_string; + cmd.location_string.encode(location_string, c->language()); cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY; cmd.extension.lobby_refs[0].item_id = result_lobby->lobby_id; - cmd.extension.player_name = result->game_data.player(true, false)->disp.name; + auto rp = result->game_data.player(true, false); + cmd.extension.player_name.encode(rp->disp.name.decode(rp->inventory.language), c->language()); send_command_t(c, 0x41, 0x00, cmd); } @@ -993,11 +976,11 @@ void send_card_search_result( if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::GC) || (c->version() == GameVersion::XB)) { - send_card_search_result_t(c, result, result_lobby); + send_card_search_result_t(c, result, result_lobby); } else if (c->version() == GameVersion::PC) { - send_card_search_result_t(c, result, result_lobby); + send_card_search_result_t(c, result, result_lobby); } else if (c->version() == GameVersion::BB) { - send_card_search_result_t(c, result, result_lobby); + send_card_search_result_t(c, result, result_lobby); } else { throw logic_error("unimplemented versioned command"); } @@ -1007,72 +990,78 @@ template void send_guild_card_dc_pc_v3_t( Channel& ch, uint32_t guild_card_number, - const u16string& name, - const u16string& description, + const string& name, + const string& description, + uint8_t language, uint8_t section_id, uint8_t char_class) { CmdT cmd; cmd.header.subcommand = 0x06; cmd.header.size = sizeof(CmdT) / 4; cmd.header.unused = 0x0000; - cmd.player_tag = 0x00010000; - cmd.guild_card_number = guild_card_number; - cmd.name = name; - remove_language_marker_inplace(cmd.name); - cmd.description = description; - cmd.present = 1; - cmd.present2 = 1; - cmd.section_id = section_id; - cmd.char_class = char_class; + cmd.guild_card.player_tag = 0x00010000; + cmd.guild_card.guild_card_number = guild_card_number; + cmd.guild_card.name.encode(name, ch.language); + cmd.guild_card.description.encode(description, ch.language); + cmd.guild_card.present = 1; + cmd.guild_card.language = language; + cmd.guild_card.section_id = section_id; + cmd.guild_card.char_class = char_class; ch.send(0x60, 0x00, &cmd, sizeof(cmd)); } static void send_guild_card_bb( Channel& ch, uint32_t guild_card_number, - const u16string& name, - const u16string& team_name, - const u16string& description, + const string& name, + const string& team_name, + const string& description, + uint8_t language, uint8_t section_id, uint8_t char_class) { G_SendGuildCard_BB_6x06 cmd; cmd.header.subcommand = 0x06; cmd.header.size = sizeof(cmd) / 4; cmd.header.unused = 0x0000; - cmd.guild_card_number = guild_card_number; - cmd.name = remove_language_marker(name); - cmd.team_name = remove_language_marker(team_name); - cmd.description = description; - cmd.present = 1; - cmd.present2 = 1; - cmd.section_id = section_id; - cmd.char_class = char_class; + cmd.guild_card.guild_card_number = guild_card_number; + cmd.guild_card.name.encode(name, ch.language); + cmd.guild_card.team_name.encode(team_name, ch.language); + cmd.guild_card.description.encode(description, ch.language); + cmd.guild_card.present = 1; + cmd.guild_card.language = language; + cmd.guild_card.section_id = section_id; + cmd.guild_card.char_class = char_class; ch.send(0x60, 0x00, &cmd, sizeof(cmd)); } void send_guild_card( Channel& ch, uint32_t guild_card_number, - const u16string& name, - const u16string& team_name, - const u16string& description, + const string& name, + const string& team_name, + const string& description, + uint8_t language, uint8_t section_id, uint8_t char_class) { - if (ch.version == GameVersion::DC) { - send_guild_card_dc_pc_v3_t( - ch, guild_card_number, name, description, section_id, char_class); - } else if (ch.version == GameVersion::PC) { - send_guild_card_dc_pc_v3_t( - ch, guild_card_number, name, description, section_id, char_class); - } else if ((ch.version == GameVersion::GC) || - (ch.version == GameVersion::XB)) { - send_guild_card_dc_pc_v3_t( - ch, guild_card_number, name, description, section_id, char_class); - } else if (ch.version == GameVersion::BB) { - send_guild_card_bb( - ch, guild_card_number, name, team_name, description, section_id, char_class); - } else { - throw logic_error("unimplemented versioned command"); + switch (ch.version) { + case GameVersion::DC: + send_guild_card_dc_pc_v3_t( + ch, guild_card_number, name, description, language, section_id, char_class); + break; + case GameVersion::PC: + send_guild_card_dc_pc_v3_t( + ch, guild_card_number, name, description, language, section_id, char_class); + break; + case GameVersion::GC: + case GameVersion::XB: + send_guild_card_dc_pc_v3_t( + ch, guild_card_number, name, description, language, section_id, char_class); + break; + case GameVersion::BB: + send_guild_card_bb(ch, guild_card_number, name, team_name, description, language, section_id, char_class); + break; + default: + throw logic_error("unimplemented versioned command"); } } @@ -1083,13 +1072,13 @@ void send_guild_card(shared_ptr c, shared_ptr source) { auto source_p = source->game_data.player(true, false); uint32_t guild_card_number = source->license->serial_number; - u16string name = source_p->disp.name; - u16string description = source_p->guild_card_description; + uint8_t language = source_p->inventory.language; + string name = source_p->disp.name.decode(language); + string description = source_p->guild_card_description.decode(language); uint8_t section_id = source_p->disp.visual.section_id; uint8_t char_class = source_p->disp.visual.char_class; - send_guild_card( - c->channel, guild_card_number, name, u"", description, section_id, char_class); + send_guild_card(c->channel, guild_card_number, name, "", description, language, section_id, char_class); } //////////////////////////////////////////////////////////////////////////////// @@ -1103,7 +1092,7 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info e.menu_id = menu->menu_id; e.item_id = 0xFFFFFFFF; e.flags = 0x0004; - e.text = menu->name; + e.text.encode(menu->name, c->language()); } for (const auto& item : menu->items) { @@ -1151,7 +1140,7 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info e.menu_id = menu->menu_id; e.item_id = item.item_id; e.flags = (c->version() == GameVersion::BB) ? 0x0004 : 0x0F04; - e.text = item.name; + e.text.encode(item.name, c->language()); } } @@ -1169,21 +1158,21 @@ void send_menu(shared_ptr c, shared_ptr menu, bool is_info_m } } -template +template void send_game_menu_t( shared_ptr c, bool is_spectator_team_list, bool show_tournaments_only) { auto s = c->require_server_state(); - vector> entries; + vector> entries; { auto& e = entries.emplace_back(); e.menu_id = MenuID::GAME; e.game_id = 0x00000000; e.difficulty_tag = 0x00; e.num_players = 0x00; - e.name = s->name; + e.name.encode(s->name, c->language()); e.episode = 0x00; e.flags = 0x04; } @@ -1251,7 +1240,7 @@ void send_game_menu_t( throw logic_error("invalid game mode"); } } - e.name = l->name; + e.name.encode(l->name, c->language()); } send_command_vt(c, is_spectator_team_list ? 0xE6 : 0x08, entries.size() - 1, entries); @@ -1264,9 +1253,9 @@ void send_game_menu( if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::GC) || (c->version() == GameVersion::XB)) { - send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); + 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); + send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); } } @@ -1279,7 +1268,7 @@ void send_quest_menu_t( auto v = c->quest_version(); vector entries; for (const auto& quest : quests) { - auto vq = quest->version(v, c->language); + auto vq = quest->version(v, c->language()); if (!vq) { continue; } @@ -1287,9 +1276,8 @@ void send_quest_menu_t( auto& e = entries.emplace_back(); e.menu_id = menu_id; e.item_id = quest->quest_number; - e.name = vq->name; - e.short_description = vq->short_description; - add_color_inplace(e.short_description); + e.name.encode(vq->name, c->language()); + e.short_description.encode(add_color(vq->short_description), c->language()); } send_command_vt(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); } @@ -1309,9 +1297,8 @@ void send_quest_menu_t( auto& e = entries.emplace_back(); e.menu_id = menu_id; e.item_id = category.category_id; - e.name = category.name; - e.short_description = category.description; - add_color_inplace(e.short_description); + e.name.encode(category.name, c->language()); + e.short_description.encode(add_color(category.description), c->language()); } send_command_vt(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); } @@ -1446,19 +1433,16 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) p.lobby_data.player_tag = 0x00010000; p.lobby_data.guild_card = wc->license->serial_number; p.lobby_data.client_id = wc->lobby_client_id; - p.lobby_data.name = wc_p->disp.name; - remove_language_marker_inplace(p.lobby_data.name); + p.lobby_data.name.encode(wc_p->disp.name.decode(wc_p->inventory.language), c->language()); p.inventory = wc_p->inventory; p.inventory.encode_mags(c->version()); - p.disp = wc_p->disp.to_dcpcv3(); - remove_language_marker_inplace(p.disp.visual.name); + p.disp = wc_p->disp.to_dcpcv3(c->language(), p.inventory.language); p.disp.enforce_lobby_join_limits(c->version()); auto& e = cmd.entries[z]; e.player_tag = 0x00010000; e.guild_card_number = wc->license->serial_number; - e.name = wc_p->disp.name; - remove_language_marker_inplace(e.name); + e.name.encode(wc_p->disp.name.decode(wc_p->inventory.language), c->language()); e.present = 1; e.level = wc->game_data.ep3_config ? (wc->game_data.ep3_config->online_clv_exp / 100) @@ -1488,18 +1472,15 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) } auto& p = cmd.players[client_id]; p.lobby_data = entry.lobby_data; - remove_language_marker_inplace(p.lobby_data.name); p.inventory = entry.inventory; p.inventory.encode_mags(c->version()); p.disp = entry.disp; - remove_language_marker_inplace(p.disp.visual.name); p.disp.enforce_lobby_join_limits(c->version()); auto& e = cmd.entries[client_id]; e.player_tag = 0x00010000; e.guild_card_number = entry.lobby_data.guild_card; e.name = entry.disp.visual.name; - remove_language_marker_inplace(e.name); e.present = 1; e.level = entry.level.load(); e.name_color = entry.disp.visual.name_color; @@ -1520,17 +1501,14 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) cmd_p.lobby_data.player_tag = 0x00010000; cmd_p.lobby_data.guild_card = other_c->license->serial_number; cmd_p.lobby_data.client_id = other_c->lobby_client_id; - cmd_p.lobby_data.name = other_p->disp.name; - remove_language_marker_inplace(cmd_p.lobby_data.name); + cmd_p.lobby_data.name.encode(other_p->disp.name.decode(other_p->inventory.language), c->language()); cmd_p.inventory = other_p->inventory; - cmd_p.disp = other_p->disp.to_dcpcv3(); - remove_language_marker_inplace(cmd_p.disp.visual.name); + cmd_p.disp = other_p->disp.to_dcpcv3(c->language(), cmd_p.inventory.language); cmd_p.disp.enforce_lobby_join_limits(c->version()); cmd_e.player_tag = 0x00010000; cmd_e.guild_card_number = other_c->license->serial_number; - cmd_e.name = other_p->disp.name; - remove_language_marker_inplace(cmd_e.name); + cmd_e.name = cmd_p.lobby_data.name; cmd_e.present = 1; cmd_e.level = other_c->game_data.ep3_config ? (other_c->game_data.ep3_config->online_clv_exp / 100) @@ -1540,7 +1518,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) player_count++; } } - cmd.spectator_team_name = encode_sjis(l->name); + cmd.spectator_team_name.encode(l->name, c->language()); send_command_t(c, 0xE8, player_count, cmd); } @@ -1554,11 +1532,12 @@ void send_join_game(shared_ptr c, shared_ptr l) { auto populate_lobby_data = [&](auto& cmd) -> size_t { size_t player_count = 0; for (size_t x = 0; x < 4; x++) { - if (l->clients[x]) { + auto lc = l->clients[x]; + if (lc) { cmd.lobby_data[x].player_tag = 0x00010000; - cmd.lobby_data[x].guild_card = l->clients[x]->license->serial_number; - cmd.lobby_data[x].client_id = l->clients[x]->lobby_client_id; - cmd.lobby_data[x].name = l->clients[x]->game_data.player()->disp.name; + cmd.lobby_data[x].guild_card = lc->license->serial_number; + cmd.lobby_data[x].client_id = lc->lobby_client_id; + cmd.lobby_data[x].name.encode(lc->game_data.player()->disp.name.decode(lc->language()), c->language()); player_count++; } else { cmd.lobby_data[x].clear(); @@ -1631,7 +1610,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { auto other_p = l->clients[x]->game_data.player(); cmd.players_ep3[x].inventory = other_p->inventory; cmd.players_ep3[x].inventory.encode_mags(c->version()); - cmd.players_ep3[x].disp = convert_player_disp_data(other_p->disp); + cmd.players_ep3[x].disp = convert_player_disp_data(other_p->disp, c->language(), other_p->inventory.language); cmd.players_ep3[x].disp.enforce_lobby_join_limits(c->version()); } } @@ -1666,8 +1645,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { } template -void send_join_lobby_t(shared_ptr c, shared_ptr l, - shared_ptr joining_client = nullptr) { +void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptr joining_client = nullptr) { uint8_t command; if (l->is_game()) { if (joining_client) { @@ -1739,14 +1717,14 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, e.lobby_data.player_tag = 0x00010000; e.lobby_data.guild_card = lc->license->serial_number; e.lobby_data.client_id = lc->lobby_client_id; - e.lobby_data.name = lp->disp.name; - remove_language_marker_inplace(e.lobby_data.name); if (UseLanguageMarkerInName) { - add_language_marker_inplace(e.lobby_data.name, 'J'); + e.lobby_data.name.encode("\tJ" + lp->disp.name.decode(lp->inventory.language), c->language()); + } else { + e.lobby_data.name.encode(lp->disp.name.decode(lp->inventory.language), c->language()); } e.inventory = lp->inventory; e.inventory.encode_mags(c->version()); - e.disp = convert_player_disp_data(lp->disp); + e.disp = convert_player_disp_data(lp->disp, c->language(), lp->inventory.language); e.disp.enforce_lobby_join_limits(c->version()); } @@ -1789,10 +1767,10 @@ void send_join_lobby_dc_nte(shared_ptr c, shared_ptr l, e.lobby_data.player_tag = 0x00010000; e.lobby_data.guild_card = lc->license->serial_number; e.lobby_data.client_id = lc->lobby_client_id; - e.lobby_data.name = lp->disp.name; + e.lobby_data.name.encode(lp->disp.name.decode(lp->inventory.language), c->language()); e.inventory = lp->inventory; e.inventory.encode_mags(c->version()); - e.disp = convert_player_disp_data(lp->disp); + e.disp = convert_player_disp_data(lp->disp, c->language(), lp->inventory.language); e.disp.enforce_lobby_join_limits(c->version()); } @@ -2244,7 +2222,7 @@ void send_ep3_rank_update(shared_ptr c) { auto s = c->require_server_state(); uint32_t current_meseta = s->ep3_infinite_meseta ? 1000000 : c->license->ep3_current_meseta; uint32_t total_meseta_earned = s->ep3_infinite_meseta ? 1000000 : c->license->ep3_total_meseta_earned; - S_RankUpdate_GC_Ep3_B7 cmd = {0, "\0\0\0\0\0\0\0\0\0\0\0", current_meseta, total_meseta_earned, 0xFFFFFFFF}; + S_RankUpdate_GC_Ep3_B7 cmd = {0, {}, current_meseta, total_meseta_earned, 0xFFFFFFFF}; send_command_t(c, 0xB7, 0x00, cmd); } @@ -2295,17 +2273,17 @@ void send_ep3_confirm_tournament_entry( S_ConfirmTournamentEntry_GC_Ep3_CC cmd; if (tourn) { auto s = c->require_server_state(); - cmd.tournament_name = tourn->get_name(); - cmd.server_name = encode_sjis(s->name); + cmd.tournament_name.encode(tourn->get_name(), c->language()); + cmd.server_name.encode(s->name, c->language()); // TODO: Fill this in appropriately when we support scheduled start times - cmd.start_time = "Unknown"; + cmd.start_time.encode("Unknown", c->language()); auto& teams = tourn->all_teams(); cmd.num_teams = min(teams.size(), 0x20); cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1; for (size_t z = 0; z < min(teams.size(), 0x20); z++) { cmd.team_entries[z].win_count = teams[z]->num_rounds_cleared; cmd.team_entries[z].is_active = teams[z]->is_active; - cmd.team_entries[z].name = teams[z]->name; + cmd.team_entries[z].name.encode(teams[z]->name, c->language()); } } send_command_t(c, 0xCC, tourn ? 0x01 : 0x00, cmd); @@ -2339,7 +2317,7 @@ void send_ep3_tournament_list( ? 0x00 : 0x05; // TODO: Fill in cmd.start_time here when we implement scheduled starts. - entry.name = tourn->get_name(); + entry.name.encode(tourn->get_name(), c->language()); const auto& teams = tourn->all_teams(); for (auto team : teams) { if (!team->name.empty()) { @@ -2379,7 +2357,7 @@ void send_ep3_tournament_entry_list( } else { entry.state = 2; } - entry.name = team->name; + entry.name.encode(team->name, c->language()); z++; } send_command_t(c, is_for_spectator_team_create ? 0xE7 : 0xE2, z, cmd); @@ -2389,14 +2367,15 @@ void send_ep3_tournament_details( shared_ptr c, shared_ptr tourn) { S_TournamentGameDetails_GC_Ep3_E3 cmd; - cmd.name = tourn->get_name(); - cmd.map_name = tourn->get_map()->version(c->language)->map->name; + auto vm = tourn->get_map()->version(c->language()); + cmd.name.encode(tourn->get_name(), c->language()); + cmd.map_name.encode(vm->map->name.decode(vm->language), c->language()); cmd.rules = tourn->get_rules(); const auto& teams = tourn->all_teams(); for (size_t z = 0; z < min(teams.size(), 0x20); z++) { cmd.bracket_entries[z].win_count = teams[z]->num_rounds_cleared; cmd.bracket_entries[z].is_active = teams[z]->is_active ? 1 : 0; - cmd.bracket_entries[z].team_name = teams[z]->name; + cmd.bracket_entries[z].team_name.encode(teams[z]->name, c->language()); } cmd.num_bracket_entries = teams.size(); cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1; @@ -2429,9 +2408,10 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { if (tourn) { S_TournamentGameDetails_GC_Ep3_E3 cmd; - cmd.name = encode_sjis(l->name); + cmd.name.encode(l->name, c->language()); - cmd.map_name = tourn->get_map()->version(c->language)->map->name; + auto vm = tourn->get_map()->version(c->language()); + cmd.map_name.encode(vm->map->name.decode(vm->language), c->language()); cmd.rules = tourn->get_rules(); const auto& teams = tourn->all_teams(); @@ -2439,7 +2419,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { auto& entry = cmd.bracket_entries[z]; entry.win_count = teams[z]->num_rounds_cleared; entry.is_active = teams[z]->is_active ? 1 : 0; - entry.team_name = teams[z]->name; + entry.team_name.encode(teams[z]->name, c->language()); } cmd.num_bracket_entries = teams.size(); cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1; @@ -2447,22 +2427,22 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { if (primary_lobby) { auto serial_number_to_client = primary_lobby->clients_by_serial_number(); auto describe_team = [&](S_TournamentGameDetails_GC_Ep3_E3::TeamEntry& team_entry, shared_ptr team) -> void { - team_entry.team_name = team->name; + team_entry.team_name.encode(team->name, c->language()); for (size_t z = 0; z < team->players.size(); z++) { auto& entry = team_entry.players[z]; const auto& player = team->players[z]; if (player.is_human()) { try { - auto c = serial_number_to_client.at(player.serial_number); - entry.name = c->game_data.player()->disp.name; - entry.description = ep3_description_for_client(c); + auto other_c = serial_number_to_client.at(player.serial_number); + entry.name.encode(other_c->game_data.player()->disp.name.decode(other_c->language()), c->language()); + entry.description.encode(ep3_description_for_client(other_c), c->language()); } catch (const out_of_range&) { - entry.name = player.player_name; - entry.description = "(Not connected)"; + entry.name.encode(player.player_name, c->language()); + entry.description.encode("(Not connected)", c->language()); } } else { - entry.name = player.com_deck->player_name; - entry.description = "Deck: " + player.com_deck->deck_name; + entry.name.encode(player.com_deck->player_name, c->language()); + entry.description.encode("Deck: " + player.com_deck->deck_name, c->language()); } } }; @@ -2475,8 +2455,8 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { for (auto spec_c : l->clients) { if (spec_c) { auto& entry = cmd.spectator_entries[cmd.num_spectators++]; - entry.name = encode_sjis(spec_c->game_data.player()->disp.name); - entry.description = ep3_description_for_client(spec_c); + entry.name.encode(spec_c->game_data.player()->disp.name.decode(spec_c->language()), c->language()); + entry.description.encode(ep3_description_for_client(spec_c), c->language()); } } flag = 0x05; @@ -2487,13 +2467,13 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { } else { S_GameInformation_GC_Ep3_E1 cmd; - cmd.game_name = encode_sjis(l->name); + cmd.game_name.encode(l->name, c->language()); if (primary_lobby) { size_t num_players = 0; for (const auto& opp_c : primary_lobby->clients) { if (opp_c) { - cmd.player_entries[num_players].name = opp_c->game_data.player()->disp.name; - cmd.player_entries[num_players].description = ep3_description_for_client(opp_c); + cmd.player_entries[num_players].name.encode(opp_c->game_data.player()->disp.name.decode(opp_c->language()), c->language()); + cmd.player_entries[num_players].description.encode(ep3_description_for_client(opp_c), c->language()); num_players++; } } @@ -2505,8 +2485,8 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { for (auto spec_c : l->clients) { if (spec_c) { auto& entry = cmd.spectator_entries[num_spectators++]; - entry.name = encode_sjis(spec_c->game_data.player()->disp.name); - entry.description = ep3_description_for_client(spec_c); + entry.name.encode(spec_c->game_data.player()->disp.name.decode(spec_c->language()), c->language()); + entry.description.encode(ep3_description_for_client(spec_c), c->language()); } } @@ -2552,8 +2532,8 @@ void send_ep3_set_tournament_player_decks(shared_ptr c) { for (size_t z = 0; z < 4; z++) { auto& entry = cmd.entries[z]; - entry.player_name.clear(0); - entry.deck_name.clear(0); + entry.player_name.clear(); + entry.deck_name.clear(); entry.unknown_a1.clear(0); entry.card_ids.clear(0); entry.client_id = z; @@ -2565,14 +2545,14 @@ void send_ep3_set_tournament_player_decks(shared_ptr c) { const auto& player = team->players[z]; if (player.is_human()) { entry.type = 1; // Human - entry.player_name = player.player_name; + entry.player_name.encode(player.player_name, c->language()); if (player.serial_number == c->license->serial_number) { cmd.player_slot = base_index + z; } } else { entry.type = 2; // COM - entry.player_name = player.com_deck->player_name; - entry.deck_name = player.com_deck->deck_name; + entry.player_name.encode(player.com_deck->player_name, c->language()); + entry.deck_name.encode(player.com_deck->deck_name, c->language()); entry.card_ids = player.com_deck->card_ids; } entry.unknown_a2 = 6; @@ -2606,41 +2586,48 @@ void send_ep3_tournament_match_result(shared_ptr l, uint32_t meseta_rewar auto serial_number_to_client = l->clients_by_serial_number(); - auto write_player_names = [&](G_TournamentMatchResult_GC_Ep3_6xB4x51::NamesEntry& entry, shared_ptr team) -> void { - for (size_t z = 0; z < team->players.size(); z++) { - const auto& player = team->players[z]; - if (player.is_human()) { - try { - entry.player_names[z] = serial_number_to_client.at(player.serial_number)->game_data.player()->disp.name; - } catch (const out_of_range&) { - entry.player_names[z] = player.player_name; - } - } else { - entry.player_names[z] = player.com_deck->player_name; - } + for (const auto& lc : l->clients) { + if (!lc) { + continue; } - }; + auto write_player_names = [&](G_TournamentMatchResult_GC_Ep3_6xB4x51::NamesEntry& entry, shared_ptr team) -> void { + for (size_t z = 0; z < team->players.size(); z++) { + const auto& player = team->players[z]; + if (player.is_human()) { + try { + auto pc = serial_number_to_client.at(player.serial_number); + entry.player_names[z].encode(pc->game_data.player()->disp.name.decode(pc->language()), lc->language()); + } catch (const out_of_range&) { + entry.player_names[z].encode(player.player_name, lc->language()); + } + } else { + entry.player_names[z].encode(player.com_deck->player_name, lc->language()); + } + } + }; - G_TournamentMatchResult_GC_Ep3_6xB4x51 cmd; - cmd.match_description = (match == tourn->get_final_match()) - ? string_printf("(%s) Final match", tourn->get_name().c_str()) - : string_printf("(%s) Round %zu", tourn->get_name().c_str(), match->round_num); - cmd.names_entries[0].team_name = match->preceding_a->winner_team->name; - write_player_names(cmd.names_entries[0], match->preceding_a->winner_team); - cmd.names_entries[1].team_name = match->preceding_b->winner_team->name; - write_player_names(cmd.names_entries[1], match->preceding_b->winner_team); - // The value 6 here causes the client to show the "Congratulations" text - // instead of "On to the next round" - cmd.round_num = (match == tourn->get_final_match()) ? 6 : match->round_num; - cmd.num_players_per_team = match->preceding_a->winner_team->max_players; - cmd.winner_team_id = (match->preceding_b->winner_team == match->winner_team); - cmd.meseta_amount = meseta_reward; - cmd.meseta_reward_text = "You got %s meseta!"; - if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { - uint8_t mask_key = (random_object() % 0xFF) + 1; - set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key); + G_TournamentMatchResult_GC_Ep3_6xB4x51 cmd; + cmd.match_description.encode((match == tourn->get_final_match()) + ? string_printf("(%s) Final match", tourn->get_name().c_str()) + : string_printf("(%s) Round %zu", tourn->get_name().c_str(), match->round_num), + lc->language()); + cmd.names_entries[0].team_name.encode(match->preceding_a->winner_team->name, lc->language()); + write_player_names(cmd.names_entries[0], match->preceding_a->winner_team); + cmd.names_entries[1].team_name.encode(match->preceding_b->winner_team->name, lc->language()); + write_player_names(cmd.names_entries[1], match->preceding_b->winner_team); + // The value 6 here causes the client to show the "Congratulations" text + // instead of "On to the next round" + cmd.round_num = (match == tourn->get_final_match()) ? 6 : match->round_num; + cmd.num_players_per_team = match->preceding_a->winner_team->max_players; + cmd.winner_team_id = (match->preceding_b->winner_team == match->winner_team); + cmd.meseta_amount = meseta_reward; + cmd.meseta_reward_text.encode("You got %s meseta!", 1); + if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { + uint8_t mask_key = (random_object() % 0xFF) + 1; + set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key); + } + send_command_t(lc, 0xC9, 0x00, cmd); } - send_command_t(l, 0xC9, 0x00, cmd); if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { send_text_message_printf(l, "$C5TOURN/%" PRIX32 "/%zu WIN %c", @@ -2688,7 +2675,7 @@ void send_ep3_update_game_metadata(shared_ptr l) { l->tournament_match->round_num, tourn->get_name().c_str()); } } else { - text = "Viewing battle in game " + encode_sjis(l->name); + text = "Viewing battle in game " + l->name; } add_color_inplace(text); for (auto watcher_l : l->watcher_lobbies) { @@ -2699,7 +2686,7 @@ void send_ep3_update_game_metadata(shared_ptr l) { } cmd.total_spectators = total_spectators; cmd.text_size = text.size(); - cmd.text = text; + cmd.text.encode(text, 1); if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { uint8_t mask_key = (random_object() % 0xFF) + 1; set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key); @@ -2759,7 +2746,7 @@ void send_quest_file_chunk( } S_WriteFile_13_A7 cmd; - cmd.filename = filename; + cmd.filename.encode(filename); memcpy(cmd.data.data(), data, size); if (size < 0x400) { memset(&cmd.data[size], 0, 0x400 - size); diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 3abf70de..733abae0 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -174,47 +174,48 @@ void send_complete_player_bb(std::shared_ptr c); void send_enter_directory_patch(std::shared_ptr c, const std::string& dir); void send_patch_file(std::shared_ptr c, std::shared_ptr f); -void send_message_box(std::shared_ptr c, const std::u16string& text); +void send_message_box(std::shared_ptr c, const std::string& text); void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const std::string& text); -void send_lobby_name(std::shared_ptr c, const std::u16string& text); -void send_quest_info(std::shared_ptr c, const std::u16string& text, +void send_lobby_name(std::shared_ptr c, const std::string& text); +void send_quest_info(std::shared_ptr c, const std::string& text, bool is_download_quest); -void send_lobby_message_box(std::shared_ptr c, const std::u16string& text); -void send_ship_info(std::shared_ptr c, const std::u16string& text); -void send_ship_info(Channel& ch, const std::u16string& text); -void send_text_message(Channel& ch, const std::u16string& text); -void send_text_message(std::shared_ptr c, const std::u16string& text); -void send_text_message(std::shared_ptr l, const std::u16string& text); -void send_text_message(std::shared_ptr s, const std::u16string& text); +void send_lobby_message_box(std::shared_ptr c, const std::string& text); +void send_ship_info(std::shared_ptr c, const std::string& text); +void send_ship_info(Channel& ch, const std::string& text); +void send_text_message(Channel& ch, const std::string& text); +void send_text_message(std::shared_ptr c, const std::string& text); +void send_text_message(std::shared_ptr l, const std::string& text); +void send_text_message(std::shared_ptr s, const std::string& text); -std::u16string prepare_chat_message( +std::string prepare_chat_data( GameVersion version, - const std::u16string& from_name, - const std::u16string& text, + uint8_t language, + const std::string& from_name, + const std::string& text, char private_flags); -void send_chat_message( +void send_chat_message_from_client( Channel& ch, - const std::u16string& text, + const std::string& text, char private_flags); -void send_chat_message( +void send_prepared_chat_message( std::shared_ptr c, uint32_t from_guild_card_number, - const std::u16string& prepared_data); -void send_chat_message( + const std::string& prepared_data); +void send_prepared_chat_message( std::shared_ptr l, uint32_t from_guild_card_number, - const std::u16string& prepared_data); + const std::string& prepared_data); void send_chat_message( std::shared_ptr c, uint32_t from_guild_card_number, - const std::u16string& from_name, - const std::u16string& text, + const std::string& from_name, + const std::string& text, char private_flags); void send_simple_mail( std::shared_ptr c, uint32_t from_serial_number, - const std::u16string& from_name, - const std::u16string& text); + const std::string& from_name, + const std::string& text); template __attribute__((format(printf, 2, 3))) void send_text_message_printf( @@ -223,8 +224,7 @@ __attribute__((format(printf, 2, 3))) void send_text_message_printf( va_start(va, format); std::string buf = string_vprintf(format, va); va_end(va); - std::u16string decoded = decode_sjis(buf); - return send_text_message(t, decoded.c_str()); + return send_text_message(t, buf.c_str()); } __attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf( @@ -240,9 +240,10 @@ void send_card_search_result( void send_guild_card( Channel& ch, uint32_t guild_card_number, - const std::u16string& name, - const std::u16string& team_name, - const std::u16string& description, + const std::string& name, + const std::string& team_name, + const std::string& description, + uint8_t language, uint8_t section_id, uint8_t char_class); void send_guild_card(std::shared_ptr c, std::shared_ptr source); diff --git a/src/Server.cc b/src/Server.cc index 9277ec08..f6fe0429 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -295,7 +295,6 @@ vector> Server::get_clients_by_identifier(const string& ident serial_number_hex = stoul(ident, nullptr, 16); } catch (const invalid_argument&) { } - u16string u16name = decode_sjis(ident); // TODO: It's kind of not great that we do a linear search here, but this is // only used in the shell, so it should be pretty rare. @@ -316,7 +315,7 @@ vector> Server::get_clients_by_identifier(const string& ident } auto p = c->game_data.player(false, false); - if (p && p->disp.name == u16name) { + if (p && p->disp.name.eq(ident, p->inventory.language)) { results.emplace_back(std::move(c)); continue; } diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 34685ef7..893b6afa 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -467,8 +467,7 @@ Proxy session commands:\n\ this->state->ep3_menu_song = stoul(command_args, nullptr, 0); } else if (command_name == "announce") { - u16string message16 = decode_sjis(command_args); - send_text_message(this->state, message16.c_str()); + send_text_message(this->state, command_args); } else if (command_name == "create-tournament") { string name = get_quoted_string(command_args); @@ -664,8 +663,9 @@ Proxy session commands:\n\ const auto& player = session->lobby_players[z]; if (player.guild_card_number) { auto secid_name = name_for_section_id(player.section_id); - fprintf(stderr, " %zu: %" PRIu32 " => %s (%s, %s)\n", + fprintf(stderr, " %zu: %" PRIu32 " => %s (%c, %s, %s)\n", z, player.guild_card_number, player.name.c_str(), + char_for_language_code(player.language), name_for_char_class(player.char_class), secid_name.c_str()); } else { fprintf(stderr, " %zu: (no player)\n", z); @@ -676,14 +676,8 @@ Proxy session commands:\n\ auto session = this->get_proxy_session(session_name); bool is_dchat = (command_name == "dchat"); - if (!is_dchat && (session->version == GameVersion::PC || session->version == GameVersion::BB)) { - u16string data(4, u'\0'); - data.push_back(u'\x09'); - data.push_back(u'E'); - data += decode_sjis(command_args); - data.push_back(u'\0'); - data.resize((data.size() + 1) & (~1)); - session->server_channel.send(0x06, 0x00, data.data(), data.size() * sizeof(char16_t)); + if (!is_dchat && (session->version() == GameVersion::PC || session->version() == GameVersion::BB)) { + send_chat_message_from_client(session->server_channel, command_args, 0); } else { string data(8, '\0'); data.push_back('\x09'); @@ -700,7 +694,7 @@ Proxy session commands:\n\ } else if ((command_name == "wc") || (command_name == "wchat")) { auto session = this->get_proxy_session(session_name); - if ((session->version != GameVersion::GC) || + if ((session->version() != GameVersion::GC) || !(session->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { throw runtime_error("wchat can only be used on Episode 3"); } @@ -758,8 +752,8 @@ Proxy session commands:\n\ session->options.override_lobby_event = -1; } else { session->options.override_lobby_event = event_for_name(command_args); - if ((session->version != GameVersion::DC) && - (session->version != GameVersion::PC) && (!((session->version == GameVersion::GC) && (session->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)))) { + if ((session->version() != GameVersion::DC) && + (session->version() != GameVersion::PC) && (!((session->version() == GameVersion::GC) && (session->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)))) { session->client_channel.send(0xDA, session->options.override_lobby_event); } } @@ -811,7 +805,7 @@ Proxy session commands:\n\ } else if ((command_name == "create-item") || (command_name == "set-next-item")) { auto session = this->get_proxy_session(session_name); - if (session->version == GameVersion::BB) { + if (session->version() == GameVersion::BB) { throw runtime_error("proxy session is BB"); } if (!session->is_in_game) { @@ -828,14 +822,14 @@ Proxy session commands:\n\ session->next_drop_item = item; string name = session->next_drop_item.name(true); - send_text_message(session->client_channel, u"$C7Next drop:\n" + decode_sjis(name)); + send_text_message(session->client_channel, "$C7Next drop:\n" + name); } else { send_drop_stacked_item(session->client_channel, item, session->area, session->x, session->z); send_drop_stacked_item(session->server_channel, item, session->area, session->x, session->z); string name = item.name(true); - send_text_message(session->client_channel, u"$C7Item created:\n" + decode_sjis(name)); + send_text_message(session->client_channel, "$C7Item created:\n" + name); } } else if (command_name == "close-idle-sessions") { diff --git a/src/ServerState.cc b/src/ServerState.cc index c5712328..5e9ee7dc 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -54,7 +54,7 @@ void ServerState::init() { vector> ep3_only_lobbies; for (size_t x = 0; x < 20; x++) { - auto lobby_name = decode_sjis(string_printf("LOBBY%zu", x + 1)); + auto lobby_name = string_printf("LOBBY%zu", x + 1); bool v2_and_later_only = (x > 9); bool is_ep3_only = (x > 14); @@ -151,7 +151,7 @@ void ServerState::add_client_to_available_lobby(shared_ptr c) { added_to_lobby = this->create_lobby(); added_to_lobby->flags |= Lobby::Flag::PUBLIC | Lobby::Flag::IS_OVERFLOW; added_to_lobby->block = 100; - added_to_lobby->name = u"Overflow"; + added_to_lobby->name = "Overflow"; added_to_lobby->max_clients = 12; added_to_lobby->event = this->pre_lobby_event; added_to_lobby->add_client(c); @@ -279,12 +279,10 @@ void ServerState::remove_lobby(uint32_t lobby_id) { this->id_to_lobby.erase(lobby_it); } -shared_ptr ServerState::find_client(const std::u16string* identifier, - uint64_t serial_number, shared_ptr l) { - +shared_ptr ServerState::find_client(const std::string* identifier, uint64_t serial_number, shared_ptr l) { if ((serial_number == 0) && identifier) { try { - serial_number = stoull(encode_sjis(*identifier), nullptr, 0); + serial_number = stoull(*identifier, nullptr, 0); } catch (const exception&) { } } @@ -521,7 +519,7 @@ static vector parse_port_configuration(const JSON& json) { void ServerState::parse_config(const JSON& json, bool is_reload) { config_log.info("Parsing configuration"); - this->name = decode_sjis(json.at("ServerName").as_string()); + this->name = json.at("ServerName").as_string(); if (!is_reload) { try { @@ -743,22 +741,22 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { config_log.info("Creating menus"); - shared_ptr information_menu_v2(new Menu(MenuID::INFORMATION, u"Information")); - shared_ptr information_menu_v3(new Menu(MenuID::INFORMATION, u"Information")); - shared_ptr> information_contents(new vector()); + shared_ptr information_menu_v2(new Menu(MenuID::INFORMATION, "Information")); + shared_ptr information_menu_v3(new Menu(MenuID::INFORMATION, "Information")); + shared_ptr> information_contents(new vector()); - information_menu_v2->items.emplace_back(InformationMenuItemID::GO_BACK, u"Go back", - u"Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU); - information_menu_v3->items.emplace_back(InformationMenuItemID::GO_BACK, u"Go back", - u"Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU); + information_menu_v2->items.emplace_back(InformationMenuItemID::GO_BACK, "Go back", + "Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU); + information_menu_v3->items.emplace_back(InformationMenuItemID::GO_BACK, "Go back", + "Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU); { uint32_t item_id = 0; for (const auto& item : json.at("InformationMenuContents").as_list()) { - u16string name = decode_sjis(item->get_string(0)); - u16string short_desc = decode_sjis(item->get_string(1)); + string name = item->get_string(0); + string short_desc = item->get_string(1); information_menu_v2->items.emplace_back(item_id, name, short_desc, 0); information_menu_v3->items.emplace_back(item_id, name, short_desc, MenuItem::Flag::REQUIRES_MESSAGE_BOXES); - information_contents->emplace_back(decode_sjis(item->get_string(2))); + information_contents->emplace_back(item->get_string(2)); item_id++; } } @@ -767,7 +765,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { this->information_contents = information_contents; auto generate_redirect_destinations_menu = [&](vector>& ret_pds, const char* key) -> shared_ptr { - shared_ptr ret(new Menu(MenuID::REDIRECT_DESTINATIONS, u"Other servers")); + shared_ptr ret(new Menu(MenuID::REDIRECT_DESTINATIONS, "Other servers")); ret_pds.clear(); try { @@ -776,13 +774,13 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { sorted_jsons.emplace(it.first, *it.second); } - ret->items.emplace_back(RedirectDestinationsMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0); + ret->items.emplace_back(RedirectDestinationsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0); uint32_t item_id = 0; for (const auto& item : sorted_jsons) { const string& netloc_str = item.second.as_string(); const string& description = "$C7Remote server:\n$C6" + netloc_str; - ret->items.emplace_back(item_id, decode_sjis(item.first), decode_sjis(description), 0); + ret->items.emplace_back(item_id, item.first, description, 0); ret_pds.emplace_back(parse_netloc(netloc_str)); item_id++; } @@ -797,7 +795,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { this->redirect_destinations_menu_xb = generate_redirect_destinations_menu(this->redirect_destinations_xb, "RedirectDestinations-XB"); auto generate_proxy_destinations_menu = [&](vector>& ret_pds, const char* key) -> shared_ptr { - shared_ptr ret(new Menu(MenuID::PROXY_DESTINATIONS, u"Proxy server")); + shared_ptr ret(new Menu(MenuID::PROXY_DESTINATIONS, "Proxy server")); ret_pds.clear(); try { @@ -806,16 +804,14 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { sorted_jsons.emplace(it.first, *it.second); } - ret->items.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, u"Go back", - u"Return to the\nmain menu", 0); - ret->items.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, u"Options", - u"Set proxy session\noptions", 0); + ret->items.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0); + ret->items.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, "Options", "Set proxy session\noptions", 0); uint32_t item_id = 0; for (const auto& item : sorted_jsons) { const string& netloc_str = item.second.as_string(); const string& description = "$C7Remote server:\n$C6" + netloc_str; - ret->items.emplace_back(item_id, decode_sjis(item.first), decode_sjis(description), 0); + ret->items.emplace_back(item_id, item.first, description, 0); ret_pds.emplace_back(parse_netloc(netloc_str)); item_id++; } @@ -856,9 +852,9 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { this->proxy_destination_bb.second = 0; } - this->welcome_message = decode_sjis(json.get_string("WelcomeMessage", "")); - this->pc_patch_server_message = decode_sjis(json.get_string("PCPatchServerMessage", "")); - this->bb_patch_server_message = decode_sjis(json.get_string("BBPatchServerMessage", "")); + this->welcome_message = json.get_string("WelcomeMessage", ""); + this->pc_patch_server_message = json.get_string("PCPatchServerMessage", ""); + this->bb_patch_server_message = json.get_string("BBPatchServerMessage", ""); } void ServerState::load_bb_private_keys() { diff --git a/src/ServerState.hh b/src/ServerState.hh index 0c644262..26b87526 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -49,7 +49,7 @@ struct ServerState : public std::enable_shared_from_this { std::string config_filename; bool is_replay; - std::u16string name; + std::string name; std::unordered_map> name_to_port_config; std::unordered_map> number_to_port_config; std::string username; @@ -125,7 +125,7 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr information_menu_v2; std::shared_ptr information_menu_v3; - std::shared_ptr> information_contents; + std::shared_ptr> information_contents; std::shared_ptr redirect_destinations_menu_dc; std::shared_ptr redirect_destinations_menu_pc; std::shared_ptr redirect_destinations_menu_gc; @@ -144,9 +144,9 @@ struct ServerState : public std::enable_shared_from_this { std::vector> proxy_destinations_xb; std::pair proxy_destination_patch; std::pair proxy_destination_bb; - std::u16string welcome_message; - std::u16string pc_patch_server_message; - std::u16string bb_patch_server_message; + std::string welcome_message; + std::string pc_patch_server_message; + std::string bb_patch_server_message; std::unordered_map> channel_to_client; std::map> id_to_lobby; @@ -192,7 +192,7 @@ struct ServerState : public std::enable_shared_from_this { void remove_lobby(uint32_t lobby_id); std::shared_ptr find_client( - const std::u16string* identifier = nullptr, + const std::string* identifier = nullptr, uint64_t serial_number = 0, std::shared_ptr l = nullptr); diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index d268e23c..c77a0d0f 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -214,10 +214,6 @@ const string& name_for_section_id(uint8_t section_id) { } } -u16string u16name_for_section_id(uint8_t section_id) { - return decode_sjis(name_for_section_id(section_id)); -} - uint8_t section_id_for_name(const string& name) { string lower_name = tolower(name); try { @@ -235,10 +231,6 @@ uint8_t section_id_for_name(const string& name) { return 0xFF; } -uint8_t section_id_for_name(const u16string& name) { - return section_id_for_name(encode_sjis(name)); -} - const string& name_for_event(uint8_t event) { if (event < lobby_event_to_name.size()) { return lobby_event_to_name[event]; @@ -248,10 +240,6 @@ const string& name_for_event(uint8_t event) { } } -u16string u16name_for_event(uint8_t event) { - return decode_sjis(name_for_event(event)); -} - uint8_t event_for_name(const string& name) { try { return name_to_lobby_event.at(name); @@ -268,10 +256,6 @@ uint8_t event_for_name(const string& name) { return 0xFF; } -uint8_t event_for_name(const u16string& name) { - return event_for_name(encode_sjis(name)); -} - const string& name_for_lobby_type(uint8_t type) { try { return lobby_type_to_name.at(type); @@ -281,10 +265,6 @@ const string& name_for_lobby_type(uint8_t type) { } } -u16string u16name_for_lobby_type(uint8_t type) { - return decode_sjis(name_for_lobby_type(type)); -} - uint8_t lobby_type_for_name(const string& name) { try { return name_to_lobby_type.at(name); @@ -301,10 +281,6 @@ uint8_t lobby_type_for_name(const string& name) { return 0x80; } -uint8_t lobby_type_for_name(const u16string& name) { - return lobby_type_for_name(encode_sjis(name)); -} - const string& name_for_npc(uint8_t npc) { try { return npc_id_to_name.at(npc); @@ -314,10 +290,6 @@ const string& name_for_npc(uint8_t npc) { } } -u16string u16name_for_npc(uint8_t npc) { - return decode_sjis(name_for_npc(npc)); -} - uint8_t npc_for_name(const string& name) { try { return name_to_npc_id.at(name); @@ -334,10 +306,6 @@ uint8_t npc_for_name(const string& name) { return 0xFF; } -uint8_t npc_for_name(const u16string& name) { - return npc_for_name(encode_sjis(name)); -} - const char* name_for_char_class(uint8_t cls) { static const array names = { "HUmar", @@ -555,10 +523,6 @@ const string& name_for_technique(uint8_t tech) { } } -u16string u16name_for_technique(uint8_t tech) { - return decode_sjis(name_for_technique(tech)); -} - uint8_t technique_for_name(const string& name) { try { return name_to_tech_id.at(name); @@ -575,10 +539,6 @@ uint8_t technique_for_name(const string& name) { return 0xFF; } -uint8_t technique_for_name(const u16string& name) { - return technique_for_name(encode_sjis(name)); -} - const vector name_for_mag_color({ /* 00 */ "red", /* 01 */ "blue", diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh index 23d777a1..0c976c67 100644 --- a/src/StaticGameData.hh +++ b/src/StaticGameData.hh @@ -37,29 +37,19 @@ extern const std::vector tech_id_to_name; extern const std::unordered_map name_to_tech_id; const std::string& name_for_technique(uint8_t tech); -std::u16string u16name_for_technique(uint8_t tech); uint8_t technique_for_name(const std::string& name); -uint8_t technique_for_name(const std::u16string& name); const std::string& name_for_section_id(uint8_t section_id); -std::u16string u16name_for_section_id(uint8_t section_id); uint8_t section_id_for_name(const std::string& name); -uint8_t section_id_for_name(const std::u16string& name); const std::string& name_for_event(uint8_t event); -std::u16string u16name_for_event(uint8_t event); uint8_t event_for_name(const std::string& name); -uint8_t event_for_name(const std::u16string& name); const std::string& name_for_lobby_type(uint8_t type); -std::u16string u16name_for_lobby_type(uint8_t type); uint8_t lobby_type_for_name(const std::string& name); -uint8_t lobby_type_for_name(const std::u16string& name); const std::string& name_for_npc(uint8_t npc); -std::u16string u16name_for_npc(uint8_t npc); uint8_t npc_for_name(const std::string& name); -uint8_t npc_for_name(const std::u16string& name); const char* name_for_char_class(uint8_t cls); const char* abbreviation_for_char_class(uint8_t cls); diff --git a/src/Text.cc b/src/Text.cc index 3dcfdfbe..a66aa210 100644 --- a/src/Text.cc +++ b/src/Text.cc @@ -11,171 +11,283 @@ using namespace std; -int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count) { - size_t x; - for (x = 0; x < count && s1[x] != 0 && s2[x] != 0; x++) { - if (s1[x] < s2[x]) { - return -1; - } else if (s1[x] > s2[x]) { - return 1; +// A third case is when inbuf is NULL or *inbuf is NULL, and outbuf is NULL or *outbuf is NULL. In this case, the iconv function sets cd’s conversion state to the initial state. + +const iconv_t TextTranscoder::INVALID_IC = (iconv_t)(-1); +const size_t TextTranscoder::FAILURE_RESULT = static_cast(-1); + +TextTranscoder::TextTranscoder(const char* to, const char* from) + : ic(iconv_open(to, from)) { + if (ic == this->INVALID_IC) { + string error_str = string_for_error(errno); + throw runtime_error(string_printf("failed to initialize %s -> %s text converter: %s", from, to, error_str.c_str())); + } +} + +TextTranscoder::TextTranscoder(TextTranscoder&& other) : ic(other.ic) { + other.ic = this->INVALID_IC; +} + +TextTranscoder& TextTranscoder::operator=(TextTranscoder&& other) { + this->ic = other.ic; + other.ic = this->INVALID_IC; + return *this; +} + +TextTranscoder::~TextTranscoder() { + iconv_close(this->ic); +} + +TextTranscoder::Result TextTranscoder::operator()( + void* dest, size_t dest_size, const void* src, size_t src_bytes, bool truncate_oversize_result) { + // Clear any conversion state left over from the previous call + iconv(this->ic, nullptr, nullptr, nullptr, nullptr); + + void* orig_dest = dest; + const void* orig_src = src; + size_t ret = iconv( + this->ic, + reinterpret_cast(const_cast(&src)), + &src_bytes, + reinterpret_cast(&dest), + &dest_size); + + size_t bytes_read = reinterpret_cast(src) - reinterpret_cast(orig_src); + if (ret == this->FAILURE_RESULT) { + switch (errno) { + case EILSEQ: + throw runtime_error(string_printf("untranslatable character at position 0x%zX", bytes_read)); + case EINVAL: + throw runtime_error(string_printf("incomplete multibyte sequence at position 0x%zX", bytes_read)); + case E2BIG: + if (!truncate_oversize_result) { + throw runtime_error("string does not fit in buffer"); + } else { + break; + } + default: + throw runtime_error("transcoding failed: " + string_for_error(errno)); } } - if (s1[x] < s2[x]) { - return -1; - } else if (s1[x] > s2[x]) { - return 1; - } - return 0; + + size_t bytes_written = reinterpret_cast(dest) - reinterpret_cast(orig_dest); + return Result{ + .bytes_read = bytes_read, + .bytes_written = bytes_written, + }; } -static vector unicode_to_sjis_table_data; -static vector sjis_to_unicode_table_data; +string TextTranscoder::operator()(const void* src, size_t src_size) { + // Clear any conversion state left over from the previous call + iconv(this->ic, nullptr, nullptr, nullptr, nullptr); -static void load_sjis_tables() { - 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"); - auto lines = split(file_contents, '\n'); - for (auto line : lines) { - auto tokens = split(line, '\t'); - if (tokens.size() < 2) { - continue; + const void* orig_src = src; + deque blocks; + while (src_size > 0) { + // Assume 2x input size on average, but always alocate at least 4 bytes + string& block = blocks.emplace_back(max((src_size << 2), 4), '\0'); + char* dest = block.data(); + size_t dest_size = block.size(); + size_t ret = iconv( + this->ic, + reinterpret_cast(const_cast(&src)), + &src_size, + reinterpret_cast(&dest), + &dest_size); + block.resize(block.size() - dest_size); + if (block.size() == 0) { + // This should never happen because no character should be more than 4 + // bytes long in any known encoding + throw runtime_error("block size too small for conversion"); } - char16_t sjis_char = stoul(tokens[0], nullptr, 0); - char16_t unicode_char = stoul(tokens[1], nullptr, 0); - unicode_to_sjis_table_data[unicode_char] = sjis_char; - sjis_to_unicode_table_data[sjis_char] = unicode_char; + size_t bytes_read = reinterpret_cast(src) - reinterpret_cast(orig_src); + if (ret == this->FAILURE_RESULT) { + switch (errno) { + case EILSEQ: + throw runtime_error(string_printf("untranslatable character at position %zu", bytes_read)); + case EINVAL: + throw runtime_error(string_printf("incomplete multibyte sequence at position %zu", bytes_read)); + case E2BIG: + break; + default: + throw runtime_error("transcoding failed: " + string_for_error(errno)); + } + } + } + + return join(blocks, ""); +} + +string TextTranscoder::operator()(const string& data) { + return this->operator()(data.data(), data.size()); +} + +TextTranscoder tt_8859_to_utf8("UTF-8", "ISO-8859-1"); +TextTranscoder tt_utf8_to_8859("ISO-8859-1", "UTF-8"); +TextTranscoder tt_sjis_to_utf8("UTF-8", "SHIFT_JIS"); +TextTranscoder tt_utf8_to_sjis("SHIFT_JIS", "UTF-8"); +TextTranscoder tt_utf16_to_utf8("UTF-8", "UTF-16LE"); +TextTranscoder tt_utf8_to_utf16("UTF-16LE", "UTF-8"); +TextTranscoder tt_ascii_to_utf8("UTF-8", "ASCII"); +TextTranscoder tt_utf8_to_ascii("ASCII", "UTF-8"); + +std::string tt_encode_marked_optional(const std::string& utf8, uint8_t default_language, bool is_utf16) { + if (is_utf16) { + return tt_utf8_to_utf16(utf8); + } else { + if (default_language) { + try { + return tt_utf8_to_8859(utf8); + } catch (const exception& e) { + return "\tJ" + tt_utf8_to_sjis(utf8); + } + } else { + try { + return tt_utf8_to_sjis(utf8); + } catch (const exception& e) { + return "\tE" + tt_utf8_to_8859(utf8); + } + } } } -static const vector& sjis_to_unicode_table() { - if (sjis_to_unicode_table_data.empty()) { - load_sjis_tables(); +std::string tt_encode_marked(const std::string& utf8, uint8_t default_language, bool is_utf16) { + if (is_utf16) { + return tt_utf8_to_utf16((default_language ? "\tE" : "\tJ") + utf8); + } else { + if (default_language) { + try { + return "\tE" + tt_utf8_to_8859(utf8); + } catch (const exception& e) { + return "\tJ" + tt_utf8_to_sjis(utf8); + } + } else { + try { + return "\tJ" + tt_utf8_to_sjis(utf8); + } catch (const exception& e) { + return "\tE" + tt_utf8_to_8859(utf8); + } + } } - return sjis_to_unicode_table_data; } -static const vector& unicode_to_sjis_table() { - if (unicode_to_sjis_table_data.empty()) { - load_sjis_tables(); +std::string tt_decode_marked(const std::string& data, uint8_t default_language, bool is_utf16) { + if (is_utf16) { + string ret = tt_utf16_to_utf8(data); + if (ret.size() >= 2 && ret[0] == '\t' && (ret[1] == 'E' || ret[1] == 'J')) { + ret = ret.substr(2); + } + return ret; + } else { + if (data.size() >= 2 && data[0] == '\t') { + if (data[1] == 'J') { + return tt_sjis_to_utf8(data.substr(2)); + } else if (data[1] == 'E') { + return tt_8859_to_utf8(data.substr(2)); + } + } + return default_language ? tt_8859_to_utf8(data) : tt_sjis_to_utf8(data); } - return unicode_to_sjis_table_data; } -std::string encode_sjis(const char16_t* src, size_t src_count) { - const auto& table = unicode_to_sjis_table(); +string add_language_marker(const string& s, char marker) { + if ((s.size() >= 2) && (s[0] == '\t') && (s[1] != 'C')) { + return s; + } - const char16_t* src_end = src + src_count; string ret; - 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); - } - }; + ret.push_back('\t'); + ret.push_back(marker); + ret += s; return ret; } -size_t encode_sjis( - char* dest, - size_t dest_count, - const char16_t* src, - size_t src_count, - bool allow_skip_terminator) { - const auto& table = unicode_to_sjis_table(); - - if (dest_count == 0) { - throw logic_error("cannot encode into zero-length buffer"); +string remove_language_marker(const string& s) { + if ((s.size() < 2) || (s[0] != '\t') || (s[1] == 'C')) { + return s; } + return s.substr(2); +} - const char* dest_start = dest; - const char16_t* src_end = src + src_count; - const char* dest_end = dest + (allow_skip_terminator ? dest_count : (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; +void replace_char_inplace(char* a, char f, char r) { + while (*a) { + if (*a == f) { + *a = r; + } + a++; + } +} + +size_t add_color_inplace(char* a, size_t max_chars) { + char* d = a; + char* orig_d = d; + + for (size_t x = 0; (x < max_chars) && *a; x++) { + if (*a == '$') { + *(d++) = '\t'; + } else if (*a == '#') { + *(d++) = '\n'; + } else if (*a == '%') { + a++; + x++; + if (*a == 's') { + *(d++) = '$'; + } else if (*a == '%') { + *(d++) = '%'; + } else if (*a == 'n') { + *(d++) = '#'; + } else if (*a == '\0') { + break; } else { - *(dest++) = translated_c & 0xFF; + *(d++) = *a; } } else { - *(dest++) = translated_c & 0xFF; + *(d++) = *a; } + a++; } - if (!allow_skip_terminator || (dest != dest_end)) { - *dest = 0; - dest++; - } - return dest - dest_start; + *d = 0; + // TODO: we should clear the chars after the null if the new string is shorter + // than the original + + return d - orig_d; } -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_end) && *src) { - uint16_t src_char = *(src++); - if (src_char & 0x80) { - if (src == src_end) { - throw runtime_error("incomplete extended character"); - } - src_char = (src_char << 8) | static_cast(*(src++)); - if ((src_char & 0xFF) == 0) { - throw runtime_error("incomplete extended character"); - } - } - ret.push_back(table[src_char]); - }; - return ret; +void add_color_inplace(string& s) { + s.resize(add_color_inplace(s.data(), s.size())); } -size_t decode_sjis( - char16_t* dest, - size_t dest_count, - const char* src, - size_t src_count, - bool allow_skip_terminator) { - const auto& table = sjis_to_unicode_table(); - - if (dest_count == 0) { - throw logic_error("cannot decode into zero-length buffer"); - } - - const char16_t* dest_start = dest; - const char* src_end = src + src_count; - const char16_t* dest_end = dest + (allow_skip_terminator ? dest_count : (dest_count - 1)); - while ((dest != dest_end) && (src != src_end) && *src) { - uint16_t src_char = *(src++); - if (src_char & 0x80) { - if (src == src_end) { - throw runtime_error("incomplete extended character"); - } - src_char = (src_char << 8) | static_cast(*(src++)); - if ((src_char & 0xFF) == 0) { - throw runtime_error("incomplete extended character"); +void add_color(StringWriter& w, const char* src, size_t max_input_chars) { + for (size_t x = 0; (x < max_input_chars) && *src; x++) { + if (*src == '$') { + w.put('\t'); + } else if (*src == '#') { + w.put('\n'); + } else if (*src == '%') { + src++; + x++; + if (*src == 's') { + w.put('$'); + } else if (*src == '%') { + w.put('%'); + } else if (*src == 'n') { + w.put('#'); + } else if (*src == '\0') { + break; + } else { + w.put(*src); } + } else { + w.put(*src); } - *(dest++) = table[src_char]; - }; - if (!allow_skip_terminator || (dest != dest_end)) { - *(dest++) = 0; + src++; } - return dest - dest_start; + w.put(0); +} + +std::string add_color(const std::string& s) { + StringWriter w; + add_color(w, s.data(), s.size()); + return std::move(w.str()); } diff --git a/src/Text.hh b/src/Text.hh index 873e513e..017ee16e 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -9,179 +10,46 @@ #include #include -// (1a) Conversion functions +// Conversion functions -// These return the number of characters written, including the terminating null -// character. In the case of encode_sjis, two-byte characters count as two -// characters, so the returned number is the number of bytes written. -// allow_skip_terminator means no null byte will be written if dest_count -// characters are written to the output. If this argument is false, a null -// terminator is always written, even if the string is truncated. -size_t encode_sjis( - char* dest, size_t dest_count, - const char16_t* src, size_t src_count, - bool allow_skip_terminator = false); -size_t decode_sjis( - char16_t* dest, size_t dest_count, - const char* src, size_t src_count, - bool allow_skip_terminator = false); +class TextTranscoder { +public: + TextTranscoder(const char* to, const char* from); + TextTranscoder(const TextTranscoder&) = delete; + TextTranscoder(TextTranscoder&&); + TextTranscoder& operator=(const TextTranscoder&) = delete; + TextTranscoder& operator=(TextTranscoder&&); + ~TextTranscoder(); -std::string encode_sjis(const char16_t* source, size_t src_count); -std::u16string decode_sjis(const char* source, size_t src_count); + struct Result { + size_t bytes_read; + size_t bytes_written; + }; + Result operator()(void* dest, size_t dest_size, const void* src, size_t src_bytes, bool truncate_oversize_result); -inline std::string encode_sjis(const std::u16string& s) { - return encode_sjis(s.data(), s.size()); -} + std::string operator()(const void* src, size_t src_bytes); + std::string operator()(const std::string& data); -inline std::u16string decode_sjis(const std::string& s) { - return decode_sjis(s.data(), s.size()); -} +private: + static const iconv_t INVALID_IC; + static const size_t FAILURE_RESULT; + iconv_t ic; +}; -// These functions exist so that decode_sjis and encode_sjis can be -// indiscriminately used within templates that use different char types. -inline const std::string& encode_sjis(const std::string& s) { return s; } -inline const std::u16string& decode_sjis(const std::u16string& s) { return s; } +extern TextTranscoder tt_8859_to_utf8; +extern TextTranscoder tt_utf8_to_8859; +extern TextTranscoder tt_sjis_to_utf8; +extern TextTranscoder tt_utf8_to_sjis; +extern TextTranscoder tt_utf16_to_utf8; +extern TextTranscoder tt_utf8_to_utf16; +extern TextTranscoder tt_ascii_to_utf8; +extern TextTranscoder tt_utf8_to_ascii; -// (1b) Type-independent utility functions +std::string tt_encode_marked_optional(const std::string& utf8, uint8_t default_language, bool is_utf16); +std::string tt_encode_marked(const std::string& utf8, uint8_t default_language, bool is_utf16); +std::string tt_decode_marked(const std::string& data, uint8_t default_language, bool is_utf16); -template -size_t text_strlen_t(const T* s) { - size_t ret = 0; - for (; s[ret] != 0; ret++) { - } - return ret; -} - -template -size_t text_strnlen_t(const T* s, size_t count) { - size_t ret = 0; - for (; (ret < count) && (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; -} - -template -size_t text_strncpy_t(T* dest, const T* src, size_t count) { - size_t x; - for (x = 0; x < count && src[x] != 0; x++) { - dest[x] = src[x]; - } - if (x < count) { - dest[x++] = 0; - } - return x; -} - -// Like strncpy, but *always* null-terminates the string, even if it has to -// truncate it. -template -size_t 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]; - } - dest[x++] = 0; - return x; -} - -// (2) Type conversion functions - -template -size_t text_strncpy_t(DestT*, size_t, const SrcT*, size_t) { - static_assert(always_false::v, - "unspecialized text_strncpy_t should never be called"); - return 0; -} - -template <> -inline size_t text_strncpy_t( - char* dest, size_t dest_count, const char* src, size_t src_count) { - size_t count = std::min(dest_count, src_count); - return text_strncpy_t(dest, src, count); -} - -template <> -inline size_t text_strncpy_t( - char* dest, size_t dest_count, const char16_t* src, size_t src_count) { - return encode_sjis(dest, dest_count, src, src_count, true); -} - -template <> -inline size_t text_strncpy_t( - char16_t* dest, size_t dest_count, const char* src, size_t src_count) { - return decode_sjis(dest, dest_count, src, src_count, true); -} - -template <> -inline size_t text_strncpy_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); - return text_strncpy_t(dest, src, count); -} - -template -size_t text_strnzcpy_t(DestT*, size_t, const SrcT*, size_t) { - static_assert(always_false::v, - "unspecialized text_strnzcpy_t should never be called"); - return 0; -} - -template <> -inline size_t 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); - return text_strnzcpy_t(dest, src, count); -} - -template <> -inline size_t text_strnzcpy_t( - char* dest, size_t dest_count, const char16_t* src, size_t src_count) { - return encode_sjis(dest, dest_count, src, src_count); -} - -template <> -inline size_t text_strnzcpy_t( - char16_t* dest, size_t dest_count, const char* src, size_t src_count) { - return decode_sjis(dest, dest_count, src, src_count); -} - -template <> -inline size_t 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); - return text_strnzcpy_t(dest, src, count); -} - -// (3) Packed text objects for use in protocol structs +// Packed array object for use in protocol structs template struct parray { @@ -357,325 +225,299 @@ struct parray { } } __attribute__((packed)); -template -struct ptext : parray { - ptext() { - this->clear(0); - } - ptext(const ptext& other) : parray(other) {} - ptext(ptext&& s) = delete; +// Packed text objects for use in protocol structs - template - ptext(const OtherCharT* s) { - if (!s) { - throw std::logic_error("attempted to assign nullptr to ptext"); +enum class TextEncoding { + UTF8, + UTF16, + SJIS, + ISO8859, + ASCII, + MARKED, + CHALLENGE8, // MARKED but with challenge encryption on top + CHALLENGE16, // UTF16 but with challenge encryption on top +}; + +template +void encrypt_challenge_rank_text_t(void* vdata, size_t count) { + CharT* data = reinterpret_cast(vdata); + CharT prev = 0; + for (CharT* p = data; p != data + count; p++) { + CharT ch = *p; + if (ch == 0) { + break; } - this->operator=(s); + *p = ((ch - prev) ^ 0x7F) & 0xFF; + prev = ch; } - template - ptext(const OtherCharT* s, size_t count) { - if (!s) { - throw std::logic_error("attempted to assign nullptr to ptext"); +} + +template +void decrypt_challenge_rank_text_t(void* vdata, size_t count) { + CharT* data = reinterpret_cast(vdata); + for (CharT* p = data; p != data + count; p++) { + if (*p == 0) { + break; + } + if (p == data) { + *p ^= 0x7F; + } else { + *p = ((*p ^ 0x7F) + *(p - 1)) & 0xFF; } - this->assign(s, count); } - template - ptext(const std::basic_string& s) { - this->operator=(s); +} + +// This struct does not inherit from parray, even though it's semantically +// similar, because we want to enforce that the correct encoding is used. +template < + TextEncoding Encoding, + size_t Chars, + size_t BytesPerChar = (((Encoding == TextEncoding::UTF16) || (Encoding == TextEncoding::CHALLENGE16)) ? 2 : 1)> +struct pstring { + static constexpr size_t Bytes = Chars * BytesPerChar; + + static constexpr size_t bytes() { + return Bytes; } - template - ptext(const ptext& s) { - this->operator=(s); + static constexpr size_t chars() { + return Chars; } - size_t len() const { - return text_strnlen_t(this->items, Count); + uint8_t data[Bytes]; + + pstring() { + memset(this->data, 0, Bytes); } + pstring(const pstring& other) { + memcpy(this->data, other.data, Bytes); + } + pstring(const std::string& s, uint8_t language) { + this->encode(s, language); + } + pstring(pstring&& other) = delete; - // Q: Why is there no c_str() here? - // A: Because the contents of a ptext don't have to be null-terminated. - - ptext& operator=(const ptext& s) { - memcpy(this->items, s.items, sizeof(CharT) * Count); + pstring& operator=(const pstring& other) { + memcpy(this->data, other.data, Bytes); return *this; } - ptext& operator=(ptext&& s) = delete; - - template - ptext& operator=(const OtherCharT* s) { - if (!s) { - throw std::logic_error("attempted to assign nullptr to ptext"); - } - size_t chars_written = text_strncpy_t(this->items, Count, s, Count); - this->clear_after(chars_written); + template + pstring& operator=(const pstring& other) { + size_t end_offset = std::min(Bytes, pstring::Bytes); + memcpy(this->data, other.data, end_offset); + this->clear_after(end_offset); return *this; } - template - ptext& assign(const OtherCharT* s, size_t s_count) { - if (!s) { - throw std::logic_error("attempted to assign nullptr to ptext"); + pstring& operator=(pstring&& s) = delete; + + void encode(const std::string& s, uint8_t client_language = 1) { + try { + switch (Encoding) { + case TextEncoding::CHALLENGE8: + case TextEncoding::ASCII: { + auto ret = tt_utf8_to_ascii(this->data, Bytes, s.data(), s.size(), true); + this->clear_after(ret.bytes_written); + if (Encoding == TextEncoding::CHALLENGE8) { + encrypt_challenge_rank_text_t(this->data, Bytes); + } + break; + } + case TextEncoding::ISO8859: { + auto ret = tt_utf8_to_8859(this->data, Bytes, s.data(), s.size(), true); + this->clear_after(ret.bytes_written); + break; + } + case TextEncoding::SJIS: { + auto ret = tt_utf8_to_sjis(this->data, Bytes, s.data(), s.size(), true); + this->clear_after(ret.bytes_written); + break; + } + case TextEncoding::UTF16: { + auto ret = tt_utf8_to_utf16(this->data, Bytes, s.data(), s.size(), true); + this->clear_after(ret.bytes_written); + break; + } + case TextEncoding::UTF8: + memcpy(this->data, s.data(), std::min(s.size(), Bytes)); + this->clear_after(s.size()); + break; + case TextEncoding::CHALLENGE16: { + auto ret = tt_utf8_to_utf16(this->data, Bytes, s.data(), s.size(), true); + encrypt_challenge_rank_text_t(this->data, ret.bytes_written / 2); + this->clear_after(ret.bytes_written); + break; + } + case TextEncoding::MARKED: { + if (client_language == 0) { + try { + auto ret = tt_utf8_to_sjis(this->data, Bytes, s.data(), s.size(), true); + this->clear_after(ret.bytes_written); + } catch (const std::runtime_error&) { + this->data[0] = '\t'; + this->data[1] = 'E'; + auto ret = tt_utf8_to_8859(this->data + 2, Bytes - 2, s.data(), s.size(), true); + this->clear_after(ret.bytes_written + 2); + } + } else { + try { + auto ret = tt_utf8_to_8859(this->data, Bytes, s.data(), s.size(), true); + this->clear_after(ret.bytes_written); + } catch (const std::runtime_error&) { + this->data[0] = '\t'; + this->data[1] = 'J'; + auto ret = tt_utf8_to_sjis(this->data + 2, Bytes - 2, s.data(), s.size(), true); + this->clear_after(ret.bytes_written + 2); + } + } + break; + } + default: + throw std::logic_error("unknown text encoding"); + } + } catch (const std::runtime_error& e) { + log_warning("Unencodable text: %s", e.what()); + if (Bytes >= 3) { + this->data[0] = '<'; + this->data[1] = '?'; + this->data[2] = '>'; + this->clear_after(3); + } else if (Bytes >= 1) { + this->data[0] = '?'; + this->clear_after(1); + } } - size_t chars_written = text_strncpy_t(this->items, Count, s, s_count); - this->clear_after(chars_written); - return *this; - } - template - ptext& operator=(const std::basic_string& s) { - size_t chars_written = text_strncpy_t(this->items, Count, s.c_str(), s.size()); - this->clear_after(chars_written); - return *this; - } - template - ptext& operator=(const ptext& s) { - size_t chars_written = text_strncpy_t(this->items, Count, s.items, OtherCount); - this->clear_after(chars_written); - return *this; } - template - bool operator==(const OtherCharT* s) const { - if (!s) { - throw std::logic_error("attempted to compare ptext to nullptr"); + std::string decode(uint8_t client_language = 1) const { + try { + switch (Encoding) { + case TextEncoding::CHALLENGE8: { + std::string decrypted(reinterpret_cast(this->data), this->used_bytes_8()); + decrypt_challenge_rank_text_t(decrypted.data(), decrypted.size()); + return tt_ascii_to_utf8(decrypted.data(), decrypted.size()); + } + case TextEncoding::ASCII: + return tt_ascii_to_utf8(this->data, this->used_bytes_8()); + case TextEncoding::ISO8859: + return tt_8859_to_utf8(this->data, this->used_bytes_8()); + case TextEncoding::SJIS: + return tt_sjis_to_utf8(this->data, this->used_bytes_8()); + case TextEncoding::UTF16: + return tt_utf16_to_utf8(this->data, this->used_bytes_16()); + case TextEncoding::UTF8: + return std::string(reinterpret_cast(&this->data[0]), this->used_bytes_8()); + case TextEncoding::CHALLENGE16: { + std::string decrypted(reinterpret_cast(&this->data[0]), this->used_bytes_8()); + decrypt_challenge_rank_text_t(decrypted.data(), decrypted.size()); + return tt_utf16_to_utf8(decrypted.data(), decrypted.size()); + } + case TextEncoding::MARKED: { + size_t offset = 0; + if (this->data[0] == '\t') { + if (this->data[1] == 'J') { + client_language = 0; + offset = 2; + } else { + client_language = 1; + offset = 2; + } + } + return client_language + ? tt_8859_to_utf8(&this->data[offset], this->used_bytes_8() - offset) + : tt_sjis_to_utf8(&this->data[offset], this->used_bytes_8() - offset); + } + default: + throw std::logic_error("unknown text encoding"); + } + } catch (const std::runtime_error& e) { + log_warning("Undecodable text: %s", e.what()); + return ""; } - return text_strneq_t(this->items, s, Count); - } - template - bool operator==(const std::basic_string& s) const { - return text_strneq_t(this->items, s.c_str(), Count); - } - template - bool operator==(const ptext& s) const { - return text_strneq_t(this->items, s.items, std::min(Count, OtherCount)); - } - template - bool operator!=(const OtherCharT* s) const { - if (!s) { - throw std::logic_error("attempted to compare ptext to nullptr"); - } - return !this->operator==(s); - } - template - bool operator!=(const std::basic_string& s) const { - return !this->operator==(s); - } - template - bool operator!=(const ptext& s) const { - return !this->operator==(s); } - template - bool eq_n(const OtherCharT* s, size_t count) const { - if (!s) { - throw std::logic_error("attempted to compare ptext to nullptr"); - } - return text_strneq_t(this->items, s, count); + bool operator==(const pstring& other) const { + return (memcmp(this->data, other.data, Bytes) == 0); } - 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); + bool operator!=(const pstring& other) const { + return (memcmp(this->data, other.data, Bytes) != 0); } - operator std::basic_string() const { - return std::basic_string(this->items, this->len()); + bool eq(const std::string& other, uint8_t language = 1) const { + return this->decode(language) == other; + } + + size_t used_bytes_8() const { + size_t size = 0; + for (size = 0; size < Bytes; size++) { + if (!this->data[size]) { + return size; + } + } + return Bytes; + } + + size_t used_bytes_16() const { + if (Bytes & 1) { + throw std::logic_error("used_bytes_16 must not be called on an odd-length pstring"); + } + for (size_t z = 0; z < Bytes; z += 2) { + if (!this->data[z] && !this->data[z + 1]) { + return z; + } + } + return Bytes; } bool empty() const { - return (this->items[0] == 0); + for (size_t z = 0; z < BytesPerChar; z++) { + if (this->data[z] != 0) { + return false; + } + } + return true; } + + void clear(uint8_t v = 0) { + memset(this->data, v, Chars * BytesPerChar); + } + + void clear_after(size_t pos, uint8_t v = 0) { + for (pos *= BytesPerChar; pos < Chars * BytesPerChar; pos++) { + this->data[pos] = v; + } + } + + void set_byte(size_t pos, uint8_t v) { + if (pos >= Bytes) { + throw std::out_of_range("pstring byte offset out of range"); + } + this->data[pos] = v; + } + + void assign_raw(const void* data, size_t size) { + memcpy(this->data, data, std::min(size, Bytes)); + this->clear_after(size); + } + void assign_raw(const std::string& data) { + this->assign_raw(data.data(), data.size()); + } + + uint8_t at(size_t pos) const { + if (pos >= Bytes) { + throw std::out_of_range("pstring index out of range"); + } + return this->data[pos]; + } + + // Note: The contents of a pstring do not have to be null-terminated, so there + // is no .c_str() function. } __attribute__((packed)); -// (4) Markers and character replacement +// Helper functions -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; - } +void replace_char_inplace(char* a, char f, char r); - std::basic_string ret; - ret.push_back('\t'); - ret.push_back(marker); - ret += s; - return ret; -} +void add_color(StringWriter& w, const char* src, size_t max_input_chars); +std::string add_color(const std::string& s); -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 -void add_language_marker_inplace(ptext& s, char16_t marker) { - static_assert(Count >= 2, "cannot use add_language_marker_inplace on ptext with fewer than 2 characters"); - - if ((s.items[0] == '\t') && (s.items[1] != 'C')) { - return; - } - - size_t end_offset = std::min(s.len() + 2, Count); - for (size_t z = end_offset; z > 2; z--) { - s[z - 1] = s[z - 3]; - } - s[0] = '\t'; - s[1] = marker; -} - -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); - a.items[text_strlen_t(a.items) + 1] = 0; - } -} - -template -void replace_char_inplace(T* a, T f, T r) { - while (*a) { - if (*a == f) { - *a = r; - } - a++; - } -} - -template -size_t add_color_inplace(T* a, size_t max_chars) { - T* d = a; - T* orig_d = d; - - for (size_t x = 0; (x < max_chars) && *a; x++) { - if (*a == '$') { - *(d++) = '\t'; - } else if (*a == '#') { - *(d++) = '\n'; - } else if (*a == '%') { - a++; - x++; - if (*a == 's') { - *(d++) = '$'; - } else if (*a == '%') { - *(d++) = '%'; - } else if (*a == 'n') { - *(d++) = '#'; - } else if (*a == '\0') { - break; - } else { - *(d++) = *a; - } - } else { - *(d++) = *a; - } - a++; - } - *d = 0; - // TODO: we should clear the chars after the null if the new string is shorter - // than the original - - return d - orig_d; -} - -template -void add_color_inplace(std::basic_string& s) { - size_t new_size = add_color_inplace(s.data(), s.size()); - s.resize(new_size); -} - -template -void add_color(StringWriter& w, const T* src, size_t max_input_chars) { - for (size_t x = 0; (x < max_input_chars) && *src; x++) { - if (*src == '$') { - w.put('\t'); - } else if (*src == '#') { - w.put('\n'); - } else if (*src == '%') { - src++; - x++; - if (*src == 's') { - w.put('$'); - } else if (*src == '%') { - w.put('%'); - } else if (*src == 'n') { - w.put('#'); - } else if (*src == '\0') { - break; - } else { - w.put(*src); - } - } else { - w.put(*src); - } - src++; - } - 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; - } -} +size_t add_color_inplace(char* a, size_t max_chars); +void add_color_inplace(std::string& s); diff --git a/src/UnicodeTextSet.cc b/src/UnicodeTextSet.cc index f45dfc85..27283815 100644 --- a/src/UnicodeTextSet.cc +++ b/src/UnicodeTextSet.cc @@ -8,20 +8,21 @@ using namespace std; -vector parse_unicode_text_set(const string& prs_data) { +vector parse_unicode_text_set(const string& prs_data) { string data = prs_decompress(prs_data); StringReader r(data); r.skip(4); uint32_t count = r.get_u32l(); - vector ret; + vector ret; while (ret.size() < count) { - ret.emplace_back(&r.pget(r.get_u32l())); + u16string s(&r.pget(r.get_u32l())); + ret.emplace_back(tt_utf16_to_utf8(s.data(), s.size() * 2)); } return ret; } -string serialize_unicode_text_set(const vector& strings) { +string serialize_unicode_text_set(const vector& strings) { StringWriter w; w.put_u32l(strings.size()); size_t string_offset = (strings.size() * 4) + 4; // Header size @@ -30,8 +31,9 @@ string serialize_unicode_text_set(const vector& strings) { string_offset = (((s.size() + 1) << 1) + 3) & (~3); } for (const auto& s : strings) { - u16string uni_s = decode_sjis(s); - w.write(uni_s.c_str(), (uni_s.size() + 1) * 2); + string s_utf16 = tt_utf8_to_utf16(s); + w.write(s_utf16.data(), s_utf16.size()); + w.put_u16(0); while (w.size() & 3) { w.put_u8(0); } diff --git a/src/UnicodeTextSet.hh b/src/UnicodeTextSet.hh index ef03ba6c..abc77134 100644 --- a/src/UnicodeTextSet.hh +++ b/src/UnicodeTextSet.hh @@ -3,5 +3,5 @@ #include #include -std::vector parse_unicode_text_set(const std::string& prs_data); -std::string serialize_unicode_text_set(const std::vector& strings); +std::vector parse_unicode_text_set(const std::string& prs_data); +std::string serialize_unicode_text_set(const std::vector& strings); diff --git a/tests/DCv2-GameSmokeTest.test.txt b/tests/DCv2-GameSmokeTest.test.txt index 8891df08..50e3d12f 100644 --- a/tests/DCv2-GameSmokeTest.test.txt +++ b/tests/DCv2-GameSmokeTest.test.txt @@ -660,7 +660,7 @@ I 40992 2023-05-26 10:53:41 - [Commands] Sending to C-2 (Tali) (version=DC comma I 40992 2023-05-26 10:53:42 - [Commands] Received from C-2 (Tali) (version=DC command=8A flag=00) 0000 | 8A 00 04 00 | I 40992 2023-05-26 10:53:42 - [Commands] Sending to C-2 (Tali) (version=DC command=8A flag=00) -0000 | 8A 00 0C 00 09 45 41 41 41 41 00 00 | EAAAA +0000 | 8A 00 0C 00 41 41 41 41 00 00 00 00 | AAAA I 40992 2023-05-26 10:53:53 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 C0 00 00 00 00 | ` ? 0010 | CE FE 64 43 00 00 00 00 B8 FF 7D 43 | dC }C diff --git a/tests/GC-Episode3BattleWithSpectator.test.txt b/tests/GC-Episode3BattleWithSpectator.test.txt index 4bdb62e3..6310895e 100644 --- a/tests/GC-Episode3BattleWithSpectator.test.txt +++ b/tests/GC-Episode3BattleWithSpectator.test.txt @@ -9431,7 +9431,7 @@ I 17097 2023-09-19 21:53:58 - [Commands] Received from C-4 (Tali) (version=GC co I 17097 2023-09-19 21:53:58 - [Commands] Sending to C-4 (Tali) (version=GC command=08 flag=01) 0000 | 08 01 3C 00 44 00 00 44 00 00 00 00 00 00 41 6C | < D D Al 0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 04 | exandria -0020 | 44 00 00 44 15 00 00 00 0A 01 09 45 31 31 31 31 | D D E1111 +0020 | 44 00 00 44 15 00 00 00 0A 01 31 31 31 31 00 00 | D D 1111 0030 | 00 00 00 00 00 00 00 00 00 00 00 04 | I 17097 2023-09-19 21:54:07 - [Commands] Received from C-4 (Tali) (version=GC command=E7 flag=00) 0000 | E7 00 30 00 44 00 00 44 15 00 00 00 09 4A 35 36 | 0 D D J56 @@ -9466,9 +9466,9 @@ I 17097 2023-09-19 21:54:07 - [Commands] Sending to C-2 (Tali) (version=GC comma 0110 | 00 00 00 00 | I 17097 2023-09-19 21:54:07 - [Commands] Sending to C-4 (Tali) (version=GC command=C9 flag=00) 0000 | C9 00 14 01 B4 44 00 00 52 00 00 00 01 00 01 00 | D R -0010 | 00 00 1D 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt -0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 09 45 31 31 31 | le in game E111 -0030 | 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1 +0010 | 00 00 1B 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt +0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 31 31 31 31 00 | le in game 1111 +0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -9806,7 +9806,7 @@ I 17097 2023-09-19 21:54:07 - [Commands] Sending to C-4 (Tali) (version=GC comma 13F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1400 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1410 | 00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF | -1420 | 00 00 00 00 09 4A 35 36 37 38 00 00 00 00 00 00 | J5678 +1420 | 00 00 00 00 35 36 37 38 00 00 00 00 00 00 00 00 | 5678 1430 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1440 | 00 00 00 00 00 00 01 00 22 22 22 22 7F 00 00 01 | """" 1450 | 04 00 00 00 54 61 6C 69 00 00 00 00 00 00 00 00 | Tali @@ -10445,9 +10445,9 @@ I 17097 2023-09-19 21:54:57 - [Commands] Sending to C-2 (Tali) (version=GC comma 0110 | 00 00 00 00 | I 17097 2023-09-19 21:54:57 - [Commands] Sending to C-4 (Tali) (version=GC command=C9 flag=00) 0000 | C9 00 14 01 B4 44 00 00 52 00 00 00 01 00 01 00 | D R -0010 | 00 00 1D 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt -0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 09 45 31 31 31 | le in game E111 -0030 | 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1 +0010 | 00 00 1B 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt +0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 31 31 31 31 00 | le in game 1111 +0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -10680,9 +10680,9 @@ I 17097 2023-09-19 21:54:57 - [Commands] Sending to C-2 (Tali) (version=GC comma 0110 | 00 00 00 00 | I 17097 2023-09-19 21:54:57 - [Commands] Sending to C-4 (Tali) (version=GC command=C9 flag=00) 0000 | C9 00 14 01 B4 44 00 00 52 00 00 00 01 00 01 00 | D R -0010 | 00 00 1D 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt -0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 09 45 31 31 31 | le in game E111 -0030 | 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1 +0010 | 00 00 1B 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt +0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 31 31 31 31 00 | le in game 1111 +0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -11735,9 +11735,9 @@ I 17097 2023-09-19 21:54:58 - [Commands] Sending to C-2 (Tali) (version=GC comma 0110 | 00 00 00 00 | I 17097 2023-09-19 21:54:58 - [Commands] Sending to C-4 (Tali) (version=GC command=C9 flag=00) 0000 | C9 00 14 01 B4 44 00 00 52 00 00 00 01 00 01 00 | D R -0010 | 00 00 1D 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt -0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 09 45 31 31 31 | le in game E111 -0030 | 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1 +0010 | 00 00 1B 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt +0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 31 31 31 31 00 | le in game 1111 +0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -14390,7 +14390,7 @@ I 17097 2023-09-19 21:56:42 - [Commands] Received from C-2 (Tali) (version=GC co 0010 | 31 31 31 00 00 00 00 00 | 111 I 17097 2023-09-19 21:56:42 - [Commands] Sending to C-2 (Tali) (version=GC command=06 flag=00) 0000 | 06 00 1C 00 00 00 00 00 11 11 11 11 54 61 6C 69 | Tali -0010 | 09 4E 09 4A 31 31 31 31 00 00 00 00 | N J1111 +0010 | 09 4E 09 45 31 31 31 31 00 00 00 00 | N E1111 I 17097 2023-09-19 21:56:42 - [Commands] Sending to C-4 (Tali) (version=GC command=06 flag=00) 0000 | 06 00 1C 00 00 00 00 00 11 11 11 11 54 61 6C 69 | Tali 0010 | 09 4E 09 4A 31 31 31 31 00 00 00 00 | N J1111 @@ -34116,7 +34116,7 @@ I 17097 2023-09-19 22:01:39 - [Commands] Received from C-2 (Tali) (version=GC co 0010 | 32 33 34 35 36 37 38 39 30 2E 2C 27 22 00 00 00 | 234567890.,'" I 17097 2023-09-19 22:01:39 - [Commands] Sending to C-2 (Tali) (version=GC command=06 flag=00) 0000 | 06 00 24 00 00 00 00 00 11 11 11 11 54 61 6C 69 | $ Tali -0010 | 09 4E 09 4A 31 32 33 34 35 36 37 38 39 30 2E 2C | N J1234567890., +0010 | 09 4E 09 45 31 32 33 34 35 36 37 38 39 30 2E 2C | N E1234567890., 0020 | 27 22 00 00 | '" I 17097 2023-09-19 22:01:39 - [Commands] Sending to C-4 (Tali) (version=GC command=06 flag=00) 0000 | 06 00 24 00 00 00 00 00 11 11 11 11 54 61 6C 69 | $ Tali @@ -35546,7 +35546,7 @@ I 17097 2023-09-19 22:01:52 - [Commands] Received from C-2 (Tali) (version=GC co 0010 | 23 23 23 23 23 23 23 23 00 00 00 00 | ######## I 17097 2023-09-19 22:01:52 - [Commands] Sending to C-2 (Tali) (version=GC command=06 flag=00) 0000 | 06 00 20 00 00 00 00 00 11 11 11 11 54 61 6C 69 | Tali -0010 | 09 4E 09 4A 23 23 23 23 23 23 23 23 23 00 00 00 | N J######### +0010 | 09 4E 09 45 23 23 23 23 23 23 23 23 23 00 00 00 | N E######### I 17097 2023-09-19 22:01:52 - [Commands] Sending to C-4 (Tali) (version=GC command=06 flag=00) 0000 | 06 00 20 00 00 00 00 00 11 11 11 11 54 61 6C 69 | Tali 0010 | 09 4E 09 4A 23 23 23 23 23 23 23 23 23 00 00 00 | N J######### diff --git a/tests/GC-ForestGame.test.txt b/tests/GC-ForestGame.test.txt index 810e193c..ed760807 100644 --- a/tests/GC-ForestGame.test.txt +++ b/tests/GC-ForestGame.test.txt @@ -657,7 +657,7 @@ I 49108 2023-05-26 16:18:32 - [Commands] Sending to C-2 (Jess) (version=GC comma I 49108 2023-05-26 16:18:33 - [Commands] Received from C-2 (Jess) (version=GC command=8A flag=00) 0000 | 8A 00 04 00 | I 49108 2023-05-26 16:18:33 - [Commands] Sending to C-2 (Jess) (version=GC command=8A flag=00) -0000 | 8A 00 0C 00 09 45 31 31 31 31 00 00 | E1111 +0000 | 8A 00 0C 00 31 31 31 31 00 00 00 00 | 1111 I 49108 2023-05-26 16:18:34 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 C0 0F 00 00 00 | ` ? 0010 | CE FE 64 43 00 00 00 00 B8 FF 7D 43 | dC }C @@ -10015,11 +10015,11 @@ I 49108 2023-05-26 16:28:20 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | D8 00 04 00 | I 49108 2023-05-26 16:28:20 - [Commands] Sending to C-2 (Jess) (version=GC command=D8 flag=01) 0000 | D8 01 C0 00 4A 65 73 73 00 00 00 00 00 00 00 00 | Jess -0010 | 00 00 00 00 09 45 09 43 30 30 20 09 43 31 31 20 | E C00 C11 -0020 | 09 43 32 32 20 09 43 33 33 20 09 43 34 34 20 09 | C22 C33 C44 -0030 | 43 35 35 0A 09 43 36 36 20 09 43 37 37 20 09 43 | C55 C66 C77 C -0040 | 38 38 20 09 43 39 39 20 09 43 47 47 20 09 43 61 | 88 C99 CGG Ca -0050 | 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | a +0010 | 00 00 00 00 09 43 30 30 20 09 43 31 31 20 09 43 | C00 C11 C +0020 | 32 32 20 09 43 33 33 20 09 43 34 34 20 09 43 35 | 22 C33 C44 C5 +0030 | 35 0A 09 43 36 36 20 09 43 37 37 20 09 43 38 38 | 5 C66 C77 C88 +0040 | 20 09 43 39 39 20 09 43 47 47 20 09 43 61 61 00 | C99 CGG Caa +0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0070 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0080 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | diff --git a/tests/PC-BasicGame.test.txt b/tests/PC-BasicGame.test.txt index fa3f4aa7..8ef817ab 100644 --- a/tests/PC-BasicGame.test.txt +++ b/tests/PC-BasicGame.test.txt @@ -752,7 +752,7 @@ I 49484 2023-05-26 16:36:40 - [Commands] Received from C-3 (Tali) (version=PC co 0010 | 20 00 00 00 | I 49484 2023-05-26 16:36:40 - [Commands] Sending to C-3 (Tali) (version=PC command=06 flag=00) 0000 | 20 00 06 00 00 00 00 00 11 11 11 11 54 00 61 00 | T a -0010 | 6C 00 69 00 09 00 09 00 4A 00 20 00 00 00 00 00 | l i J +0010 | 6C 00 69 00 09 00 09 00 45 00 20 00 00 00 00 00 | l i E I 49484 2023-05-26 16:36:40 - [Commands] Received from C-3 (Tali) (version=PC command=62 flag=00) 0000 | 10 00 62 00 5A 03 00 00 05 00 01 00 01 00 00 00 | b Z I 49484 2023-05-26 16:36:40 - [Commands] Sending to C-3 (Tali) (version=PC command=62 flag=00)