Merge pull request #15 from diamante0018/main

feature(user_info): allow GSC to edit name/clantag
This commit is contained in:
fed 2023-10-31 05:12:27 +01:00 committed by GitHub
commit 964d6dc0b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 991 additions and 796 deletions

View File

@ -19,7 +19,7 @@ jobs:
- Release
steps:
- name: Check out files
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
@ -27,10 +27,9 @@ jobs:
lfs: false
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2
uses: microsoft/setup-msbuild@v1.3.1
- name: Generate project files
#run: tools/premake5 vs2022 --ci-build
run: tools/premake5 vs2022
- name: Set up problem matching
@ -40,7 +39,7 @@ jobs:
run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:PlatformTarget=x86 build/t5-gsc-utils.sln
- name: Upload ${{matrix.configuration}} binaries
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3.1.3
with:
name: ${{matrix.configuration}} binaries
path: |

2
deps/curl vendored

@ -1 +1 @@
Subproject commit 134963a5efdc3906257c88ce62dba8d46c292908
Subproject commit ab885eccd6c82a949e21cfab9af778c0fd44d0ae

View File

@ -92,7 +92,6 @@ workspace "t5-gsc-utils"
pchheader "stdinc.hpp"
pchsource "src/stdinc.cpp"
buildoptions { "/Zm100 -Zm100" }
dependencies.imports()

View File

@ -29,7 +29,7 @@ namespace command
game::CmdArgs* get_cmd_args()
{
return reinterpret_cast<game::CmdArgs*>(game::Sys_GetValue(4));
return static_cast<game::CmdArgs*>(game::Sys_GetValue(4));
}
void main_handler()
@ -315,7 +315,7 @@ namespace command
gsc::method::add("tell", [](const scripting::entity& player, const std::string& msg)
{
const auto entref = player.get_entity_reference();
if (entref.classnum != 0 || entref.entnum >= 18)
if (entref.classnum)
{
throw std::runtime_error("Not a player entity");
}

View File

@ -6,12 +6,9 @@
#include "gsc.hpp"
#include "scheduler.hpp"
#include "scripting.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
namespace gsc
{
@ -309,7 +306,7 @@ namespace gsc
gsc::function::add("toint", [](const std::string& str, const scripting::variadic_args& va)
{
auto radix = 10;
if (va.size() > 0)
if (!va.empty())
{
radix = va[0];
}
@ -320,7 +317,7 @@ namespace gsc
gsc::function::add("os::date", [](const scripting::variadic_args& va)
{
std::string format = "%Y-%m-%dT%H:%M:%S%z";
if (va.size() > 0)
if (!va.empty())
{
format = va[0].as<std::string>();
}

143
src/component/user_info.cpp Normal file
View File

@ -0,0 +1,143 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "gsc.hpp"
#include <utils/hook.hpp>
#include <utils/info_string.hpp>
namespace user_info
{
namespace
{
using user_info_map = std::unordered_map<std::string, std::string>;
std::unordered_map<int, user_info_map> user_info_overrides;
utils::hook::detour scr_shutdown_system_hook;
void clear_client_overrides(const int client_num)
{
user_info_overrides[client_num].clear();
}
void clear_all_overrides()
{
user_info_overrides.clear();
}
void client_disconnect_stub(const int client_num)
{
clear_client_overrides(client_num);
game::ClientDisconnect(client_num);
}
void scr_shutdown_system_stub(const game::scriptInstance_t inst, const unsigned char sys, const int b_complete)
{
clear_all_overrides();
scr_shutdown_system_hook.invoke<void>(inst, sys, b_complete);
}
void sv_get_user_info_stub(const int index, char* buffer, const int buffer_size)
{
game::SV_GetUserinfo(index, buffer, buffer_size);
utils::info_string map(buffer);
if (!user_info_overrides.contains(index))
{
user_info_overrides[index] = {};
}
for (const auto& [key, val] : user_info_overrides[index])
{
if (val.empty())
{
map.remove(key);
}
else
{
map.set(key, val);
}
}
const auto user_info = map.build();
strncpy_s(buffer, buffer_size, user_info.data(), _TRUNCATE);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
utils::hook::call(SELECT_VALUE(0x5D38EB, 0x4A75E2), sv_get_user_info_stub);
utils::hook::call(SELECT_VALUE(0x67FFE9, 0x548DB0), sv_get_user_info_stub);
utils::hook::call(SELECT_VALUE(0x4F3931, 0x5DC953), client_disconnect_stub);
scr_shutdown_system_hook.create(SELECT_VALUE(0x596D40, 0x540780), scr_shutdown_system_stub);
gsc::method::add_multiple([](const scripting::entity& player, const std::string& name) -> void
{
const auto entref = player.get_entity_reference();
if (entref.classnum)
{
throw std::runtime_error("Not a player entity");
}
if (name.empty())
{
throw std::runtime_error("set_name: Illegal parameter!");
}
user_info_overrides[entref.entnum]["name"] = name;
game::ClientUserinfoChanged(entref.entnum);
}, "user_info::set_name", "set_name");
gsc::method::add_multiple([](const scripting::entity& player) -> void
{
const auto entref = player.get_entity_reference();
if (entref.classnum)
{
throw std::runtime_error("Not a player entity");
}
user_info_overrides[entref.entnum].erase("name");
game::ClientUserinfoChanged(entref.entnum);
}, "user_info::reset_name", "reset_name");
gsc::method::add_multiple([](const scripting::entity& player, const std::string& tag) -> void
{
const auto entref = player.get_entity_reference();
if (entref.classnum)
{
throw std::runtime_error("Not a player entity");
}
if (tag.empty())
{
throw std::runtime_error("set_clantag: Illegal parameter!");
}
user_info_overrides[entref.entnum]["clanAbbrev"] = tag;
game::ClientUserinfoChanged(entref.entnum);
}, "user_info::set_clantag", "set_clantag");
gsc::method::add_multiple([](const scripting::entity& player) -> void
{
const auto entref = player.get_entity_reference();
if (entref.classnum)
{
throw std::runtime_error("Not a player entity");
}
user_info_overrides[entref.entnum].erase("clanAbbrev");
game::ClientUserinfoChanged(entref.entnum);
}, "user_info::reset_clantag", "reset_clantag");
}
};
}
REGISTER_COMPONENT(user_info::component)

View File

@ -17,8 +17,6 @@ namespace game
WEAK symbol<const char*(int index)> Cmd_Argv{0x0, 0x0};
WEAK symbol<void(const char* cmdName)> Cmd_RemoveCommand{0x5F1A90, 0x527EA0};
WEAK symbol<void(int clientNum)> ClientUserInfoChanged{0x0, 0x0};
WEAK symbol<void(errorParm_t code, const char* fmt, ...)> Com_Error{0x651D90, 0x627380};
WEAK symbol<void(int channel, const char* fmt, ...)> Com_Printf{0x43BF30, 0x4126C0};
WEAK symbol<void(const char* fmt, ...)> Com_Printf_NoFilter{0x566BC0, 0x64C260};
@ -107,10 +105,14 @@ namespace game
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int localId, const char* pos, unsigned int paramcount)> VM_Execute{0x8ACE60, 0x8EADE0};
WEAK symbol<void(int index, char* buffer, int bufferSize)> SV_GetUserinfo{0x50DCD0, 0x6916A0};
WEAK symbol<void(int clientNum, const char* reason)> SV_GameDropClient{0x0, 0x0};
WEAK symbol<bool(int clientNum)> SV_IsTestClient{0x0, 0x0};
WEAK symbol<void(int clientNum, int type, const char* command)> SV_GameSendServerCommand{0x543CF0, 0x6B8730};
WEAK symbol<void(int clientNum)> ClientDisconnect{0x4F4000, 0x66FA00};
WEAK symbol<void(int clientNum)> ClientUserinfoChanged{0x67FFC0, 0x548D80};
WEAK symbol<void*(int valueIndex)> Sys_GetValue{0x67D4F0, 0x529EB0};
WEAK symbol<int()> Sys_Milliseconds{0x0, 0x0};

View File

@ -11,24 +11,25 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <vector>
#include <cassert>
#include <mutex>
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <algorithm>
#include <functional>
#include <regex>
#include <queue>
#include <unordered_set>
#include <filesystem>
#include <map>
#include <csetjmp>
#include <atlcomcli.h>
#include <variant>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <mutex>
#include <optional>
#include <queue>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <variant>
#include <vector>
#ifdef max
#undef max

View File

@ -101,12 +101,5 @@ namespace utils::http
return result;
}
if (helper.exception)
{
std::rethrow_exception(helper.exception);
}
return {};
}
}

67
src/utils/info_string.cpp Normal file
View File

@ -0,0 +1,67 @@
#include <stdinc.hpp>
#include "info_string.hpp"
#include "string.hpp"
namespace utils
{
info_string::info_string(const std::string& buffer)
{
this->parse(buffer);
}
void info_string::set(const std::string& key, const std::string& value)
{
this->key_value_pairs_[key] = value;
}
void info_string::remove(const std::string& key)
{
this->key_value_pairs_.erase(key);
}
std::string info_string::get(const std::string& key) const
{
const auto value = this->key_value_pairs_.find(key);
if (value != this->key_value_pairs_.end())
{
return value->second;
}
return {};
}
void info_string::parse(std::string buffer)
{
if (buffer[0] == '\\')
{
buffer = buffer.substr(1);
}
const auto key_values = string::split(buffer, '\\');
for (size_t i = 0; !key_values.empty() && i < (key_values.size() - 1); i += 2)
{
const auto& key = key_values[i];
const auto& value = key_values[i + 1];
if (!this->key_value_pairs_.contains(key))
{
this->key_value_pairs_[key] = value;
}
}
}
std::string info_string::build() const
{
std::string info_string;
for (const auto& [key, val] : this->key_value_pairs_)
{
info_string.append("\\");
info_string.append(key);
info_string.append("\\");
info_string.append(val);
}
return info_string;
}
}

25
src/utils/info_string.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <string>
#include <unordered_map>
namespace utils
{
class info_string
{
public:
info_string() = default;
info_string(const std::string& buffer);
void set(const std::string& key, const std::string& value);
void remove(const std::string& key);
[[nodiscard]] std::string get(const std::string& key) const;
[[nodiscard]] std::string build() const;
private:
std::unordered_map<std::string, std::string> key_value_pairs_{};
void parse(std::string buffer);
};
}

View File

@ -35,7 +35,7 @@ namespace utils::string
std::string to_lower(std::string text)
{
std::transform(text.begin(), text.end(), text.begin(), [](const char input)
std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input)
{
return static_cast<char>(tolower(input));
});
@ -45,7 +45,7 @@ namespace utils::string
std::string to_upper(std::string text)
{
std::transform(text.begin(), text.end(), text.begin(), [](const char input)
std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input)
{
return static_cast<char>(toupper(input));
});
@ -86,34 +86,23 @@ namespace utils::string
return result;
}
void strip(const char* in, char* out, int max)
std::string replace(std::string str, const std::string& from, const std::string& to)
{
if (!in || !out) return;
max--;
auto current = 0;
while (*in != 0 && current < max)
if (from.empty())
{
const auto color_index = (*(in + 1) - 48) >= 0xC ? 7 : (*(in + 1) - 48);
if (*in == '^' && (color_index != 7 || *(in + 1) == '7'))
{
++in;
}
else
{
*out = *in;
++out;
++current;
return str;
}
++in;
}
*out = '\0';
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
{
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
return str;
}
#pragma warning(push)
#pragma warning(disable: 4100)
std::string convert(const std::wstring& wstr)
{
std::string result;
@ -139,24 +128,6 @@ namespace utils::string
return result;
}
#pragma warning(pop)
std::string replace(std::string str, const std::string& from, const std::string& to)
{
if (from.empty())
{
return str;
}
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
{
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
return str;
}
std::string get_timestamp()
{

View File

@ -90,12 +90,10 @@ namespace utils::string
std::string dump_hex(const std::string& data, const std::string& separator = " ");
void strip(const char* in, char* out, int max);
std::string replace(std::string str, const std::string& from, const std::string& to);
std::string convert(const std::wstring& wstr);
std::wstring convert(const std::string& str);
std::string replace(std::string str, const std::string& from, const std::string& to);
std::string get_timestamp();
}

Binary file not shown.