mirror of
https://github.com/alicealys/t5-gsc-utils.git
synced 2025-04-19 20:42:54 +00:00
Merge pull request #15 from diamante0018/main
feature(user_info): allow GSC to edit name/clantag
This commit is contained in:
commit
964d6dc0b7
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -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
2
deps/curl
vendored
@ -1 +1 @@
|
||||
Subproject commit 134963a5efdc3906257c88ce62dba8d46c292908
|
||||
Subproject commit ab885eccd6c82a949e21cfab9af778c0fd44d0ae
|
@ -92,7 +92,6 @@ workspace "t5-gsc-utils"
|
||||
|
||||
pchheader "stdinc.hpp"
|
||||
pchsource "src/stdinc.cpp"
|
||||
buildoptions { "/Zm100 -Zm100" }
|
||||
|
||||
dependencies.imports()
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
143
src/component/user_info.cpp
Normal 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)
|
@ -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};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
67
src/utils/info_string.cpp
Normal 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
25
src/utils/info_string.hpp
Normal 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);
|
||||
};
|
||||
}
|
@ -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()
|
||||
{
|
||||
|
@ -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.
Loading…
x
Reference in New Issue
Block a user