feature(user_info): allow GSC to edit name/clantag

This commit is contained in:
6arelyFuture 2023-10-30 11:09:37 +01:00
parent 7d5060c8bc
commit 1aed1787ba
Signed by: Future
GPG Key ID: FA77F074E98D98A5
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

@ -19,129 +19,129 @@
namespace exception
{
namespace
{
thread_local struct
{
DWORD code = 0;
PVOID address = nullptr;
} exception_data;
namespace
{
thread_local struct
{
DWORD code = 0;
PVOID address = nullptr;
} exception_data;
void show_mouse_cursor()
{
while (ShowCursor(TRUE) < 0);
}
void show_mouse_cursor()
{
while (ShowCursor(TRUE) < 0);
}
void display_error_dialog()
{
std::string error_str = utils::string::va("Fatal error (0x%08X) at 0x%p.\n"
"A minidump has been written.\n\n",
exception_data.code, exception_data.address);
void display_error_dialog()
{
std::string error_str = utils::string::va("Fatal error (0x%08X) at 0x%p.\n"
"A minidump has been written.\n\n",
exception_data.code, exception_data.address);
error_str += "Make sure to update your graphics card drivers and install operating system updates!";
error_str += "Make sure to update your graphics card drivers and install operating system updates!";
utils::thread::suspend_other_threads();
show_mouse_cursor();
MessageBoxA(nullptr, error_str.data(), "Plutonium T5 ERROR", MB_ICONERROR);
TerminateProcess(GetCurrentProcess(), exception_data.code);
}
utils::thread::suspend_other_threads();
show_mouse_cursor();
MessageBoxA(nullptr, error_str.data(), "Plutonium T5 ERROR", MB_ICONERROR);
TerminateProcess(GetCurrentProcess(), exception_data.code);
}
void reset_state()
{
display_error_dialog();
}
void reset_state()
{
display_error_dialog();
}
size_t get_reset_state_stub()
{
static auto* stub = utils::hook::assemble([](utils::hook::assembler& a)
{
a.sub(esp, 0x10);
a.or_(esp, 0x8);
a.jmp(reset_state);
});
size_t get_reset_state_stub()
{
static auto* stub = utils::hook::assemble([](utils::hook::assembler& a)
{
a.sub(esp, 0x10);
a.or_(esp, 0x8);
a.jmp(reset_state);
});
return reinterpret_cast<size_t>(stub);
}
return reinterpret_cast<size_t>(stub);
}
std::string generate_crash_info(const LPEXCEPTION_POINTERS exceptioninfo)
{
std::string info{};
const auto line = [&info](const std::string& text)
{
info.append(text);
info.append("\r\n");
};
std::string generate_crash_info(const LPEXCEPTION_POINTERS exceptioninfo)
{
std::string info{};
const auto line = [&info](const std::string& text)
{
info.append(text);
info.append("\r\n");
};
line("Plutonium T5 Crash Dump");
line("");
line("Timestamp: "s + utils::string::get_timestamp());
line(utils::string::va("Exception: 0x%08X", exceptioninfo->ExceptionRecord->ExceptionCode));
line(utils::string::va("Address: 0x%lX", exceptioninfo->ExceptionRecord->ExceptionAddress));
line("Plutonium T5 Crash Dump");
line("");
line("Timestamp: "s + utils::string::get_timestamp());
line(utils::string::va("Exception: 0x%08X", exceptioninfo->ExceptionRecord->ExceptionCode));
line(utils::string::va("Address: 0x%lX", exceptioninfo->ExceptionRecord->ExceptionAddress));
#pragma warning(push)
#pragma warning(disable: 4996)
OSVERSIONINFOEXA version_info;
ZeroMemory(&version_info, sizeof(version_info));
version_info.dwOSVersionInfoSize = sizeof(version_info);
GetVersionExA(reinterpret_cast<LPOSVERSIONINFOA>(&version_info));
OSVERSIONINFOEXA version_info;
ZeroMemory(&version_info, sizeof(version_info));
version_info.dwOSVersionInfoSize = sizeof(version_info);
GetVersionExA(reinterpret_cast<LPOSVERSIONINFOA>(&version_info));
#pragma warning(pop)
line(utils::string::va("OS Version: %u.%u", version_info.dwMajorVersion, version_info.dwMinorVersion));
line(utils::string::va("OS Version: %u.%u", version_info.dwMajorVersion, version_info.dwMinorVersion));
return info;
}
return info;
}
void write_minidump(const LPEXCEPTION_POINTERS exceptioninfo)
{
const std::string crash_name = utils::string::va("minidumps/plutonium-t5-crash-%s.zip",
utils::string::get_timestamp().data());
void write_minidump(const LPEXCEPTION_POINTERS exceptioninfo)
{
const std::string crash_name = utils::string::va("minidumps/plutonium-t5-crash-%s.zip",
utils::string::get_timestamp().data());
utils::compression::zip::archive zip_file{};
zip_file.add("crash.dmp", create_minidump(exceptioninfo));
zip_file.add("info.txt", generate_crash_info(exceptioninfo));
zip_file.write(crash_name, "Plutonium T5 Crash Dump");
}
utils::compression::zip::archive zip_file{};
zip_file.add("crash.dmp", create_minidump(exceptioninfo));
zip_file.add("info.txt", generate_crash_info(exceptioninfo));
zip_file.write(crash_name, "Plutonium T5 Crash Dump");
}
bool is_harmless_error(const LPEXCEPTION_POINTERS exceptioninfo)
{
const auto code = exceptioninfo->ExceptionRecord->ExceptionCode;
return code == STATUS_INTEGER_OVERFLOW || code == STATUS_FLOAT_OVERFLOW || code == STATUS_SINGLE_STEP;
}
bool is_harmless_error(const LPEXCEPTION_POINTERS exceptioninfo)
{
const auto code = exceptioninfo->ExceptionRecord->ExceptionCode;
return code == STATUS_INTEGER_OVERFLOW || code == STATUS_FLOAT_OVERFLOW || code == STATUS_SINGLE_STEP;
}
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS exceptioninfo)
{
if (is_harmless_error(exceptioninfo))
{
return EXCEPTION_CONTINUE_EXECUTION;
}
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS exceptioninfo)
{
if (is_harmless_error(exceptioninfo))
{
return EXCEPTION_CONTINUE_EXECUTION;
}
write_minidump(exceptioninfo);
write_minidump(exceptioninfo);
exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode;
exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress;
exceptioninfo->ContextRecord->Eip = get_reset_state_stub();
exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode;
exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress;
exceptioninfo->ContextRecord->Eip = get_reset_state_stub();
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_EXECUTION;
}
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI set_unhandled_exception_filter_stub(LPTOP_LEVEL_EXCEPTION_FILTER)
{
// Don't register anything here...
return &exception_filter;
}
}
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI set_unhandled_exception_filter_stub(LPTOP_LEVEL_EXCEPTION_FILTER)
{
// Don't register anything here...
return &exception_filter;
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
class component final : public component_interface
{
public:
void post_unpack() override
{
#ifdef DEBUG
SetUnhandledExceptionFilter(exception_filter);
utils::hook::jump(reinterpret_cast<uintptr_t>(&SetUnhandledExceptionFilter), set_unhandled_exception_filter_stub);
SetUnhandledExceptionFilter(exception_filter);
utils::hook::jump(reinterpret_cast<uintptr_t>(&SetUnhandledExceptionFilter), set_unhandled_exception_filter_stub);
#endif
}
};
}
};
}
REGISTER_COMPONENT(exception::component)

View File

@ -6,336 +6,333 @@
#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
{
namespace
{
utils::hook::detour get_function_hook;
utils::hook::detour get_method_hook;
namespace
{
utils::hook::detour get_function_hook;
utils::hook::detour get_method_hook;
std::unordered_map<std::string, function_t> functions;
std::unordered_map<std::string, function_t> methods;
std::unordered_map<std::string, function_t> functions;
std::unordered_map<std::string, function_t> methods;
std::unordered_map<std::string, void*> function_wraps;
std::unordered_map<std::string, void*> method_wraps;
std::unordered_map<std::string, void*> function_wraps;
std::unordered_map<std::string, void*> method_wraps;
std::vector<scripting::script_value> get_arguments()
{
std::vector<scripting::script_value> args;
std::vector<scripting::script_value> get_arguments()
{
std::vector<scripting::script_value> args;
for (auto i = 0; static_cast<unsigned int>(i) < game::scr_VmPub->outparamcount; i++)
{
const auto value = game::scr_VmPub->top[-i];
args.push_back(value);
}
for (auto i = 0; static_cast<unsigned int>(i) < game::scr_VmPub->outparamcount; i++)
{
const auto value = game::scr_VmPub->top[-i];
args.push_back(value);
}
return args;
}
return args;
}
void return_value(const scripting::script_value& value)
{
if (game::scr_VmPub->outparamcount)
{
game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER);
}
void return_value(const scripting::script_value& value)
{
if (game::scr_VmPub->outparamcount)
{
game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER);
}
scripting::push_value(value);
}
scripting::push_value(value);
}
void call_function(const char* name)
{
if (functions.find(name) == functions.end())
{
return;
}
void call_function(const char* name)
{
if (functions.find(name) == functions.end())
{
return;
}
const auto args = get_arguments();
const auto& function = functions[name];
const auto args = get_arguments();
const auto& function = functions[name];
try
{
const auto value = function(args);
return_value(value);
}
catch (const std::exception& e)
{
game::Scr_Error(game::SCRIPTINSTANCE_SERVER, e.what(), false);
}
}
try
{
const auto value = function(args);
return_value(value);
}
catch (const std::exception& e)
{
game::Scr_Error(game::SCRIPTINSTANCE_SERVER, e.what(), false);
}
}
void call_method(const char* name, const game::scr_entref_t entref)
{
if (methods.find(name) == methods.end())
{
return;
}
void call_method(const char* name, const game::scr_entref_t entref)
{
if (methods.find(name) == methods.end())
{
return;
}
const auto args = get_arguments();
const auto& method = methods[name];
const auto args = get_arguments();
const auto& method = methods[name];
try
{
const scripting::entity entity = game::Scr_GetEntityId(
game::SCRIPTINSTANCE_SERVER, entref.entnum, entref.classnum, 0);
try
{
const scripting::entity entity = game::Scr_GetEntityId(
game::SCRIPTINSTANCE_SERVER, entref.entnum, entref.classnum, 0);
std::vector<scripting::script_value> args_{};
args_.push_back(entity);
for (const auto& arg : args)
{
args_.push_back(arg);
}
std::vector<scripting::script_value> args_{};
args_.push_back(entity);
for (const auto& arg : args)
{
args_.push_back(arg);
}
const auto value = method(args_);
return_value(value);
}
catch (const std::exception& e)
{
game::Scr_Error(game::SCRIPTINSTANCE_SERVER, e.what(), false);
}
}
const auto value = method(args_);
return_value(value);
}
catch (const std::exception& e)
{
game::Scr_Error(game::SCRIPTINSTANCE_SERVER, e.what(), false);
}
}
void* wrap_function_call(const std::string& name)
{
const auto name_ = utils::memory::get_allocator()->duplicate_string(name);
return utils::hook::assemble([name_](utils::hook::assembler& a)
{
a.pushad();
a.push(name_);
a.call(call_function);
a.add(esp, 0x4);
a.popad();
void* wrap_function_call(const std::string& name)
{
const auto name_ = utils::memory::get_allocator()->duplicate_string(name);
return utils::hook::assemble([name_](utils::hook::assembler& a)
{
a.pushad();
a.push(name_);
a.call(call_function);
a.add(esp, 0x4);
a.popad();
a.ret();
});
}
a.ret();
});
}
void* wrap_method_call(const std::string& name)
{
const auto name_ = utils::memory::get_allocator()->duplicate_string(name);
return utils::hook::assemble([name_](utils::hook::assembler& a)
{
a.pushad();
a.push(dword_ptr(esp, 0x24));
a.push(name_);
a.call(call_method);
a.add(esp, 0x8);
a.popad();
void* wrap_method_call(const std::string& name)
{
const auto name_ = utils::memory::get_allocator()->duplicate_string(name);
return utils::hook::assemble([name_](utils::hook::assembler& a)
{
a.pushad();
a.push(dword_ptr(esp, 0x24));
a.push(name_);
a.call(call_method);
a.add(esp, 0x8);
a.popad();
a.ret();
});
}
a.ret();
});
}
script_function get_function_stub(const char** name, int* type)
{
if (function_wraps.find(*name) != function_wraps.end())
{
return reinterpret_cast<script_function>(function_wraps[*name]);
}
script_function get_function_stub(const char** name, int* type)
{
if (function_wraps.find(*name) != function_wraps.end())
{
return reinterpret_cast<script_function>(function_wraps[*name]);
}
return get_function_hook.invoke<script_function>(name, type);
}
return get_function_hook.invoke<script_function>(name, type);
}
script_function get_method_stub(const char** name, int* type)
{
if (method_wraps.find(*name) != method_wraps.end())
{
return reinterpret_cast<script_function>(method_wraps[*name]);
}
script_function get_method_stub(const char** name, int* type)
{
if (method_wraps.find(*name) != method_wraps.end())
{
return reinterpret_cast<script_function>(method_wraps[*name]);
}
return get_method_hook.invoke<script_function>(name, type);
}
return get_method_hook.invoke<script_function>(name, type);
}
void print(int, const char* fmt, ...)
{
char buffer[2048];
void print(int, const char* fmt, ...)
{
char buffer[2048];
va_list ap;
va_start(ap, fmt);
va_list ap;
va_start(ap, fmt);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
va_end(ap);
va_end(ap);
printf("%s", buffer);
}
printf("%s", buffer);
}
utils::hook::detour scr_settings_hook;
void scr_settings_stub(int /*developer*/, int developer_script, int /*abort_on_error*/, int inst)
{
scr_settings_hook.invoke<void>(developer_script, developer_script, 0, inst);
}
utils::hook::detour scr_settings_hook;
void scr_settings_stub(int /*developer*/, int developer_script, int /*abort_on_error*/, int inst)
{
scr_settings_hook.invoke<void>(developer_script, developer_script, 0, inst);
}
utils::hook::detour scr_get_builtin_hook;
unsigned int scr_get_builtin_stub(int inst, game::sval_u func_name)
{
const auto type = *reinterpret_cast<uint8_t*>(func_name.block);
if (type != 28)
{
return scr_get_builtin_hook.invoke<unsigned int>(inst, func_name);
}
utils::hook::detour scr_get_builtin_hook;
unsigned int scr_get_builtin_stub(int inst, game::sval_u func_name)
{
const auto type = *reinterpret_cast<uint8_t*>(func_name.block);
if (type != 28)
{
return scr_get_builtin_hook.invoke<unsigned int>(inst, func_name);
}
const auto func_namea = *reinterpret_cast<void**>(reinterpret_cast<size_t>(func_name.block) + 4);
const auto typea = *reinterpret_cast<uint8_t*>(func_namea);
if (typea != 20)
{
return scr_get_builtin_hook.invoke<unsigned int>(inst, func_name);
}
const auto func_namea = *reinterpret_cast<void**>(reinterpret_cast<size_t>(func_name.block) + 4);
const auto typea = *reinterpret_cast<uint8_t*>(func_namea);
if (typea != 20)
{
return scr_get_builtin_hook.invoke<unsigned int>(inst, func_name);
}
const auto func_nameb = *reinterpret_cast<void**>(reinterpret_cast<size_t>(func_namea) + 4);
const auto typeb = *reinterpret_cast<uint8_t*>(func_nameb);
const auto func_nameb = *reinterpret_cast<void**>(reinterpret_cast<size_t>(func_namea) + 4);
const auto typeb = *reinterpret_cast<uint8_t*>(func_nameb);
if (typeb == 23) // script::function type call
{
const auto namespace_ = game::SL_ConvertToString(
*reinterpret_cast<unsigned int*>(reinterpret_cast<size_t>(func_nameb) + 4), game::SCRIPTINSTANCE_SERVER);
const auto name = game::SL_ConvertToString(
*reinterpret_cast<unsigned int*>(reinterpret_cast<size_t>(func_nameb) + 8), game::SCRIPTINSTANCE_SERVER);
if (typeb == 23) // script::function type call
{
const auto namespace_ = game::SL_ConvertToString(
*reinterpret_cast<unsigned int*>(reinterpret_cast<size_t>(func_nameb) + 4), game::SCRIPTINSTANCE_SERVER);
const auto name = game::SL_ConvertToString(
*reinterpret_cast<unsigned int*>(reinterpret_cast<size_t>(func_nameb) + 8), game::SCRIPTINSTANCE_SERVER);
const auto full_name = utils::string::va("%s::%s", namespace_, name);
if (functions.find(full_name) != functions.end())
{
return game::SL_GetString(full_name, 0, game::SCRIPTINSTANCE_SERVER);
}
}
const auto full_name = utils::string::va("%s::%s", namespace_, name);
if (functions.find(full_name) != functions.end())
{
return game::SL_GetString(full_name, 0, game::SCRIPTINSTANCE_SERVER);
}
}
return scr_get_builtin_hook.invoke<unsigned int>(inst, func_name);
}
return scr_get_builtin_hook.invoke<unsigned int>(inst, func_name);
}
// Scr_NotifyId doesn't exist, Scr_NotifyNum_Internal calls FindVariableId to get the variable id from entnum, classnum & clientNum
// to not have to recreate Scr_NotifyId we simply make FindVariableId return `entnum` (which in this case will be the id) if `clientNum` == -1
unsigned int find_variable_id_stub(int inst, int entnum, unsigned int classnum, int client_num)
{
if (client_num == -1)
{
return entnum;
}
// Scr_NotifyId doesn't exist, Scr_NotifyNum_Internal calls FindVariableId to get the variable id from entnum, classnum & clientNum
// to not have to recreate Scr_NotifyId we simply make FindVariableId return `entnum` (which in this case will be the id) if `clientNum` == -1
unsigned int find_variable_id_stub(int inst, int entnum, unsigned int classnum, int client_num)
{
if (client_num == -1)
{
return entnum;
}
return utils::hook::invoke<unsigned int>(SELECT_VALUE(0x5E96E0, 0x40BEF0), inst, entnum, classnum, client_num);
}
}
return utils::hook::invoke<unsigned int>(SELECT_VALUE(0x5E96E0, 0x40BEF0), inst, entnum, classnum, client_num);
}
}
namespace function
{
void add_internal(const std::string& name, const function_t& function)
{
const auto name_ = utils::string::to_lower(name);
functions[name_] = function;
const auto call_wrap = wrap_function_call(name_);
function_wraps[name_] = call_wrap;
}
}
namespace function
{
void add_internal(const std::string& name, const function_t& function)
{
const auto name_ = utils::string::to_lower(name);
functions[name_] = function;
const auto call_wrap = wrap_function_call(name_);
function_wraps[name_] = call_wrap;
}
}
namespace method
{
void add_internal(const std::string& name, const function_t& method)
{
const auto name_ = utils::string::to_lower(name);
methods[name_] = method;
const auto call_wrap = wrap_method_call(name_);
method_wraps[name_] = call_wrap;
}
}
namespace method
{
void add_internal(const std::string& name, const function_t& method)
{
const auto name_ = utils::string::to_lower(name);
methods[name_] = method;
const auto call_wrap = wrap_method_call(name_);
method_wraps[name_] = call_wrap;
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// Don't com_error on gsc errors
utils::hook::nop(SELECT_VALUE(0x5A17E1, 0x4D9BB1), 5);
utils::hook::jump(SELECT_VALUE(0x5DFC40, 0x568B90), print);
class component final : public component_interface
{
public:
void post_unpack() override
{
// Don't com_error on gsc errors
utils::hook::nop(SELECT_VALUE(0x5A17E1, 0x4D9BB1), 5);
utils::hook::jump(SELECT_VALUE(0x5DFC40, 0x568B90), print);
scr_settings_hook.create(SELECT_VALUE(0x4CEEA0, 0x55D010), scr_settings_stub);
scr_settings_hook.create(SELECT_VALUE(0x4CEEA0, 0x55D010), scr_settings_stub);
get_function_hook.create(utils::hook::extract<size_t>(SELECT_VALUE(0x8A02FB, 0x8DE11B) + 1), get_function_stub);
get_method_hook.create(utils::hook::extract<size_t>(SELECT_VALUE(0x8A052E, 0x8DE34E) + 1), get_method_stub);
get_function_hook.create(utils::hook::extract<size_t>(SELECT_VALUE(0x8A02FB, 0x8DE11B) + 1), get_function_stub);
get_method_hook.create(utils::hook::extract<size_t>(SELECT_VALUE(0x8A052E, 0x8DE34E) + 1), get_method_stub);
scr_get_builtin_hook.create(SELECT_VALUE(0x4ACAC0, 0x411490), scr_get_builtin_stub);
scr_get_builtin_hook.create(SELECT_VALUE(0x4ACAC0, 0x411490), scr_get_builtin_stub);
// \n******* script runtime error *******\n%s\n
utils::hook::set<char>(SELECT_VALUE(0x9FC5C0 + 40, 0xAABA68 + 40), '\n');
utils::hook::set<char>(SELECT_VALUE(0x9FC5C0 + 41, 0xAABA68 + 41), '\0');
// \n******* script runtime error *******\n%s\n
utils::hook::set<char>(SELECT_VALUE(0x9FC5C0 + 40, 0xAABA68 + 40), '\n');
utils::hook::set<char>(SELECT_VALUE(0x9FC5C0 + 41, 0xAABA68 + 41), '\0');
utils::hook::call(SELECT_VALUE(0x41D2B5, 0x416325), find_variable_id_stub);
utils::hook::call(SELECT_VALUE(0x41D2B5, 0x416325), find_variable_id_stub);
gsc::function::add("array", [](const scripting::variadic_args& va)
{
scripting::array array{};
gsc::function::add("array", [](const scripting::variadic_args& va)
{
scripting::array array{};
for (const auto& arg : va)
{
array.push(arg);
}
for (const auto& arg : va)
{
array.push(arg);
}
return array;
});
return array;
});
gsc::function::add_multiple([](const scripting::script_value& value)
{
return value.type_name();
}, "typeof", "type");
gsc::function::add_multiple([](const scripting::script_value& value)
{
return value.type_name();
}, "typeof", "type");
gsc::function::add("debug::get_var_count", []()
{
auto count = 0;
gsc::function::add("debug::get_var_count", []()
{
auto count = 0;
if (game::environment::is_sp())
{
for (auto i = 1; i < 0x5FFE; i++)
{
const auto var = game::scr_VarGlob->variableList_mp[i];
count += var.w.status != 0;
}
}
else
{
for (auto i = 1; i < 0x7FFE; i++)
{
const auto var = game::scr_VarGlob->variableList_mp[i];
count += var.w.status != 0;
}
}
if (game::environment::is_sp())
{
for (auto i = 1; i < 0x5FFE; i++)
{
const auto var = game::scr_VarGlob->variableList_mp[i];
count += var.w.status != 0;
}
}
else
{
for (auto i = 1; i < 0x7FFE; i++)
{
const auto var = game::scr_VarGlob->variableList_mp[i];
count += var.w.status != 0;
}
}
return count;
});
return count;
});
gsc::function::add("toint", [](const std::string& str, const scripting::variadic_args& va)
{
auto radix = 10;
if (va.size() > 0)
{
radix = va[0];
}
gsc::function::add("toint", [](const std::string& str, const scripting::variadic_args& va)
{
auto radix = 10;
if (!va.empty())
{
radix = va[0];
}
return static_cast<int>(std::strtoull(str.data(), nullptr, radix));
});
return static_cast<int>(std::strtoull(str.data(), nullptr, radix));
});
gsc::function::add("os::date", [](const scripting::variadic_args& va)
{
std::string format = "%Y-%m-%dT%H:%M:%S%z";
if (va.size() > 0)
{
format = va[0].as<std::string>();
}
gsc::function::add("os::date", [](const scripting::variadic_args& va)
{
std::string format = "%Y-%m-%dT%H:%M:%S%z";
if (!va.empty())
{
format = va[0].as<std::string>();
}
tm ltime{};
char timestamp[MAX_PATH] = {0};
const auto time = _time64(nullptr);
tm ltime{};
char timestamp[MAX_PATH] = {0};
const auto time = _time64(nullptr);
_localtime64_s(&ltime, &time);
std::strftime(timestamp, sizeof(timestamp) - 1, format.data(), &ltime);
_localtime64_s(&ltime, &time);
std::strftime(timestamp, sizeof(timestamp) - 1, format.data(), &ltime);
return std::string(timestamp);
});
}
};
return std::string(timestamp);
});
}
};
}
REGISTER_COMPONENT(gsc::component)

View File

@ -13,165 +13,165 @@
namespace http
{
std::unordered_map<uint64_t, bool> active_requests{};
uint64_t request_id{};
std::unordered_map<uint64_t, bool> active_requests{};
uint64_t request_id{};
class component final : public component_interface
{
public:
void post_unpack() override
{
scripting::on_shutdown([]()
{
active_requests.clear();
});
class component final : public component_interface
{
public:
void post_unpack() override
{
scripting::on_shutdown([]()
{
active_requests.clear();
});
gsc::function::add_multiple([](const std::string& url)
{
const auto id = request_id++;
active_requests[id] = true;
gsc::function::add_multiple([](const std::string& url)
{
const auto id = request_id++;
active_requests[id] = true;
const auto object = scripting::object{};
const auto object_id = object.get_entity_id();
const auto object = scripting::object{};
const auto object_id = object.get_entity_id();
scheduler::once([id, object_id, url]()
{
const auto data = utils::http::get_data(url);
scheduler::once([id, object_id, data]()
{
if (active_requests.find(id) == active_requests.end())
{
return;
}
scheduler::once([id, object_id, url]()
{
const auto data = utils::http::get_data(url);
scheduler::once([id, object_id, data]()
{
if (active_requests.find(id) == active_requests.end())
{
return;
}
if (!data.has_value())
{
scripting::notify(object_id, "done", {{}, false, "Unknown error"});
return;
}
if (!data.has_value())
{
scripting::notify(object_id, "done", {{}, false, "Unknown error"});
return;
}
const auto& result = data.value();
const auto error = curl_easy_strerror(result.code);
const auto& result = data.value();
const auto error = curl_easy_strerror(result.code);
if (result.code != CURLE_OK)
{
scripting::notify(object_id, "done", {{}, false, error});
return;
}
if (result.code != CURLE_OK)
{
scripting::notify(object_id, "done", {{}, false, error});
return;
}
if (result.buffer.size() >= 0x5000)
{
printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast<int>(result.buffer.size()));
}
if (result.buffer.size() >= 0x5000)
{
printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast<int>(result.buffer.size()));
}
scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true});
}, scheduler::pipeline::server);
}, scheduler::pipeline::async);
scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true});
}, scheduler::pipeline::server);
}, scheduler::pipeline::async);
return object;
}, "http::get", "httpget", "curl");
return object;
}, "http::get", "httpget", "curl");
gsc::function::add("http::request", [](const std::string& url, const scripting::variadic_args& va)
{
const auto id = request_id++;
active_requests[id] = true;
gsc::function::add("http::request", [](const std::string& url, const scripting::variadic_args& va)
{
const auto id = request_id++;
active_requests[id] = true;
const auto object = scripting::object{};
const auto object_id = object.get_entity_id();
const auto object = scripting::object{};
const auto object_id = object.get_entity_id();
std::string fields_string{};
std::unordered_map<std::string, std::string> headers_map{};
std::string fields_string{};
std::unordered_map<std::string, std::string> headers_map{};
if (va.size() > 0)
{
const auto options = va[0].as<scripting::array>();
if (va.size() > 0)
{
const auto options = va[0].as<scripting::array>();
const auto fields = options["parameters"];
const auto body = options["body"];
const auto headers = options["headers"];
const auto fields = options["parameters"];
const auto body = options["body"];
const auto headers = options["headers"];
if (fields.is<scripting::array>())
{
const auto fields_ = fields.as<scripting::array>();
const auto keys = fields_.get_keys();
if (fields.is<scripting::array>())
{
const auto fields_ = fields.as<scripting::array>();
const auto keys = fields_.get_keys();
for (const auto& key : keys)
{
if (!key.is<std::string>())
{
continue;
}
for (const auto& key : keys)
{
if (!key.is<std::string>())
{
continue;
}
const auto key_ = key.as<std::string>();
const auto value = fields_[key].to_string();
fields_string += key_ + "=" + value + "&";
}
const auto key_ = key.as<std::string>();
const auto value = fields_[key].to_string();
fields_string += key_ + "=" + value + "&";
}
}
}
if (body.is<std::string>())
{
fields_string = body.as<std::string>();
}
if (body.is<std::string>())
{
fields_string = body.as<std::string>();
}
if (headers.is<scripting::array>())
{
const auto headers_ = headers.as<scripting::array>();
const auto keys = headers_.get_keys();
if (headers.is<scripting::array>())
{
const auto headers_ = headers.as<scripting::array>();
const auto keys = headers_.get_keys();
for (const auto& key : keys)
{
if (!key.is<std::string>())
{
continue;
}
for (const auto& key : keys)
{
if (!key.is<std::string>())
{
continue;
}
const auto key_ = key.as<std::string>();
const auto value = headers_[key].to_string();
const auto key_ = key.as<std::string>();
const auto value = headers_[key].to_string();
headers_map[key_] = value;
}
}
}
headers_map[key_] = value;
}
}
}
scheduler::once([id, object_id, url, fields_string, headers_map]()
{
const auto data = utils::http::get_data(url, fields_string, headers_map);
scheduler::once([data, object_id, id]
{
if (active_requests.find(id) == active_requests.end())
{
return;
}
scheduler::once([id, object_id, url, fields_string, headers_map]()
{
const auto data = utils::http::get_data(url, fields_string, headers_map);
scheduler::once([data, object_id, id]
{
if (active_requests.find(id) == active_requests.end())
{
return;
}
if (!data.has_value())
{
scripting::notify(object_id, "done", {{}, false, "Unknown error"});
return;
}
if (!data.has_value())
{
scripting::notify(object_id, "done", {{}, false, "Unknown error"});
return;
}
const auto& result = data.value();
const auto error = curl_easy_strerror(result.code);
const auto& result = data.value();
const auto error = curl_easy_strerror(result.code);
if (result.code != CURLE_OK)
{
scripting::notify(object_id, "done", {{}, false, error});
return;
}
if (result.code != CURLE_OK)
{
scripting::notify(object_id, "done", {{}, false, error});
return;
}
if (result.buffer.size() >= 0x5000)
{
printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast<int>(result.buffer.size()));
}
if (result.buffer.size() >= 0x5000)
{
printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast<int>(result.buffer.size()));
}
scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true});
}, scheduler::pipeline::server);
}, scheduler::pipeline::async);
scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true});
}, scheduler::pipeline::server);
}, scheduler::pipeline::async);
return object;
});
}
};
return object;
});
}
};
}
REGISTER_COMPONENT(http::component)

View File

@ -10,57 +10,57 @@
namespace io
{
class component final : public component_interface
{
public:
void post_unpack() override
{
const auto fs_basegame = game::Dvar_FindVar("fs_basegame");
std::filesystem::current_path(fs_basegame->current.string);
class component final : public component_interface
{
public:
void post_unpack() override
{
const auto fs_basegame = game::Dvar_FindVar("fs_basegame");
std::filesystem::current_path(fs_basegame->current.string);
gsc::function::add_multiple([](const std::string& file, const std::string& data,
const scripting::variadic_args& va)
{
auto append = false;
gsc::function::add_multiple([](const std::string& file, const std::string& data,
const scripting::variadic_args& va)
{
auto append = false;
if (va.size() > 0)
{
append = va[0];
}
if (va.size() > 0)
{
append = va[0];
}
return utils::io::write_file(file, data, append);
}, "writefile", "io::write_file");
return utils::io::write_file(file, data, append);
}, "writefile", "io::write_file");
gsc::function::add_multiple([](const std::string& file, const std::string& data)
{
return utils::io::write_file(file, data, true);
}, "appendfile", "io::append_file");
gsc::function::add_multiple([](const std::string& file, const std::string& data)
{
return utils::io::write_file(file, data, true);
}, "appendfile", "io::append_file");
gsc::function::add_multiple(utils::io::file_exists, "fileexists", "io::file_exists");
gsc::function::add_multiple(utils::io::move_file, "movefile", "io::move_file");
gsc::function::add_multiple(utils::io::file_size, "filesize", "io::file_size");
gsc::function::add_multiple(utils::io::create_directory, "createdirectory", "io::create_directory");
gsc::function::add_multiple(utils::io::directory_exists, "directoryexists", "io::directory_exists");
gsc::function::add_multiple(utils::io::directory_is_empty, "directoryisempty", "io::directory_is_empty");
gsc::function::add_multiple(utils::io::list_files, "listfiles", "io::list_files");
gsc::function::add_multiple(utils::io::remove_file, "removefile", "io::remove_file");
gsc::function::add_multiple(utils::io::file_exists, "fileexists", "io::file_exists");
gsc::function::add_multiple(utils::io::move_file, "movefile", "io::move_file");
gsc::function::add_multiple(utils::io::file_size, "filesize", "io::file_size");
gsc::function::add_multiple(utils::io::create_directory, "createdirectory", "io::create_directory");
gsc::function::add_multiple(utils::io::directory_exists, "directoryexists", "io::directory_exists");
gsc::function::add_multiple(utils::io::directory_is_empty, "directoryisempty", "io::directory_is_empty");
gsc::function::add_multiple(utils::io::list_files, "listfiles", "io::list_files");
gsc::function::add_multiple(utils::io::remove_file, "removefile", "io::remove_file");
gsc::function::add_multiple([](const std::filesystem::path& src, const scripting::variadic_args& va)
{
bool recursive = false;
if (va.size() > 0)
{
recursive = va[0];
}
gsc::function::add_multiple([](const std::filesystem::path& src, const scripting::variadic_args& va)
{
bool recursive = false;
if (va.size() > 0)
{
recursive = va[0];
}
utils::io::remove_directory(src, recursive);
}, "removedirectory", "io::remove_directory");
utils::io::remove_directory(src, recursive);
}, "removedirectory", "io::remove_directory");
gsc::function::add_multiple(utils::io::copy_folder, "copyfolder", "io::copy_folder");
gsc::function::add_multiple(utils::io::copy_folder, "copydirectory", "io::copy_directory");
gsc::function::add_multiple(static_cast<std::string(*)(const std::string&)>(utils::io::read_file), "readfile", "io::read_file");
}
};
gsc::function::add_multiple(utils::io::copy_folder, "copyfolder", "io::copy_folder");
gsc::function::add_multiple(utils::io::copy_folder, "copydirectory", "io::copy_directory");
gsc::function::add_multiple(static_cast<std::string(*)(const std::string&)>(utils::io::read_file), "readfile", "io::read_file");
}
};
}
REGISTER_COMPONENT(io::component)

View File

@ -19,205 +19,205 @@
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)");
}
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;
}
*(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<unsigned char>(*s)))
{
s++;
if (isdigit(static_cast<unsigned char>(*s)))
{
s++;
}
}
// lua/lstrlib.c
const char* get_2_digits(const char* s)
{
if (isdigit(static_cast<unsigned char>(*s)))
{
s++;
if (isdigit(static_cast<unsigned char>(*s)))
{
s++;
}
}
return 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<unsigned char>(*spec)))
{
throw std::runtime_error(utils::string::va("invalid conversion specification: '%s'", form));
}
}
// 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<unsigned char>(*spec)))
{
throw std::runtime_error(utils::string::va("invalid conversion specification: '%s'", form));
}
}
// partially lua/lstrlib.c
std::string format_string(const std::string& fmt, const scripting::variadic_args& va)
{
std::string buffer{};
size_t va_index{};
const char* strfrmt = fmt.data();
const char* strfrmt_end = strfrmt + fmt.size();
// partially lua/lstrlib.c
std::string format_string(const std::string& fmt, const scripting::variadic_args& va)
{
std::string buffer{};
size_t va_index{};
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);
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 = va[va_index].as<int>();
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 = va[va_index].as<float>();
buffer.append(utils::string::va(form, value));
va_index++;
break;
}
case 'c':
{
const auto value = va[va_index].as<int>();
check_format(form, L_FMTFLAGSC, 0);
buffer.append(utils::string::va(form, static_cast<char>(value)));
va_index++;
break;
}
case 's':
{
const auto str = va[va_index].as<std::string>();
buffer.append(str);
va_index++;
break;
}
default:
{
throw std::runtime_error(utils::string::va("invalid conversion '%s' to 'format'", 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 = va[va_index].as<int>();
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 = va[va_index].as<float>();
buffer.append(utils::string::va(form, value));
va_index++;
break;
}
case 'c':
{
const auto value = va[va_index].as<int>();
check_format(form, L_FMTFLAGSC, 0);
buffer.append(utils::string::va(form, static_cast<char>(value)));
va_index++;
break;
}
case 's':
{
const auto str = va[va_index].as<std::string>();
buffer.append(str);
va_index++;
break;
}
default:
{
throw std::runtime_error(utils::string::va("invalid conversion '%s' to 'format'", form));
}
}
}
}
return buffer;
}
}
return buffer;
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
gsc::function::add_multiple(format_string, "va", "string::va",
"formatstring", "string::format", "sprintf");
class component final : public component_interface
{
public:
void post_unpack() override
{
gsc::function::add_multiple(format_string, "va", "string::va",
"formatstring", "string::format", "sprintf");
gsc::function::add("printf", [](const std::string& fmt, const scripting::variadic_args& va)
{
printf("%s", format_string(fmt, va).data());
});
gsc::function::add("printf", [](const std::string& fmt, const scripting::variadic_args& va)
{
printf("%s", format_string(fmt, va).data());
});
gsc::function::add("print", [](const scripting::variadic_args& va)
{
std::string buffer{};
gsc::function::add("print", [](const scripting::variadic_args& va)
{
std::string buffer{};
for (const auto& arg : va)
{
buffer.append(utils::string::va("%s\t", arg.to_string().data()));
}
for (const auto& arg : va)
{
buffer.append(utils::string::va("%s\t", arg.to_string().data()));
}
printf("%s\n", buffer.data());
});
printf("%s\n", buffer.data());
});
gsc::function::add_multiple(utils::string::to_upper, "toupper", "string::to_upper");
gsc::function::add_multiple(utils::string::to_lower, "tolower", "string::to_lower");
gsc::function::add_multiple(utils::string::to_upper, "toupper", "string::to_upper");
gsc::function::add_multiple(utils::string::to_lower, "tolower", "string::to_lower");
gsc::function::add("string::is_numeric", utils::string::is_numeric);
gsc::function::add("string::starts_with", utils::string::starts_with);
gsc::function::add("string::ends_with", utils::string::ends_with);
gsc::function::add("string::replace", utils::string::replace);
gsc::function::add("string::is_numeric", utils::string::is_numeric);
gsc::function::add("string::starts_with", utils::string::starts_with);
gsc::function::add("string::ends_with", utils::string::ends_with);
gsc::function::add("string::replace", utils::string::replace);
gsc::function::add("string::regex_replace", [](const std::string& str, const std::regex& expr,
const std::string& with)
{
return std::regex_replace(str, expr, with);
});
gsc::function::add("string::regex_replace", [](const std::string& str, const std::regex& expr,
const std::string& with)
{
return std::regex_replace(str, expr, with);
});
gsc::function::add("string::regex_match", [](const std::string& str, const std::regex& expr)
{
scripting::array array_match{};
std::smatch match{};
gsc::function::add("string::regex_match", [](const std::string& str, const std::regex& expr)
{
scripting::array array_match{};
std::smatch match{};
if (std::regex_match(str, match, std::regex(expr)))
{
for (const auto& s : match)
{
array_match.push((s.str()));
}
}
if (std::regex_match(str, match, std::regex(expr)))
{
for (const auto& s : match)
{
array_match.push((s.str()));
}
}
return array_match;
});
return array_match;
});
gsc::function::add("string::regex_search", [](const std::string& str, const std::regex& expr)
{
return std::regex_search(str, expr);
});
}
};
gsc::function::add("string::regex_search", [](const std::string& str, const std::regex& expr)
{
return std::regex_search(str, expr);
});
}
};
}
REGISTER_COMPONENT(string::component)

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,20 +35,20 @@ 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));
});
});
return text;
}
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));
});
});
return text;
}
@ -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;
}
++in;
return str;
}
*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.