diff options
-rw-r--r-- | src/common/Configuration/Config.cpp | 10 | ||||
-rw-r--r-- | src/common/Utilities/Util.cpp | 8 | ||||
-rw-r--r-- | src/common/Utilities/Util.h | 3 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommand.h | 259 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp | 42 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandArgs.h | 151 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandHelpers.h | 36 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandTags.h | 112 | ||||
-rw-r--r-- | src/server/game/Chat/HyperlinkTags.cpp | 81 | ||||
-rw-r--r-- | src/server/game/Chat/Hyperlinks.cpp | 204 | ||||
-rw-r--r-- | src/server/game/Chat/Hyperlinks.h | 105 | ||||
-rw-r--r-- | src/server/scripts/Commands/cs_debug.cpp | 4 | ||||
-rw-r--r-- | src/server/scripts/Commands/cs_message.cpp | 36 |
13 files changed, 513 insertions, 538 deletions
diff --git a/src/common/Configuration/Config.cpp b/src/common/Configuration/Config.cpp index d9af7ae1187..fa0e546ccc3 100644 --- a/src/common/Configuration/Config.cpp +++ b/src/common/Configuration/Config.cpp @@ -137,15 +137,7 @@ bool ConfigMgr::GetBoolDefault(std::string const& name, bool def, bool quiet) co { std::string val = GetValueDefault(name, std::string(def ? "1" : "0"), quiet); val.erase(std::remove(val.begin(), val.end(), '"'), val.end()); - Optional<bool> boolVal = StringToBool(val); - if (boolVal) - return *boolVal; - else - { - TC_LOG_ERROR("server.loading", "Bad value defined for name %s in config file %s, going to use '%s' instead", - name.c_str(), _filename.c_str(), def ? "true" : "false"); - return def; - } + return StringToBool(val); } int ConfigMgr::GetIntDefault(std::string const& name, int def, bool quiet) const diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp index cb70cc114d3..da086c6b6cc 100644 --- a/src/common/Utilities/Util.cpp +++ b/src/common/Utilities/Util.cpp @@ -651,13 +651,9 @@ void Trinity::Impl::HexStrToByteArray(std::string_view str, uint8* out, size_t o } } -Optional<bool> StringToBool(std::string_view str) +bool StringToBool(std::string_view str) { - if (str == "1" || StringEqualI(str, "y") || StringEqualI(str, "on") || StringEqualI(str, "yes") || StringEqualI(str, "true")) - return true; - if (str == "0" || StringEqualI(str, "n") || StringEqualI(str, "off") || StringEqualI(str, "no") || StringEqualI(str, "false")) - return false; - return std::nullopt; + return ((str == "1") || StringEqualI(str, "true") || StringEqualI(str, "yes")); } bool StringEqualI(std::string_view str1, std::string_view str2) diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h index 8c97f99fe63..cd8765b309f 100644 --- a/src/common/Utilities/Util.h +++ b/src/common/Utilities/Util.h @@ -20,7 +20,6 @@ #include "Define.h" #include "Errors.h" -#include "Optional.h" #include <array> #include <string> @@ -346,7 +345,7 @@ inline std::vector<uint8> HexStrToByteVector(std::string_view str, bool reverse return buf; } -TC_COMMON_API Optional<bool> StringToBool(std::string_view str); +TC_COMMON_API bool StringToBool(std::string_view str); TC_COMMON_API bool StringEqualI(std::string_view str1, std::string_view str2); TC_COMMON_API bool StringStartsWith(std::string_view haystack, std::string_view needle); diff --git a/src/server/game/Chat/ChatCommands/ChatCommand.h b/src/server/game/Chat/ChatCommands/ChatCommand.h index 65584ce7430..c54f7f52cb5 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommand.h +++ b/src/server/game/Chat/ChatCommands/ChatCommand.h @@ -33,106 +33,199 @@ class ChatHandler; class CommandArgs; -namespace Trinity::Impl::ChatCommands +template <typename T> +struct CommandArgsConsumerSingle { - template <typename T> - struct SingleConsumer + using arginfo = Trinity::ChatCommands::ArgInfo<T>; + static char const* TryConsumeTo(T& val, char const* args) { - static Optional<std::string_view> TryConsumeTo(T& val, std::string_view args) + return arginfo::TryConsume(val, args); + } +}; + +struct CommandArgsVariantConsumer +{ + template <typename V, typename T1, typename... Ts> + static char const* TryConsumeTo(V& val, char const* args) + { + T1 v; + if (char const* next = CommandArgsConsumerSingle<T1>::TryConsumeTo(v, args)) { - return ArgInfo<T>::TryConsume(val, args); + val = std::move(v); + return next; } - }; + else if constexpr (sizeof...(Ts) > 0) + return TryConsumeTo<V, Ts...>(val, args); + else + return nullptr; + } +}; - template <typename... Ts> - struct SingleConsumer<Trinity::ChatCommands::Variant<Ts...>> +template <typename... Ts> +struct CommandArgsConsumerSingle<Trinity::ChatCommands::Variant<Ts...>> +{ + static char const* TryConsumeTo(Trinity::ChatCommands::Variant<Ts...>& val, char const* args) { - using V = std::variant<Ts...>; - static constexpr size_t N = std::variant_size_v<V>; + return CommandArgsVariantConsumer::TryConsumeTo<Trinity::ChatCommands::Variant<Ts...>, Ts...>(val, args); + } +}; - template <size_t I> - static Optional<std::string_view> TryAtIndex(Trinity::ChatCommands::Variant<Ts...>& val, [[maybe_unused]] std::string_view args) - { - if constexpr (I < N) - { - if (Optional<std::string_view> next = ArgInfo<std::variant_alternative_t<I, V>>::TryConsume(val.template emplace<I>(), args)) - return next; - else - return TryAtIndex<I+1>(val, args); - } - else - return std::nullopt; - } +template <typename T> +struct CommandArgsConsumerSingle<std::vector<T>> +{ + static char const* TryConsumeTo(std::vector<T>& val, char const* args) + { + char const* last; + val.clear(); + + do val.emplace_back(); + while ((args = CommandArgsConsumerSingle<T>::TryConsumeTo(val.back(), (last = args)))); + + val.pop_back(); + return last; + } +}; - static Optional<std::string_view> TryConsumeTo(Trinity::ChatCommands::Variant<Ts...>& val, std::string_view args) +template <typename T, std::size_t C> +struct CommandArgsConsumerSingle<std::array<T, C>> +{ + static char const* TryConsumeTo(std::array<T, C>& val, char const* args) + { + for (T& t : val) { - return TryAtIndex<0>(val, args); + args = CommandArgsConsumerSingle<T>::TryConsumeTo(t, args); + + if (!args) + return nullptr; } - }; - - /* - for backwards compatibility, consumes the rest of the string - new code should use the Tail/WTail tags defined in ChatCommandTags - */ - template <> - struct SingleConsumer<char const*> + + return args; + } +}; + +template <> +struct CommandArgsConsumerSingle<CommandArgs*> +{ + static char const* TryConsumeTo(CommandArgs*&, char const* args) { return args; } +}; + +template <> +struct CommandArgsConsumerSingle<char const*> +{ + static char const* TryConsumeTo(char const*&, char const* args) { return args; } +}; + +template <typename T, size_t offset> +struct CommandArgsConsumerNext; + +template <typename Tuple, typename NextType, size_t offset> +struct CommandArgsConsumerMulti +{ + static char const* TryConsumeTo(Tuple& tuple, char const* args) { - static Optional<std::string_view> TryConsumeTo(char const*& arg, std::string_view args) { arg = args.data(); return std::string_view(); } - }; + if (char const* next = CommandArgsConsumerSingle<NextType>::TryConsumeTo(std::get<offset>(tuple), args)) + return CommandArgsConsumerNext<Tuple, offset+1>::GoNext(tuple, next); + else + return nullptr; + } +}; + +template <typename Tuple, typename NestedNextType, size_t offset> +struct CommandArgsConsumerMulti<Tuple, Optional<NestedNextType>, offset> +{ + static char const* TryConsumeTo(Tuple& tuple, char const* args) + { + // try with the argument + auto& myArg = std::get<offset>(tuple); + myArg.emplace(); + if (char const* next = CommandArgsConsumerSingle<NestedNextType>::TryConsumeTo(myArg.value(), args)) + if ((next = CommandArgsConsumerNext<Tuple, offset+1>::GoNext(tuple, next))) + return next; + // try again omitting the argument + myArg = std::nullopt; + if (char const* next = CommandArgsConsumerNext<Tuple, offset+1>::GoNext(tuple, args)) + return next; + return nullptr; + } +}; +template <size_t offset, typename... Ts> +struct CommandArgsConsumerNext<std::tuple<Ts...>, offset> +{ + using tuple_type = std::tuple<Ts...>; - // forward declaration - // ConsumeFromOffset contains the bounds check for offset, then hands off to MultiConsumer - // the call stack is MultiConsumer -> ConsumeFromOffset -> MultiConsumer -> ConsumeFromOffset etc - // MultiConsumer calls SingleConsumer in each iteration - template <typename Tuple, size_t offset> - Optional<std::string_view> ConsumeFromOffset(Tuple&, std::string_view args); + template <bool C = (offset < sizeof...(Ts))> + static std::enable_if_t<C, char const*> GoNext(tuple_type& tuple, char const* args) + { + return CommandArgsConsumerMulti<tuple_type, std::tuple_element_t<offset, tuple_type>, offset>::TryConsumeTo(tuple, args); + } - template <typename Tuple, typename NextType, size_t offset> - struct MultiConsumer + template <bool C = (offset < sizeof...(Ts))> + static std::enable_if_t<!C, char const*> GoNext(tuple_type&, char const* args) { - static Optional<std::string_view> TryConsumeTo(Tuple& tuple, std::string_view args) + return args; + } +}; + +class TC_GAME_API CommandArgs +{ + public: + CommandArgs(char const* args) : _original(args), _args(args) {} + + template <typename T1, typename T2, typename... Ts> + auto TryConsume() { - if (Optional<std::string_view> next = SingleConsumer<NextType>::TryConsumeTo(std::get<offset>(tuple), args)) - return ConsumeFromOffset<Tuple, offset + 1>(tuple, *next); + Optional<std::tuple<advstd::remove_cvref_t<T1>, advstd::remove_cvref_t<T2>, advstd::remove_cvref_t<Ts>...>> rv; + rv.emplace(); + if (!TryConsumeToTuple<0>(rv.value())) + rv = std::nullopt; + return rv; + } + + template <typename T1> + auto TryConsume() + { + using T = advstd::remove_cvref_t<T1>; + Optional<T> rv; + rv.emplace(); + if (char const* next = CommandArgsConsumerSingle<T>::TryConsumeTo(rv.value(), _args)) + _args = next; else - return std::nullopt; + rv = std::nullopt; + return rv; } - }; - template <typename Tuple, typename NestedNextType, size_t offset> - struct MultiConsumer<Tuple, Optional<NestedNextType>, offset> - { - static Optional<std::string_view> TryConsumeTo(Tuple& tuple, std::string_view args) + template <size_t offset = 0, typename T> + bool TryConsumeToTuple(T& tuple) { - // try with the argument - auto& myArg = std::get<offset>(tuple); - myArg.emplace(); - if (Optional<std::string_view> next = SingleConsumer<NestedNextType>::TryConsumeTo(myArg.value(), args)) - if ((next = ConsumeFromOffset<Tuple, offset + 1>(tuple, *next))) - return next; - // try again omitting the argument - myArg = std::nullopt; - return ConsumeFromOffset<Tuple, offset + 1>(tuple, args); + if (char const* next = CommandArgsConsumerNext<T, offset>::GoNext(tuple, _args)) + { + _args = next; + return true; + } + else + return false; } - }; - template <typename Tuple, size_t offset> - Optional<std::string_view> ConsumeFromOffset(Tuple& tuple, std::string_view args) - { - if constexpr (offset < std::tuple_size_v<Tuple>) - return MultiConsumer<Tuple, std::tuple_element_t<offset, Tuple>, offset>::TryConsumeTo(tuple, args); - else if (!args.empty()) /* the entire string must be consumed */ - return std::nullopt; - else - return args; - } + void Reset() { _args = _original; } + + char const* GetFullArgs() const { return _original; } + char const* GetRemainingArgs() const { return _args; } + + bool IsEmpty() const { return !!*_args; } + explicit operator bool() const { return IsEmpty(); } - template <typename T> struct HandlerToTuple { static_assert(!std::is_same_v<T, T>, "Invalid command handler signature"); }; - template <typename... Ts> struct HandlerToTuple<bool(*)(ChatHandler*, Ts...)> { using type = std::tuple<ChatHandler*, advstd::remove_cvref_t<Ts>...>; }; - template <typename T> using TupleType = typename HandlerToTuple<T>::type; -} + private: + char const* const _original; + char const* _args; +}; +template <typename T> struct ChatCommandHandlerToTuple { static_assert(!std::is_same_v<T,T>, "Invalid command handler signature"); }; +template <typename... Ts> struct ChatCommandHandlerToTuple<bool(*)(ChatHandler*, Ts...)> { using type = std::tuple<ChatHandler*, advstd::remove_cvref_t<Ts>...>; }; + +template <typename T> struct ChatCommandStoreLastArg { static void store(T&, CommandArgs&) {} }; +template <> struct ChatCommandStoreLastArg<char const*> { static void store(char const*& arg, CommandArgs& args) { arg = args.GetRemainingArgs(); } }; +template <> struct ChatCommandStoreLastArg<CommandArgs*> { static void store(CommandArgs*& arg, CommandArgs& args) { arg = &args; } }; class TC_GAME_API ChatCommand { @@ -145,12 +238,18 @@ class TC_GAME_API ChatCommand { _wrapper = [](void* handler, ChatHandler* chatHandler, char const* argsStr) { - using Tuple = Trinity::Impl::ChatCommands::TupleType<TypedHandler>; + using tuple_type = typename ChatCommandHandlerToTuple<TypedHandler>::type; - Tuple arguments; + tuple_type arguments; std::get<0>(arguments) = chatHandler; - if (Trinity::Impl::ChatCommands::ConsumeFromOffset<Tuple, 1>(arguments, argsStr)) + + CommandArgs args(argsStr); + if (args.TryConsumeToTuple<1>(arguments)) + { + auto& last = std::get<std::tuple_size_v<tuple_type>-1>(arguments); + ChatCommandStoreLastArg<advstd::remove_cvref_t<decltype(last)>>::store(last, args); return std::apply(reinterpret_cast<TypedHandler>(handler), std::move(arguments)); + } else return false; }; @@ -173,7 +272,7 @@ class TC_GAME_API ChatCommand bool HasHandler() const { return !!_handler; } char const* Name; - uint32 Permission; + uint32 Permission; // function pointer required correct align (use uint32) bool AllowConsole; std::string Help; std::vector<ChatCommand> ChildCommands; diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp index bd3a5d32459..22eabb01613 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp @@ -20,7 +20,6 @@ #include "ChatCommand.h" #include "ObjectMgr.h" #include "SpellMgr.h" -#include "Util.h" using namespace Trinity::ChatCommands; @@ -30,13 +29,12 @@ struct AchievementVisitor value_type operator()(Hyperlink<achievement> achData) const { return achData->Achievement; } value_type operator()(uint32 achId) const { return sAchievementMgr->GetAchievement(achId); } }; -Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, std::string_view args) +char const* Trinity::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, char const* args) { Variant<Hyperlink<achievement>, uint32> val; - Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); - if (next) + if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args))) data = val.visit(AchievementVisitor()); - return next; + return args; } struct GameTeleVisitor @@ -45,13 +43,12 @@ struct GameTeleVisitor value_type operator()(Hyperlink<tele> tele) const { return sObjectMgr->GetGameTele(tele); } value_type operator()(std::string const& tele) const { return sObjectMgr->GetGameTele(tele); } }; -Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<GameTele const*>::TryConsume(GameTele const*& data, std::string_view args) +char const* Trinity::ChatCommands::ArgInfo<GameTele const*>::TryConsume(GameTele const*& data, char const* args) { Variant<Hyperlink<tele>, std::string> val; - Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); - if (next) + if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args))) data = val.visit(GameTeleVisitor()); - return next; + return args; } struct SpellInfoVisitor @@ -68,25 +65,26 @@ struct SpellInfoVisitor value_type operator()(uint32 spellId) const { return sSpellMgr->GetSpellInfo(spellId); } }; -Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<SpellInfo const*>::TryConsume(SpellInfo const*& data, std::string_view args) +char const* Trinity::ChatCommands::ArgInfo<SpellInfo const*>::TryConsume(SpellInfo const*& data, char const* args) { Variant<Hyperlink<enchant>, Hyperlink<glyph>, Hyperlink<spell>, Hyperlink<talent>, Hyperlink<trade>, uint32> val; - Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args); - if (next) + if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args))) data = val.visit(SpellInfoVisitor()); - return next; + return args; } -Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<bool>::TryConsume(bool& data, std::string_view args) +char const* Trinity::ChatCommands::ArgInfo<bool>::TryConsume(bool& data, char const* args) { - std::string_view str; - if (Optional<std::string_view> next = SingleConsumer<std::string_view>::TryConsumeTo(str, args)) + std::string val; + if ((args = CommandArgsConsumerSingle<std::string>::TryConsumeTo(val, args))) { - if (Optional<bool> strBool = StringToBool(str)) - { - data = *strBool; - return next; - } + strToLower(val); + if (val == "on" || val == "yes" || val == "true" || val == "1" || val == "y") + data = true; + else if (val == "off" || val == "no" || val == "false" || val == "0" || val == "n") + data = false; + else + return nullptr; } - return std::nullopt; + return args; } diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h index 4293822699f..ca94835272f 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h @@ -29,15 +29,17 @@ struct GameTele; -namespace Trinity::Impl::ChatCommands +namespace Trinity +{ +namespace ChatCommands { /************************** ARGUMENT HANDLERS *******************************************\ |* Define how to extract contents of a certain requested type from a string *| |* Must implement the following: *| -|* - TryConsume: T&, std::string_view -> Optional<std::string_view> *| -|* returns nullopt if no match, otherwise tail of argument string *| -|* - if nullopt is returned, state of T& is indeterminate *| +|* - TryConsume: T&, char const* -> char const* *| +|* returns nullptr if no match, otherwise pointer to first character of next token *| +|* - if nullptr is returned, state of T& is indeterminate *| |* - otherwise, T& should be initialized to the intended return value *| |* *| \****************************************************************************************/ @@ -48,27 +50,26 @@ struct ArgInfo { static_assert(!std::is_same_v<T,T>, "Invalid command parameter template <typename T> struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T>>> { - static Optional<std::string_view> TryConsume(T& val, std::string_view args) + static char const* TryConsume(T& val, char const* args) { - auto [token, tail] = tokenize(args); - if (token.empty()) - return std::nullopt; + char const* next = args; + std::string_view token(args, Trinity::Impl::ChatCommands::tokenize(next)); if (!token.length()) return nullptr; std::from_chars_result result; - if (StringEqualI(token.substr(0,2), "0x")) + if (StringStartsWith(token, "0x")) result = std::from_chars(token.data() + 2, token.data() + token.length(), val, 16); - else if (StringEqualI(token.substr(0,2), "0b")) + else if (StringStartsWith(token, "0b")) result = std::from_chars(token.data() + 2, token.data() + token.length(), val, 2); else result = std::from_chars(token.data(), token.data() + token.length(), val, 10); if ((token.data() + token.length()) != result.ptr) - return std::nullopt; + return nullptr; - return tail; + return next; } }; @@ -76,26 +77,20 @@ struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T>>> template <typename T> struct ArgInfo<T, std::enable_if_t<std::is_floating_point_v<T>>> { - static Optional<std::string_view> TryConsume(T& val, std::string_view args) + static char const* TryConsume(T& val, char const* args) { - auto [token, tail] = tokenize(args); - if (token.empty()) - return std::nullopt; - + char const* next = args; + std::string token(args, Trinity::Impl::ChatCommands::tokenize(next)); try { // @todo replace this once libc++ supports double args to from_chars for required minimum size_t processedChars = 0; - val = std::stold(std::string(token), &processedChars); + val = std::stold(token, &processedChars); if (processedChars != token.length()) - return std::nullopt; + return nullptr; } - catch (...) { return std::nullopt; } - - if (std::isfinite(val)) - return tail; - else - return std::nullopt; + catch (...) { return nullptr; } + return std::isfinite(val) ? next : nullptr; } }; @@ -103,13 +98,16 @@ struct ArgInfo<T, std::enable_if_t<std::is_floating_point_v<T>>> template <> struct ArgInfo<std::string_view, void> { - static Optional<std::string_view> TryConsume(std::string_view& val, std::string_view args) + static char const* TryConsume(std::string_view& val, char const* args) { - auto [token, next] = tokenize(args); - if (token.empty()) - return std::nullopt; - val = token; - return next; + char const* next = args; + if (size_t len = Trinity::Impl::ChatCommands::tokenize(next)) + { + val = std::string_view(args, len); + return next; + } + else + return nullptr; } }; @@ -117,13 +115,13 @@ struct ArgInfo<std::string_view, void> template <> struct ArgInfo<std::string, void> { - static Optional<std::string_view> TryConsume(std::string& val, std::string_view args) + static char const* TryConsume(std::string& val, char const* args) { std::string_view view; - Optional<std::string_view> next = ArgInfo<std::string_view>::TryConsume(view, args); - if (next) + args = ArgInfo<std::string_view>::TryConsume(view, args); + if (args) val.assign(view); - return next; + return args; } }; @@ -131,15 +129,18 @@ struct ArgInfo<std::string, void> template <> struct ArgInfo<std::wstring, void> { - static Optional<std::string_view> TryConsume(std::wstring& val, std::string_view args) + static char const* TryConsume(std::wstring& val, char const* args) { std::string_view utf8view; - Optional<std::string_view> next = ArgInfo<std::string_view>::TryConsume(utf8view, args); + char const* ret = ArgInfo<std::string_view>::TryConsume(utf8view, args); - if (next && Utf8toWStr(utf8view, val)) - return next; - else - return std::nullopt; + if (!ret) + return nullptr; + + if (!Utf8toWStr(utf8view, val)) + return nullptr; + + return ret; } }; @@ -197,31 +198,32 @@ struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>> return nullptr; } - static Optional<std::string_view> TryConsume(T& val, std::string_view args) + static char const* TryConsume(T& val, char const* args) { std::string strVal; - Optional<std::string_view> next = ArgInfo<std::string>::TryConsume(strVal, args); + char const* ret = ArgInfo<std::string>::TryConsume(strVal, args); + + if (!ret) + return nullptr; - if (next) + if (T const* tmpVal = Match(strVal)) { - if (T const* match = Match(strVal)) - { - val = *match; - return next; - } + val = *tmpVal; + return ret; } // Value not found. Try to parse arg as underlying type and cast it to enum type using U = std::underlying_type_t<T>; U uVal = 0; - next = ArgInfo<U>::TryConsume(uVal, args); - if (next && EnumUtils::IsValid<T>(uVal)) + ret = ArgInfo<U>::TryConsume(uVal, args); + if (ret) { val = static_cast<T>(uVal); - return next; + if (!EnumUtils::IsValid(val)) + return nullptr; } - return std::nullopt; + return ret; } }; @@ -229,70 +231,41 @@ struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>> template <typename T> struct ArgInfo<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> { - static Optional<std::string_view> TryConsume(T& tag, std::string_view args) + static char const* TryConsume(T& tag, char const* args) { return tag.TryConsume(args); } }; -template <typename T> -struct ArgInfo<std::vector<T>, void> -{ - static Optional<std::string_view> TryConsume(std::vector<T>& val, std::string_view args) - { - val.clear(); - Optional<std::string_view> next = ArgInfo<T>::TryConsume(val.emplace_back(), args); - - if (!next) - return std::nullopt; - - while (Optional<std::string_view> next2 = ArgInfo<T>::TryConsume(val.emplace_back(), *next)) - next = next2; - return next; - } -}; - -template <typename T, size_t N> -struct ArgInfo<std::array<T, N>, void> -{ - static Optional<std::string_view> TryConsume(std::array<T, N>& val, std::string_view args) - { - Optional<std::string_view> next = args; - for (T& t : val) - if (!(next = ArgInfo<T>::TryConsume(t, *next))) - return std::nullopt; - return next; - } -}; - // AchievementEntry* from numeric id or link template <> struct TC_GAME_API ArgInfo<AchievementEntry const*> { - static Optional<std::string_view> TryConsume(AchievementEntry const*&, std::string_view); + static char const* TryConsume(AchievementEntry const*&, char const*); }; // GameTele* from string name or link template <> struct TC_GAME_API ArgInfo<GameTele const*> { - static Optional<std::string_view> TryConsume(GameTele const*&, std::string_view); + static char const* TryConsume(GameTele const*&, char const*); }; // SpellInfo const* from spell id or link template <> struct TC_GAME_API ArgInfo<SpellInfo const*> { - static Optional<std::string_view> TryConsume(SpellInfo const*&, std::string_view); + static char const* TryConsume(SpellInfo const*&, char const*); }; -// bool from string +// bool from 1/0 or on/off template <> struct TC_GAME_API ArgInfo<bool> { - static Optional<std::string_view> TryConsume(bool&, std::string_view); + static char const* TryConsume(bool&, char const*); }; } +} #endif diff --git a/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h index c6cf4f8ad94..e22823e5d35 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h @@ -18,15 +18,10 @@ #ifndef TRINITY_CHATCOMMANDHELPERS_H #define TRINITY_CHATCOMMANDHELPERS_H -#include <string_view> #include <type_traits> -namespace Trinity::Impl::ChatCommands +namespace Trinity::ChatCommands { - /***************** HELPERS *************************\ - |* These really aren't for outside use... *| - \***************************************************/ - static constexpr char COMMAND_DELIMITER = ' '; template <typename T, typename = void> @@ -37,26 +32,19 @@ namespace Trinity::Impl::ChatCommands template <typename T> using tag_base_t = typename tag_base<T>::type; +} - struct TokenizeResult { - explicit operator bool() { return !token.empty(); } - std::string_view token; - std::string_view tail; - }; - - inline TokenizeResult tokenize(std::string_view args) +namespace Trinity::Impl::ChatCommands +{ + /***************** HELPERS *************************\ + |* These really aren't for outside use... *| + \***************************************************/ + inline std::size_t tokenize(char const*& end) { - TokenizeResult result; - if (size_t delimPos = args.find(COMMAND_DELIMITER); delimPos != std::string_view::npos) - { - result.token = args.substr(0, delimPos); - if (size_t tailPos = args.find_first_not_of(COMMAND_DELIMITER, delimPos); tailPos != std::string_view::npos) - result.tail = args.substr(tailPos); - } - else - result.token = args; - - return result; + std::size_t len = 0; + for (; *end && *end != Trinity::ChatCommands::COMMAND_DELIMITER; ++end, ++len); + for (; *end && *end == Trinity::ChatCommands::COMMAND_DELIMITER; ++end); + return len; } template <typename T, typename... Ts> diff --git a/src/server/game/Chat/ChatCommands/ChatCommandTags.h b/src/server/game/Chat/ChatCommands/ChatCommandTags.h index 6e150b62e8e..22db765f102 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandTags.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandTags.h @@ -22,95 +22,59 @@ #include "ChatCommandHelpers.h" #include "Hyperlinks.h" #include "Optional.h" -#include "Util.h" #include <cmath> #include <cstring> #include <iostream> #include <string> -#include <string_view> #include <tuple> #include <type_traits> #include <utility> #include <variant> -namespace Trinity::Impl::ChatCommands -{ - struct ContainerTag {}; - template <typename T> - struct tag_base<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> - { - using type = typename T::value_type; - }; -} - namespace Trinity::ChatCommands { /************************** CONTAINER TAGS **********************************************\ |* Simple holder classes to differentiate between extraction methods *| - |* Must inherit from Trinity::Impl::ChatCommands::ContainerTag *| + |* Should inherit from ContainerTag for template identification *| |* Must implement the following: *| - |* - TryConsume: std::string_view -> Optional<std::string_view> *| - |* returns nullopt if no match, otherwise the tail of the provided argument string *| + |* - TryConsume: char const* -> char const* *| + |* returns nullptr if no match, otherwise pointer to first character of next token *| |* - typedef value_type of type that is contained within the tag *| |* - cast operator to value_type *| |* *| \****************************************************************************************/ + struct ContainerTag {}; + template <typename T> + struct tag_base<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> + { + using type = typename T::value_type; + }; template <char c1, char... chars> - struct ExactSequence : Trinity::Impl::ChatCommands::ContainerTag + struct ExactSequence : public ContainerTag { using value_type = void; - static constexpr size_t N = (sizeof...(chars) + 1); - - static bool Match(char const* pos) - { - if (*(pos++) != c1) - return false; - else if constexpr (sizeof...(chars) > 0) - return ExactSequence<chars...>::Match(pos); - else - return true; - } - - Optional<std::string_view> TryConsume(std::string_view args) const + static char const* _TryConsume(char const* pos) { - if ((N <= args.length()) && ExactSequence::Match(args.data())) + if (*(pos++) == c1) { - auto [remainingToken, tail] = Trinity::Impl::ChatCommands::tokenize(args.substr(N)); - if (remainingToken.empty()) // if this is not empty, then we did not consume the full token - return tail; + if constexpr (sizeof...(chars) > 0) + return ExactSequence<chars...>::_TryConsume(pos); + else if (Trinity::Impl::ChatCommands::tokenize(pos)) /* we did not consume the entire token */ + return nullptr; + else + return pos; } - return std::nullopt; - } - }; - - struct Tail : std::string_view, Trinity::Impl::ChatCommands::ContainerTag - { - using value_type = std::string_view; - - Optional<std::string_view> TryConsume(std::string_view args) - { - std::string_view::operator=(args); - return std::string_view(); - } - }; - - struct WTail : std::wstring, Trinity::Impl::ChatCommands::ContainerTag - { - using value_type = std::wstring; - - Optional<std::string_view> TryConsume(std::string_view args) - { - if (Utf8toWStr(args, *this)) - return std::string_view(); else - return std::nullopt; + return nullptr; } + + char const* TryConsume(char const* pos) const { return ExactSequence::_TryConsume(pos); } }; template <typename linktag> - struct Hyperlink : Trinity::Impl::ChatCommands::ContainerTag + struct Hyperlink : public ContainerTag { using value_type = typename linktag::value_type; using storage_type = advstd::remove_cvref_t<value_type>; @@ -120,27 +84,29 @@ namespace Trinity::ChatCommands value_type operator*() const { return val; } storage_type const* operator->() const { return &val; } - Optional<std::string_view> TryConsume(std::string_view args) + char const* TryConsume(char const* pos) { - Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseSingleHyperlink(args); + Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseHyperlink(pos); // invalid hyperlinks cannot be consumed if (!info) - return std::nullopt; + return nullptr; // check if we got the right tag - if (info.tag != linktag::tag()) - return std::nullopt; + if (info.tag.second != strlen(linktag::tag())) + return nullptr; + if (strncmp(info.tag.first, linktag::tag(), strlen(linktag::tag())) != 0) + return nullptr; // store value - if (!linktag::StoreTo(val, info.data)) - return std::nullopt; + if (!linktag::StoreTo(val, info.data.first, info.data.second)) + return nullptr; - // finally, skip any potential delimiters - auto [token, next] = Trinity::Impl::ChatCommands::tokenize(info.tail); - if (token.empty()) /* empty token = first character is delimiter, skip past it */ - return next; - else - return info.tail; + // finally, skip to end of token + pos = info.next; + Trinity::Impl::ChatCommands::tokenize(pos); + + // return final pos + return pos; } private: @@ -171,8 +137,8 @@ namespace Trinity::ChatCommands { using base = std::variant<T1, Ts...>; - using first_type = Trinity::Impl::ChatCommands::tag_base_t<T1>; - static constexpr bool have_operators = Trinity::Impl::ChatCommands::are_all_assignable<first_type, Trinity::Impl::ChatCommands::tag_base_t<Ts>...>::value; + using first_type = tag_base_t<T1>; + static constexpr bool have_operators = Trinity::Impl::ChatCommands::are_all_assignable<first_type, tag_base_t<Ts>...>::value; template <bool C = have_operators> std::enable_if_t<C, first_type> operator*() const diff --git a/src/server/game/Chat/HyperlinkTags.cpp b/src/server/game/Chat/HyperlinkTags.cpp index f9d2bb4c645..a933504b866 100644 --- a/src/server/game/Chat/HyperlinkTags.cpp +++ b/src/server/game/Chat/HyperlinkTags.cpp @@ -26,38 +26,37 @@ static constexpr char HYPERLINK_DATA_DELIMITER = ':'; class HyperlinkDataTokenizer { public: - HyperlinkDataTokenizer(std::string_view str) : _str(str) {} - - template <typename T> - bool TryConsumeTo(T& val) - { - if (IsEmpty()) - return false; - - if (size_t off = _str.find(HYPERLINK_DATA_DELIMITER); off != std::string_view::npos) - { - if (!Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, _str.substr(0, off))) - return false; - _str = _str.substr(off+1); - } - else - { - if (!Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, _str)) - return false; - _str = std::string_view(); - } - return true; - } - - bool IsEmpty() { return _str.empty(); } + HyperlinkDataTokenizer(char const* pos, size_t len) : _pos(pos), _len(len), _empty(false) {} + + template <typename T> + bool TryConsumeTo(T& val) + { + if (_empty) + return false; + + char const* firstPos = _pos; + size_t thisLen = 0; + // find next delimiter + for (; _len && *_pos != HYPERLINK_DATA_DELIMITER; --_len, ++_pos, ++thisLen); + if (_len) + --_len, ++_pos; // skip the delimiter + else + _empty = true; + + return Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, firstPos, thisLen); + } + + bool IsEmpty() { return _empty; } private: - std::string_view _str; + char const* _pos; + size_t _len; + bool _empty; }; -bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& val, std::string_view text) +bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& val, char const* pos, size_t len) { - HyperlinkDataTokenizer t(text); + HyperlinkDataTokenizer t(pos, len); uint32 achievementId; if (!t.TryConsumeTo(achievementId)) return false; @@ -67,18 +66,18 @@ bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& va t.TryConsumeTo(val.Criteria[1]) && t.TryConsumeTo(val.Criteria[2]) && t.TryConsumeTo(val.Criteria[3]) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::enchant::StoreTo(SpellInfo const*& val, std::string_view text) +bool Trinity::Hyperlinks::LinkTags::enchant::StoreTo(SpellInfo const*& val, char const* pos, size_t len) { - HyperlinkDataTokenizer t(text); + HyperlinkDataTokenizer t(pos, len); uint32 spellId; if (!(t.TryConsumeTo(spellId) && t.IsEmpty())) return false; return (val = sSpellMgr->GetSpellInfo(spellId)) && val->HasAttribute(SPELL_ATTR0_TRADESPELL); } -bool Trinity::Hyperlinks::LinkTags::glyph::StoreTo(GlyphLinkData& val, std::string_view text) +bool Trinity::Hyperlinks::LinkTags::glyph::StoreTo(GlyphLinkData& val, char const* pos, size_t len) { - HyperlinkDataTokenizer t(text); + HyperlinkDataTokenizer t(pos, len); uint32 slot, prop; if (!(t.TryConsumeTo(slot) && t.TryConsumeTo(prop) && t.IsEmpty())) return false; @@ -89,9 +88,9 @@ bool Trinity::Hyperlinks::LinkTags::glyph::StoreTo(GlyphLinkData& val, std::stri return true; } -bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, std::string_view text) +bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, char const* pos, size_t len) { - HyperlinkDataTokenizer t(text); + HyperlinkDataTokenizer t(pos, len); uint32 itemId, dummy; if (!t.TryConsumeTo(itemId)) return false; @@ -101,27 +100,27 @@ bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, std::string t.TryConsumeTo(val.RenderLevel) && t.IsEmpty() && !dummy; } -bool Trinity::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, std::string_view text) +bool Trinity::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, char const* pos, size_t len) { - HyperlinkDataTokenizer t(text); + HyperlinkDataTokenizer t(pos, len); uint32 questId; if (!t.TryConsumeTo(questId)) return false; return (val.Quest = sObjectMgr->GetQuestTemplate(questId)) && t.TryConsumeTo(val.QuestLevel) && t.IsEmpty(); } -bool Trinity::Hyperlinks::LinkTags::spell::StoreTo(SpellInfo const*& val, std::string_view text) +bool Trinity::Hyperlinks::LinkTags::spell::StoreTo(SpellInfo const*& val, char const* pos, size_t len) { - HyperlinkDataTokenizer t(text); + HyperlinkDataTokenizer t(pos, len); uint32 spellId; if (!(t.TryConsumeTo(spellId) && t.IsEmpty())) return false; return !!(val = sSpellMgr->GetSpellInfo(spellId)); } -bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentLinkData& val, std::string_view text) +bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentLinkData& val, char const* pos, size_t len) { - HyperlinkDataTokenizer t(text); + HyperlinkDataTokenizer t(pos, len); uint32 talentId; int8 rank; // talent links contain <learned rank>-1, we store <learned rank> if (!(t.TryConsumeTo(talentId) && t.TryConsumeTo(rank) && t.IsEmpty())) @@ -136,9 +135,9 @@ bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentLinkData& val, std::st return true; } -bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, std::string_view text) +bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, char const* pos, size_t len) { - HyperlinkDataTokenizer t(text); + HyperlinkDataTokenizer t(pos, len); uint32 spellId; if (!t.TryConsumeTo(spellId)) return false; diff --git a/src/server/game/Chat/Hyperlinks.cpp b/src/server/game/Chat/Hyperlinks.cpp index ed18f7b380b..eb49cdcc10b 100644 --- a/src/server/game/Chat/Hyperlinks.cpp +++ b/src/server/game/Chat/Hyperlinks.cpp @@ -31,90 +31,82 @@ using namespace Trinity::Hyperlinks; inline uint8 toHex(char c) { return (c >= '0' && c <= '9') ? c - '0' + 0x10 : (c >= 'a' && c <= 'f') ? c - 'a' + 0x1a : 0x00; } // Validates a single hyperlink -HyperlinkInfo Trinity::Hyperlinks::ParseSingleHyperlink(std::string_view str) +HyperlinkInfo Trinity::Hyperlinks::ParseHyperlink(char const* pos) { - uint32 color = 0; - std::string_view tag; - std::string_view data; - std::string_view text; - //color tag - if (str.substr(0, 2) != "|c") - return {}; - str.remove_prefix(2); - - if (str.length() < 8) - return {}; - + if (*(pos++) != '|' || *(pos++) != 'c') + return nullptr; + uint32 color = 0; for (uint8 i = 0; i < 8; ++i) { - if (uint8 hex = toHex(str[i])) + if (uint8 hex = toHex(*(pos++))) color = (color << 4) | (hex & 0xf); else - return {}; + return nullptr; } - str.remove_prefix(8); - - if (str.substr(0, 2) != "|H") - return {}; - str.remove_prefix(2); - - // tag+data part follows - if (size_t delimPos = str.find('|'); delimPos != std::string_view::npos) - { - tag = str.substr(0, delimPos); - str.remove_prefix(delimPos+1); - } - else - return {}; - - // split tag if : is present (data separator) - if (size_t dataStart = tag.find(':'); dataStart != std::string_view::npos) - { - data = tag.substr(dataStart+1); - tag = tag.substr(0, dataStart); - } - + // link data start tag + if (*(pos++) != '|' || *(pos++) != 'H') + return nullptr; + // link tag, find next : or | + char const* tagStart = pos; + size_t tagLength = 0; + while (*pos && *pos != '|' && *(pos++) != ':') // we only advance pointer to one past if the last thing is : (not for |), this is intentional! + ++tagLength; + // ok, link data, skip to next | + char const* dataStart = pos; + size_t dataLength = 0; + while (*pos && *(pos++) != '|') + ++dataLength; // ok, next should be link data end tag... - if (str.substr(0, 1) != "h") - return {}; - str.remove_prefix(1); - // skip to final | - if (size_t end = str.find('|'); end != std::string_view::npos) + if (*(pos++) != 'h') + return nullptr; + // then visible link text, starts with [ + if (*(pos++) != '[') + return nullptr; + // skip until we hit the next ], abort on unexpected | + char const* textStart = pos; + size_t textLength = 0; + while (*pos) { - // check end tag - if (str.substr(end, 4) != "|h|r") - return {}; - // check text brackets - if ((str[0] != '[') || (str[end - 1] != ']')) - return {}; - text = str.substr(1, end - 2); - // tail - str = str.substr(end + 4); + if (*pos == '|') + return nullptr; + if (*(pos++) == ']') + break; + ++textLength; } - else - return {}; - + // link end tag + if (*(pos++) != '|' || *(pos++) != 'h' || *(pos++) != '|' || *(pos++) != 'r') + return nullptr; // ok, valid hyperlink, return info - return { str, color, tag, data, text }; + return { pos, color, tagStart, tagLength, dataStart, dataLength, textStart, textLength }; } template <typename T> struct LinkValidator { - static bool IsTextValid(typename T::value_type, std::string_view) { return true; } + static bool IsTextValid(typename T::value_type, char const*, size_t) { return true; } static bool IsColorValid(typename T::value_type, HyperlinkColor) { return true; } }; +// str1 is null-terminated, str2 is length-terminated, check if they are exactly equal +static bool equal_with_len(char const* str1, char const* str2, size_t len) +{ + if (!*str1) + return false; + while (len && *str1 && *(str1++) == *(str2++)) + --len; + return !len && !*str1; +} + template <> struct LinkValidator<LinkTags::achievement> { - static bool IsTextValid(AchievementLinkData const& data, std::string_view text) + static bool IsTextValid(AchievementLinkData const& data, char const* pos, size_t len) { - if (text.empty()) + if (!len) return false; for (uint8 i = 0; i < TOTAL_LOCALES; ++i) - if (text == data.Achievement->Title[i]) + if (equal_with_len(data.Achievement->Title[i], pos, len)) return true; return false; } @@ -128,22 +120,22 @@ struct LinkValidator<LinkTags::achievement> template <> struct LinkValidator<LinkTags::item> { - static bool IsTextValid(ItemLinkData const& data, std::string_view text) + static bool IsTextValid(ItemLinkData const& data, char const* pos, size_t len) { ItemLocale const* locale = sObjectMgr->GetItemLocale(data.Item->ItemId); - char const* const* randomSuffixes = nullptr; // this is a c-style array of c strings (and i don't want to touch DBCStructure.h right now) + char const* const* randomSuffix = nullptr; if (data.RandomPropertyId < 0) { if (ItemRandomSuffixEntry const* suffixEntry = sItemRandomSuffixStore.LookupEntry(-data.RandomPropertyId)) - randomSuffixes = suffixEntry->Name; + randomSuffix = suffixEntry->Name; else return false; } else if (data.RandomPropertyId > 0) { if (ItemRandomPropertiesEntry const* propEntry = sItemRandomPropertiesStore.LookupEntry(data.RandomPropertyId)) - randomSuffixes = propEntry->Name; + randomSuffix = propEntry->Name; else return false; } @@ -155,18 +147,15 @@ struct LinkValidator<LinkTags::item> std::string const& name = (i == DEFAULT_LOCALE) ? data.Item->Name1 : locale->Name[i]; if (name.empty()) continue; - if (randomSuffixes) + if (randomSuffix) { - std::string_view randomSuffix(randomSuffixes[i]); - if ( - (text.length() == (name.length() + 1 + randomSuffix.length())) && - (text.substr(0, name.length()) == name) && - (text[name.length()] == ' ') && - (text.substr(name.length() + 1) == randomSuffix) - ) + if (len > name.length() + 1 && + (strncmp(name.c_str(), pos, name.length()) == 0) && + (*(pos + name.length()) == ' ') && + equal_with_len(randomSuffix[i], pos + name.length() + 1, len - name.length() - 1)) return true; } - else if (text == name) + else if (equal_with_len(name.c_str(), pos, len)) return true; } return false; @@ -181,18 +170,18 @@ struct LinkValidator<LinkTags::item> template <> struct LinkValidator<LinkTags::quest> { - static bool IsTextValid(QuestLinkData const& data, std::string_view text) + static bool IsTextValid(QuestLinkData const& data, char const* pos, size_t len) { QuestLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId()); if (!locale) - return text == data.Quest->GetTitle(); + return equal_with_len(data.Quest->GetTitle().c_str(), pos, len); for (uint8 i = 0; i < TOTAL_LOCALES; ++i) { std::string const& name = (i == DEFAULT_LOCALE) ? data.Quest->GetTitle() : locale->Title[i]; if (name.empty()) continue; - if (text == name) + if (equal_with_len(name.c_str(), pos, len)) return true; } @@ -211,10 +200,10 @@ struct LinkValidator<LinkTags::quest> template <> struct LinkValidator<LinkTags::spell> { - static bool IsTextValid(SpellInfo const* info, std::string_view text) + static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len) { for (uint8 i = 0; i < TOTAL_LOCALES; ++i) - if (text == info->SpellName[i]) + if (equal_with_len(info->SpellName[i], pos, len)) return true; return false; } @@ -228,9 +217,9 @@ struct LinkValidator<LinkTags::spell> template <> struct LinkValidator<LinkTags::enchant> { - static bool IsTextValid(SpellInfo const* info, std::string_view text) + static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len) { - if (LinkValidator<LinkTags::spell>::IsTextValid(info, text)) + if (LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len)) return true; SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id); if (bounds.first == bounds.second) @@ -244,15 +233,12 @@ struct LinkValidator<LinkTags::enchant> for (uint8 i = 0; i < TOTAL_LOCALES; ++i) { - std::string_view spellName = info->SpellName[i]; - std::string_view skillName = skill->DisplayName[i]; - // alternate form [Skill Name: Spell Name] - return ( - (text.length() == (spellName.length() + 2 + skillName.length())) && - (text.substr(0, spellName.length()) == spellName) && - (text.substr(spellName.length(), 2) == ": ") && - (text.substr(spellName.length() + 2) == skillName) - ); + char const* skillName = skill->DisplayName[i]; + size_t skillLen = strlen(skillName); + if (len > skillLen + 2 && // or of form [Skill Name: Spell Name] + !strncmp(pos, skillName, skillLen) && !strncmp(pos + skillLen, ": ", 2) && + equal_with_len(info->SpellName[i], pos + (skillLen + 2), len - (skillLen + 2))) + return true; } } return false; @@ -267,10 +253,10 @@ struct LinkValidator<LinkTags::enchant> template <> struct LinkValidator<LinkTags::glyph> { - static bool IsTextValid(GlyphLinkData const& data, std::string_view text) + static bool IsTextValid(GlyphLinkData const& data, char const* pos, size_t len) { if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.Glyph->SpellID)) - return LinkValidator<LinkTags::spell>::IsTextValid(info, text); + return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); return false; } @@ -283,10 +269,10 @@ struct LinkValidator<LinkTags::glyph> template <> struct LinkValidator<LinkTags::talent> { - static bool IsTextValid(TalentLinkData const& data, std::string_view text) + static bool IsTextValid(TalentLinkData const& data, char const* pos, size_t len) { if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.Talent->SpellRank[0])) - return LinkValidator<LinkTags::spell>::IsTextValid(info, text); + return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); return false; } @@ -299,9 +285,9 @@ struct LinkValidator<LinkTags::talent> template <> struct LinkValidator<LinkTags::trade> { - static bool IsTextValid(TradeskillLinkData const& data, std::string_view text) + static bool IsTextValid(TradeskillLinkData const& data, char const* pos, size_t len) { - return LinkValidator<LinkTags::spell>::IsTextValid(data.Spell, text); + return LinkValidator<LinkTags::spell>::IsTextValid(data.Spell, pos, len); } static bool IsColorValid(TradeskillLinkData const&, HyperlinkColor c) @@ -312,16 +298,17 @@ struct LinkValidator<LinkTags::trade> #define TryValidateAs(tagname) \ { \ - static_assert(LinkTags::tagname::tag() == #tagname); \ - if (info.tag == LinkTags::tagname::tag()) \ + ASSERT(!strcmp(LinkTags::tagname::tag(), #tagname)); \ + if (info.tag.second == strlen(LinkTags::tagname::tag()) && \ + !strncmp(info.tag.first, LinkTags::tagname::tag(), strlen(LinkTags::tagname::tag()))) \ { \ advstd::remove_cvref_t<typename LinkTags::tagname::value_type> t; \ - if (!LinkTags::tagname::StoreTo(t, info.data)) \ + if (!LinkTags::tagname::StoreTo(t, info.data.first, info.data.second)) \ return false; \ if (!LinkValidator<LinkTags::tagname>::IsColorValid(t, info.color)) \ return false; \ if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY)) \ - if (!LinkValidator<LinkTags::tagname>::IsTextValid(t, info.text)) \ + if (!LinkValidator<LinkTags::tagname>::IsTextValid(t, info.text.first, info.text.second)) \ return false; \ return true; \ } \ @@ -354,19 +341,16 @@ static bool ValidateLinkInfo(HyperlinkInfo const& info) } // Validates all hyperlinks and control sequences contained in str -bool Trinity::Hyperlinks::CheckAllLinks(std::string_view str) +bool Trinity::Hyperlinks::CheckAllLinks(std::string const& str) { // Step 1: Disallow all control sequences except ||, |H, |h, |c and |r { - std::string_view::size_type pos = 0; + std::string::size_type pos = 0; while ((pos = str.find('|', pos)) != std::string::npos) { - ++pos; - if (pos == str.length()) - return false; - char next = str[pos]; + char next = str[pos + 1]; if (next == 'H' || next == 'h' || next == 'c' || next == 'r' || next == '|') - ++pos; + pos += 2; else return false; } @@ -379,21 +363,21 @@ bool Trinity::Hyperlinks::CheckAllLinks(std::string_view str) // - <linkdata> is arbitrary length, no | contained // - <linktext> is printable { - std::string::size_type pos; - while ((pos = str.find('|')) != std::string::npos) + std::string::size_type pos = 0; + while ((pos = str.find('|', pos)) != std::string::npos) { if (str[pos + 1] == '|') // this is an escaped pipe character (||) { - str = str.substr(pos + 2); + pos += 2; continue; } - HyperlinkInfo info = ParseSingleHyperlink(str.substr(pos)); + HyperlinkInfo info = ParseHyperlink(str.c_str() + pos); if (!info || !ValidateLinkInfo(info)) return false; // tag is fine, find the next one - str = info.tail; + pos = info.next - str.c_str(); } } diff --git a/src/server/game/Chat/Hyperlinks.h b/src/server/game/Chat/Hyperlinks.h index 3a5e980667c..f47235e409a 100644 --- a/src/server/game/Chat/Hyperlinks.h +++ b/src/server/game/Chat/Hyperlinks.h @@ -19,9 +19,7 @@ #define TRINITY_HYPERLINKS_H #include "ObjectGuid.h" -#include <charconv> #include <string> -#include <string_view> #include <type_traits> #include <utility> @@ -90,62 +88,47 @@ namespace Trinity::Hyperlinks |* Link tags must abide by the following: *| |* - MUST expose ::value_type typedef *| |* - storage type is remove_cvref_t<value_type> *| - |* - MUST expose static ::tag method, void -> std::string_view *| + |* - MUST expose static ::tag method, void -> const char* *| |* - this method SHOULD be constexpr *| |* - returns identifier string for the link ("creature", "creature_entry", "item") *| - |* - MUST expose static ::StoreTo method, (storage&, std::string_view) *| - |* - assign value_type& based on content of std::string_view *| + |* - MUST expose static ::StoreTo method, (storage&, char const*, size_t) *| + |* - assign value_type& based on content of std::string(char const*, size_t) *| |* - return value indicates success/failure *| |* - for integral/string types this can be achieved by extending base_tag *| \****************************************************************************************/ struct base_tag { - static bool StoreTo(std::string_view& val, std::string_view data) + static bool StoreTo(std::string& val, char const* pos, size_t len) { - val = data; + val.assign(pos, len); return true; } - static bool StoreTo(std::string& val, std::string_view data) + template <typename T> + static std::enable_if_t<std::is_integral_v<T> && std::is_unsigned_v<T>, bool> StoreTo(T& val, char const* pos, size_t len) { - val = data; + try { val = std::stoull(std::string(pos, len)); } + catch (...) { return false; } return true; } template <typename T> - static std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, bool> StoreTo(T& val, std::string_view data) + static std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<T>, bool> StoreTo(T& val, char const* pos, size_t len) { - if (data.empty()) - return false; - char const* end = (data.data() + data.length()); - std::from_chars_result result = std::from_chars(data.data(), end, val, 10); - return (result.ptr == end); - } - - static bool StoreTo(bool& val, std::string_view data) - { - if (data == "1") - val = true; - else if (data == "0") - val = false; - else - return false; + try { val = std::stoll(std::string(pos, len)); } + catch (...) { return false; } return true; } - static bool StoreTo(ObjectGuid& val, std::string_view data) + static bool StoreTo(ObjectGuid& val, char const* pos, size_t len) { - if (data.empty()) - return false; - uint64 guid; - char const* const end = (data.data() + data.length()); - std::from_chars_result result = std::from_chars(data.data(), end, guid, 16); - val.Set(guid); - return (result.ptr == end); + try { val.Set(std::stoul(std::string(pos, len), nullptr, 16)); } + catch (...) { return false; } + return true; } }; - #define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr std::string_view tag() { return #ltag; } } + #define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr char const* tag() { return #ltag; } } make_base_tag(area, uint32); make_base_tag(areatrigger, uint32); make_base_tag(creature, ObjectGuid::LowType); @@ -164,64 +147,64 @@ namespace Trinity::Hyperlinks struct TC_GAME_API achievement { using value_type = AchievementLinkData const&; - static constexpr std::string_view tag() { return "achievement"; } - static bool StoreTo(AchievementLinkData& val, std::string_view data); + static constexpr char const* tag() { return "achievement"; } + static bool StoreTo(AchievementLinkData& val, char const* pos, size_t len); }; struct TC_GAME_API enchant { using value_type = SpellInfo const*; - static constexpr std::string_view tag() { return "enchant"; } - static bool StoreTo(SpellInfo const*& val, std::string_view data); + static constexpr char const* tag() { return "enchant"; } + static bool StoreTo(SpellInfo const*& val, char const* pos, size_t len); }; struct TC_GAME_API glyph { using value_type = GlyphLinkData const&; - static constexpr std::string_view tag() { return "glyph"; }; - static bool StoreTo(GlyphLinkData& val, std::string_view data); + static constexpr char const* tag() { return "glyph"; }; + static bool StoreTo(GlyphLinkData& val, char const* pos, size_t len); }; struct TC_GAME_API item { using value_type = ItemLinkData const&; - static constexpr std::string_view tag() { return "item"; } - static bool StoreTo(ItemLinkData& val, std::string_view data); + static constexpr char const* tag() { return "item"; } + static bool StoreTo(ItemLinkData& val, char const* pos, size_t len); }; struct TC_GAME_API quest { using value_type = QuestLinkData const&; - static constexpr std::string_view tag() { return "quest"; } - static bool StoreTo(QuestLinkData& val, std::string_view data); + static constexpr char const* tag() { return "quest"; } + static bool StoreTo(QuestLinkData& val, char const* pos, size_t len); }; struct TC_GAME_API spell { using value_type = SpellInfo const*; - static constexpr std::string_view tag() { return "spell"; } - static bool StoreTo(SpellInfo const*& val, std::string_view data); + static constexpr char const* tag() { return "spell"; } + static bool StoreTo(SpellInfo const*& val, char const* pos, size_t len); }; struct TC_GAME_API talent { using value_type = TalentLinkData const&; - static constexpr std::string_view tag() { return "talent"; } - static bool StoreTo(TalentLinkData& val, std::string_view data); + static constexpr char const* tag() { return "talent"; } + static bool StoreTo(TalentLinkData& val, char const* pos, size_t len); }; struct TC_GAME_API trade { using value_type = TradeskillLinkData const&; - static constexpr std::string_view tag() { return "trade"; } - static bool StoreTo(TradeskillLinkData& val, std::string_view data); + static constexpr char const* tag() { return "trade"; } + static bool StoreTo(TradeskillLinkData& val, char const* pos, size_t len); }; } struct HyperlinkColor { HyperlinkColor(uint32 c) : r(c >> 16), g(c >> 8), b(c), a(c >> 24) {} - uint8 const r, g, b, a; + uint8 r, g, b, a; bool operator==(uint32 c) const { if ((c & 0xff) ^ b) @@ -238,20 +221,18 @@ namespace Trinity::Hyperlinks struct HyperlinkInfo { - HyperlinkInfo() : ok(false), color(0) {} - HyperlinkInfo(std::string_view t, uint32 c, std::string_view ta, std::string_view d, std::string_view te) : - ok(true), tail(t), color(c), tag(ta), data(d), text(te) {} + HyperlinkInfo(char const* n = nullptr, uint32 c = 0, char const* tS = nullptr, size_t tL = 0, char const* dS = nullptr, size_t dL = 0, char const* cS = nullptr, size_t cL = 0) : + next(n), color(c), tag(tS, tL), data(dS, dL), text(cS, cL) {} - explicit operator bool() { return ok; } - bool const ok; - std::string_view const tail; + explicit operator bool() { return next; } + char const* const next; HyperlinkColor const color; - std::string_view const tag; - std::string_view const data; - std::string_view const text; + std::pair<char const*, size_t> const tag; + std::pair<char const*, size_t> const data; + std::pair<char const*, size_t> const text; }; - HyperlinkInfo TC_GAME_API ParseSingleHyperlink(std::string_view str); - bool TC_GAME_API CheckAllLinks(std::string_view str); + HyperlinkInfo TC_GAME_API ParseHyperlink(char const* pos); + bool TC_GAME_API CheckAllLinks(std::string const&); } diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index 8f6a80b2201..22654c48b42 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -1706,7 +1706,7 @@ public: return true; } - static bool HandleDebugOutOfBounds([[maybe_unused]] ChatHandler* handler) + static bool HandleDebugOutOfBounds([[maybe_unused]] ChatHandler* handler, CommandArgs* /*args*/) { #ifdef ASAN uint8 stack_array[10] = {}; @@ -1852,7 +1852,7 @@ public: handler->PSendSysMessage("Entry: %u Count: %u", p.first, p.second); } - static bool HandleDebugDummyCommand(ChatHandler* handler) + static bool HandleDebugDummyCommand(ChatHandler* handler, CommandArgs* /*args*/) { handler->SendSysMessage("This command does nothing right now. Edit your local core (cs_debug.cpp) to make it do whatever you need for testing."); return true; diff --git a/src/server/scripts/Commands/cs_message.cpp b/src/server/scripts/Commands/cs_message.cpp index 9809e4270a9..2c6affbdc2e 100644 --- a/src/server/scripts/Commands/cs_message.cpp +++ b/src/server/scripts/Commands/cs_message.cpp @@ -127,60 +127,60 @@ public: return true; } - static bool HandleNameAnnounceCommand(ChatHandler* handler, Tail message) + static bool HandleNameAnnounceCommand(ChatHandler* handler, CommandArgs* args) { - if (message.empty()) + if (!*args) return false; std::string name("Console"); if (WorldSession* session = handler->GetSession()) name = session->GetPlayer()->GetName(); - sWorld->SendWorldText(LANG_ANNOUNCE_COLOR, name.c_str(), message.data()); + sWorld->SendWorldText(LANG_ANNOUNCE_COLOR, name.c_str(), args->GetFullArgs()); return true; } - static bool HandleGMNameAnnounceCommand(ChatHandler* handler, Tail message) + static bool HandleGMNameAnnounceCommand(ChatHandler* handler, CommandArgs* args) { - if (message.empty()) + if (!*args) return false; std::string name("Console"); if (WorldSession* session = handler->GetSession()) name = session->GetPlayer()->GetName(); - sWorld->SendGMText(LANG_GM_ANNOUNCE_COLOR, name.c_str(), message.data()); + sWorld->SendGMText(LANG_GM_ANNOUNCE_COLOR, name.c_str(), args->GetFullArgs()); return true; } // global announce - static bool HandleAnnounceCommand(ChatHandler* handler, Tail message) + static bool HandleAnnounceCommand(ChatHandler* handler, char const* args) { - if (message.empty()) + if (!*args) return false; - sWorld->SendServerMessage(SERVER_MSG_STRING, Trinity::StringFormat(handler->GetTrinityString(LANG_SYSTEMMESSAGE), message.data()).c_str()); + sWorld->SendServerMessage(SERVER_MSG_STRING, Trinity::StringFormat(handler->GetTrinityString(LANG_SYSTEMMESSAGE), args).c_str()); return true; } // announce to logged in GMs - static bool HandleGMAnnounceCommand(ChatHandler* /*handler*/, Tail message) + static bool HandleGMAnnounceCommand(ChatHandler* /*handler*/, CommandArgs* args) { - if (message.empty()) + if (!*args) return false; - sWorld->SendGMText(LANG_GM_BROADCAST, message.data()); + sWorld->SendGMText(LANG_GM_BROADCAST, args->GetFullArgs()); return true; } // send on-screen notification to players - static bool HandleNotifyCommand(ChatHandler* handler, Tail message) + static bool HandleNotifyCommand(ChatHandler* handler, CommandArgs* args) { - if (message.empty()) + if (!*args) return false; std::string str = handler->GetTrinityString(LANG_GLOBAL_NOTIFY); - str += message; + str += args->GetFullArgs(); WorldPacket data(SMSG_NOTIFICATION, (str.size() + 1)); data << str; @@ -190,13 +190,13 @@ public: } // send on-screen notification to GMs - static bool HandleGMNotifyCommand(ChatHandler* handler, Tail message) + static bool HandleGMNotifyCommand(ChatHandler* handler, CommandArgs* args) { - if (message.empty()) + if (!*args) return false; std::string str = handler->GetTrinityString(LANG_GM_NOTIFY); - str += message; + str += args->GetFullArgs(); WorldPacket data(SMSG_NOTIFICATION, (str.size() + 1)); data << str; |