#include #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 #include namespace gsc { std::unordered_map functions; std::unordered_map methods; std::unordered_map function_wraps; std::unordered_map 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 custom_fields[class_count]; std::string current_namespace; std::unordered_map 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(find_function(*name)); if (func == nullptr) { return scr_get_common_function_hook.invoke(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(find_method(*name)); if (method == nullptr) { return player_get_method_hook.invoke(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(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(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(); } __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(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 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 get_arguments() { std::vector args; for (auto i = 0; static_cast(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(); } ); 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(); } ); 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(); } ); 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(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(); }); 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(); }); 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)