diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81fbbe99d..7e453e45e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -161,6 +161,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_credits.cpp music.cpp music_manager.cpp + sanitize.cpp ) if(SRB2_CONFIG_ENABLE_WEBM_MOVIES) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index bef24f02a..c0170bd89 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -65,6 +65,7 @@ #include "k_zvote.h" #include "music.h" #include "k_bans.h" +#include "sanitize.h" // cl loading screen #include "v_video.h" @@ -1001,70 +1002,6 @@ static boolean CL_SendKey(void) return HSendPacket(servernode, false, 0, sizeof (clientkey_pak) ); } -static void -CopyCaretColors (char *p, const char *s, int n) -{ - char *t; - int m; - int c; - if (!n) - return; - while (( t = strchr(s, '^') )) - { - m = ( t - s ); - - if (m >= n) - { - memcpy(p, s, n); - return; - } - else - memcpy(p, s, m); - - p += m; - n -= m; - s += m; - - if (!n) - return; - - if (s[1]) - { - c = toupper(s[1]); - if (isdigit(c)) - c = 0x80 + ( c - '0' ); - else if (c >= 'A' && c <= 'F') - c = 0x80 + ( c - 'A' ); - else - c = 0; - - if (c) - { - *p++ = c; - n--; - - if (!n) - return; - } - else - { - if (n < 2) - break; - - memcpy(p, s, 2); - - p += 2; - n -= 2; - } - - s += 2; - } - else - break; - } - strncpy(p, s, n); -} - static void SV_SendServerInfo(INT32 node, tic_t servertime) { UINT8 *p; @@ -1111,8 +1048,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) (dedicated ? SV_DEDICATED : 0) ); - CopyCaretColors(netbuffer->u.serverinfo.servername, cv_servername.string, - MAXSERVERNAME); + D_ParseCarets(netbuffer->u.serverinfo.servername, cv_servername.string, MAXSERVERNAME); M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16); @@ -1265,8 +1201,8 @@ static boolean SV_SendServerConfig(INT32 node) memcpy(netbuffer->u.servercfg.server_context, server_context, 8); - strncpy(netbuffer->u.servercfg.server_name, cv_servername.string, MAXSERVERNAME); - strncpy(netbuffer->u.servercfg.server_contact, cv_server_contact.string, MAXSERVERCONTACT); + D_ParseCarets(netbuffer->u.servercfg.server_name, cv_servername.string, MAXSERVERNAME); + D_ParseCarets(netbuffer->u.servercfg.server_contact, cv_server_contact.string, MAXSERVERCONTACT); { const size_t len = sizeof (serverconfig_pak); @@ -3438,8 +3374,8 @@ static void SV_GenContext(void) server_context[i] = 'a'+(a-26); } - strlcpy(connectedservername, cv_servername.string, MAXSERVERNAME); - strlcpy(connectedservercontact, cv_server_contact.string, MAXSERVERCONTACT); + D_ParseCarets(connectedservername, cv_servername.string, MAXSERVERNAME); + D_ParseCarets(connectedservercontact, cv_server_contact.string, MAXSERVERCONTACT); } #endif // TESTERS @@ -4340,7 +4276,6 @@ void HandleSigfail(const char *string) */ static void HandleServerInfo(SINT8 node) { - char servername[MAXSERVERNAME]; // compute ping in ms const tic_t ticnow = I_GetTime(); const tic_t ticthen = (tic_t)LONG(netbuffer->u.serverinfo.time); @@ -4351,8 +4286,7 @@ static void HandleServerInfo(SINT8 node) [sizeof netbuffer->u.serverinfo.application - 1] = '\0'; netbuffer->u.serverinfo.gametypename [sizeof netbuffer->u.serverinfo.gametypename - 1] = '\0'; - memcpy(servername, netbuffer->u.serverinfo.servername, MAXSERVERNAME); - CopyCaretColors(netbuffer->u.serverinfo.servername, servername, MAXSERVERNAME); + D_SanitizeKeepColors(netbuffer->u.serverinfo.servername, netbuffer->u.serverinfo.servername, MAXSERVERNAME); // If we have cause to reject it, it's not worth observing. if ( @@ -4572,8 +4506,8 @@ static void HandlePacketFromAwayNode(SINT8 node) memcpy(server_context, netbuffer->u.servercfg.server_context, 8); - strlcpy(connectedservername, netbuffer->u.servercfg.server_name, MAXSERVERNAME); - strlcpy(connectedservercontact, netbuffer->u.servercfg.server_contact, MAXSERVERCONTACT); + D_SanitizeKeepColors(connectedservername, netbuffer->u.servercfg.server_name, MAXSERVERNAME); + D_SanitizeKeepColors(connectedservercontact, netbuffer->u.servercfg.server_contact, MAXSERVERCONTACT); } #ifdef HAVE_DISCORDRPC diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8a1102665..c147b1985 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -69,6 +69,7 @@ #include "i_time.h" #include "m_easing.h" +#include "sanitize.h" #ifdef PC_DOS #include // for snprintf @@ -532,12 +533,12 @@ static void M_DrawMenuTyping(void) V_DrawFill(x + 4, y + 4 + 5, 1, 8+6, 121); V_DrawFill(x + 5 + boxwidth - 8, y + 4 + 5, 1, 8+6, 121); - V_DrawString(x + 8, y + 12, 0, menutyping.cache); + INT32 textwidth = M_DrawCaretString(x + 8, y + 12, menutyping.cache, true); if (skullAnimCounter < 4 && menutyping.menutypingclose == false && menutyping.menutypingfade == (menutyping.keyboardtyping ? 9 : 18)) { - V_DrawCharacter(x + 8 + V_StringWidth(menutyping.cache, 0), y + 12 + 1, '_', false); + V_DrawCharacter(x + 8 + textwidth, y + 12 + 1, '_', false); } const INT32 buttonwidth = ((boxwidth + 1)/NUMVIRTUALKEYSINROW); @@ -4493,7 +4494,7 @@ box_found: V_DrawMenuString(x + (skullAnimCounter/5) + 7, y + 9, highlightflags, "\x1D"); } - V_DrawString(x + xoffs + 8, y + 9, 0, cv->string); + M_DrawCaretString(x + xoffs + 8, y + 9, cv->string, false); y += LINEHEIGHT; } diff --git a/src/sanitize.cpp b/src/sanitize.cpp new file mode 100644 index 000000000..fc03a1cfc --- /dev/null +++ b/src/sanitize.cpp @@ -0,0 +1,143 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "doomtype.h" +#include "sanitize.h" +#include "v_draw.hpp" + +using namespace srb2::sanitize; + +namespace +{ + +bool print_filter(char c) +{ + return !std::isprint(c); +} + +bool color_filter(char c) +{ + return print_filter(c) && (c & 0xF0) != 0x80; // color codes +} + +template +std::string& filter_out(std::string& out, const std::string_view& range, F filter) +{ + std::remove_copy_if( + range.begin(), + range.end(), + std::back_inserter(out), + filter + ); + return out; +}; + +int hexconv(int c) +{ + if (std::isdigit(c)) + return c - '0'; + + c = std::toupper(c); + if (c >= 'A' && c <= 'F') + return 10 + (c - 'A'); + + return -1; +} + +}; // namespace + +namespace srb2::sanitize +{ + +std::string sanitize(std::string_view in, SanitizeMode mode) +{ + std::string out; + return filter_out(out, in, [mode] + { + switch (mode) + { + default: + case SanitizeMode::kPrintable: + return print_filter; + case SanitizeMode::kKeepColors: + return color_filter; + } + }()); +} + +std::string parse_carets(std::string_view in, ParseMode mode) +{ + std::string out; + + using std::size_t; + for (;;) + { + size_t p = in.find('^'); + + // copy chars up until the caret + // but filter out codes outside of the ASCII range + filter_out(out, in.substr(0, p), print_filter); + + if (p == in.npos) + { + break; // end of input + } + + in.remove_prefix(p); + + // need two characters for caret code + // convert to color byte + if (int c; in.length() > 1 && (c = hexconv(in[1])) != -1) + { + out.push_back(0x80 | c); + } + + if (mode != ParseMode::kConsume) + { + // preserve caret code in output + filter_out(out, in.substr(0, 2), print_filter); + } + + if (in.length() < 2) + { + break; + } + + in.remove_prefix(2); + } + + return out; +} + +}; // namespace srb2 + +void D_SanitizeKeepColors(char *out, const char *in, size_t out_size) +{ + strlcpy(out, sanitize(in, SanitizeMode::kKeepColors).c_str(), out_size); +} + +void D_ParseCarets(char *out, const char *in, size_t out_size) +{ + strlcpy(out, parse_carets(in, ParseMode::kConsume).c_str(), out_size); +} + +INT32 M_DrawCaretString(INT32 x, INT32 y, const char *string, boolean preserve) +{ + using srb2::Draw; + Draw::TextElement text(parse_carets(string, preserve ? ParseMode::kPreserve : ParseMode::kConsume)); + text.font(Draw::Font::kConsole); + Draw(x, y).text(text); + return text.width(); +} diff --git a/src/sanitize.h b/src/sanitize.h new file mode 100644 index 000000000..f6a56d582 --- /dev/null +++ b/src/sanitize.h @@ -0,0 +1,55 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef sanitize_h +#define sanitize_h + +#include "doomtype.h" + +#ifdef __cplusplus +#include +#include + +namespace srb2::sanitize +{ + +enum class SanitizeMode +{ + kPrintable, + kKeepColors, +}; + +enum class ParseMode +{ + kConsume, + kPreserve, +}; + +// sanitizes string of all 0x80 codes +std::string sanitize(std::string_view in, SanitizeMode mode); + +// sanitizes string of all 0x80 codes then parses caret codes +std::string parse_carets(std::string_view in, ParseMode mode); + +}; // namespace srb2 + +extern "C" { +#endif + +void D_SanitizeKeepColors(char *out, const char *in, size_t out_size); // SanitizeMode::kKeepColors +void D_ParseCarets(char *out, const char *in, size_t out_size); // ParseMode::kConsume + +// returns string width in pixels +INT32 M_DrawCaretString(INT32 x, INT32 y, const char *string, boolean preserve); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // sanitize_h