diff --git a/src/component/string.cpp b/src/component/string.cpp index 00fda69..4fd7744 100644 --- a/src/component/string.cpp +++ b/src/component/string.cpp @@ -3,10 +3,159 @@ #include "gsc.hpp" +// lua/lstrlib.c +#define MAX_FORMAT 32 +#define L_FMTFLAGSF "-+#0 " +#define L_FMTFLAGSX "-#0" +#define L_FMTFLAGSI "-+0 " +#define L_FMTFLAGSU "-0" +#define L_FMTFLAGSC "-" + #include namespace string { + namespace + { + // lua/lstrlib.c + const char* getformat(const char* strfrmt, char* form) + { + const auto len = std::strspn(strfrmt, L_FMTFLAGSF "123456789.") + 1; + if (len >= MAX_FORMAT - 10) + { + throw std::runtime_error("invalid format (too long)"); + } + + *(form++) = '%'; + std::memcpy(form, strfrmt, len * sizeof(char)); + *(form + len) = '\0'; + return strfrmt + len - 1; + } + + // lua/lstrlib.c + const char* get_2_digits(const char* s) + { + if (isdigit(static_cast(*s))) + { + s++; + if (isdigit(static_cast(*s))) + { + s++; + } + } + + return s; + } + + // lua/lstrlib.c + void check_format(const char* form, const char* flags, int precision) + { + const char* spec = form + 1; + spec += std::strspn(spec, flags); + if (*spec != '0') + { + spec = get_2_digits(spec); + if (*spec == '.' && precision) + { + spec++; + spec = get_2_digits(spec); + } + } + if (!std::isalpha(static_cast(*spec))) + { + throw std::runtime_error(utils::string::va("invalid conversion specification: '%s'", form)); + } + } + + // partially lua/lstrlib.c + std::string format_string(const gsc::function_args& args) + { + std::string buffer{}; + size_t va_index = 1; + + const auto fmt = args[0].as(); + + const char* strfrmt = fmt.data(); + const char* strfrmt_end = strfrmt + fmt.size(); + + while (strfrmt < strfrmt_end) + { + if (*strfrmt != '%') + { + buffer.push_back(*strfrmt++); + } + else if (*++strfrmt == '%') + { + buffer.push_back(*strfrmt++); + } + else + { + char form[MAX_FORMAT]{}; + const char* flags = ""; + strfrmt = getformat(strfrmt, form); + + switch (*strfrmt++) + { + case 'd': + case 'i': + flags = L_FMTFLAGSI; + goto intcase; + case 'u': + case 'p': + flags = L_FMTFLAGSU; + goto intcase; + case 'o': + case 'x': + case 'X': + flags = L_FMTFLAGSX; + intcase: + { + check_format(form, flags, 1); + const auto value = args[va_index].as(); + buffer.append(utils::string::va(form, value)); + va_index++; + break; + } + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + { + check_format(form, L_FMTFLAGSF, 1); + const auto value = args[va_index].as(); + buffer.append(utils::string::va(form, value)); + va_index++; + break; + } + case 'c': + { + const auto value = args[va_index].as(); + check_format(form, L_FMTFLAGSC, 0); + buffer.append(utils::string::va(form, static_cast(value))); + va_index++; + break; + } + case 's': + { + const auto str = args[va_index].as(); + buffer.append(str); + va_index++; + break; + } + default: + { + throw std::runtime_error(utils::string::va("invalid conversion '%s' to 'format'", form)); + } + } + } + } + + return buffer; + } + } + class component final : public component_interface { public: @@ -34,6 +183,14 @@ namespace string const auto char_ = static_cast(args[0].as()); return std::string(1, char_); }); + + gsc::function::add("parsehex", [](const gsc::function_args& args) + { + const auto str = args[0].as(); + return static_cast(std::strtoul(str.data(), nullptr, 0x10)); + }); + + gsc::function::add("string_format", format_string); } }; } diff --git a/src/component/userinfo.cpp b/src/component/userinfo.cpp index 402970f..3d0442f 100644 --- a/src/component/userinfo.cpp +++ b/src/component/userinfo.cpp @@ -72,6 +72,19 @@ namespace userinfo { userinfo_overrides[client].clear(); } + + void* client_connect_stub(int client_num, int script_pers_id) + { + const auto res = utils::hook::invoke(0x4FAFB0, client_num, script_pers_id); + + if (res == nullptr) + { + const scripting::entity player = game::Scr_GetEntityId(client_num, 0); + scripting::notify(*game::levelEntityId, "direct_connect", {player}); + } + + return res; + } } void clear_overrides() @@ -88,6 +101,7 @@ namespace userinfo void on_startup([[maybe_unused]] plugin::plugin* plugin) override { sv_getuserinfo_hook.create(0x573E00, sv_getuserinfo_stub); + utils::hook::call(0x573042, client_connect_stub); plugin->get_interface()->callbacks()->on_player_connect(clear_client_overrides); plugin->get_interface()->callbacks()->on_player_disconnect(clear_client_overrides);