diff --git a/src/game/game.cpp b/src/game/game.cpp index b243252..7490658 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -47,6 +47,8 @@ namespace game int* g_script_error_level; jmp_buf* g_script_error; + scr_classStruct_t* g_classMap; + void AddRefToValue(VariableValue* value) { if (value->type == SCRIPT_OBJECT) @@ -69,15 +71,42 @@ namespace game } } + __declspec(naked) unsigned int find_variable_dedicated(unsigned int parentId, unsigned int name) + { + static DWORD func = 0x4E7ED0; + + __asm + { + mov eax, name + mov ecx, parentId + call func + retn + } + } + + unsigned int FindVariable(const unsigned int parentId, const unsigned int name) + { + if (is_dedi()) + { + return find_variable_dedicated(parentId, name); + } + else + { + return reinterpret_cast // + (SELECT_VALUE(0x4C4E70, 0x5651F0, 0x0))(parentId, name); + } + } + __declspec(naked) VariableValue get_entity_field_value_dedicated(unsigned int classnum, int entnum, int _offset) { + static DWORD func = 0x4F1400; + __asm { push _offset push entnum mov ecx, classnum - mov eax, 4F1400h - call eax + call func add esp, 8h retn } @@ -150,6 +179,34 @@ namespace game } } + __declspec(naked) int scr_set_object_field_dedicated(unsigned int classnum, int entnum, int _offset) + { + static DWORD func = 0x4B15C0; + + __asm + { + mov ecx, _offset + mov eax, entnum + push classnum + call func + add esp, 4h + retn + } + } + + int Scr_SetObjectField(const unsigned int classnum, const int entnum, const int offset) + { + if (is_dedi()) + { + return scr_set_object_field_dedicated(classnum, entnum, offset); + } + else + { + return reinterpret_cast // + (SELECT_VALUE(0x42CAD0, 0x52BCC0, 0x0))(classnum, entnum, offset); + } + } + const char* SL_ConvertToString(const unsigned int stringValue) { if (!stringValue) return nullptr; @@ -228,6 +285,8 @@ namespace game native::g_script_error_level = reinterpret_cast(SELECT_VALUE(0x1BEFCFC, 0x20B21FC, 0x1F5B058)); native::g_script_error = reinterpret_cast(SELECT_VALUE(0x1BF1D18, 0x20B4218, 0x1F5A818)); + native::g_classMap = reinterpret_cast(SELECT_VALUE(0x92D140, 0x8B4300, 0x7C0408)); + native::levelEntityId = reinterpret_cast(SELECT_VALUE(0x1BCBCA4, 0x208E1A4, 0x1CD873C)); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 624214f..d10333e 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -62,8 +62,12 @@ namespace game extern int* g_script_error_level; extern jmp_buf* g_script_error; + extern scr_classStruct_t* g_classMap; + void AddRefToValue(VariableValue* value); + unsigned int FindVariable(unsigned int parentId, unsigned int name); + VariableValue GetEntityFieldValue(unsigned int classnum, int entnum, int offset); void* MT_Alloc(int numBytes, int type); @@ -72,6 +76,7 @@ namespace game void Scr_ClearOutParams(); scr_entref_t Scr_GetEntityIdRef(unsigned int id); scr_call_t Scr_GetFunc(unsigned int index); + int Scr_SetObjectField(unsigned int classnum, int entnum, int offset); const char* SL_ConvertToString(unsigned int stringValue); unsigned int SL_GetString(const char* str, unsigned int user); diff --git a/src/game/structs.hpp b/src/game/structs.hpp index 43e98df..4633a1d 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -412,7 +412,7 @@ namespace game scr_entref_raw raw; }; - typedef void(__cdecl * scr_call_t)(int entref); + typedef void (__cdecl * scr_call_t)(int entref); enum scriptType_e { @@ -422,7 +422,8 @@ namespace game SCRIPT_VECTOR = 4, SCRIPT_FLOAT = 5, SCRIPT_INTEGER = 6, - SCRIPT_END = 8, // Custom + SCRIPT_END = 8, + // Custom }; struct VariableStackBuffer @@ -453,38 +454,46 @@ namespace game scriptType_e type; }; - struct function_stack_t - { - const char *pos; - unsigned int localId; - unsigned int localVarCount; - VariableValue *top; - VariableValue *startTop; + struct function_stack_t + { + const char* pos; + unsigned int localId; + unsigned int localVarCount; + VariableValue* top; + VariableValue* startTop; }; - struct function_frame_t - { - function_stack_t fs; - int topType; + struct function_frame_t + { + function_stack_t fs; + int topType; }; - struct scrVmPub_t - { - unsigned int *localVars; - VariableValue *maxstack; - int function_count; - function_frame_t *function_frame; - VariableValue *top; + struct scrVmPub_t + { + unsigned int* localVars; + VariableValue* maxstack; + int function_count; + function_frame_t* function_frame; + VariableValue* top; /*bool debugCode; bool abort_on_error; bool terminal_error; - bool block_execution;*/ - unsigned int inparamcount; - unsigned int outparamcount; - unsigned int breakpointOutparamcount; - bool showError; - function_frame_t function_frame_start[32]; - VariableValue stack[2048]; + bool block_execution;*/ + unsigned int inparamcount; + unsigned int outparamcount; + unsigned int breakpointOutparamcount; + bool showError; + function_frame_t function_frame_start[32]; + VariableValue stack[2048]; + }; + + struct scr_classStruct_t + { + unsigned __int16 id; + unsigned __int16 entArrayId; + char charId; + const char* name; }; } } diff --git a/src/module/scripting.cpp b/src/module/scripting.cpp index 79d5630..d0a9b18 100644 --- a/src/module/scripting.cpp +++ b/src/module/scripting.cpp @@ -75,6 +75,16 @@ void scripting::entity::notify(const std::string& event, const std::vectorenvironment_->notify(event, this->get_entity_id(), arguments); } +void scripting::entity::set(const std::string& field, const chaiscript::Boxed_Value& value) +{ + this->environment_->set_entity_field(field, this->get_entity_id(), value); +} + +chaiscript::Boxed_Value scripting::entity::get(const std::string& field) +{ + return this->environment_->get_entity_field(field, this->get_entity_id()); +} + scripting::variable::variable(game::native::VariableValue value) : value_(value) { game::native::AddRefToValue(&value); @@ -90,6 +100,28 @@ scripting::variable::operator game::native::VariableValue() const return this->value_; } +scripting::stack_context::stack_context() +{ + this->inparamcount_ = game::native::scr_VmPub->inparamcount; + this->outparamcount_ = game::native::scr_VmPub->outparamcount; + this->top_ = game::native::scr_VmPub->top; + this->maxstack_ = game::native::scr_VmPub->maxstack; + + game::native::scr_VmPub->top = this->stack_; + game::native::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1]; + game::native::scr_VmPub->inparamcount = 0; + game::native::scr_VmPub->outparamcount = 0; +} + +scripting::stack_context::~stack_context() +{ + game::native::Scr_ClearOutParams(); + game::native::scr_VmPub->inparamcount = this->inparamcount_; + game::native::scr_VmPub->outparamcount = this->outparamcount_; + game::native::scr_VmPub->top = this->top_; + game::native::scr_VmPub->maxstack = this->maxstack_; +} + void scripting::post_start() { on_start([this]() @@ -158,16 +190,16 @@ void scripting::initialize() notification::listen([this](notification::event* event) { - std::vector arguments; - - for (const auto& argument : event->arguments) + try { - arguments.push_back(this->make_boxed(argument)); - } + std::vector arguments; - for (auto listener = this->event_listeners_.begin(); listener.is_valid(); ++listener) - { - try + for (const auto& argument : event->arguments) + { + arguments.push_back(this->make_boxed(argument)); + } + + for (auto listener = this->event_listeners_.begin(); listener.is_valid(); ++listener) { if (listener->event == event->name && listener->entity_id == event->entity_id) { @@ -179,11 +211,24 @@ void scripting::initialize() listener->callback(arguments); } } - catch (chaiscript::exception::eval_error& e) + + for (auto listener = this->generic_event_listeners_.begin(); listener.is_valid(); ++listener) { - throw std::runtime_error(e.pretty_print()); + if (listener->event == event->name) + { + if (listener->is_volatile) + { + this->generic_event_listeners_.remove(listener); + } + + listener->callback(entity(this, event->entity_id), arguments); + } } } + catch (chaiscript::exception::eval_error& e) + { + throw std::runtime_error(e.pretty_print()); + } }); } @@ -193,6 +238,9 @@ void scripting::initialize_entity() this->chai_->add(chaiscript::constructor(), "entity"); this->chai_->add(chaiscript::constructor(), "entity"); + this->chai_->add(chaiscript::fun(&entity::get), "get"); + this->chai_->add(chaiscript::fun(&entity::set), "set"); + this->chai_->add(chaiscript::fun(&entity::on_notify), "onNotify"); this->chai_->add(chaiscript::fun([](const entity& ent, const std::string& event, const std::function&)>& @@ -206,6 +254,32 @@ void scripting::initialize_entity() return lhs = rhs; }), "="); + this->chai_->add(chaiscript::fun([this](const std::string& event, + const std::function&)>& + callback) + { + generic_event_listener listener; + listener.event = event; + listener.is_volatile = false; + listener.callback = callback; + + this->generic_event_listeners_.add(listener); + }), "onNotify"); + + this->chai_->add(chaiscript::fun([this](const std::string& event, + const std::function&)>& + callback, const bool is_volatile) + { + generic_event_listener listener; + listener.event = event; + listener.is_volatile = is_volatile; + listener.callback = callback; + + this->generic_event_listeners_.add(listener); + }), "onNotify"); + // Notification this->chai_->add(chaiscript::fun(&entity::notify), "vectorNotify"); this->chai_->add(chaiscript::fun([](const entity& ent, const std::string& event) @@ -469,45 +543,92 @@ void scripting::stop_execution() } } -int scripting::get_field_id(const int classnum, const std::string& field) const +int scripting::get_field_id(const int classnum, const std::string& field) { - switch (classnum) + const auto field_name = utils::string::to_lower(field); + const auto class_id = game::native::g_classMap[classnum].id; + const auto field_str = game::native::SL_GetString(field_name.data(), 1); + const auto _ = gsl::finally([field_str]() { - case 0: // Entity - case 1: // HudElem - case 2: // Pathnode - case 3: // VehPathNode - case 4: // VehTrackSegment - case 6: // PIPElem + game::native::VariableUnion u{}; + u.stringValue = field_str; + game::native::RemoveRefToValue(game::native::SCRIPT_STRING, u); + }); - default: - return -1; + const auto offset = game::native::FindVariable(class_id, field_str); + if (offset) + { + const auto index = 4 * (offset + 0xC800 * (class_id & 1)); + return PINT(SELECT_VALUE(0x1A3BC80, 0x1EFE180, 0x1DC8800))[index]; } + + return -1; +} + +void scripting::set_entity_field(const std::string& field, const unsigned int entity_id, + const chaiscript::Boxed_Value& value) +{ + const auto entref = game::native::Scr_GetEntityIdRef(entity_id); + const int id = get_field_id(entref.raw.classnum, field); + + if (id != -1) + { + stack_context _; + this->push_param(value); + + game::native::scr_VmPub->outparamcount = game::native::scr_VmPub->inparamcount; + game::native::scr_VmPub->inparamcount = 0; + + if (!set_entity_field_safe(entref, id)) + { + throw std::runtime_error("Failed to set value for field '" + field + "'"); + } + } + else + { + this->entity_fields_[entity_id][field] = value; + } +} + +chaiscript::Boxed_Value scripting::get_entity_field(const std::string& field, const unsigned int entity_id) +{ + const auto entref = game::native::Scr_GetEntityIdRef(entity_id); + const auto id = this->get_field_id(entref.raw.classnum, field); + + if (id != -1) + { + stack_context _; + + game::native::VariableValue value{}; + if (!get_entity_field_safe(entref, id, &value)) + { + throw std::runtime_error("Failed to get value for field '" + field + "'"); + } + + const auto $ = gsl::finally([value]() + { + game::native::RemoveRefToValue(value.type, value.u); + }); + + return this->make_boxed(value); + } + else + { + const auto& map = this->entity_fields_[entity_id]; + const auto value = map.find(field); + if (value != map.end()) + { + return value->second; + } + } + + return {}; } void scripting::notify(const std::string& event, const unsigned int entity_id, std::vector arguments) { - - const auto old_args = game::native::scr_VmPub->inparamcount; - const auto old_params = game::native::scr_VmPub->outparamcount; - const auto old_stack_top = game::native::scr_VmPub->top; - const auto old_stack_end = game::native::scr_VmPub->maxstack; - - game::native::VariableValue stack[512]; - game::native::scr_VmPub->top = stack; - game::native::scr_VmPub->maxstack = &stack[ARRAYSIZE(stack) - 1]; - game::native::scr_VmPub->inparamcount = 0; - game::native::scr_VmPub->outparamcount = 0; - - const auto cleanup = gsl::finally([=]() - { - game::native::Scr_ClearOutParams(); - game::native::scr_VmPub->inparamcount = old_args; - game::native::scr_VmPub->outparamcount = old_params; - game::native::scr_VmPub->top = old_stack_top; - game::native::scr_VmPub->maxstack = old_stack_end; - }); + stack_context _; std::reverse(arguments.begin(), arguments.end()); for (const auto& argument : arguments) @@ -534,25 +655,7 @@ chaiscript::Boxed_Value scripting::call(const std::string& function, const unsig const auto function_ptr = game::native::Scr_GetFunc(function_index); - const auto old_args = game::native::scr_VmPub->inparamcount; - const auto old_params = game::native::scr_VmPub->outparamcount; - const auto old_stack_top = game::native::scr_VmPub->top; - const auto old_stack_end = game::native::scr_VmPub->maxstack; - - game::native::VariableValue stack[512]; - game::native::scr_VmPub->top = stack; - game::native::scr_VmPub->maxstack = &stack[ARRAYSIZE(stack) - 1]; - game::native::scr_VmPub->inparamcount = 0; - game::native::scr_VmPub->outparamcount = 0; - - const auto cleanup = gsl::finally([=]() - { - game::native::Scr_ClearOutParams(); - game::native::scr_VmPub->inparamcount = old_args; - game::native::scr_VmPub->outparamcount = old_params; - game::native::scr_VmPub->top = old_stack_top; - game::native::scr_VmPub->maxstack = old_stack_end; - }); + stack_context _; std::reverse(arguments.begin(), arguments.end()); for (const auto& argument : arguments) @@ -589,6 +692,38 @@ bool scripting::call_safe(const game::native::scr_call_t function, const game::n *game::native::g_script_error_level -= 1; return true; } + +bool scripting::set_entity_field_safe(game::native::scr_entref_t entref, int offset) +{ + *game::native::g_script_error_level += 1; + if (setjmp(game::native::g_script_error[*game::native::g_script_error_level])) + { + *game::native::g_script_error_level -= 1; + return false; + } + + game::native::Scr_SetObjectField(entref.raw.classnum, entref.raw.entnum, offset); + + *game::native::g_script_error_level -= 1; + return true; +} + +bool scripting::get_entity_field_safe(game::native::scr_entref_t entref, int offset, game::native::VariableValue* value) +{ + *game::native::g_script_error_level += 1; + if (setjmp(game::native::g_script_error[*game::native::g_script_error_level])) + { + value->type = game::native::SCRIPT_NONE; + value->u.intValue = 0; + *game::native::g_script_error_level -= 1; + return false; + } + + *value = game::native::GetEntityFieldValue(entref.raw.classnum, entref.raw.entnum, offset); + + *game::native::g_script_error_level -= 1; + return true; +} #pragma warning(pop) int scripting::find_function_index(const std::string& function, const bool prefer_global) @@ -682,7 +817,7 @@ void scripting::push_param(const chaiscript::Boxed_Value& value) const throw std::runtime_error("Invalid vector length. Size must be exactly 3"); } - const auto unbox_float = [&real_value, this](size_t index) -> float + const auto unbox_float = [&real_value, this](const size_t index) -> float { const auto value = real_value[index]; if (value.get_type_info() == typeid(float)) diff --git a/src/module/scripting.hpp b/src/module/scripting.hpp index 163bbec..c65e3e7 100644 --- a/src/module/scripting.hpp +++ b/src/module/scripting.hpp @@ -25,6 +25,9 @@ public: chaiscript::Boxed_Value call(const std::string& function, const std::vector& arguments) const; void notify(const std::string& event, const std::vector& arguments) const; + void set(const std::string& field, const chaiscript::Boxed_Value& value); + chaiscript::Boxed_Value get(const std::string& field); + private: scripting* environment_; unsigned int entity_id_; @@ -51,6 +54,14 @@ public: bool is_volatile = false; }; + class generic_event_listener final + { + public: + std::string event = {}; + std::function&)> callback = {}; + bool is_volatile = false; + }; + void post_start() override; void post_load() override; void pre_destroy() override; @@ -61,8 +72,26 @@ public: static void propagate_error(const std::exception& e); private: + class stack_context final + { + public: + stack_context(); + ~stack_context(); + + private: + game::native::VariableValue stack_[512]{}; + + game::native::VariableValue *maxstack_; + game::native::VariableValue *top_; + unsigned int inparamcount_; + unsigned int outparamcount_; + }; + std::unique_ptr chai_; utils::chain event_listeners_; + utils::chain generic_event_listeners_; + + std::unordered_map> entity_fields_; void add_event_listener(const event_listener& listener); @@ -82,7 +111,12 @@ private: static void start_execution(); static void stop_execution(); - int get_field_id(int classnum, const std::string& field) const; + int get_field_id(int classnum, const std::string& field); + void set_entity_field(const std::string& field, unsigned int entity_id, const chaiscript::Boxed_Value& value); + chaiscript::Boxed_Value get_entity_field(const std::string& field, unsigned int entity_id); + + static bool set_entity_field_safe(game::native::scr_entref_t entref, int offset); + static bool get_entity_field_safe(game::native::scr_entref_t entref, int offset, game::native::VariableValue* value); void notify(const std::string& event, unsigned int entity_id, std::vector arguments);