diff --git a/TODO.md b/TODO.md index 424c522c..a125aa6c 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,6 @@ - Implement decrypt/encrypt actions for VMS files - Make UI strings localizable (e.g. entries in menus, welcome message, etc.) - Add an idle connection timeout for proxy sessions -- Look into JP heart symbol bug on Linux - Check server's rare enemy logic against GC's logic ## Episode 3 diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 199252a5..33c34bb8 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1145,7 +1145,7 @@ static HandlerResult C_B_D9(shared_ptr ses, uint16_t 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()); + ses->log.warning("Failed to decode and unescape D9 command: %s", e.what()); } // TODO: We should check if the info board text was actually modified and // return HandlerResult::FORWARD if not. @@ -1610,16 +1610,21 @@ static HandlerResult C_06(shared_ptr ses, uint16_t, strip_trailing_zeroes(text); uint8_t private_flags = 0; - if (uses_utf16(ses->version())) { - if (text.size() & 1) { - text.push_back(0); + try { + if (uses_utf16(ses->version())) { + if (text.size() & 1) { + text.push_back(0); + } + text = tt_decode_marked(text, ses->language(), true); + } else if (!text.empty() && (text[0] != '\t') && is_ep3(ses->version())) { + private_flags = text[0]; + text = tt_decode_marked(text.substr(1), ses->language(), false); + } else { + text = tt_decode_marked(text, ses->language(), false); } - text = tt_decode_marked(text, ses->language(), true); - } else if (!text.empty() && (text[0] != '\t') && is_ep3(ses->version())) { - private_flags = text[0]; - text = tt_decode_marked(text.substr(1), ses->language(), false); - } else { - text = tt_decode_marked(text, ses->language(), false); + } catch (const runtime_error& e) { + ses->log.warning("Failed to decode and unescape chat text: %s", e.what()); + return HandlerResult::Type::FORWARD; } if (text.empty()) { diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index c56503fb..c2e06f4e 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2978,7 +2978,11 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri if (auto_reply.size() & 1) { auto_reply.push_back(0); } - player->auto_reply.encode(tt_utf16_to_utf8(auto_reply), player->inventory.language); + try { + player->auto_reply.encode(tt_utf16_to_utf8(auto_reply), player->inventory.language); + } catch (const runtime_error& e) { + c->log.warning("Failed to decode auto-reply message: %s", e.what()); + } c->license->auto_reply_message = auto_reply; } else { player->auto_reply.clear(); @@ -3001,8 +3005,12 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri if (cmd.auto_reply_enabled) { 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); + try { + string encoded = tt_decode_marked(auto_reply, player->inventory.language, false); + player->auto_reply.encode(encoded, player->inventory.language); + } catch (const runtime_error& e) { + c->log.warning("Failed to decode auto-reply message: %s", e.what()); + } c->license->auto_reply_message = auto_reply; } else { player->auto_reply.clear(); @@ -3073,8 +3081,12 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri if (cmd->auto_reply_enabled) { 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); + try { + string encoded = tt_decode_marked(auto_reply, player->inventory.language, false); + player->auto_reply.encode(encoded, player->inventory.language); + } catch (const runtime_error& e) { + c->log.warning("Failed to decode auto-reply message: %s", e.what()); + } c->license->auto_reply_message = auto_reply; } else { player->auto_reply.clear(); @@ -3098,7 +3110,11 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri if (auto_reply.size() & 1) { auto_reply.push_back(0); } - player->auto_reply.encode(tt_utf16_to_utf8(auto_reply), player->inventory.language); + try { + player->auto_reply.encode(tt_utf16_to_utf8(auto_reply), player->inventory.language); + } catch (const runtime_error& e) { + c->log.warning("Failed to decode auto-reply message: %s", e.what()); + } c->license->auto_reply_message = auto_reply; } else { player->auto_reply.clear(); @@ -3223,7 +3239,13 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { text = text.substr(1); } - text = tt_decode_marked(text, c->language(), is_w); + try { + text = tt_decode_marked(text, c->language(), is_w); + } catch (const runtime_error& e) { + c->log.warning("Failed to decode chat message: %s", e.what()); + send_text_message_printf(c, "$C4Failed to decode\nchat message:\n%s", e.what()); + return; + } if (text.empty()) { return; } @@ -3247,32 +3269,42 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { if (from_name.size() >= 2 && from_name[0] == '\t' && (from_name[1] == 'E' || from_name[1] == 'J')) { from_name = from_name.substr(2); } + static const string whisper_text = "(whisper)"; for (size_t x = 0; x < l->max_clients; x++) { if (l->clients[x]) { - if (private_flags & (1 << x)) { - send_chat_message(l->clients[x], c->license->serial_number, from_name, "(whisper)", private_flags); - } else { - send_chat_message(l->clients[x], c->license->serial_number, from_name, text, private_flags); + const string& effective_text = (private_flags & (1 << x)) ? whisper_text : text; + try { + send_chat_message(l->clients[x], c->license->serial_number, from_name, effective_text, private_flags); + } catch (const runtime_error& e) { + l->clients[x]->log.warning("Failed to encode chat message: %s", e.what()); } } } 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, text, private_flags); + try { + send_chat_message(watcher_l->clients[x], c->license->serial_number, from_name, text, private_flags); + } catch (const runtime_error& e) { + watcher_l->clients[x]->log.warning("Failed to encode chat message: %s", e.what()); + } } } } if (l->battle_record && l->battle_record->battle_in_progress()) { - auto prepared_message = prepare_chat_data( - c->version(), - c->language(), - c->lobby_client_id, - p->disp.name.decode(c->language()), - text, - private_flags); - l->battle_record->add_chat_message(c->license->serial_number, std::move(prepared_message)); + try { + auto prepared_message = prepare_chat_data( + c->version(), + c->language(), + c->lobby_client_id, + p->disp.name.decode(c->language()), + text, + private_flags); + l->battle_record->add_chat_message(c->license->serial_number, std::move(prepared_message)); + } catch (const runtime_error& e) { + l->log.warning("Failed to encode chat message for battle record: %s", e.what()); + } } } @@ -3902,7 +3934,11 @@ void on_D9(shared_ptr c, uint16_t, uint32_t, string& data) { if (is_w && (data.size() & 1)) { data.push_back(0); } - c->character(true, false)->info_board.encode(tt_decode_marked(data, c->language(), is_w), c->language()); + try { + c->character(true, false)->info_board.encode(tt_decode_marked(data, c->language(), is_w), c->language()); + } catch (const runtime_error& e) { + c->log.warning("Failed to decode info board message: %s", e.what()); + } } void on_C7(shared_ptr c, uint16_t, uint32_t, string& data) { @@ -3912,8 +3948,14 @@ void on_C7(shared_ptr c, uint16_t, uint32_t, string& data) { data.push_back(0); } - string message = tt_decode_marked(data, c->language(), is_w); - c->character(true, false)->auto_reply.encode(message, c->language()); + string message; + try { + message = tt_decode_marked(data, c->language(), is_w); + c->character(true, false)->auto_reply.encode(message, c->language()); + } catch (const runtime_error& e) { + c->log.warning("Failed to decode auto-reply message: %s", e.what()); + return; + } c->license->auto_reply_message = message; c->license->save(); } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index a88ca17f..2787c85e 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -711,16 +711,21 @@ static void send_text( color_mode = ColorMode::STRIP; } - switch (color_mode) { - case ColorMode::NONE: - w.write(tt_encode_marked_optional(text, ch.language, is_w)); - break; - case ColorMode::ADD: - w.write(tt_encode_marked_optional(add_color(text), ch.language, is_w)); - break; - case ColorMode::STRIP: - w.write(tt_encode_marked_optional(strip_color(text), ch.language, is_w)); - break; + try { + switch (color_mode) { + case ColorMode::NONE: + w.write(tt_encode_marked_optional(text, ch.language, is_w)); + break; + case ColorMode::ADD: + w.write(tt_encode_marked_optional(add_color(text), ch.language, is_w)); + break; + case ColorMode::STRIP: + w.write(tt_encode_marked_optional(strip_color(text), ch.language, is_w)); + break; + } + } catch (const runtime_error& e) { + log_warning("Failed to encode message for %02hX command: %s", command, e.what()); + return; } if (is_w) { @@ -776,7 +781,13 @@ void send_message_box(shared_ptr c, const string& text) { } void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const string& message) { - string encoded = tt_encode_marked(add_color(message), ch.language, false); + string encoded; + try { + encoded = tt_encode_marked(add_color(message), ch.language, false); + } catch (const runtime_error& e) { + log_warning("Failed to encode message for EA command: %s", e.what()); + return; + } StringWriter w; w.put({frames}); w.write(encoded); @@ -921,7 +932,12 @@ void send_prepared_chat_message(shared_ptr l, uint32_t from_guild_card_nu } } -void send_chat_message(shared_ptr c, uint32_t from_guild_card_number, const string& from_name, const string& text, char private_flags) { +void send_chat_message( + shared_ptr c, + uint32_t from_guild_card_number, + const string& from_name, + const string& text, + char private_flags) { string prepared_data = prepare_chat_data( c->version(), c->language(),