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:
@@ -0,0 +1,348 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "gsc.hpp"
|
||||
#include "scripting.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/info_string.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace chat
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct chat_command_t
|
||||
{
|
||||
scripting::function callback;
|
||||
bool hide_message;
|
||||
bool enabled;
|
||||
bool sync;
|
||||
};
|
||||
|
||||
struct chat_callback_t
|
||||
{
|
||||
scripting::function callback;
|
||||
bool sync;
|
||||
};
|
||||
|
||||
using userinfo_map = std::unordered_map<std::string, std::string>;
|
||||
|
||||
utils::hook::detour sv_get_user_info_hook;
|
||||
utils::hook::detour client_connect_hook;
|
||||
utils::hook::detour g_say_hook;
|
||||
|
||||
std::array<userinfo_map, 18> userinfo_overrides;
|
||||
std::unordered_map<std::string, chat_command_t> chat_commands;
|
||||
std::vector<chat_callback_t> chat_callbacks;
|
||||
|
||||
void sv_get_user_info_stub(int index, char* buffer, int buffer_size)
|
||||
{
|
||||
sv_get_user_info_hook.invoke<void>(index, buffer, buffer_size);
|
||||
|
||||
utils::info_string map(buffer);
|
||||
|
||||
for (const auto& [key, val] : userinfo_overrides[index])
|
||||
{
|
||||
if (val.empty())
|
||||
{
|
||||
map.remove(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
map.set(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
const auto userinfo = map.build();
|
||||
strncpy_s(buffer, 1024, userinfo.data(), _TRUNCATE);
|
||||
}
|
||||
|
||||
void clear_client_overrides(unsigned int client)
|
||||
{
|
||||
userinfo_overrides[client].clear();
|
||||
}
|
||||
|
||||
bool handle_chat_command(game::gentity_s* ent, const std::string& text)
|
||||
{
|
||||
const auto trimmed = utils::string::trim(text);
|
||||
const auto args = utils::string::split(trimmed, ' ');
|
||||
if (args.size() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto name = utils::string::to_lower(args[0]);
|
||||
const auto iter = chat_commands.find(name);
|
||||
if (iter == chat_commands.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!iter->second.enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (iter->second.sync)
|
||||
{
|
||||
const auto entity_id = game::Scr_GetEntityId(game::SCRIPTINSTANCE_SERVER, ent->number, 0, 0);
|
||||
const auto result = iter->second.callback(entity_id, {args});
|
||||
|
||||
if (result.is<bool>())
|
||||
{
|
||||
return result.as<bool>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduler::once([=]
|
||||
{
|
||||
const auto entity_id = game::Scr_GetEntityId(game::SCRIPTINSTANCE_SERVER, ent->number, 0, 0);
|
||||
iter->second.callback(entity_id, {args});
|
||||
}, scheduler::server);
|
||||
}
|
||||
|
||||
return iter->second.hide_message;
|
||||
}
|
||||
|
||||
bool handle_chat_callback(game::gentity_s* ent, const std::string& text, const int mode)
|
||||
{
|
||||
auto hidden = false;
|
||||
|
||||
for (const auto& chat_callback : chat_callbacks)
|
||||
{
|
||||
if (chat_callback.sync)
|
||||
{
|
||||
const auto entity_id = game::Scr_GetEntityId(game::SCRIPTINSTANCE_SERVER, ent->number, 0, 0);
|
||||
const auto result = chat_callback.callback(entity_id, {text, mode});
|
||||
|
||||
if (result.is<bool>())
|
||||
{
|
||||
hidden = hidden || result.as<bool>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduler::once([=]
|
||||
{
|
||||
const auto entity_id = game::Scr_GetEntityId(game::SCRIPTINSTANCE_SERVER, ent->number, 0, 0);
|
||||
chat_callback.callback(entity_id, {text, mode});
|
||||
}, scheduler::server);
|
||||
}
|
||||
}
|
||||
|
||||
return hidden;
|
||||
}
|
||||
|
||||
void g_say_stub(game::gentity_s* ent, game::gentity_s* target, int mode, const char* text)
|
||||
{
|
||||
auto hidden = false;
|
||||
hidden = hidden || handle_chat_command(ent, text);
|
||||
hidden = hidden || handle_chat_callback(ent, text, mode);
|
||||
|
||||
if (!hidden)
|
||||
{
|
||||
g_say_hook.invoke<void>(ent, target, mode, text);
|
||||
}
|
||||
}
|
||||
|
||||
void client_clean_name(const char* in, char* out, int out_size)
|
||||
{
|
||||
utils::hook::invoke<void>(SELECT(0x424F00, 0x655010), out, in, out_size); // I_strncpyz
|
||||
}
|
||||
|
||||
__declspec(naked) void client_clean_name_stub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
|
||||
push [esp + 0x20 + 0x4] // outSize
|
||||
push edx // out
|
||||
push ecx // in
|
||||
call client_clean_name
|
||||
add esp, 0xC
|
||||
|
||||
popad
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
game::scr_entref_t check_entity(const scripting::entity& entity)
|
||||
{
|
||||
const auto ent = entity.get_entity_reference();
|
||||
if (ent.classnum != 0)
|
||||
{
|
||||
throw std::runtime_error("invalid entity");
|
||||
}
|
||||
|
||||
if (game::g_entities[ent.entnum].client == nullptr)
|
||||
{
|
||||
throw std::runtime_error("not a player entity");
|
||||
}
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
template <bool value>
|
||||
void enable_chat_command(const std::string& name)
|
||||
{
|
||||
const auto lower = utils::string::to_lower(name);
|
||||
const auto iter = chat_commands.find(lower);
|
||||
if (iter == chat_commands.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
iter->second.enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
const auto plutonium_g_say_stub = utils::hook::extract<std::size_t>(SELECT(0x6A7A40, 0x493DF0) + 1);
|
||||
g_say_hook.create(plutonium_g_say_stub, g_say_stub);
|
||||
sv_get_user_info_hook.create(SELECT(0x68BB90, 0x4C10F0), 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);
|
||||
|
||||
utils::hook::call(SELECT(0x4ED764, 0x427E84), client_clean_name_stub);
|
||||
utils::hook::call(SELECT(0x4ED79F, 0x427EBF), client_clean_name_stub);
|
||||
|
||||
scripting::on_shutdown([]()
|
||||
{
|
||||
for (auto& entry : userinfo_overrides)
|
||||
{
|
||||
entry.clear();
|
||||
}
|
||||
|
||||
chat_callbacks.clear();
|
||||
chat_commands.clear();
|
||||
});
|
||||
|
||||
gsc::method::add("rename", [](const scripting::entity& entity, const std::string& name)
|
||||
{
|
||||
const auto ent = check_entity(entity);
|
||||
userinfo_overrides[ent.entnum]["name"] = name;
|
||||
game::ClientUserInfoChanged(ent.entnum);
|
||||
});
|
||||
|
||||
gsc::method::add("setname", [](const scripting::entity& entity, const std::string& name)
|
||||
{
|
||||
const auto ent = check_entity(entity);
|
||||
userinfo_overrides[ent.entnum]["name"] = name;
|
||||
game::ClientUserInfoChanged(ent.entnum);
|
||||
});
|
||||
|
||||
gsc::method::add("resetname", [](const scripting::entity& entity)
|
||||
{
|
||||
const auto ent = check_entity(entity);
|
||||
userinfo_overrides[ent.entnum].erase("name");
|
||||
game::ClientUserInfoChanged(ent.entnum);
|
||||
});
|
||||
|
||||
gsc::method::add("resetclantag", [](const scripting::entity& entity)
|
||||
{
|
||||
const auto ent = check_entity(entity);
|
||||
userinfo_overrides[ent.entnum].erase("clantag");
|
||||
userinfo_overrides[ent.entnum].erase("clanAbbrev");
|
||||
userinfo_overrides[ent.entnum].erase("clanAbbrevEV");
|
||||
game::ClientUserInfoChanged(ent.entnum);
|
||||
});
|
||||
|
||||
gsc::method::add("setclantag", [](const scripting::entity& entity, const std::string& name)
|
||||
{
|
||||
const auto ent = check_entity(entity);
|
||||
userinfo_overrides[ent.entnum]["clantag"] = name;
|
||||
userinfo_overrides[ent.entnum]["clanAbbrev"] = name;
|
||||
userinfo_overrides[ent.entnum]["clanAbbrevEV"] = "1";
|
||||
game::ClientUserInfoChanged(ent.entnum);
|
||||
});
|
||||
|
||||
gsc::method::add("tell", [](const scripting::entity& entity, const std::string& msg)
|
||||
{
|
||||
const auto ent = check_entity(entity);
|
||||
game::SV_GameSendServerCommand(ent.entnum, 0, utils::string::va("j \"%s\"", msg.data()));
|
||||
});
|
||||
|
||||
gsc::function::add("say", [](const std::string& msg)
|
||||
{
|
||||
game::SV_GameSendServerCommand(-1, 0, utils::string::va("j \"%s\"", msg.data()));
|
||||
});
|
||||
|
||||
gsc::function::add("sendservercommand", [](const int client, const std::string& cmd)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client, 0, cmd.data());
|
||||
});
|
||||
|
||||
gsc::method::add("sendservercommand", [](const scripting::entity& entity, const std::string& cmd)
|
||||
{
|
||||
const auto ent = check_entity(entity);
|
||||
game::SV_GameSendServerCommand(ent.entnum, 0, cmd.data());
|
||||
});
|
||||
|
||||
gsc::function::add_multiple([](const scripting::function& callback, const scripting::variadic_args& va)
|
||||
{
|
||||
chat_callback_t chat_callback{};
|
||||
chat_callback.sync = false;
|
||||
chat_callback.callback = callback;
|
||||
|
||||
if (va.size() >= 1)
|
||||
{
|
||||
chat_callback.sync = va[0].as<bool>();
|
||||
}
|
||||
|
||||
chat_callbacks.emplace_back(chat_callback);
|
||||
}, "onplayersay", "chat::register_callback");
|
||||
|
||||
gsc::function::add("chat::register_command", [](const scripting::function_argument& names_or_name,
|
||||
const scripting::function& callback, const scripting::variadic_args& va)
|
||||
{
|
||||
chat_command_t command{};
|
||||
command.hide_message = true;
|
||||
command.sync = false;
|
||||
command.callback = callback;
|
||||
command.enabled = true;
|
||||
|
||||
if (va.size() >= 1)
|
||||
{
|
||||
command.hide_message = va[0].as<bool>();
|
||||
}
|
||||
|
||||
if (va.size() >= 2)
|
||||
{
|
||||
command.sync = va[1].as<bool>();
|
||||
}
|
||||
|
||||
if (names_or_name.is<scripting::array>())
|
||||
{
|
||||
const std::vector<std::string> names = names_or_name;
|
||||
for (const auto& name : names)
|
||||
{
|
||||
const auto lower = utils::string::to_lower(name);
|
||||
chat_commands.insert(std::make_pair(lower, command));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto name = names_or_name.as<std::string>();
|
||||
const auto lower = utils::string::to_lower(name);
|
||||
chat_commands.insert(std::make_pair(lower, command));
|
||||
}
|
||||
});
|
||||
|
||||
gsc::function::add("chat::disable_command", enable_chat_command<false>);
|
||||
gsc::function::add("chat::enable_command", enable_chat_command<true>);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(chat::component)
|
||||
@@ -0,0 +1,120 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "gsc.hpp"
|
||||
|
||||
namespace clients
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
gsc::function::add("dropallbots", []
|
||||
{
|
||||
const auto* dvar = game::Dvar_FindVar("com_maxclients");
|
||||
const auto client_count = game::Dvar_GetInt(dvar);
|
||||
|
||||
for (auto i = 0; i < client_count; ++i)
|
||||
{
|
||||
if (game::SV_IsTestClient(i))
|
||||
{
|
||||
game::SV_GameDropClient(i, "GAME_DROPPEDFORINACTIVITY");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
gsc::method::add("noclip", [](const scripting::entity& entity)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const auto* ent = game::GetPlayerEntity(entref);
|
||||
|
||||
if (ent->client->flags & game::gclientFlag::NOCLIP)
|
||||
{
|
||||
ent->client->flags &= ~game::gclientFlag::NOCLIP;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->flags |= game::gclientFlag::NOCLIP;
|
||||
}
|
||||
|
||||
game::SV_GameSendServerCommand(entref.entnum, 0, utils::string::va("%c \"%s\"", 0x4F,
|
||||
(ent->client->flags & game::gclientFlag::NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
|
||||
});
|
||||
|
||||
gsc::method::add("ufo", [](const scripting::entity& entity)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const auto* ent = game::GetPlayerEntity(entref);
|
||||
|
||||
if (ent->client->flags & game::gclientFlag::UFO)
|
||||
{
|
||||
ent->client->flags &= ~game::gclientFlag::UFO;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->client->flags |= game::gclientFlag::UFO;
|
||||
}
|
||||
|
||||
game::SV_GameSendServerCommand(entref.entnum, 0, utils::string::va("%c \"%s\"", 0x4F,
|
||||
(ent->client->flags & game::gclientFlag::UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
|
||||
});
|
||||
|
||||
gsc::method::add("god", [](const scripting::entity& entity)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
auto* ent = game::GetEntity(entref);
|
||||
|
||||
if (ent->flags & game::entityFlag::FL_GODMODE)
|
||||
{
|
||||
ent->flags &= ~game::entityFlag::FL_GODMODE;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->flags |= game::entityFlag::FL_GODMODE;
|
||||
}
|
||||
|
||||
game::SV_GameSendServerCommand(entref.entnum, 0, utils::string::va("%c \"%s\"", 0x4F,
|
||||
(ent->flags & game::entityFlag::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF"));
|
||||
});
|
||||
|
||||
gsc::method::add("demigod", [](const scripting::entity& entity)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
auto* ent = game::GetEntity(entref);
|
||||
|
||||
if (ent->flags & game::entityFlag::FL_DEMI_GODMODE)
|
||||
{
|
||||
ent->flags &= ~game::entityFlag::FL_DEMI_GODMODE;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->flags |= game::entityFlag::FL_DEMI_GODMODE;
|
||||
}
|
||||
|
||||
game::SV_GameSendServerCommand(entref.entnum, 0, utils::string::va("%c \"%s\"", 0x4F,
|
||||
(ent->flags & game::entityFlag::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF"));
|
||||
});
|
||||
|
||||
gsc::method::add("notarget", [](const scripting::entity& entity)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
auto* ent = game::GetEntity(entref);
|
||||
|
||||
if (ent->flags & game::entityFlag::FL_NOTARGET)
|
||||
{
|
||||
ent->flags &= ~game::entityFlag::FL_NOTARGET;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent->flags |= game::entityFlag::FL_NOTARGET;
|
||||
}
|
||||
|
||||
game::SV_GameSendServerCommand(entref.entnum, 0, utils::string::va("%c \"%s\"", 0x4F,
|
||||
(ent->flags & game::entityFlag::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF"));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(clients::component)
|
||||
@@ -0,0 +1,334 @@
|
||||
#include "stdinc.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "gsc.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
|
||||
namespace command
|
||||
{
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
params::params()
|
||||
: nesting_(get_cmd_args()->nesting)
|
||||
{
|
||||
}
|
||||
|
||||
int params::size() const
|
||||
{
|
||||
const auto cmd_args = get_cmd_args();
|
||||
return cmd_args->argc[this->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 (int i = index; i < this->size(); i++)
|
||||
{
|
||||
if (i > index)
|
||||
{
|
||||
result.append(" ");
|
||||
}
|
||||
|
||||
result.append(this->get(i));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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.emplace_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, const 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, const 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] = callback;
|
||||
}
|
||||
}
|
||||
|
||||
void add_script_command(const std::string& name, const std::function<void(const params&)>& callback)
|
||||
{
|
||||
script_commands.push_back(name);
|
||||
add(allocator.duplicate_string(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_script_commands()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
void client_command_stub(const int client_num)
|
||||
{
|
||||
params_sv params = {};
|
||||
|
||||
const auto command = utils::string::to_lower(params[0]);
|
||||
if (handlers_sv.find(command) != handlers_sv.end())
|
||||
{
|
||||
handlers_sv[command](client_num, params);
|
||||
return;
|
||||
}
|
||||
|
||||
client_command_hook.invoke<void>(client_num);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
scripting::on_shutdown(clear_script_commands);
|
||||
|
||||
client_command_hook.create(SELECT(0x47E590, 0x4490C0), client_command_stub);
|
||||
|
||||
add("notifylevel", [](const params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
printf("Usage: notifylevel <name> [...]\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!*game::levelEntityId)
|
||||
{
|
||||
printf("Level not loaded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto argc = params.size();
|
||||
|
||||
for (auto i = argc - 1; i > 1; i--)
|
||||
{
|
||||
const auto arg = params[i];
|
||||
scripting::push_value(arg);
|
||||
}
|
||||
|
||||
const auto name = game::SL_GetString(params[1], 0);
|
||||
game::Scr_NotifyId(game::SCRIPTINSTANCE_SERVER, 0, *game::levelEntityId, name, argc - 2);
|
||||
});
|
||||
|
||||
add("notifynum", [](const params& params)
|
||||
{
|
||||
if (params.size() < 3)
|
||||
{
|
||||
printf("Usage: notifynum <num> <name> [...]\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!*game::levelEntityId)
|
||||
{
|
||||
printf("Level not loaded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto client = atoi(params[1]);
|
||||
const auto sv_maxclients = game::Dvar_FindVar("sv_maxclients")->current.integer;
|
||||
|
||||
if (client < 0 || client >= sv_maxclients)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::g_entities[client].client == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto argc = params.size();
|
||||
|
||||
for (auto i = argc - 1; i > 2; i--)
|
||||
{
|
||||
const auto arg = params[i];
|
||||
scripting::push_value(arg);
|
||||
}
|
||||
|
||||
const auto name = game::SL_GetString(params[2], 0);
|
||||
game::Scr_NotifyNum(client, 0, name, argc - 3);
|
||||
});
|
||||
|
||||
gsc::function::add_multiple([](const std::string& cmd)
|
||||
{
|
||||
execute(cmd);
|
||||
}, "executecommand", "command::execute");
|
||||
|
||||
gsc::function::add_multiple([](const std::string& name, const scripting::function& function)
|
||||
{
|
||||
add_script_command(name, [function](const params& params)
|
||||
{
|
||||
scripting::array array;
|
||||
|
||||
for (auto i = 0; i < params.size(); i++)
|
||||
{
|
||||
array.emplace_back(params[i]);
|
||||
}
|
||||
|
||||
function({array.get_raw()});
|
||||
});
|
||||
}, "addcommand", "command::add");
|
||||
|
||||
gsc::function::add_multiple([](const std::string& name, const scripting::function& function)
|
||||
{
|
||||
add_script_sv_command(name, [function](const int client_num, const 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");
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(command::component)
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
namespace command
|
||||
{
|
||||
class params
|
||||
{
|
||||
public:
|
||||
params();
|
||||
|
||||
[[nodiscard]] int size() const;
|
||||
[[nodiscard]] const char* get(int index) const;
|
||||
[[nodiscard]] std::string join(int index) const;
|
||||
|
||||
[[nodiscard]] const char* operator[](const int index) const
|
||||
{
|
||||
return this->get(index);
|
||||
}
|
||||
|
||||
private:
|
||||
int nesting_;
|
||||
};
|
||||
|
||||
class params_sv
|
||||
{
|
||||
public:
|
||||
params_sv();
|
||||
|
||||
[[nodiscard]] int size() const;
|
||||
[[nodiscard]] const char* get(int index) const;
|
||||
[[nodiscard]] std::string join(int index) const;
|
||||
[[nodiscard]] std::vector<std::string> get_all() const;
|
||||
|
||||
[[nodiscard]] 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, const std::function<void(params&)>& callback);
|
||||
|
||||
void add_sv(const std::string& name, const std::function<void(int, const params_sv&)>& callback);
|
||||
|
||||
void add_script_command(const std::string& name, const std::function<void(const params&)>& callback);
|
||||
void clear_script_commands();
|
||||
|
||||
void execute(std::string command, bool sync = false);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "gsc.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace config_strings
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_set<std::uint32_t> reserved_config_strings;
|
||||
std::unordered_map<game::game_hudelem_s*, std::uint32_t> hudelem_config_strings;
|
||||
|
||||
bool is_string_reserved(const std::uint32_t offset)
|
||||
{
|
||||
return reserved_config_strings.contains(offset);
|
||||
}
|
||||
|
||||
std::uint32_t get_config_string()
|
||||
{
|
||||
auto index = -1;
|
||||
const auto scr_const_ = *reinterpret_cast<short*>(SELECT(0x24E5588, 0x24B7188));
|
||||
for (auto i = 1; i < 512; i++)
|
||||
{
|
||||
const auto string_value = game::sv_configstrings[i + 488];
|
||||
if (string_value == scr_const_ && !is_string_reserved(i))
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
throw std::runtime_error("out of config strings");
|
||||
}
|
||||
|
||||
reserved_config_strings.insert(index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
utils::hook::detour scr_free_hud_elem_hook;
|
||||
void scr_free_hud_elem_stub(game::game_hudelem_s* hud)
|
||||
{
|
||||
const auto _0 = gsl::finally([&]
|
||||
{
|
||||
scr_free_hud_elem_hook.invoke<void>(hud);
|
||||
});
|
||||
|
||||
const auto iter = hudelem_config_strings.find(hud);
|
||||
if (iter == hudelem_config_strings.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::SV_SetConfigString(iter->second + 488, 0);
|
||||
reserved_config_strings.erase(iter->second);
|
||||
hudelem_config_strings.erase(iter);
|
||||
}
|
||||
|
||||
void g_localized_string_index_stub_1(utils::hook::assembler& a)
|
||||
{
|
||||
const auto not_reserved = a.newLabel();
|
||||
|
||||
a.add(esp, 4);
|
||||
|
||||
a.pushad();
|
||||
a.push(esi);
|
||||
a.call(is_string_reserved);
|
||||
a.cmp(al, 0);
|
||||
a.jz(not_reserved);
|
||||
|
||||
a.pop(esi);
|
||||
a.popad();
|
||||
a.jmp(SELECT(0x6D657E, 0x467D6E));
|
||||
|
||||
a.bind(not_reserved);
|
||||
a.pop(esi);
|
||||
a.popad();
|
||||
|
||||
a.cmp(eax, ecx);
|
||||
a.jmp(SELECT(0x6D6578, 0x467D68));
|
||||
}
|
||||
|
||||
void g_localized_string_index_stub_2(utils::hook::assembler& a)
|
||||
{
|
||||
const auto not_reserved = a.newLabel();
|
||||
|
||||
a.add(esp, 4);
|
||||
|
||||
a.pushad();
|
||||
a.push(esi);
|
||||
a.call(is_string_reserved);
|
||||
a.cmp(al, 0);
|
||||
a.jz(not_reserved);
|
||||
|
||||
a.pop(esi);
|
||||
a.popad();
|
||||
a.jmp(SELECT(0x6D6638, 0x467E2E));
|
||||
|
||||
a.bind(not_reserved);
|
||||
a.pop(esi);
|
||||
a.popad();
|
||||
|
||||
a.cmp(eax, ecx);
|
||||
a.jmp(SELECT(0x6D663E, 0x467E28));
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
utils::hook::jump(SELECT(0x6D6573, 0x467D63), utils::hook::assemble(g_localized_string_index_stub_1));
|
||||
utils::hook::jump(SELECT(0x6D6633, 0x467E23), utils::hook::assemble(g_localized_string_index_stub_2));
|
||||
scr_free_hud_elem_hook.create(SELECT(0x56E800, 0x6186A0), scr_free_hud_elem_stub);
|
||||
|
||||
scripting::on_shutdown([&]
|
||||
{
|
||||
reserved_config_strings.clear();
|
||||
hudelem_config_strings.clear();
|
||||
});
|
||||
|
||||
gsc::method::add("hudelem::set_text", [](const scripting::entity& entity, const std::string& text)
|
||||
{
|
||||
const auto ent = entity.get_entity_reference();
|
||||
const auto hudelem = &game::g_hudelems[ent.entnum];
|
||||
const auto iter = hudelem_config_strings.find(hudelem);
|
||||
|
||||
if (iter == hudelem_config_strings.end())
|
||||
{
|
||||
const auto index = get_config_string();
|
||||
hudelem->elem.text = static_cast<short>(index);
|
||||
hudelem_config_strings.insert(std::make_pair(hudelem, index));
|
||||
}
|
||||
else
|
||||
{
|
||||
hudelem->elem.text = static_cast<short>(iter->second);
|
||||
}
|
||||
|
||||
game::SV_SetConfigString(hudelem->elem.text + 488, text.data());
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(config_strings::component)
|
||||
@@ -0,0 +1,135 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "gsc.hpp"
|
||||
#include "io.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
|
||||
namespace cryptography
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::vector<utils::cryptography::ecc::key> keys;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
scripting::on_shutdown([]
|
||||
{
|
||||
keys.clear();
|
||||
});
|
||||
|
||||
gsc::function::add("ecc::generate_key", [](int bits)
|
||||
{
|
||||
const auto key = utils::cryptography::ecc::generate_key(bits);
|
||||
keys.emplace_back(key);
|
||||
return keys.size() - 1;
|
||||
});
|
||||
|
||||
gsc::function::add("ecc::create_key_from_public_key", [](const std::string& data)
|
||||
{
|
||||
utils::cryptography::ecc::key key;
|
||||
key.set(utils::string::hex_to_bin(data));
|
||||
keys.emplace_back(key);
|
||||
return keys.size() - 1;
|
||||
});
|
||||
|
||||
gsc::function::add("ecc::encrypt", [](const std::uint32_t key_index, const std::string& data)
|
||||
{
|
||||
if (key_index < 0 || key_index >= keys.size())
|
||||
{
|
||||
throw std::runtime_error("invalid key");
|
||||
}
|
||||
|
||||
const auto& key = keys.at(key_index);
|
||||
std::string encrypted = data;
|
||||
if (!utils::cryptography::ecc::encrypt(key, encrypted))
|
||||
{
|
||||
throw std::runtime_error("failed to encrypt");
|
||||
}
|
||||
|
||||
return utils::string::bin_to_hex(encrypted);
|
||||
});
|
||||
|
||||
gsc::function::add("ecc::decrypt", [](const std::uint32_t key_index, const std::string& data)
|
||||
{
|
||||
if (key_index < 0 || key_index >= keys.size())
|
||||
{
|
||||
throw std::runtime_error("invalid key");
|
||||
}
|
||||
|
||||
const auto& key = keys.at(key_index);
|
||||
std::string decrypted = utils::string::hex_to_bin(data);
|
||||
if (!utils::cryptography::ecc::decrypt(key, decrypted))
|
||||
{
|
||||
throw std::runtime_error("failed to decrypt");
|
||||
}
|
||||
|
||||
return decrypted;
|
||||
});
|
||||
|
||||
gsc::function::add("ecc::import_key", [](const std::string& data)
|
||||
{
|
||||
utils::cryptography::ecc::key key;
|
||||
key.deserialize(utils::string::hex_to_bin(data));
|
||||
keys.emplace_back(key);
|
||||
return keys.size() - 1;
|
||||
});
|
||||
|
||||
gsc::function::add("ecc::export_public_key", [](const std::uint32_t key_index)
|
||||
{
|
||||
if (key_index < 0 || key_index >= keys.size())
|
||||
{
|
||||
throw std::runtime_error("invalid key");
|
||||
}
|
||||
|
||||
const auto& key = keys.at(key_index);
|
||||
return utils::string::bin_to_hex(key.serialize(PK_PUBLIC));
|
||||
});
|
||||
|
||||
gsc::function::add("ecc::export_private_key", [](const std::uint32_t key_index)
|
||||
{
|
||||
if (key_index < 0 || key_index >= keys.size())
|
||||
{
|
||||
throw std::runtime_error("invalid key");
|
||||
}
|
||||
|
||||
const auto& key = keys.at(key_index);
|
||||
return utils::string::bin_to_hex(key.serialize(PK_PRIVATE));
|
||||
});
|
||||
|
||||
gsc::function::add("aes::encrypt", [](const std::string& data, const std::string& iv, const std::string& key)
|
||||
{
|
||||
return utils::string::bin_to_hex(utils::cryptography::aes::encrypt(data,
|
||||
utils::string::hex_to_bin(iv),
|
||||
utils::string::hex_to_bin(key))
|
||||
);
|
||||
});
|
||||
|
||||
gsc::function::add("aes::decrypt", [](const std::string& data, const std::string& iv, const std::string& key)
|
||||
{
|
||||
return utils::cryptography::aes::decrypt(utils::string::hex_to_bin(data),
|
||||
utils::string::hex_to_bin(iv),
|
||||
utils::string::hex_to_bin(key));
|
||||
});
|
||||
|
||||
gsc::function::add("cryptography::random_bytes", [](const int len)
|
||||
{
|
||||
return utils::string::bin_to_hex(utils::cryptography::random::get_data(len));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(cryptography::component)
|
||||
@@ -0,0 +1,145 @@
|
||||
#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 T6 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 T6 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-t6-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 T6 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
|
||||
{
|
||||
SetUnhandledExceptionFilter(exception_filter);
|
||||
utils::hook::jump(reinterpret_cast<uintptr_t>(&SetUnhandledExceptionFilter), set_unhandled_exception_filter_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(exception::component)
|
||||
@@ -0,0 +1,585 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/structs.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "gsc.hpp"
|
||||
#include "json.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
#include <utils/hook.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 player_get_method_hook;
|
||||
utils::hook::detour scr_get_common_function_hook;
|
||||
utils::hook::detour scr_error_internal_hook;
|
||||
utils::hook::detour scr_post_load_scripts_hook;
|
||||
|
||||
auto field_offset_start = 0xA000;
|
||||
std::unordered_map<unsigned int, entity_field_t> custom_fields[class_count];
|
||||
|
||||
std::string current_namespace;
|
||||
|
||||
std::unordered_map<std::string, scripting::script_value> world;
|
||||
|
||||
std::string get_full_name(const std::string& name)
|
||||
{
|
||||
if (current_namespace.empty())
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return std::format("{}::{}", current_namespace, name);
|
||||
}
|
||||
|
||||
void* find_function(const std::string& name)
|
||||
{
|
||||
const auto full_name = get_full_name(name);
|
||||
const auto iter = function_wraps.find(full_name);
|
||||
if (iter == function_wraps.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void* find_method(const std::string& name)
|
||||
{
|
||||
const auto full_name = get_full_name(name);
|
||||
const auto iter = method_wraps.find(full_name);
|
||||
if (iter == method_wraps.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
builtin_function scr_get_common_function(const char** name, int* type, int* min_args, int* max_args)
|
||||
{
|
||||
const auto func = reinterpret_cast<builtin_function>(find_function(*name));
|
||||
|
||||
if (func == nullptr)
|
||||
{
|
||||
return scr_get_common_function_hook.invoke<builtin_function>(name, type, min_args, max_args);
|
||||
}
|
||||
|
||||
*type = 1;
|
||||
*min_args = 0;
|
||||
*max_args = 512;
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
builtin_method player_get_method(const char** name, int* min_args, int* max_args)
|
||||
{
|
||||
const auto method = reinterpret_cast<builtin_method>(find_method(*name));
|
||||
|
||||
if (method == nullptr)
|
||||
{
|
||||
return player_get_method_hook.invoke<builtin_method>(name, min_args, max_args);
|
||||
}
|
||||
|
||||
*min_args = 0;
|
||||
*max_args = 512;
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
const gsc::entity_field_t* find_field(unsigned int classnum, unsigned int offset)
|
||||
{
|
||||
const auto& class_map = custom_fields[classnum];
|
||||
const auto field_iter = class_map.find(offset);
|
||||
if (field_iter == class_map.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &field_iter->second;
|
||||
}
|
||||
|
||||
utils::hook::detour scr_get_object_field_hook;
|
||||
void scr_get_object_field_stub(unsigned int classnum, int entnum, unsigned int offset)
|
||||
{
|
||||
const auto field = find_field(classnum, offset);
|
||||
if (field == nullptr)
|
||||
{
|
||||
return scr_get_object_field_hook.invoke<void>(classnum, entnum, offset);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const auto result = field->getter(entnum);
|
||||
return_value(result);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("******* script runtime error *******\n");
|
||||
printf("while getting field \"%s\": %s\n", field->name.data(), e.what());
|
||||
printf("************************************\n");
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour scr_set_object_field_hook;
|
||||
int scr_set_object_field_stub(unsigned int classnum, int entnum, unsigned int offset)
|
||||
{
|
||||
const auto field = find_field(classnum, offset);
|
||||
if (field == nullptr)
|
||||
{
|
||||
return scr_set_object_field_hook.invoke<int>(classnum, entnum, offset);
|
||||
}
|
||||
|
||||
const auto args = get_arguments();
|
||||
|
||||
try
|
||||
{
|
||||
field->setter(entnum, args[0]);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("******* script runtime error *******\n");
|
||||
printf("while setting field \"%s\": %s\n", field->name.data(), e.what());
|
||||
printf("************************************\n");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void scr_post_load_scripts_stub()
|
||||
{
|
||||
for (auto i = 0; i < class_id_t::class_count; i++)
|
||||
{
|
||||
const auto& class_map = custom_fields[i];
|
||||
for (const auto& [offset, field] : class_map)
|
||||
{
|
||||
game::Scr_AddClassField(game::SCRIPTINSTANCE_SERVER, i, field.name.data(), offset);
|
||||
}
|
||||
}
|
||||
|
||||
return scr_post_load_scripts_hook.invoke<void>();
|
||||
}
|
||||
|
||||
__declspec(naked) void scr_error_internal_stub_1_mp()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
mov eax, 0
|
||||
mov edx, eax
|
||||
imul edx, 0x54
|
||||
|
||||
push 0x8F3F65
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void scr_error_internal_stub_1_zm()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
mov eax, 0
|
||||
mov edx, eax
|
||||
imul edx, 0x54
|
||||
|
||||
push 0x8F2CC5
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
void gsc_obj_resolve_stub_1(game::GSC_OBJ* obj, game::GSC_IMPORT_ITEM* item)
|
||||
{
|
||||
const auto name = &obj->magic[item->name];
|
||||
current_namespace = &obj->magic[item->name_space];
|
||||
|
||||
if (current_namespace[0] == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto is_function = find_function(name);
|
||||
const auto is_method = !is_function && find_method(name);
|
||||
|
||||
if (is_function || is_method)
|
||||
{
|
||||
item->name_space += static_cast<unsigned short>(current_namespace.size());
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void gsc_obj_resolve_stub_mp()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
push edi
|
||||
push esi
|
||||
call gsc_obj_resolve_stub_1
|
||||
pop esi
|
||||
pop edi
|
||||
popad
|
||||
|
||||
movzx eax, byte ptr[edi + 7]
|
||||
and eax, 0xF
|
||||
dec eax
|
||||
|
||||
push 0x6CD7F1
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void gsc_obj_resolve_stub_zm()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
push edi
|
||||
push esi
|
||||
call gsc_obj_resolve_stub_1
|
||||
pop esi
|
||||
pop edi
|
||||
popad
|
||||
|
||||
movzx eax, byte ptr[edi + 7]
|
||||
and eax, 0xF
|
||||
dec eax
|
||||
|
||||
push 0x4F0441
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void call_function(const function_t& function, const std::string& name)
|
||||
{
|
||||
const auto args = get_arguments();
|
||||
|
||||
try
|
||||
{
|
||||
const auto value = function(args);
|
||||
return_value(value);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("******* script runtime error *******\n");
|
||||
printf("in call to builtin function \"%s\": %s\n", name.data(), e.what());
|
||||
printf("************************************\n");
|
||||
}
|
||||
}
|
||||
|
||||
void call_method(const function_t& method, const std::string& name, 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> next_args{};
|
||||
next_args.push_back(entity);
|
||||
for (const auto& arg : args)
|
||||
{
|
||||
next_args.push_back(arg);
|
||||
}
|
||||
|
||||
const auto value = method(next_args);
|
||||
return_value(value);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("******* script runtime error *******\n");
|
||||
printf("in call to builtin method \"%s\": %s\n", name.data(), e.what());
|
||||
printf("************************************\n");
|
||||
}
|
||||
}
|
||||
|
||||
namespace field
|
||||
{
|
||||
void add(const class_id_t classnum, const std::string& name, const field_getter_t getter, const field_setter_t& setter)
|
||||
{
|
||||
const auto offset = field_offset_start++;
|
||||
auto& class_map = custom_fields[classnum];
|
||||
|
||||
entity_field_t field{};
|
||||
field.name = name;
|
||||
field.getter = getter;
|
||||
field.setter = setter;
|
||||
class_map.insert(std::make_pair(offset, field));
|
||||
}
|
||||
}
|
||||
|
||||
void return_value(const scripting::script_value& value)
|
||||
{
|
||||
if (game::scr_VmPub->outparamcount)
|
||||
{
|
||||
game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER);
|
||||
}
|
||||
|
||||
scripting::push_value(value);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::string find_builtin_name(void* function)
|
||||
{
|
||||
for (auto i = scripting::function_map.begin(); i != scripting::function_map.end(); ++i)
|
||||
{
|
||||
if (i->second.actionFunc == function)
|
||||
{
|
||||
return i->second.actionString;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = function_wraps.begin(); i != function_wraps.end(); ++i)
|
||||
{
|
||||
if (i->second == function)
|
||||
{
|
||||
return i->first;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string find_builtin_method_name(void* function)
|
||||
{
|
||||
for (auto i = scripting::method_map.begin(); i != scripting::method_map.end(); ++i)
|
||||
{
|
||||
if (i->second.actionFunc == function)
|
||||
{
|
||||
return i->second.actionString;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = method_wraps.begin(); i != method_wraps.end(); ++i)
|
||||
{
|
||||
if (i->second == function)
|
||||
{
|
||||
return i->first;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
utils::hook::jump(SELECT(0x6CD7E9, 0x4F0439), SELECT(gsc_obj_resolve_stub_mp, gsc_obj_resolve_stub_zm));
|
||||
|
||||
scr_get_common_function_hook.create(SELECT(0x4B57B0, 0x4AD040), scr_get_common_function);
|
||||
player_get_method_hook.create(SELECT(0x432480, 0x6F2DB0), player_get_method);
|
||||
|
||||
scr_get_object_field_hook.create(SELECT(0x573160, 0x4224E0), scr_get_object_field_stub);
|
||||
scr_set_object_field_hook.create(SELECT(0x5B9820, 0x43F2A0), scr_set_object_field_stub);
|
||||
scr_post_load_scripts_hook.create(SELECT(0x6B75B0, 0x492440), scr_post_load_scripts_stub);
|
||||
|
||||
utils::hook::jump(SELECT(0x8F3F60, 0x8F2CC0), SELECT(scr_error_internal_stub_1_mp, scr_error_internal_stub_1_zm));
|
||||
|
||||
field::add(class_id_t::class_entity, "eflags",
|
||||
[](unsigned int entnum) -> scripting::script_value
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
return entity->flags;
|
||||
},
|
||||
[](unsigned int entnum, const scripting::script_value& value)
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
entity->flags = value.as<int>();
|
||||
}
|
||||
);
|
||||
|
||||
field::add(class_id_t::class_entity, "eflags2",
|
||||
[](unsigned int entnum) -> scripting::script_value
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
return entity->eFlags2;
|
||||
},
|
||||
[](unsigned int entnum, const scripting::script_value& value)
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
entity->eFlags2 = value.as<int>();
|
||||
}
|
||||
);
|
||||
|
||||
field::add(class_id_t::class_entity, "clientflags",
|
||||
[](unsigned int entnum) -> scripting::script_value
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
return entity->client->flags;
|
||||
},
|
||||
[](unsigned int entnum, const scripting::script_value& value)
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
entity->client->flags = value.as<int>();
|
||||
}
|
||||
);
|
||||
|
||||
field::add(class_id_t::class_entity, "address",
|
||||
[](unsigned int entnum) -> scripting::script_value
|
||||
{
|
||||
if (entnum >= 18)
|
||||
{
|
||||
throw std::runtime_error("not a player entity");
|
||||
}
|
||||
|
||||
const auto clients = *game::svs_clients;
|
||||
const auto client = &clients[entnum];
|
||||
const auto address = client->header.netchan.remoteAddress.ip;
|
||||
|
||||
const std::string address_str = utils::string::va("%i.%i.%i.%i",
|
||||
address[0],
|
||||
address[1],
|
||||
address[2],
|
||||
address[3]
|
||||
);
|
||||
|
||||
return address_str;
|
||||
},
|
||||
[](unsigned int, const scripting::script_value&)
|
||||
{
|
||||
throw std::runtime_error("cannot set address");
|
||||
}
|
||||
);
|
||||
|
||||
function::add("getfunctionargcount", [](const scripting::function& function)
|
||||
{
|
||||
const auto pos = function.get_pos();
|
||||
if (*pos != 0x17) // OP_SafeCreateLocalVariables
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<int>(pos[1]);
|
||||
});
|
||||
|
||||
function::add("arrayremovekey", [](const scripting::array& array, const std::string& key)
|
||||
{
|
||||
array.erase(key);
|
||||
});
|
||||
|
||||
function::add("xor", [](const int a, const int b)
|
||||
{
|
||||
return a ^ b;
|
||||
});
|
||||
|
||||
function::add("not", [](const int a)
|
||||
{
|
||||
return ~a;
|
||||
});
|
||||
|
||||
function::add("and", [](const int a, const int b)
|
||||
{
|
||||
return a & b;
|
||||
});
|
||||
|
||||
function::add("or", [](const int a, const int b)
|
||||
{
|
||||
return a | b;
|
||||
});
|
||||
|
||||
function::add("structget", [](const scripting::object& obj, const std::string& key)
|
||||
{
|
||||
return obj[key];
|
||||
});
|
||||
|
||||
function::add("structset", [](const scripting::object& obj, const std::string& key,
|
||||
const scripting::script_value& value)
|
||||
{
|
||||
obj[key] = value;
|
||||
});
|
||||
|
||||
function::add("struct::get", [](const scripting::object& obj, const std::string& key)
|
||||
{
|
||||
return obj[key];
|
||||
});
|
||||
|
||||
function::add("struct::set", [](const scripting::object& obj, const std::string& key,
|
||||
const scripting::script_value& value)
|
||||
{
|
||||
obj[key] = value;
|
||||
});
|
||||
|
||||
function::add("structremove", [](const scripting::object& obj, const std::string& key)
|
||||
{
|
||||
obj.erase(key);
|
||||
});
|
||||
|
||||
function::add("getstructkeys", [](const scripting::object& obj)
|
||||
{
|
||||
return obj.get_keys();
|
||||
});
|
||||
|
||||
function::add("isfunctionptr", [](const scripting::script_value& value)
|
||||
{
|
||||
return value.is<scripting::function>();
|
||||
});
|
||||
|
||||
function::add("isentity", [](const scripting::script_value& value)
|
||||
{
|
||||
const auto& raw = value.get_raw();
|
||||
const auto type = game::scr_VarGlob->objectVariableValue[raw.u.uintValue].w.type & 0x7F;
|
||||
return raw.type == game::SCRIPT_OBJECT && type == game::SCRIPT_ENTITY;
|
||||
});
|
||||
|
||||
function::add("isstruct", [](const scripting::script_value& value)
|
||||
{
|
||||
return value.is<scripting::object>();
|
||||
});
|
||||
|
||||
function::add("typeof", [](const scripting::script_value& value)
|
||||
{
|
||||
return value.type_name();
|
||||
});
|
||||
|
||||
method::add("get", [](const scripting::entity& entity, const std::string& field)
|
||||
{
|
||||
return entity.get(field);
|
||||
});
|
||||
|
||||
method::add("set", [](const scripting::entity& entity, const std::string& field,
|
||||
const scripting::script_value& value)
|
||||
{
|
||||
entity.set(field, value);
|
||||
});
|
||||
|
||||
function::add("worldget", [](const std::string& key)
|
||||
{
|
||||
return world[key];
|
||||
});
|
||||
|
||||
function::add("worldset", [](const std::string& key, const scripting::script_value& value)
|
||||
{
|
||||
world[key] = value;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::component)
|
||||
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/scripting/array.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "../plugin.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));
|
||||
}
|
||||
|
||||
enum class_id_t
|
||||
{
|
||||
class_entity,
|
||||
class_hudelem,
|
||||
class_pathnode,
|
||||
class_node,
|
||||
class_count
|
||||
};
|
||||
|
||||
using field_getter_t = std::function<scripting::script_value(unsigned int entnum)>;
|
||||
using field_setter_t = std::function<void(unsigned int entnum, const scripting::script_value&)>;
|
||||
|
||||
struct entity_field_t
|
||||
{
|
||||
std::string name;
|
||||
field_getter_t getter;
|
||||
field_setter_t setter;
|
||||
};
|
||||
|
||||
void return_value(const scripting::script_value& value);
|
||||
std::vector<scripting::script_value> get_arguments();
|
||||
|
||||
using builtin_function = void(*)();
|
||||
using builtin_method = void(*)(game::scr_entref_t);
|
||||
|
||||
std::string find_builtin_name(void* function);
|
||||
std::string find_builtin_method_name(void* function);
|
||||
|
||||
void call_function(const function_t& function, const std::string& name);
|
||||
void call_method(const function_t& method, const std::string& name, 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;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[function] %s\n", name.data());
|
||||
#endif
|
||||
|
||||
static 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, lower);
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[method] %s\n", name.data());
|
||||
#endif
|
||||
|
||||
static 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, lower, 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), ...);
|
||||
}
|
||||
}
|
||||
|
||||
namespace field
|
||||
{
|
||||
void add(const class_id_t classnum, const std::string& name, const field_getter_t getter, const field_setter_t& setter);
|
||||
}
|
||||
}
|
||||
#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,133 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "gsc.hpp"
|
||||
#include "io.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/http.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/file_watcher.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
|
||||
namespace io
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct watched_file
|
||||
{
|
||||
std::unique_ptr<utils::io::file_watcher> watcher;
|
||||
scripting::object handle;
|
||||
};
|
||||
|
||||
std::vector<watched_file> watched_files;
|
||||
|
||||
void poll_files()
|
||||
{
|
||||
for (auto& file : watched_files)
|
||||
{
|
||||
std::string data;
|
||||
if (file.watcher->run(1ms, &data, true))
|
||||
{
|
||||
scripting::notify(file.handle.get_entity_id(), "data", {data});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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("hashstring", [](const char* str)
|
||||
{
|
||||
return game::BG_StringHashValue(str);
|
||||
});
|
||||
|
||||
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::string& directory, const scripting::variadic_args& va)
|
||||
{
|
||||
auto recursive = false;
|
||||
if (va.size() > 0)
|
||||
{
|
||||
recursive = va[0];
|
||||
}
|
||||
|
||||
utils::io::remove_directory(directory, 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");
|
||||
|
||||
if (utils::flags::has_flag("experimental-utils"))
|
||||
{
|
||||
scheduler::loop(poll_files, scheduler::server_packet_loop);
|
||||
|
||||
scripting::on_shutdown([]()
|
||||
{
|
||||
watched_files.clear();
|
||||
});
|
||||
|
||||
gsc::function::add("io::watch_file", [](const std::string& file)
|
||||
{
|
||||
scripting::object handle;
|
||||
|
||||
auto watcher = std::make_unique<utils::io::file_watcher>(file);
|
||||
watcher->init();
|
||||
watched_file watched_file{};
|
||||
watched_file.watcher = std::move(watcher);
|
||||
watched_file.handle = handle;
|
||||
watched_files.emplace_back(std::move(watched_file));
|
||||
|
||||
return handle;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(io::component)
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <stdinc.hpp>
|
||||
|
||||
namespace io
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.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
|
||||
{
|
||||
constexpr const auto max_str_len = 0x5000u;
|
||||
|
||||
struct dump_context_t
|
||||
{
|
||||
std::unordered_set<std::uint32_t> visited_objects;
|
||||
};
|
||||
|
||||
nlohmann::json gsc_to_json(const scripting::script_value& value, bool print_id, dump_context_t& ctx);
|
||||
|
||||
nlohmann::json array_to_json(const scripting::array& array, bool print_id, dump_context_t& ctx)
|
||||
{
|
||||
if (array.size() == 0)
|
||||
{
|
||||
return nlohmann::json::array();
|
||||
}
|
||||
|
||||
nlohmann::json obj;
|
||||
|
||||
auto string_indexed = -1;
|
||||
const auto keys = array.get_keys();
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
const auto is_int = key.is<int>();
|
||||
const auto is_string = key.is<std::string>();
|
||||
|
||||
if (string_indexed == -1)
|
||||
{
|
||||
string_indexed = is_string;
|
||||
}
|
||||
|
||||
if (!string_indexed && is_int)
|
||||
{
|
||||
const auto index = key.as<int>();
|
||||
obj[index] = gsc_to_json(array[index], print_id, ctx);
|
||||
}
|
||||
else if (string_indexed && is_string)
|
||||
{
|
||||
const auto key_str = key.as<std::string>();
|
||||
obj.emplace(key_str, gsc_to_json(array[key_str], print_id, ctx));
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
nlohmann::json object_to_json(const scripting::object& object, bool print_id, dump_context_t& ctx)
|
||||
{
|
||||
const auto id = object.get_entity_id();
|
||||
if (ctx.visited_objects.find(id) != ctx.visited_objects.end())
|
||||
{
|
||||
return utils::string::va("[struct reference %i]", id);
|
||||
}
|
||||
|
||||
ctx.visited_objects.insert(id);
|
||||
auto obj = nlohmann::json::object();
|
||||
|
||||
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, ctx));
|
||||
}
|
||||
|
||||
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(const scripting::script_value& value, bool print_id, dump_context_t& ctx)
|
||||
{
|
||||
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, ctx);
|
||||
}
|
||||
|
||||
if (value.is<scripting::array>())
|
||||
{
|
||||
return array_to_json(variable.u.uintValue, print_id, ctx);
|
||||
}
|
||||
|
||||
if (value.is<scripting::entity>())
|
||||
{
|
||||
return object_to_json(variable.u.uintValue, print_id, ctx);
|
||||
}
|
||||
|
||||
if (value.is<scripting::function>())
|
||||
{
|
||||
const auto function = value.as<scripting::function>();
|
||||
return utils::string::va("[[ %s ]]", function.get_name().data());
|
||||
}
|
||||
|
||||
if (variable.type == game::SCRIPT_NONE)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (variable.type == game::SCRIPT_CODEPOS)
|
||||
{
|
||||
const auto function = scripting::find_function(variable.u.codePosValue);
|
||||
return utils::string::va("[codepos: %s]", function.data());
|
||||
}
|
||||
|
||||
if (variable.type == game::SCRIPT_END)
|
||||
{
|
||||
const auto function = scripting::find_function(variable.u.codePosValue);
|
||||
return utils::string::va("[precodepos: %s]", function.data());
|
||||
}
|
||||
|
||||
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::boolean):
|
||||
return obj.get<bool>();
|
||||
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)
|
||||
{
|
||||
dump_context_t ctx;
|
||||
return gsc_to_json(value, false, ctx).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& args)
|
||||
{
|
||||
scripting::array array;
|
||||
|
||||
for (auto i = 0u; i < args.size(); i += 2)
|
||||
{
|
||||
if (i >= args.size() - 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto key = args[i].as<std::string>();
|
||||
array[key] = args[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& args)
|
||||
{
|
||||
auto indent = -1;
|
||||
auto print_id = false;
|
||||
|
||||
if (args.size() > 0)
|
||||
{
|
||||
indent = args[0].as<int>();
|
||||
}
|
||||
|
||||
if (args.size() > 1)
|
||||
{
|
||||
print_id = args[1].as<bool>();
|
||||
}
|
||||
|
||||
dump_context_t ctx;
|
||||
const auto json = gsc_to_json(value, print_id, ctx);
|
||||
auto dump = json.dump(indent);
|
||||
|
||||
if (dump.size() > max_str_len)
|
||||
{
|
||||
printf("^3WARNING: json dump longer than %i bytes, truncating!", max_str_len);
|
||||
dump.resize(max_str_len);
|
||||
}
|
||||
|
||||
return dump;
|
||||
}, "jsonserialize", "json::serialize");
|
||||
|
||||
gsc::function::add_multiple([](const std::string& file, const scripting::script_value& value, const scripting::variadic_args& va)
|
||||
{
|
||||
std::string file_name = file;
|
||||
|
||||
auto indent = -1;
|
||||
auto print_id = false;
|
||||
|
||||
if (!file_name.ends_with(".json"))
|
||||
{
|
||||
file_name.append(".json");
|
||||
}
|
||||
|
||||
if (va.size() > 0)
|
||||
{
|
||||
indent = va[0].as<int>();
|
||||
}
|
||||
|
||||
if (va.size() > 1)
|
||||
{
|
||||
print_id = va[1].as<bool>();
|
||||
}
|
||||
|
||||
dump_context_t ctx;
|
||||
const auto json = gsc_to_json(value, print_id, ctx);
|
||||
const auto dump = json.dump(indent);
|
||||
|
||||
return utils::io::write_file(file_name, dump);
|
||||
}, "jsondump", "json::dump");
|
||||
|
||||
gsc::function::add_multiple([](const scripting::variadic_args& args)
|
||||
{
|
||||
std::string buffer;
|
||||
|
||||
for (const auto& arg : args)
|
||||
{
|
||||
buffer.append(gsc_to_string(arg));
|
||||
buffer.append("\t");
|
||||
}
|
||||
|
||||
printf("%s\n", buffer.data());
|
||||
}, "jsonprint", "json::print");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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,49 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string filename;
|
||||
std::mutex mutex;
|
||||
|
||||
bool is_logging_enabled()
|
||||
{
|
||||
static const auto is_enabled = utils::flags::has_flag("log");
|
||||
return is_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
void log_hook(const std::string& buffer)
|
||||
{
|
||||
if (!is_logging_enabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard _(mutex);
|
||||
utils::io::write_file(filename, buffer, true);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_after_dvar_init([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
utils::io::create_directory("logs");
|
||||
filename = utils::string::va("logs/console-%s.log",
|
||||
utils::string::get_timestamp().data());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(logfile::component)
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
void log_hook(const std::string& buffer);
|
||||
}
|
||||
@@ -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,210 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "gsc.hpp"
|
||||
#include "io.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
|
||||
namespace network
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct socket_t
|
||||
{
|
||||
SOCKET socket;
|
||||
scripting::object handle;
|
||||
};
|
||||
|
||||
std::vector<socket_t> connected_sockets;
|
||||
std::unordered_map<std::uint32_t, SOCKET> handle_to_socket;
|
||||
|
||||
void poll_sockets()
|
||||
{
|
||||
for (const auto& inst : connected_sockets)
|
||||
{
|
||||
std::string data;
|
||||
data.reserve(0x1000);
|
||||
|
||||
auto result = 0;
|
||||
do
|
||||
{
|
||||
char buffer[0x1000]{};
|
||||
result = recv(inst.socket, buffer, sizeof(buffer), 0);
|
||||
if (result != -1 && result != 0)
|
||||
{
|
||||
data.append(buffer);
|
||||
}
|
||||
} while (result != -1 && result != 0);
|
||||
|
||||
if (data.size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto start_index = 0;
|
||||
scripting::array chunks;
|
||||
|
||||
for (auto i = 0u; i < data.size(); i++)
|
||||
{
|
||||
const auto is_eof = data[i] == EOF;
|
||||
const auto is_end = i == data.size() - 1;
|
||||
if (is_eof || is_end)
|
||||
{
|
||||
const auto chunk = data.substr(start_index, i - start_index);
|
||||
chunks.emplace_back(chunk);
|
||||
start_index = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
scripting::notify(inst.handle.get_entity_id(), "data", {chunks.get_raw()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
if (!utils::flags::has_flag("experimental-utils"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scripting::on_shutdown([]
|
||||
{
|
||||
for (const auto& inst : connected_sockets)
|
||||
{
|
||||
closesocket(inst.socket);
|
||||
}
|
||||
|
||||
connected_sockets.clear();
|
||||
handle_to_socket.clear();
|
||||
});
|
||||
|
||||
scheduler::loop(poll_sockets, scheduler::server_packet_loop);
|
||||
|
||||
gsc::function::add("network::connect", [](const std::string& type, const std::string& address, const int port)
|
||||
-> scripting::script_value
|
||||
{
|
||||
static std::unordered_map<std::string, int> socket_types =
|
||||
{
|
||||
{"tcp", SOCK_STREAM},
|
||||
{"udp", SOCK_DGRAM},
|
||||
};
|
||||
|
||||
const auto type_lower = utils::string::to_lower(type);
|
||||
const auto type_id = socket_types.find(type_lower);
|
||||
if (type_id == socket_types.end())
|
||||
{
|
||||
throw std::runtime_error("invalid socket type");
|
||||
}
|
||||
|
||||
const auto sock = socket(AF_INET, type_id->second, 0);
|
||||
|
||||
u_long mode = 1;
|
||||
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
|
||||
{
|
||||
closesocket(sock);
|
||||
return {};
|
||||
}
|
||||
|
||||
int flag = 1;
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int));
|
||||
|
||||
SOCKADDR_IN sin{};
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons(static_cast<std::uint16_t>(port));
|
||||
inet_pton(AF_INET, address.data(), &sin.sin_addr);
|
||||
|
||||
if (connect(sock, reinterpret_cast<SOCKADDR*>(&sin), sizeof(sin)) == SOCKET_ERROR)
|
||||
{
|
||||
const auto error = WSAGetLastError();
|
||||
if (error != WSAEWOULDBLOCK && error != WSAEINPROGRESS)
|
||||
{
|
||||
closesocket(sock);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
scripting::object handle;
|
||||
|
||||
socket_t socket_ref{};
|
||||
socket_ref.socket = sock;
|
||||
socket_ref.handle = handle;
|
||||
|
||||
const auto handle_id = handle.get_entity_id();
|
||||
handle_to_socket[handle_id] = sock;
|
||||
|
||||
connected_sockets.emplace_back(std::move(socket_ref));
|
||||
|
||||
return handle;
|
||||
});
|
||||
|
||||
gsc::function::add("network::send", [](const scripting::object& handle, std::string data)
|
||||
{
|
||||
const auto handle_id = handle.get_entity_id();
|
||||
const auto iter = handle_to_socket.find(handle_id);
|
||||
if (iter == handle_to_socket.end())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto sock = iter->second;
|
||||
data += EOF;
|
||||
return send(sock, data.data(), data.size(), 0);
|
||||
});
|
||||
|
||||
gsc::function::add("network::close", [](const scripting::object& handle)
|
||||
{
|
||||
const auto handle_id = handle.get_entity_id();
|
||||
const auto iter = handle_to_socket.find(handle_id);
|
||||
if (iter == handle_to_socket.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
closesocket(iter->second);
|
||||
handle_to_socket.erase(iter);
|
||||
|
||||
for (auto i = connected_sockets.begin(), end = connected_sockets.end(); i != end; ++i)
|
||||
{
|
||||
if (i->handle.get_entity_id() == handle_id)
|
||||
{
|
||||
connected_sockets.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
gsc::function::add("network::is_connected", [](const scripting::object& handle)
|
||||
{
|
||||
const auto handle_id = handle.get_entity_id();
|
||||
const auto iter = handle_to_socket.find(handle_id);
|
||||
if (iter == handle_to_socket.end())
|
||||
{
|
||||
return (int)0;
|
||||
}
|
||||
|
||||
char code{};
|
||||
int size = sizeof(code);
|
||||
getsockopt(iter->second, SOL_SOCKET, SO_ERROR, &code, &size);
|
||||
return static_cast<int>(code);
|
||||
});
|
||||
|
||||
gsc::function::add("network::get_last_error", []()
|
||||
{
|
||||
return WSAGetLastError();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(network::component)
|
||||
@@ -0,0 +1,211 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/scripting/stack_isolation.hpp"
|
||||
#include "gsc.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace notifies
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct notify_group
|
||||
{
|
||||
unsigned int owner_id;
|
||||
unsigned int notify_target;
|
||||
unsigned int main_notify;
|
||||
std::unordered_set<unsigned int> sub_notifies;
|
||||
};
|
||||
|
||||
using notify_groups_t = std::vector<notify_group>;
|
||||
|
||||
bool in_notify_queue;
|
||||
notify_groups_t notify_groups_queue;
|
||||
|
||||
utils::hook::detour vm_notify_hook;
|
||||
|
||||
void push_sl_string(unsigned int value)
|
||||
{
|
||||
game::VariableValue var{};
|
||||
var.type = game::SCRIPT_STRING;
|
||||
var.u.stringValue = value;
|
||||
scripting::push_value(var);
|
||||
}
|
||||
|
||||
void vm_notify_stub(game::scriptInstance_t inst, unsigned int notify_list_owner_id,
|
||||
unsigned int string_value, game::VariableValue* top)
|
||||
{
|
||||
vm_notify_hook.invoke<void>(inst, notify_list_owner_id, string_value, top);
|
||||
|
||||
if (in_notify_queue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto disconnect_str = *reinterpret_cast<unsigned short*>(0x24B71CE);
|
||||
|
||||
in_notify_queue = true;
|
||||
const auto _0 = gsl::finally([]
|
||||
{
|
||||
in_notify_queue = false;
|
||||
});
|
||||
|
||||
std::unordered_map<unsigned int, std::unordered_set<unsigned int>> notified;
|
||||
|
||||
for (auto i = notify_groups_queue.begin(); i != notify_groups_queue.end(); )
|
||||
{
|
||||
if (i->owner_id == notify_list_owner_id)
|
||||
{
|
||||
const auto should_notify = i->sub_notifies.find(string_value) != i->sub_notifies.end();
|
||||
const auto should_delete = string_value == disconnect_str || notified[i->notify_target].contains(i->main_notify);
|
||||
const auto object_type = game::scr_VarGlob->objectVariableValue[i->notify_target].w.type;
|
||||
|
||||
if (object_type != game::SCRIPT_FREE && should_notify)
|
||||
{
|
||||
scripting::stack_isolation _1;
|
||||
push_sl_string(string_value);
|
||||
game::Scr_NotifyId(inst, 0, i->notify_target, i->main_notify, 1);
|
||||
notified[i->notify_target].insert(i->main_notify);
|
||||
}
|
||||
|
||||
if (should_notify || should_delete)
|
||||
{
|
||||
i = notify_groups_queue.erase(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_notify_group(unsigned int id)
|
||||
{
|
||||
for (auto i = notify_groups_queue.begin(); i != notify_groups_queue.end(); )
|
||||
{
|
||||
if (i->owner_id == id)
|
||||
{
|
||||
i = notify_groups_queue.erase(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void free_variable_stub_1(utils::hook::assembler& a)
|
||||
{
|
||||
a.pushad();
|
||||
a.push(ebx);
|
||||
a.call(clear_notify_group);
|
||||
a.pop(ebx);
|
||||
a.popad();
|
||||
|
||||
a.movzx(ecx, word_ptr(edi, 2));
|
||||
a.xor_(edx, edx);
|
||||
a.jmp(0x550EA5);
|
||||
}
|
||||
|
||||
void free_variable_stub_2(utils::hook::assembler& a)
|
||||
{
|
||||
a.pushad();
|
||||
a.push(edi);
|
||||
a.call(clear_notify_group);
|
||||
a.pop(edi);
|
||||
a.popad();
|
||||
|
||||
a.mov(word_ptr(esi, 2), di);
|
||||
a.pop(edi);
|
||||
a.jmp(0x415EA2);
|
||||
}
|
||||
|
||||
void* client_connect_stub(int client_num, int script_pers_id)
|
||||
{
|
||||
const scripting::entity player = game::Scr_GetEntityId(game::SCRIPTINSTANCE_SERVER, client_num, 0, 0);
|
||||
scripting::notify(*game::levelEntityId, "direct_connect", {player});
|
||||
return utils::hook::invoke<void*>(0x41BE10, client_num, script_pers_id);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
if (!game::environment::t6zm())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vm_notify_hook.create(0x8F3620, vm_notify_stub);
|
||||
|
||||
utils::hook::jump(0x550E9F, utils::hook::assemble(free_variable_stub_1));
|
||||
utils::hook::jump(0x415E9D, utils::hook::assemble(free_variable_stub_2));
|
||||
|
||||
utils::hook::call(0x501F0F, client_connect_stub);
|
||||
|
||||
scripting::on_shutdown([]
|
||||
{
|
||||
notify_groups_queue.clear();
|
||||
});
|
||||
|
||||
gsc::function::add("createnotifygroup", [](const scripting::variadic_args& args)
|
||||
-> scripting::script_value
|
||||
{
|
||||
std::unordered_set<unsigned int> notifies;
|
||||
|
||||
const auto& entity = args[0].get_raw();
|
||||
if (entity.type != game::SCRIPT_OBJECT)
|
||||
{
|
||||
throw std::runtime_error("argument 1 must be a script object");
|
||||
}
|
||||
|
||||
const auto& entity_target = args[1].get_raw();
|
||||
if (entity_target.type != game::SCRIPT_OBJECT)
|
||||
{
|
||||
throw std::runtime_error("argument 1 must be a script object");
|
||||
}
|
||||
|
||||
const auto main_notify_str = args[2].as<std::string>();
|
||||
const auto main_notify = game::SL_GetString(main_notify_str.data(), 0);
|
||||
|
||||
for (auto i = 3u; i < args.size(); i++)
|
||||
{
|
||||
const auto& arg = args[i];
|
||||
if (arg.is<scripting::array>())
|
||||
{
|
||||
const auto arr = arg.as<scripting::array>();
|
||||
for (const auto& [key, value] : arr)
|
||||
{
|
||||
const auto str = value.as<std::string>();
|
||||
notifies.insert(game::SL_GetString(str.data(), 0));
|
||||
}
|
||||
}
|
||||
else if (arg.is<std::string>())
|
||||
{
|
||||
const auto str = arg.as<std::string>();
|
||||
notifies.insert(game::SL_GetString(str.data(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (notifies.size() <= 0)
|
||||
{
|
||||
throw std::runtime_error("must pass atleast 1 notify");
|
||||
}
|
||||
|
||||
notify_groups_queue.emplace_back(entity.u.uintValue, entity_target.u.uintValue,
|
||||
main_notify, notifies);
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(notifies::component)
|
||||
@@ -0,0 +1,72 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/scripting/safe_execution.hpp"
|
||||
#include "component/gsc.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace patches
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const game::dvar_t* sv_display_clan_tag;
|
||||
|
||||
char* cs_display_name_stub(const void* client_state, int type)
|
||||
{
|
||||
if (!sv_display_clan_tag->current.enabled)
|
||||
{
|
||||
type = 1;
|
||||
}
|
||||
|
||||
return utils::hook::invoke<char*>(SELECT(0x59AE90, 0x563AD0), client_state, type);
|
||||
}
|
||||
|
||||
void* bg_get_client_field_stub(int a1, const char* a2)
|
||||
{
|
||||
const auto result = utils::hook::invoke<void*>(0x5B4220, a1, a2);
|
||||
if (!result)
|
||||
{
|
||||
const auto err = utils::string::va("no client field named %s found", a2);
|
||||
game::Scr_Error(game::SCRIPTINSTANCE_SERVER, err, false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
patch();
|
||||
|
||||
if (game::environment::t6zm())
|
||||
{
|
||||
patch_zm();
|
||||
}
|
||||
}
|
||||
|
||||
void patch()
|
||||
{
|
||||
sv_display_clan_tag = game::Dvar_RegisterBool("sv_display_clan_tag", false, 0, "Display the clan tag in the game log");
|
||||
utils::hook::call(SELECT(0x820F1C, 0x81F91C), cs_display_name_stub);
|
||||
}
|
||||
|
||||
void patch_zm()
|
||||
{
|
||||
// Fix some common crashes, not necessary because of `patch()` but just to be sure
|
||||
|
||||
// sets an always null pointer :/
|
||||
utils::hook::nop(0x6FF36E, 0x2);
|
||||
|
||||
// related to custom perks and tombstone/electric chair on motd and possibly other contexts
|
||||
utils::hook::call(0x85842A, bg_get_client_field_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(patches::component)
|
||||
@@ -0,0 +1,185 @@
|
||||
#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
|
||||
{
|
||||
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>;
|
||||
|
||||
utils::hook::detour com_server_packet_event_hook;
|
||||
|
||||
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;
|
||||
std::atomic_bool killed = false;
|
||||
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(utils::hook::assembler& a)
|
||||
{
|
||||
a.pushad();
|
||||
a.call(execute_server);
|
||||
a.popad();
|
||||
|
||||
a.jmp(SELECT(0x49E910, 0x5001A0));
|
||||
}
|
||||
|
||||
void com_server_packet_event_stub()
|
||||
{
|
||||
execute(pipeline::server_packet_loop);
|
||||
com_server_packet_event_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 (!killed)
|
||||
{
|
||||
execute(pipeline::async);
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
});
|
||||
|
||||
utils::hook::jump(SELECT(0x4A59F7, 0x6AA2F7), utils::hook::assemble(server_frame_stub));
|
||||
com_server_packet_event_hook.create(SELECT(0x42F850, 0x549300), com_server_packet_event_stub);
|
||||
}
|
||||
|
||||
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
thread_pool.stop();
|
||||
killed = true;
|
||||
|
||||
if (thread.joinable())
|
||||
{
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scheduler::component)
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/thread_pool.hpp>
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
enum pipeline
|
||||
{
|
||||
server,
|
||||
server_packet_loop,
|
||||
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,283 @@
|
||||
#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;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour scr_add_class_field_hook;
|
||||
utils::hook::detour g_shutdown_game_hook;
|
||||
|
||||
void scr_add_class_field_stub(game::scriptInstance_t inst, unsigned int classnum, char const* name, unsigned int offset)
|
||||
{
|
||||
if (fields_table[classnum].find(name) == fields_table[classnum].end())
|
||||
{
|
||||
fields_table[classnum][name] = offset;
|
||||
}
|
||||
|
||||
scr_add_class_field_hook.invoke<void>(inst, classnum, name, offset);
|
||||
}
|
||||
|
||||
game::BuiltinMethodDef get_method(const std::string& name)
|
||||
{
|
||||
game::BuiltinMethodDef method{};
|
||||
|
||||
auto pName = name.data();
|
||||
int arg = 0;
|
||||
|
||||
const auto func = game::Scr_GetMethod(&pName, &arg, &arg, &arg);
|
||||
|
||||
if (func)
|
||||
{
|
||||
method.actionFunc = reinterpret_cast<script_function>(func);
|
||||
method.actionString = pName;
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
void load_functions()
|
||||
{
|
||||
for (auto i = 0; i < 278; i++)
|
||||
{
|
||||
const auto method = game::player_methods[i];
|
||||
method_map[method.actionString] = method;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 22; i++)
|
||||
{
|
||||
const auto method = game::scriptEnt_methods[i];
|
||||
method_map[method.actionString] = method;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 114; i++)
|
||||
{
|
||||
const auto method = game::scriptVehicle_methods[i];
|
||||
method_map[method.actionString] = method;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 29; i++)
|
||||
{
|
||||
const auto method = game::hudElem_methods[i];
|
||||
method_map[method.actionString] = method;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 29; i++)
|
||||
{
|
||||
const auto method = game::helicopter_methods[i];
|
||||
method_map[method.actionString] = method;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 109; i++)
|
||||
{
|
||||
const auto method = game::actor_methods[i];
|
||||
method_map[method.actionString] = method;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 95; i++)
|
||||
{
|
||||
const auto method = game::builtInCommon_methods[i];
|
||||
method_map[method.actionString] = method;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 286; i++)
|
||||
{
|
||||
const auto method = game::builtIn_methods[i];
|
||||
method_map[method.actionString] = method;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 334; i++)
|
||||
{
|
||||
const auto function = game::functions[i];
|
||||
function_map[function.actionString] = function;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 220; i++)
|
||||
{
|
||||
const auto function = game::common_functions[i];
|
||||
function_map[function.actionString] = function;
|
||||
}
|
||||
|
||||
method_map["notifyonplayercommand"] = get_method("notifyonplayercommand");
|
||||
}
|
||||
|
||||
utils::hook::detour scr_load_script_hook;
|
||||
|
||||
void extract_obj_functions(game::GSC_OBJ* obj)
|
||||
{
|
||||
const auto exported = reinterpret_cast<game::GSC_EXPORT_ITEM*>(&obj->magic[obj->exports_offset]);
|
||||
const auto count = obj->exports_count;
|
||||
auto iter = &obj->magic[obj->exports_offset + 8];
|
||||
|
||||
std::string filename = &obj->magic[obj->name];
|
||||
if (filename.ends_with(".gsc"))
|
||||
{
|
||||
filename = filename.substr(0, filename.size() - 4);
|
||||
}
|
||||
|
||||
std::string original = filename;
|
||||
auto filename_count = 0;
|
||||
while (script_function_table.find(filename) != script_function_table.end())
|
||||
{
|
||||
filename = utils::string::va("%s_%i", original.data(), filename_count++);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < count; i++)
|
||||
{
|
||||
const auto index = *reinterpret_cast<unsigned __int16*>(iter);
|
||||
const auto function = &obj->magic[index];
|
||||
const auto address = reinterpret_cast<char*>(reinterpret_cast<unsigned int>(obj) + exported[i].address);
|
||||
|
||||
script_function_table[filename][function] = address;
|
||||
script_function_table_sort[filename].push_back({function, address});
|
||||
|
||||
if (i == count - 1)
|
||||
{
|
||||
script_function_table[filename]["$end"] = &obj->magic[obj->fixup_offset];
|
||||
script_function_table_sort[filename].push_back({"$end", &obj->magic[obj->fixup_offset]});
|
||||
}
|
||||
|
||||
iter += 12;
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour scr_post_load_scripts_hook;
|
||||
unsigned int post_load_scripts_stub(unsigned int inst)
|
||||
{
|
||||
script_function_table.clear();
|
||||
script_function_table_sort.clear();
|
||||
|
||||
const auto script_count = *reinterpret_cast<unsigned int*>(SELECT(0x2DB9F18, 0x2D8A218));
|
||||
const auto scripts = reinterpret_cast<game::objFileInfo_t*>(SELECT(0x2DA2FE8, 0x2D732E8));
|
||||
|
||||
for (auto i = 0u; i < script_count; i++)
|
||||
{
|
||||
const auto obj = scripts[i].activeVersion;
|
||||
extract_obj_functions(obj);
|
||||
}
|
||||
|
||||
return scr_post_load_scripts_hook.invoke<unsigned int>(inst);
|
||||
}
|
||||
|
||||
std::vector<std::function<void()>> shutdown_callbacks;
|
||||
void g_shutdown_game_stub(const int free_scripts)
|
||||
{
|
||||
for (const auto& callback : shutdown_callbacks)
|
||||
{
|
||||
callback();
|
||||
}
|
||||
|
||||
return g_shutdown_game_hook.invoke<void>(free_scripts);
|
||||
}
|
||||
}
|
||||
|
||||
script_function find_function_ptr(const std::string& _name)
|
||||
{
|
||||
const auto name = utils::string::to_lower(_name);
|
||||
|
||||
if (function_map.find(name) != function_map.end())
|
||||
{
|
||||
const auto func = function_map[name].actionFunc;
|
||||
return reinterpret_cast<script_function>(func);
|
||||
}
|
||||
|
||||
if (method_map.find(name) != method_map.end())
|
||||
{
|
||||
const auto func = method_map[name].actionFunc;
|
||||
return reinterpret_cast<script_function>(func);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string find_function(const char* pos)
|
||||
{
|
||||
for (const auto& file : script_function_table_sort)
|
||||
{
|
||||
for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i)
|
||||
{
|
||||
const auto next = std::next(i);
|
||||
if (pos >= i->second && pos < next->second)
|
||||
{
|
||||
return utils::string::va("%s::%s", file.first.data(), i->first.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "unknown function";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> find_function_pair(const char* pos)
|
||||
{
|
||||
for (const auto& file : script_function_table_sort)
|
||||
{
|
||||
for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i)
|
||||
{
|
||||
const auto next = std::next(i);
|
||||
if (pos >= i->second && pos < next->second)
|
||||
{
|
||||
return {{file.first, i->first}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
const char* find_function_start(const char* pos)
|
||||
{
|
||||
for (const auto& file : script_function_table_sort)
|
||||
{
|
||||
for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i)
|
||||
{
|
||||
const auto next = std::next(i);
|
||||
if (pos >= i->second && pos < next->second)
|
||||
{
|
||||
return i->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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(0x60DCF0, 0x688A40), g_shutdown_game_stub);
|
||||
|
||||
scr_add_class_field_hook.create(SELECT(0x6B7620, 0x438AD0), scr_add_class_field_stub);
|
||||
scr_post_load_scripts_hook.create(SELECT(0x642EB0, 0x425F80), post_load_scripts_stub);
|
||||
|
||||
load_functions();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scripting::component)
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
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<std::string, game::BuiltinMethodDef> method_map;
|
||||
extern std::unordered_map<std::string, game::BuiltinFunctionDef> function_map;
|
||||
|
||||
using script_function = void(*)(game::scr_entref_t);
|
||||
|
||||
script_function find_function_ptr(const std::string& name);
|
||||
std::string find_function(const char* pos);
|
||||
const char* find_function_start(const char* pos);
|
||||
std::optional<std::pair<std::string, std::string>> find_function_pair(const char* pos);
|
||||
|
||||
void on_shutdown(const std::function<void()>& callback);
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
#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_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,418 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "gsc.hpp"
|
||||
#include "game/structs.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#define TURRET_DUMP_FIELD(__field__) \
|
||||
data[#__field__] = turret->__field__; \
|
||||
|
||||
#define TURRET_ADD_FIELD_INTERNAL(__name__, __getter__, __setter__) \
|
||||
{utils::string::to_lower(#__name__), turret_field{offsetof(game::TurretInfo, __name__), __getter__, __setter__}} \
|
||||
|
||||
#define TURRET_ADD_FIELD_GETTER(__name__, __getter__) \
|
||||
TURRET_ADD_FIELD_INTERNAL(__name__, __getter__, get_default_setter(dummy_info.__name__, #__name__, offsetof(game::TurretInfo, __name__))) \
|
||||
|
||||
#define TURRET_ADD_FIELD_SETTER(__name__, __setter__) \
|
||||
TURRET_ADD_FIELD_INTERNAL(__name__, get_default_getter(dummy_info.__name__, offsetof(game::TurretInfo, __name__)), __setter__) \
|
||||
|
||||
#define TURRET_ADD_FIELD(__name__) \
|
||||
TURRET_ADD_FIELD_INTERNAL(__name__, get_default_getter(dummy_info.__name__, offsetof(game::TurretInfo, __name__)), get_default_setter(dummy_info.__name__, #__name__, offsetof(game::TurretInfo, __name__))) \
|
||||
|
||||
#define NEW_MAX_TURRETS 255
|
||||
|
||||
namespace turret
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::TurretInfo turret_info_store_relocated[NEW_MAX_TURRETS];
|
||||
|
||||
game::TurretInfo dummy_info{};
|
||||
|
||||
using getter_t = std::function<scripting::script_value(game::TurretInfo* info)>;
|
||||
using setter_t = std::function<void(game::TurretInfo* info, const scripting::function_argument& value)>;
|
||||
|
||||
struct turret_field
|
||||
{
|
||||
std::size_t offset;
|
||||
getter_t getter;
|
||||
setter_t setter;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
getter_t get_default_getter([[maybe_unused]] T, const size_t offset)
|
||||
{
|
||||
return [=](game::TurretInfo* info)
|
||||
{
|
||||
return *reinterpret_cast<T*>(reinterpret_cast<size_t>(info) + offset);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_same<T, float*>::value, setter_t>::type
|
||||
get_default_setter([[maybe_unused]] T, const std::string& name, const size_t offset)
|
||||
{
|
||||
return [=](game::TurretInfo* info, const scripting::function_argument& value)
|
||||
{
|
||||
if (!value.is<T>())
|
||||
{
|
||||
const auto type_name = value.type_name();
|
||||
throw std::runtime_error(utils::string::va("type '%s' is not valid for field '%s'", type_name.data(), name.data()));
|
||||
}
|
||||
|
||||
const auto vec = value.as<scripting::vector>();
|
||||
const auto val = reinterpret_cast<float*>(reinterpret_cast<size_t>(info) + offset);
|
||||
val[0] = vec.get_x();
|
||||
val[1] = vec.get_y();
|
||||
val[2] = vec.get_z();
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_same<T, float*>::value, setter_t>::type
|
||||
get_default_setter([[maybe_unused]] T, const std::string& name, const size_t offset)
|
||||
{
|
||||
return [=](game::TurretInfo* info, const scripting::function_argument& value)
|
||||
{
|
||||
if (!value.is<T>())
|
||||
{
|
||||
const auto type_name = value.type_name();
|
||||
throw std::runtime_error(utils::string::va("type '%s' is not valid for field '%s'", type_name.data(), name.data()));
|
||||
}
|
||||
|
||||
*reinterpret_cast<T*>(reinterpret_cast<size_t>(info) + offset) = value.as<T>();
|
||||
};
|
||||
}
|
||||
|
||||
scripting::script_value turret_get_team(game::TurretInfo* info)
|
||||
{
|
||||
return utils::hook::invoke<const char*>(0x4D7A30, info->eTeam);
|
||||
}
|
||||
|
||||
void turret_set_team(game::TurretInfo* info, const scripting::function_argument& value)
|
||||
{
|
||||
const auto str = value.as<std::string>();
|
||||
const auto team_names = reinterpret_cast<const char**>(0xD39970);
|
||||
for (auto i = 0; i < game::TEAM_NUM_TEAMS; i++)
|
||||
{
|
||||
if (team_names[i] == str)
|
||||
{
|
||||
info->eTeam = static_cast<game::team_t>(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error(utils::string::va("invalid team '%s'", str.data()));
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, turret_field> turret_fields =
|
||||
{
|
||||
TURRET_ADD_FIELD(inuse),
|
||||
TURRET_ADD_FIELD(state),
|
||||
TURRET_ADD_FIELD(flags),
|
||||
TURRET_ADD_FIELD(fireTime),
|
||||
TURRET_ADD_FIELD(manualTarget.infoIndex),
|
||||
TURRET_ADD_FIELD(manualTarget.number),
|
||||
TURRET_ADD_FIELD(target.infoIndex),
|
||||
TURRET_ADD_FIELD(target.number),
|
||||
TURRET_ADD_FIELD(targetPos),
|
||||
TURRET_ADD_FIELD(targetTime),
|
||||
TURRET_ADD_FIELD(missOffsetNormalized),
|
||||
TURRET_ADD_FIELD(arcmin[0]),
|
||||
TURRET_ADD_FIELD(arcmin[1]),
|
||||
TURRET_ADD_FIELD(arcmax[0]),
|
||||
TURRET_ADD_FIELD(arcmax[1]),
|
||||
TURRET_ADD_FIELD(initialYawmin),
|
||||
TURRET_ADD_FIELD(initialYawmax),
|
||||
TURRET_ADD_FIELD(forwardAngleDot),
|
||||
TURRET_ADD_FIELD(dropPitch),
|
||||
TURRET_ADD_FIELD(scanningPitch),
|
||||
TURRET_ADD_FIELD(convergenceTime[0]),
|
||||
TURRET_ADD_FIELD(convergenceTime[1]),
|
||||
TURRET_ADD_FIELD(suppressTime),
|
||||
TURRET_ADD_FIELD(maxRangeSquared),
|
||||
TURRET_ADD_FIELD(detachSentient.infoIndex),
|
||||
TURRET_ADD_FIELD(detachSentient.number),
|
||||
TURRET_ADD_FIELD(stance),
|
||||
TURRET_ADD_FIELD(prevStance),
|
||||
TURRET_ADD_FIELD(fireSndDelay),
|
||||
TURRET_ADD_FIELD(accuracy),
|
||||
TURRET_ADD_FIELD(userOrigin),
|
||||
TURRET_ADD_FIELD(prevSentTarget),
|
||||
TURRET_ADD_FIELD(aiSpread),
|
||||
TURRET_ADD_FIELD(playerSpread),
|
||||
TURRET_ADD_FIELD_INTERNAL(eTeam, turret_get_team, turret_set_team),
|
||||
TURRET_ADD_FIELD(heatVal),
|
||||
TURRET_ADD_FIELD(overheating),
|
||||
TURRET_ADD_FIELD(fireBarrel),
|
||||
TURRET_ADD_FIELD(scanSpeed),
|
||||
TURRET_ADD_FIELD(scanDecelYaw),
|
||||
TURRET_ADD_FIELD(scanPauseTime),
|
||||
TURRET_ADD_FIELD(originError),
|
||||
TURRET_ADD_FIELD(anglesError),
|
||||
TURRET_ADD_FIELD(pitchCap),
|
||||
TURRET_ADD_FIELD(triggerDown),
|
||||
TURRET_ADD_FIELD(fireSnd),
|
||||
TURRET_ADD_FIELD(fireSndPlayer),
|
||||
TURRET_ADD_FIELD(startFireSnd),
|
||||
TURRET_ADD_FIELD(startFireSndPlayer),
|
||||
TURRET_ADD_FIELD(loopFireEnd),
|
||||
TURRET_ADD_FIELD(loopFireEndPlayer),
|
||||
TURRET_ADD_FIELD(rotateLoopSnd),
|
||||
TURRET_ADD_FIELD(rotateLoopSndPlayer),
|
||||
TURRET_ADD_FIELD(rotateStopSnd),
|
||||
TURRET_ADD_FIELD(rotateStopSndPlayer),
|
||||
TURRET_ADD_FIELD(sndIsFiring),
|
||||
TURRET_ADD_FIELD(targetOffset),
|
||||
TURRET_ADD_FIELD(onTargetAngle),
|
||||
TURRET_ADD_FIELD(previousAngles),
|
||||
TURRET_ADD_FIELD(previousAngles),
|
||||
};
|
||||
|
||||
utils::hook::detour bg_get_weapon_def_hook;
|
||||
game::WeaponDef* bg_get_weapon_def_stub(const unsigned int weapon)
|
||||
{
|
||||
auto asset = bg_get_weapon_def_hook.invoke<game::WeaponDef*>(weapon);
|
||||
|
||||
if (asset->maxTurnSpeed[0] == 0.f && asset->maxTurnSpeed[1] == 0.f)
|
||||
{
|
||||
asset->maxTurnSpeed[0] = 360.f;
|
||||
asset->maxTurnSpeed[1] = 360.f;
|
||||
asset->minTurnSpeed[0] = 1.f;
|
||||
asset->minTurnSpeed[1] = 1.f;
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
void g_init_turrets_stub()
|
||||
{
|
||||
for (int i = 0; i < NEW_MAX_TURRETS; ++i)
|
||||
{
|
||||
turret_info_store_relocated[i].inuse = 0;
|
||||
}
|
||||
|
||||
// level.turrets = turret_info_store_relocated;
|
||||
utils::hook::set<game::TurretInfo*>(0x233CAB8, turret_info_store_relocated);
|
||||
}
|
||||
|
||||
auto turret_index = 0;
|
||||
const auto turret_index_ptr = &turret_index;
|
||||
|
||||
game::TurretInfo* g_spawn_turret_relocate()
|
||||
{
|
||||
turret_index = 0;
|
||||
game::TurretInfo* turret_info{};
|
||||
|
||||
for (auto i = 0; i < NEW_MAX_TURRETS; ++i)
|
||||
{
|
||||
turret_info = &turret_info_store_relocated[i];
|
||||
if (!turret_info->inuse)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
turret_index++;
|
||||
}
|
||||
|
||||
return turret_info;
|
||||
}
|
||||
|
||||
void __declspec(naked) g_spawn_turret_stub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
push esi;
|
||||
pushad;
|
||||
call g_spawn_turret_relocate;
|
||||
mov[esp + 0x20], eax;
|
||||
popad;
|
||||
pop esi;
|
||||
mov ecx, [turret_index_ptr];
|
||||
push 0x60AE90;
|
||||
retn;
|
||||
}
|
||||
}
|
||||
|
||||
game::gentity_s* g_spawn_turret_static_entity_stub(int /*unk1*/)
|
||||
{
|
||||
return utils::hook::invoke<game::gentity_s*>(0x54FCC0);
|
||||
}
|
||||
|
||||
void reallocate_turrets()
|
||||
{
|
||||
// Modify G_InitTurrets to initialize more tuurets
|
||||
utils::hook::jump(0x61E850, g_init_turrets_stub);
|
||||
// Modify G_SpawnTurret to check for a larger amount of turretInfoStore
|
||||
utils::hook::jump(0x60AE3B, g_spawn_turret_stub);
|
||||
// Modify G_SpawnturetStaticEntity to call G_Spawn instead
|
||||
utils::hook::jump(0x6E1B00, g_spawn_turret_static_entity_stub);
|
||||
// Patch G_SpawnTurret com_error to happen at a higher limit (255)
|
||||
utils::hook::set<std::uint8_t>(0x60AE92, NEW_MAX_TURRETS);
|
||||
|
||||
// Patch level.num_gentities to min of 78 to not preallocate turret slots
|
||||
utils::hook::set<int>(0x67EEA2 + 6, 78);
|
||||
// Patch SV_LocateGameData arg so sv.num_entities is the same as the above
|
||||
utils::hook::set<std::uint8_t>(0x67EE9F + 1, 78);
|
||||
// G_FreeEntity remove turrets from slot calculations
|
||||
utils::hook::set<std::uint8_t>(0x6658E7 + 2, 78);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
if (!game::environment::t6zm())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gsc::method::add("setturretfield", [](const scripting::entity& entity, const std::string& field, const scripting::variadic_args& args)
|
||||
{
|
||||
const auto ent = entity.get_entity_reference();
|
||||
if (ent.classnum != 0)
|
||||
{
|
||||
throw std::runtime_error("Invalid entity");
|
||||
}
|
||||
|
||||
const auto turret = game::g_entities[ent.entnum].pTurretInfo;
|
||||
if (turret == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Not a valid turret entity");
|
||||
}
|
||||
const auto field_name = utils::string::to_lower(field);
|
||||
const auto itr = turret_fields.find(field_name);
|
||||
|
||||
if (itr == turret_fields.end())
|
||||
{
|
||||
throw std::runtime_error(utils::string::va("Invalid field %s", field_name.data()));
|
||||
}
|
||||
|
||||
itr->second.setter(turret, args[0]);
|
||||
});
|
||||
|
||||
gsc::method::add("getturretfield", [](const scripting::entity& entity, const std::string& field)
|
||||
{
|
||||
const auto ent = entity.get_entity_reference();
|
||||
if (ent.classnum != 0)
|
||||
{
|
||||
throw std::runtime_error("Invalid entity");
|
||||
}
|
||||
|
||||
const auto turret = game::g_entities[ent.entnum].pTurretInfo;
|
||||
if (turret == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Not a valid turret entity");
|
||||
}
|
||||
|
||||
const auto field_name = utils::string::to_lower(field);
|
||||
const auto itr = turret_fields.find(field_name);
|
||||
|
||||
if (itr == turret_fields.end())
|
||||
{
|
||||
throw std::runtime_error(utils::string::va("Invalid field %s", field_name.data()));
|
||||
}
|
||||
|
||||
return itr->second.getter(turret);
|
||||
});
|
||||
|
||||
gsc::function::add("dumpturret", [](const scripting::entity& entity, const std::string& name)
|
||||
{
|
||||
const auto ent = entity.get_entity_reference();
|
||||
if (ent.classnum != 0)
|
||||
{
|
||||
throw std::runtime_error("Invalid entity");
|
||||
}
|
||||
|
||||
const auto turret = game::g_entities[ent.entnum].pTurretInfo;
|
||||
if (turret == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Not a valid turret entity");
|
||||
}
|
||||
|
||||
nlohmann::ordered_json data;
|
||||
|
||||
TURRET_DUMP_FIELD(inuse);
|
||||
TURRET_DUMP_FIELD(state);
|
||||
TURRET_DUMP_FIELD(flags);
|
||||
TURRET_DUMP_FIELD(fireTime);
|
||||
data["manualTarget"]["infoIndex"] = turret->manualTarget.infoIndex;
|
||||
data["manualTarget"]["number"] = turret->manualTarget.number;
|
||||
data["target"]["infoIndex"] = turret->target.infoIndex;
|
||||
data["target"]["number"] = turret->target.number;
|
||||
TURRET_DUMP_FIELD(targetPos);
|
||||
TURRET_DUMP_FIELD(targetTime);
|
||||
TURRET_DUMP_FIELD(missOffsetNormalized);
|
||||
TURRET_DUMP_FIELD(arcmin);
|
||||
TURRET_DUMP_FIELD(arcmax);
|
||||
TURRET_DUMP_FIELD(initialYawmin);
|
||||
TURRET_DUMP_FIELD(initialYawmax);
|
||||
TURRET_DUMP_FIELD(forwardAngleDot);
|
||||
TURRET_DUMP_FIELD(dropPitch);
|
||||
TURRET_DUMP_FIELD(scanningPitch);
|
||||
TURRET_DUMP_FIELD(convergenceTime);
|
||||
TURRET_DUMP_FIELD(suppressTime);
|
||||
TURRET_DUMP_FIELD(maxRangeSquared);
|
||||
data["detachSentient"]["infoIndex"] = turret->detachSentient.infoIndex;
|
||||
data["detachSentient"]["number"] = turret->detachSentient.number;
|
||||
TURRET_DUMP_FIELD(stance);
|
||||
TURRET_DUMP_FIELD(prevStance);
|
||||
TURRET_DUMP_FIELD(fireSndDelay);
|
||||
TURRET_DUMP_FIELD(accuracy);
|
||||
TURRET_DUMP_FIELD(userOrigin);
|
||||
TURRET_DUMP_FIELD(prevSentTarget);
|
||||
TURRET_DUMP_FIELD(aiSpread);
|
||||
TURRET_DUMP_FIELD(playerSpread);
|
||||
TURRET_DUMP_FIELD(eTeam);
|
||||
TURRET_DUMP_FIELD(heatVal);
|
||||
TURRET_DUMP_FIELD(overheating);
|
||||
TURRET_DUMP_FIELD(fireBarrel);
|
||||
TURRET_DUMP_FIELD(scanSpeed);
|
||||
TURRET_DUMP_FIELD(scanDecelYaw);
|
||||
TURRET_DUMP_FIELD(scanPauseTime);
|
||||
TURRET_DUMP_FIELD(originError);
|
||||
TURRET_DUMP_FIELD(anglesError);
|
||||
TURRET_DUMP_FIELD(pitchCap);
|
||||
TURRET_DUMP_FIELD(triggerDown);
|
||||
TURRET_DUMP_FIELD(fireSnd);
|
||||
TURRET_DUMP_FIELD(fireSndPlayer);
|
||||
TURRET_DUMP_FIELD(startFireSnd);
|
||||
TURRET_DUMP_FIELD(startFireSndPlayer);
|
||||
TURRET_DUMP_FIELD(loopFireEnd);
|
||||
TURRET_DUMP_FIELD(loopFireEndPlayer);
|
||||
TURRET_DUMP_FIELD(rotateLoopSnd);
|
||||
TURRET_DUMP_FIELD(rotateLoopSndPlayer);
|
||||
TURRET_DUMP_FIELD(rotateStopSnd);
|
||||
TURRET_DUMP_FIELD(rotateStopSndPlayer);
|
||||
TURRET_DUMP_FIELD(sndIsFiring);
|
||||
TURRET_DUMP_FIELD(targetOffset);
|
||||
TURRET_DUMP_FIELD(onTargetAngle);
|
||||
TURRET_DUMP_FIELD(turretRotateState);
|
||||
TURRET_DUMP_FIELD(previousAngles);
|
||||
|
||||
const std::string buffer = data.dump(4);
|
||||
const std::string turret_entity_path = utils::string::va("entity_dumps/turrets/%s.json", name.data());
|
||||
|
||||
return utils::io::write_file(turret_entity_path, buffer, false);
|
||||
});
|
||||
|
||||
// Check for whether a weapon can be spawned as a turret
|
||||
utils::hook::nop(0x60AF4E, 5);
|
||||
|
||||
// Don't use weapon def value for turrets turnspeed
|
||||
bg_get_weapon_def_hook.create(0x5906B0, bg_get_weapon_def_stub);
|
||||
|
||||
// Reallocate turrets to NEW_MAX_TURRETS
|
||||
reallocate_turrets();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(turret::component)
|
||||
@@ -0,0 +1,78 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "plugin.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,85 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace game
|
||||
{
|
||||
gamemode current = reinterpret_cast<const char*>(0xC2F028) == "multiplayer"s
|
||||
? gamemode::multiplayer
|
||||
: gamemode::zombies;
|
||||
|
||||
namespace environment
|
||||
{
|
||||
bool t6mp()
|
||||
{
|
||||
return current == gamemode::multiplayer;
|
||||
}
|
||||
|
||||
bool t6zm()
|
||||
{
|
||||
return current == gamemode::zombies;
|
||||
}
|
||||
}
|
||||
|
||||
gentity_s* GetEntity(scr_entref_t entref)
|
||||
{
|
||||
if (entref.classnum != 0)
|
||||
{
|
||||
game::Scr_ObjectError(SCRIPTINSTANCE_SERVER, "Not an entity");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
assert(entref.entnum < (1 << 10));
|
||||
|
||||
return &game::g_entities[entref.entnum];
|
||||
}
|
||||
|
||||
scr_entref_t Scr_GetEntityIdRef(unsigned int ent_id)
|
||||
{
|
||||
scr_entref_t entref{};
|
||||
|
||||
const auto v2 = &game::scr_VarGlob->objectVariableValue[ent_id];
|
||||
|
||||
entref.entnum = v2->u.f.next;
|
||||
entref.classnum = gsl::narrow_cast<unsigned short>(v2->w.classnum >> 8);
|
||||
|
||||
return entref;
|
||||
}
|
||||
|
||||
void Scr_TerminateWaitThread(scriptInstance_t inst, unsigned int local_id, unsigned int start_local_id)
|
||||
{
|
||||
static const auto func = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.pushad();
|
||||
a.mov(esi, dword_ptr(esp, 0x24));
|
||||
a.push(dword_ptr(esp, 0x28));
|
||||
a.push(dword_ptr(esp, 0x2C));
|
||||
a.call(SELECT(0x8F4620, 0x8F3380));
|
||||
a.add(esp, 0x8);
|
||||
a.popad();
|
||||
|
||||
a.ret();
|
||||
});
|
||||
|
||||
utils::hook::invoke<void>(func, inst, local_id, start_local_id);
|
||||
}
|
||||
|
||||
void Scr_TerminateWaittillThread(scriptInstance_t inst, unsigned int local_id, unsigned int start_local_id)
|
||||
{
|
||||
static const auto func = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.pushad();
|
||||
a.mov(esi, dword_ptr(esp, 0x24));
|
||||
a.mov(edi, dword_ptr(esp, 0x28));
|
||||
a.push(dword_ptr(esp, 0x2C));
|
||||
a.call(SELECT(0x8F4750, 0x8F34B0));
|
||||
a.add(esp, 0x4);
|
||||
a.popad();
|
||||
|
||||
a.ret();
|
||||
});
|
||||
|
||||
utils::hook::invoke<void>(func, inst, local_id, start_local_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "structs.hpp"
|
||||
|
||||
#define SELECT(mp, zm) (game::environment::t6mp() ? mp : zm)
|
||||
|
||||
namespace game
|
||||
{
|
||||
enum gamemode
|
||||
{
|
||||
none,
|
||||
multiplayer,
|
||||
zombies
|
||||
};
|
||||
|
||||
extern gamemode current;
|
||||
|
||||
namespace environment
|
||||
{
|
||||
bool t6mp();
|
||||
bool t6zm();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class symbol
|
||||
{
|
||||
public:
|
||||
symbol(const size_t t6mp, const size_t t6zm)
|
||||
: t6mp_(reinterpret_cast<T*>(t6mp))
|
||||
, t6zm_(reinterpret_cast<T*>(t6zm))
|
||||
{
|
||||
}
|
||||
|
||||
T* get() const
|
||||
{
|
||||
if (environment::t6mp())
|
||||
{
|
||||
return t6mp_;
|
||||
}
|
||||
|
||||
return t6zm_;
|
||||
}
|
||||
|
||||
void set(const size_t ptr)
|
||||
{
|
||||
this->t6mp_ = reinterpret_cast<T*>(ptr);
|
||||
this->t6zm_ = reinterpret_cast<T*>(ptr);
|
||||
}
|
||||
|
||||
operator T* () const
|
||||
{
|
||||
return this->get();
|
||||
}
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
return this->get();
|
||||
}
|
||||
|
||||
private:
|
||||
T* t6mp_;
|
||||
T* t6zm_;
|
||||
};
|
||||
|
||||
gentity_s* GetEntity(scr_entref_t entref);
|
||||
scr_entref_t Scr_GetEntityIdRef(unsigned int entId);
|
||||
void Scr_TerminateWaitThread(scriptInstance_t inst, unsigned int localId, unsigned int startLocalId);
|
||||
void Scr_TerminateWaittillThread(scriptInstance_t inst, unsigned int localId, unsigned int startLocalId);
|
||||
}
|
||||
|
||||
#include "symbols.hpp"
|
||||
@@ -0,0 +1,354 @@
|
||||
#include <stdinc.hpp>
|
||||
|
||||
#include "array.hpp"
|
||||
#include "execution.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
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_ = make_array();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
std::vector<script_value> result;
|
||||
|
||||
auto current = game::scr_VarGlob->objectVariableChildren[this->id_].firstChild;
|
||||
|
||||
while (current)
|
||||
{
|
||||
const auto var = game::scr_VarGlob->childVariableValue[current];
|
||||
|
||||
if (var.type == game::SCRIPT_NONE)
|
||||
{
|
||||
current = var.nextSibling;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto string_value = static_cast<std::uint8_t>(var.name_lo) + (var.k.keys.name_hi << 8);
|
||||
const auto* str = game::SL_ConvertToString(string_value);
|
||||
|
||||
script_value key;
|
||||
if (string_value < 0x40000 && str)
|
||||
{
|
||||
key = str;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = (string_value - 0x800000) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
result.push_back(key);
|
||||
|
||||
current = var.nextSibling;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::uint32_t array::size() const
|
||||
{
|
||||
return 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::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, (index - 0x800000) & 0xFFFFFF);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 std::string& key) const
|
||||
{
|
||||
const auto string_value = game::SL_GetString(key.data(), 0);
|
||||
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = game::scr_VarGlob->childVariableValue[variable_id];
|
||||
game::VariableValue variable{};
|
||||
variable.u = value.u.u;
|
||||
variable.type = static_cast<game::scriptType_e>(value.type);
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
script_value array::get(const std::uint32_t index) const
|
||||
{
|
||||
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, (index - 0x800000) & 0xFFFFFF);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = game::scr_VarGlob->childVariableValue[variable_id];
|
||||
game::VariableValue variable{};
|
||||
variable.u = value.u.u;
|
||||
variable.type = static_cast<game::scriptType_e>(value.type);
|
||||
|
||||
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 variable_id = this->get_value_id(key);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& value_raw = value.get_raw();
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id];
|
||||
game::VariableValue variable_value{};
|
||||
variable_value.type = variable->type;
|
||||
variable_value.u = variable->u.u;
|
||||
|
||||
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_raw);
|
||||
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable_value.type, variable_value.u);
|
||||
|
||||
variable->type = gsl::narrow_cast<char>(value_raw.type);
|
||||
variable->u.u = value_raw.u;
|
||||
}
|
||||
|
||||
void array::set(const std::uint32_t index, const script_value& value) const
|
||||
{
|
||||
const auto variable_id = this->get_value_id(index);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& value_raw = value.get_raw();
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id];
|
||||
game::VariableValue variable_value{};
|
||||
variable_value.type = variable->type;
|
||||
variable_value.u = variable->u.u;
|
||||
|
||||
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_raw);
|
||||
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable_value.type, variable_value.u);
|
||||
|
||||
variable->type = gsl::narrow_cast<char>(value_raw.type);
|
||||
variable->u.u = value_raw.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);
|
||||
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::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, (index - 0x800000) & 0xFFFFFF);
|
||||
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,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "script_value.hpp"
|
||||
#include "container_iterator.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class array;
|
||||
|
||||
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(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,123 @@
|
||||
#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};
|
||||
}
|
||||
|
||||
return game::Scr_GetEntityIdRef(this->get_entity_id());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
script_value entity::call(const std::string& name, const std::vector<script_value>& arguments) const
|
||||
{
|
||||
return call_function(name, *this, arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#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;
|
||||
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments = {}) 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,210 @@
|
||||
#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::Scr_NotifyId(game::SCRIPTINSTANCE_SERVER, 0, entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount);
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const auto function = find_function_ptr(name);
|
||||
if (!function)
|
||||
{
|
||||
throw std::runtime_error("Unknown function '" + name + "'");
|
||||
}
|
||||
|
||||
stack_isolation _;
|
||||
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
if (!safe_execution::call(function, entref))
|
||||
{
|
||||
const auto error = reinterpret_cast<const char*>(SELECT(0x2E27C70, 0x2DF7F70));
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
|
||||
return get_return_value();
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, entity(), arguments);
|
||||
}
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, arguments);
|
||||
}
|
||||
|
||||
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_array()
|
||||
{
|
||||
unsigned int index = 0;
|
||||
const auto variable = game::AllocVariable(game::SCRIPTINSTANCE_SERVER, &index);
|
||||
variable->w.type = game::SCRIPT_ARRAY;
|
||||
variable->u.f.prev = 0;
|
||||
variable->u.f.next = 0;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
unsigned int make_object()
|
||||
{
|
||||
unsigned int index = 0;
|
||||
const auto variable = game::AllocVariable(game::SCRIPTINSTANCE_SERVER, &index);
|
||||
variable->w.type = game::SCRIPT_STRUCT;
|
||||
variable->u.f.prev = 0;
|
||||
|
||||
return index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#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 call_function(const std::string& name, const std::vector<script_value>& arguments);
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T = script_value>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments = {});
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call<script_value>(name, arguments).as<T>();
|
||||
}
|
||||
|
||||
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_array();
|
||||
unsigned int make_object();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#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
|
||||
{
|
||||
return scripting::find_function(this->pos_);
|
||||
}
|
||||
|
||||
script_value function::call(const entity& self, std::vector<script_value> arguments) const
|
||||
{
|
||||
if (this->pos_ == 0)
|
||||
{
|
||||
throw std::runtime_error("function pos not set");
|
||||
}
|
||||
|
||||
return exec_ent_thread(self, this->pos_, arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "entity.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class function
|
||||
{
|
||||
public:
|
||||
function() = default;
|
||||
function(const char*);
|
||||
|
||||
script_value get_raw() const;
|
||||
const char* get_pos() const;
|
||||
std::string get_name() const;
|
||||
|
||||
script_value call(const entity& self, std::vector<script_value> arguments) const;
|
||||
|
||||
script_value operator()(const entity& self, std::vector<script_value> arguments) const
|
||||
{
|
||||
return this->call(self, arguments);
|
||||
}
|
||||
|
||||
script_value operator()(std::vector<script_value> arguments) const
|
||||
{
|
||||
return this->call(*game::levelEntityId, arguments);
|
||||
}
|
||||
|
||||
script_value operator()() const
|
||||
{
|
||||
return this->call(*game::levelEntityId, {});
|
||||
}
|
||||
private:
|
||||
const char* pos_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
#include <stdinc.hpp>
|
||||
|
||||
#include "object.hpp"
|
||||
#include "execution.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
object_value::object_value(const object* object, const std::string& key)
|
||||
: object_(object)
|
||||
, key_(key)
|
||||
{
|
||||
const auto value = this->object_->get(key);
|
||||
this->script_value::operator=(value);
|
||||
}
|
||||
|
||||
void object_value::operator=(const script_value& value)
|
||||
{
|
||||
this->object_->set(this->key_, value);
|
||||
this->script_value::operator=(value);
|
||||
}
|
||||
|
||||
object::object(const std::uint32_t 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_ = make_object();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void object::iterate_keys(const std::function<bool(const std::string& key)>& callback) const
|
||||
{
|
||||
auto current = game::scr_VarGlob->objectVariableChildren[this->id_].firstChild;
|
||||
|
||||
while (current)
|
||||
{
|
||||
const auto var = game::scr_VarGlob->childVariableValue[current];
|
||||
const auto string_id = static_cast<std::uint8_t>(var.name_lo) + (var.k.keys.name_hi << 8);
|
||||
|
||||
// pluto has this table relocated
|
||||
static const auto max_string_id = *reinterpret_cast<int*>(SELECT(0x6927A1 + 2, 0x51A9A1 + 2));
|
||||
if (string_id < max_string_id)
|
||||
{
|
||||
static const auto address = *reinterpret_cast<unsigned int*>(SELECT(0x635D7E + 3, 0x5466C5 + 3));
|
||||
const auto string = reinterpret_cast<const char**>(address)[string_id];
|
||||
if (string != nullptr && callback(string))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
current = var.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> object::get_keys() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
iterate_keys([&](const std::string& key)
|
||||
{
|
||||
result.emplace_back(key);
|
||||
return false;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::string> object::get_next_key(const std::string& current) const
|
||||
{
|
||||
auto is_next = false;
|
||||
std::optional<std::string> next;
|
||||
|
||||
iterate_keys([&](const std::string& key)
|
||||
{
|
||||
if (is_next)
|
||||
{
|
||||
next.emplace(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == current)
|
||||
{
|
||||
is_next = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
std::uint32_t 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);
|
||||
}
|
||||
}
|
||||
|
||||
void object::erase(const object_iterator& iter) const
|
||||
{
|
||||
this->erase(iter->first);
|
||||
}
|
||||
|
||||
script_value object::get(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)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = game::scr_VarGlob->childVariableValue[variable_id];
|
||||
game::VariableValue variable{};
|
||||
variable.u = value.u.u;
|
||||
variable.type = static_cast<game::scriptType_e>(value.type);
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
void object::set(const std::string& key, const script_value& value) const
|
||||
{
|
||||
const auto& value_raw = value.get_raw();
|
||||
const auto variable_id = this->get_value_id(key);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id];
|
||||
game::VariableValue variable_{};
|
||||
variable_.type = variable->type;
|
||||
variable_.u = variable->u.u;
|
||||
|
||||
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_raw);
|
||||
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable_.type, variable_.u);
|
||||
|
||||
variable->type = gsl::narrow_cast<char>(value_raw.type);
|
||||
variable->u.u = value_raw.u;
|
||||
}
|
||||
|
||||
std::uint32_t object::get_entity_id() const
|
||||
{
|
||||
return this->id_;
|
||||
}
|
||||
|
||||
std::uint32_t object::get_value_id(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)
|
||||
{
|
||||
return game::GetNewVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
|
||||
}
|
||||
|
||||
return variable_id;
|
||||
}
|
||||
|
||||
entity object::get_raw() const
|
||||
{
|
||||
return entity(this->id_);
|
||||
}
|
||||
|
||||
object_value object::operator[](const std::string& key) const
|
||||
{
|
||||
return object_value(this, key);
|
||||
}
|
||||
|
||||
object_iterator object::begin() const
|
||||
{
|
||||
const auto keys = this->get_keys();
|
||||
return object_iterator(this, keys, 0);
|
||||
}
|
||||
|
||||
object_iterator object::end() const
|
||||
{
|
||||
return object_iterator(this);
|
||||
}
|
||||
|
||||
object_iterator object::find(const std::string& key) const
|
||||
{
|
||||
const auto keys = this->get_keys();
|
||||
for (auto i = 0u; i < keys.size(); i++)
|
||||
{
|
||||
if (keys[i] == key)
|
||||
{
|
||||
return object_iterator(this, keys, i);
|
||||
}
|
||||
}
|
||||
|
||||
return object_iterator(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include "script_value.hpp"
|
||||
#include "container_iterator.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class object;
|
||||
|
||||
class object_value : public script_value
|
||||
{
|
||||
public:
|
||||
object_value(const object* object, const std::string& 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("object field '{}' {}", this->key_, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const object* object_;
|
||||
std::string key_;
|
||||
|
||||
};
|
||||
|
||||
template <typename IteratorType>
|
||||
class object_iterator_base : public IteratorType
|
||||
{
|
||||
public:
|
||||
object_iterator_base(const object* container)
|
||||
: IteratorType(container)
|
||||
{
|
||||
}
|
||||
|
||||
object_iterator_base(const object* container, const std::vector<std::string>& keys, const std::int64_t key_index)
|
||||
: IteratorType(container, keys, key_index)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using object_iterator = object_iterator_base<container_iterator<object, std::string, object_value>>;
|
||||
|
||||
class object final
|
||||
{
|
||||
public:
|
||||
object();
|
||||
object(const std::uint32_t id);
|
||||
|
||||
object(const object& other);
|
||||
object(object&& other) noexcept;
|
||||
|
||||
~object();
|
||||
|
||||
object& operator=(const object& other);
|
||||
object& operator=(object&& other) noexcept;
|
||||
|
||||
void iterate_keys(const std::function<bool(const std::string& key)>& callback) const;
|
||||
std::vector<std::string> get_keys() const;
|
||||
std::optional<std::string> get_next_key(const std::string& current) const;
|
||||
|
||||
std::uint32_t size() const;
|
||||
void erase(const std::string& key) const;
|
||||
void erase(const object_iterator& iter) const;
|
||||
|
||||
script_value get(const std::string&) const;
|
||||
void set(const std::string&, const script_value&) const;
|
||||
|
||||
std::uint32_t get_entity_id() const;
|
||||
std::uint32_t get_value_id(const std::string&) const;
|
||||
|
||||
entity get_raw() const;
|
||||
|
||||
object_value operator[](const std::string& key) const;
|
||||
|
||||
object_iterator begin() const;
|
||||
object_iterator end() const;
|
||||
object_iterator find(const std::string& key) const;
|
||||
|
||||
private:
|
||||
void add() const;
|
||||
void release() const;
|
||||
|
||||
std::uint32_t 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, 0, 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, 0, 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, 0, 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,478 @@
|
||||
#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);
|
||||
|
||||
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<short>() const
|
||||
{
|
||||
return this->is<int>();
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<char>() const
|
||||
{
|
||||
return this->is<int>();
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<bool>() 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));
|
||||
}
|
||||
|
||||
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::scr_VarGlob->objectVariableValue[id].w.type & 0x7F;
|
||||
|
||||
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::scr_VarGlob->objectVariableValue[id].w.type & 0x7F;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/***********************************************
|
||||
*
|
||||
**********************************************/
|
||||
|
||||
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,393 @@
|
||||
#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, "hash"},
|
||||
{6, "float"},
|
||||
{7, "int"},
|
||||
{8, "codepos"},
|
||||
{9, "precodepos"},
|
||||
{10, "function"},
|
||||
{11, "stack"},
|
||||
{12, "animation"},
|
||||
{13, "developer codepos"},
|
||||
{14, "thread"},
|
||||
{15, "thread"},
|
||||
{16, "thread"},
|
||||
{17, "thread"},
|
||||
{18, "struct"},
|
||||
{19, "removed entity"},
|
||||
{20, "entity"},
|
||||
{21, "array"},
|
||||
{22, "removed thread"},
|
||||
{23, "<free>"},
|
||||
{24, "thread list"},
|
||||
{25, "ent list"},
|
||||
};
|
||||
|
||||
std::string get_typename(const game::VariableValue& value)
|
||||
{
|
||||
if (value.type == game::SCRIPT_OBJECT)
|
||||
{
|
||||
const auto type = game::scr_VarGlob->objectVariableValue[value.u.uintValue].w.type & 0x7F;
|
||||
return typenames[type];
|
||||
}
|
||||
else
|
||||
{
|
||||
return typenames[value.type];
|
||||
}
|
||||
}
|
||||
|
||||
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 (std::is_pointer<T>::value && 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,141 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "thread.hpp"
|
||||
#include "execution.hpp"
|
||||
#include "../../component/scripting.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
thread::thread(unsigned int id)
|
||||
: id_(id)
|
||||
, type_(game::scr_VarGlob->objectVariableValue[id].w.type & 0x7F)
|
||||
{
|
||||
}
|
||||
|
||||
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 game::scr_VarGlob->objectVariableValue[this->id_].w.waitTime >> 8;
|
||||
}
|
||||
|
||||
unsigned int thread::get_notify_name_id() const
|
||||
{
|
||||
return game::scr_VarGlob->objectVariableValue[this->id_].w.notifyName >> 8;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
const char* thread::get_pos() const
|
||||
{
|
||||
const auto start_local_id = game::GetStartLocalId(game::SCRIPTINSTANCE_SERVER, this->id_);
|
||||
|
||||
if (this->type_ == game::SCRIPT_THREAD)
|
||||
{
|
||||
for (auto frame = game::scr_VmPub->function_frame; frame != game::scr_VmPub->function_frame_start; --frame)
|
||||
{
|
||||
if (frame->fs.localId == this->id_)
|
||||
{
|
||||
return frame->fs.pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int stack_id = 0;
|
||||
|
||||
if (this->type_ == game::SCRIPT_TIME_THREAD)
|
||||
{
|
||||
const auto waittime = this->get_wait_time();
|
||||
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, game::scr_VarPub->timeArrayId, waittime);
|
||||
const auto object_id = game::scr_VarGlob->childVariableValue[variable_id].u.f.prev;
|
||||
|
||||
stack_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, object_id, start_local_id + 0x10000);
|
||||
}
|
||||
|
||||
if (this->type_ == game::SCRIPT_NOTIFY_THREAD)
|
||||
{
|
||||
const auto notify_name = this->get_notify_name_id();
|
||||
|
||||
if (!notify_name)
|
||||
{
|
||||
stack_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, start_local_id, 0x16001);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto self = game::Scr_GetSelf(game::SCRIPTINSTANCE_SERVER, start_local_id);
|
||||
|
||||
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, game::scr_VarPub->pauseArrayId, self + 0x10000);
|
||||
const auto object_id = game::scr_VarGlob->childVariableValue[variable_id].u.f.prev;
|
||||
|
||||
const auto variable_id2 = game::FindVariable(game::SCRIPTINSTANCE_SERVER, object_id, start_local_id + 0x10000);
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id2];
|
||||
|
||||
const auto variable_id3 = game::FindVariable(game::SCRIPTINSTANCE_SERVER, variable->u.u.uintValue, 0x18000);
|
||||
const auto object_id2 = game::scr_VarGlob->childVariableValue[variable_id3].u.f.prev;
|
||||
|
||||
const auto variable_id4 = game::FindVariable(game::SCRIPTINSTANCE_SERVER, object_id2, notify_name);
|
||||
const auto object_id3 = game::scr_VarGlob->childVariableValue[variable_id4].u.f.prev;
|
||||
|
||||
stack_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, object_id3, start_local_id + 0x10000);
|
||||
}
|
||||
}
|
||||
|
||||
const auto stack = &game::scr_VarGlob->childVariableValue[stack_id];
|
||||
if (stack && stack->type == 11)
|
||||
{
|
||||
return stack->u.u.stackValue->pos;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* thread::get_start_pos() const
|
||||
{
|
||||
const auto current = this->get_pos();
|
||||
return scripting::find_function_start(current);
|
||||
}
|
||||
|
||||
void thread::kill() const
|
||||
{
|
||||
const auto start_local_id = game::GetStartLocalId(game::SCRIPTINSTANCE_SERVER, this->id_);
|
||||
|
||||
if (this->type_ == game::SCRIPT_THREAD)
|
||||
{
|
||||
game::Scr_TerminateRunningThread(game::SCRIPTINSTANCE_SERVER, this->id_);
|
||||
}
|
||||
|
||||
if (this->type_ == game::SCRIPT_TIME_THREAD)
|
||||
{
|
||||
game::Scr_TerminateWaitThread(game::SCRIPTINSTANCE_SERVER, this->id_, start_local_id);
|
||||
}
|
||||
|
||||
if (this->type_ == game::SCRIPT_NOTIFY_THREAD)
|
||||
{
|
||||
game::Scr_TerminateWaittillThread(game::SCRIPTINSTANCE_SERVER, this->id_, start_local_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#define WEAK __declspec(selectany)
|
||||
|
||||
namespace game
|
||||
{
|
||||
// Functions
|
||||
|
||||
WEAK symbol<int(const char* str)> BG_StringHashValue{0x6BB660, 0x612780};
|
||||
|
||||
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_InsertText{0x4A5FF0, 0x702E40};
|
||||
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x5C6F10, 0x6B9D20};
|
||||
WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x6B9C10, 0x43B8D0};
|
||||
WEAK symbol<void(const char* cmdName, void(), cmd_function_t* allocedCmd)> Cmd_AddCommandInternal{0x5B3070, 0x4DC2A0};
|
||||
WEAK symbol<const char*(int index)> Cmd_Argv{0x5608F0, 0x6B3D40};
|
||||
WEAK symbol<void(const char* cmdName)> Cmd_RemoveCommand{0x6033D0, 0x65EB30};
|
||||
|
||||
WEAK symbol<void(int clientNum)> ClientUserInfoChanged{0x4ED6A0, 0x427DC0};
|
||||
|
||||
WEAK symbol<const dvar_t*(const char*)> Dvar_FindVar{0x563A70, 0x673C80};
|
||||
WEAK symbol<int(const dvar_t*)> Dvar_GetInt{0x44DFC0, 0x6909F0};
|
||||
WEAK symbol<dvar_t*(const char* dvarName, int value, int min, int max,
|
||||
unsigned int flags, const char* description)> Dvar_RegisterInt{0x5F24E0, 0x5A3D70};
|
||||
WEAK symbol<dvar_t*(const char* dvarName, bool value, unsigned int flags, const char* description)> Dvar_RegisterBool{0x4E0170, 0x5FBCC0};
|
||||
|
||||
WEAK symbol<XAssetHeader(XAssetType type, const char* name, bool errorIfMissing, int waitTime)> DB_FindXAssetHeader{0x6F9030, 0x43F460};
|
||||
|
||||
WEAK symbol<char*(const char*)> I_CleanStr{0x44F2B0, 0x483F40};
|
||||
|
||||
WEAK symbol<void*(const char** pName, int* min_args, int* max_args)> Player_GetMethod{0x432480, 0x6F2DB0};
|
||||
WEAK symbol<void*(const char** pName, int* type, int* min_args, int* max_args)> Scr_GetCommonFunction{0x691110, 0x4EB070};
|
||||
WEAK symbol<void*(const char** pName, int* type, int* min_args, int* max_args)> Scr_GetMethod{0x59D090, 0x486120};
|
||||
|
||||
WEAK symbol<void(scriptInstance_t inst, gentity_s* ent)> Scr_AddEntity{0x4C20F0, 0x5D8F80};
|
||||
WEAK symbol<void(scriptInstance_t inst, float value)> Scr_AddFloat{0x579130, 0x503480};
|
||||
WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x57AFF0, 0x643A40};
|
||||
WEAK symbol<void(scriptInstance_t inst, const char* value)> Scr_AddString{0x4F1650, 0x6A7A70};
|
||||
WEAK symbol<void(scriptInstance_t inst, float* value)> Scr_AddVector{0x4C1A40, 0x4FAB00};
|
||||
WEAK symbol<void(scriptInstance_t inst, unsigned int id)> Scr_AddObject{0x539B50, 0x584DF0};
|
||||
|
||||
WEAK symbol<void(scriptInstance_t inst)> Scr_ClearOutParams{0x591410, 0x627540};
|
||||
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst)> AllocObject{0x6FB1B0, 0x6FE9D0};
|
||||
WEAK symbol<ObjectVariableValue*(scriptInstance_t inst, unsigned int* index)> AllocVariable{0x8F3150, 0x8F1EB0};
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int id)> AllocThread{0x49FDF0, 0x482C40};
|
||||
WEAK symbol<void(scriptInstance_t inst, unsigned int id)> RemoveRefToObject{0x6FB1B0, 0x550DC0};
|
||||
WEAK symbol<void(scriptInstance_t inst, const float* vectorValue)> RemoveRefToVector{0x50C580, 0x432600};
|
||||
WEAK symbol<void(scriptInstance_t inst, const VariableValue* value)> AddRefToValue{0x488FA0, 0x4E5060};
|
||||
WEAK symbol<void(scriptInstance_t inst, const int type, VariableUnion value)> RemoveRefToValue{0x55E820, 0x42E7D0};
|
||||
WEAK symbol<void(scriptInstance_t inst, unsigned int parentId, unsigned int index)> RemoveVariableValue{0x56A820, 0x6CC320};
|
||||
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int parentId, unsigned int id)> FindVariable{0x6EB1B0, 0x5FE180};
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int parentId, unsigned int id)> GetVariable{0, 0x5CD170};
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int parentId, unsigned int name)> GetNewVariable{0x5AD920, 0x5C18A0};
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int parentId, unsigned int unsignedValue)> GetNewArrayVariable{0x5D80C0, 0x42C260};
|
||||
|
||||
WEAK symbol<void(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x5B9820, 0x43F2A0};
|
||||
WEAK symbol<VariableValue(scriptInstance_t inst, unsigned int classnum, int entnum, int clientNum, int offset)> GetEntityFieldValue{0x693130, 0x6DD0E0};
|
||||
|
||||
WEAK symbol<void(gentity_s* ent, unsigned int stringValue, unsigned int paramcount)> Scr_Notify{0x644AB0, 0x495420};
|
||||
WEAK symbol<void(scriptInstance_t inst, int clientNum, unsigned int id, unsigned int stringValue, unsigned int paramcount)> Scr_NotifyId{0x4FDC10, 0x4BC0D0};
|
||||
WEAK symbol<void(int entnum, unsigned int classnum, unsigned int stringValue, unsigned int paramcount)> Scr_NotifyNum{0x578690, 0x664770};
|
||||
|
||||
WEAK symbol<unsigned int(const char* str, unsigned int user)> SL_GetString{0x602C40, 0x4601E0};
|
||||
WEAK symbol<const char*(unsigned int stringValue)> SL_ConvertToString{0x422D10, 0x532230};
|
||||
WEAK symbol<unsigned int(const char* str, bool is_static)> SL_GetCanonicalString{0x692740, 0x546640};
|
||||
|
||||
WEAK symbol<int(scriptInstance_t inst)> Scr_GetNumParam{0x42E990, 0x680EA0};
|
||||
WEAK symbol<gentity_s*(scriptInstance_t inst, int index)> Scr_GetEntity{0x48F250, 0x489100};
|
||||
WEAK symbol<float(scriptInstance_t inst, int index)> Scr_GetFloat{0x633400, 0x625EE0};
|
||||
WEAK symbol<int(scriptInstance_t inst, int index)> Scr_GetInt{0x45D840, 0x49A060};
|
||||
WEAK symbol<const char*(scriptInstance_t inst, int index)> Scr_GetString{0x67C6A0, 0x488110};
|
||||
WEAK symbol<void(scriptInstance_t inst, int index, float* out)> Scr_GetVector{0x4CBCC0, 0x4BB100};
|
||||
WEAK symbol<const float*(scriptInstance_t inst, const float* v)> Scr_AllocVector{0x510D30, 0x44D8C0};
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int threadId)> Scr_GetSelf{0x4682C0, 0x6C4E60};
|
||||
WEAK symbol<int(scriptInstance_t inst, const char* filename,
|
||||
const char* name, unsigned int* checksum, bool errorIfMissing)> Scr_GetFunctionHandle{0x416D30, 0x53E5A0};
|
||||
WEAK symbol<void(scriptInstance_t inst, unsigned int classnum, char const* name, unsigned int offset)> Scr_AddClassField{0x6B7620, 0x438AD0};
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, int entnum, unsigned int classnum, int clientNum)> Scr_GetEntityId{0x5765B0, 0x488430};
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int localId)> GetStartLocalId{0x402760, 0x5D6CD0};
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int localId)> Scr_TerminateRunningThread{0x8F45A0, 0x8F3300};
|
||||
WEAK symbol<void(scriptInstance_t inst, const char* error, bool force_terminal)> Scr_Error{0x403AE0, 0x5E81C0};
|
||||
WEAK symbol<void(scriptInstance_t inst, unsigned int paramIndex, const char* error)> Scr_ParamError{0x415A30, 0x617CA0};
|
||||
WEAK symbol<void(scriptInstance_t inst, const char* error)> Scr_ObjectError{0x561660, 0x6B67E0};
|
||||
WEAK symbol<void(scriptInstance_t inst, const char* error)> Scr_TerminalError{0x698C50, 0x410440};
|
||||
|
||||
WEAK symbol<gentity_s*(scr_entref_t entref)> GetPlayerEntity{0x48B760, 0x4BF6F0};
|
||||
|
||||
WEAK symbol<unsigned int(scriptInstance_t inst, unsigned int localId, const char* pos, unsigned int paramcount)> VM_Execute{0x8F9550, 0x8F82B0};
|
||||
|
||||
WEAK symbol<void(int clientNum, const char* reason)> SV_GameDropClient{0x6D2980, 0x677690};
|
||||
WEAK symbol<bool(int clientNum)> SV_IsTestClient{0x4E64B0, 0x42C300};
|
||||
WEAK symbol<void(int clientNum, int type, const char* command)> SV_GameSendServerCommand{0x45D7D0, 0x40D450};
|
||||
WEAK symbol<void(int index, const char* val)> SV_SetConfigString{0x6EAA00, 0x673E40};
|
||||
|
||||
WEAK symbol<void*(int valueIndex)> Sys_GetValue{0x5EFBA0, 0x59A740};
|
||||
WEAK symbol<int()> Sys_Milliseconds{0x57EC40, 0x6F4C00};
|
||||
|
||||
WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0xA78870, 0xA71AD0};
|
||||
WEAK symbol<int(jmp_buf* Buf, int a6, int a7, int a9)> _setjmp{0xA77B10, 0xA70D70};
|
||||
|
||||
// Variables
|
||||
|
||||
WEAK symbol<CmdArgs> sv_cmd_args{0x255DD48, 0x252F8C8};
|
||||
|
||||
WEAK symbol<int> g_script_error_level{0x2E23BC8, 0x2DF3EC8};
|
||||
WEAK symbol<jmp_buf> g_script_error{0x2E22B48, 0x2DF2E48};
|
||||
|
||||
WEAK symbol<scrVmPub_t> scr_VmPub{0x2E1A5D0, 0x2DEA8D0};
|
||||
WEAK symbol<scrVarGlob_t> scr_VarGlob{0x2E1A100, 0x2DEA400};
|
||||
WEAK symbol<scrVarPub_t> scr_VarPub{0x2E1A500, 0x2DEA800};
|
||||
WEAK symbol<int> scr_starttime{0x2E23C48, 0x2DF3F48};
|
||||
WEAK symbol<function_stack_t> fs{0x2E23C08, 0x2DF3F08};
|
||||
|
||||
WEAK symbol<unsigned short> sv_configstrings{0x28E67AC, 0x28B70AC};
|
||||
|
||||
WEAK symbol<scr_classStruct_t*> g_classMap{0xD6FFE0, 0xD64E60};
|
||||
|
||||
WEAK symbol<BuiltinFunctionDef> common_functions{0xC75160, 0xC6ACC8};
|
||||
WEAK symbol<BuiltinFunctionDef> functions{0xD4DD28, 0xD42BC8};
|
||||
|
||||
WEAK symbol<BuiltinMethodDef> player_methods{0xC726A8, 0xC68218};
|
||||
WEAK symbol<BuiltinMethodDef> scriptEnt_methods{0xC76F18, 0xC6CA90};
|
||||
WEAK symbol<BuiltinMethodDef> scriptVehicle_methods{0xC77538, 0xC6D0B0};
|
||||
WEAK symbol<BuiltinMethodDef> hudElem_methods{0xC74580, 0xC6A0E8};
|
||||
WEAK symbol<BuiltinMethodDef> helicopter_methods{0xC74E98, 0xC6AA00};
|
||||
WEAK symbol<BuiltinMethodDef> actor_methods{0xC871A0, 0xC7CC70};
|
||||
WEAK symbol<BuiltinMethodDef> builtInCommon_methods{0xC76600, 0xC6C168};
|
||||
WEAK symbol<BuiltinMethodDef> builtIn_methods{0xD4FC78, 0xD44B18};
|
||||
|
||||
WEAK symbol<gentity_s> g_entities{0x21EF7C0, 0x21C13C0};
|
||||
WEAK symbol<unsigned int> levelEntityId{0x2E1A51C, 0x2DEA81C};
|
||||
|
||||
WEAK symbol<client_s*> svs_clients{0x291C0C0, 0x28EC9C0};
|
||||
|
||||
WEAK symbol<game_hudelem_s> g_hudelems{0x21C5140, 0x2196D40};
|
||||
}
|
||||
@@ -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,61 @@
|
||||
#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* load_import(const std::string& library, const std::string& function);
|
||||
|
||||
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,79 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "component/logfile.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);
|
||||
|
||||
logfile::log_hook(buffer);
|
||||
|
||||
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-t6-gsc-utils";
|
||||
}
|
||||
|
||||
bool plugin::is_game_supported([[maybe_unused]] plutonium::sdk::game game)
|
||||
{
|
||||
return game == plutonium::sdk::game::t6;
|
||||
}
|
||||
|
||||
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,25 @@
|
||||
#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,63 @@
|
||||
#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 <ws2tcpip.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <csetjmp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <atlcomcli.h>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
|
||||
#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, ¤t_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,686 @@
|
||||
#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
|
||||
|
||||
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(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"), &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;
|
||||
}
|
||||
}
|
||||
@@ -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,160 @@
|
||||
#include <stdinc.hpp>
|
||||
#include <fstream>
|
||||
|
||||
#include "string.hpp"
|
||||
#include "file_watcher.hpp"
|
||||
|
||||
namespace utils::io
|
||||
{
|
||||
file_watcher::file_watcher(const std::string& path)
|
||||
{
|
||||
this->path_ = path;
|
||||
const auto pos = this->path_.find_last_of("/\\");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
this->filename_ = path.substr(pos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->filename_ = path;
|
||||
}
|
||||
}
|
||||
|
||||
file_watcher::~file_watcher()
|
||||
{
|
||||
#ifdef WIN32
|
||||
const auto close = [](HANDLE handle)
|
||||
{
|
||||
if (handle != NULL && handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(handle);
|
||||
}
|
||||
};
|
||||
|
||||
close(this->handle_);
|
||||
close(this->event_handle_);
|
||||
close(this->stop_handle_);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool file_watcher::init()
|
||||
{
|
||||
#ifdef WIN32
|
||||
std::string folder{};
|
||||
const auto pos = this->path_.find_last_of("/\\");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
folder = this->path_.substr(0, pos);
|
||||
}
|
||||
|
||||
this->handle_ = CreateFile(
|
||||
folder.data(),
|
||||
FILE_LIST_DIRECTORY,
|
||||
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
||||
NULL
|
||||
);
|
||||
this->event_handle_ = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
this->stop_handle_ = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
|
||||
const auto is_valid = [](HANDLE handle)
|
||||
{
|
||||
return handle != NULL && handle != INVALID_HANDLE_VALUE;
|
||||
};
|
||||
|
||||
if (!is_valid(this->event_handle_) ||
|
||||
!is_valid(this->handle_ ) ||
|
||||
!is_valid(this->stop_handle_))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->stream_ = std::ifstream{this->path_, std::ios::binary};
|
||||
this->stream_.seekg(0, std::ios::end);
|
||||
this->current_pos_ = static_cast<size_t>(this->stream_.tellg());
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool file_watcher::run(int wait_time, std::string* data, bool force_check)
|
||||
{
|
||||
#ifdef WIN32
|
||||
this->stop_ = false;
|
||||
|
||||
char buffer[0x1000] = {0};
|
||||
OVERLAPPED overlapped = {};
|
||||
overlapped.hEvent = this->event_handle_;
|
||||
|
||||
ReadDirectoryChangesW(this->handle_, buffer, sizeof(buffer),
|
||||
FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, &overlapped, NULL);
|
||||
|
||||
HANDLE handles[2] =
|
||||
{
|
||||
this->event_handle_,
|
||||
this->stop_handle_
|
||||
};
|
||||
|
||||
WaitForMultipleObjects(2, handles, FALSE, wait_time);
|
||||
if (this->stop_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer);
|
||||
const auto filename = utils::string::convert(info->FileName);
|
||||
|
||||
if (!force_check && (filename != this->filename_ || (info->Action & FILE_GENERIC_WRITE) == 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->stream_.seekg(0, std::ios::end);
|
||||
const auto new_size = static_cast<size_t>(this->stream_.tellg());
|
||||
|
||||
if (new_size < this->current_pos_)
|
||||
{
|
||||
this->current_pos_ = new_size;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto bytes_written = new_size - this->current_pos_;
|
||||
if (bytes_written == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->stream_.seekg(-1 * static_cast<int64_t>(bytes_written), std::ios::cur);
|
||||
this->current_pos_ += bytes_written;
|
||||
|
||||
data->resize(bytes_written);
|
||||
this->stream_.read(const_cast<char*>(data->data()), bytes_written);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool file_watcher::run_blocking(std::string* data, bool force_check)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return this->run(INFINITE, data, force_check);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool file_watcher::run(const std::chrono::high_resolution_clock::duration& wait_time, std::string* data, bool force_check)
|
||||
{
|
||||
#ifdef WIN32
|
||||
const auto msec = static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(wait_time).count());
|
||||
return this->run(msec, data, force_check);
|
||||
#endif
|
||||
}
|
||||
|
||||
void file_watcher::stop()
|
||||
{
|
||||
this->stop_ = true;
|
||||
#ifdef WIN32
|
||||
SetEvent(this->stop_handle_);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
namespace utils::io
|
||||
{
|
||||
class file_watcher
|
||||
{
|
||||
public:
|
||||
file_watcher(const std::string& path);
|
||||
|
||||
file_watcher(file_watcher&&) noexcept = delete;
|
||||
file_watcher& operator=(file_watcher&&) noexcept = delete;
|
||||
|
||||
file_watcher(const file_watcher&) = delete;
|
||||
file_watcher& operator=(const file_watcher&) = delete;
|
||||
|
||||
~file_watcher();
|
||||
|
||||
bool init();
|
||||
bool run(int wait_time, std::string* data, bool force_check);
|
||||
bool run(const std::chrono::high_resolution_clock::duration& wait_time,
|
||||
std::string* data, bool force_check = false);
|
||||
bool run_blocking(std::string* data, bool force_check = false);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
std::string path_{};
|
||||
std::string filename_{};
|
||||
|
||||
std::ifstream stream_{};
|
||||
size_t current_pos_{};
|
||||
|
||||
#ifdef WIN32
|
||||
HANDLE handle_{};
|
||||
HANDLE event_handle_{};
|
||||
HANDLE stop_handle_{};
|
||||
#endif
|
||||
|
||||
bool stop_{};
|
||||
|
||||
};
|
||||
}
|
||||
@@ -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,247 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "hook.hpp"
|
||||
#include "string.hpp"
|
||||
// iw6x-client
|
||||
|
||||
namespace utils::hook
|
||||
{
|
||||
void signature::process()
|
||||
{
|
||||
if (this->signatures_.empty()) return;
|
||||
|
||||
const auto start = static_cast<char*>(this->start_);
|
||||
|
||||
const unsigned int sig_count = this->signatures_.size();
|
||||
const auto containers = this->signatures_.data();
|
||||
|
||||
for (size_t i = 0; i < this->length_; ++i)
|
||||
{
|
||||
const auto address = start + i;
|
||||
|
||||
for (unsigned int k = 0; k < sig_count; ++k)
|
||||
{
|
||||
const auto container = &containers[k];
|
||||
|
||||
unsigned int j;
|
||||
for (j = 0; j < static_cast<unsigned int>(container->mask.size()); ++j)
|
||||
{
|
||||
if (container->mask[j] != '?' && container->signature[j] != address[j])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (j == container->mask.size())
|
||||
{
|
||||
container->callback(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void signature::add(const container& container)
|
||||
{
|
||||
signatures_.push_back(container);
|
||||
}
|
||||
|
||||
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,177 @@
|
||||
#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 signature final
|
||||
{
|
||||
public:
|
||||
struct container final
|
||||
{
|
||||
std::string signature;
|
||||
std::string mask;
|
||||
std::function<void(char*)> callback;
|
||||
};
|
||||
|
||||
signature(void* start, const size_t length) : start_(start), length_(length)
|
||||
{
|
||||
}
|
||||
|
||||
signature(const DWORD start, const size_t length) : signature(reinterpret_cast<void*>(start), length)
|
||||
{
|
||||
}
|
||||
|
||||
signature() : signature(0x400000, 0x800000)
|
||||
{
|
||||
}
|
||||
|
||||
void process();
|
||||
void add(const container& container);
|
||||
|
||||
private:
|
||||
void* start_;
|
||||
size_t length_;
|
||||
std::vector<container> signatures_;
|
||||
};
|
||||
|
||||
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,136 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
bool remove_directory(const std::string& directory, const bool recursive)
|
||||
{
|
||||
std::error_code ec;
|
||||
|
||||
if (recursive)
|
||||
{
|
||||
std::uintmax_t n{ std::filesystem::remove_all(directory, ec) };
|
||||
return (n && (n != static_cast<std::uintmax_t>(-1))); // Why did they make it this way?
|
||||
}
|
||||
|
||||
return std::filesystem::remove(directory, ec);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
bool remove_directory(const std::string& directory, bool recursive = false);
|
||||
std::vector<std::string> list_files(const std::string& directory);
|
||||
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
|
||||
}
|
||||
@@ -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,181 @@
|
||||
#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 !text.empty() && std::find_if(text.begin(),
|
||||
text.end(),
|
||||
[](unsigned char input)
|
||||
{
|
||||
return !std::isdigit(input);
|
||||
}) == text.end();
|
||||
}
|
||||
|
||||
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 bin_to_hex(const std::string& data)
|
||||
{
|
||||
return dump_hex(data, "");
|
||||
}
|
||||
|
||||
std::string hex_to_bin(const std::string& data)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
for (auto i = 0u; i < data.size(); i += 2)
|
||||
{
|
||||
const auto byte = data.substr(i, 2);
|
||||
const auto value = static_cast<char>(std::strtol(byte.data(), nullptr, 0x10));
|
||||
result += value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 replace(std::string str, const std::string& from, const std::string& to)
|
||||
{
|
||||
if (from.empty())
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
size_t start_pos = 0;
|
||||
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
|
||||
{
|
||||
str.replace(start_pos, from.length(), to);
|
||||
start_pos += to.length();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string get_timestamp()
|
||||
{
|
||||
tm ltime{};
|
||||
char timestamp[MAX_PATH] = { 0 };
|
||||
const auto time = _time64(nullptr);
|
||||
|
||||
_localtime64_s(<ime, &time);
|
||||
strftime(timestamp, sizeof(timestamp) - 1, "%Y-%m-%d-%H-%M-%S", <ime);
|
||||
|
||||
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,105 @@
|
||||
#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 bin_to_hex(const std::string& data);
|
||||
std::string hex_to_bin(const std::string& data);
|
||||
|
||||
void strip(const char* in, char* out, int max);
|
||||
|
||||
std::string convert(const std::wstring& wstr);
|
||||
std::wstring convert(const std::string& str);
|
||||
|
||||
std::string replace(std::string str, const std::string& from, const std::string& to);
|
||||
|
||||
std::string get_timestamp();
|
||||
|
||||
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_;
|
||||
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user