#include #include "loader/component_loader.hpp" #include "scheduler.hpp" #include "scripting.hpp" #include "command.hpp" #include "game/scripting/event.hpp" #include "game/scripting/execution.hpp" #include "game/scripting/functions.hpp" #include "game/scripting/array.hpp" #include "game/scripting/function.hpp" #include "gsc.hpp" namespace gsc { std::unordered_map functions; std::unordered_map methods; namespace { std::string method_name(unsigned int id) { const auto& map = (*game::plutonium::gsc_ctx)->meth_map(); for (const auto& function : map) { if (function.second == id) { return function.first.data(); } } return {}; } std::string function_name(unsigned int id) { const auto& map = (*game::plutonium::gsc_ctx)->func_map(); for (const auto& function : map) { if (function.second == id) { return function.first.data(); } } return {}; } function_args get_arguments() { std::vector args; for (auto i = 0; i < game::scr_VmPub->outparamcount; i++) { const auto value = game::scr_VmPub->top[-i]; args.push_back(value); } return args; } void return_value(const scripting::script_value& value) { if (game::scr_VmPub->outparamcount) { game::Scr_ClearOutParams(); } scripting::push_value(value); } auto function_map_start = 0x200; auto method_map_start = 0x8400; auto token_map_start = 0x8000; auto field_offset_start = 0xA000; struct entity_field { std::string name; std::function getter; std::function setter; }; std::vector> post_load_callbacks; std::unordered_map> custom_fields; void call_function(unsigned int id) { if (id < 0x200) { return reinterpret_cast(game::plutonium::function_table.get())[id](); } try { const auto result = functions[id](get_arguments()); const auto type = result.get_raw().type; if (type) { return_value(result); } } catch (const std::exception& e) { printf("************** Script execution error **************\n"); printf("Error executing function %s:\n", function_name(id).data()); printf(" %s\n", e.what()); printf("****************************************************\n"); } } void call_method(game::scr_entref_t ent, unsigned int id) { if (id < 0x8400) { return reinterpret_cast(game::plutonium::method_table.get())[id - 0x8000](ent); } try { const auto result = methods[id](ent, get_arguments()); const auto type = result.get_raw().type; if (type) { return_value(result); } } catch (const std::exception& e) { printf("************** Script execution error **************\n"); printf("Error executing method %s:\n", method_name(id).data()); printf(" %s\n", e.what()); printf("****************************************************\n"); } } __declspec(naked) void call_builtin_stub() { __asm { push eax mov eax, 0x20B4A5C mov [eax], esi mov eax, 0x20B4A90 mov [eax], edx pop eax pushad push eax call call_function pop eax popad push 0x56C900 retn } } __declspec(naked) void call_builtin_method_stub() { __asm { pushad push ecx push ebx call call_method pop ebx pop ecx popad push ebx add esp, 0xC push 0x56CBE9 retn } } utils::hook::detour scr_get_object_field_hook; void scr_get_object_field_stub(unsigned int classnum, int entnum, unsigned int offset) { if (custom_fields[classnum].find(offset) == custom_fields[classnum].end()) { return scr_get_object_field_hook.invoke(classnum, entnum, offset); } const auto& field = custom_fields[classnum][offset]; try { const auto result = field.getter(entnum); return_value(result); } catch (const std::exception& e) { printf("************** Script execution error **************\n"); printf("Error getting field %s:\n", field.name.data()); printf(" %s\n", e.what()); printf("****************************************************\n"); } } utils::hook::detour scr_set_object_field_hook; void scr_set_object_field_stub(unsigned int classnum, int entnum, unsigned int offset) { if (custom_fields[classnum].find(offset) == custom_fields[classnum].end()) { return scr_set_object_field_hook.invoke(classnum, entnum, offset); } const auto args = get_arguments(); const auto& field = custom_fields[classnum][offset]; try { field.setter(entnum, args[0]); } catch (const std::exception& e) { printf("************** Script execution error **************\n"); printf("Error setting field %s:\n", field.name.data()); printf(" %s\n", e.what()); printf("****************************************************\n"); } } utils::hook::detour scr_post_load_scripts_hook; void scr_post_load_scripts_stub() { for (const auto& callback : post_load_callbacks) { callback(); } return scr_post_load_scripts_hook.invoke(); } } namespace function { void add(const std::string& name, const script_function& func) { auto index = 0u; auto& ctx = (*game::plutonium::gsc_ctx); if (ctx->func_exists(name)) { printf("[iw5-gsc-utils] Warning: function '%s' already defined\n", name.data()); index = ctx->func_id(name); } else { index = function_map_start++; ctx->func_add(name, index); } functions.insert(std::make_pair(index, func)); } } namespace method { void add(const std::string& name, const script_method& func) { auto index = 0u; auto& ctx = (*game::plutonium::gsc_ctx); if (ctx->meth_exists(name)) { printf("[iw5-gsc-utils] Warning: method '%s' already defined\n", name.data()); index = ctx->meth_id(name); } else { index = method_map_start++; ctx->meth_add(name, index); } methods.insert(std::make_pair(index, func)); } } namespace field { void add(const classid classnum, const std::string& name, const std::function& getter, const std::function& setter) { const auto offset = field_offset_start++; custom_fields[classnum][offset] = {name, getter, setter}; post_load_callbacks.push_back([=]() { const auto name_str = game::SL_GetString(name.data(), 0); game::Scr_AddClassField(classnum, name_str, game::SL_GetCanonicalString(name.data()), offset); }); } } function_args::function_args(std::vector values) : values_(values) { } unsigned int function_args::size() const { return this->values_.size(); } std::vector function_args::get_raw() const { return this->values_; } scripting::value_wrap function_args::get(const int index) const { if (index >= this->values_.size()) { throw std::runtime_error(utils::string::va("parameter %d does not exist", index)); } return {this->values_[index], index}; } class component final : public component_interface { public: void post_unpack() override { scr_get_object_field_hook.create(0x52BDB0, scr_get_object_field_stub); scr_set_object_field_hook.create(0x52BCC0, scr_set_object_field_stub); scr_post_load_scripts_hook.create(0x628B50, scr_post_load_scripts_stub); field::add(classid::entity, "entityflags", [](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(); } ); field::add(classid::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(); } ); function::add("executecommand", [](const function_args& args) -> scripting::script_value { game::Cbuf_AddText(0, args[0].as()); return {}; }); function::add("addcommand", [](const function_args& args) -> scripting::script_value { const auto name = args[0].as(); const auto function = args[1].as(); command::add_script_command(name, [function](const command::params& params) { scripting::array array; for (auto i = 0; i < params.size(); i++) { array.push(params[i]); } function({array.get_raw()}); }); return {}; }); function::add("say", [](const function_args& args) -> scripting::script_value { const auto message = args[0].as(); game::SV_GameSendServerCommand(-1, 0, utils::string::va("%c \"%s\"", 84, message.data())); return {}; }); function::add("dropallbots", [](const function_args&) -> scripting::script_value { for (auto i = 0; i < *game::svs_clientCount; i++) { if (game::svs_clients[i].header.state != game::CS_FREE && game::svs_clients[i].header.netchan.remoteAddress.type == game::NA_BOT) { game::SV_GameDropClient(i, "GAME_GET_TO_COVER"); } } return {}; }); method::add("tell", [](const game::scr_entref_t ent, const function_args& args) -> scripting::script_value { if (ent.classnum != 0) { throw std::runtime_error("Invalid entity"); } const auto client = ent.entnum; if (game::g_entities[client].client == nullptr) { throw std::runtime_error("Not a player entity"); } const auto message = args[0].as(); game::SV_GameSendServerCommand(client, 0, utils::string::va("%c \"%s\"", 84, message.data())); return {}; }); method::add("specialtymarathon", [](const game::scr_entref_t ent, const function_args& args) -> scripting::script_value { if (ent.classnum != 0) { throw std::runtime_error("Invalid entity"); } const auto client = ent.entnum; if (game::g_entities[client].client == nullptr) { throw std::runtime_error("Not a player entity"); } const auto toggle = args[0].as(); auto flags = game::g_entities[client].client->ps.perks[0]; game::g_entities[client].client->ps.perks[0] = toggle ? flags | 0x4000u : flags & ~0x4000u; return {}; }); method::add("isbot", [](const game::scr_entref_t ent, const function_args&) -> scripting::script_value { if (ent.classnum != 0) { throw std::runtime_error("Invalid entity"); } const auto client = ent.entnum; if (game::g_entities[client].client == nullptr) { throw std::runtime_error("Not a player entity"); } return game::svs_clients[client].bIsTestClient; }); method::add("arecontrolsfrozen", [](const game::scr_entref_t ent, const function_args&) -> scripting::script_value { if (ent.classnum != 0) { throw std::runtime_error("Invalid entity"); } const auto client = ent.entnum; if (game::g_entities[client].client == nullptr) { throw std::runtime_error("Not a player entity"); } return {(game::g_entities[client].client->flags & 4) != 0}; }); // let other plugins read the pointers post_load_callbacks.push_back([]() { utils::hook::jump(0x56C8EB, call_builtin_stub); utils::hook::jump(0x56CBDC, call_builtin_method_stub); }); } }; } REGISTER_COMPONENT(gsc::component)