diff --git a/src/component/gsc.cpp b/src/component/gsc.cpp index ceeaff8..c7a3f79 100644 --- a/src/component/gsc.cpp +++ b/src/component/gsc.cpp @@ -339,7 +339,7 @@ namespace gsc { if (ent.classnum != 0) { - throw std::runtime_error("Invalid type"); + throw std::runtime_error("Invalid entity"); } const auto client = ent.entnum; diff --git a/src/component/scripting.cpp b/src/component/scripting.cpp index 00953e1..8255cf5 100644 --- a/src/component/scripting.cpp +++ b/src/component/scripting.cpp @@ -3,6 +3,7 @@ #include "scheduler.hpp" #include "command.hpp" +#include "userinfo.hpp" #include "game/scripting/event.hpp" #include "game/scripting/execution.hpp" @@ -46,7 +47,9 @@ namespace scripting if (e.name == "connected") { - scripting::clear_entity_fields(e.entity); + const auto player = e.arguments[0].as(); + const auto client = player.call("getentitynumber").as(); + userinfo::clear_client_overrides(client); } } @@ -72,6 +75,7 @@ namespace scripting void g_shutdown_game_stub(const int free_scripts) { + userinfo::clear_overrides(); command::clear_script_commands(); gsc::replaced_functions.clear(); g_shutdown_game_hook.invoke(free_scripts); diff --git a/src/component/userinfo.cpp b/src/component/userinfo.cpp new file mode 100644 index 0000000..498fdfc --- /dev/null +++ b/src/component/userinfo.cpp @@ -0,0 +1,177 @@ +#include +#include "loader/component_loader.hpp" + +#include "scheduler.hpp" + +#include "game/scripting/event.hpp" +#include "game/scripting/execution.hpp" +#include "game/scripting/functions.hpp" +#include "game/scripting/array.hpp" + +#include "gsc.hpp" + +namespace userinfo +{ + using userinfo_map = std::unordered_map; + std::unordered_map userinfo_overrides; + + namespace + { + utils::hook::detour sv_getuserinfo_hook; + + userinfo_map userinfo_to_map(const std::string& userinfo) + { + userinfo_map map; + const auto args = utils::string::split(userinfo, '\\'); + + for (auto i = 1; i < args.size() - 1; i += 2) + { + map[args[i]] = args[i + 1]; + } + + return map; + } + + std::string map_to_userinfo(const userinfo_map& map) + { + std::string buffer; + + for (const auto& value : map) + { + buffer.append("\\"); + buffer.append(value.first); + buffer.append("\\"); + buffer.append(value.second); + } + + return buffer; + } + + void sv_getuserinfo_stub(int index, char* buffer, int bufferSize) + { + char _buffer[1024]; + sv_getuserinfo_hook.invoke(index, _buffer, 1024); + auto map = userinfo_to_map(_buffer); + + if (userinfo_overrides.find(index) == userinfo_overrides.end()) + { + userinfo_overrides[index] = {}; + } + + for (const auto& values : userinfo_overrides[index]) + { + if (values.second.empty()) + { + map.erase(values.first); + } + else + { + map[values.first] = values.second; + } + } + + const auto userinfo = map_to_userinfo(map); + strcpy_s(buffer, 1024, userinfo.data()); + } + } + + void clear_client_overrides(int client) + { + userinfo_overrides[client].clear(); + } + + void clear_overrides() + { + userinfo_overrides.clear(); + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + sv_getuserinfo_hook.create(0x573E00, sv_getuserinfo_stub); + + gsc::method::add("setname", [](const game::scr_entref_t ent, const gsc::function_args& args) -> scripting::script_value + { + const auto name = args[0].as(); + + if (ent.classnum != 0 || ent.entnum > 17) + { + throw std::runtime_error("Invalid entity"); + } + + userinfo_overrides[ent.entnum]["name"] = name; + game::ClientUserinfoChanged(ent.entnum); + + return {}; + }); + + gsc::method::add("resetname", [](const game::scr_entref_t ent, const gsc::function_args& args) -> scripting::script_value + { + const auto name = args[0].as(); + + if (ent.classnum != 0 || ent.entnum > 17) + { + throw std::runtime_error("Invalid entity"); + } + + userinfo_overrides[ent.entnum].erase("name"); + game::ClientUserinfoChanged(ent.entnum); + + return {}; + }); + + gsc::method::add("setclantag", [](const game::scr_entref_t ent, const gsc::function_args& args) -> scripting::script_value + { + const auto name = args[0].as(); + + if (ent.classnum != 0 || ent.entnum > 17) + { + throw std::runtime_error("Invalid entity"); + } + + userinfo_overrides[ent.entnum]["clantag"] = name; + userinfo_overrides[ent.entnum]["ec_TagText"] = name; + userinfo_overrides[ent.entnum]["ec_usingTag"] = "1"; + game::ClientUserinfoChanged(ent.entnum); + + return {}; + }); + + gsc::method::add("resetclantag", [](const game::scr_entref_t ent, const gsc::function_args& args) -> scripting::script_value + { + const auto name = args[0].as(); + + if (ent.classnum != 0 || ent.entnum > 17) + { + throw std::runtime_error("Invalid entity"); + } + + userinfo_overrides[ent.entnum].erase("clantag"); + userinfo_overrides[ent.entnum].erase("ec_TagText"); + userinfo_overrides[ent.entnum].erase("ec_usingTag"); + game::ClientUserinfoChanged(ent.entnum); + + return {}; + }); + + gsc::method::add("removeclantag", [](const game::scr_entref_t ent, const gsc::function_args& args) -> scripting::script_value + { + if (ent.classnum != 0 || ent.entnum > 17) + { + throw std::runtime_error("Invalid entity"); + } + + userinfo_overrides[ent.entnum]["clantag"] = ""; + userinfo_overrides[ent.entnum]["ec_TagText"] = ""; + userinfo_overrides[ent.entnum]["ec_usingTag"] = "0"; + game::ClientUserinfoChanged(ent.entnum); + + return {}; + }); + } + }; +} + +REGISTER_COMPONENT(userinfo::component) \ No newline at end of file diff --git a/src/component/userinfo.hpp b/src/component/userinfo.hpp new file mode 100644 index 0000000..3c483e8 --- /dev/null +++ b/src/component/userinfo.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace userinfo +{ + void clear_client_overrides(int client); + void clear_overrides(); +} \ No newline at end of file diff --git a/src/game/symbols.hpp b/src/game/symbols.hpp index 1eddf34..9e9b772 100644 --- a/src/game/symbols.hpp +++ b/src/game/symbols.hpp @@ -16,6 +16,7 @@ namespace game WEAK symbol BG_GetWeaponNameComplete{0x42F760}; + WEAK symbol ClientUserinfoChanged{0x4FADB0}; WEAK symbol ConcatArgs{0x502150}; WEAK symbol Cbuf_AddText{0x545680}; WEAK symbol Cmd_AddCommandInternal{0x545DF0};