From 349894471422a55ff9af9d818fb4c645f0a035c2 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 16 Jan 2019 16:19:21 +0100 Subject: [PATCH] Scripting progress --- src/game/game.cpp | 8 ++ src/game/game.hpp | 5 ++ src/module/notification.cpp | 37 ++++++--- src/module/notification.hpp | 2 +- src/module/scheduler.cpp | 51 ++++++++++++- src/module/scheduler.hpp | 7 ++ src/module/scripting.cpp | 104 ++++++++++++++++++------- src/module/scripting.hpp | 33 +++++++- src/utils/chain.hpp | 147 ++++++++++++++++++++++++++++++++++++ 9 files changed, 349 insertions(+), 45 deletions(-) create mode 100644 src/utils/chain.hpp diff --git a/src/game/game.cpp b/src/game/game.cpp index 4aa9fb6..2a33277 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -7,6 +7,8 @@ namespace game { Cmd_AddCommand_t Cmd_AddCommand; + Com_Error_t Com_Error; + Conbuf_AppendText_t Conbuf_AppendText; DB_LoadXAssets_t DB_LoadXAssets; @@ -26,6 +28,8 @@ namespace game short* scrVarGlob; char** scrMemTreePub; + unsigned int* levelEntityId; + void AddRefToValue(VariableValue* value) { if (value->type == SCRIPT_OBJECT) @@ -92,6 +96,8 @@ namespace game native::Cmd_AddCommand = native::Cmd_AddCommand_t(SELECT_VALUE(0x558820, 0x545DF0, 0)); + native::Com_Error = native::Com_Error_t(SELECT_VALUE(0x425540, 0x555450, 0x4D93F0)); + native::Conbuf_AppendText = native::Conbuf_AppendText_t(SELECT_VALUE(0x4C84E0, 0x5CF610, 0x53C790)); native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020, 0x44F770)); @@ -110,5 +116,7 @@ namespace game native::scrVarGlob = reinterpret_cast(SELECT_VALUE(0x19AFC80, 0x1E72180, 0x1D3C800)); native::scrMemTreePub = reinterpret_cast(SELECT_VALUE(0x196FB00, 0x1E32000, 0x1C152A4)); + + native::levelEntityId = reinterpret_cast(SELECT_VALUE(0x1BCBCA4, 0x208E1A4, 0x1CD873C)); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 4b67fc2..57eb1c0 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -12,6 +12,9 @@ namespace game typedef void (*Cmd_AddCommand_t)(const char* cmdName, void (*function)(), cmd_function_t* allocedCmd); extern Cmd_AddCommand_t Cmd_AddCommand; + typedef void (*Com_Error_t)(int code, const char *fmt, ...); + extern Com_Error_t Com_Error; + typedef void (*Conbuf_AppendText_t)(const char* message); extern Conbuf_AppendText_t Conbuf_AppendText; @@ -37,6 +40,8 @@ namespace game extern short* scrVarGlob; extern char** scrMemTreePub; + extern unsigned int* levelEntityId; + void AddRefToValue(VariableValue* value); scr_entref_t Scr_GetEntityIdRef(unsigned int id); diff --git a/src/module/notification.cpp b/src/module/notification.cpp index a0d479e..c0c58f2 100644 --- a/src/module/notification.cpp +++ b/src/module/notification.cpp @@ -2,6 +2,8 @@ #include "loader/module_loader.hpp" #include "notification.hpp" #include "utils/hook.hpp" +#include "utils/string.hpp" +#include "scheduler.hpp" std::mutex notification::mutex_; std::vector> notification::callbacks_; @@ -45,7 +47,7 @@ void notification::dispatch(event* event) copy = callbacks_; } - for(const auto& callback : copy) + for (const auto& callback : copy) { callback(event); } @@ -54,18 +56,29 @@ void notification::dispatch(event* event) void notification::vm_notify_stub(const unsigned int notify_id, const unsigned short type, game::native::VariableValue* stack) { - event e; - e.name = game::native::SL_ConvertToString(type); - e.entity = game::native::Scr_GetEntityIdRef(notify_id); - - for (auto value = stack; value->type != game::native::SCRIPT_END; --value) - { - e.arguments.emplace_back(*value); - } - - dispatch(&e); - game::native::VM_Notify(notify_id, type, stack); + + try + { + event e; + e.name = game::native::SL_ConvertToString(type); + e.entity_id = notify_id; + + if (e.name == "touch") return; // Skip that for now + + //printf("%X %X: %s\n", e.entity_id, *game::native::levelEntityId, e.name.data()); + + for (auto value = stack; value->type != game::native::SCRIPT_END; --value) + { + e.arguments.emplace_back(*value); + } + + dispatch(&e); + } + catch (std::exception& e) + { + scheduler::error(e.what(), 5); + } } REGISTER_MODULE(notification) diff --git a/src/module/notification.hpp b/src/module/notification.hpp index a3d9385..b205623 100644 --- a/src/module/notification.hpp +++ b/src/module/notification.hpp @@ -9,7 +9,7 @@ public: { public: std::string name; - game::native::scr_entref_t entity; + unsigned int entity_id; //std::vector arguments; std::vector arguments; }; diff --git a/src/module/scheduler.cpp b/src/module/scheduler.cpp index a5b1ee3..fafcc52 100644 --- a/src/module/scheduler.cpp +++ b/src/module/scheduler.cpp @@ -1,7 +1,11 @@ #include #include "scheduler.hpp" +#include "utils/string.hpp" +#include "game/structs.hpp" +#include "game/game.hpp" std::mutex scheduler::mutex_; +std::queue> scheduler::errors_; std::vector> scheduler::callbacks_; std::vector> scheduler::single_callbacks_; @@ -17,7 +21,23 @@ void scheduler::once(const std::function& callback) single_callbacks_.push_back(callback); } -void scheduler::execute() +void scheduler::error(const std::string& message, int level) +{ + std::lock_guard _(mutex_); + errors_.emplace(message, level); +} + +__declspec(naked) void scheduler::execute() +{ + __asm + { + call execute_error + call execute_safe + retn + } +} + +void scheduler::execute_safe() { std::vector> callbacks_copy; std::vector> single_callbacks_copy; @@ -40,6 +60,35 @@ void scheduler::execute() } } +void scheduler::execute_error() +{ + const char* message; + int level; + + if(get_next_error(&message, &level) && message) + { + game::native::Com_Error(level, "%s", message); + } +} + +bool scheduler::get_next_error(const char** error_message, int* error_level) +{ + std::lock_guard _(mutex_); + if(errors_.empty()) + { + *error_message = nullptr; + return false; + } + + const auto error = errors_.front(); + errors_.pop(); + + *error_level = error.second; + *error_message = utils::string::va("%s", error.first.data()); + + return true; +} + void scheduler::pre_destroy() { std::lock_guard _(mutex_); diff --git a/src/module/scheduler.hpp b/src/module/scheduler.hpp index 0024548..3e592be 100644 --- a/src/module/scheduler.hpp +++ b/src/module/scheduler.hpp @@ -8,10 +8,17 @@ public: static void once(const std::function& callback); static void execute(); + static void error(const std::string& message, int level); + void pre_destroy() override; private: static std::mutex mutex_; + static std::queue> errors_; static std::vector> callbacks_; static std::vector> single_callbacks_; + + static void execute_safe(); + static void execute_error(); + static bool get_next_error(const char** error_message, int* error_level); }; diff --git a/src/module/scripting.cpp b/src/module/scripting.cpp index 4e4853b..146e442 100644 --- a/src/module/scripting.cpp +++ b/src/module/scripting.cpp @@ -2,6 +2,7 @@ #include "notification.hpp" #include "utils/io.hpp" #include "utils/string.hpp" +#include "scheduler.hpp" utils::hook scripting::start_hook_; utils::hook scripting::stop_hook_; @@ -10,6 +11,35 @@ std::mutex scripting::mutex_; std::vector> scripting::start_callbacks_; std::vector> scripting::stop_callbacks_; +scripting::entity::entity(scripting* environment, const unsigned int entity_id) : environment_(environment), + entity_id_(entity_id) +{ +} + +void scripting::entity::on_notify(const std::string& event, + const std::function&)>& callback, + const bool is_volatile) +const +{ + event_listener listener; + listener.event = event; + listener.callback = callback; + listener.entity_id = this->entity_id_; + listener.is_volatile = is_volatile; + + this->environment_->add_event_listener(listener); +} + +unsigned int scripting::entity::get_entity_id() const +{ + return this->entity_id_; +} + +game::native::scr_entref_t scripting::entity::get_entity_reference() const +{ + return game::native::Scr_GetEntityIdRef(this->get_entity_id()); +} + scripting::variable::variable(game::native::VariableValue value) : value_(value) { game::native::AddRefToValue(&value); @@ -27,7 +57,17 @@ scripting::variable::operator game::native::VariableValue() const void scripting::post_start() { - on_start(std::bind(&scripting::initialize, this)); + on_start([this]() + { + try + { + this->initialize(); + } + catch (std::exception& e) + { + scheduler::error(e.what(), 5); + } + }); on_stop([this]() { this->chai_ = {}; @@ -56,21 +96,29 @@ void scripting::pre_destroy() stop_callbacks_.clear(); } +void scripting::add_event_listener(const event_listener& listener) +{ + this->event_listeners_.add(listener); +} + void scripting::initialize() { this->chai_ = std::make_unique(); + this->chai_->add(chaiscript::user_type(), "entity"); + this->chai_->add(chaiscript::fun(&entity::on_notify), "onNotify"); + this->chai_->add(chaiscript::fun([](const std::string& string) { printf("%s\n", string.data()); }), "print"); - this->chai_->add(chaiscript::fun( - [this](const std::function&)>& callback) - { - std::lock_guard _(mutex_); - this->callbacks_.push_back(callback); - }), "onNotify"); + this->chai_->add(chaiscript::fun([](const std::string& string) + { + MessageBoxA(nullptr, string.data(), nullptr, 0); + }), "alert"); + + const auto level_id = *game::native::levelEntityId; + this->chai_->add_global(chaiscript::var(entity(this, level_id)), "level"); this->load_scripts(); @@ -80,28 +128,26 @@ void scripting::initialize() for (const auto& argument : event->arguments) { - arguments.push_back(make_boxed(argument)); + arguments.push_back(this->make_boxed(argument)); } - decltype(this->callbacks_) copy; - { - std::lock_guard _(mutex_); - copy = this->callbacks_; - } - - for (const auto& callback : copy) + for (auto listener = this->event_listeners_.begin(); listener.is_valid(); ++listener) { try { - callback(event->name, arguments); + if (listener->event == event->name && listener->entity_id == event->entity_id) + { + if (listener->is_volatile) + { + this->event_listeners_.remove(listener); + } + + listener->callback(arguments); + } } - catch (chaiscript::exception::eval_error &e) + catch (chaiscript::exception::eval_error& e) { - printf("Failed to handle event: %s\n", e.pretty_print().data()); - } - catch (std::exception& e) - { - printf("Failed to handle event: %s\n", e.what()); + throw std::runtime_error(e.pretty_print()); } } }); @@ -119,13 +165,9 @@ void scripting::load_scripts() const { this->chai_->eval_file(script); } - catch (chaiscript::exception::eval_error &e) + catch (chaiscript::exception::eval_error& e) { - printf("Failed to load script %s: %s\n", script.data(), e.pretty_print().data()); - } - catch (std::exception& e) - { - printf("Failed to load script %s: %s\n", script.data(), e.what()); + throw std::runtime_error(e.pretty_print()); } } } @@ -146,6 +188,10 @@ chaiscript::Boxed_Value scripting::make_boxed(const game::native::VariableValue { return chaiscript::var(value.u.intValue); } + else if (value.type == game::native::SCRIPT_OBJECT) + { + return chaiscript::var(entity(this, value.u.entityId)); + } return {}; } diff --git a/src/module/scripting.hpp b/src/module/scripting.hpp index 9bc9487..7a979ee 100644 --- a/src/module/scripting.hpp +++ b/src/module/scripting.hpp @@ -2,10 +2,28 @@ #include "loader/module_loader.hpp" #include "game/game.hpp" #include "utils/hook.hpp" +#include "utils/chain.hpp" class scripting final : public module { public: + class entity final + { + public: + entity(scripting* environment, unsigned int entity_id); + + void on_notify(const std::string& event, + const std::function&)>& callback, + bool is_volatile) const; + + unsigned int get_entity_id() const; + game::native::scr_entref_t get_entity_reference() const; + + private: + scripting* environment_; + unsigned int entity_id_; + }; + class variable final { public: @@ -18,6 +36,15 @@ public: game::native::VariableValue value_; }; + class event_listener final + { + public: + std::string event = {}; + unsigned int entity_id = 0; + std::function&)> callback = {}; + bool is_volatile = false; + }; + void post_start() override; void post_load() override; void pre_destroy() override; @@ -27,12 +54,14 @@ public: private: std::unique_ptr chai_; - std::vector&)>> callbacks_; + utils::chain event_listeners_; + + void add_event_listener(const event_listener& listener); void initialize(); void load_scripts() const; - static chaiscript::Boxed_Value make_boxed(game::native::VariableValue value); + chaiscript::Boxed_Value make_boxed(game::native::VariableValue value); static utils::hook start_hook_; static utils::hook stop_hook_; diff --git a/src/utils/chain.hpp b/src/utils/chain.hpp new file mode 100644 index 0000000..bb55bdc --- /dev/null +++ b/src/utils/chain.hpp @@ -0,0 +1,147 @@ +namespace utils +{ + template + class chain + { + public: + class entry + { + private: + std::shared_ptr object_; + std::shared_ptr next_; + + public: + bool has_next() + { + return (this->next_.use_count() > 0); + } + + bool is_valid() + { + return (this->object_.use_count() > 0); + } + + void set(T object) + { + this->object_ = std::shared_ptr(new T()); + *this->object_.get() = object; + } + + std::shared_ptr get() + { + return this->object_; + } + + entry get_next() + { + if (this->has_next()) + { + return *(this->next_.get()); + } + else + { + return entry(); + } + } + + std::shared_ptr get_next_entry() + { + return this->next_; + } + + void set_next_entry(std::shared_ptr entry) + { + this->next_ = entry; + } + + T *operator->() + { + return (this->object_.get()); + } + + entry& operator++ () + { + *this = this->get_next(); + return *this; + } + + entry operator++ (int) + { + entry result = *this; + this->operator++(); + return result; + } + }; + + private: + std::mutex mutex_; + entry object_; + + public: + void add(const T& object) + { + std::lock_guard _(this->mutex_); + + if (!this->empty()) + { + // Create new chain entry + std::shared_ptr current_object = std::shared_ptr(new entry); + *current_object.get() = this->object_; + + // Add it to the chain + this->object_ = entry(); + this->object_.set_next_entry(current_object); + } + + this->object_.set(object); + } + + void remove(std::shared_ptr object) + { + std::lock_guard _(this->mutex_); + + if (!this->empty()) + { + if (this->object_.get().get() == object.get()) + { + this->object_ = this->object_.get_next(); + } + else if(this->object_.has_next()) + { + for (auto entry = this->object_; entry.is_valid(); ++entry) + { + auto next = entry.get_next(); + + if (next.is_valid() && next.get().get() == object.get()) + { + *entry.get_next_entry().get() = next.get_next(); + } + } + } + } + } + + void remove(entry entry) + { + if (entry.is_valid()) + { + this->remove(entry.get()); + } + } + + bool empty() + { + return !this->object_.is_valid(); + } + + entry begin() + { + return this->object_; + } + + void clear() + { + this->object_ = entry(); + } + }; +}