diff --git a/src/client/component/gsc.cpp b/src/client/component/gsc.cpp index 8d046315..840520a6 100644 --- a/src/client/component/gsc.cpp +++ b/src/client/component/gsc.cpp @@ -14,6 +14,8 @@ #include "game/scripting/functions.hpp" #include "game/scripting/lua/error.hpp" +#include "notifies.hpp" + #include #include #include @@ -28,174 +30,20 @@ namespace gsc { + void* func_table[0x1000]{}; + namespace { game::dvar_t* developer_script = nullptr; - std::unordered_map opcodes = - { - {0x17, "SET_NEW_LOCAL_VARIABLE_FIELD_CACHED0"}, - {0x18, "EVAL_SELF_FIELD_VARIABLE"}, - {0x19, "RETN"}, - {0x1A, "CALL_BUILTIN_FUNC_0"}, - {0x1B, "CALL_BUILTIN_FUNC_1"}, - {0x1C, "CALL_BUILTIN_FUNC_2"}, - {0x1D, "CALL_BUILTIN_FUNC_3"}, - {0x1E, "CALL_BUILTIN_FUNC_4"}, - {0x1F, "CALL_BUILTIN_FUNC_5"}, - {0x20, "CALL_BUILTIN_FUNC"}, - {0x21, "BOOL_NOT"}, - {0x22, "CALL_FAR_METHOD_THEAD"}, - {0x23, "JMP_EXPR_TRUE"}, - {0x24, "SET_LEVEL_FIELD_VARIABLE_FIELD"}, - {0x25, "CAST_BOOL"}, - {0x26, "EVAL_NEW_LOCAL_ARRAY_REF_CACHED0"}, - {0x27, "CALL_BUILTIN_FUNC_POINTER"}, - {0x28, "INEQUALITY"}, - {0x29, "GET_THISTHREAD"}, - {0x2A, "CLEAR_FIELD_VARIABLE"}, - {0x2B, "GET_FLOAT"}, - {0x2C, "SAFE_CREATE_VARIABLE_FIELD_CACHED"}, - {0x2D, "CALL_FAR_FUNC2"}, - {0x2E, "CALL_FAR_FUNC"}, - {0x2F, "CALL_FAR_FUNC_CHILD_THREAD"}, - {0x30, "CLEAR_LOCAL_VARIABLE_FIELD_CACHED0"}, - {0x31, "CLEAR_LOCAL_VARIABLE_FIELD_CACHED"}, - {0x32, "CHECK_CLEAR_PARAMS"}, - {0x33, "CAST_FIELD_OBJ"}, - {0x34, "END"}, - {0x35, "SIZE"}, - {0x36, "EMPTY_ARRAY"}, - {0x37, "BIT_AND"}, - {0x38, "LESSEQUAL"}, - {0x39, "VOIDCODEPOS"}, - {0x3A, "CALL_METHOD_THREAD_POINTER"}, - {0x3B, "ENDSWITCH"}, - {0x3C, "CLEAR_VARIABLE_FIELD"}, - {0x3D, "DIV"}, - {0x3E, "CALL_FAR_METHOD_CHILD_THEAD"}, - {0x3F, "GET_USHORT"}, - {0x40, "JMP_TRUE"}, - {0x41, "GET_SELF"}, - {0x42, "CALL_FAR_FUNC_THREAD"}, - {0x43, "CALL_LOCAL_FUNC_THREAD"}, - {0x44, "SET_LOCAL_VARIABLE_FIELD_CACHED0"}, - {0x45, "SET_LOCAL_VARIABLE_FIELD_CACHED"}, - {0x46, "PLUS"}, - {0x47, "BOOL_COMPLEMENT"}, - {0x48, "CALL_METHOD_POINTER"}, - {0x49, "INC"}, - {0x4A, "REMOVE_LOCAL_VARIABLES"}, - {0x4B, "JMP_EXPR_FALSE"}, - {0x4C, "SWITCH"}, - {0x4D, "CLEAR_PARAMS"}, - {0x4E, "EVAL_LOCAL_VARIABLE_REF_CACHED0"}, - {0x4F, "EVAL_LOCAL_VARIABLE_REF_CACHED"}, - {0x50, "CALL_LOCAL_METHOD"}, - {0x51, "EVAL_FIELD_VARIABLE"}, - {0x52, "EVAL_FIELD_VARIABLE_REF"}, - {0x53, "GET_STRING"}, - {0x54, "CALL_FUNC_POINTER"}, - {0x55, "EVAL_LEVEL_FIELD_VARIABLE"}, - {0x56, "GET_VECTOR"}, - {0x57, "ENDON"}, - {0x58, "GREATEREQUAL"}, - {0x59, "GET_SELF_OBJ"}, - {0x5A, "SET_ANIM_FIELD_VARIABLE_FIELD"}, - {0x5B, "SET_VARIABLE_FIELD"}, - {0x5C, "CALL_LOCAL_FUNC2"}, - {0x5D, "CALL_LOCAL_FUNC"}, - {0x5E, "EVAL_LOCAL_ARRAY_REF_CACHED0"}, - {0x5F, "EVAL_LOCAL_ARRAY_REF_CACHED"}, - {0x60, "GET_FAR_FUNC"}, - {0x61, "LESS"}, - {0x62, "GET_GAME_REF"}, - {0x63, "WAITFRAME"}, - {0x64, "WAITTILLFRAMEEND"}, - {0x65, "SAFE_SET_VARIABLE_FIELD_CACHED0"}, - {0x66, "SAFE_SET_VARIABLE_FIELD_CACHED"}, - {0x67, "CALL_METHOD_CHILD_THREAD_POINTER"}, - {0x68, "GET_LEVEL"}, - {0x69, "NOTIFY"}, - {0x6A, "DEC_TOP"}, - {0x6B, "SHIFT_LEFT"}, - {0x6C, "CALL_LOCAL_METHOD_THREAD"}, - {0x6D, "CALL_LOCAL_METHOD_CHILD_THREAD"}, - {0x6E, "GREATER"}, - {0x6F, "EVAL_LOCAL_VARIABLE_CACHED0"}, - {0x70, "EVAL_LOCAL_VARIABLE_CACHED1"}, - {0x71, "EVAL_LOCAL_VARIABLE_CACHED2"}, - {0x72, "EVAL_LOCAL_VARIABLE_CACHED3"}, - {0x73, "EVAL_LOCAL_VARIABLE_CACHED4"}, - {0x74, "EVAL_LOCAL_VARIABLE_CACHED5"}, - {0x75, "EVAL_LOCAL_VARIABLE_CACHED"}, - {0x76, "SAFE_SET_WAITTILL_VARIABLE_FIELD_CACHED"}, - {0x77, "JMP"}, - {0x78, "CALL_FUNC_THREAD_POINTER"}, - {0x79, "GET_ZERO"}, - {0x7A, "WAIT"}, - {0x7B, "MINUS"}, - {0x7C, "SET_SELF_FIELD_VARIABLE_FIELD"}, - {0x7D, "EVAL_NEW_LOCAL_VARIABLE_REF_CACHED0"}, - {0x7E, "MULT"}, - {0x7F, "CREATE_LOCAL_VARIABLE"}, - {0x80, "CALL_LOCAL_FUNC_CHILD_THREAD"}, - {0x81, "GET_INT"}, - {0x82, "MOD"}, - {0x83, "EVAL_ANIM_FIELD_VARIABLE_REF"}, - {0x84, "GET_BUILTIN_FUNC"}, - {0x85, "GET_GAME"}, - {0x86, "WAITTILL"}, - {0x87, "DEC"}, - {0x88, "EVAL_LOCAL_VARIABLE_OBJECT_CACHED"}, - {0x89, "PRE_CALL"}, - {0x8A, "GET_ANIM"}, - {0x8B, "GET_UNDEFINED"}, - {0x8C, "EVAL_LEVEL_FIELD_VARIABLE_REF"}, - {0x8D, "GET_ANIM_OBJ"}, - {0x8E, "GET_LEVEL_OBJ"}, - {0x8F, "BIT_EXOR"}, - {0x90, "EQUALITY"}, - {0x91, "CLEAR_ARRAY"}, - {0x92, "JMP_BACK"}, - {0x93, "GET_ANIMATION"}, - {0x94, "EVAL_ANIM_FIELD_VARIABLE"}, - {0x95, "GET_ANIMTREE"}, - {0x96, "GET_ISTRING"}, - {0x97, "EVAL_ARRAY_REF"}, - {0x98, "EVAL_SELF_FIELD_VARIABLE_REF"}, - {0x99, "GET_NBYTE"}, - {0x9A, "GET_BUILTIN_METHOD"}, - {0x9B, "CALL_BUILTIN_METHOD_POINTER"}, - {0x9C, "EVAL_ARRAY"}, - {0x9D, "VECTOR"}, - {0x9E, "CALL_FAR_METHOD"}, - {0x9F, "EVAL_LOCAL_ARRAY_CACHED"}, - {0xA0, "GET_BYTE"}, - {0xA1, "CALL_FUNC_CHILD_THREAD_POINTER"}, - {0xA2, "BIT_OR"}, - {0xA3, "ADD_ARRAY"}, - {0xA4, "WAITTILLMATCH2"}, - {0xA5, "WAITTILLMATCH"}, - {0xA6, "GET_LOCAL_FUNC"}, - {0xA7, "GET_NUSHORT"}, - {0xA8, "SHIFT_RIGHT"}, - {0xA9, "CALL_BUILTIN_METHOD_0"}, - {0xAA, "CALL_BUILTIN_METHOD_1"}, - {0xAB, "CALL_BUILTIN_METHOD_2"}, - {0xAC, "CALL_BUILTIN_METHOD_3"}, - {0xAD, "CALL_BUILTIN_METHOD_4"}, - {0xAE, "CALL_BUILTIN_METHOD_5"}, - {0xAF, "CALL_BUILTIN_METHOD"}, - {0xB0, "JMP_FALSE"}, - }; - auto compiler = ::gsc::compiler(); auto assembler = ::gsc::assembler(); std::unordered_map main_handles; std::unordered_map init_handles; std::unordered_map loaded_scripts; + std::unordered_map functions; + std::optional gsc_error; char* allocate_buffer(size_t size) { @@ -416,24 +264,32 @@ namespace gsc } } + bool force_error_print = false; void* vm_error_stub(void* a1) { - if (!developer_script->current.enabled) + if (!developer_script->current.enabled || force_error_print) { return utils::hook::invoke(0x140614670, a1); } + force_error_print = false; + console::warn("*********** script runtime error *************\n"); const auto opcode_id = *reinterpret_cast(0x14BAA93E8); const auto opcode = get_opcode_name(opcode_id); + const std::string error_str = gsc_error.has_value() + ? utils::string::va(": %s", gsc_error.value().data()) + : ""; if (opcode.has_value()) { - console::warn("while processing instruction %s\n", opcode.value().data()); + console::warn("while processing instruction %s%s\n", + opcode.value().data(), error_str.data()); } else { - console::warn("while processing instruction 0x%X\n", opcode_id); + console::warn("while processing instruction 0x%X%s\n", + opcode_id, error_str.data()); } print_callstack(); @@ -446,6 +302,42 @@ namespace gsc game::Com_Error(game::ERR_DROP, "LinkFile: unknown function in script '%s.gsc'", scripting::current_file.data()); } + + void register_gsc_functions_stub(void* a1, void* a2) + { + utils::hook::invoke(0x140509F20, a1, a2); + for (const auto& func : functions) + { + game::Scr_RegisterFunction(func.second, 0, func.first); + } + } + + scripting::script_value get_argument(int index) + { + if (index >= static_cast(game::scr_VmPub->outparamcount)) + { + return {}; + } + + const auto value = &game::scr_VmPub->top[-index]; + + return scripting::script_value(*value); + } + + auto function_id_start = 0x320; + void add_function(const std::string& name, scripting::script_function function) + { + const auto id = ++function_id_start; + scripting::function_map[name] = id; + functions[id] = function; + } + + void set_gsc_error(const std::string& error) + { + force_error_print = true; + gsc_error = error; + game::Scr_ErrorInternal(); + } } game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/) @@ -510,6 +402,42 @@ namespace gsc utils::hook::call(0x1405BC583, unknown_function_stub); utils::hook::call(0x1405BC5CF, unknown_function_stub); + utils::hook::call(0x1405BCBAB, register_gsc_functions_stub); + utils::hook::set(0x1405BC7BC, 0x1000); // change builtin func count + +#define RVA(ptr) static_cast(reinterpret_cast(ptr) - 0x140000000) + std::memcpy(&func_table, reinterpret_cast(0x14B153F90), 0x1900); + utils::hook::set(0x1405BC7C2 + 4, RVA(&func_table)); + utils::hook::inject(0x1405BCB78 + 3, &func_table); + utils::hook::set(0x1405CA678 + 4, RVA(&func_table)); + + add_function("replacefunc", [](const game::scr_entref_t ref) + { + try + { + const auto what = get_argument(0).get_raw(); + const auto with = get_argument(1).get_raw(); + + if (what.type != game::SCRIPT_FUNCTION) + { + set_gsc_error("replaceFunc: parameter 0 must be a function"); + return; + } + + if (with.type != game::SCRIPT_FUNCTION) + { + set_gsc_error("replaceFunc: parameter 0 must be a function"); + return; + } + + notifies::set_gsc_hook(what.u.codePosValue, with.u.codePosValue); + } + catch (const std::exception& e) + { + set_gsc_error(utils::string::va("replaceFunc: %s", e.what())); + } + }); + scripting::on_shutdown([](int free_scripts) { if (free_scripts) diff --git a/src/client/component/gsc.hpp b/src/client/component/gsc.hpp index 79444862..12277297 100644 --- a/src/client/component/gsc.hpp +++ b/src/client/component/gsc.hpp @@ -2,6 +2,7 @@ namespace gsc { + extern void* func_table[0x1000]; + game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/); } - diff --git a/src/client/component/notifies.cpp b/src/client/component/notifies.cpp index d2ecb7f3..3babf96c 100644 --- a/src/client/component/notifies.cpp +++ b/src/client/component/notifies.cpp @@ -12,15 +12,23 @@ namespace notifies { - std::unordered_map vm_execute_hooks; bool hook_enabled = true; namespace { + struct gsc_hook_t + { + bool is_lua_hook{}; + const char* target_pos{}; + sol::protected_function lua_function; + }; + + std::unordered_map vm_execute_hooks; utils::hook::detour scr_entity_damage_hook; std::vector entity_damage_callbacks; char empty_function[2] = {0x32, 0x34}; // CHECK_CLEAR_PARAMS, END + const char* target_function = nullptr; unsigned int local_id_to_entity(unsigned int local_id) { @@ -43,21 +51,30 @@ namespace notifies } const auto& hook = vm_execute_hooks[pos]; - const auto state = hook.lua_state(); - - const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId); - - std::vector args; - - const auto top = game::scr_function_stack->top; - - for (auto* value = top; value->type != game::SCRIPT_END; --value) + if (hook.is_lua_hook) { - args.push_back(scripting::lua::convert(state, *value)); - } + const auto& function = hook.lua_function; + const auto state = function.lua_state(); - const auto result = hook(self, sol::as_args(args)); - scripting::lua::handle_error(result); + const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId); + + std::vector args; + + const auto top = game::scr_function_stack->top; + + for (auto* value = top; value->type != game::SCRIPT_END; --value) + { + args.push_back(scripting::lua::convert(state, *value)); + } + + const auto result = function(self, sol::as_args(args)); + scripting::lua::handle_error(result); + target_function = empty_function; + } + else + { + target_function = hook.target_pos; + } return true; } @@ -89,7 +106,8 @@ namespace notifies a.bind(replace); a.popad64(); - a.mov(r14, reinterpret_cast(empty_function)); + a.mov(rax, qword_ptr(reinterpret_cast(&target_function))); + a.mov(r14, rax); a.jmp(end); } @@ -184,12 +202,39 @@ namespace notifies entity_damage_callbacks.clear(); } + void set_lua_hook(const char* pos, const sol::protected_function& callback) + { + gsc_hook_t hook; + hook.is_lua_hook = true; + hook.lua_function = callback; + vm_execute_hooks[pos] = hook; + } + + void set_gsc_hook(const char* source, const char* target) + { + gsc_hook_t hook; + hook.is_lua_hook = false; + hook.target_pos = target; + vm_execute_hooks[source] = hook; + } + + void clear_hook(const char* pos) + { + vm_execute_hooks.erase(pos); + } + + size_t get_hook_count() + { + return vm_execute_hooks.size(); + } + class component final : public component_interface { public: void post_unpack() override { - utils::hook::jump(0x1405C90A5, utils::hook::assemble(vm_execute_stub), true); + const auto a = utils::hook::assemble(vm_execute_stub); + utils::hook::jump(0x1405C90A5, a, true); scr_entity_damage_hook.create(0x1404BD2E0, scr_entity_damage_stub); } diff --git a/src/client/component/notifies.hpp b/src/client/component/notifies.hpp index 61a1dd29..9ea66a9c 100644 --- a/src/client/component/notifies.hpp +++ b/src/client/component/notifies.hpp @@ -2,9 +2,13 @@ namespace notifies { - extern std::unordered_map vm_execute_hooks; extern bool hook_enabled; + void set_lua_hook(const char* pos, const sol::protected_function&); + void set_gsc_hook(const char* source, const char* target); + void clear_hook(const char* pos); + size_t get_hook_count(); + void add_entity_damage_callback(const sol::protected_function&); void clear_callbacks(); } \ No newline at end of file diff --git a/src/client/game/scripting/function_tables.cpp b/src/client/game/scripting/function_tables.cpp index 38647de3..84339ef7 100644 --- a/src/client/game/scripting/function_tables.cpp +++ b/src/client/game/scripting/function_tables.cpp @@ -2061,6 +2061,7 @@ namespace scripting {"export", 13703}, {"animation", 70}, {"spammed_model", 49508}, + {"vehicletype", 1282}, // script {"script_flag", 31190}, @@ -2102,5 +2103,11 @@ namespace scripting {"script_drone", 31152}, {"script_health", 31247}, {"script_friendname", 31217}, + {"script_airspeed", 31027}, + {"script_missiles", 31293}, + {"script_spotlight", 31447}, + {"script_team", 31474}, + {"script_ai_invulnerable", 31024}, + {"script_mp_style_helicopter", 31353}, }; } diff --git a/src/client/game/scripting/functions.cpp b/src/client/game/scripting/functions.cpp index 6bf72ac0..5b7720f1 100644 --- a/src/client/game/scripting/functions.cpp +++ b/src/client/game/scripting/functions.cpp @@ -1,6 +1,8 @@ #include #include "functions.hpp" +#include "../../component/gsc.hpp" + #include namespace scripting @@ -59,7 +61,7 @@ namespace scripting script_function get_function_by_index(const unsigned index) { - static const auto function_table = 0x14B153F90; + static const auto function_table = &gsc::func_table; static const auto method_table = 0x14B155890; if (index < 0x320) diff --git a/src/client/game/scripting/lua/context.cpp b/src/client/game/scripting/lua/context.cpp index e673e0fd..5939887e 100644 --- a/src/client/game/scripting/lua/context.cpp +++ b/src/client/game/scripting/lua/context.cpp @@ -533,18 +533,18 @@ namespace scripting::lua const std::string function_name, const sol::protected_function& function) { const auto pos = get_function_pos(filename, function_name); - notifies::vm_execute_hooks[pos] = function; + notifies::set_lua_hook(pos, function); auto detour = sol::table::create(function.lua_state()); detour["disable"] = [pos]() { - notifies::vm_execute_hooks.erase(pos); + notifies::clear_hook(pos); }; - detour["enable"] = [pos, function]() + detour["enable"] = [&]() { - notifies::vm_execute_hooks[pos] = function; + notifies::set_lua_hook(pos, function); }; detour["invoke"] = [filename, function_name](const entity& entity, const sol::this_state s, sol::variadic_args va) diff --git a/src/client/game/scripting/lua/value_conversion.cpp b/src/client/game/scripting/lua/value_conversion.cpp index c3a76b09..d9980827 100644 --- a/src/client/game/scripting/lua/value_conversion.cpp +++ b/src/client/game/scripting/lua/value_conversion.cpp @@ -144,11 +144,10 @@ namespace scripting::lua game::VariableValue convert_function(sol::lua_value value) { const auto function = value.as(); - const auto index = (char*)notifies::vm_execute_hooks.size() + 1; + const auto index = reinterpret_cast(notifies::get_hook_count() + 1); + notifies::set_lua_hook(index, function); - notifies::vm_execute_hooks[index] = function; - - game::VariableValue func; + game::VariableValue func{}; func.type = game::SCRIPT_FUNCTION; func.u.codePosValue = index; diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 731b3455..8aaa3d0d 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -59,7 +59,7 @@ namespace game struct EntityState { - char entityNum; + uint16_t entityNum; }; enum scr_string_t @@ -75,25 +75,33 @@ namespace game struct gentity_s { - EntityState s; - char __pad0[0x1B]; + char __pad0[26]; vec3_t origin; - char __pad1[0x98]; + char __pad1[100]; + EntityState s; + char __pad2[50]; Bounds box; - char __pad2[0x4]; + char __pad3[4]; Bounds absBox; - char __pad3[0x24]; + char __pad4[36]; gclient_s* client; - char __pad4[0x30]; + char __pad5[48]; scr_string_t script_classname; - char __pad5[0x18]; + char __pad6[24]; char flags; - char __pad6[0x188]; + char __pad7[395]; }; // size = 760 //auto a = sizeof(gentity_s); static_assert(sizeof(gentity_s) == 760); + static_assert(offsetof(gentity_s, origin) == 28); + static_assert(offsetof(gentity_s, box) == 192); + static_assert(offsetof(gentity_s, absBox) == 220); + static_assert(offsetof(gentity_s, client) == 280); + static_assert(offsetof(gentity_s, script_classname) == 336); + static_assert(offsetof(gentity_s, flags) == 364); + static_assert(offsetof(gentity_s, s) == 140); struct pathlink_s { diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index c1d74b79..1952f045 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -128,6 +128,7 @@ namespace game WEAK symbol Scr_GetNumParam{0x1405C7940}; WEAK symbol Scr_GetFunctionHandle{0x1405BCD50}; WEAK symbol Scr_ExecThread{0x1405C6F40}; + WEAK symbol Scr_RegisterFunction{0x1405BC7B0}; WEAK symbol VM_Execute{0x1405C8DB0};