diff --git a/src/command_type.h b/src/command_type.h index 12b44b1bbb..0d06214586 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -588,6 +588,7 @@ enum CommandFlags { CMD_SERVER_NS = 0x1000, ///< the command can only be initiated by the server (this is not executed in spectator mode) CMD_LOG_AUX = 0x2000, ///< the command should be logged in the auxiliary log instead of the main log CMD_P1_TILE = 0x4000, ///< use p1 for money text and error tile + CMD_STR_SEP = 0x8000, ///< the command's string may contain separator control characters }; DECLARE_ENUM_AS_BIT_SET(CommandFlags) diff --git a/src/network/network_command.cpp b/src/network/network_command.cpp index 078b8f16b3..51fc9619fc 100644 --- a/src/network/network_command.cpp +++ b/src/network/network_command.cpp @@ -330,7 +330,9 @@ const char *NetworkGameSocketHandler::ReceiveCommand(Packet *p, CommandPacket *c cp->tile = p->Recv_uint32(); cp->binary_length = p->Recv_uint32(); if (cp->binary_length == 0) { - p->Recv_string(cp->text, (!_network_server && GetCommandFlags(cp->cmd) & CMD_STR_CTRL) != 0 ? SVS_ALLOW_CONTROL_CODE | SVS_REPLACE_WITH_QUESTION_MARK : SVS_REPLACE_WITH_QUESTION_MARK); + StringValidationSettings settings = (!_network_server && GetCommandFlags(cp->cmd) & CMD_STR_CTRL) != 0 ? SVS_ALLOW_CONTROL_CODE | SVS_REPLACE_WITH_QUESTION_MARK : SVS_REPLACE_WITH_QUESTION_MARK; + if (GetCommandFlags(cp->cmd) & CMD_STR_SEP) settings |= SVS_ALLOW_SEPARATOR_CODE; + p->Recv_string(cp->text, settings); } else { if (!p->CanReadFromPacket(cp->binary_length + /* callback index */ 1)) return "invalid binary data length"; if (cp->binary_length > MAX_CMD_TEXT_LENGTH) return "over-size binary data length"; diff --git a/src/script/api/script_object.cpp b/src/script/api/script_object.cpp index 991ee8cf88..cf677a43ab 100644 --- a/src/script/api/script_object.cpp +++ b/src/script/api/script_object.cpp @@ -319,7 +319,7 @@ ScriptObject::ActiveInstance::~ActiveInstance() /* The string must be valid, i.e. not contain special codes. Since some * can be made with GSText, make sure the control codes are removed. */ text_validated = text; - ::StrMakeValidInPlace(text_validated, SVS_NONE); + ::StrMakeValidInPlace(text_validated, (GetCommandFlags(cmd) & CMD_STR_SEP) ? SVS_ALLOW_SEPARATOR_CODE : SVS_NONE); text = text_validated.c_str(); } diff --git a/src/string.cpp b/src/string.cpp index ad43c3e056..afb1ee7591 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -261,6 +261,8 @@ static void StrMakeValidInPlace(T &dst, const char *str, const char *last, Strin } while (--len != 0); } else if ((settings & SVS_ALLOW_NEWLINE) != 0 && c == '\n') { *dst++ = *str++; + } else if ((settings & SVS_ALLOW_SEPARATOR_CODE) != 0 && c == 0x1F) { + *dst++ = *str++; } else { if ((settings & SVS_ALLOW_NEWLINE) != 0 && c == '\r' && str[1] == '\n') { str += len; @@ -416,6 +418,21 @@ bool StrEndsWith(const std::string_view str, const std::string_view suffix) return str.compare(str.size() - suffix_len, suffix_len, suffix, 0, suffix_len) == 0; } +const char *StrConsumeToSeparator(std::string &result, const char *str) +{ + if (str == nullptr) { + result = ""; + return nullptr; + } + + const char *end = str; + while (*end != '\0' && *end != 0x1F) { + end++; + } + result.assign(str, end); + if (*end == 0x1F) return end + 1; + return nullptr; +} /** Scans the string for colour codes and strips them */ void str_strip_colours(char *str) diff --git a/src/string_func.h b/src/string_func.h index 244eb68cd8..27fdebbd21 100644 --- a/src/string_func.h +++ b/src/string_func.h @@ -59,6 +59,8 @@ void StrTrimInPlace(std::string &str); bool StrStartsWith(const std::string_view str, const std::string_view prefix); bool StrEndsWith(const std::string_view str, const std::string_view suffix); +const char *StrConsumeToSeparator(std::string &result, const char *str); + /** * Check if a string buffer is empty. * diff --git a/src/string_type.h b/src/string_type.h index 698dabe22f..ddd27a9d91 100644 --- a/src/string_type.h +++ b/src/string_type.h @@ -52,6 +52,7 @@ enum StringValidationSettings { SVS_REPLACE_WITH_QUESTION_MARK = 1 << 0, ///< Replace the unknown/bad bits with question marks. SVS_ALLOW_NEWLINE = 1 << 1, ///< Allow newlines. SVS_ALLOW_CONTROL_CODE = 1 << 2, ///< Allow the special control codes. + SVS_ALLOW_SEPARATOR_CODE = 1 << 3, ///< Allow separator control code (0x1F). }; DECLARE_ENUM_AS_BIT_SET(StringValidationSettings)