From 2def629d3c4f1897412edf6876810b13ba478ac5 Mon Sep 17 00:00:00 2001 From: Andreas Maerten <24669514+Yimura@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:19:52 +0100 Subject: [PATCH] feat(LuaMgr): Disable and Enable scripts from UI and prevent crash when renaming the `scripts/` dir. (#2493) --- src/file_manager.cpp | 8 +- src/file_manager/folder.cpp | 9 ++ src/file_manager/folder.hpp | 1 + src/lua/lua_manager.cpp | 199 ++++++++++++++++++++---- src/lua/lua_manager.hpp | 24 ++- src/lua/lua_module.cpp | 57 ++++--- src/lua/lua_module.hpp | 4 +- src/views/settings/view_lua_scripts.cpp | 115 ++++++++++---- 8 files changed, 326 insertions(+), 91 deletions(-) diff --git a/src/file_manager.cpp b/src/file_manager.cpp index 540e5c8d..3f30d098 100644 --- a/src/file_manager.cpp +++ b/src/file_manager.cpp @@ -44,15 +44,15 @@ namespace big std::filesystem::path file_manager::ensure_folder_exists(const std::filesystem::path folder_path) { - bool create_path = !std::filesystem::exists(folder_path); + bool create_path = !exists(folder_path); - if (!create_path && !std::filesystem::is_directory(folder_path)) + if (!create_path && !is_directory(folder_path)) { - std::filesystem::remove(folder_path); + remove(folder_path); create_path = true; } if (create_path) - std::filesystem::create_directory(folder_path); + create_directories(folder_path); return folder_path; } diff --git a/src/file_manager/folder.cpp b/src/file_manager/folder.cpp index d21557fa..b4becd9f 100644 --- a/src/file_manager/folder.cpp +++ b/src/file_manager/folder.cpp @@ -19,6 +19,15 @@ namespace big return file(m_folder_path / file_path); } + folder folder::get_folder(std::filesystem::path folder_path) const + { + if (folder_path.is_absolute()) + throw std::invalid_argument("folder#get_folder requires a relative path."); + if (folder_path.string().contains("..")) + throw std::invalid_argument("Relative path traversal is not allowed, refrain from using \"..\" in file paths."); + return folder(m_folder_path / folder_path); + } + const std::filesystem::path folder::get_path() const { return m_folder_path; diff --git a/src/file_manager/folder.hpp b/src/file_manager/folder.hpp index c836a07e..166158b3 100644 --- a/src/file_manager/folder.hpp +++ b/src/file_manager/folder.hpp @@ -11,6 +11,7 @@ namespace big folder(const std::filesystem::path& folder_path = ""); file get_file(std::filesystem::path file_path) const; + folder get_folder(std::filesystem::path folder_path) const; const std::filesystem::path get_path() const; private: diff --git a/src/lua/lua_manager.cpp b/src/lua/lua_manager.cpp index 9a6f8f8a..33c3e637 100644 --- a/src/lua/lua_manager.cpp +++ b/src/lua/lua_manager.cpp @@ -4,9 +4,29 @@ namespace big { + std::optional move_file_relative_to_folder(const std::filesystem::path& original, const std::filesystem::path& target, const std::filesystem::path& file) + { + // keeps folder hierarchy intact + const auto new_module_path = target / relative(file, original); + g_file_manager.ensure_file_can_be_created(new_module_path); + + try + { + rename(file, new_module_path); + } + catch(const std::filesystem::filesystem_error& e) + { + LOG(FATAL) << "Failed to move Lua file: " << e.what(); + + return std::nullopt; + } + return { new_module_path }; + } + lua_manager::lua_manager(folder scripts_folder, folder scripts_config_folder) : m_scripts_folder(scripts_folder), - m_scripts_config_folder(scripts_config_folder) + m_scripts_config_folder(scripts_config_folder), + m_disabled_scripts_folder(scripts_folder.get_folder("./disabled")) { m_wake_time_changed_scripts_check = std::chrono::high_resolution_clock::now() + m_delay_between_changed_scripts_check; @@ -22,6 +42,81 @@ namespace big g_lua_manager = nullptr; } + void lua_manager::disable_all_modules() + { + std::vector script_paths; + + { + std::lock_guard guard(m_module_lock); + for (auto& module : m_modules) + { + script_paths.push_back(module->module_path()); + + module.reset(); + } + m_modules.clear(); + } + + for (const auto& script_path : script_paths) + { + const auto new_module_path = move_file_relative_to_folder(m_scripts_folder.get_path(), m_disabled_scripts_folder.get_path(), script_path); + if (new_module_path) + { + load_module(*new_module_path); + } + } + } + + void lua_manager::enable_all_modules() + { + std::vector script_paths; + + { + std::lock_guard guard(m_disabled_module_lock); + for (auto& module : m_disabled_modules) + { + script_paths.push_back(module->module_path()); + + module.reset(); + } + m_disabled_modules.clear(); + } + + for (const auto& script_path : script_paths) + { + const auto new_module_path = move_file_relative_to_folder(m_disabled_scripts_folder.get_path(), m_scripts_folder.get_path(), script_path); + if (new_module_path) + { + load_module(*new_module_path); + } + } + } + + void lua_manager::load_all_modules() + { + for (const auto& entry : std::filesystem::recursive_directory_iterator(m_scripts_folder.get_path(), std::filesystem::directory_options::skip_permission_denied)) + if (entry.is_regular_file() && entry.path().extension() == ".lua") + load_module(entry.path()); + } + + void lua_manager::unload_all_modules() + { + { + std::lock_guard guard(m_module_lock); + + for (auto& module : m_modules) + module.reset(); + m_modules.clear(); + } + { + std::lock_guard guard(m_disabled_module_lock); + + for (auto& module : m_disabled_modules) + module.reset(); + m_disabled_modules.clear(); + } + } + bool lua_manager::has_gui_to_draw(rage::joaat_t tab_hash) { std::lock_guard guard(m_module_lock); @@ -78,6 +173,49 @@ namespace big } } + std::weak_ptr lua_manager::enable_module(rage::joaat_t module_id) + { + if (auto module = get_disabled_module(module_id).lock()) + { + const auto module_path = module->module_path(); + + // unload module + std::lock_guard guard(m_disabled_module_lock); + std::erase_if(m_disabled_modules, [module_id](auto& module) { + return module_id == module->module_id(); + }); + + const auto new_module_path = move_file_relative_to_folder(m_disabled_scripts_folder.get_path(), m_scripts_folder.get_path(), module_path); + if (new_module_path) + { + return load_module(*new_module_path); + } + } + + return {}; + } + + std::weak_ptr lua_manager::disable_module(rage::joaat_t module_id) + { + if (auto module = get_module(module_id).lock()) + { + const auto module_path = module->module_path(); + + // unload module + std::lock_guard guard(m_disabled_module_lock); + std::erase_if(m_modules, [module_id](auto& module) { + return module_id == module->module_id(); + }); + + const auto new_module_path = move_file_relative_to_folder(m_scripts_folder.get_path(), m_disabled_scripts_folder.get_path(), module_path); + if (new_module_path) + { + return load_module(*new_module_path); + } + } + return {}; + } + void lua_manager::unload_module(rage::joaat_t module_id) { std::lock_guard guard(m_module_lock); @@ -87,33 +225,41 @@ namespace big }); } - void lua_manager::load_module(const std::filesystem::path& module_path) + std::weak_ptr lua_manager::load_module(const std::filesystem::path& module_path) { if (!std::filesystem::exists(module_path)) { LOG(WARNING) << reinterpret_cast(module_path.u8string().c_str()) << " does not exist in the filesystem. Not loading it."; - return; + return {}; } - std::lock_guard guard(m_module_lock); - const auto module_name = module_path.filename().string(); - const auto id = rage::joaat(module_name); + std::lock_guard guard(m_module_lock); for (const auto& module : m_modules) { if (module->module_id() == id) { LOG(WARNING) << "Module with the name " << module_name << " already loaded."; - return; + return {}; } } - const auto module = std::make_shared(module_path, m_scripts_folder); - module->load_and_call_script(); + const auto rel = relative(module_path, m_disabled_scripts_folder.get_path()); + const auto is_disabled_module = !rel.empty() && rel.native()[0] != '.'; + const auto module = std::make_shared(module_path, m_scripts_folder, is_disabled_module); + if (!module->is_disabled()) + { + module->load_and_call_script(); + m_modules.push_back(module); - m_modules.push_back(module); + return module; + } + + std::lock_guard disabled_guard(m_disabled_module_lock); + m_disabled_modules.push_back(module); + return module; } void lua_manager::reload_changed_scripts() @@ -125,6 +271,12 @@ namespace big if (m_wake_time_changed_scripts_check <= std::chrono::high_resolution_clock::now()) { + if (!exists(m_scripts_folder.get_path())) + { + // g_file_manager.ensure_folder_exists(m_scripts_folder.get_path()); + return; + } + for (const auto& entry : std::filesystem::recursive_directory_iterator(m_scripts_folder.get_path(), std::filesystem::directory_options::skip_permission_denied)) { if (entry.is_regular_file()) @@ -159,25 +311,20 @@ namespace big return {}; } + std::weak_ptr lua_manager::get_disabled_module(rage::joaat_t module_id) + { + std::lock_guard guard(m_disabled_module_lock); + + for (const auto& module : m_disabled_modules) + if (module->module_id() == module_id) + return module; + + return {}; + } + void lua_manager::handle_error(const sol::error& error, const sol::state_view& state) { LOG(FATAL) << state["!module_name"].get() << ": " << error.what(); Logger::FlushQueue(); } - - void lua_manager::load_all_modules() - { - for (const auto& entry : std::filesystem::recursive_directory_iterator(m_scripts_folder.get_path(), std::filesystem::directory_options::skip_permission_denied)) - if (entry.is_regular_file() && entry.path().extension() == ".lua") - load_module(entry.path()); - } - void lua_manager::unload_all_modules() - { - std::lock_guard guard(m_module_lock); - - for (auto& module : m_modules) - module.reset(); - - m_modules.clear(); - } } \ No newline at end of file diff --git a/src/lua/lua_manager.hpp b/src/lua/lua_manager.hpp index 2c5d37d7..2ae88aba 100644 --- a/src/lua/lua_manager.hpp +++ b/src/lua/lua_manager.hpp @@ -4,15 +4,18 @@ namespace big { - class lua_manager + class lua_manager final { private: std::mutex m_module_lock; std::vector> m_modules; + std::mutex m_disabled_module_lock; + std::vector> m_disabled_modules; static constexpr std::chrono::seconds m_delay_between_changed_scripts_check = 3s; std::chrono::high_resolution_clock::time_point m_wake_time_changed_scripts_check; + folder m_disabled_scripts_folder; folder m_scripts_folder; folder m_scripts_config_folder; @@ -20,6 +23,9 @@ namespace big lua_manager(folder scripts_folder, folder scripts_config_folder); ~lua_manager(); + void disable_all_modules(); + void enable_all_modules(); + void load_all_modules(); void unload_all_modules(); @@ -39,13 +45,17 @@ namespace big } std::weak_ptr get_module(rage::joaat_t module_id); + std::weak_ptr get_disabled_module(rage::joaat_t module_id); bool has_gui_to_draw(rage::joaat_t tab_hash); void draw_independent_gui(); void draw_gui(rage::joaat_t tab_hash); + std::weak_ptr enable_module(rage::joaat_t module_id); + std::weak_ptr disable_module(rage::joaat_t module_id); + void unload_module(rage::joaat_t module_id); - void load_module(const std::filesystem::path& module_path); + std::weak_ptr load_module(const std::filesystem::path& module_path); void reload_changed_scripts(); @@ -97,6 +107,16 @@ namespace big func(module); } } + + inline void for_each_disabled_module(auto func) + { + std::lock_guard guard(m_disabled_module_lock); + + for (auto& module : m_disabled_modules) + { + func(module); + } + } }; inline lua_manager* g_lua_manager; diff --git a/src/lua/lua_module.cpp b/src/lua/lua_module.cpp index e77a0950..37607bd2 100644 --- a/src/lua/lua_module.cpp +++ b/src/lua/lua_module.cpp @@ -80,38 +80,42 @@ namespace big return sol::stack::push(L, msg); } - lua_module::lua_module(const std::filesystem::path& module_path, folder& scripts_folder) : + lua_module::lua_module(const std::filesystem::path& module_path, folder& scripts_folder, bool disabled) : m_state(), m_module_path(module_path), m_module_name(module_path.filename().string()), - m_module_id(rage::joaat(m_module_name)) + m_module_id(rage::joaat(m_module_name)), + m_disabled(disabled) { - // clang-format off - m_state.open_libraries( - sol::lib::base, - sol::lib::package, - sol::lib::coroutine, - sol::lib::string, - sol::lib::os, - sol::lib::math, - sol::lib::table, - sol::lib::bit32, - sol::lib::io, - sol::lib::utf8 - ); - // clang-format on + if (!m_disabled) + { + // clang-format off + m_state.open_libraries( + sol::lib::base, + sol::lib::package, + sol::lib::coroutine, + sol::lib::string, + sol::lib::os, + sol::lib::math, + sol::lib::table, + sol::lib::bit32, + sol::lib::io, + sol::lib::utf8 + ); + // clang-format on - init_lua_api(scripts_folder); + init_lua_api(scripts_folder); - m_state["!module_name"] = m_module_name; - m_state["!this"] = this; + m_state["!module_name"] = m_module_name; + m_state["!this"] = this; - m_state.set_exception_handler(exception_handler); - m_state.set_panic(sol::c_call); - lua_CFunction traceback_function = sol::c_call; - sol::protected_function::set_default_handler(sol::object(m_state.lua_state(), sol::in_place, traceback_function)); + m_state.set_exception_handler(exception_handler); + m_state.set_panic(sol::c_call); + lua_CFunction traceback_function = sol::c_call; + sol::protected_function::set_default_handler(sol::object(m_state.lua_state(), sol::in_place, traceback_function)); - m_last_write_time = std::filesystem::last_write_time(m_module_path); + m_last_write_time = std::filesystem::last_write_time(m_module_path); + } } lua_module::~lua_module() @@ -150,6 +154,11 @@ namespace big return m_last_write_time; } + const bool lua_module::is_disabled() const + { + return m_disabled; + } + void lua_module::set_folder_for_lua_require(folder& scripts_folder) { std::string scripts_search_path = scripts_folder.get_path().string() + "/?.lua;"; diff --git a/src/lua/lua_module.hpp b/src/lua/lua_module.hpp index 84b4f2fa..e0945772 100644 --- a/src/lua/lua_module.hpp +++ b/src/lua/lua_module.hpp @@ -21,6 +21,7 @@ namespace big std::chrono::time_point m_last_write_time; + bool m_disabled; std::mutex m_registered_scripts_mutex; public: @@ -36,7 +37,7 @@ namespace big std::unordered_map> m_event_callbacks; std::vector m_allocated_memory; - lua_module(const std::filesystem::path& module_path, folder& scripts_folder); + lua_module(const std::filesystem::path& module_path, folder& scripts_folder, bool disabled = false); ~lua_module(); const std::filesystem::path& module_path() const; @@ -44,6 +45,7 @@ namespace big rage::joaat_t module_id() const; const std::string& module_name() const; const std::chrono::time_point last_write_time() const; + const bool is_disabled() const; // used for sandboxing and limiting to only our custom search path for the lua require function void set_folder_for_lua_require(folder& scripts_folder); diff --git a/src/views/settings/view_lua_scripts.cpp b/src/views/settings/view_lua_scripts.cpp index 4837ffa3..9219e08b 100644 --- a/src/views/settings/view_lua_scripts.cpp +++ b/src/views/settings/view_lua_scripts.cpp @@ -14,40 +14,6 @@ namespace big ImGui::PushItemWidth(250); components::sub_title("VIEW_LUA_SCRIPTS_LOADED_LUA_SCRIPTS"_T); - if (ImGui::BeginListBox("##empty", ImVec2(200, 200))) - { - g_lua_manager->for_each_module([](auto& module) { - if (ImGui::Selectable(module->module_name().c_str(), - !selected_module.expired() && selected_module.lock().get() == module.get())) - selected_module = module; - }); - - ImGui::EndListBox(); - } - - ImGui::SameLine(); - - ImGui::BeginGroup(); - - if (!selected_module.expired()) - { - ImGui::Text(std::format("{}: {}", "VIEW_LUA_SCRIPTS_SCRIPTS_REGISTERED"_T, selected_module.lock()->m_registered_scripts.size()).c_str()); - ImGui::Text(std::format("{}: {}", "VIEW_LUA_SCRIPTS_MEMORY_PATCHES_REGISTERED"_T, selected_module.lock()->m_registered_patches.size()).c_str()); - ImGui::Text(std::format("{}: {}", "VIEW_LUA_SCRIPTS_GUI_TABS_REGISTERED"_T, selected_module.lock()->m_gui.size()).c_str()); - - if (components::button("VIEW_LUA_SCRIPTS_RELOAD"_T)) - { - const std::filesystem::path module_path = selected_module.lock()->module_path(); - const auto id = selected_module.lock()->module_id(); - - g_lua_manager->unload_module(id); - g_lua_manager->load_module(module_path); - selected_module = g_lua_manager->get_module(id); - } - } - - ImGui::EndGroup(); - if (components::button("VIEW_LUA_SCRIPTS_RELOAD_ALL"_T)) { g_lua_manager->unload_all_modules(); @@ -58,9 +24,90 @@ namespace big if (components::button("VIEW_LUA_SCRIPTS_OPEN_LUA_SCRIPTS_FOLDER"_T)) { + // TODO: make utility function instead std::string command = "explorer.exe /select," + g_lua_manager->get_scripts_folder().get_path().string(); std::system(command.c_str()); } + + ImGui::BeginGroup(); + components::sub_title("ENABLED_LUA_SCRIPTS"_T); + { + if (ImGui::BeginListBox("##empty", ImVec2(200, 200))) + { + g_lua_manager->for_each_module([](auto& module) { + if (ImGui::Selectable(module->module_name().c_str(), + !selected_module.expired() && selected_module.lock().get() == module.get())) + selected_module = module; + }); + + ImGui::EndListBox(); + } + } + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + components::sub_title("DISABLED_LUA_SCRIPTS"_T); + { + if (ImGui::BeginListBox("##disabled_empty", ImVec2(200, 200))) + { + g_lua_manager->for_each_disabled_module([](auto& module) { + if (ImGui::Selectable(module->module_name().c_str(), + !selected_module.expired() && selected_module.lock().get() == module.get())) + selected_module = module; + }); + + ImGui::EndListBox(); + } + } + ImGui::EndGroup(); + + ImGui::BeginGroup(); + if (!selected_module.expired()) + { + ImGui::Separator(); + + ImGui::Text(std::format("{}: {}", + "VIEW_LUA_SCRIPTS_SCRIPTS_REGISTERED"_T, + selected_module.lock()->m_registered_scripts.size()) + .c_str()); + ImGui::Text(std::format("{}: {}", + "VIEW_LUA_SCRIPTS_MEMORY_PATCHES_REGISTERED"_T, + selected_module.lock()->m_registered_patches.size()) + .c_str()); + ImGui::Text( + std::format("{}: {}", "VIEW_LUA_SCRIPTS_GUI_TABS_REGISTERED"_T, selected_module.lock()->m_gui.size()).c_str()); + + const auto id = selected_module.lock()->module_id(); + if (components::button("VIEW_LUA_SCRIPTS_RELOAD"_T)) + { + const std::filesystem::path module_path = selected_module.lock()->module_path(); + + g_lua_manager->unload_module(id); + selected_module = g_lua_manager->load_module(module_path); + } + + const auto is_disabled = selected_module.lock()->is_disabled(); + if (!is_disabled && components::button("DISABLE"_T)) + { + selected_module = g_lua_manager->disable_module(id); + } + else if (is_disabled && components::button("ENABLE"_T.data())) + { + selected_module = g_lua_manager->enable_module(id); + } + } + + ImGui::EndGroup(); + + if (components::button("DISABLE_ALL_LUA_SCRIPTS"_T)) + { + g_lua_manager->disable_all_modules(); + } + ImGui::SameLine(); + if (components::button("ENABLE_ALL_LUA_SCRIPTS"_T)) + { + g_lua_manager->enable_all_modules(); + } } }