Organize scripts and add TWS plugins with HTTP timeouts

Vendor pinned T5 and T6 plugin sources under the new plugins layout. Add native HTTP deadlines, status and timing diagnostics, telemetry integration, and build/test tooling.
This commit is contained in:
2026-10-09 11:02:20 -04:00
parent b172086301
commit 1121b44857
253 changed files with 28028 additions and 59 deletions
@@ -0,0 +1,352 @@
#include "stdinc.hpp"
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "command.hpp"
#include "gsc.hpp"
#include "scripting.hpp"
#include "scheduler.hpp"
#include <utils/string.hpp>
#include <utils/memory.hpp>
#include <utils/hook.hpp>
namespace command
{
namespace
{
std::unordered_map<std::string, std::function<void(params&)>> handlers;
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
std::vector<std::string> script_commands;
std::vector<std::string> script_sv_commands;
utils::memory::allocator allocator;
utils::hook::detour client_command_hook;
std::vector<scripting::function> chat_callbacks;
game::CmdArgs* get_cmd_args()
{
return static_cast<game::CmdArgs*>(game::Sys_GetValue(4));
}
void main_handler()
{
params params = {};
const auto command = utils::string::to_lower(params[0]);
if (handlers.find(command) != handlers.end())
{
handlers[command](params);
}
}
bool can_add_callback = true;
bool handle_chat_command(const int client_num, const params_sv& params)
{
const auto _0 = gsl::finally([]
{
can_add_callback = true;
});
can_add_callback = false;
auto hide = false;
const scripting::entity player = game::Scr_GetEntityId(game::SCRIPTINSTANCE_SERVER, client_num, 0, 0);
const auto command = utils::string::to_lower(params[0]);
const auto message = params.join(1).substr(1);
for (const auto& callback : chat_callbacks)
{
const auto res = callback(player, {message, command == "say_team"});
if (res.is<bool>() && !res.as<bool>())
{
hide = true;
}
}
return hide;
}
void client_command_stub(const int client_num)
{
params_sv params = {};
if (params.size() < 1)
{
client_command_hook.invoke<void>(client_num);
return;
}
const auto command = utils::string::to_lower(params[0]);
if (params.size() > 1 && (command == "say" || command == "say_team") &&
handle_chat_command(client_num, params))
{
return;
}
if (handlers_sv.find(command) != handlers_sv.end())
{
handlers_sv[command](client_num, params);
}
client_command_hook.invoke<void>(client_num);
}
void add_script_command(const std::string& name, const std::function<void(const params&)>& callback)
{
script_commands.push_back(name);
const auto name_ = allocator.duplicate_string(name);
add(name_, callback);
}
void add_script_sv_command(const std::string& name, const std::function<void(int, const params_sv&)>& callback)
{
script_sv_commands.push_back(name);
add_sv(name, callback);
}
void clear()
{
for (const auto& name : script_commands)
{
handlers.erase(name);
game::Cmd_RemoveCommand(name.data());
}
for (const auto& name : script_sv_commands)
{
handlers_sv.erase(name);
}
allocator.clear();
script_commands.clear();
script_sv_commands.clear();
chat_callbacks.clear();
}
}
params::params()
: nesting_(get_cmd_args()->nesting)
{
}
int params::size() const
{
const auto cmd_args = get_cmd_args();
return cmd_args->argc[cmd_args->nesting];
}
const char* params::get(int index) const
{
if (index >= this->size())
{
return "";
}
const auto cmd_args = get_cmd_args();
return cmd_args->argv[this->nesting_][index];
}
std::string params::join(int index) const
{
std::string result = {};
for (auto i = index; i < this->size(); i++)
{
if (i > index)
{
result.append(" ");
}
result.append(this->get(i));
}
return result;
}
std::vector<std::string> params::get_all() const
{
std::vector<std::string> params_;
for (auto i = 0; i < this->size(); i++)
{
params_.push_back(this->get(i));
}
return params_;
}
params_sv::params_sv()
: nesting_(game::sv_cmd_args->nesting)
{
}
int params_sv::size() const
{
return game::sv_cmd_args->argc[this->nesting_];
}
const char* params_sv::get(const int index) const
{
if (index >= this->size())
{
return "";
}
return game::sv_cmd_args->argv[this->nesting_][index];
}
std::string params_sv::join(const int index) const
{
std::string result = {};
for (auto i = index; i < this->size(); i++)
{
if (i > index)
{
result.append(" ");
}
result.append(this->get(i));
}
return result;
}
std::vector<std::string> params_sv::get_all() const
{
std::vector<std::string> params_;
for (auto i = 0; i < this->size(); i++)
{
params_.push_back(this->get(i));
}
return params_;
}
void add_raw(const char* name, void (*callback)())
{
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_t>());
}
void add(const char* name, std::function<void(params&)> callback)
{
const auto command = utils::string::to_lower(name);
if (handlers.find(command) == handlers.end())
{
add_raw(name, main_handler);
}
handlers[command] = callback;
}
void add_sv(const std::string& name, std::function<void(int, const params_sv&)> callback)
{
const auto command = utils::string::to_lower(name);
if (handlers_sv.find(command) == handlers_sv.end())
{
handlers_sv[command] = std::move(callback);
}
}
void execute(std::string command, const bool sync)
{
command += "\n";
if (sync)
{
game::Cmd_ExecuteSingleCommand(0, 0, command.data());
}
else
{
game::Cbuf_AddText(0, command.data());
}
}
class component final : public component_interface
{
public:
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
scripting::on_shutdown(clear);
client_command_hook.create(SELECT_VALUE(0x4AF770, 0x63DB70), client_command_stub);
gsc::function::add_multiple([](const std::string& command)
{
execute(command, false);
}, "executecommand", "command::execute");
gsc::function::add_multiple([](const std::string& name, const scripting::function& function)
{
command::add_script_command(name, [function](const command::params& params)
{
const auto params_ = params.get_all();
scheduler::once([=]()
{
scripting::array array;
for (auto i = 0; i < params.size(); i++)
{
array.emplace_back(params[i]);
}
function({array});
});
});
}, "addcommand", "command::add");
gsc::function::add_multiple([](const std::string& name, const scripting::function& function)
{
command::add_script_sv_command(name, [function](const int client_num, const command::params_sv& params)
{
const auto params_ = params.get_all();
scheduler::once([=]()
{
const scripting::entity player = game::Scr_GetEntityId(game::SCRIPTINSTANCE_SERVER, client_num, 0, 0);
scripting::array array;
for (auto i = 0; i < params.size(); i++)
{
array.emplace_back(params[i]);
}
function(player, {array});
}, scheduler::pipeline::server);
});
}, "addclientcommand", "command::add_sv");
gsc::method::add("tell", [](const scripting::entity& player, const std::string& msg)
{
const auto entref = player.get_entity_reference();
if (entref.classnum)
{
throw std::runtime_error("Not a player entity");
}
game::SV_GameSendServerCommand(entref.entnum, 0, utils::string::va("h \"%s\"", msg.data()));
});
gsc::function::add("say", [](const std::string& msg)
{
game::SV_GameSendServerCommand(-1, 0, utils::string::va("h \"%s\"", msg.data()));
});
gsc::function::add("sendservercommand", game::SV_GameSendServerCommand.get());
gsc::function::add_multiple([](const scripting::function& callback)
{
if (!can_add_callback)
{
throw std::runtime_error("Cannot add a callback in this context");
}
chat_callbacks.push_back(callback);
}, "onplayersay", "command::on_player_say");
}
};
}
REGISTER_COMPONENT(command::component)
@@ -0,0 +1,48 @@
#pragma once
namespace command
{
class params
{
public:
params();
int size() const;
const char* get(int index) const;
std::string join(int index) const;
std::vector<std::string> get_all() const;
const char* operator[](const int index) const
{
return this->get(index);
}
private:
int nesting_;
};
class params_sv
{
public:
params_sv();
int size() const;
const char* get(int index) const;
std::string join(int index) const;
std::vector<std::string> get_all() const;
const char* operator[](const int index) const
{
return this->get(index);
}
private:
int nesting_;
};
void add_raw(const char* name, void (*callback)());
void add(const char* name, std::function<void(params&)> callback);
void add_sv(const std::string& name, std::function<void(int, const params_sv&)> callback);
void execute(std::string command, const bool sync = false);
}
@@ -0,0 +1,147 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "scheduler.hpp"
#include "gsc.hpp"
#include "json.hpp"
#include "scripting.hpp"
#include "game/scripting/execution.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/thread.hpp>
#include <utils/compression.hpp>
#include <exception/minidump.hpp>
namespace exception
{
namespace
{
thread_local struct
{
DWORD code = 0;
PVOID address = nullptr;
} exception_data;
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);
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);
}
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);
});
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");
};
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));
#pragma warning(pop)
line(utils::string::va("OS Version: %u.%u", version_info.dwMajorVersion, version_info.dwMinorVersion));
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());
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;
}
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS exceptioninfo)
{
if (is_harmless_error(exceptioninfo))
{
return EXCEPTION_CONTINUE_EXECUTION;
}
write_minidump(exceptioninfo);
exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode;
exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress;
exceptioninfo->ContextRecord->Eip = get_reset_state_stub();
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;
}
}
class component final : public component_interface
{
public:
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
//#ifdef DEBUG
SetUnhandledExceptionFilter(exception_filter);
utils::hook::jump(reinterpret_cast<uintptr_t>(&SetUnhandledExceptionFilter), set_unhandled_exception_filter_stub);
//#endif
}
};
}
REGISTER_COMPONENT(exception::component)
@@ -0,0 +1,277 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "game/structs.hpp"
#include "game/game.hpp"
#include "gsc.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
namespace gsc
{
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;
namespace
{
utils::hook::detour get_function_hook;
utils::hook::detour get_method_hook;
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);
}
return args;
}
void return_value(const scripting::script_value& value)
{
if (game::scr_VmPub->outparamcount)
{
game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER);
}
scripting::push_value(value);
}
script_function get_function_stub(const char** name, int* type)
{
const auto iter = function_wraps.find(*name);
if (iter != function_wraps.end())
{
return reinterpret_cast<script_function>(iter->second);
}
return get_function_hook.invoke<script_function>(name, type);
}
script_function get_method_stub(const char** name, int* type)
{
const auto iter = method_wraps.find(*name);
if (iter != method_wraps.end())
{
return reinterpret_cast<script_function>(iter->second);
}
return get_method_hook.invoke<script_function>(name, type);
}
void print(int, const char* fmt, ...)
{
char buffer[2048];
va_list ap;
va_start(ap, fmt);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
va_end(ap);
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_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_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);
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);
}
// 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
std::size_t find_variable_id_stub_plutonium = 0;
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>(find_variable_id_stub_plutonium, inst, entnum, classnum, client_num);
}
}
void call_function(const function_t& function)
{
const auto args = get_arguments();
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 function_t& method, const game::scr_entref_t entref)
{
const auto args = get_arguments();
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);
}
const auto value = method(args_);
return_value(value);
}
catch (const std::exception& e)
{
game::Scr_Error(game::SCRIPTINSTANCE_SERVER, e.what(), false);
}
}
class component final : public component_interface
{
public:
void on_startup([[maybe_unused]] plugin::plugin* plugin) 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);
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);
// \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');
find_variable_id_stub_plutonium = utils::hook::extract<size_t>(SELECT_VALUE(0x41D2B5, 0x416325) + 1);
utils::hook::call(SELECT_VALUE(0x41D2B5, 0x416325), find_variable_id_stub);
gsc::function::add("array", [](const scripting::variadic_args& va)
{
scripting::array array{};
for (const auto& arg : va)
{
array.emplace_back(arg);
}
return array;
});
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;
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;
});
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));
});
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);
_localtime64_s(&ltime, &time);
std::strftime(timestamp, sizeof(timestamp) - 1, format.data(), &ltime);
return std::string(timestamp);
});
}
};
}
REGISTER_COMPONENT(gsc::component)
@@ -0,0 +1,128 @@
#pragma once
#include "game/scripting/array.hpp"
#include "game/scripting/execution.hpp"
#if _HAS_CXX20
namespace gsc
{
using function_t = std::function<scripting::script_value(const scripting::function_arguments& args)>;
using script_function = void(*)(game::scr_entref_t);
extern std::unordered_map<std::string, function_t> functions;
extern std::unordered_map<std::string, function_t> methods;
extern std::unordered_map<std::string, void*> function_wraps;
extern std::unordered_map<std::string, void*> method_wraps;
// auto = []{} forces template reevaluation
template <class... Args, std::size_t... I, auto = []{}>
auto wrap_function(const std::function<void(Args...)>& f, std::index_sequence<I...>)
{
return [f]([[maybe_unused]] const scripting::function_arguments& args)
{
f(args[I]...);
return scripting::script_value{};
};
}
template <class... Args, std::size_t... I, auto = []{}>
auto wrap_function(const std::function<scripting::script_value(Args...)>& f, std::index_sequence<I...>)
{
return [f]([[maybe_unused]] const scripting::function_arguments& args)
{
return f(args[I]...);
};
}
template <typename R, class... Args, std::size_t... I, auto = []{}>
auto wrap_function(const std::function<R(Args...)>& f, std::index_sequence<I...>)
{
return [f]([[maybe_unused]] const scripting::function_arguments& args)
{
return scripting::script_value{f(args[I]...)};
};
}
template <typename R, class... Args, auto = []{}>
auto wrap_function(const std::function<R(Args...)>& f)
{
return wrap_function(f, std::index_sequence_for<Args...>{});
}
template <class F, auto = []{}>
auto wrap_function(F f)
{
return wrap_function(std::function(f));
}
void call_function(const function_t& function);
void call_method(const function_t& method, const game::scr_entref_t entref);
namespace function
{
template <typename F, auto = []{}>
void add_internal(const std::string& name, F function)
{
static auto called = false;
assert(!called);
called = true;
const auto lower = utils::string::to_lower(name);
static const auto [iterator, was_inserted] = functions.insert(std::make_pair(lower, function));
static const auto& function_ptr = iterator->second;
function_wraps[lower] = []()
{
call_function(function_ptr);
};
}
template <typename F, auto = []{}>
void add(const std::string& name, F f)
{
add_internal(name, wrap_function(f));
}
template <typename ...Args, typename F, auto = []{}>
void add_multiple(F f, Args&& ...names)
{
(add(names, f), ...);
}
}
namespace method
{
template <typename F, auto = []{}>
void add_internal(const std::string& name, F function)
{
static auto called = false;
assert(!called);
called = true;
const auto lower = utils::string::to_lower(name);
static const auto [iterator, was_inserted] = functions.insert(std::make_pair(lower, function));
static const auto& function_ptr = iterator->second;
method_wraps[lower] = [](game::scr_entref_t entref)
{
call_method(function_ptr, entref);
};
}
template <typename F, auto = []{}>
void add(const std::string& name, F f)
{
add_internal(name, wrap_function(f));
}
template <typename ...Args, typename F, auto = []{}>
void add_multiple(F f, Args&& ...names)
{
(add(names, f), ...);
}
}
}
#else
#error pre c++20 is not supported
#endif
@@ -0,0 +1,287 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "game/structs.hpp"
#include "game/game.hpp"
#include "gsc.hpp"
#include "scheduler.hpp"
#include "scripting.hpp"
#include <utils/http.hpp>
#include <utils/concurrency.hpp>
#include <curl/curl.h>
namespace http
{
namespace
{
constexpr const auto max_result_size = 0x5000u;
struct http_request_params_t
{
std::string method;
std::string url;
std::string fields;
utils::http::headers headers;
utils::http::timeouts limits;
};
struct http_request_t
{
http_request_params_t params;
scripting::object handle;
std::optional<utils::http::result> result;
std::chrono::steady_clock::time_point queued_at = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point started_at;
std::chrono::steady_clock::time_point finished_at;
std::atomic_bool completed{false};
};
std::vector<std::shared_ptr<http_request_t>> requests;
double elapsed_ms(const std::chrono::steady_clock::time_point start,
const std::chrono::steady_clock::time_point end)
{
return std::chrono::duration<double, std::milli>(end - start).count();
}
void notify_request_result(std::shared_ptr<http_request_t>& request)
{
const auto handle_id = request->handle.get_entity_id();
const utils::http::result missing;
const auto& result = request->result ? *request->result : missing;
scripting::array diagnostics;
diagnostics["http_status"] = static_cast<int>(result.status);
diagnostics["curl_code"] = static_cast<int>(result.code);
diagnostics["dns_ms"] = result.dns_ms;
diagnostics["connect_ms"] = result.connect_ms;
diagnostics["tls_ms"] = result.tls_ms;
diagnostics["first_byte_ms"] = result.first_byte_ms;
diagnostics["total_ms"] = result.total_ms;
diagnostics["worker_queue_ms"] = elapsed_ms(request->queued_at, request->started_at);
diagnostics["notify_ms"] = elapsed_ms(request->finished_at, std::chrono::steady_clock::now());
diagnostics["connect_timeout_ms"] = static_cast<int>(request->params.limits.connect_timeout_ms);
diagnostics["timeout_ms"] = static_cast<int>(request->params.limits.timeout_ms);
if (!request->result.has_value())
{
scripting::notify(handle_id, "done", {{}, false, "unknown error", diagnostics});
return;
}
const auto error = curl_easy_strerror(result.code);
if (result.code != CURLE_OK)
{
scripting::notify(handle_id, "done", {{}, false, error, diagnostics});
return;
}
auto& buffer = request->result->buffer;
if (buffer.size() >= max_result_size)
{
printf("^3WARNING: http result size bigger than %i bytes (%i), truncating!", max_result_size,
static_cast<int>(buffer.size()));
buffer.resize(max_result_size);
}
// Keep the original three values; older waiters ignore the fourth.
// HTTP 4xx/5xx are completed transfers, not transport failures.
scripting::notify(handle_id, "done", {buffer, true, {}, diagnostics});
}
void check_requests()
{
std::vector<std::shared_ptr<http_request_t>> ready;
for (auto i = requests.begin(); i != requests.end(); )
{
auto& request = *i;
if (!request->completed)
{
++i;
continue;
}
else
{
ready.emplace_back(request);
i = requests.erase(i);
}
}
// A resumed script may enqueue another request. Do not hold an iterator
// into requests while notifying the VM.
for (auto& request : ready)
notify_request_result(request);
}
scripting::object create_request(const http_request_params_t& params)
{
const auto request = std::make_shared<http_request_t>();
requests.emplace_back(request);
request->params = params;
scheduler::thread_pool.push([request]
{
request->started_at = std::chrono::steady_clock::now();
try
{
request->result = utils::http::get_data(
request->params.url,
request->params.fields,
request->params.headers,
request->params.method,
request->params.limits);
}
catch (...)
{
// Exceptions must also release GSC waiters through the done event.
request->result = std::nullopt;
}
request->finished_at = std::chrono::steady_clock::now();
request->completed = true;
});
return request->handle;
}
template <typename T>
void push_request(T&& r)
{
const auto request = std::make_shared<http_request_t>(std::forward<T>(r));
requests.emplace_back(request);
}
void wait_and_clear_requests()
{
for (auto& task : requests)
{
while (!task->completed)
{
std::this_thread::sleep_for(10ms);
}
}
requests.clear();
}
long timeout_option(const scripting::array& options, const char* name, const long fallback)
{
const auto value = options[name];
if (value.get_raw().type == game::SCRIPT_NONE)
return fallback;
if (!value.is<int>() || value.as<int>() <= 0)
throw std::runtime_error(std::string(name) + " must be a positive integer in milliseconds");
return value.as<int>();
}
void parse_request_options(http_request_params_t& params, const scripting::array& options)
{
params.limits.connect_timeout_ms = timeout_option(options, "connect_timeout_ms", params.limits.connect_timeout_ms);
params.limits.timeout_ms = timeout_option(options, "timeout_ms", params.limits.timeout_ms);
const auto fields = options["parameters"];
const auto body = options["body"];
const auto headers = options["headers"];
const auto method = options["method"];
if (method.is<std::string>())
{
params.method = method.as<std::string>();
}
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;
}
const auto key_ = key.as<std::string>();
const auto value = fields_[key].to_string();
params.fields += key_ + "=" + value + "&";
}
}
else if (body.is<std::string>())
{
params.fields = body.as<std::string>();
}
if (headers.is<scripting::array>())
{
const auto headers_arr = headers.as<scripting::array>();
const auto keys = headers_arr.get_keys();
for (const auto& key : keys)
{
if (!key.is<std::string>())
{
continue;
}
const auto key_str = key.as<std::string>();
const auto value = headers_arr[key].to_string();
params.headers[key_str] = value;
}
}
}
}
class component final : public component_interface
{
public:
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
{
scripting::on_shutdown(wait_and_clear_requests);
}
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
scheduler::loop(check_requests, scheduler::server);
scripting::on_shutdown(wait_and_clear_requests);
gsc::function::add_multiple([](const std::string& url)
{
http_request_params_t params{};
params.url = url;
return create_request(params);
}, "http::get", "httpget", "curl");
gsc::function::add("http::request", [](const std::string& url, const scripting::variadic_args& va)
{
http_request_params_t params{};
params.url = url;
if (va.size() > 0)
{
const auto options = va[0].as<scripting::array>();
parse_request_options(params, options);
}
return create_request(params);
});
gsc::function::add_multiple([](const std::string& url, const scripting::variadic_args& va)
{
http_request_params_t params{};
params.url = url;
params.method = "POST";
if (va.size() > 0)
{
const auto options = va[0].as<scripting::array>();
parse_request_options(params, options);
}
return create_request(params);
}, "httppost", "http::post");
}
};
}
REGISTER_COMPONENT(http::component)
@@ -0,0 +1,119 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "gsc.hpp"
#include <utils/io.hpp>
#include <utils/hook.hpp>
#define INT64_OPERATION(expr) \
[](const std::int64_t a, [[maybe_unused]] const std::int64_t b) \
{ \
return expr; \
} \
namespace int64
{
namespace
{
std::int64_t get_int64_arg(const scripting::variadic_args& args, const size_t index, bool optional)
{
if (optional && index >= static_cast<int>(args.size()))
{
return 0;
}
if (args[index].is<int>())
{
return static_cast<std::int64_t>(args[index].as<int>());
}
if (args[index].is<const char*>())
{
return std::strtoll(args[index].as<const char*>(), nullptr, 0);
}
throw std::runtime_error(std::format("parameter {} does not have type 'string' or 'int'", index));
}
std::unordered_map<std::string, std::function<std::int64_t(std::int64_t, std::int64_t)>> operations =
{
{"+", INT64_OPERATION(a + b)},
{"-", INT64_OPERATION(a - b)},
{"*", INT64_OPERATION(a * b)},
{"/", INT64_OPERATION(a / b)},
{"&", INT64_OPERATION(a & b)},
{"^", INT64_OPERATION(a ^ b)},
{"|", INT64_OPERATION(a | b)},
{"~", INT64_OPERATION(~a)},
{"%", INT64_OPERATION(a % b)},
{">>", INT64_OPERATION(a >> b)},
{"<<", INT64_OPERATION(a << b)},
{"++", INT64_OPERATION(a + 1)},
{"--", INT64_OPERATION(a - 1)},
};
std::unordered_map<std::string, std::function<bool(std::int64_t, std::int64_t)>> comparisons =
{
{">", INT64_OPERATION(a > b)},
{">=", INT64_OPERATION(a >= b)},
{"==", INT64_OPERATION(a == b)},
{"!=", INT64_OPERATION(a != b)},
{"<=", INT64_OPERATION(a <= b)},
{"<", INT64_OPERATION(a < b)},
};
}
class component final : public component_interface
{
public:
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
gsc::function::add_multiple([](const scripting::variadic_args& args)
{
auto value = get_int64_arg(args, 0, false);
return value <= std::numeric_limits<std::int32_t>::max() && value >= std::numeric_limits<std::int32_t>::min();
}, "int64_is_int", "int64::is_int");
gsc::function::add_multiple([](const scripting::variadic_args& args)
{
return static_cast<std::int32_t>(get_int64_arg(args, 0, false));
}, "int64_to_int", "int64::to_int");
gsc::function::add_multiple([](const scripting::variadic_args& args) -> scripting::script_value
{
auto a = get_int64_arg(args, 0, false);
const auto op = args[1].as<std::string>();
auto b = get_int64_arg(args, 2, true);
if (const auto iter = operations.find(op); iter != operations.end())
{
return std::to_string(iter->second(a, b));
}
if (const auto iter = comparisons.find(op); iter != comparisons.end())
{
return iter->second(a, b);
}
throw std::runtime_error("invalid int64 operation");
}, "int64_op", "int64::op");
gsc::function::add_multiple([](const scripting::variadic_args& args)
{
const auto a = get_int64_arg(args, 0, false);
const auto b = get_int64_arg(args, 1, false);
return std::to_string(std::min(a, b));
}, "int64_min", "int64::min");
gsc::function::add_multiple([](const scripting::variadic_args& args)
{
const auto a = get_int64_arg(args, 0, false);
const auto b = get_int64_arg(args, 1, false);
return std::to_string(std::max(a, b));
}, "int64_max", "int64::max");
}
};
}
REGISTER_COMPONENT(int64::component)
@@ -0,0 +1,75 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "game/structs.hpp"
#include "game/game.hpp"
#include "gsc.hpp"
#include <utils/io.hpp>
namespace io
{
class component final : public component_interface
{
public:
void on_after_dvar_init([[maybe_unused]] plugin::plugin* plugin) override
{
const auto fs_homepath = game::Dvar_FindVar("fs_homepath");
if (fs_homepath == nullptr)
{
return;
}
std::filesystem::current_path(fs_homepath->current.string);
printf("working directory: %s", fs_homepath->current.string);
}
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
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];
}
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(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];
}
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");
}
};
}
REGISTER_COMPONENT(io::component)
@@ -0,0 +1,281 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "scheduler.hpp"
#include "game/scripting/event.hpp"
#include "game/scripting/execution.hpp"
#include "game/scripting/array.hpp"
#include "gsc.hpp"
#include "json.hpp"
#include "scripting.hpp"
#include <utils/io.hpp>
namespace json
{
namespace
{
std::unordered_set<unsigned int> dumped_objects;
nlohmann::json gsc_to_json(scripting::script_value value, bool print_id);
nlohmann::json array_to_json(const scripting::array& array, bool print_id)
{
nlohmann::json obj;
auto string_indexed = -1;
const auto keys = array.get_keys();
for (auto i = 0u; i < keys.size(); i++)
{
const auto is_int = keys[i].is<int>();
const auto is_string = keys[i].is<std::string>();
if (string_indexed == -1)
{
string_indexed = is_string;
}
if (!string_indexed && is_int)
{
const auto index = keys[i].as<int>();
obj[index] = gsc_to_json(array[index], print_id);
}
else if (string_indexed && is_string)
{
const auto key = keys[i].as<std::string>();
obj.emplace(key, gsc_to_json(array[key], print_id));
}
}
return obj;
}
nlohmann::json object_to_json(const scripting::object& object, bool print_id)
{
const auto id = object.get_entity_id();
if (dumped_objects.find(id) != dumped_objects.end())
{
return utils::string::va("[struct reference %i]", id);
}
dumped_objects.insert(id);
nlohmann::json obj;
if (print_id)
{
obj["__id"] = id;
}
const auto keys = object.get_keys();
for (const auto& key : keys)
{
if (key == "__id")
{
continue;
}
obj.emplace(key, gsc_to_json(object[key], print_id));
}
return obj;
}
nlohmann::json vector_to_array(const float* value)
{
nlohmann::json obj;
obj.push_back(value[0]);
obj.push_back(value[1]);
obj.push_back(value[2]);
return obj;
}
nlohmann::json gsc_to_json(scripting::script_value value, bool print_id)
{
const auto& variable = value.get_raw();
if (value.is<int>())
{
return value.as<int>();
}
if (value.is<float>())
{
return value.as<float>();
}
if (value.is<std::string>())
{
return value.as<std::string>();
}
if (value.is<scripting::vector>())
{
return vector_to_array(variable.u.vectorValue);
}
if (value.is<scripting::object>())
{
return object_to_json(variable.u.uintValue, print_id);
}
if (value.is<scripting::array>())
{
return array_to_json(variable.u.uintValue, print_id);
}
if (value.is<scripting::entity>())
{
return object_to_json(variable.u.uintValue, print_id);
}
if (variable.type == game::SCRIPT_NONE)
{
return {};
}
if (value.is<scripting::function>())
{
return "[function]";
}
if (variable.type == game::SCRIPT_CODEPOS)
{
return "[codepos]";
}
if (variable.type == game::SCRIPT_END)
{
return "[precodepos]";
}
return utils::string::va("[%s]", value.type_name().data());
}
scripting::script_value json_to_gsc(nlohmann::json obj)
{
const auto type = obj.type();
switch (type)
{
case (nlohmann::detail::value_t::number_integer):
case (nlohmann::detail::value_t::number_unsigned):
return obj.get<int>();
case (nlohmann::detail::value_t::number_float):
return obj.get<float>();
case (nlohmann::detail::value_t::string):
return obj.get<std::string>();
case (nlohmann::detail::value_t::array):
{
scripting::array array;
for (const auto& [key, value] : obj.items())
{
array.emplace_back(json_to_gsc(value));
}
return array.get_raw();
}
case (nlohmann::detail::value_t::object):
{
scripting::array array;
for (const auto& [key, value] : obj.items())
{
array[key] = json_to_gsc(value);
}
return array.get_raw();
}
}
return {};
}
}
std::string gsc_to_string(const scripting::script_value& value)
{
dumped_objects = {};
return gsc_to_json(value, false).dump();
}
class component final : public component_interface
{
public:
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
gsc::function::add_multiple([](const scripting::variadic_args& va)
{
scripting::array array;
for (auto i = 0u; i < va.size(); i += 2)
{
if (i >= va.size() - 1)
{
continue;
}
const auto key = va[i].as<std::string>();
array[key] = va[i + 1];
}
return array;
}, "createmap", "json::create_map");
gsc::function::add_multiple([](const std::string json)
{
const auto obj = nlohmann::json::parse(json);
return json_to_gsc(obj);
}, "jsonparse", "json::parse");
gsc::function::add_multiple([](const scripting::script_value& value,
const scripting::variadic_args& va)
{
auto indent = -1;
auto print_id = false;
if (va.size() > 0)
{
indent = va[0].as<int>();
}
if (va.size() > 1)
{
print_id = va[1].as<bool>();
}
dumped_objects = {};
return gsc_to_json(value, print_id).dump(indent).substr(0, 0x5000);
}, "jsonserialize", "json::serialize");
gsc::function::add_multiple([](std::string file, const scripting::script_value value,
const scripting::variadic_args& va)
{
auto indent = -1;
auto print_id = false;
if (!file.ends_with(".json"))
{
file.append(".json");
}
if (va.size() > 0)
{
indent = va[0].as<int>();
}
if (va.size() > 1)
{
print_id = va[1].as<bool>();
}
dumped_objects = {};
return utils::io::write_file(file, gsc_to_json(value, print_id).dump(indent));
}, "jsondump", "json::dump");
}
};
}
REGISTER_COMPONENT(json::component)
@@ -0,0 +1,6 @@
#pragma once
namespace json
{
std::string gsc_to_string(const scripting::script_value& _value);
}
@@ -0,0 +1,545 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "mysql.hpp"
#include "component/gsc.hpp"
#include "component/scheduler.hpp"
#include "component/scripting.hpp"
#include <utils/string.hpp>
namespace mysql
{
namespace
{
struct mysql_result_t
{
MYSQL_RES* result;
MYSQL_STMT* stmt;
uint64_t affected_rows;
std::string error;
};
struct task_t
{
scripting::object handle;
mysql_result_t result{};
std::atomic_bool completed;
};
std::vector<std::shared_ptr<task_t>> tasks;
std::array<connection, max_connections> connection_pool;
scripting::script_value field_to_value(const MYSQL_FIELD* field, const std::string& row)
{
switch (field->type)
{
case enum_field_types::MYSQL_TYPE_INT24:
case enum_field_types::MYSQL_TYPE_LONG:
case enum_field_types::MYSQL_TYPE_SHORT:
return std::atoi(row.data());
case enum_field_types::MYSQL_TYPE_LONGLONG:
return row;
case enum_field_types::MYSQL_TYPE_FLOAT:
case enum_field_types::MYSQL_TYPE_DOUBLE:
return static_cast<float>(std::atof(row.data()));
}
return row;
}
scripting::script_value bind_to_value(const MYSQL_BIND* bind)
{
switch (bind->buffer_type)
{
case enum_field_types::MYSQL_TYPE_INT24:
case enum_field_types::MYSQL_TYPE_LONG:
case enum_field_types::MYSQL_TYPE_SHORT:
return *reinterpret_cast<int*>(bind->buffer);
case enum_field_types::MYSQL_TYPE_LONGLONG:
return std::to_string(*reinterpret_cast<std::int64_t*>(bind->buffer));
case enum_field_types::MYSQL_TYPE_FLOAT:
return *reinterpret_cast<float*>(bind->buffer);
case enum_field_types::MYSQL_TYPE_DOUBLE:
return static_cast<float>(*reinterpret_cast<double*>(bind->buffer));
case enum_field_types::MYSQL_TYPE_STRING:
return std::string{reinterpret_cast<char*>(bind->buffer), bind->buffer_length};
}
return {};
}
scripting::array generate_result(MYSQL_STMT* stmt)
{
if (stmt == nullptr)
{
return {};
}
utils::memory::allocator allocator;
scripting::array result_arr;
const auto meta = mysql_stmt_result_metadata(stmt);
if (meta == nullptr)
{
return {};
}
const auto column_count = mysql_num_fields(meta);
const auto fields = mysql_fetch_fields(meta);
const auto is_null = allocator.allocate_array<my_bool>(column_count);
const auto errors = allocator.allocate_array<my_bool>(column_count);
const auto real_lengths = allocator.allocate_array<unsigned long>(column_count);
const auto binds = allocator.allocate_array<MYSQL_BIND>(column_count);
for (auto i = 0u; i < column_count; i++)
{
binds[i].length = &real_lengths[i];
binds[i].is_null = &is_null[i];
binds[i].error = &errors[i];
}
if (mysql_stmt_bind_result(stmt, binds) != 0 ||
mysql_stmt_store_result(stmt) != 0)
{
return {};
}
while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA)
{
for (auto i = 0u; i < column_count; i++)
{
switch (fields[i].type)
{
case enum_field_types::MYSQL_TYPE_INT24:
case enum_field_types::MYSQL_TYPE_LONG:
case enum_field_types::MYSQL_TYPE_SHORT:
{
binds[i].buffer_type = MYSQL_TYPE_LONG;
binds[i].buffer = allocator.allocate<int>();
binds[i].buffer_length = sizeof(int);
break;
};
case enum_field_types::MYSQL_TYPE_LONGLONG:
{
binds[i].buffer_type = MYSQL_TYPE_LONGLONG;
binds[i].buffer = allocator.allocate<std::int64_t>();
binds[i].buffer_length = sizeof(std::int64_t);
break;
};
case enum_field_types::MYSQL_TYPE_FLOAT:
binds[i].buffer_type = MYSQL_TYPE_FLOAT;
binds[i].buffer = allocator.allocate<float>();
binds[i].buffer_length = sizeof(float);
break;
case enum_field_types::MYSQL_TYPE_DOUBLE:
{
binds[i].buffer_type = MYSQL_TYPE_DOUBLE;
binds[i].buffer = allocator.allocate<double>();
binds[i].buffer_length = sizeof(double);
break;
}
default:
binds[i].buffer_type = MYSQL_TYPE_STRING;
binds[i].buffer = allocator.allocate_array<char>(real_lengths[i]);
binds[i].buffer_length = real_lengths[i];
break;
}
mysql_stmt_fetch_column(stmt, &binds[i], i, 0);
}
scripting::array row_arr;
for (auto i = 0u; i < column_count; i++)
{
const auto field = &fields[i];
const std::string field_name = {field->name, field->name_length};
row_arr[field_name] = bind_to_value(&binds[i]);
}
result_arr.emplace_back(row_arr);
}
mysql_free_result(meta);
return result_arr;
}
scripting::array generate_result(MYSQL_RES* result)
{
if (result == nullptr)
{
return {};
}
scripting::array result_arr;
const auto num_rows = mysql_num_rows(result);
const auto num_fields = mysql_num_fields(result);
const auto fields = mysql_fetch_fields(result);
for (auto i = 0u; i < num_rows; i++)
{
scripting::array row_arr;
const auto row = mysql_fetch_row(result);
const auto lengths = mysql_fetch_lengths(result);
for (auto f = 0u; f < num_fields; f++)
{
const auto field = &fields[f];
const std::string field_str = {field->name, field->name_length};
const std::string row_str = {row[f], lengths[f]};
const auto value = field_to_value(field, row_str);
row_arr[field_str] = value;
}
result_arr.emplace_back(row_arr);
}
return result_arr;
}
template <typename F>
scripting::object create_mysql_query(F&& cb)
{
auto task = std::make_shared<task_t>();
tasks.emplace_back(task);
scheduler::thread_pool.push([=]
{
try
{
mysql::access([&](database_t& db)
{
task->result = cb(db);
task->completed = true;
});
}
catch (const std::exception& e)
{
printf("^1MySQL ERROR: %s\n", e.what());
task->completed = true;
}
});
return task->handle;
}
void wait_and_clear_tasks()
{
for (auto& task : tasks)
{
while (!task->completed)
{
std::this_thread::sleep_for(10ms);
}
}
tasks.clear();
}
void notify_task_result(std::shared_ptr<task_t>& task)
{
const auto result = task->result.result
? generate_result(task->result.result)
: generate_result(task->result.stmt);
const auto rows = static_cast<std::size_t>(task->result.affected_rows);
scripting::notify(task->handle.get_entity_id(), "done", {result, rows, task->result.error});
if (task->result.result != nullptr)
{
mysql_free_result(task->result.result);
task->result.result = nullptr;
}
if (task->result.stmt != nullptr)
{
mysql_stmt_close(task->result.stmt);
task->result.stmt = nullptr;
}
}
void check_tasks()
{
for (auto i = tasks.begin(); i != tasks.end(); )
{
auto& task = *i;
if (!task->completed)
{
++i;
continue;
}
else
{
notify_task_result(task);
i = tasks.erase(i);
}
}
}
template <typename T>
MYSQL_BIND* bind_statement_args(const T& args, std::size_t& bind_count)
{
bind_count = args.size();
const auto binds = utils::memory::allocate_array<MYSQL_BIND>(bind_count);
for (auto i = 0u; i < args.size(); i++)
{
const auto& arg = args[i];
const auto& raw_value = arg.get_raw();
switch (raw_value.type)
{
case game::SCRIPT_FLOAT:
{
binds[i].buffer = utils::memory::allocate<float>();
binds[i].buffer_type = MYSQL_TYPE_FLOAT;
*reinterpret_cast<float*>(binds[i].buffer) = raw_value.u.floatValue;
break;
}
case game::SCRIPT_INTEGER:
{
binds[i].buffer = utils::memory::allocate<int>();
binds[i].buffer_type = MYSQL_TYPE_LONG;
*reinterpret_cast<int*>(binds[i].buffer) = raw_value.u.intValue;
break;
}
case game::SCRIPT_STRING:
{
const auto str = arg.as<std::string>();
const auto str_copy = utils::memory::duplicate_string(str);
binds[i].buffer = str_copy;
binds[i].buffer_length = str.size();
binds[i].buffer_type = MYSQL_TYPE_STRING;
break;
}
default:
{
binds[i].buffer_type = MYSQL_TYPE_NULL;
break;
}
}
}
return binds;
}
void cleanup_connections()
{
for (auto& connection : connection_pool)
{
connection.cleanup();
}
}
void free_statement_binds(MYSQL_BIND* binds, const std::size_t bind_count)
{
if (binds == nullptr)
{
return;
}
for (auto i = 0u; i < bind_count; i++)
{
utils::memory::free(binds[i].buffer);
}
utils::memory::free(binds);
}
}
utils::concurrency::container<sql::connection_config>& get_config()
{
static utils::concurrency::container<sql::connection_config> config;
static auto initialized = false;
if (!initialized)
{
config.access([&](sql::connection_config& cfg)
{
cfg.user = "root";
cfg.password = "root";
cfg.host = "localhost";
cfg.port = 3306;
cfg.database = "default";
});
initialized = true;
}
return config;
}
void connection::check()
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - this->start_;
if (this->db.get() == nullptr || !this->db->ping_server() || diff >= connection_timeout)
{
get_config().access([&](sql::connection_config& cfg)
{
this->db = std::make_unique<sql::connection>(cfg);
this->start_ = now;
});
}
this->last_access_ = now;
}
void connection::cleanup()
{
std::unique_lock<database_mutex_t> lock(this->mutex, std::try_to_lock);
if (!lock.owns_lock())
{
return;
}
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - this->last_access_;
if (diff >= connection_timeout)
{
this->db.reset();
}
}
connection* get_connection(std::unique_lock<database_mutex_t>& lock)
{
static thread_local connection* last_connection{};
if (last_connection != nullptr)
{
lock = std::unique_lock(last_connection->mutex, std::try_to_lock);
if (lock.owns_lock())
{
return last_connection;
}
}
for (auto i = 0u; i < connection_pool.size(); i++)
{
auto connection = &connection_pool[i];
if (connection == last_connection)
{
continue;
}
lock = std::unique_lock(connection->mutex, std::try_to_lock);
if (!lock.owns_lock())
{
continue;
}
last_connection = connection;
return connection;
}
return nullptr;
}
class component final : public component_interface
{
public:
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
{
wait_and_clear_tasks();
}
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
scripting::on_shutdown(wait_and_clear_tasks);
scheduler::loop(cleanup_connections, scheduler::async, 60s);
scheduler::loop(check_tasks, scheduler::server);
gsc::function::add("mysql::set_config", [](const scripting::object& config)
{
get_config().access([&](sql::connection_config& cfg)
{
cfg.host = config["host"].as<std::string>();
cfg.user = config["user"].as<std::string>();
cfg.password = config["password"].as<std::string>();
cfg.port = config["port"].as<unsigned short>();
cfg.database = config["database"].as<std::string>();
});
});
gsc::function::add_multiple([](const std::string& query)
{
return create_mysql_query([=](database_t& db)
{
const auto handle = db->get_handle();
mysql_result_t result{};
if (mysql_query(handle, query.data()) != 0)
{
result.error = mysql_error(handle);
return result;
}
result.result = mysql_store_result(handle);
result.affected_rows = mysql_affected_rows(handle);
return result;
});
}, "mysql::query", "mysql::execute");
gsc::function::add("mysql::prepared_statement", [](const std::string& query, const scripting::variadic_args& values)
{
MYSQL_BIND* binds = nullptr;
auto bind_count = 0u;
try
{
if (values.size() > 0 && values[0].is<scripting::array>())
{
binds = bind_statement_args(values[0].as<scripting::array>(), bind_count);
}
else
{
binds = bind_statement_args(values, bind_count);
}
const auto handle = create_mysql_query([binds, bind_count, query](database_t& db)
{
const auto _0 = gsl::finally([=]
{
free_statement_binds(binds, bind_count);
});
mysql_result_t result{};
const auto handle = db->get_handle();
const auto stmt = mysql_stmt_init(handle);
if (mysql_stmt_prepare(stmt, query.data(), query.size()) != 0 ||
(binds != nullptr && mysql_stmt_bind_param(stmt, binds) != 0) ||
mysql_stmt_execute(stmt) != 0)
{
result.error = mysql_stmt_error(stmt);
return result;
}
result.stmt = stmt;
result.affected_rows = mysql_stmt_affected_rows(stmt);
return result;
});
return handle;
}
catch (const std::exception& e)
{
free_statement_binds(binds, bind_count);
throw e;
}
});
}
};
}
REGISTER_COMPONENT(mysql::component)
@@ -0,0 +1,58 @@
#pragma once
#include <utils/concurrency.hpp>
#pragma warning(push)
#pragma warning(disable: 4127)
#pragma warning(disable: 4267)
#pragma warning(disable: 4018)
#pragma warning(disable: 4996)
#pragma warning(disable: 4244)
#include <sqlpp11/sqlpp11.h>
#include <sqlpp11/mysql/mysql.h>
#pragma warning(pop)
namespace sql = sqlpp::mysql;
namespace mysql
{
constexpr auto max_connections = 100;
constexpr auto connection_timeout = 200s;
using database_mutex_t = std::recursive_mutex;
using database_t = std::unique_ptr<sql::connection>;
utils::concurrency::container<sql::connection_config>& get_config();
class connection
{
public:
connection() = default;
void check();
void cleanup();
database_t db;
database_mutex_t mutex;
private:
std::chrono::high_resolution_clock::time_point start_;
std::chrono::high_resolution_clock::time_point last_access_;
};
connection* get_connection(std::unique_lock<database_mutex_t>& lock);
template <typename T = void, typename F>
T access(F&& accessor)
{
std::unique_lock<database_mutex_t> lock;
auto conn = get_connection(lock);
if (conn == nullptr)
{
throw std::runtime_error("out of connections");
}
conn->check();
return accessor(conn->db);
}
}
@@ -0,0 +1,175 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "scheduler.hpp"
#include <utils/concurrency.hpp>
#include <utils/hook.hpp>
namespace scheduler
{
utils::thread_pool thread_pool;
namespace
{
utils::hook::detour server_frame_hook;
bool kill_thread = false;
struct task
{
std::function<bool()> handler{};
std::chrono::milliseconds interval{};
std::chrono::high_resolution_clock::time_point last_call{};
};
using task_list = std::vector<task>;
class task_pipeline
{
public:
void add(task&& task)
{
new_callbacks_.access([&task](task_list& tasks)
{
tasks.emplace_back(std::move(task));
});
}
void execute()
{
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
for (auto i = tasks.begin(); i != tasks.end();)
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - i->last_call;
if (diff < i->interval)
{
++i;
continue;
}
i->last_call = now;
const auto res = i->handler();
if (res == cond_end)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
private:
utils::concurrency::container<task_list> new_callbacks_;
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
void merge_callbacks()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()), std::move_iterator<task_list::iterator>(new_tasks.end()));
new_tasks = {};
});
});
}
};
std::thread thread;
task_pipeline pipelines[pipeline::count];
void execute(const pipeline type)
{
assert(type >= 0 && type < pipeline::count);
pipelines[type].execute();
}
void execute_server()
{
execute(pipeline::server);
}
void server_frame_stub()
{
execute(pipeline::server);
server_frame_hook.invoke<void>();
}
}
void schedule(const std::function<bool()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
assert(type >= 0 && type < pipeline::count);
task task;
task.handler = callback;
task.interval = delay;
task.last_call = std::chrono::high_resolution_clock::now();
pipelines[type].add(std::move(task));
}
void loop(const std::function<void()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
schedule([callback]()
{
callback();
return cond_continue;
}, type, delay);
}
void once(const std::function<void()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
schedule([callback]()
{
callback();
return cond_end;
}, type, delay);
}
class component final : public component_interface
{
public:
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
thread_pool.initialize(8);
thread_pool.start();
thread = std::thread([]()
{
while (!kill_thread)
{
execute(pipeline::async);
std::this_thread::sleep_for(10ms);
}
});
server_frame_hook.create(SELECT_VALUE(0x43E340, 0x46B680), server_frame_stub);
}
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
{
thread_pool.stop();
kill_thread = true;
if (thread.joinable())
{
thread.join();
}
}
};
}
REGISTER_COMPONENT(scheduler::component)
@@ -0,0 +1,25 @@
#pragma once
#include <utils/thread_pool.hpp>
namespace scheduler
{
enum pipeline
{
server,
async,
count,
};
static const bool cond_continue = false;
static const bool cond_end = true;
void schedule(const std::function<bool()>& callback, pipeline type = pipeline::server,
std::chrono::milliseconds delay = 0ms);
void loop(const std::function<void()>& callback, pipeline type = pipeline::server,
std::chrono::milliseconds delay = 0ms);
void once(const std::function<void()>& callback, pipeline type = pipeline::server,
std::chrono::milliseconds delay = 0ms);
extern utils::thread_pool thread_pool;
}
@@ -0,0 +1,78 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/scripting/event.hpp"
#include "game/scripting/execution.hpp"
#include "scripting.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace scripting
{
std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
std::unordered_map<std::string, game::BuiltinMethodDef> method_map;
std::unordered_map<std::string, game::BuiltinFunctionDef> function_map;
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
std::unordered_map<unsigned int, std::string> canonical_string_table;
namespace
{
utils::hook::detour g_shutdown_game_hook;
utils::hook::detour sl_get_canonical_string_hook;
std::vector<std::function<void()>> shutdown_callbacks;
void g_shutdown_game_stub(const int free_scripts)
{
if (free_scripts)
{
canonical_string_table.clear();
}
for (const auto& callback : shutdown_callbacks)
{
callback();
}
return g_shutdown_game_hook.invoke<void>(free_scripts);
}
unsigned int sl_transfer_canonical_string_stub(game::scriptInstance_t inst, unsigned int string_value)
{
const auto result = sl_get_canonical_string_hook.invoke<unsigned int>(inst, string_value);
const auto str = game::SL_ConvertToString(string_value, inst);
if (str)
{
canonical_string_table[result] = game::SL_ConvertToString(string_value, inst);
}
return result;
}
}
void on_shutdown(const std::function<void()>& callback)
{
shutdown_callbacks.push_back(callback);
}
class component final : public component_interface
{
public:
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
g_shutdown_game_hook.create(SELECT_VALUE(0x607700, 0x540950), g_shutdown_game_stub);
sl_get_canonical_string_hook.create(SELECT_VALUE(0x5F3F40, 0x622530), sl_transfer_canonical_string_stub);
}
};
}
REGISTER_COMPONENT(scripting::component)
@@ -0,0 +1,19 @@
#pragma once
namespace scripting
{
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
extern std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
extern std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
extern std::unordered_map<std::string, game::BuiltinMethodDef> method_map;
extern std::unordered_map<std::string, game::BuiltinFunctionDef> function_map;
extern std::unordered_map<unsigned int, std::string> canonical_string_table;
using script_function = void(*)(game::scr_entref_t);
void on_shutdown(const std::function<void()>& callback);
}
@@ -0,0 +1,218 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "gsc.hpp"
// lua/lstrlib.c
#define MAX_FORMAT 32
#define L_FMTFLAGSF "-+#0 "
#define L_FMTFLAGSX "-#0"
#define L_FMTFLAGSI "-+0 "
#define L_FMTFLAGSU "-0"
#define L_FMTFLAGSC "-"
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)");
}
*(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++;
}
}
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));
}
}
// 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);
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;
}
}
class component final : public component_interface
{
public:
void on_startup([[maybe_unused]] plugin::plugin* plugin) 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("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()));
}
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("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_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.emplace_back((s.str()));
}
}
return array_match;
});
gsc::function::add("string::regex_search", [](const std::string& str, const std::regex& expr)
{
return std::regex_search(str, expr);
});
}
};
}
REGISTER_COMPONENT(string::component)
@@ -0,0 +1,136 @@
#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::array<user_info_map, 18> user_info_overrides;
utils::hook::detour scr_shutdown_system_hook;
void clear_client_overrides(unsigned int client_num)
{
user_info_overrides[client_num].clear();
}
void clear_all_overrides()
{
for (auto& entry : user_info_overrides)
{
entry.clear();
}
}
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);
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 on_startup([[maybe_unused]] plugin::plugin* plugin) 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);
plugin->get_interface()->callbacks()->on_player_connect(clear_client_overrides);
plugin->get_interface()->callbacks()->on_player_disconnect(clear_client_overrides);
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)
@@ -0,0 +1,78 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "plugin.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/binary_resource.hpp>
#include <utils/nt.hpp>
#include <utils/cryptography.hpp>
#include <utils/io.hpp>
namespace
{
utils::hook::detour load_library_hook;
std::string extract_resource(const std::string& name, const int resource)
{
const auto data = utils::nt::load_resource(resource);
const auto path = std::filesystem::current_path() / "tmp" / name;
const auto path_str = path.generic_string();
if (!utils::io::write_file(path_str, data))
{
const auto current = utils::io::read_file(path_str);
const auto hash_current = utils::cryptography::md5::compute(current);
const auto hash_target = utils::cryptography::md5::compute(data);
if (hash_target != hash_current)
{
throw std::runtime_error("failed to extract libmysql.dll!");
}
}
return path_str;
}
HMODULE __stdcall load_library_stub(LPCSTR lib_name, HANDLE file, DWORD flags)
{
if (lib_name == "libmysql.dll"s)
{
const auto path = extract_resource(lib_name, LIBMYSQL_DLL);
const auto handle = load_library_hook.invoke_pascal<HMODULE>(path.data(), file, flags);
if (handle != nullptr)
{
return handle;
}
else
{
throw std::runtime_error(std::format("failed to load libmysql.dll: {}", GetLastError()));
}
}
return load_library_hook.invoke_pascal<HMODULE>(lib_name, file, flags);
}
}
PLUTONIUM_API plutonium::sdk::plugin* PLUTONIUM_CALLBACK on_initialize()
{
return plugin::get();
}
BOOL APIENTRY DllMain(HMODULE module, DWORD ul_reason_for_call, LPVOID /*reserved*/)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
utils::nt::library::set_current_handle(module);
load_library_hook.create(LoadLibraryExA, load_library_stub);
}
if (ul_reason_for_call == DLL_PROCESS_DETACH)
{
component_loader::on_shutdown();
}
return TRUE;
}
@@ -0,0 +1,94 @@
#include <stdinc.hpp>
#include "minidump.hpp"
#include <DbgHelp.h>
#pragma comment(lib, "dbghelp.lib")
#include <gsl/gsl>
namespace exception
{
namespace
{
constexpr MINIDUMP_TYPE get_minidump_type()
{
const auto type = MiniDumpIgnoreInaccessibleMemory //
| MiniDumpWithHandleData //
| MiniDumpScanMemory //
| MiniDumpWithProcessThreadData //
| MiniDumpWithFullMemoryInfo //
| MiniDumpWithThreadInfo //
| MiniDumpWithUnloadedModules;
return static_cast<MINIDUMP_TYPE>(type);
}
std::string get_temp_filename()
{
char filename[MAX_PATH] = {0};
char pathname[MAX_PATH] = {0};
GetTempPathA(sizeof(pathname), pathname);
GetTempFileNameA(pathname, "Plutonium T6 -", 0, filename);
return filename;
}
HANDLE write_dump_to_temp_file(const LPEXCEPTION_POINTERS exceptioninfo)
{
MINIDUMP_EXCEPTION_INFORMATION minidump_exception_info = {GetCurrentThreadId(), exceptioninfo, FALSE};
auto* const file_handle = CreateFileA(get_temp_filename().data(), GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS,
FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
nullptr);
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file_handle, get_minidump_type(),
&minidump_exception_info,
nullptr,
nullptr))
{
MessageBoxA(nullptr, "There was an error creating the minidump! Hit OK to close the program.",
"Minidump Error", MB_OK | MB_ICONERROR);
TerminateProcess(GetCurrentProcess(), 123);
}
return file_handle;
}
std::string read_file(HANDLE file_handle)
{
FlushFileBuffers(file_handle);
SetFilePointer(file_handle, 0, nullptr, FILE_BEGIN);
std::string buffer{};
DWORD bytes_read = 0;
char temp_bytes[0x2000];
do
{
if (!ReadFile(file_handle, temp_bytes, sizeof(temp_bytes), &bytes_read, nullptr))
{
return {};
}
buffer.append(temp_bytes, bytes_read);
}
while (bytes_read == sizeof(temp_bytes));
return buffer;
}
}
std::string create_minidump(const LPEXCEPTION_POINTERS exceptioninfo)
{
auto* const file_handle = write_dump_to_temp_file(exceptioninfo);
const auto _ = gsl::finally([file_handle]()
{
CloseHandle(file_handle);
});
return read_file(file_handle);
}
}
@@ -0,0 +1,6 @@
#pragma once
namespace exception
{
std::string create_minidump(LPEXCEPTION_POINTERS exceptioninfo);
}
@@ -0,0 +1,123 @@
#include <stdinc.hpp>
#include "game.hpp"
#include <utils/hook.hpp>
namespace game
{
gamemode current = *reinterpret_cast<int*>(0x401337) == 0x281488C0
? gamemode::multiplayer
: gamemode::singleplayer;
namespace environment
{
bool is_mp()
{
return current == gamemode::multiplayer;
}
bool is_sp()
{
return current == gamemode::singleplayer;
}
}
void SV_GetServerinfo(char* buffer, int bufferSize)
{
if (bufferSize < 1)
{
Com_Error(ERR_DROP, "\x15SV_GetServerinfo: bufferSize == %i", bufferSize);
}
I_strncpyz(buffer, Dvar_InfoString(0, 4), bufferSize);
}
void AddRefToValue(scriptInstance_t inst, const VariableValue* value)
{
AddRefToValue_(inst, value->type, value->u);
}
void RemoveRefToValue(scriptInstance_t inst, const int type, VariableUnion value)
{
if (game::environment::is_mp())
{
mp::RemoveRefToValue(inst, type, value);
}
else
{
game::VariableValue var{};
var.type = type;
var.u = value;
sp::RemoveRefToValue(inst, &var);
}
}
unsigned int GetObjectType(scriptInstance_t, unsigned int id)
{
if (game::environment::is_sp())
{
return game::scr_VarGlob->variableList_sp[id + 1].w.type & 0x1F;
}
else
{
return game::scr_VarGlob->variableList_mp[id + 1].w.type & 0x1F;
}
}
unsigned int AllocVariable(scriptInstance_t inst)
{
static const auto func = utils::hook::assemble([](utils::hook::assembler& a)
{
a.mov(eax, 0);
a.call(SELECT_VALUE(0, 0x8E49A0));
a.ret();
});
return utils::hook::invoke<unsigned int>(func, inst);
}
void Scr_NotifyId(scriptInstance_t inst, int /*client_num*/, unsigned int id,
unsigned int string_value, unsigned int paramcount)
{
game::Scr_NotifyNum_Internal(inst, -1, id, 0, string_value, paramcount);
}
VariableValue Scr_GetArrayIndexValue(scriptInstance_t, unsigned int name)
{
VariableValue value{};
if (name >= 0x10000)
{
if (name >= 0x17FFE)
{
value.type = 6;
value.u.intValue = name - 0x800000;
}
else
{
value.type = 1;
value.u.intValue = name - 0x10000;
}
}
else
{
value.type = 2;
value.u.intValue = name;
}
return value;
}
unsigned int Scr_GetSelf(scriptInstance_t, unsigned int threadId)
{
if (game::environment::is_sp())
{
return game::scr_VarGlob->variableList_sp[threadId + 1].u.o.size;
}
else
{
return game::scr_VarGlob->variableList_mp[threadId + 1].u.o.u.self;
}
}
}
@@ -0,0 +1,75 @@
#pragma once
#include "structs.hpp"
#define SELECT_VALUE(sp, mp) (game::environment::is_sp() ? (sp) : (mp))
namespace game
{
enum gamemode
{
none,
multiplayer,
singleplayer
};
extern gamemode current;
namespace environment
{
bool is_mp();
bool is_sp();
}
template <typename T>
class symbol
{
public:
symbol(const size_t sp_address, const size_t mp_address)
: sp_object_(reinterpret_cast<T*>(sp_address))
, mp_object_(reinterpret_cast<T*>(mp_address))
{
}
T* get() const
{
if (environment::is_mp())
{
return mp_object_;
}
return sp_object_;
}
operator T* () const
{
return this->get();
}
T* operator->() const
{
return this->get();
}
private:
T* sp_object_;
T* mp_object_;
};
void SV_GetServerinfo(char* buffer, int bufferSize);
void AddRefToValue(scriptInstance_t inst, const VariableValue* value);
void RemoveRefToValue(scriptInstance_t inst, const int type, VariableUnion value);
unsigned int GetObjectType(scriptInstance_t, unsigned int id);
unsigned int AllocVariable(scriptInstance_t inst);
VariableValue Scr_GetArrayIndexValue(scriptInstance_t inst, unsigned int name);
unsigned int Scr_GetSelf(scriptInstance_t inst, unsigned int threadId);
void Scr_NotifyId(scriptInstance_t inst, int client_num, unsigned int id,
unsigned int string_value, unsigned int paramcount);
}
#include "symbols.hpp"
@@ -0,0 +1,418 @@
#include <stdinc.hpp>
#include "array.hpp"
#include "execution.hpp"
namespace scripting
{
namespace
{
std::vector<script_value> get_keys_sp(unsigned int id)
{
std::vector<script_value> result;
auto current = game::scr_VarGlob->variableList_sp[id + 1].nextSibling;
while (current)
{
const auto var = &game::scr_VarGlob->variableList_sp[current + 0x6000];
const auto key_value = game::Scr_GetArrayIndexValue(game::SCRIPTINSTANCE_SERVER, var->w.status >> 8);
result.push_back(key_value);
const auto next_sibling = game::scr_VarGlob->variableList_sp[current + 0x6000].nextSibling;
if (!next_sibling)
{
break;
}
current = static_cast<unsigned __int16>(game::scr_VarGlob->variableList_sp[next_sibling + 0x6000].id);
}
return result;
}
std::vector<script_value> get_keys_mp(unsigned int id)
{
std::vector<script_value> result;
auto current = game::scr_VarGlob->variableList_mp[id + 1].nextSibling;
while (current)
{
const auto var = &game::scr_VarGlob->variableList_mp[current + 0x8000];
const auto key_value = game::Scr_GetArrayIndexValue(game::SCRIPTINSTANCE_SERVER, var->w.status >> 8);
result.push_back(key_value);
const auto next_sibling = game::scr_VarGlob->variableList_mp[current + 0x8000].nextSibling;
if (!next_sibling)
{
break;
}
current = game::scr_VarGlob->variableList_mp[next_sibling + 0x8000].hash.id;
}
return result;
}
}
array_value::array_value(const array* array, const script_value& key)
: array_(array)
, key_(key)
{
const auto value = this->array_->get(key);
this->script_value::operator=(value);
}
void array_value::operator=(const script_value& value)
{
this->array_->set(this->key_, value);
this->script_value::operator=(value);
}
array::array(const std::uint32_t id)
: id_(id)
{
this->add();
}
array::array(const array& other)
{
this->operator=(other);
}
array::array(array&& other) noexcept
{
this->id_ = other.id_;
other.id_ = 0;
}
array::array()
{
this->id_ = game::Scr_AllocArray(game::SCRIPTINSTANCE_SERVER);
}
array::~array()
{
this->release();
}
array& array::operator=(const array& other)
{
if (&other != this)
{
this->release();
this->id_ = other.id_;
this->add();
}
return *this;
}
array& array::operator=(array&& other) noexcept
{
if (&other != this)
{
this->release();
this->id_ = other.id_;
other.id_ = 0;
}
return *this;
}
void array::add() const
{
if (this->id_)
{
game::VariableValue value{};
value.u.uintValue = this->id_;
value.type = game::SCRIPT_OBJECT;
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value);
}
}
void array::release() const
{
if (this->id_)
{
game::VariableValue value{};
value.u.uintValue = this->id_;
value.type = game::SCRIPT_OBJECT;
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, value.type, value.u);
}
}
std::vector<script_value> array::get_keys() const
{
return SELECT_VALUE(get_keys_sp, get_keys_mp)(this->id_);
}
std::uint32_t array::size() const
{
return static_cast<int>(game::Scr_GetSelf(game::SCRIPTINSTANCE_SERVER, this->id_));
}
std::uint32_t array::push_back(const script_value& value) const
{
this->set(this->size(), value);
return this->size();
}
void array::erase(const std::uint32_t index) const
{
const auto variable_id = game::FindArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index);
if (variable_id)
{
game::RemoveVariableValue(game::SCRIPTINSTANCE_SERVER, this->id_, variable_id);
}
}
void array::erase(const std::string& key) const
{
const auto string_value = game::SL_GetString(key.data(), 0, game::SCRIPTINSTANCE_SERVER);
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
if (variable_id)
{
game::RemoveVariableValue(game::SCRIPTINSTANCE_SERVER, this->id_, variable_id);
}
}
script_value array::get(const std::string& key) const
{
const auto string_value = game::SL_GetString(key.data(), 0, game::SCRIPTINSTANCE_SERVER);
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
if (!variable_id)
{
return {};
}
game::VariableValue variable_{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[variable_id + 0x6000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[variable_id + 0x8000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
return variable_;
}
void array::erase(const script_value& key) const
{
if (key.is<int>())
{
return this->erase(key.as<int>());
}
if (key.is<std::string>())
{
return this->erase(key.as<std::string>());
}
}
void array::erase(const array_iterator& iter) const
{
this->erase(iter->first);
}
script_value array::get(const unsigned int index) const
{
const auto variable_id = game::FindArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index);
if (!variable_id)
{
return {};
}
game::VariableValue variable_{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[variable_id + 0x6000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[variable_id + 0x8000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
return variable_;
}
script_value array::get(const script_value& key) const
{
if (key.is<int>())
{
return this->get(key.as<int>());
}
if (key.is<std::string>())
{
return this->get(key.as<std::string>());
}
throw std::runtime_error(std::format("invalid key type '{}'", key.type_name()));
}
void array::set(const std::string& key, const script_value& value) const
{
const auto& value_ = value.get_raw();
const auto variable_id = this->get_value_id(key);
if (!variable_id)
{
return;
}
game::VariableValue previous{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[variable_id + 0x6000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_.type;
variable->u.u = value_.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[variable_id + 0x8000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_.type;
variable->u.u = value_.u;
}
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_);
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, previous.type, previous.u);
}
void array::set(const std::uint32_t index, const script_value& value) const
{
const auto& value_ = value.get_raw();
const auto variable_id = this->get_value_id(index);
if (!variable_id)
{
return;
}
game::VariableValue previous{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[variable_id + 0x6000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_.type;
variable->u.u = value_.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[variable_id + 0x8000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_.type;
variable->u.u = value_.u;
}
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_);
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, previous.type, previous.u);
}
void array::set(const script_value& key, const script_value& value) const
{
if (key.is<int>())
{
return this->set(key.as<int>(), value);
}
if (key.is<std::string>())
{
return this->set(key.as<std::string>(), value);
}
throw std::runtime_error(std::format("invalid key type '{}'", key.type_name()));
}
std::uint32_t array::get_entity_id() const
{
return this->id_;
}
std::uint32_t array::get_value_id(const std::string& key) const
{
const auto string_value = game::SL_GetString(key.data(), 0, game::SCRIPTINSTANCE_SERVER);
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
if (!variable_id)
{
return game::GetNewVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
}
return variable_id;
}
std::uint32_t array::get_value_id(const std::uint32_t index) const
{
const auto variable_id = game::FindArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index);
if (!variable_id)
{
return game::GetNewArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index);
}
return variable_id;
}
entity array::get_raw() const
{
return entity(this->id_);
}
array_value array::operator[](const script_value& key) const
{
return array_value(this, key);
}
array_iterator array::begin() const
{
const auto keys = this->get_keys();
return array_iterator(this, keys, 0);
}
array_iterator array::end() const
{
return array_iterator(this);
}
array_iterator array::find(const script_value& key) const
{
const auto keys = this->get_keys();
for (auto i = 0u; i < keys.size(); i++)
{
if (keys[i] == key)
{
return array_iterator(this, keys, i);
}
}
return array_iterator(this);
}
}
@@ -0,0 +1,107 @@
#pragma once
#include "script_value.hpp"
#include "container_iterator.hpp"
namespace scripting
{
class array_value : public script_value
{
public:
array_value(const array* array, const script_value& key);
void operator=(const script_value& value);
template <typename T>
T as() const
{
try
{
return script_value::as<T>();
}
catch (const std::exception& e)
{
throw std::runtime_error(std::format("array value '{}' {}", this->key_.to_string(), e.what()));
}
}
private:
script_value key_;
const array* array_;
};
template <typename IteratorType>
class array_iterator_base : public IteratorType
{
public:
array_iterator_base(const array* container)
: IteratorType(container)
{
}
array_iterator_base(const array* container, const std::vector<script_value>& keys, const std::int64_t key_index)
: IteratorType(container, keys, key_index)
{
}
};
using array_iterator = array_iterator_base<container_iterator<array, script_value, array_value>>;
class array final
{
public:
array();
array(const std::uint32_t id);
array(const array& other);
array(array&& other) noexcept;
~array();
array& operator=(const array& other);
array& operator=(array&& other) noexcept;
std::vector<script_value> get_keys() const;
std::uint32_t size() const;
std::uint32_t push_back(const script_value& value) const;
template <typename ...Args>
void emplace_back(Args&&... args)
{
this->push_back(std::forward<Args>(args)...);
}
void erase(const std::uint32_t index) const;
void erase(const std::string& key) const;
void erase(const script_value& key) const;
void erase(const array_iterator& iter) const;
script_value get(const script_value&) const;
script_value get(const std::string&) const;
script_value get(const std::uint32_t) const;
void set(const script_value&, const script_value&) const;
void set(const std::string&, const script_value&) const;
void set(const std::uint32_t, const script_value&) const;
std::uint32_t get_entity_id() const;
std::uint32_t get_value_id(const std::string&) const;
std::uint32_t get_value_id(const std::uint32_t) const;
entity get_raw() const;
array_value operator[](const script_value& key) const;
array_iterator begin() const;
array_iterator end() const;
array_iterator find(const script_value& key) const;
private:
void add() const;
void release() const;
std::uint32_t id_{};
};
}
@@ -0,0 +1,99 @@
#pragma once
#include "script_value.hpp"
namespace scripting
{
template <typename ContainerType, typename KeyType, typename ValueType>
class container_iterator
{
public:
static constexpr std::int64_t container_iterator_end_key = -1;
container_iterator(const ContainerType* container)
: container_(container)
, key_index_(container_iterator_end_key)
{
}
container_iterator(const ContainerType* container, const std::vector<KeyType>& keys, const std::int64_t key_index)
: container_(container)
, keys_(keys)
, key_index_(key_index)
{
this->update_pair();
}
std::pair<KeyType, ValueType>& operator*()
{
return this->pair_.value();
}
std::pair<KeyType, ValueType>* operator->()
{
return &this->pair_.value();
}
const std::pair<KeyType, ValueType>& operator*() const
{
return this->pair_.value();
}
const std::pair<KeyType, ValueType>* operator->() const
{
return &this->pair_.value();
}
container_iterator& operator++()
{
if (this->key_index_ == container_iterator_end_key)
{
return *this;
}
const auto size = this->keys_.size();
if (this->key_index_ + 1 >= size)
{
this->key_index_ = container_iterator_end_key;
return *this;
}
this->key_index_++;
this->update_pair();
return *this;
}
container_iterator operator++(int)
{
const auto pre = *this;
this->operator++();
return pre;
}
friend bool operator==(const container_iterator& a, const container_iterator& b)
{
return a.container_ == b.container_ && a.key_index_ == b.key_index_;
};
friend bool operator!=(const container_iterator& a, const container_iterator& b)
{
return a.container_ != b.container_ || a.key_index_ != b.key_index_;
};
private:
void update_pair()
{
const auto index = static_cast<size_t>(this->key_index_);
const auto& key = this->keys_[index];
const auto value = this->container_->operator[](key);
this->pair_.emplace(std::make_pair(key, value));
}
const ContainerType* container_;
std::int64_t key_index_;
std::vector<KeyType> keys_;
std::optional<std::pair<KeyType, ValueType>> pair_;
};
}
@@ -0,0 +1,120 @@
#include <stdinc.hpp>
#include "entity.hpp"
#include "script_value.hpp"
#include "execution.hpp"
namespace scripting
{
entity::entity()
: entity(0)
{
}
entity::entity(const entity& other) : entity(other.entity_id_)
{
}
entity::entity(entity&& other) noexcept
{
this->entity_id_ = other.entity_id_;
other.entity_id_ = 0;
}
entity::entity(const unsigned int entity_id)
: entity_id_(entity_id)
{
this->add();
}
entity::~entity()
{
this->release();
}
entity& entity::operator=(const entity& other)
{
if (&other != this)
{
this->release();
this->entity_id_ = other.entity_id_;
this->add();
}
return *this;
}
entity& entity::operator=(entity&& other) noexcept
{
if (&other != this)
{
this->release();
this->entity_id_ = other.entity_id_;
other.entity_id_ = 0;
}
return *this;
}
unsigned int entity::get_entity_id() const
{
return this->entity_id_;
}
game::scr_entref_t entity::get_entity_reference() const
{
if (!this->entity_id_)
{
const auto not_null = static_cast<uint16_t>(~0ui16);
return game::scr_entref_t{not_null, not_null};
}
game::scr_entref_t entref{};
game::Scr_GetEntityIdRef(&entref, game::SCRIPTINSTANCE_SERVER, this->get_entity_id());
return entref;
}
bool entity::operator==(const entity& other) const noexcept
{
return this->get_entity_id() == other.get_entity_id();
}
bool entity::operator!=(const entity& other) const noexcept
{
return !this->operator==(other);
}
void entity::add() const
{
if (this->entity_id_)
{
game::VariableValue value;
value.type = game::SCRIPT_OBJECT;
value.u.uintValue = this->entity_id_;
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value);
}
}
void entity::release() const
{
if (this->entity_id_)
{
game::VariableValue value;
value.type = game::SCRIPT_OBJECT;
value.u.uintValue = this->entity_id_;
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, value.type, value.u);
}
}
void entity::set(const std::string& field, const script_value& value) const
{
set_entity_field(*this, field, value);
}
template <>
script_value entity::get<script_value>(const std::string& field) const
{
return get_entity_field(*this, field);
}
}
@@ -0,0 +1,47 @@
#pragma once
#include "game/game.hpp"
#include "script_value.hpp"
namespace scripting
{
class entity final
{
public:
entity();
entity(unsigned int entity_id);
entity(const entity& other);
entity(entity&& other) noexcept;
~entity();
entity& operator=(const entity& other);
entity& operator=(entity&& other) noexcept;
void set(const std::string& field, const script_value& value) const;
template <typename T = script_value>
T get(const std::string& field) const;
unsigned int get_entity_id() const;
game::scr_entref_t get_entity_reference() const;
bool operator ==(const entity& other) const noexcept;
bool operator !=(const entity& other) const noexcept;
private:
unsigned int entity_id_;
void add() const;
void release() const;
};
template <>
script_value entity::get(const std::string& field) const;
template <typename T>
T entity::get(const std::string& field) const
{
return this->get<script_value>(field).as<T>();
}
}
@@ -0,0 +1,13 @@
#pragma once
#include "script_value.hpp"
#include "entity.hpp"
namespace scripting
{
struct event
{
std::string name;
entity entity{};
std::vector<script_value> arguments;
};
}
@@ -0,0 +1,154 @@
#include <stdinc.hpp>
#include "execution.hpp"
#include "safe_execution.hpp"
#include "stack_isolation.hpp"
#include "component/scripting.hpp"
namespace scripting
{
namespace
{
game::VariableValue* allocate_argument()
{
game::VariableValue* value_ptr = ++game::scr_VmPub->top;
++game::scr_VmPub->inparamcount;
return value_ptr;
}
script_value get_return_value()
{
if (game::scr_VmPub->inparamcount == 0)
{
return {};
}
game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER);
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
game::scr_VmPub->inparamcount = 0;
return script_value(game::scr_VmPub->top[1 - game::scr_VmPub->outparamcount]);
}
int get_field_id(const int classnum, const std::string& field)
{
if (scripting::fields_table[classnum].find(field) != scripting::fields_table[classnum].end())
{
return scripting::fields_table[classnum][field];
}
return -1;
}
}
void push_value(const script_value& value)
{
auto* value_ptr = allocate_argument();
*value_ptr = value.get_raw();
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, value_ptr);
}
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments)
{
stack_isolation _;
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
{
push_value(*i);
}
const auto event_id = game::SL_GetString(event.data(), 0, game::SCRIPTINSTANCE_SERVER);
game::Scr_NotifyId(game::SCRIPTINSTANCE_SERVER, 0, entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount);
}
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments)
{
const auto id = entity.get_entity_id();
stack_isolation _;
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
{
push_value(*i);
}
game::VariableValue value{};
value.type = game::SCRIPT_OBJECT;
value.u.uintValue = id;
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value);
const auto local_id = game::AllocThread(game::SCRIPTINSTANCE_SERVER, id);
const auto result = game::VM_Execute(game::SCRIPTINSTANCE_SERVER, local_id, pos, arguments.size());
game::RemoveRefToObject(game::SCRIPTINSTANCE_SERVER, result);
return get_return_value();
}
script_value get_custom_field(const entity& entity, const std::string& field)
{
const object object = entity.get_entity_id();
return object.get(field);
}
void set_custom_field(const entity& entity, const std::string& field, const script_value& value)
{
const object object = entity.get_entity_id();
object.set(field, value);
}
void set_entity_field(const entity& entity, const std::string& field, const script_value& value)
{
const auto entref = entity.get_entity_reference();
const int id = get_field_id(entref.classnum, field);
if (id != -1)
{
stack_isolation _;
push_value(value);
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
game::scr_VmPub->inparamcount = 0;
if (!safe_execution::set_entity_field(entref, id))
{
throw std::runtime_error("Failed to set value for field '" + field + "'");
}
}
else
{
set_custom_field(entity, field, value);
}
}
script_value get_entity_field(const entity& entity, const std::string& field)
{
const auto entref = entity.get_entity_reference();
const auto id = get_field_id(entref.classnum, field);
if (id != -1)
{
stack_isolation _;
game::VariableValue value{};
if (!safe_execution::get_entity_field(entref, id, &value))
{
throw std::runtime_error("Failed to get value for field '" + field + "'");
}
const auto __ = gsl::finally([&value]()
{
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, value.type, value.u);
});
return value;
}
else
{
return get_custom_field(entity, field);
}
}
unsigned int make_object()
{
return 0;
}
}
@@ -0,0 +1,22 @@
#pragma once
#include "game/game.hpp"
#include "entity.hpp"
#include "array.hpp"
#include "object.hpp"
#include "function.hpp"
#include "thread.hpp"
#include "script_value.hpp"
namespace scripting
{
void push_value(const script_value& value);
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments);
void set_entity_field(const entity& entity, const std::string& field, const script_value& value);
script_value get_entity_field(const entity& entity, const std::string& field);
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments);
unsigned int make_object();
}
@@ -0,0 +1,42 @@
#include <stdinc.hpp>
#include "function.hpp"
#include "execution.hpp"
#include "../../component/scripting.hpp"
namespace scripting
{
function::function(const char* pos)
: pos_(pos)
{
}
script_value function::get_raw() const
{
game::VariableValue value;
value.type = game::SCRIPT_FUNCTION;
value.u.codePosValue = this->pos_;
return value;
}
const char* function::get_pos() const
{
return this->pos_;
}
std::string function::get_name() const
{
if (scripting::script_function_table_rev.find(this->pos_) != scripting::script_function_table_rev.end())
{
const auto& func = scripting::script_function_table_rev[this->pos_];
return utils::string::va("%s::%s", func.first.data(), func.second.data());
}
return "unknown function";
}
script_value function::call(const entity& self, const arguments& arguments) const
{
return exec_ent_thread(self, this->pos_, arguments);
}
}
@@ -0,0 +1,48 @@
#pragma once
#include "entity.hpp"
#include "script_value.hpp"
namespace scripting
{
class function
{
public:
function(const char*);
script_value get_raw() const;
const char* get_pos() const;
std::string get_name() const;
script_value call(const entity& self, const arguments& arguments) const;
script_value operator()(const entity& self, const arguments& arguments) const
{
return this->call(self, arguments);
}
script_value operator()(const arguments& arguments) const
{
return this->call(*game::levelEntityId, arguments);
}
script_value operator()() const
{
return this->call(*game::levelEntityId, {});
}
template<class ...T>
arguments operator()(T... arguments) const
{
return this->call(*game::levelEntityId, {arguments...});
}
template<class ...T>
arguments operator()(const entity& self, T... arguments) const
{
return this->call(self, {arguments...});
}
private:
const char* pos_;
};
}
@@ -0,0 +1,326 @@
#include <stdinc.hpp>
#include "object.hpp"
#include "execution.hpp"
#include "../../component/scripting.hpp"
namespace scripting
{
namespace
{
std::string get_string(unsigned int canonical_string)
{
if (canonical_string_table.find(canonical_string) != canonical_string_table.end())
{
return canonical_string_table[canonical_string];
}
return "";
}
unsigned int get_canonical_string(const std::string& str)
{
for (const auto& value : canonical_string_table)
{
if (value.second == str)
{
return value.first;
}
}
return game::SL_GetCanonicalString(game::SCRIPTINSTANCE_SERVER, str.data());
}
std::vector<std::string> get_keys_sp(unsigned int id)
{
std::vector<std::string> result;
auto current = game::scr_VarGlob->variableList_sp[id + 1].nextSibling;
while (current)
{
const auto var = &game::scr_VarGlob->variableList_sp[current + 0x6000];
const auto key_value = get_string(var->w.status >> 8);
result.push_back(key_value);
const auto next_sibling = game::scr_VarGlob->variableList_sp[current + 0x6000].nextSibling;
if (!next_sibling)
{
break;
}
current = static_cast<unsigned __int16>(game::scr_VarGlob->variableList_sp[next_sibling + 0x6000].id);
}
return result;
}
std::vector<std::string> get_keys_mp(unsigned int id)
{
std::vector<std::string> result;
auto current = game::scr_VarGlob->variableList_mp[id + 1].nextSibling;
while (current)
{
const auto var = &game::scr_VarGlob->variableList_mp[current + 0x8000];
const auto key_value = get_string(var->w.status >> 8);
result.push_back(key_value);
const auto next_sibling = game::scr_VarGlob->variableList_mp[current + 0x8000].nextSibling;
if (!next_sibling)
{
break;
}
current = game::scr_VarGlob->variableList_mp[next_sibling + 0x8000].hash.id;
}
return result;
}
}
object_value::object_value(unsigned int parent_id, unsigned int id)
: id_(id)
, parent_id_(parent_id)
{
if (!this->id_)
{
return;
}
game::VariableValue variable_{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[this->id_ + 0x6000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[this->id_ + 0x8000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
this->value_ = variable_;
}
void object_value::operator=(const script_value& value)
{
if (!this->id_)
{
return;
}
const auto& value_0 = value.get_raw();
game::VariableValue previous{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[this->id_ + 0x6000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_0.type;
variable->u.u = value_0.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[this->id_ + 0x8000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_0.type;
variable->u.u = value_0.u;
}
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_0);
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, previous.type, previous.u);
this->value_ = value_0;
}
object::object(const unsigned int id)
: id_(id)
{
this->add();
}
object::object(const object& other)
{
this->operator=(other);
}
object::object(object&& other) noexcept
{
this->id_ = other.id_;
other.id_ = 0;
}
object::object()
{
this->id_ = game::AllocObject(game::SCRIPTINSTANCE_SERVER);
}
object::~object()
{
this->release();
}
object& object::operator=(const object& other)
{
if (&other != this)
{
this->release();
this->id_ = other.id_;
this->add();
}
return *this;
}
object& object::operator=(object&& other) noexcept
{
if (&other != this)
{
this->release();
this->id_ = other.id_;
other.id_ = 0;
}
return *this;
}
void object::add() const
{
if (this->id_)
{
game::VariableValue value{};
value.u.uintValue = this->id_;
value.type = game::SCRIPT_OBJECT;
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value);
}
}
void object::release() const
{
if (this->id_)
{
game::VariableValue value{};
value.u.uintValue = this->id_;
value.type = game::SCRIPT_OBJECT;
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, value.type, value.u);
}
}
std::vector<std::string> object::get_keys() const
{
return SELECT_VALUE(get_keys_sp, get_keys_mp)(this->id_);
}
unsigned int object::size() const
{
return game::Scr_GetSelf(game::SCRIPTINSTANCE_SERVER, this->id_);
}
void object::erase(const std::string& /*key*/) const
{
/*const auto string_value = game::SL_GetCanonicalString(key.data(), 0);
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
if (variable_id)
{
game::RemoveVariableValue(game::SCRIPTINSTANCE_SERVER, this->id_, variable_id);
}*/
}
script_value object::get(const std::string& key) const
{
const auto string_value = get_canonical_string(key);
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
if (!variable_id)
{
return {};
}
game::VariableValue variable_{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[variable_id + 0x6000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[variable_id + 0x8000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
return variable_;
}
void object::set(const std::string& key, const script_value& value) const
{
const auto& value_ = value.get_raw();
const auto variable_id = this->get_value_id(key);
if (!variable_id)
{
return;
}
game::VariableValue previous{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[variable_id + 0x6000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_.type;
variable->u.u = value_.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[variable_id + 0x8000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_.type;
variable->u.u = value_.u;
}
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_);
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, previous.type, previous.u);
}
unsigned int object::get_entity_id() const
{
return this->id_;
}
unsigned int object::get_value_id(const std::string& key) const
{
const auto string_value = get_canonical_string(key);
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
if (!variable_id)
{
return game::GetNewVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
}
return variable_id;
}
entity object::get_raw() const
{
return entity(this->id_);
}
}
@@ -0,0 +1,53 @@
#pragma once
#include "script_value.hpp"
namespace scripting
{
class object_value : public script_value
{
public:
object_value(unsigned int, unsigned int);
void operator=(const script_value&);
private:
unsigned int id_;
unsigned int parent_id_;
};
class object final
{
public:
object();
object(const unsigned int);
object(const object& other);
object(object&& other) noexcept;
~object();
object& operator=(const object& other);
object& operator=(object&& other) noexcept;
std::vector<std::string> get_keys() const;
unsigned int size() const;
void erase(const std::string&) const;
script_value get(const std::string&) const;
void set(const std::string&, const script_value&) const;
unsigned int get_entity_id() const;
unsigned int get_value_id(const std::string&) const;
entity get_raw() const;
object_value operator[](const std::string& key) const
{
return {this->id_, this->get_value_id(key)};
}
private:
void add() const;
void release() const;
unsigned int id_{};
};
}
@@ -0,0 +1,67 @@
#include <stdinc.hpp>
#include "safe_execution.hpp"
#pragma warning(push)
#pragma warning(disable: 4611)
namespace scripting::safe_execution
{
bool execute_with_seh(const script_function function, const game::scr_entref_t& entref)
{
__try
{
function(entref);
return true;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
}
bool call(const script_function function, const game::scr_entref_t& entref)
{
*game::g_script_error_level += 1;
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level], 0))
{
*game::g_script_error_level -= 1;
return false;
}
const auto result = execute_with_seh(function, entref);
*game::g_script_error_level -= 1;
return result;
}
bool set_entity_field(const game::scr_entref_t& entref, const int offset)
{
*game::g_script_error_level += 1;
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level], 0))
{
*game::g_script_error_level -= 1;
return false;
}
game::Scr_SetObjectField(entref.classnum, entref.entnum, offset);
*game::g_script_error_level -= 1;
return true;
}
bool get_entity_field(const game::scr_entref_t& entref, const int offset, game::VariableValue* value)
{
*game::g_script_error_level += 1;
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level], 0))
{
value->type = game::SCRIPT_NONE;
value->u.intValue = 0;
*game::g_script_error_level -= 1;
return false;
}
*value = game::GetEntityFieldValue(game::SCRIPTINSTANCE_SERVER, entref.classnum, entref.entnum, 0, offset);
*game::g_script_error_level -= 1;
return true;
}
}
@@ -0,0 +1,14 @@
#pragma once
#include "game/game.hpp"
namespace scripting::safe_execution
{
using script_function = void(*)(game::scr_entref_t);
bool execute_with_seh(const script_function function, const game::scr_entref_t& entref);
bool call(script_function function, const game::scr_entref_t& entref);
bool set_entity_field(const game::scr_entref_t& entref, int offset);
bool get_entity_field(const game::scr_entref_t& entref, int offset, game::VariableValue* value);
}
@@ -0,0 +1,491 @@
#include <stdinc.hpp>
#include "script_value.hpp"
#include "entity.hpp"
#include "array.hpp"
#include "function.hpp"
#include "object.hpp"
namespace scripting
{
/***********************************************
* Constructors
**********************************************/
script_value::script_value(const game::VariableValue& value)
: value_(value)
{
}
script_value::script_value(void* value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_INTEGER;
variable.u.intValue = reinterpret_cast<uintptr_t>(value);
this->value_ = variable;
}
script_value::script_value(const int value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_INTEGER;
variable.u.intValue = value;
this->value_ = variable;
}
script_value::script_value(const unsigned int value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_INTEGER;
variable.u.uintValue = value;
this->value_ = variable;
}
script_value::script_value(const bool value)
: script_value(static_cast<unsigned>(value))
{
}
script_value::script_value(const float value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_FLOAT;
variable.u.floatValue = value;
this->value_ = variable;
}
script_value::script_value(const double value)
: script_value(static_cast<float>(value))
{
}
script_value::script_value(const char* value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_STRING;
variable.u.stringValue = game::SL_GetString(value, 0, game::SCRIPTINSTANCE_SERVER);
const auto _ = gsl::finally([&variable]()
{
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable.type, variable.u);
});
this->value_ = variable;
}
script_value::script_value(const std::string& value)
: script_value(value.data())
{
}
script_value::script_value(const entity& value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_OBJECT;
variable.u.pointerValue = value.get_entity_id();
this->value_ = variable;
}
script_value::script_value(const array& value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_OBJECT;
variable.u.pointerValue = value.get_entity_id();
this->value_ = variable;
}
script_value::script_value(const object& value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_OBJECT;
variable.u.pointerValue = value.get_entity_id();
this->value_ = variable;
}
script_value::script_value(const function& value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_FUNCTION;
variable.u.codePosValue = value.get_pos();
this->value_ = variable;
}
script_value::script_value(const vector& value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_VECTOR;
variable.u.vectorValue = game::Scr_AllocVector(game::SCRIPTINSTANCE_SERVER, value);
const auto _ = gsl::finally([&variable]()
{
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable.type, variable.u);
});
this->value_ = variable;
}
/***********************************************
* Integer
**********************************************/
template <>
bool script_value::is<int>() const
{
return this->get_raw().type == game::SCRIPT_INTEGER;
}
template <>
bool script_value::is<unsigned int>() const
{
return this->is<int>();
}
template <>
bool script_value::is<unsigned short>() const
{
return this->is<int>();
}
template <>
bool script_value::is<bool>() const
{
return this->is<int>();
}
template <>
bool script_value::is<short>() const
{
return this->is<int>();
}
template <>
bool script_value::is<char>() const
{
return this->is<int>();
}
template <>
int script_value::get() const
{
return this->get_raw().u.intValue;
}
template <>
unsigned int script_value::get() const
{
return this->get_raw().u.uintValue;
}
template <>
bool script_value::get() const
{
return this->get_raw().u.uintValue != 0;
}
template <>
unsigned short script_value::get() const
{
return static_cast<unsigned short>(this->get_raw().u.uintValue);
}
template <>
short script_value::get() const
{
return static_cast<short>(this->get_raw().u.intValue);
}
template <>
char script_value::get() const
{
return static_cast<char>(this->get_raw().u.intValue);
}
/***********************************************
* Float
**********************************************/
template <>
bool script_value::is<float>() const
{
const auto type = this->get_raw().type;
return type == game::SCRIPT_FLOAT || type == game::SCRIPT_INTEGER;
}
template <>
bool script_value::is<double>() const
{
return this->is<float>();
}
template <>
float script_value::get() const
{
const auto type = this->get_raw().type;
if (type == game::SCRIPT_INTEGER)
{
return static_cast<float>(this->get_raw().u.intValue);
}
return this->get_raw().u.floatValue;
}
template <>
double script_value::get() const
{
const auto type = this->get_raw().type;
if (type == game::SCRIPT_INTEGER)
{
return static_cast<double>(this->get_raw().u.intValue);
}
return static_cast<double>(this->get_raw().u.floatValue);
}
/***********************************************
* String
**********************************************/
template <>
bool script_value::is<const char*>() const
{
return this->get_raw().type == game::SCRIPT_STRING
|| this->get_raw().type == game::SCRIPT_ISTRING;
}
template <>
bool script_value::is<std::string>() const
{
return this->is<const char*>();
}
template <>
const char* script_value::get() const
{
return game::SL_ConvertToString(static_cast<unsigned int>(this->get_raw().u.stringValue),
game::SCRIPTINSTANCE_SERVER);
}
template <>
std::string script_value::get() const
{
return this->get<const char*>();
}
/***********************************************
* Entity
**********************************************/
template <>
bool script_value::is<entity>() const
{
return this->get_raw().type == game::SCRIPT_OBJECT;
}
template <>
entity script_value::get() const
{
return entity(this->get_raw().u.pointerValue);
}
/***********************************************
* Array
**********************************************/
template <>
bool script_value::is<array>() const
{
if (this->get_raw().type != game::SCRIPT_OBJECT)
{
return false;
}
const auto id = this->get_raw().u.uintValue;
const auto type = game::GetObjectType(game::SCRIPTINSTANCE_SERVER, id);
return type == game::SCRIPT_ARRAY;
}
template <>
array script_value::get() const
{
return array(this->get_raw().u.uintValue);
}
/***********************************************
* Struct
**********************************************/
template <>
bool script_value::is<object>() const
{
if (this->get_raw().type != game::SCRIPT_OBJECT)
{
return false;
}
const auto id = this->get_raw().u.uintValue;
const auto type = game::GetObjectType(game::SCRIPTINSTANCE_SERVER, id);
return type == game::SCRIPT_STRUCT || type == game::SCRIPT_ENTITY;
}
template <>
object script_value::get() const
{
return object(this->get_raw().u.uintValue);
}
/***********************************************
* Function
**********************************************/
template <>
bool script_value::is<function>() const
{
return this->get_raw().type == game::SCRIPT_FUNCTION;
}
template <>
function script_value::get() const
{
return function(this->get_raw().u.codePosValue);
}
/***********************************************
* Vector
**********************************************/
template <>
bool script_value::is<vector>() const
{
return this->get_raw().type == game::SCRIPT_VECTOR;
}
template <>
bool script_value::is<float*>() const
{
return this->is<vector>();
}
template <>
vector script_value::get() const
{
return this->get_raw().u.vectorValue;
}
/***********************************************
*
**********************************************/
template <>
bool script_value::is<script_value>() const
{
return true;
}
template <>
script_value script_value::get() const
{
return this;
}
const game::VariableValue& script_value::get_raw() const
{
return this->value_.get();
}
std::string script_value::to_string() const
{
if (this->is<int>())
{
return utils::string::va("%i", this->as<int>());
}
if (this->is<float>())
{
return utils::string::va("%f", this->as<float>());
}
if (this->is<std::string>())
{
return this->as<std::string>();
}
if (this->is<vector>())
{
const auto vec = this->as<vector>();
return utils::string::va("(%g, %g, %g)",
vec.get_x(),
vec.get_y(),
vec.get_z()
);
}
if (this->is<function>())
{
const auto func = this->as<function>();
const auto& name = func.get_name();
return utils::string::va("[[ %s ]]", name.data());
}
return this->type_name();
}
function_argument::function_argument(const arguments& args, const script_value& value, const size_t index, const bool exists)
: script_value(value)
, values_(args)
, index_(index)
, exists_(exists)
{
}
function_arguments::function_arguments(const arguments& values)
: values_(values)
{
}
variadic_args::variadic_args(const size_t begin)
: std::vector<function_argument>({})
, begin_(begin)
{
}
function_argument variadic_args::operator[](size_t index) const
{
if (index >= this->size())
{
throw std::runtime_error(utils::string::va("parameter %d does not exist", this->begin_ + index));
}
return std::vector<function_argument>::operator[](index);
}
function_argument function_arguments::operator[](const size_t index) const
{
if (index >= values_.size())
{
return {values_, {}, index, false};
}
return {values_, values_[index], index, true};
}
arguments function_arguments::get_raw() const
{
return this->values_;
}
size_t function_arguments::size() const
{
return this->values_.size();
}
}
@@ -0,0 +1,399 @@
#pragma once
#include "game/game.hpp"
#include "variable_value.hpp"
#include "vector.hpp"
#include <utils/string.hpp>
namespace scripting
{
class entity;
class array;
class object;
class function;
class script_value;
namespace
{
std::unordered_map<int, std::string> typenames =
{
{0, "undefined"},
{1, "object"},
{2, "string"},
{3, "localized string"},
{4, "vector"},
{5, "float"},
{6, "int"},
{7, "codepos"},
{8, "precodepos"},
{9, "function"},
{10, "stack"},
{11, "animation"},
{12, "developer codepos"},
{13, "thread"},
{14, "thread"},
{15, "thread"},
{16, "thread"},
{17, "struct"},
{18, "removed entity"},
{19, "entity"},
{20, "array"},
{21, "removed thread"},
};
std::string get_typename(const game::VariableValue& value)
{
auto type_ = 0;
if (value.type == game::SCRIPT_OBJECT)
{
type_ = game::GetObjectType(game::SCRIPTINSTANCE_SERVER, value.u.uintValue);
}
else
{
type_ = value.type;
}
if (typenames.find(type_) != typenames.end())
{
return typenames[type_];
}
return "unknown";
}
template <typename T>
std::string get_c_typename()
{
auto& info = typeid(T);
if (info == typeid(std::string))
{
return "string";
}
if (info == typeid(const char*))
{
return "string";
}
if (info == typeid(entity))
{
return "entity";
}
if (info == typeid(array))
{
return "array";
}
if (info == typeid(object))
{
return "struct";
}
if (info == typeid(function))
{
return "function";
}
if (info == typeid(vector))
{
return "vector";
}
return info.name();
}
}
using arguments = std::vector<script_value>;
class script_value
{
public:
script_value() = default;
script_value(const game::VariableValue& value);
script_value(void* value);
script_value(int value);
script_value(unsigned int value);
script_value(bool value);
script_value(float value);
script_value(double value);
script_value(const char* value);
script_value(const std::string& value);
script_value(const entity& value);
script_value(const array& value);
script_value(const object& value);
script_value(const function& value);
script_value(const vector& value);
private:
template <typename T>
T get() const
{
if constexpr (std::is_pointer<T>::value)
{
if (this->is<unsigned int>())
{
return reinterpret_cast<T>(this->as<unsigned int>());
}
}
if constexpr (std::is_constructible<T, std::string>::value)
{
if (this->is<std::string>())
{
return T(this->as<std::string>());
}
}
throw std::runtime_error("Invalid type");
}
public:
#define ADD_TYPE(type) \
template <> \
bool is<type>() const; \
private: \
template <> \
type get<>() const; \
public: \
template <typename T>
bool is() const
{
if constexpr (std::is_pointer<T>::value)
{
if (this->is<unsigned int>())
{
return true;
}
}
if (std::is_constructible<T, std::string>::value && this->is<std::string>())
{
return true;
}
return false;
}
ADD_TYPE(bool)
ADD_TYPE(int)
ADD_TYPE(unsigned int)
ADD_TYPE(unsigned short)
ADD_TYPE(float)
ADD_TYPE(float*)
ADD_TYPE(double)
ADD_TYPE(const char*)
ADD_TYPE(std::string)
ADD_TYPE(vector)
ADD_TYPE(array)
ADD_TYPE(object)
ADD_TYPE(function)
ADD_TYPE(entity)
ADD_TYPE(script_value)
template <typename T>
T as() const
{
if (!this->is<T>())
{
const auto type = get_typename(this->get_raw());
const auto c_type = get_c_typename<T>();
throw std::runtime_error(utils::string::va("has type '%s' but should be '%s'", type.data(), c_type.data()));
}
return get<T>();
}
template <typename T>
T as_or(const T& default_value) const
{
if (!this->is<T>())
{
return default_value;
}
return get<T>();
}
std::string type_name() const
{
return get_typename(this->get_raw());
}
template <template<class, class> class C, class T, typename ArrayType = array>
script_value(const C<T, std::allocator<T>>& container)
{
ArrayType array_{};
for (const auto& value : container)
{
array_.emplace_back(value);
}
game::VariableValue value{};
value.type = game::SCRIPT_OBJECT;
value.u.pointerValue = array_.get_entity_id();
this->value_ = value;
}
template<class ...T>
arguments operator()(T... arguments) const
{
return this->as<function>().call({arguments...});
}
std::string to_string() const;
friend bool operator==(const script_value& a, const script_value& b)
{
const auto& value_raw_a = a.get_raw();
const auto& value_raw_b = b.get_raw();
return value_raw_a.type != value_raw_b.type && value_raw_a.u.uintValue == value_raw_b.u.uintValue;
}
const game::VariableValue& get_raw() const;
variable_value value_{};
};
class function_argument;
class variadic_args : public std::vector<function_argument>
{
public:
variadic_args(const size_t begin);
function_argument operator[](size_t index) const;
private:
size_t begin_;
};
class function_argument : public script_value
{
public:
function_argument(const arguments& args, const script_value& value, const size_t index, const bool exists);
template <typename T>
T as() const
{
if (!this->exists_)
{
throw std::runtime_error(utils::string::va("parameter %d does not exist", this->index_));
}
try
{
return script_value::as<T>();
}
catch (const std::exception& e)
{
throw std::runtime_error(utils::string::va("parameter %d %s",
this->index_, e.what()));
}
}
template <typename T, typename I = int>
T* as_ptr() const
{
const auto value = script_value::as<I>();
if (value == nullptr)
{
throw std::runtime_error("is null");
}
return reinterpret_cast<T*>(value);
}
template <>
variadic_args as() const
{
variadic_args args{this->index_};
for (auto i = this->index_; i < this->values_.size(); i++)
{
args.emplace_back(this->values_, this->values_[i], i, true);
}
return args;
}
operator variadic_args() const
{
return this->as<variadic_args>();
}
template <template<class, class> class C, class T, class ArrayType = array>
operator C<T, std::allocator<T>>() const
{
const auto container_type = get_c_typename<C<T, std::allocator<T>>>();
if (!script_value::is<ArrayType>())
{
const auto type = get_typename(this->get_raw());
throw std::runtime_error(utils::string::va("has type '%s' but should be '%s'",
type.data(),
container_type.data()
));
}
C<T, std::allocator<T>> container{};
const auto array = script_value::as<ArrayType>();
for (auto i = 0u; i < array.size(); i++)
{
try
{
container.emplace_back(array.get(i).as<T>());
}
catch (const std::exception& e)
{
throw std::runtime_error(utils::string::va("element %d of parameter %d of type '%s' %s",
i, this->index_, container_type.data(), e.what()));
}
}
return container;
}
template <typename T>
operator T() const
{
return this->as<T>();
}
private:
arguments values_{};
size_t index_{};
bool exists_{};
};
class function_arguments
{
public:
function_arguments(const arguments& values);
function_argument operator[](const size_t index) const;
arguments get_raw() const;
size_t size() const;
private:
arguments values_{};
};
}
@@ -0,0 +1,27 @@
#include <stdinc.hpp>
#include "stack_isolation.hpp"
namespace scripting
{
stack_isolation::stack_isolation()
{
this->in_param_count_ = game::scr_VmPub->inparamcount;
this->out_param_count_ = game::scr_VmPub->outparamcount;
this->top_ = game::scr_VmPub->top;
this->max_stack_ = game::scr_VmPub->maxstack;
game::scr_VmPub->top = this->stack_;
game::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1];
game::scr_VmPub->inparamcount = 0;
game::scr_VmPub->outparamcount = 0;
}
stack_isolation::~stack_isolation()
{
game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER);
game::scr_VmPub->inparamcount = this->in_param_count_;
game::scr_VmPub->outparamcount = this->out_param_count_;
game::scr_VmPub->top = this->top_;
game::scr_VmPub->maxstack = this->max_stack_;
}
}
@@ -0,0 +1,25 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
class stack_isolation final
{
public:
stack_isolation();
~stack_isolation();
stack_isolation(stack_isolation&&) = delete;
stack_isolation(const stack_isolation&) = delete;
stack_isolation& operator=(stack_isolation&&) = delete;
stack_isolation& operator=(const stack_isolation&) = delete;
private:
game::VariableValue stack_[512]{};
game::VariableValue* max_stack_;
game::VariableValue* top_;
unsigned int in_param_count_;
unsigned int out_param_count_;
};
}
@@ -0,0 +1,67 @@
#include <stdinc.hpp>
#include "thread.hpp"
#include "execution.hpp"
#include "../../component/scripting.hpp"
namespace scripting
{
thread::thread(unsigned int id)
: id_(id)
, type_(game::GetObjectType(game::SCRIPTINSTANCE_SERVER, id))
{
}
script_value thread::get_raw() const
{
game::VariableValue value;
value.type = game::SCRIPT_OBJECT;
value.u.uintValue = this->id_;
return value;
}
unsigned int thread::get_entity_id() const
{
return this->id_;
}
unsigned int thread::get_type() const
{
return this->type_;
}
unsigned int thread::get_wait_time() const
{
return 0;
}
unsigned int thread::get_notify_name_id() const
{
return 0;
}
unsigned int thread::get_self() const
{
return game::Scr_GetSelf(game::SCRIPTINSTANCE_SERVER, this->id_);
}
std::string thread::get_notify_name() const
{
return game::SL_ConvertToString(this->get_notify_name_id(), game::SCRIPTINSTANCE_SERVER);
}
const char* thread::get_pos() const
{
return 0;
}
const char* thread::get_start_pos() const
{
return 0;
}
void thread::kill() const
{
}
}
@@ -0,0 +1,32 @@
#pragma once
#include "entity.hpp"
#include "script_value.hpp"
namespace scripting
{
class thread
{
public:
thread(unsigned int);
script_value get_raw() const;
unsigned int get_entity_id() const;
unsigned int get_type() const;
unsigned int get_self() const;
unsigned int get_wait_time() const;
unsigned int get_notify_name_id() const;
std::string get_notify_name() const;
const char* get_pos() const;
const char* get_start_pos() const;
void kill() const;
private:
unsigned int id_{};
unsigned int type_{};
};
}
@@ -0,0 +1,68 @@
#include <stdinc.hpp>
#include "variable_value.hpp"
namespace scripting
{
variable_value::variable_value(const game::VariableValue& value)
{
this->assign(value);
}
variable_value::variable_value(const variable_value& other) noexcept
{
this->operator=(other);
}
variable_value::variable_value(variable_value&& other) noexcept
{
this->operator=(std::move(other));
}
variable_value& variable_value::operator=(const variable_value& other) noexcept
{
if (this != &other)
{
this->release();
this->assign(other.value_);
}
return *this;
}
variable_value& variable_value::operator=(variable_value&& other) noexcept
{
if (this != &other)
{
this->release();
this->value_ = other.value_;
other.value_.type = game::SCRIPT_NONE;
}
return *this;
}
variable_value::~variable_value()
{
this->release();
}
const game::VariableValue& variable_value::get() const
{
return this->value_;
}
void variable_value::assign(const game::VariableValue& value)
{
this->value_ = value;
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &this->value_);
}
void variable_value::release()
{
if (this->value_.type != game::SCRIPT_NONE)
{
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, this->value_.type, this->value_.u);
this->value_.type = game::SCRIPT_NONE;
}
}
}
@@ -0,0 +1,27 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
class variable_value
{
public:
variable_value() = default;
variable_value(const game::VariableValue& value);
variable_value(const variable_value& other) noexcept;
variable_value(variable_value&& other) noexcept;
variable_value& operator=(const variable_value& other) noexcept;
variable_value& operator=(variable_value&& other) noexcept;
~variable_value();
const game::VariableValue& get() const;
private:
void assign(const game::VariableValue& value);
void release();
game::VariableValue value_{{0}, game::SCRIPT_NONE};
};
}
@@ -0,0 +1,85 @@
#include <stdinc.hpp>
#include "vector.hpp"
namespace scripting
{
vector::vector(const float* value)
{
for (auto i = 0; i < 3; ++i)
{
this->value_[i] = value[i];
}
}
vector::vector(const game::vec3_t& value)
: vector(&value[0])
{
}
vector::vector(const float x, const float y, const float z)
{
this->value_[0] = x;
this->value_[1] = y;
this->value_[2] = z;
}
vector::operator game::vec3_t& ()
{
return this->value_;
}
vector::operator const game::vec3_t& () const
{
return this->value_;
}
game::vec_t& vector::operator[](const size_t i)
{
if (i >= 3)
{
throw std::runtime_error("Out of bounds.");
}
return this->value_[i];
}
const game::vec_t& vector::operator[](const size_t i) const
{
if (i >= 3)
{
throw std::runtime_error("Out of bounds.");
}
return this->value_[i];
}
float vector::get_x() const
{
return this->operator[](0);
}
float vector::get_y() const
{
return this->operator[](1);
}
float vector::get_z() const
{
return this->operator[](2);
}
void vector::set_x(const float value)
{
this->operator[](0) = value;
}
void vector::set_y(const float value)
{
this->operator[](1) = value;
}
void vector::set_z(const float value)
{
this->operator[](2) = value;
}
}
@@ -0,0 +1,31 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
class vector final
{
public:
vector() = default;
vector(const float* value);
vector(const game::vec3_t& value);
vector(float x, float y, float z);
operator game::vec3_t& ();
operator const game::vec3_t& () const;
game::vec_t& operator[](size_t i);
const game::vec_t& operator[](size_t i) const;
float get_x() const;
float get_y() const;
float get_z() const;
void set_x(float value);
void set_y(float value);
void set_z(float value);
private:
game::vec3_t value_{ 0 };
};
}
@@ -0,0 +1,857 @@
#pragma once
namespace game
{
typedef float vec_t;
typedef vec_t vec2_t[2];
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
enum scriptInstance_t
{
SCRIPTINSTANCE_SERVER,
SCRIPTINSTANCE_CLIENT,
};
struct scr_entref_t
{
unsigned short entnum;
unsigned short classnum;
int client;
};
struct BuiltinMethodDef
{
const char* actionString;
unsigned int constId;
int min_args;
int max_args;
void(__cdecl* actionFunc)(scr_entref_t);
int type;
};
struct BuiltinFunctionDef
{
const char* actionString;
unsigned int constId;
int min_args;
int max_args;
void(__cdecl* actionFunc)();
int type;
};
enum scriptType_e
{
SCRIPT_NONE = 0,
SCRIPT_OBJECT = 1,
SCRIPT_STRING = 2,
SCRIPT_ISTRING = 3,
SCRIPT_VECTOR = 4,
SCRIPT_FLOAT = 5,
SCRIPT_INTEGER = 6,
SCRIPT_CODEPOS = 7,
SCRIPT_END = 8,
SCRIPT_FUNCTION = 9,
SCRIPT_THREAD = 13,
SCRIPT_NOTIFY_THREAD = 14,
SCRIPT_TIME_THREAD = 15,
SCRIPT_STRUCT = 17,
SCRIPT_ENTITY = 19,
SCRIPT_ARRAY = 20,
SCRIPT_FREE = 0x17,
};
struct VariableStackBuffer
{
const char* pos;
unsigned __int16 size;
unsigned __int16 bufLen;
unsigned __int16 localId;
char time;
char buf[1];
};
union VariableUnion
{
int intValue;
unsigned int uintValue;
float floatValue;
unsigned int stringValue;
const float* vectorValue;
const char* codePosValue;
unsigned int pointerValue;
VariableStackBuffer* stackValue;
unsigned int entityOffset;
};
struct VariableValue
{
VariableUnion u;
int type;
};
struct function_stack_t
{
char* pos;
VariableValue* top;
unsigned int localId;
unsigned int localVarCount;
VariableValue* startTop;
};
struct function_frame_t
{
function_stack_t fs;
};
struct scrVmPub_t
{
unsigned int* localVars;
VariableValue* maxstack;
int function_count;
function_frame_t* function_frame;
VariableValue* top;
bool abort_on_error;
bool terminal_error;
bool block_execution;
unsigned int inparamcount;
unsigned int outparamcount;
function_frame_t function_frame_start[32];
VariableValue stack[2048];
void(__cdecl* notifyListeners[1])(unsigned int, unsigned int);
};
static_assert(offsetof(scrVmPub_t, top) == 16);
static_assert(offsetof(scrVmPub_t, inparamcount) == 24);
struct scrVarPub_t
{
const char* fieldBuffer;
unsigned __int16 canonicalStrCount;
bool developer;
bool developer_script;
bool evaluate;
const char* error_message;
int error_index;
unsigned int time;
unsigned int timeArrayId;
unsigned int pauseArrayId;
unsigned int levelId;
unsigned int gameId;
unsigned int animId;
unsigned int freeEntList;
unsigned int tempVariable;
bool bInited;
unsigned __int16 savecount;
unsigned int checksum;
unsigned int entId;
unsigned int entFieldName;
void* programHunkUser;
const char* programBuffer;
const char* endScriptBuffer;
unsigned __int16* saveIdMap;
unsigned __int16* saveIdMapRev;
unsigned int numScriptThreads;
unsigned int numScriptValues;
unsigned int numScriptObjects;
const char* varUsagePos;
int ext_threadcount;
int totalObjectRefCount;
volatile int totalVectorRefCount;
};
union Variable_u
{
unsigned int prev;
unsigned int prevSibling;
};
struct Variable
{
unsigned int id;
Variable_u u;
};
union ObjectInfo_u
{
unsigned __int16 entnum;
unsigned __int16 size;
unsigned int nextEntId;
unsigned int self;
};
struct ObjectInfo
{
unsigned __int16 refCount;
ObjectInfo_u u;
};
union VariableValueInternal_u
{
unsigned int next;
VariableUnion u;
ObjectInfo o;
};
union VariableValueInternal_w
{
unsigned int status;
unsigned int type;
unsigned int name;
unsigned int classnum;
unsigned int notifyName;
unsigned int waitTime;
unsigned int parentLocalId;
};
union VariableValueInternal_v
{
unsigned int next;
unsigned int index;
};
enum fsMode_t
{
FS_READ = 0x0,
FS_WRITE = 0x1,
FS_APPEND = 0x2,
FS_APPEND_SYNC = 0x3,
};
enum errorParm_t
{
ERR_FATAL = 0x0,
ERR_DROP = 0x1,
ERR_SERVERDISCONNECT = 0x2,
ERR_DISCONNECT = 0x3,
ERR_SCRIPT = 0x4,
ERR_SCRIPT_DROP = 0x5,
ERR_LOCALIZATION = 0x6,
};
enum conChannel_t
{
CON_CHANNEL_DONT_FILTER = 0x0,
CON_CHANNEL_ERROR = 0x1,
CON_CHANNEL_GAMENOTIFY = 0x2,
CON_CHANNEL_BOLDGAME = 0x3,
CON_CHANNEL_SUBTITLE = 0x4,
CON_CHANNEL_OBITUARY = 0x5,
CON_CHANNEL_LOGFILEONLY = 0x6,
CON_CHANNEL_CONSOLEONLY = 0x7,
CON_CHANNEL_GFX = 0x8,
CON_CHANNEL_SOUND = 0x9,
CON_CHANNEL_FILES = 0xA,
CON_CHANNEL_DEVGUI = 0xB,
CON_CHANNEL_PROFILE = 0xC,
CON_CHANNEL_UI = 0xD,
CON_CHANNEL_CLIENT = 0xE,
CON_CHANNEL_SERVER = 0xF,
CON_CHANNEL_SYSTEM = 0x10,
CON_CHANNEL_PLAYERWEAP = 0x11,
CON_CHANNEL_AI = 0x12,
CON_CHANNEL_ANIM = 0x13,
CON_CHANNEL_PHYS = 0x14,
CON_CHANNEL_FX = 0x15,
CON_CHANNEL_LEADERBOARDS = 0x16,
CON_CHANNEL_LIVE = 0x17,
CON_CHANNEL_PARSERSCRIPT = 0x18,
CON_CHANNEL_SCRIPT = 0x19,
CON_CHANNEL_SPAWNSYSTEM = 0x1A,
CON_CHANNEL_COOPINFO = 0x1B,
CON_CHANNEL_SERVERDEMO = 0x1C,
CON_CHANNEL_DDL = 0x1D,
CON_CHANNEL_NETWORK = 0x1E,
CON_CHANNEL_SCHEDULER = 0x1F,
CON_FIRST_DEBUG_CHANNEL = 0x1F,
CON_CHANNEL_TASK = 0x20,
CON_CHANNEL_SPU = 0x21,
CON_BUILTIN_CHANNEL_COUNT = 0x22,
};
namespace sp
{
struct ObjectInfo
{
unsigned __int16 refCount;
unsigned __int16 size;
};
union VariableValueInternal_u
{
VariableUnion u;
ObjectInfo o;
};
struct VariableValueInternal
{
unsigned int id;
VariableValueInternal_u u;
VariableValueInternal_w w;
char __pad1[2];
unsigned __int16 nextSibling;
};
static_assert(sizeof(VariableValueInternal) == 16);
static_assert(offsetof(VariableValueInternal, nextSibling) == 14);
static_assert(offsetof(VariableValueInternal, u) == 4);
static_assert(offsetof(VariableValueInternal, w) == 8);
}
namespace mp
{
struct VariableValueInternal
{
Variable hash;
VariableValueInternal_u u;
VariableValueInternal_w w;
VariableValueInternal_v v;
unsigned int nextSibling;
};
}
union scrVarGlob_t
{
sp::VariableValueInternal* variableList_sp;
mp::VariableValueInternal* variableList_mp;
};
struct scr_classStruct_t
{
unsigned __int16 id;
unsigned __int16 entArrayId;
char charId;
const char* name;
};
enum gclientFlag
{
NOCLIP = 1 << 0,
UFO = 1 << 1,
};
enum entityFlag
{
FL_GODMODE = 1 << 0,
FL_DEMI_GODMODE = 1 << 1,
FL_NOTARGET = 1 << 2,
FL_SUPPORTS_LINKTO = 1 << 12,
}; // TODO: Finish
struct gclient_s
{
char __pad0[0x18];
int eflags;
char __pad1[0x5668];
int flags;
};
struct gentity_s
{
int number;
char __pad0[0x150];
gclient_s* client; // 340
char __pad1[0x30];
int flags; // 392
char __pad2[0x190];
};
static_assert(sizeof(gentity_s) == 0x31C);
enum clientState_t
{
CS_FREE,
CS_ZOMBIE,
CS_RECONNECTING,
CS_CONNECTED,
CS_CLIENTLOADING,
CS_ACTIVE,
};
enum netsrc_t
{
NS_CLIENT1 = 0x0,
NS_CLIENT2 = 0x1,
NS_CLIENT3 = 0x2,
NS_CLIENT4 = 0x3,
NS_SERVER = 0x4,
NS_PACKET = 0x5,
NS_NULL = -1,
};
enum netadrtype_t
{
NA_BOT = 0x0,
NA_BAD = 0x1,
NA_LOOPBACK = 0x2,
NA_BROADCAST = 0x3,
NA_IP = 0x4,
};
struct netadr_t
{
union
{
unsigned char ip[4];
unsigned int inaddr;
};
unsigned __int16 port;
netadrtype_t type;
netsrc_t localNetID;
unsigned __int16 serverID;
};
static_assert(sizeof(netadr_t) == 0x14);
struct netProfileInfo_t
{
unsigned char __pad0[0x5E0];
};
struct netchan_t
{
int outgoingSequence;
netsrc_t sock;
int dropped;
int incomingSequence;
netadr_t remoteAddress;
int qport;
int fragmentSequence;
int fragmentLength;
unsigned char* fragmentBuffer;
int fragmentBufferSize;
int unsentFragments;
int unsentOnLoan;
int unsentFragmentStart;
int unsentLength;
unsigned char* unsentBuffer;
int unsentBufferSize;
int reliable_fragments;
unsigned char fragment_send_count[128];
unsigned int fragment_ack[4];
int lowest_send_count;
netProfileInfo_t prof;
};
static_assert(sizeof(netchan_t) == 0x6C8);
struct PredictedVehicleDef
{
bool fullPhysics;
vec3_t origin;
vec3_t angles;
vec3_t tVel;
vec3_t aVel;
int serverTime;
};
static_assert(sizeof(PredictedVehicleDef) == 0x38);
struct clientHeader_t
{
clientState_t state;
int sendAsActive;
int deltaMessage;
int rateDelayed;
int hasAckedBaselineData;
int hugeSnapshotSent;
netchan_t netchan;
vec3_t predictedOrigin;
int predictedOriginServerTime;
int migrationState;
PredictedVehicleDef predictedVehicle;
};
static_assert(sizeof(clientHeader_t) == 0x72C);
struct client_s
{
clientHeader_t header;
const char* dropReason;
char userinfo[1024];
unsigned char __pad0[0x3F75C];
int bIsTestClient;
unsigned char __pad1[0xDEF0];
};
static_assert(sizeof(client_s) == 0x4E180);
struct cmd_function_t
{
cmd_function_t* next;
const char* name;
const char* autoCompleteDir;
const char* autoCompleteExt;
void(__cdecl* function)();
int flags;
};
struct CmdArgs
{
int nesting;
int localClientNum[8];
int controllerIndex[8];
void* itemDef[8];
int argshift[8];
int argc[8];
const char** argv[8];
char textPool[8192];
const char* argvPool[512];
int usedTextPool[8];
int totalUsedArgvPool;
int totalUsedTextPool;
};
enum dvarType_t
{
DVAR_TYPE_INVALID = 0x0,
DVAR_TYPE_BOOL = 0x1,
DVAR_TYPE_FLOAT = 0x2,
DVAR_TYPE_FLOAT_2 = 0x3,
DVAR_TYPE_FLOAT_3 = 0x4,
DVAR_TYPE_FLOAT_4 = 0x5,
DVAR_TYPE_INT = 0x6,
DVAR_TYPE_ENUM = 0x7,
DVAR_TYPE_STRING = 0x8,
DVAR_TYPE_COLOR = 0x9,
DVAR_TYPE_INT64 = 0xA,
DVAR_TYPE_LINEAR_COLOR_RGB = 0xB,
DVAR_TYPE_COLOR_XYZ = 0xC,
DVAR_TYPE_COUNT = 0xD,
};
union DvarValue
{
bool enabled;
int integer;
unsigned int unsignedInt;
__int64 integer64;
unsigned __int64 unsignedInt64;
float value;
vec4_t vector;
const char* string;
char color[4];
};
struct $A37BA207B3DDD6345C554D4661813EDD
{
int stringCount;
const char* const* strings;
};
struct $9CA192F9DB66A3CB7E01DE78A0DEA53D
{
int min;
int max;
};
struct $251C2428A496074035CACA7AAF3D55BD
{
float min;
float max;
};
struct DvarLimits_integer64
{
__int64 min;
__int64 max;
};
union DvarLimits
{
$A37BA207B3DDD6345C554D4661813EDD enumeration;
$9CA192F9DB66A3CB7E01DE78A0DEA53D integer;
$251C2428A496074035CACA7AAF3D55BD value;
$251C2428A496074035CACA7AAF3D55BD vector;
DvarLimits_integer64 integer64;
};
struct dvar_t
{
const char* name;
const char* description;
int hash;
unsigned int flags;
dvarType_t type;
bool modified;
bool loadedFromSaveGame;
DvarValue current;
DvarValue latched;
DvarValue saved;
DvarValue reset;
DvarLimits domain;
dvar_t* hashNext;
};
static_assert(offsetof(dvar_t, hashNext) == 104);
static_assert(offsetof(dvar_t, current) == 24);
struct GSC_OBJ
{
char magic[8];
unsigned int source_crc;
unsigned int include_offset;
unsigned int animtree_offset;
unsigned int cseg_offset;
unsigned int stringtablefixup_offset;
unsigned int exports_offset;
unsigned int imports_offset;
unsigned int fixup_offset;
unsigned int profile_offset;
unsigned int cseg_size;
unsigned __int16 name;
unsigned __int16 stringtablefixup_count;
unsigned __int16 exports_count;
unsigned __int16 imports_count;
unsigned __int16 fixup_count;
unsigned __int16 profile_count;
char include_count;
char animtree_count;
char flags;
};
struct GSC_EXPORT_ITEM
{
unsigned int checksum;
unsigned int address;
unsigned __int16 name;
char param_count;
char flags;
};
// gsc-tool
enum class opcode : std::uint8_t
{
OP_End = 0x0,
OP_Return = 0x1,
OP_GetUndefined = 0x2,
OP_GetZero = 0x3,
OP_GetByte = 0x4,
OP_GetNegByte = 0x5,
OP_GetUnsignedShort = 0x6,
OP_GetNegUnsignedShort = 0x7,
OP_GetInteger = 0x8,
OP_GetFloat = 0x9,
OP_GetString = 0xA,
OP_GetIString = 0xB,
OP_GetVector = 0xC,
OP_GetLevelObject = 0xD,
OP_GetAnimObject = 0xE,
OP_GetSelf = 0xF,
OP_GetLevel = 0x10,
OP_GetGame = 0x11,
OP_GetAnim = 0x12,
OP_GetAnimation = 0x13,
OP_GetGameRef = 0x14,
OP_GetFunction = 0x15,
OP_CreateLocalVariable = 0x16,
OP_SafeCreateLocalVariables = 0x17,
OP_RemoveLocalVariables = 0x18,
OP_EvalLocalVariableCached = 0x19,
OP_EvalArray = 0x1A,
OP_EvalLocalArrayRefCached = 0x1B,
OP_EvalArrayRef = 0x1C,
OP_ClearArray = 0x1D,
OP_EmptyArray = 0x1E,
OP_GetSelfObject = 0x1F,
OP_EvalFieldVariable = 0x20,
OP_EvalFieldVariableRef = 0x21,
OP_ClearFieldVariable = 0x22,
OP_SafeSetVariableFieldCached = 0x23,
OP_SafeSetWaittillVariableFieldCached = 0x24,
OP_ClearParams = 0x25,
OP_CheckClearParams = 0x26,
OP_EvalLocalVariableRefCached = 0x27,
OP_SetVariableField = 0x28,
OP_CallBuiltin = 0x29,
OP_CallBuiltinMethod = 0x2A,
OP_Wait = 0x2B,
OP_WaitTillFrameEnd = 0x2C,
OP_PreScriptCall = 0x2D,
OP_ScriptFunctionCall = 0x2E,
OP_ScriptFunctionCallPointer = 0x2F,
OP_ScriptMethodCall = 0x30,
OP_ScriptMethodCallPointer = 0x31,
OP_ScriptThreadCall = 0x32,
OP_ScriptThreadCallPointer = 0x33,
OP_ScriptMethodThreadCall = 0x34,
OP_ScriptMethodThreadCallPointer = 0x35,
OP_DecTop = 0x36,
OP_CastFieldObject = 0x37,
OP_CastBool = 0x38,
OP_BoolNot = 0x39,
OP_BoolComplement = 0x3A,
OP_JumpOnFalse = 0x3B,
OP_JumpOnTrue = 0x3C,
OP_JumpOnFalseExpr = 0x3D,
OP_JumpOnTrueExpr = 0x3E,
OP_Jump = 0x3F,
OP_JumpBack = 0x40,
OP_Inc = 0x41,
OP_Dec = 0x42,
OP_Bit_Or = 0x43,
OP_Bit_Xor = 0x44,
OP_Bit_And = 0x45,
OP_Equal = 0x46,
OP_NotEqual = 0x47,
OP_LessThan = 0x48,
OP_GreaterThan = 0x49,
OP_LessThanOrEqualTo = 0x4A,
OP_GreaterThanOrEqualTo = 0x4B,
OP_ShiftLeft = 0x4C,
OP_ShiftRight = 0x4D,
OP_Plus = 0x4E,
OP_Minus = 0x4F,
OP_Multiply = 0x50,
OP_Divide = 0x51,
OP_Modulus = 0x52,
OP_SizeOf = 0x53,
OP_WaitTillMatch = 0x54,
OP_WaitTill = 0x55,
OP_Notify = 0x56,
OP_EndOn = 0x57,
OP_VoidCodePos = 0x58,
OP_Switch = 0x59,
OP_EndSwitch = 0x5A,
OP_Vector = 0x5B,
OP_GetHash = 0x5C,
OP_RealWait = 0x5D,
OP_VectorConstant = 0x5E,
OP_IsDefined = 0x5F,
OP_VectorScale = 0x60,
OP_AnglesToUp = 0x61,
OP_AnglesToRight = 0x62,
OP_AnglesToForward = 0x63,
OP_AngleClamp180 = 0x64,
OP_VectorToAngles = 0x65,
OP_Abs = 0x66,
OP_GetTime = 0x67,
OP_GetDvar = 0x68,
OP_GetDvarInt = 0x69,
OP_GetDvarFloat = 0x6A,
OP_GetDvarVector = 0x6B,
OP_GetDvarColorRed = 0x6C,
OP_GetDvarColorGreen = 0x6D,
OP_GetDvarColorBlue = 0x6E,
OP_GetDvarColorAlpha = 0x6F,
OP_FirstArrayKey = 0x70,
OP_NextArrayKey = 0x71,
OP_ProfileStart = 0x72,
OP_ProfileStop = 0x73,
OP_SafeDecTop = 0x74,
OP_Nop = 0x75,
OP_Abort = 0x76,
OP_Object = 0x77,
OP_ThreadObject = 0x78,
OP_EvalLocalVariable = 0x79,
OP_EvalLocalVariableRef = 0x7A,
OP_DevblockBegin = 0x7B,
OP_DevblockEnd = 0x7C,
OP_Breakpoint = 0x7D,
OP_Count = 0x7E,
};
enum XAssetType
{
ASSET_TYPE_XMODELPIECES = 0x0,
ASSET_TYPE_PHYSPRESET = 0x1,
ASSET_TYPE_PHYSCONSTRAINTS = 0x2,
ASSET_TYPE_DESTRUCTIBLEDEF = 0x3,
ASSET_TYPE_XANIMPARTS = 0x4,
ASSET_TYPE_XMODEL = 0x5,
ASSET_TYPE_MATERIAL = 0x6,
ASSET_TYPE_TECHNIQUE_SET = 0x7,
ASSET_TYPE_IMAGE = 0x8,
ASSET_TYPE_SOUND = 0x9,
ASSET_TYPE_SOUND_PATCH = 0xA,
ASSET_TYPE_CLIPMAP = 0xB,
ASSET_TYPE_CLIPMAP_PVS = 0xC,
ASSET_TYPE_COMWORLD = 0xD,
ASSET_TYPE_GAMEWORLD_SP = 0xE,
ASSET_TYPE_GAMEWORLD_MP = 0xF,
ASSET_TYPE_MAP_ENTS = 0x10,
ASSET_TYPE_GFXWORLD = 0x11,
ASSET_TYPE_LIGHT_DEF = 0x12,
ASSET_TYPE_UI_MAP = 0x13,
ASSET_TYPE_FONT = 0x14,
ASSET_TYPE_FONTICON = 0x15,
ASSET_TYPE_MENULIST = 0x16,
ASSET_TYPE_MENU = 0x17,
ASSET_TYPE_LOCALIZE_ENTRY = 0x18,
ASSET_TYPE_WEAPON = 0x19,
ASSET_TYPE_WEAPONDEF = 0x1A,
ASSET_TYPE_WEAPON_VARIANT = 0x1B,
ASSET_TYPE_WEAPON_FULL = 0x1C,
ASSET_TYPE_ATTACHMENT = 0x1D,
ASSET_TYPE_ATTACHMENT_UNIQUE = 0x1E,
ASSET_TYPE_WEAPON_CAMO = 0x1F,
ASSET_TYPE_SNDDRIVER_GLOBALS = 0x20,
ASSET_TYPE_FX = 0x21,
ASSET_TYPE_IMPACT_FX = 0x22,
ASSET_TYPE_AITYPE = 0x23,
ASSET_TYPE_MPTYPE = 0x24,
ASSET_TYPE_MPBODY = 0x25,
ASSET_TYPE_MPHEAD = 0x26,
ASSET_TYPE_CHARACTER = 0x27,
ASSET_TYPE_XMODELALIAS = 0x28,
ASSET_TYPE_RAWFILE = 0x29,
ASSET_TYPE_STRINGTABLE = 0x2A,
ASSET_TYPE_LEADERBOARD = 0x2B,
ASSET_TYPE_XGLOBALS = 0x2C,
ASSET_TYPE_DDL = 0x2D,
ASSET_TYPE_GLASSES = 0x2E,
ASSET_TYPE_EMBLEMSET = 0x2F,
ASSET_TYPE_SCRIPTPARSETREE = 0x30,
ASSET_TYPE_KEYVALUEPAIRS = 0x31,
ASSET_TYPE_VEHICLEDEF = 0x32,
ASSET_TYPE_MEMORYBLOCK = 0x33,
ASSET_TYPE_ADDON_MAP_ENTS = 0x34,
ASSET_TYPE_TRACER = 0x35,
ASSET_TYPE_SKINNEDVERTS = 0x36,
ASSET_TYPE_QDB = 0x37,
ASSET_TYPE_SLUG = 0x38,
ASSET_TYPE_FOOTSTEP_TABLE = 0x39,
ASSET_TYPE_FOOTSTEPFX_TABLE = 0x3A,
ASSET_TYPE_ZBARRIER = 0x3B,
ASSET_TYPE_COUNT = 0x3C,
ASSET_TYPE_STRING = 0x3C,
ASSET_TYPE_ASSETLIST = 0x3D,
ASSET_TYPE_REPORT = 0x3E,
ASSET_TYPE_DEPEND = 0x3F,
ASSET_TYPE_FULL_COUNT = 0x40,
};
struct ScriptParseTree
{
const char* name;
int len;
GSC_OBJ* obj;
};
struct objFileInfo_t
{
GSC_OBJ* activeVersion;
char __pad[0x24];
};
union XAssetHeader
{
ScriptParseTree* scriptParseTree;
};
union sval_u
{
unsigned __int8 type;
unsigned int stringValue;
unsigned int idValue;
float floatValue;
int intValue;
sval_u* node;
unsigned int sourcePosValue;
const char* codePosValue;
const char* debugString;
void* block;
};
}
@@ -0,0 +1,148 @@
#pragma once
#define WEAK __declspec(selectany)
namespace game
{
// Functions
WEAK symbol<int(const char* str)> BG_StringHashValue{0x0, 0x0};
WEAK symbol<void(const char* fmt, ...)> G_LogPrintf{0x69EA30, 0x5CD250};
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_InsertText{0x0, 0x0};
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x49B930, 0x56EF70};
WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x619D00, 0x50B470};
WEAK symbol<void(const char* cmdName, void(), cmd_function_t* allocedCmd)> Cmd_AddCommandInternal{0x661400, 0x6AD580};
WEAK symbol<const char*(int index)> Cmd_Argv{0x0, 0x0};
WEAK symbol<void(const char* cmdName)> Cmd_RemoveCommand{0x5F1A90, 0x527EA0};
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};
WEAK symbol<void(int channel, const char* fmt, ...)> Com_PrintWarning{0x51CE50, 0x54B6A0};
WEAK symbol<const dvar_t*(const char*)> Dvar_FindVar{0x5AE810, 0x512F70};
WEAK symbol<int(const dvar_t*)> Dvar_GetInt{0x0, 0x0};
WEAK symbol<dvar_t*(const char* dvarName, int value, int min, int max,
unsigned int flags, const char* description)> Dvar_RegisterInt{0x0, 0x0};
WEAK symbol<char*(int localClientNum, int bit)> Dvar_InfoString{0x613060, 0x54A8B0};
WEAK symbol<XAssetHeader(XAssetType type, const char* name, bool errorIfMissing, int waitTime)> DB_FindXAssetHeader{0x0, 0x0};
WEAK symbol<char*(const char*)> I_CleanStr{0x0, 0x0};
WEAK symbol<void(char* dest, const char* src, int destsize)> I_strncpyz{0x5D4D60, 0x5A7140};
WEAK symbol<void*(const char** pName, int* min_args, int* max_args)> Player_GetMethod{0x0, 0x0};
WEAK symbol<void*(const char** pName, int* type, int* min_args, int* max_args)> Scr_GetCommonFunction{0x0, 0x0};
WEAK symbol<void*(const char** pName, int* type, int* min_args, int* max_args)> Scr_GetMethod{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, gentity_s* ent)> Scr_AddEntity{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, float value)> Scr_AddFloat{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, const char* value)> Scr_AddString{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, float* value)> Scr_AddVector{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, unsigned int id)> Scr_AddObject{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst)> Scr_ClearOutParams{0x654D10, 0x588680};
WEAK symbol<unsigned int(scriptInstance_t inst)> AllocObject{0x603400, 0x6970B0};
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int id)> AllocThread{0x69E140, 0x43CA60};
WEAK symbol<void(scriptInstance_t inst, unsigned int id)> RemoveRefToObject{0x5517B0, 0x698FA0};
WEAK symbol<void(scriptInstance_t inst, const float* vectorValue)> RemoveRefToVector{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, const int type, VariableUnion value)> AddRefToValue_{0x53FD50, 0x6706B0};
namespace mp
{
WEAK symbol<void(scriptInstance_t inst, const int type, VariableUnion value)> RemoveRefToValue{0x0, 0x4249C0};
}
namespace sp
{
WEAK symbol<void(scriptInstance_t inst, VariableValue* value)> RemoveRefToValue{0x556830, 0x0};
}
WEAK symbol<void(scriptInstance_t inst, unsigned int parentId, unsigned int index)> RemoveVariableValue{0x0, 0x0};
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int parentId, unsigned int id)> FindVariable{0x476720, 0x5DF7E0};
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int parentId, unsigned int id)> FindArrayVariable{0x62F8E0, 0x509230};
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int parentId, unsigned int id)> GetVariable{0x0, 0x0};
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int parentId, unsigned int name)> GetNewVariable{0x674820, 0x5B3750};
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int parentId, unsigned int unsignedValue)> GetNewArrayVariable{0x689C60, 0x468BC0};
WEAK symbol<void(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x0, 0x0};
WEAK symbol<VariableValue(scriptInstance_t inst, unsigned int classnum, int entnum, int clientNum, int offset)> GetEntityFieldValue{0x0, 0x0};
WEAK symbol<void(gentity_s* ent, unsigned int stringValue, unsigned int paramcount)> Scr_Notify{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, int clientNum, int entnum,
unsigned int classnum, unsigned int stringValue, unsigned int paramcount)> Scr_NotifyNum_Internal{0x41D270, 0x4162E0};
WEAK symbol<void(int entnum, unsigned int classnum, unsigned int stringValue, unsigned int paramcount)> Scr_NotifyNum{0x0, 0x0};
WEAK symbol<unsigned int(const char* str, unsigned int user, scriptInstance_t inst)> SL_GetString{0x463130, 0x4B1770};
WEAK symbol<const char*(unsigned int stringValue, scriptInstance_t inst)> SL_ConvertToString{0x687530, 0x624C70};
WEAK symbol<unsigned int(scriptInstance_t inst, const char* str)> SL_GetCanonicalString{0x4FB120, 0x68D860};
WEAK symbol<int(scriptInstance_t inst)> Scr_GetNumParam{0x0, 0x0};
WEAK symbol<gentity_s*(scriptInstance_t inst, int index)> Scr_GetEntity{0x0, 0x0};
WEAK symbol<float(scriptInstance_t inst, int index)> Scr_GetFloat{0x0, 0x0};
WEAK symbol<int(scriptInstance_t inst, int index)> Scr_GetInt{0x0, 0x0};
WEAK symbol<const char*(scriptInstance_t inst, int index)> Scr_GetString{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, int index, float* out)> Scr_GetVector{0x0, 0x0};
WEAK symbol<const float*(scriptInstance_t inst, const float* v)> Scr_AllocVector{0x41B400, 0x4C4440};
WEAK symbol<int(scriptInstance_t inst, const char* filename,
const char* name, unsigned int* checksum, bool errorIfMissing)> Scr_GetFunctionHandle{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, unsigned int classnum, char const* name, unsigned int offset)> Scr_AddClassField{0x0, 0x0};
WEAK symbol<unsigned int(scriptInstance_t inst, int entnum, unsigned int classnum, int clientNum)> Scr_GetEntityId{0x4B7050, 0x6A07D0};
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int localId)> GetStartLocalId{0x0, 0x0};
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int localId)> Scr_TerminateRunningThread{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, const char* error, bool force_terminal)> Scr_Error{0x4B5940, 0x6245E0};
WEAK symbol<void(scriptInstance_t inst, unsigned int paramIndex, const char* error)> Scr_ParamError{0x0, 0x0};
WEAK symbol<void(scriptInstance_t inst, const char* error)> Scr_ObjectError{0x0, 0x0};
WEAK symbol<scr_entref_t*(scr_entref_t* refref, scriptInstance_t inst, unsigned int entId)> Scr_GetEntityIdRef{0x433B60, 0x6A53C0};
WEAK symbol<unsigned int(scriptInstance_t inst)> Scr_AllocArray{0x438800, 0x5B8400};
WEAK symbol<gentity_s*(scr_entref_t entref)> GetPlayerEntity{0x0, 0x0};
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};
WEAK symbol<int(const char* qpath, int* f, fsMode_t mode)> FS_FOpenFileByMode{0x4DD530, 0x40C790};
WEAK symbol<void(int h)> FS_FCloseFile{0x46CAA0, 0x533020};
WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0x96B980, 0x9D05C4};
WEAK symbol<int(jmp_buf* Buf, int a2)> _setjmp{0x969EAC, 0x9CED5C};
// Variables
WEAK symbol<int> g_script_error_level{0x32D1E18, 0x3DD4A18};
WEAK symbol<jmp_buf> g_script_error{0x32D0D98, 0x3DD3998};
WEAK symbol<scrVmPub_t> scr_VmPub{0x32C8738, 0x3DCB338};
WEAK symbol<scrVarGlob_t> scr_VarGlob{0x32C8580, 0x3DCB180};
WEAK symbol<scrVarPub_t> scr_VarPub{0x32C8680, 0x3DCB280};
WEAK symbol<int> scr_starttime{0x0, 0x0};
WEAK symbol<function_stack_t> fs{0x0, 0x0};
WEAK symbol<unsigned short> sv_configstrings{0x0, 0x0};
WEAK symbol<scr_classStruct_t*> g_classMap{0x0, 0x0};
WEAK symbol<gentity_s> g_entities{0x0, 0x0};
WEAK symbol<unsigned int> levelEntityId{0x32C86A0, 0x3DCB2A0};
WEAK symbol<client_s> svs_clients{0x0, 0x0};
WEAK symbol<CmdArgs> sv_cmd_args{0x243D208, 0x355BD88};
WEAK symbol<int> logFile{0x1C0417C, 0x3443838};
}
@@ -0,0 +1,35 @@
#pragma once
#include "../plugin.hpp"
class component_interface
{
public:
virtual ~component_interface()
{
}
virtual void on_startup([[maybe_unused]] plugin::plugin* plugin)
{
}
virtual void on_dvar_init([[maybe_unused]] plugin::plugin* plugin)
{
}
virtual void on_after_dvar_init([[maybe_unused]] plugin::plugin* plugin)
{
}
virtual void on_game_init([[maybe_unused]] plugin::plugin* plugin)
{
}
virtual void on_shutdown([[maybe_unused]] plugin::plugin* plugin)
{
}
virtual bool is_supported()
{
return true;
}
};
@@ -0,0 +1,72 @@
#include <stdinc.hpp>
#include "component_loader.hpp"
void component_loader::register_component(const std::string& name, std::unique_ptr<component_interface>&& component_)
{
get_components().push_back(std::make_pair(name, std::move(component_)));
}
#define ON_CALLBACK(__name__) \
void component_loader::__name__() \
{ \
static auto handled = false; \
if (handled) \
{ \
return; \
} \
\
handled = true; \
\
for (const auto& component_ : get_components()) \
{ \
try \
{ \
component_.second->__name__(plugin::get()); \
} \
catch (const std::exception& e) \
{ \
printf("error executing component \"%s\" callback \"%s\": %s\n", component_.first.data(), #__name__, e.what()); \
} \
} \
} \
ON_CALLBACK(on_startup);
ON_CALLBACK(on_dvar_init);
ON_CALLBACK(on_after_dvar_init);
ON_CALLBACK(on_shutdown);
void component_loader::clean()
{
auto& components = get_components();
for (auto i = components.begin(); i != components.end();)
{
if (!(*i).second->is_supported())
{
(*i).second->on_shutdown(plugin::get());
i = components.erase(i);
}
else
{
++i;
}
}
}
void component_loader::trigger_premature_shutdown()
{
throw premature_shutdown_trigger();
}
std::vector<std::pair<std::string, std::unique_ptr<component_interface>>>& component_loader::get_components()
{
using component_vector = std::vector<std::pair<std::string, std::unique_ptr<component_interface>>>;
using component_vector_container = std::unique_ptr<component_vector, std::function<void(component_vector*)>>;
static component_vector_container components(new component_vector, [](component_vector* component_vector)
{
on_shutdown();
delete component_vector;
});
return *components;
}
@@ -0,0 +1,59 @@
#pragma once
#include "component_interface.hpp"
class component_loader final
{
public:
class premature_shutdown_trigger final : public std::exception
{
[[nodiscard]] const char* what() const noexcept override
{
return "Premature shutdown requested";
}
};
template <typename T>
class installer final
{
static_assert(std::is_base_of<component_interface, T>::value, "component has invalid base class");
public:
installer(const std::string& name)
{
register_component(name, std::make_unique<T>());
}
};
template <typename T>
static T* get()
{
for (const auto& component_ : get_components())
{
if (typeid(*component_.get()) == typeid(T))
{
return reinterpret_cast<T*>(component_.get());
}
}
return nullptr;
}
static void register_component(const std::string& name, std::unique_ptr<component_interface>&& component);
static void on_startup();
static void on_dvar_init();
static void on_after_dvar_init();
static void on_shutdown();
static void clean();
static void trigger_premature_shutdown();
private:
static std::vector<std::pair<std::string, std::unique_ptr<component_interface>>>& get_components();
};
#define REGISTER_COMPONENT(name) \
namespace \
{ \
static component_loader::installer<name> __component(#name); \
}
@@ -0,0 +1,75 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "plugin.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace plugin
{
namespace
{
void printf_stub(const char* fmt, ...)
{
char buffer[0x2000] = {};
va_list ap;
va_start(ap, fmt);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
va_end(ap);
const auto trimmed = utils::string::trim(buffer);
get()->get_interface()->logging()->info(trimmed);
}
}
std::uint32_t plugin::plugin_version()
{
return 1;
}
const char* plugin::plugin_name()
{
return "tws-t5-gsc-utils";
}
bool plugin::is_game_supported([[maybe_unused]] plutonium::sdk::game game)
{
return game == plutonium::sdk::game::t5;
}
void plugin::on_startup(plutonium::sdk::iinterface* interface_ptr, plutonium::sdk::game game)
{
this->interface_ = interface_ptr;
this->game_ = game;
utils::hook::jump(reinterpret_cast<uintptr_t>(&printf), printf_stub);
component_loader::on_startup();
interface_ptr->callbacks()->on_dvar_init(&component_loader::on_dvar_init);
interface_ptr->callbacks()->on_after_dvar_init(&component_loader::on_after_dvar_init);
}
void plugin::on_shutdown()
{
component_loader::on_shutdown();
}
plutonium::sdk::iinterface* plugin::get_interface()
{
return this->interface_;
}
plutonium::sdk::game plugin::get_game()
{
return this->game_;
}
plugin* get()
{
static plugin instance;
return &instance;
}
}
@@ -0,0 +1,30 @@
#pragma once
#include <plutonium_sdk.hpp>
namespace plugin
{
class plugin : public plutonium::sdk::plugin
{
public:
~plugin() = default;
std::uint32_t plugin_version() override;
const char* plugin_name() override;
bool is_game_supported([[maybe_unused]] plutonium::sdk::game game) override;
void on_startup(plutonium::sdk::iinterface* interface_ptr, plutonium::sdk::game game) override;
void on_shutdown() override;
plutonium::sdk::iinterface* get_interface();
plutonium::sdk::game get_game();
private:
plutonium::sdk::iinterface* interface_{};
plutonium::sdk::game game_{};
};
plugin* get();
}
@@ -0,0 +1,3 @@
#pragma once
#define LIBMYSQL_DLL 100
@@ -0,0 +1,3 @@
#include "resource.hpp"
LIBMYSQL_DLL RCDATA "../../deps/mysql/lib/libmysql.dll"
@@ -0,0 +1,24 @@
#include <stdinc.hpp>
extern "C"
{
int s_read_arc4random(void*, size_t)
{
return -1;
}
int s_read_getrandom(void*, size_t)
{
return -1;
}
int s_read_urandom(void*, size_t)
{
return -1;
}
int s_read_ltm_rng(void*, size_t)
{
return -1;
}
}
@@ -0,0 +1,60 @@
#pragma once
#pragma warning(disable: 6011)
#pragma warning(disable: 6054)
#pragma warning(disable: 6387)
#pragma warning(disable: 26451)
#pragma warning(disable: 26812)
#pragma warning(disable: 28182)
#define DLL_EXPORT extern "C" __declspec(dllexport)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <cassert>
#include <csetjmp>
#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
#endif
#ifdef min
#undef min
#endif
#include <MinHook.h>
#include <gsl/gsl>
#include <json.hpp>
#include <asmjit/core/jitruntime.h>
#include <asmjit/x86/x86assembler.h>
#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "urlmon.lib" )
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "Crypt32.lib")
#pragma comment(lib, "libmysql.lib")
#pragma comment(lib, "delayimp.lib")
#include "resource.hpp"
using namespace std::literals;
@@ -0,0 +1,76 @@
#include <stdinc.hpp>
#include "binary_resource.hpp"
#include <utility>
#include "nt.hpp"
#include "io.hpp"
namespace utils
{
namespace
{
std::string get_temp_folder()
{
char path[MAX_PATH] = {0};
if (!GetTempPathA(sizeof(path), path))
{
throw std::runtime_error("Unable to get temp path");
}
return path;
}
std::string write_exitisting_temp_file(const std::string& file, const std::string& data,
const bool fatal_if_overwrite_fails)
{
const auto temp = get_temp_folder();
auto file_path = temp + file;
std::string current_data;
if (!io::read_file(file_path, &current_data))
{
if (!io::write_file(file_path, data))
{
throw std::runtime_error("Failed to write file: " + file_path);
}
return file_path;
}
if (current_data == data || io::write_file(file_path, data) || !fatal_if_overwrite_fails)
{
return file_path;
}
throw std::runtime_error(
"Temporary file was already written, but differs. It can't be overwritten as it's still in use: " +
file_path);
}
}
binary_resource::binary_resource(const int id, std::string file)
: filename_(std::move(file))
{
this->resource_ = nt::load_resource(id);
if (this->resource_.empty())
{
throw std::runtime_error("Unable to load resource: " + std::to_string(id));
}
}
std::string binary_resource::get_extracted_file(const bool fatal_if_overwrite_fails)
{
if (this->path_.empty())
{
this->path_ = write_exitisting_temp_file(this->filename_, this->resource_, fatal_if_overwrite_fails);
}
return this->path_;
}
const std::string& binary_resource::get_data() const
{
return this->resource_;
}
}
@@ -0,0 +1,20 @@
#pragma once
#include <string>
namespace utils
{
class binary_resource
{
public:
binary_resource(int id, std::string file);
std::string get_extracted_file(bool fatal_if_overwrite_fails = false);
const std::string& get_data() const;
private:
std::string resource_;
std::string filename_;
std::string path_;
};
}
@@ -0,0 +1,170 @@
#include <stdinc.hpp>
#include "memory.hpp"
#include "compression.hpp"
#include <zlib.h>
#include <zip.h>
#include <gsl/gsl>
#include "io.hpp"
namespace utils::compression
{
namespace zlib
{
namespace
{
class zlib_stream
{
public:
zlib_stream()
{
memset(&stream_, 0, sizeof(stream_));
valid_ = inflateInit(&stream_) == Z_OK;
}
zlib_stream(zlib_stream&&) = delete;
zlib_stream(const zlib_stream&) = delete;
zlib_stream& operator=(zlib_stream&&) = delete;
zlib_stream& operator=(const zlib_stream&) = delete;
~zlib_stream()
{
if (valid_)
{
inflateEnd(&stream_);
}
}
z_stream& get()
{
return stream_; //
}
bool is_valid() const
{
return valid_;
}
private:
bool valid_{false};
z_stream stream_{};
};
}
std::string decompress(const std::string& data)
{
std::string buffer{};
zlib_stream stream_container{};
if (!stream_container.is_valid())
{
return {};
}
int ret{};
size_t offset = 0;
static thread_local uint8_t dest[CHUNK] = {0};
auto& stream = stream_container.get();
do
{
const auto input_size = std::min(sizeof(dest), data.size() - offset);
stream.avail_in = static_cast<uInt>(input_size);
stream.next_in = reinterpret_cast<const Bytef*>(data.data()) + offset;
offset += stream.avail_in;
do
{
stream.avail_out = sizeof(dest);
stream.next_out = dest;
ret = inflate(&stream, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_STREAM_END)
{
return {};
}
buffer.insert(buffer.end(), dest, dest + sizeof(dest) - stream.avail_out);
}
while (stream.avail_out == 0);
}
while (ret != Z_STREAM_END);
return buffer;
}
std::string compress(const std::string& data)
{
std::string result{};
auto length = compressBound(static_cast<uLong>(data.size()));
result.resize(length);
if (compress2(reinterpret_cast<Bytef*>(result.data()), &length,
reinterpret_cast<const Bytef*>(data.data()), static_cast<uLong>(data.size()),
Z_BEST_COMPRESSION) != Z_OK)
{
return {};
}
result.resize(length);
return result;
}
}
namespace zip
{
namespace
{
bool add_file(zipFile& zip_file, const std::string& filename, const std::string& data)
{
const auto zip_64 = data.size() > 0xffffffff ? 1 : 0;
if (ZIP_OK != zipOpenNewFileInZip64(zip_file, filename.data(), nullptr, nullptr, 0, nullptr, 0, nullptr,
Z_DEFLATED, Z_BEST_COMPRESSION, zip_64))
{
return false;
}
const auto _ = gsl::finally([&zip_file]()
{
zipCloseFileInZip(zip_file);
});
return ZIP_OK == zipWriteInFileInZip(zip_file, data.data(), static_cast<unsigned>(data.size()));
}
}
void archive::add(std::string filename, std::string data)
{
this->files_[std::move(filename)] = std::move(data);
}
bool archive::write(const std::string& filename, const std::string& comment)
{
// Hack to create the directory :3
io::write_file(filename, {});
io::remove_file(filename);
auto* zip_file = zipOpen64(filename.data(), 0);
if (!zip_file)
{
return false;
}
const auto _ = gsl::finally([&zip_file, &comment]()
{
zipClose(zip_file, comment.empty() ? nullptr : comment.data());
});
for (const auto& file : this->files_)
{
if (!add_file(zip_file, file.first, file.second))
{
return false;
}
}
return true;
}
}
}
@@ -0,0 +1,28 @@
#pragma once
#include <string>
#include <unordered_map>
#define CHUNK 16384u
namespace utils::compression
{
namespace zlib
{
std::string compress(const std::string& data);
std::string decompress(const std::string& data);
}
namespace zip
{
class archive
{
public:
void add(std::string filename, std::string data);
bool write(const std::string& filename, const std::string& comment = {});
private:
std::unordered_map<std::string, std::string> files_;
};
}
};
@@ -0,0 +1,46 @@
#pragma once
#include <mutex>
namespace utils::concurrency
{
template <typename T, typename MutexType = std::mutex>
class container
{
public:
template <typename R = void, typename F>
R access(F&& accessor) const
{
std::lock_guard<MutexType> _{mutex_};
return accessor(object_);
}
template <typename R = void, typename F>
R access(F&& accessor)
{
std::lock_guard<MutexType> _{mutex_};
return accessor(object_);
}
template <typename R = void, typename F>
R access_with_lock(F&& accessor) const
{
std::unique_lock<MutexType> lock{mutex_};
return accessor(object_, lock);
}
template <typename R = void, typename F>
R access_with_lock(F&& accessor)
{
std::unique_lock<MutexType> lock{mutex_};
return accessor(object_, lock);
}
T& get_raw() { return object_; }
const T& get_raw() const { return object_; }
private:
mutable MutexType mutex_{};
T object_{};
};
}
@@ -0,0 +1,691 @@
#include <stdinc.hpp>
#include "cryptography.hpp"
#include "nt.hpp"
#include <gsl/gsl>
#undef max
using namespace std::string_literals;
/// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf
#pragma warning(push)
#pragma warning(disable: 4996)
namespace utils::cryptography
{
namespace
{
struct __
{
__()
{
ltc_mp = ltm_desc;
register_cipher(&aes_desc);
register_cipher(&des3_desc);
register_prng(&sprng_desc);
register_prng(&fortuna_desc);
register_prng(&yarrow_desc);
register_hash(&sha1_desc);
register_hash(&sha256_desc);
register_hash(&sha512_desc);
}
} ___;
[[maybe_unused]] const char* cs(const uint8_t* data)
{
return reinterpret_cast<const char*>(data);
}
[[maybe_unused]] char* cs(uint8_t* data)
{
return reinterpret_cast<char*>(data);
}
[[maybe_unused]] const uint8_t* cs(const char* data)
{
return reinterpret_cast<const uint8_t*>(data);
}
[[maybe_unused]] uint8_t* cs(char* data)
{
return reinterpret_cast<uint8_t*>(data);
}
[[maybe_unused]] unsigned long ul(const size_t value)
{
return static_cast<unsigned long>(value);
}
class prng
{
public:
prng(const ltc_prng_descriptor& descriptor, const bool autoseed = true)
: state_(std::make_unique<prng_state>())
, descriptor_(descriptor)
{
this->id_ = register_prng(&descriptor);
if (this->id_ == -1)
{
throw std::runtime_error("PRNG "s + this->descriptor_.name + " could not be registered!");
}
if (autoseed)
{
this->auto_seed();
}
else
{
this->descriptor_.start(this->state_.get());
}
}
~prng()
{
this->descriptor_.done(this->state_.get());
}
prng_state* get_state() const
{
this->descriptor_.ready(this->state_.get());
return this->state_.get();
}
int get_id() const
{
return this->id_;
}
void add_entropy(const void* data, const size_t length) const
{
this->descriptor_.add_entropy(static_cast<const uint8_t*>(data), ul(length), this->state_.get());
}
void read(void* data, const size_t length) const
{
this->descriptor_.read(static_cast<unsigned char*>(data), ul(length), this->get_state());
}
private:
int id_;
std::unique_ptr<prng_state> state_;
const ltc_prng_descriptor& descriptor_;
void auto_seed() const
{
rng_make_prng(128, this->id_, this->state_.get(), nullptr);
int i[4]; // uninitialized data
auto* i_ptr = &i;
this->add_entropy(reinterpret_cast<uint8_t*>(&i), sizeof(i));
this->add_entropy(reinterpret_cast<uint8_t*>(&i_ptr), sizeof(i_ptr));
auto t = time(nullptr);
this->add_entropy(reinterpret_cast<uint8_t*>(&t), sizeof(t));
}
};
const prng prng_(fortuna_desc);
}
ecc::key::key()
{
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
}
ecc::key::~key()
{
this->free();
}
ecc::key::key(key&& obj) noexcept
: key()
{
this->operator=(std::move(obj));
}
ecc::key::key(const key& obj)
: key()
{
this->operator=(obj);
}
ecc::key& ecc::key::operator=(key&& obj) noexcept
{
if (this != &obj)
{
std::memmove(&this->key_storage_, &obj.key_storage_, sizeof(this->key_storage_));
ZeroMemory(&obj.key_storage_, sizeof(obj.key_storage_));
}
return *this;
}
ecc::key& ecc::key::operator=(const key& obj)
{
if (this != &obj && obj.is_valid())
{
this->deserialize(obj.serialize(obj.key_storage_.type));
}
return *this;
}
bool ecc::key::is_valid() const
{
return (!memory::is_set(&this->key_storage_, 0, sizeof(this->key_storage_)));
}
ecc_key& ecc::key::get()
{
return this->key_storage_;
}
const ecc_key& ecc::key::get() const
{
return this->key_storage_;
}
std::string ecc::key::get_public_key() const
{
uint8_t buffer[512] = {0};
unsigned long length = sizeof(buffer);
if (ecc_ansi_x963_export(&this->key_storage_, buffer, &length) == CRYPT_OK)
{
return std::string(cs(buffer), length);
}
return {};
}
void ecc::key::set(const std::string& pub_key_buffer)
{
this->free();
if (ecc_ansi_x963_import(cs(pub_key_buffer.data()),
ul(pub_key_buffer.size()),
&this->key_storage_) != CRYPT_OK)
{
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
}
}
void ecc::key::deserialize(const std::string& key)
{
this->free();
if (ecc_import(cs(key.data()), ul(key.size()),
&this->key_storage_) != CRYPT_OK
)
{
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
}
}
std::string ecc::key::serialize(const int type) const
{
uint8_t buffer[4096] = {0};
unsigned long length = sizeof(buffer);
if (ecc_export(buffer, &length, type, &this->key_storage_) == CRYPT_OK)
{
return std::string(cs(buffer), length);
}
return "";
}
void ecc::key::free()
{
if (this->is_valid())
{
ecc_free(&this->key_storage_);
}
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
}
bool ecc::key::operator==(key& key) const
{
return (this->is_valid() && key.is_valid() && this->serialize(PK_PUBLIC) == key.serialize(PK_PUBLIC));
}
uint64_t ecc::key::get_hash() const
{
const auto hash = sha1::compute(this->get_public_key());
if (hash.size() >= 8)
{
return *reinterpret_cast<const uint64_t*>(hash.data());
}
return 0;
}
ecc::key ecc::generate_key(const int bits)
{
key key;
ecc_make_key(prng_.get_state(), prng_.get_id(), bits / 8, &key.get());
return key;
}
ecc::key ecc::generate_key(const int bits, const std::string& entropy)
{
key key{};
const prng yarrow(yarrow_desc, false);
yarrow.add_entropy(entropy.data(), entropy.size());
ecc_make_key(yarrow.get_state(), yarrow.get_id(), bits / 8, &key.get());
return key;
}
std::string ecc::sign_message(const key& key, const std::string& message)
{
if (!key.is_valid()) return "";
uint8_t buffer[512];
unsigned long length = sizeof(buffer);
ecc_sign_hash(cs(message.data()), ul(message.size()), buffer, &length, prng_.get_state(), prng_.get_id(),
&key.get());
return std::string(cs(buffer), length);
}
bool ecc::verify_message(const key& key, const std::string& message, const std::string& signature)
{
if (!key.is_valid()) return false;
auto result = 0;
return (ecc_verify_hash(cs(signature.data()),
ul(signature.size()),
cs(message.data()),
ul(message.size()), &result,
&key.get()) == CRYPT_OK && result != 0);
}
bool ecc::encrypt(const key& key, std::string& data)
{
std::string out_data{};
out_data.resize(std::max(ul(data.size() * 3), ul(0x100)));
auto out_len = ul(out_data.size());
auto crypt = [&]()
{
return ecc_encrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len,
prng_.get_state(), prng_.get_id(), find_hash("sha512"), &key.get());
};
auto res = crypt();
if (res == CRYPT_BUFFER_OVERFLOW)
{
out_data.resize(out_len);
res = crypt();
}
if (res != CRYPT_OK)
{
return false;
}
out_data.resize(out_len);
data = std::move(out_data);
return true;
}
bool ecc::decrypt(const key& key, std::string& data)
{
std::string out_data{};
out_data.resize(std::max(ul(data.size() * 3), ul(0x100)));
auto out_len = ul(out_data.size());
auto crypt = [&]()
{
return ecc_decrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, &key.get());
};
auto res = crypt();
if (res == CRYPT_BUFFER_OVERFLOW)
{
out_data.resize(out_len);
res = crypt();
}
if (res != CRYPT_OK)
{
return false;
}
out_data.resize(out_len);
data = std::move(out_data);
return true;
}
std::string rsa::encrypt(const std::string& data, const std::string& hash, const std::string& key)
{
rsa_key new_key;
rsa_import(cs(key.data()), ul(key.size()), &new_key);
const auto _ = gsl::finally([&]()
{
rsa_free(&new_key);
});
std::string out_data{};
out_data.resize(std::max(ul(data.size() * 3), ul(0x100)));
auto out_len = ul(out_data.size());
auto crypt = [&]()
{
return rsa_encrypt_key_ex(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, cs(hash.data()),
ul(hash.size()), prng_.get_state(), prng_.get_id(), find_hash("sha512"), LTC_PKCS_1_V1_5, &new_key);
};
auto res = crypt();
if (res == CRYPT_BUFFER_OVERFLOW)
{
out_data.resize(out_len);
res = crypt();
}
if (res == CRYPT_OK)
{
out_data.resize(out_len);
return out_data;
}
return {};
}
std::string des3::encrypt(const std::string& data, const std::string& iv, const std::string& key)
{
std::string enc_data;
enc_data.resize(data.size());
symmetric_CBC cbc;
const auto des3 = find_cipher("3des");
cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast<int>(key.size()), 0, &cbc);
cbc_encrypt(cs(data.data()), cs(enc_data.data()), ul(data.size()), &cbc);
cbc_done(&cbc);
return enc_data;
}
std::string des3::decrypt(const std::string& data, const std::string& iv, const std::string& key)
{
std::string dec_data;
dec_data.resize(data.size());
symmetric_CBC cbc;
const auto des3 = find_cipher("3des");
cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast<int>(key.size()), 0, &cbc);
cbc_decrypt(cs(data.data()), cs(dec_data.data()), ul(data.size()), &cbc);
cbc_done(&cbc);
return dec_data;
}
std::string tiger::compute(const std::string& data, const bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string tiger::compute(const uint8_t* data, const size_t length, const bool hex)
{
uint8_t buffer[24] = {0};
hash_state state;
tiger_init(&state);
tiger_process(&state, data, ul(length));
tiger_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
std::string aes::encrypt(const std::string& data, const std::string& iv, const std::string& key)
{
std::string aligned_data = data;
const auto mod = aligned_data.size() % 16;
if (mod != 0)
{
aligned_data.resize(aligned_data.size() + (16 - mod));
}
std::string enc_data;
enc_data.resize(aligned_data.size());
symmetric_CBC cbc;
const auto aes = find_cipher("aes");
cbc_start(aes, cs(iv.data()), cs(key.data()),
static_cast<int>(key.size()), 0, &cbc);
cbc_encrypt(cs(aligned_data.data()),
cs(enc_data.data()),
ul(aligned_data.size()), &cbc);
cbc_done(&cbc);
return enc_data;
}
std::string aes::decrypt(const std::string& data, const std::string& iv, const std::string& key)
{
std::string dec_data;
dec_data.resize(data.size());
symmetric_CBC cbc;
const auto aes = find_cipher("aes");
cbc_start(aes, cs(iv.data()), cs(key.data()),
static_cast<int>(key.size()), 0, &cbc);
cbc_decrypt(cs(data.data()),
cs(dec_data.data()),
ul(data.size()), &cbc);
cbc_done(&cbc);
return dec_data;
}
std::string hmac_sha1::compute(const std::string& data, const std::string& key)
{
std::string buffer;
buffer.resize(20);
hmac_state state;
hmac_init(&state, find_hash("sha1"), cs(key.data()), ul(key.size()));
hmac_process(&state, cs(data.data()), static_cast<int>(data.size()));
auto out_len = ul(buffer.size());
hmac_done(&state, cs(buffer.data()), &out_len);
buffer.resize(out_len);
return buffer;
}
std::string sha1::compute(const std::string& data, const bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string sha1::compute(const uint8_t* data, const size_t length, const bool hex)
{
uint8_t buffer[20] = {0};
hash_state state;
sha1_init(&state);
sha1_process(&state, data, ul(length));
sha1_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
std::string sha256::compute(const std::string& data, const bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string sha256::compute(const uint8_t* data, const size_t length, const bool hex)
{
uint8_t buffer[32] = {0};
hash_state state;
sha256_init(&state);
sha256_process(&state, data, ul(length));
sha256_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
std::string sha512::compute(const std::string& data, const bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string sha512::compute(const uint8_t* data, const size_t length, const bool hex)
{
uint8_t buffer[64] = {0};
hash_state state;
sha512_init(&state);
sha512_process(&state, data, ul(length));
sha512_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
namespace md5
{
std::string compute(const std::string& data, bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string compute(const uint8_t* data, size_t length, bool hex)
{
uint8_t buffer[16] = {0};
hash_state state;
md5_init(&state);
md5_process(&state, data, ul(length));
md5_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
}
std::string base64::encode(const uint8_t* data, const size_t len)
{
std::string result;
result.resize((len + 2) * 2);
auto out_len = ul(result.size());
if (base64_encode(data, ul(len), result.data(), &out_len) != CRYPT_OK)
{
return {};
}
result.resize(out_len);
return result;
}
std::string base64::encode(const std::string& data)
{
return base64::encode(cs(data.data()), static_cast<unsigned>(data.size()));
}
std::string base64::decode(const std::string& data)
{
std::string result;
result.resize((data.size() + 2) * 2);
auto out_len = ul(result.size());
if (base64_decode(data.data(), ul(data.size()), cs(result.data()), &out_len) != CRYPT_OK)
{
return {};
}
result.resize(out_len);
return result;
}
unsigned int jenkins_one_at_a_time::compute(const std::string& data)
{
return compute(data.data(), data.size());
}
unsigned int jenkins_one_at_a_time::compute(const char* key, const size_t len)
{
unsigned int hash, i;
for (hash = i = 0; i < len; ++i)
{
hash += key[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
uint32_t random::get_integer()
{
uint32_t result;
random::get_data(&result, sizeof(result));
return result;
}
uint32_t random::get_integer(const std::uint32_t min, const std::uint32_t max)
{
const auto range = max - min + 1;
const auto value = random::get_integer();
return value % range + min;
}
std::string random::get_challenge()
{
std::string result;
result.resize(sizeof(uint32_t));
random::get_data(result.data(), result.size());
return string::dump_hex(result, "");
}
void random::get_data(void* data, const size_t size)
{
prng_.read(data, size);
}
std::string random::get_data(const size_t size)
{
std::string data;
data.resize(size);
random::get_data(data.data(), size);
return data;
}
}
#pragma warning(pop)
@@ -0,0 +1,151 @@
#pragma once
#include <string>
#include <tomcrypt.h>
#include "string.hpp"
namespace utils::cryptography
{
namespace ecc
{
class key final
{
public:
key();
~key();
key(key&& obj) noexcept;
key(const key& obj);
key& operator=(key&& obj) noexcept;
key& operator=(const key& obj);
bool is_valid() const;
ecc_key& get();
const ecc_key& get() const;
std::string get_public_key() const;
void set(const std::string& pub_key_buffer);
void deserialize(const std::string& key);
std::string serialize(int type = PK_PRIVATE) const;
void free();
bool operator==(key& key) const;
uint64_t get_hash() const;
private:
ecc_key key_storage_{};
};
key generate_key(int bits);
key generate_key(int bits, const std::string& entropy);
std::string sign_message(const key& key, const std::string& message);
bool verify_message(const key& key, const std::string& message, const std::string& signature);
bool encrypt(const key& key, std::string& data);
bool decrypt(const key& key, std::string& data);
}
namespace rsa
{
std::string encrypt(const std::string& data, const std::string& hash, const std::string& key);
std::string decrypt(const std::string& data, const std::string& hash, const std::string& key);
}
namespace des3
{
std::string encrypt(const std::string& data, const std::string& iv, const std::string& key);
std::string decrypt(const std::string& data, const std::string& iv, const std::string& key);
}
namespace tiger
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace aes
{
std::string encrypt(const std::string& data, const std::string& iv, const std::string& key);
std::string decrypt(const std::string& data, const std::string& iv, const std::string& key);
}
namespace hmac_sha1
{
std::string compute(const std::string& data, const std::string& key);
}
namespace sha1
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace sha256
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace sha512
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace md5
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace argon2
{
template <size_t HashLen = 32, size_t SaltLen = 16, std::uint32_t TCost = 2,
std::uint32_t MCost = 1 << 6, std::uint32_t Threads = 1>
std::string compute(const std::string& data, bool hex = false)
{
std::uint8_t buffer[HashLen]{};
std::uint8_t salt[SaltLen]{};
argon2i_hash_raw(TCost, MCost, Threads, data.data(), data.size(), salt, SaltLen, buffer, HashLen);
const auto str = std::string{reinterpret_cast<char*>(buffer), HashLen};
if (hex)
{
return string::dump_hex(str, "", false);
}
else
{
return str;
}
}
}
namespace base64
{
std::string encode(const uint8_t* data, size_t len);
std::string encode(const std::string& data);
std::string decode(const std::string& data);
}
namespace jenkins_one_at_a_time
{
unsigned int compute(const std::string& data);
unsigned int compute(const char* key, size_t len);
};
namespace random
{
uint32_t get_integer();
uint32_t get_integer(const std::uint32_t min, const std::uint32_t max);
std::string get_challenge();
void get_data(void* data, size_t size);
std::string get_data(const size_t size);
}
}
@@ -0,0 +1,54 @@
#include <stdinc.hpp>
#include "flags.hpp"
#include "string.hpp"
#include <shellapi.h>
namespace utils::flags
{
void parse_flags(std::vector<std::string>& flags)
{
int num_args;
auto* const argv = CommandLineToArgvW(GetCommandLineW(), &num_args);
flags.clear();
if (argv)
{
for (auto i = 0; i < num_args; ++i)
{
std::wstring wide_flag(argv[i]);
if (wide_flag[0] == L'-')
{
wide_flag.erase(wide_flag.begin());
flags.emplace_back(string::convert(wide_flag));
}
}
LocalFree(argv);
}
}
bool has_flag(const std::string& flag)
{
static auto parsed = false;
static std::vector<std::string> enabled_flags;
if (!parsed)
{
parse_flags(enabled_flags);
parsed = true;
}
for (const auto& entry : enabled_flags)
{
if (string::to_lower(entry) == string::to_lower(flag))
{
return true;
}
}
return false;
}
}
@@ -0,0 +1,6 @@
#pragma once
namespace utils::flags
{
bool has_flag(const std::string& flag);
}
@@ -0,0 +1,208 @@
#include <stdinc.hpp>
#include "hook.hpp"
#include "string.hpp"
// iw6x-client
namespace utils::hook
{
namespace
{
[[maybe_unused]] class _
{
public:
_()
{
if (MH_Initialize() != MH_OK)
{
throw std::runtime_error("Failed to initialize MinHook");
}
}
~_()
{
MH_Uninitialize();
}
} __;
}
asmjit::Error assembler::call(void* target)
{
return Assembler::call(size_t(target));
}
asmjit::Error assembler::jmp(void* target)
{
return Assembler::jmp(size_t(target));
}
detour::detour(const size_t place, void* target) : detour(reinterpret_cast<void*>(place), target)
{
}
detour::detour(void* place, void* target)
{
this->create(place, target);
}
detour::~detour()
{
this->clear();
}
void detour::enable() const
{
MH_EnableHook(this->place_);
}
void detour::disable() const
{
MH_DisableHook(this->place_);
}
void detour::create(void* place, void* target)
{
this->clear();
this->place_ = place;
if (MH_CreateHook(this->place_, target, &this->original_) != MH_OK)
{
throw std::runtime_error(string::va("Unable to create hook at location: %p", this->place_));
}
this->enable();
}
void detour::create(const size_t place, void* target)
{
this->create(reinterpret_cast<void*>(place), target);
}
void detour::clear()
{
if (this->place_)
{
MH_RemoveHook(this->place_);
}
this->place_ = nullptr;
this->original_ = nullptr;
}
void* detour::get_original() const
{
return this->original_;
}
void nop(void* place, const size_t length)
{
DWORD old_protect{};
VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect);
std::memset(place, 0x90, length);
VirtualProtect(place, length, old_protect, &old_protect);
FlushInstructionCache(GetCurrentProcess(), place, length);
}
void nop(const size_t place, const size_t length)
{
nop(reinterpret_cast<void*>(place), length);
}
void copy(void* place, const void* data, const size_t length)
{
DWORD old_protect{};
VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect);
std::memmove(place, data, length);
VirtualProtect(place, length, old_protect, &old_protect);
FlushInstructionCache(GetCurrentProcess(), place, length);
}
void copy(const size_t place, const void* data, const size_t length)
{
copy(reinterpret_cast<void*>(place), data, length);
}
bool is_relatively_far(const void* pointer, const void* data, int offset)
{
const int64_t diff = size_t(data) - (size_t(pointer) + offset);
const auto small_diff = int32_t(diff);
return diff != int64_t(small_diff);
}
void call(void* pointer, void* data)
{
if (is_relatively_far(pointer, data))
{
throw std::runtime_error("Too far away to create 32bit relative branch");
}
auto* patch_pointer = PBYTE(pointer);
set<uint8_t>(patch_pointer, 0xE8);
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
}
void call(const size_t pointer, void* data)
{
return call(reinterpret_cast<void*>(pointer), data);
}
void call(const size_t pointer, const size_t data)
{
return call(pointer, reinterpret_cast<void*>(data));
}
void set(std::uintptr_t address, std::vector<std::uint8_t>&& bytes)
{
DWORD oldProtect = 0;
auto* place = reinterpret_cast<void*>(address);
VirtualProtect(place, bytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(place, bytes.data(), bytes.size());
VirtualProtect(place, bytes.size(), oldProtect, &oldProtect);
FlushInstructionCache(GetCurrentProcess(), place, bytes.size());
}
void set(std::uintptr_t address, void* buffer, size_t size)
{
DWORD oldProtect = 0;
auto* place = reinterpret_cast<void*>(address);
VirtualProtect(place, size, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(place, buffer, size);
VirtualProtect(place, size, oldProtect, &oldProtect);
FlushInstructionCache(GetCurrentProcess(), place, size);
}
void jump(std::uintptr_t address, void* destination)
{
if (!address) return;
std::uint8_t* bytes = new std::uint8_t[5];
*bytes = 0xE9;
*reinterpret_cast<std::uint32_t*>(bytes + 1) = CalculateRelativeJMPAddress(address, destination);
set(address, bytes, 5);
delete[] bytes;
}
void* assemble(const std::function<void(assembler&)>& asm_function)
{
static asmjit::JitRuntime runtime;
asmjit::CodeHolder code;
code.init(runtime.environment());
assembler a(&code);
asm_function(a);
void* result = nullptr;
runtime.add(&result, &code);
return result;
}
}
@@ -0,0 +1,146 @@
#pragma once
#define CalculateRelativeJMPAddress(X, Y) (((std::uintptr_t)Y - (std::uintptr_t)X) - 5)
#include <asmjit/core/jitruntime.h>
#include <asmjit/x86/x86assembler.h>
using namespace asmjit::x86;
namespace utils::hook
{
class assembler : public Assembler
{
public:
using Assembler::Assembler;
using Assembler::call;
using Assembler::jmp;
asmjit::Error call(void* target);
asmjit::Error jmp(void* target);
};
class detour
{
public:
detour() = default;
detour(void* place, void* target);
detour(size_t place, void* target);
~detour();
detour(detour&& other) noexcept
{
this->operator=(std::move(other));
}
detour& operator= (detour&& other) noexcept
{
if (this != &other)
{
this->~detour();
this->place_ = other.place_;
this->original_ = other.original_;
other.place_ = nullptr;
other.original_ = nullptr;
}
return *this;
}
detour(const detour&) = delete;
detour& operator= (const detour&) = delete;
void enable() const;
void disable() const;
void create(void* place, void* target);
void create(size_t place, void* target);
void clear();
template <typename T>
T* get() const
{
return static_cast<T*>(this->get_original());
}
template <typename T, typename... Args>
T invoke(Args... args)
{
return static_cast<T(*)(Args ...)>(this->get_original())(args...);
}
template <typename T, typename... Args>
T invoke_pascal(Args... args)
{
return static_cast<T(__stdcall*)(Args ...)>(this->get_original())(args...);
}
[[nodiscard]] void* get_original() const;
private:
void* place_{};
void* original_{};
};
void nop(void* place, size_t length);
void nop(size_t place, size_t length);
void copy(void* place, const void* data, size_t length);
void copy(size_t place, const void* data, size_t length);
bool is_relatively_far(const void* pointer, const void* data, int offset = 5);
void call(void* pointer, void* data);
void call(size_t pointer, void* data);
void call(size_t pointer, size_t data);
void jump(std::uintptr_t address, void* destination);
void* assemble(const std::function<void(assembler&)>& asm_function);
template <typename T>
T extract(void* address)
{
const auto data = static_cast<uint8_t*>(address);
const auto offset = *reinterpret_cast<int32_t*>(data);
return reinterpret_cast<T>(data + offset + 4);
}
template <typename T>
T extract(const size_t address)
{
return extract<T>(reinterpret_cast<void*>(address));
}
template <typename T>
static void set(void* place, T value)
{
DWORD old_protect;
VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &old_protect);
*static_cast<T*>(place) = value;
VirtualProtect(place, sizeof(T), old_protect, &old_protect);
FlushInstructionCache(GetCurrentProcess(), place, sizeof(T));
}
template <typename T>
static void set(const size_t place, T value)
{
return set<T>(reinterpret_cast<void*>(place), value);
}
template <typename T, typename... Args>
static T invoke(size_t func, Args... args)
{
return reinterpret_cast<T(*)(Args ...)>(func)(args...);
}
template <typename T, typename... Args>
static T invoke(void* func, Args... args)
{
return static_cast<T(*)(Args ...)>(func)(args...);
}
}
@@ -0,0 +1,112 @@
#ifdef _WIN32
#include <stdinc.hpp>
#endif
#include "http.hpp"
#include <memory>
#include <utility>
#ifdef _WIN32
#pragma comment(lib, "ws2_32.lib")
#endif
namespace utils::http
{
namespace
{
size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp) noexcept
{
const auto buffer = static_cast<std::string*>(userp);
const auto total_size = size * nmemb;
try
{
buffer->append(static_cast<char*>(contents), total_size);
}
catch (...)
{
return 0; // Never unwind a C++ exception through libcurl.
}
return total_size;
}
double timing_ms(CURL* curl, const CURLINFO info)
{
double seconds = 0;
curl_easy_getinfo(curl, info, &seconds);
return seconds * 1000.0;
}
}
std::optional<result> get_data(const std::string& url, const std::string& fields,
const headers& headers, const std::string& method, const timeouts& limits)
{
result response;
if (limits.connect_timeout_ms <= 0 || limits.timeout_ms <= 0)
{
response.code = CURLE_BAD_FUNCTION_ARGUMENT;
return response;
}
const std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> handle(curl_easy_init(), curl_easy_cleanup);
const auto curl = handle.get();
if (!curl)
{
return {};
}
std::unique_ptr<curl_slist, decltype(&curl_slist_free_all)> header_list(nullptr, curl_slist_free_all);
for (const auto& header : headers)
{
auto data = header.first + ": " + header.second;
const auto appended = curl_slist_append(header_list.get(), data.data());
if (!appended)
{
response.code = CURLE_OUT_OF_MEMORY;
return response;
}
header_list.release();
header_list.reset(appended);
}
std::string buffer{};
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list.get());
curl_easy_setopt(curl, CURLOPT_URL, url.data());
// Real native deadlines: a script-side wait limit cannot stop this transfer.
auto code = curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, limits.connect_timeout_ms);
if (code == CURLE_OK)
code = curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, limits.timeout_ms);
if (code != CURLE_OK)
{
response.code = code;
return response;
}
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
if (!fields.empty())
{
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields.data());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(fields.size()));
}
if (!method.empty())
{
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.data());
}
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
response.code = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.status);
response.dns_ms = timing_ms(curl, CURLINFO_NAMELOOKUP_TIME);
response.connect_ms = timing_ms(curl, CURLINFO_CONNECT_TIME);
response.tls_ms = timing_ms(curl, CURLINFO_APPCONNECT_TIME);
response.first_byte_ms = timing_ms(curl, CURLINFO_STARTTRANSFER_TIME);
response.total_ms = timing_ms(curl, CURLINFO_TOTAL_TIME);
if (response.code == CURLE_OK)
response.buffer = std::move(buffer);
return response;
}
}
@@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <optional>
#include <unordered_map>
#include <curl/curl.h>
namespace utils::http
{
struct result
{
CURLcode code = CURLE_FAILED_INIT;
std::string buffer;
long status = 0;
// Cumulative milliseconds from the start of curl_easy_perform.
double dns_ms = 0;
double connect_ms = 0;
double tls_ms = 0;
double first_byte_ms = 0;
double total_ms = 0;
};
struct timeouts
{
long connect_timeout_ms = 5000;
long timeout_ms = 15000;
};
using headers = std::unordered_map<std::string, std::string>;
std::optional<result> get_data(const std::string& url, const std::string& fields = {},
const headers& headers = {}, const std::string& method = {}, const timeouts& limits = {});
}
@@ -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;
}
}
@@ -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);
};
}
@@ -0,0 +1,135 @@
#include <stdinc.hpp>
#include <fstream>
#include "io.hpp"
namespace utils::io
{
bool remove_file(const std::string& file)
{
return DeleteFileA(file.data()) == TRUE;
}
bool move_file(const std::string& src, const std::string& target)
{
return MoveFileA(src.data(), target.data()) == TRUE;
}
bool file_exists(const std::string& file)
{
return std::ifstream(file).good();
}
bool write_file(const std::string& file, const std::string& data, const bool append)
{
const auto pos = file.find_last_of("/\\");
if (pos != std::string::npos)
{
create_directory(file.substr(0, pos));
}
std::ofstream stream(
file, std::ios::binary | std::ofstream::out | (append ? std::ofstream::app : 0));
if (stream.is_open())
{
stream.write(data.data(), data.size());
stream.close();
return true;
}
return false;
}
std::string read_file(const std::string& file)
{
std::string data;
read_file(file, &data);
return data;
}
bool read_file(const std::string& file, std::string* data)
{
if (!data) return false;
data->clear();
if (file_exists(file))
{
std::ifstream stream(file, std::ios::binary);
if (!stream.is_open()) return false;
stream.seekg(0, std::ios::end);
const std::streamsize size = stream.tellg();
stream.seekg(0, std::ios::beg);
if (size > -1)
{
data->resize(static_cast<uint32_t>(size));
stream.read(const_cast<char*>(data->data()), size);
stream.close();
return true;
}
}
return false;
}
size_t file_size(const std::string& file)
{
if (file_exists(file))
{
std::ifstream stream(file, std::ios::binary);
if (stream.good())
{
stream.seekg(0, std::ios::end);
return static_cast<size_t>(stream.tellg());
}
}
return 0;
}
bool create_directory(const std::string& directory)
{
return std::filesystem::create_directories(directory);
}
bool directory_exists(const std::string& directory)
{
return std::filesystem::is_directory(directory);
}
bool directory_is_empty(const std::string& directory)
{
return std::filesystem::is_empty(directory);
}
std::vector<std::string> list_files(const std::string& directory)
{
std::vector<std::string> files;
for (auto& file : std::filesystem::directory_iterator(directory))
{
files.push_back(file.path().generic_string());
}
return files;
}
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target)
{
std::filesystem::copy(src, target, std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive);
}
size_t remove_directory(const std::filesystem::path& src, bool recursive)
{
if (recursive)
{
return static_cast<size_t>(std::filesystem::remove_all(src));
}
else
{
return static_cast<size_t>(std::filesystem::remove(src));
}
}
}
@@ -0,0 +1,22 @@
#pragma once
#include <string>
#include <vector>
#include <filesystem>
namespace utils::io
{
bool remove_file(const std::string& file);
bool move_file(const std::string& src, const std::string& target);
bool file_exists(const std::string& file);
bool write_file(const std::string& file, const std::string& data, bool append = false);
bool read_file(const std::string& file, std::string* data);
std::string read_file(const std::string& file);
size_t file_size(const std::string& file);
bool create_directory(const std::string& directory);
bool directory_exists(const std::string& directory);
bool directory_is_empty(const std::string& directory);
std::vector<std::string> list_files(const std::string& directory);
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
size_t remove_directory(const std::filesystem::path& src, bool recursive = false);
}
@@ -0,0 +1,109 @@
#include <stdinc.hpp>
#include "memory.hpp"
namespace utils
{
memory::allocator memory::mem_allocator_;
memory::allocator::~allocator()
{
this->clear();
}
void memory::allocator::clear()
{
std::lock_guard _(this->mutex_);
for (auto& data : this->pool_)
{
memory::free(data);
}
this->pool_.clear();
}
void memory::allocator::free(void* data)
{
std::lock_guard _(this->mutex_);
const auto j = std::find(this->pool_.begin(), this->pool_.end(), data);
if (j != this->pool_.end())
{
memory::free(data);
this->pool_.erase(j);
}
}
void memory::allocator::free(const void* data)
{
this->free(const_cast<void*>(data));
}
void* memory::allocator::allocate(const size_t length)
{
std::lock_guard _(this->mutex_);
const auto data = memory::allocate(length);
this->pool_.push_back(data);
return data;
}
bool memory::allocator::empty() const
{
return this->pool_.empty();
}
char* memory::allocator::duplicate_string(const std::string& string)
{
std::lock_guard _(this->mutex_);
const auto data = memory::duplicate_string(string);
this->pool_.push_back(data);
return data;
}
void* memory::allocate(const size_t length)
{
return calloc(length, 1);
}
char* memory::duplicate_string(const std::string& string)
{
const auto new_string = allocate_array<char>(string.size() + 1);
std::memcpy(new_string, string.data(), string.size());
return new_string;
}
void memory::free(void* data)
{
if (data)
{
::free(data);
}
}
void memory::free(const void* data)
{
free(const_cast<void*>(data));
}
bool memory::is_set(const void* mem, const char chr, const size_t length)
{
const auto mem_arr = static_cast<const char*>(mem);
for (size_t i = 0; i < length; ++i)
{
if (mem_arr[i] != chr)
{
return false;
}
}
return true;
}
memory::allocator* memory::get_allocator()
{
return &memory::mem_allocator_;
}
}
@@ -0,0 +1,75 @@
#pragma once
#include <mutex>
#include <vector>
namespace utils
{
class memory final
{
public:
class allocator final
{
public:
~allocator();
void clear();
void free(void* data);
void free(const void* data);
void* allocate(size_t length);
template <typename T>
inline T* allocate()
{
return this->allocate_array<T>(1);
}
template <typename T>
inline T* allocate_array(const size_t count = 1)
{
return static_cast<T*>(this->allocate(count * sizeof(T)));
}
bool empty() const;
char* duplicate_string(const std::string& string);
private:
std::mutex mutex_;
std::vector<void*> pool_;
};
static void* allocate(size_t length);
template <typename T>
static inline T* allocate()
{
return allocate_array<T>(1);
}
template <typename T>
static inline T* allocate_array(const size_t count = 1)
{
return static_cast<T*>(allocate(count * sizeof(T)));
}
static char* duplicate_string(const std::string& string);
static void free(void* data);
static void free(const void* data);
static bool is_set(const void* mem, char chr, size_t length);
static bool is_bad_read_ptr(const void* ptr);
static bool is_bad_code_ptr(const void* ptr);
static bool is_rdata_ptr(void* ptr);
static allocator* get_allocator();
private:
static allocator mem_allocator_;
};
}
@@ -0,0 +1,250 @@
#include <stdinc.hpp>
#include "nt.hpp"
#include "string.hpp"
namespace utils::nt
{
HMODULE library::current_handle_;
library library::load(const std::string& name)
{
return library(LoadLibraryA(name.data()));
}
library library::load(const std::filesystem::path& path)
{
return library::load(path.generic_string());
}
library library::get_by_address(void* address)
{
HMODULE handle = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast<LPCSTR>(address), &handle);
return library(handle);
}
void library::set_current_handle(HMODULE handle)
{
current_handle_ = handle;
}
HMODULE library::get_current_handle()
{
return current_handle_;
}
library::library()
{
this->module_ = GetModuleHandleA(nullptr);
}
library::library(const std::string& name)
{
this->module_ = GetModuleHandleA(name.data());
}
library::library(const HMODULE handle)
{
this->module_ = handle;
}
bool library::operator==(const library& obj) const
{
return this->module_ == obj.module_;
}
library::operator bool() const
{
return this->is_valid();
}
library::operator HMODULE() const
{
return this->get_handle();
}
PIMAGE_NT_HEADERS library::get_nt_headers() const
{
if (!this->is_valid()) return nullptr;
return reinterpret_cast<PIMAGE_NT_HEADERS>(this->get_ptr() + this->get_dos_header()->e_lfanew);
}
PIMAGE_DOS_HEADER library::get_dos_header() const
{
return reinterpret_cast<PIMAGE_DOS_HEADER>(this->get_ptr());
}
PIMAGE_OPTIONAL_HEADER library::get_optional_header() const
{
if (!this->is_valid()) return nullptr;
return &this->get_nt_headers()->OptionalHeader;
}
std::vector<PIMAGE_SECTION_HEADER> library::get_section_headers() const
{
std::vector<PIMAGE_SECTION_HEADER> headers;
auto nt_headers = this->get_nt_headers();
auto section = IMAGE_FIRST_SECTION(nt_headers);
for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i, ++section)
{
if (section) headers.push_back(section);
else OutputDebugStringA("There was an invalid section :O");
}
return headers;
}
std::uint8_t* library::get_ptr() const
{
return reinterpret_cast<std::uint8_t*>(this->module_);
}
void library::unprotect() const
{
if (!this->is_valid()) return;
DWORD protection;
VirtualProtect(this->get_ptr(), this->get_optional_header()->SizeOfImage, PAGE_EXECUTE_READWRITE,
&protection);
}
size_t library::get_relative_entry_point() const
{
if (!this->is_valid()) return 0;
return this->get_nt_headers()->OptionalHeader.AddressOfEntryPoint;
}
void* library::get_entry_point() const
{
if (!this->is_valid()) return nullptr;
return this->get_ptr() + this->get_relative_entry_point();
}
bool library::is_valid() const
{
return this->module_ != nullptr && this->get_dos_header()->e_magic == IMAGE_DOS_SIGNATURE;
}
std::string library::get_name() const
{
if (!this->is_valid()) return "";
auto path = this->get_path();
const auto pos = path.find_last_of("/\\");
if (pos == std::string::npos) return path;
return path.substr(pos + 1);
}
std::string library::get_path() const
{
if (!this->is_valid()) return "";
char name[MAX_PATH] = {0};
GetModuleFileNameA(this->module_, name, sizeof name);
return name;
}
std::string library::get_folder() const
{
if (!this->is_valid()) return "";
const auto path = std::filesystem::path(this->get_path());
return path.parent_path().generic_string();
}
void library::free()
{
if (this->is_valid())
{
FreeLibrary(this->module_);
this->module_ = nullptr;
}
}
HMODULE library::get_handle() const
{
return this->module_;
}
void** library::get_iat_entry(const std::string& module_name, const std::string& proc_name) const
{
if (!this->is_valid()) return nullptr;
const library other_module(module_name);
if (!other_module.is_valid()) return nullptr;
auto* const target_function = other_module.get_proc<void*>(proc_name);
if (!target_function) return nullptr;
auto* header = this->get_optional_header();
if (!header) return nullptr;
auto* import_descriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(this->get_ptr() + header->DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (import_descriptor->Name)
{
if (!_stricmp(reinterpret_cast<char*>(this->get_ptr() + import_descriptor->Name), module_name.data()))
{
auto* original_thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->
OriginalFirstThunk + this->get_ptr());
auto* thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->FirstThunk + this->
get_ptr());
while (original_thunk_data->u1.AddressOfData)
{
const size_t ordinal_number = original_thunk_data->u1.AddressOfData & 0xFFFFFFF;
if (ordinal_number > 0xFFFF) continue;
if (GetProcAddress(other_module.module_, reinterpret_cast<char*>(ordinal_number)) ==
target_function)
{
return reinterpret_cast<void**>(&thunk_data->u1.Function);
}
++original_thunk_data;
++thunk_data;
}
//break;
}
++import_descriptor;
}
return nullptr;
}
void raise_hard_exception()
{
int data = false;
const library ntdll("ntdll.dll");
ntdll.invoke_pascal<void>("RtlAdjustPrivilege", 19, true, false, &data);
ntdll.invoke_pascal<void>("NtRaiseHardError", 0xC000007B, 0, nullptr, nullptr, 6, &data);
}
std::string load_resource(const int id)
{
const auto self_handle = library::get_current_handle();
const auto res = FindResource(self_handle, MAKEINTRESOURCE(id), RT_RCDATA);
if (res == nullptr)
{
return {};
}
const auto handle = LoadResource(self_handle, res);
if (handle == nullptr)
{
return {};
}
const auto str = LPSTR(LockResource(handle));
const auto size = SizeofResource(self_handle, res);
return std::string{str, size};
}
}
@@ -0,0 +1,112 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
// min and max is required by gdi, therefore NOMINMAX won't work
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#include <string>
#include <functional>
#include <filesystem>
namespace utils::nt
{
class library final
{
public:
static library load(const std::string& name);
static library load(const std::filesystem::path& path);
static library get_by_address(void* address);
static void set_current_handle(HMODULE handle);
static HMODULE get_current_handle();
library();
explicit library(const std::string& name);
explicit library(HMODULE handle);
library(const library& a) : module_(a.module_)
{
}
bool operator!=(const library& obj) const { return !(*this == obj); };
bool operator==(const library& obj) const;
operator bool() const;
operator HMODULE() const;
void unprotect() const;
void* get_entry_point() const;
size_t get_relative_entry_point() const;
bool is_valid() const;
std::string get_name() const;
std::string get_path() const;
std::string get_folder() const;
std::uint8_t* get_ptr() const;
void free();
HMODULE get_handle() const;
template <typename T>
T get_proc(const std::string& process) const
{
if (!this->is_valid()) T{};
return reinterpret_cast<T>(GetProcAddress(this->module_, process.data()));
}
template <typename T>
std::function<T> get(const std::string& process) const
{
if (!this->is_valid()) return std::function<T>();
return static_cast<T*>(this->get_proc<void*>(process));
}
template <typename T, typename... Args>
T invoke(const std::string& process, Args ... args) const
{
auto method = this->get<T(__cdecl)(Args ...)>(process);
if (method) return method(args...);
return T();
}
template <typename T, typename... Args>
T invoke_pascal(const std::string& process, Args ... args) const
{
auto method = this->get<T(__stdcall)(Args ...)>(process);
if (method) return method(args...);
return T();
}
template <typename T, typename... Args>
T invoke_this(const std::string& process, void* this_ptr, Args ... args) const
{
auto method = this->get<T(__thiscall)(void*, Args ...)>(this_ptr, process);
if (method) return method(args...);
return T();
}
std::vector<PIMAGE_SECTION_HEADER> get_section_headers() const;
PIMAGE_NT_HEADERS get_nt_headers() const;
PIMAGE_DOS_HEADER get_dos_header() const;
PIMAGE_OPTIONAL_HEADER get_optional_header() const;
void** get_iat_entry(const std::string& module_name, const std::string& proc_name) const;
private:
HMODULE module_;
static HMODULE current_handle_;
};
__declspec(noreturn) void raise_hard_exception();
std::string load_resource(int id);
}
@@ -0,0 +1,157 @@
#include <stdinc.hpp>
#include "string.hpp"
#include <sstream>
#include <cstdarg>
#include <algorithm>
namespace utils::string
{
const char* va(const char* fmt, ...)
{
static thread_local va_provider<8, 256> provider;
va_list ap;
va_start(ap, fmt);
const char* result = provider.get(fmt, ap);
va_end(ap);
return result;
}
std::vector<std::string> split(const std::string& s, const char delim)
{
std::stringstream ss(s);
std::string item;
std::vector<std::string> elems;
while (std::getline(ss, item, delim))
{
elems.push_back(item); // elems.push_back(std::move(item)); // if C++11 (based on comment from @mchiasson)
}
return elems;
}
std::string to_lower(std::string text)
{
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 unsigned char input)
{
return static_cast<char>(toupper(input));
});
return text;
}
bool starts_with(const std::string& text, const std::string& substring)
{
return text.find(substring) == 0;
}
bool ends_with(const std::string& text, const std::string& substring)
{
if (substring.size() > text.size()) return false;
return std::equal(substring.rbegin(), substring.rend(), text.rbegin());
}
bool is_numeric(const std::string& text)
{
return std::to_string(atoi(text.data())) == text;
}
std::string dump_hex(const std::string& data, const std::string& separator)
{
std::string result;
for (unsigned int i = 0; i < data.size(); ++i)
{
if (i > 0)
{
result.append(separator);
}
result.append(va("%02X", data[i] & 0xFF));
}
return result;
}
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 convert(const std::wstring& wstr)
{
std::string result;
result.reserve(wstr.size());
for (const auto& chr : wstr)
{
result.push_back(static_cast<char>(chr));
}
return result;
}
std::wstring convert(const std::string& str)
{
std::wstring result;
result.reserve(str.size());
for (const auto& chr : str)
{
result.push_back(static_cast<wchar_t>(chr));
}
return result;
}
std::string get_timestamp()
{
tm ltime{};
char timestamp[MAX_PATH] = { 0 };
const auto time = _time64(nullptr);
_localtime64_s(&ltime, &time);
strftime(timestamp, sizeof(timestamp) - 1, "%Y-%m-%d-%H-%M-%S", &ltime);
return timestamp;
}
std::string trim(const std::string& str, const std::string& whitespace)
{
const auto first = str.find_first_not_of(whitespace);
if (first == std::string::npos)
{
return {};
}
const auto last = str.find_last_not_of(whitespace);
const auto range = last - first + 1;
return str.substr(first, range);
}
}
@@ -0,0 +1,101 @@
#pragma once
#include "memory.hpp"
#include <cstdint>
#ifndef ARRAYSIZE
template <class Type, size_t n>
size_t ARRAYSIZE(Type(&)[n]) { return n; }
#endif
namespace utils::string
{
template <size_t Buffers, size_t MinBufferSize>
class va_provider final
{
public:
static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0");
va_provider() : current_buffer_(0)
{
}
char* get(const char* format, const va_list ap)
{
++this->current_buffer_ %= ARRAYSIZE(this->string_pool_);
auto entry = &this->string_pool_[this->current_buffer_];
if (!entry->size || !entry->buffer)
{
throw std::runtime_error("String pool not initialized");
}
while (true)
{
const int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap);
if (res > 0) break; // Success
if (res == 0) return nullptr; // Error
entry->double_size();
}
return entry->buffer;
}
private:
class entry final
{
public:
explicit entry(const size_t _size = MinBufferSize) : size(_size), buffer(nullptr)
{
if (this->size < MinBufferSize) this->size = MinBufferSize;
this->allocate();
}
~entry()
{
if (this->buffer) memory::get_allocator()->free(this->buffer);
this->size = 0;
this->buffer = nullptr;
}
void allocate()
{
if (this->buffer) memory::get_allocator()->free(this->buffer);
this->buffer = memory::get_allocator()->allocate_array<char>(this->size + 1);
}
void double_size()
{
this->size *= 2;
this->allocate();
}
size_t size;
char* buffer;
};
size_t current_buffer_;
entry string_pool_[Buffers];
};
const char* va(const char* fmt, ...);
std::vector<std::string> split(const std::string& s, char delim);
std::string to_lower(std::string text);
std::string to_upper(std::string text);
bool starts_with(const std::string& text, const std::string& substring);
bool ends_with(const std::string& text, const std::string& substring);
bool is_numeric(const std::string& text);
std::string dump_hex(const std::string& data, const std::string& separator = " ");
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 get_timestamp();
std::string trim(const std::string& str, const std::string& whitespace = " \t\n\r\f\v");
}
@@ -0,0 +1,89 @@
#include <stdinc.hpp>
#include "thread.hpp"
#include "string.hpp"
#include <TlHelp32.h>
#include <gsl/gsl>
namespace utils::thread
{
std::vector<DWORD> get_thread_ids()
{
auto* const h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
if (h == INVALID_HANDLE_VALUE)
{
return {};
}
const auto _ = gsl::finally([h]()
{
CloseHandle(h);
});
THREADENTRY32 entry{};
entry.dwSize = sizeof(entry);
if (!Thread32First(h, &entry))
{
return {};
}
std::vector<DWORD> ids{};
do
{
const auto check_size = entry.dwSize < FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID)
+ sizeof(entry.th32OwnerProcessID);
entry.dwSize = sizeof(entry);
if (check_size && entry.th32OwnerProcessID == GetCurrentProcessId())
{
ids.emplace_back(entry.th32ThreadID);
}
}
while (Thread32Next(h, &entry));
return ids;
}
void for_each_thread(const std::function<void(HANDLE)>& callback)
{
const auto ids = get_thread_ids();
for (const auto& id : ids)
{
auto* const thread = OpenThread(THREAD_ALL_ACCESS, FALSE, id);
if (thread != nullptr)
{
const auto _ = gsl::finally([thread]()
{
CloseHandle(thread);
});
callback(thread);
}
}
}
void suspend_other_threads()
{
for_each_thread([](const HANDLE thread)
{
if (GetThreadId(thread) != GetCurrentThreadId())
{
SuspendThread(thread);
}
});
}
void resume_other_threads()
{
for_each_thread([](const HANDLE thread)
{
if (GetThreadId(thread) != GetCurrentThreadId())
{
ResumeThread(thread);
}
});
}
}
@@ -0,0 +1,19 @@
#pragma once
#include <thread>
namespace utils::thread
{
template <typename ...Args>
std::thread create_named_thread(const std::string& name, Args&&... args)
{
auto t = std::thread(std::forward<Args>(args)...);
set_name(t, name);
return t;
}
std::vector<DWORD> get_thread_ids();
void for_each_thread(const std::function<void(HANDLE)>& callback);
void suspend_other_threads();
void resume_other_threads();
}
@@ -0,0 +1,96 @@
#include <stdinc.hpp>
#include "thread_pool.hpp"
namespace utils
{
thread_pool::worker::worker()
{
}
void thread_pool::worker::start(thread_pool& pool)
{
this->thread_ = std::thread([&]
{
this->loop(pool);
});
}
void thread_pool::worker::stop()
{
if (this->thread_.joinable())
{
this->thread_.join();
}
}
void thread_pool::worker::loop(thread_pool& pool)
{
while (!pool.stopped_)
{
pool.run_job();
}
}
thread_pool::job_ptr thread_pool::pop_job()
{
thread_pool::job_ptr job = std::move(this->queue_.front());
this->queue_.pop_front();
return job;
}
void thread_pool::run_job()
{
std::unique_lock<std::mutex> lock(this->mutex_);
this->event_.wait(lock, [&]()
{
return !this->queue_.empty() || this->stopped_;
});
if (this->stopped_ || this->queue_.empty())
{
return;
}
auto job = this->pop_job();
lock.unlock();
job->operator()();
}
thread_pool::thread_pool(const std::size_t num_workers)
{
this->initialize(num_workers);
}
void thread_pool::initialize(const std::size_t num_workers)
{
for (auto i = 0u; i < num_workers; i++)
{
this->workers_.emplace_back(std::make_unique<thread_pool::worker>());
}
}
void thread_pool::start()
{
for (auto& worker : this->workers_)
{
worker->start(*this);
}
}
void thread_pool::update()
{
}
void thread_pool::stop()
{
this->stopped_ = true;
this->event_.notify_all();
for (auto& worker : this->workers_)
{
worker->stop();
}
}
}
@@ -0,0 +1,60 @@
#pragma once
namespace utils
{
class thread_pool
{
public:
using job = std::function<void()>;
using job_ptr = std::unique_ptr<job>;
friend class worker;
class worker
{
public:
worker();
friend class thread_pool;
void start(thread_pool& pool);
void stop();
void loop(thread_pool& pool);
private:
std::thread thread_;
};
thread_pool() = default;
thread_pool(const std::size_t num_workers);
void initialize(const std::size_t num_workers);
void start();
void update();
void stop();
template <typename F>
void push(F&& job)
{
if (this->stopped_)
{
return;
}
std::lock_guard lock(this->mutex_);
this->queue_.emplace_back(std::make_unique<thread_pool::job>(std::forward<F>(job)));
this->event_.notify_one();
}
private:
thread_pool::job_ptr pop_job();
void run_job();
std::mutex mutex_;
std::atomic_bool stopped_;
std::deque<job_ptr> queue_;
std::condition_variable event_;
std::deque<std::unique_ptr<worker>> workers_;
};
}