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 - Release
steps: steps:
- name: Check out files - name: Check out files
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0
@ -27,10 +27,9 @@ jobs:
lfs: false lfs: false
- name: Add msbuild to PATH - name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2 uses: microsoft/setup-msbuild@v1.3.1
- name: Generate project files - name: Generate project files
#run: tools/premake5 vs2022 --ci-build
run: tools/premake5 vs2022 run: tools/premake5 vs2022
- name: Set up problem matching - 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 run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:PlatformTarget=x86 build/t5-gsc-utils.sln
- name: Upload ${{matrix.configuration}} binaries - name: Upload ${{matrix.configuration}} binaries
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3.1.3
with: with:
name: ${{matrix.configuration}} binaries name: ${{matrix.configuration}} binaries
path: | 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" pchheader "stdinc.hpp"
pchsource "src/stdinc.cpp" pchsource "src/stdinc.cpp"
buildoptions { "/Zm100 -Zm100" }
dependencies.imports() dependencies.imports()

View File

@ -29,7 +29,7 @@ namespace command
game::CmdArgs* get_cmd_args() 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() void main_handler()
@ -315,7 +315,7 @@ namespace command
gsc::method::add("tell", [](const scripting::entity& player, const std::string& msg) gsc::method::add("tell", [](const scripting::entity& player, const std::string& msg)
{ {
const auto entref = player.get_entity_reference(); 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"); throw std::runtime_error("Not a player entity");
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -19,205 +19,205 @@
namespace string namespace string
{ {
namespace namespace
{ {
// lua/lstrlib.c // lua/lstrlib.c
const char* getformat(const char* strfrmt, char* form) const char* getformat(const char* strfrmt, char* form)
{ {
const auto len = std::strspn(strfrmt, L_FMTFLAGSF "123456789.") + 1; const auto len = std::strspn(strfrmt, L_FMTFLAGSF "123456789.") + 1;
if (len >= MAX_FORMAT - 10) if (len >= MAX_FORMAT - 10)
{ {
throw std::runtime_error("invalid format (too long)"); throw std::runtime_error("invalid format (too long)");
} }
*(form++) = '%'; *(form++) = '%';
std::memcpy(form, strfrmt, len * sizeof(char)); std::memcpy(form, strfrmt, len * sizeof(char));
*(form + len) = '\0'; *(form + len) = '\0';
return strfrmt + len - 1; return strfrmt + len - 1;
} }
// lua/lstrlib.c // lua/lstrlib.c
const char* get_2_digits(const char* s) const char* get_2_digits(const char* s)
{ {
if (isdigit(static_cast<unsigned char>(*s))) if (isdigit(static_cast<unsigned char>(*s)))
{ {
s++; s++;
if (isdigit(static_cast<unsigned char>(*s))) if (isdigit(static_cast<unsigned char>(*s)))
{ {
s++; s++;
} }
} }
return s; return s;
} }
// lua/lstrlib.c // lua/lstrlib.c
void check_format(const char* form, const char* flags, int precision) void check_format(const char* form, const char* flags, int precision)
{ {
const char* spec = form + 1; const char* spec = form + 1;
spec += std::strspn(spec, flags); spec += std::strspn(spec, flags);
if (*spec != '0') if (*spec != '0')
{ {
spec = get_2_digits(spec); spec = get_2_digits(spec);
if (*spec == '.' && precision) if (*spec == '.' && precision)
{ {
spec++; spec++;
spec = get_2_digits(spec); spec = get_2_digits(spec);
} }
} }
if (!std::isalpha(static_cast<unsigned char>(*spec))) if (!std::isalpha(static_cast<unsigned char>(*spec)))
{ {
throw std::runtime_error(utils::string::va("invalid conversion specification: '%s'", form)); throw std::runtime_error(utils::string::va("invalid conversion specification: '%s'", form));
} }
} }
// partially lua/lstrlib.c // partially lua/lstrlib.c
std::string format_string(const std::string& fmt, const scripting::variadic_args& va) std::string format_string(const std::string& fmt, const scripting::variadic_args& va)
{ {
std::string buffer{}; std::string buffer{};
size_t va_index{}; size_t va_index{};
const char* strfrmt = fmt.data(); const char* strfrmt = fmt.data();
const char* strfrmt_end = strfrmt + fmt.size(); const char* strfrmt_end = strfrmt + fmt.size();
while (strfrmt < strfrmt_end) while (strfrmt < strfrmt_end)
{ {
if (*strfrmt != '%') if (*strfrmt != '%')
{ {
buffer.push_back(*strfrmt++); buffer.push_back(*strfrmt++);
} }
else if (*++strfrmt == '%') else if (*++strfrmt == '%')
{ {
buffer.push_back(*strfrmt++); buffer.push_back(*strfrmt++);
} }
else else
{ {
char form[MAX_FORMAT]{}; char form[MAX_FORMAT]{};
const char* flags = ""; const char* flags = "";
strfrmt = getformat(strfrmt, form); strfrmt = getformat(strfrmt, form);
switch (*strfrmt++) switch (*strfrmt++)
{ {
case 'd': case 'd':
case 'i': case 'i':
flags = L_FMTFLAGSI; flags = L_FMTFLAGSI;
goto intcase; goto intcase;
case 'u': case 'u':
case 'p': case 'p':
flags = L_FMTFLAGSU; flags = L_FMTFLAGSU;
goto intcase; goto intcase;
case 'o': case 'o':
case 'x': case 'x':
case 'X': case 'X':
flags = L_FMTFLAGSX; flags = L_FMTFLAGSX;
intcase: intcase:
{ {
check_format(form, flags, 1); check_format(form, flags, 1);
const auto value = va[va_index].as<int>(); const auto value = va[va_index].as<int>();
buffer.append(utils::string::va(form, value)); buffer.append(utils::string::va(form, value));
va_index++; va_index++;
break; break;
} }
case 'f': case 'f':
case 'F': case 'F':
case 'e': case 'e':
case 'E': case 'E':
case 'g': case 'g':
case 'G': case 'G':
{ {
check_format(form, L_FMTFLAGSF, 1); check_format(form, L_FMTFLAGSF, 1);
const auto value = va[va_index].as<float>(); const auto value = va[va_index].as<float>();
buffer.append(utils::string::va(form, value)); buffer.append(utils::string::va(form, value));
va_index++; va_index++;
break; break;
} }
case 'c': case 'c':
{ {
const auto value = va[va_index].as<int>(); const auto value = va[va_index].as<int>();
check_format(form, L_FMTFLAGSC, 0); check_format(form, L_FMTFLAGSC, 0);
buffer.append(utils::string::va(form, static_cast<char>(value))); buffer.append(utils::string::va(form, static_cast<char>(value)));
va_index++; va_index++;
break; break;
} }
case 's': case 's':
{ {
const auto str = va[va_index].as<std::string>(); const auto str = va[va_index].as<std::string>();
buffer.append(str); buffer.append(str);
va_index++; va_index++;
break; break;
} }
default: default:
{ {
throw std::runtime_error(utils::string::va("invalid conversion '%s' to 'format'", form)); throw std::runtime_error(utils::string::va("invalid conversion '%s' to 'format'", form));
} }
} }
} }
} }
return buffer; return buffer;
} }
} }
class component final : public component_interface class component final : public component_interface
{ {
public: public:
void post_unpack() override void post_unpack() override
{ {
gsc::function::add_multiple(format_string, "va", "string::va", gsc::function::add_multiple(format_string, "va", "string::va",
"formatstring", "string::format", "sprintf"); "formatstring", "string::format", "sprintf");
gsc::function::add("printf", [](const std::string& fmt, const scripting::variadic_args& va) gsc::function::add("printf", [](const std::string& fmt, const scripting::variadic_args& va)
{ {
printf("%s", format_string(fmt, va).data()); printf("%s", format_string(fmt, va).data());
}); });
gsc::function::add("print", [](const scripting::variadic_args& va) gsc::function::add("print", [](const scripting::variadic_args& va)
{ {
std::string buffer{}; std::string buffer{};
for (const auto& arg : va) for (const auto& arg : va)
{ {
buffer.append(utils::string::va("%s\t", arg.to_string().data())); 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_upper, "toupper", "string::to_upper");
gsc::function::add_multiple(utils::string::to_lower, "tolower", "string::to_lower"); 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::is_numeric", utils::string::is_numeric);
gsc::function::add("string::starts_with", utils::string::starts_with); gsc::function::add("string::starts_with", utils::string::starts_with);
gsc::function::add("string::ends_with", utils::string::ends_with); gsc::function::add("string::ends_with", utils::string::ends_with);
gsc::function::add("string::replace", utils::string::replace); gsc::function::add("string::replace", utils::string::replace);
gsc::function::add("string::regex_replace", [](const std::string& str, const std::regex& expr, gsc::function::add("string::regex_replace", [](const std::string& str, const std::regex& expr,
const std::string& with) const std::string& with)
{ {
return std::regex_replace(str, expr, with); return std::regex_replace(str, expr, with);
}); });
gsc::function::add("string::regex_match", [](const std::string& str, const std::regex& expr) gsc::function::add("string::regex_match", [](const std::string& str, const std::regex& expr)
{ {
scripting::array array_match{}; scripting::array array_match{};
std::smatch match{}; std::smatch match{};
if (std::regex_match(str, match, std::regex(expr))) if (std::regex_match(str, match, std::regex(expr)))
{ {
for (const auto& s : match) for (const auto& s : match)
{ {
array_match.push((s.str())); 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) gsc::function::add("string::regex_search", [](const std::string& str, const std::regex& expr)
{ {
return std::regex_search(str, expr); return std::regex_search(str, expr);
}); });
} }
}; };
} }
REGISTER_COMPONENT(string::component) 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<const char*(int index)> Cmd_Argv{0x0, 0x0};
WEAK symbol<void(const char* cmdName)> Cmd_RemoveCommand{0x5F1A90, 0x527EA0}; 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(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(int channel, const char* fmt, ...)> Com_Printf{0x43BF30, 0x4126C0};
WEAK symbol<void(const char* fmt, ...)> Com_Printf_NoFilter{0x566BC0, 0x64C260}; 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<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<void(int clientNum, const char* reason)> SV_GameDropClient{0x0, 0x0};
WEAK symbol<bool(int clientNum)> SV_IsTestClient{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, 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<void*(int valueIndex)> Sys_GetValue{0x67D4F0, 0x529EB0};
WEAK symbol<int()> Sys_Milliseconds{0x0, 0x0}; WEAK symbol<int()> Sys_Milliseconds{0x0, 0x0};

View File

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

View File

@ -101,12 +101,5 @@ namespace utils::http
return result; 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::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 static_cast<char>(tolower(input));
}); });
return text; return text;
} }
std::string to_upper(std::string 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 static_cast<char>(toupper(input));
}); });
return text; return text;
} }
@ -86,34 +86,23 @@ namespace utils::string
return result; 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; if (from.empty())
max--;
auto current = 0;
while (*in != 0 && current < max)
{ {
const auto color_index = (*(in + 1) - 48) >= 0xC ? 7 : (*(in + 1) - 48); return str;
if (*in == '^' && (color_index != 7 || *(in + 1) == '7'))
{
++in;
}
else
{
*out = *in;
++out;
++current;
}
++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 convert(const std::wstring& wstr)
{ {
std::string result; std::string result;
@ -139,24 +128,6 @@ namespace utils::string
return result; 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() 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 = " "); 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::string convert(const std::wstring& wstr);
std::wstring convert(const std::string& str); 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(); std::string get_timestamp();
} }

Binary file not shown.