diff --git a/src/lua/bindings/script.hpp b/src/lua/bindings/script.hpp index 389c0d7f..b302ef9f 100644 --- a/src/lua/bindings/script.hpp +++ b/src/lua/bindings/script.hpp @@ -20,12 +20,20 @@ namespace lua::script // Class: script_util // Name: yield // Yield execution. + int yield() + { + return 0; + } // Lua API: Function // Class: script_util // Name: sleep // Param: ms: integer: The amount of time in milliseconds that we will sleep for. // Sleep for the given amount of time, time is in milliseconds. + int sleep(int ms) + { + return ms; + } }; static script_util dummy_script_util; @@ -62,12 +70,16 @@ namespace lua::script // ENTITY.DELETE_ENTITY(spawnedVehicle) // end) // ``` - static void register_looped(const std::string& name, sol::coroutine func, sol::this_state state) + static void register_looped(const std::string& name, sol::function func_, sol::this_state state) { auto module = sol::state_view(state)["!this"].get(); - module->m_registered_scripts.push_back(big::g_script_mgr.add_script(std::make_unique( - [func] () mutable { + std::unique_ptr lua_script = std::make_unique( + [func_, state]() mutable { + + sol::thread t = sol::thread::create(state); + sol::coroutine func = sol::coroutine(t.state(), func_); + while (big::g_running) { auto res = func(dummy_script_util); @@ -75,17 +87,22 @@ namespace lua::script if (!res.valid()) big::g_lua_manager->handle_error(res, res.lua_state()); - if (func.runnable()) - { + if (func.runnable()) + { big::script::get_current()->yield(std::chrono::milliseconds(res.return_count() ? res[0] : 0)); - } - else - { + } + else + { big::script::get_current()->yield(); - } + } } }, - name))); + name + ); + + const auto registered_script = big::g_script_mgr.add_script(std::move(lua_script)); + + module->m_registered_scripts.push_back(registered_script); } // Lua API: Function @@ -116,28 +133,42 @@ namespace lua::script // ENTITY.DELETE_ENTITY(spawnedVehicle) // end) // ``` - static void run_in_fiber(sol::coroutine func) + static void run_in_fiber(sol::function func_, sol::this_state state) { - big::g_fiber_pool->queue_job([func] () mutable { - while (big::g_running) - { - auto res = func(dummy_script_util); + auto module = sol::state_view(state)["!this"].get(); - if (!res.valid()) - big::g_lua_manager->handle_error(res, res.lua_state()); + static size_t name_i = 0; + std::string job_name = module->module_name() + std::to_string(name_i++); - // Still runnable, meaning that the user function yielded - // and that there is more code to run still. - if (func.runnable()) - { - big::script::get_current()->yield(std::chrono::milliseconds(res.return_count() ? res[0] : 0)); - } - else - { - break; - } - } - }); + // We make a new script for lua state destruction timing purposes, see lua_module dctor for more info. + std::unique_ptr lua_script = std::make_unique( + [func_, state]() mutable { + sol::thread t = sol::thread::create(state); + sol::coroutine func = sol::coroutine(t.state(), func_); + + while (big::g_running) + { + auto res = func(dummy_script_util); + + if (!res.valid()) + big::g_lua_manager->handle_error(res, res.lua_state()); + + if (func.runnable()) + { + big::script::get_current()->yield(std::chrono::milliseconds(res.return_count() ? res[0] : 0)); + } + else + { + big::g_script_mgr.remove_script(big::script::get_current()); + break; + } + } + }, + job_name); + + const auto registered_script = big::g_script_mgr.add_script(std::move(lua_script)); + + module->m_registered_scripts.push_back(registered_script); } static void bind(sol::state& state) @@ -146,12 +177,9 @@ namespace lua::script ns["register_looped"] = register_looped; ns["run_in_fiber"] = run_in_fiber; - sol::function lua_coroutine_yield = state["coroutine"]["yield"]; + state.new_usertype("script_util"); - //clang-format off - state.new_usertype("script_util", - "yield", lua_coroutine_yield, - "sleep", lua_coroutine_yield); - //clang-format on + state["script_util"]["yield"] = sol::yielding(&script_util::yield); + state["script_util"]["sleep"] = sol::yielding(&script_util::sleep); } } \ No newline at end of file diff --git a/src/lua/lua_module.cpp b/src/lua/lua_module.cpp index 039ba259..148f5f06 100644 --- a/src/lua/lua_module.cpp +++ b/src/lua/lua_module.cpp @@ -45,37 +45,36 @@ namespace big } lua_module::lua_module(std::string module_name) : - m_state(), m_module_name(module_name), m_module_id(rage::joaat(module_name)) { - m_state.open_libraries( + m_state = std::make_unique(); + + auto& state = *m_state; + + 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::debug, sol::lib::bit32, sol::lib::utf8 ); - const auto& scripts_folder = g_lua_manager->get_scripts_folder(); - - add_folder_to_require_available_paths(scripts_folder); - init_lua_api(); - m_state["!module_name"] = module_name; - m_state["!this"] = this; + state["!module_name"] = module_name; + state["!this"] = this; - m_state.set_exception_handler((sol::exception_handler_function)exception_handler); - m_state.set_panic(panic_handler); + state.set_exception_handler((sol::exception_handler_function)exception_handler); + state.set_panic(panic_handler); - const auto script_file_path = scripts_folder.get_file(module_name).get_path(); + const auto script_file_path = g_lua_manager->get_scripts_folder().get_file(module_name).get_path(); m_last_write_time = std::filesystem::last_write_time(script_file_path); - auto result = m_state.load_file(script_file_path.string()); + auto result = state.load_file(script_file_path.string()); if (!result.valid()) { @@ -95,7 +94,17 @@ namespace big } for (auto script : m_registered_scripts) + { g_script_mgr.remove_script(script); + } + + // the lua state is about to be destroyed, + // but we need to keep it around a little bit longer + // until the script manager properly finish executing any potential lua script. + // There are most likely much better ways of doing all this, feel free to refactor I guess. + std::shared_ptr lua_state_shared = std::shared_ptr(std::move(m_state)); + g_script_mgr.add_on_script_batch_removed([lua_state_shared] { + }); for (auto memory : m_allocated_memory) delete[] memory; @@ -116,27 +125,73 @@ namespace big return m_last_write_time; } - void lua_module::add_folder_to_require_available_paths(const big::folder& scripts_folder) + void lua_module::set_folder_for_lua_require() { - const std::string package_path = m_state["package"]["path"]; - const auto scripts_search_path = scripts_folder.get_path() / "?.lua"; - m_state["package"]["path"] = package_path + (!package_path.empty() ? ";" : "") + scripts_search_path.string(); + auto& state = *m_state; + + const auto scripts_search_path = g_lua_manager->get_scripts_folder().get_path() / "?.lua"; + state["package"]["path"] = scripts_search_path.string(); + } + + void lua_module::sandbox_lua_os_library() + { + auto& state = *m_state; + + const auto& os = state["os"]; + sol::table sandbox_os(state, sol::create); + + sandbox_os["clock"] = os["clock"]; + sandbox_os["date"] = os["date"]; + sandbox_os["difftime"] = os["difftime"]; + sandbox_os["time"] = os["time"]; + + state["os"] = sandbox_os; + } + + void lua_module::sandbox_lua_loads() + { + auto& state = *m_state; + + // That's from lua base lib, luaB + state.set_function("load", nullptr); + state.set_function("loadstring", nullptr); + state.set_function("loadfile", nullptr); + state.set_function("dofile", nullptr); + + // That's from lua package lib. + // We only allow dependencies between .lua files, no DLLs. + state["package"]["loadlib"] = nullptr; + state["package"]["cpath"] = ""; + + // 1 2 3 4 + // {searcher_preload, searcher_Lua, searcher_C, searcher_Croot, NULL}; + state["package"]["searchers"][3] = nullptr; + state["package"]["searchers"][4] = nullptr; + + set_folder_for_lua_require(); } void lua_module::init_lua_api() { - lua::log::bind(m_state); - lua::globals::bind(m_state); - lua::script::bind(m_state); - lua::native::bind(m_state); - lua::memory::bind(m_state); - lua::gui::bind(m_state); - lua::network::bind(m_state); - lua::command::bind(m_state); - lua::tunables::bind(m_state); - lua::locals::bind(m_state); - lua::event::bind(m_state); - lua::vector::bind(m_state); - lua::global_table::bind(m_state); + auto& state = *m_state; + + // https://blog.rubenwardy.com/2020/07/26/sol3-script-sandbox/ + // https://www.lua.org/manual/5.4/manual.html#pdf-require + sandbox_lua_os_library(); + sandbox_lua_loads(); + + lua::log::bind(state); + lua::globals::bind(state); + lua::script::bind(state); + lua::native::bind(state); + lua::memory::bind(state); + lua::gui::bind(state); + lua::network::bind(state); + lua::command::bind(state); + lua::tunables::bind(state); + lua::locals::bind(state); + lua::event::bind(state); + lua::vector::bind(state); + lua::global_table::bind(state); } } \ No newline at end of file diff --git a/src/lua/lua_module.hpp b/src/lua/lua_module.hpp index 59cb538f..cf3a94d9 100644 --- a/src/lua/lua_module.hpp +++ b/src/lua/lua_module.hpp @@ -11,7 +11,7 @@ namespace big class lua_module { - sol::state m_state; + std::unique_ptr m_state; std::string m_module_name; rage::joaat_t m_module_id; @@ -37,8 +37,11 @@ namespace big const std::string& module_name() const; const std::chrono::time_point last_write_time() const; - // used for adding our own paths to the search paths of the lua require function - void add_folder_to_require_available_paths(const big::folder& scripts_folder); + // used for sandboxing and limiting to only our custom search path for the lua require function + void set_folder_for_lua_require(); + + void sandbox_lua_os_library(); + void sandbox_lua_loads(); void init_lua_api(); }; diff --git a/src/script_mgr.cpp b/src/script_mgr.cpp index 3ac0ef91..8d082c09 100644 --- a/src/script_mgr.cpp +++ b/src/script_mgr.cpp @@ -33,6 +33,11 @@ namespace big m_scripts.clear(); } + void script_mgr::add_on_script_batch_removed(std::function f) + { + m_on_script_batch_removed.push(f); + } + void script_mgr::tick() { gta_util::execute_as_script(RAGE_JOAAT("main_persistent"), std::mem_fn(&script_mgr::tick_internal), this); @@ -62,6 +67,18 @@ namespace big return iter->m_should_be_deleted; }); + while (m_on_script_batch_removed.size()) + { + auto& f = m_on_script_batch_removed.front(); + + if (f) + { + f(); + } + + m_on_script_batch_removed.pop(); + } + if (g_lua_manager->m_schedule_reload_modules) { g_lua_manager->load_all_modules(); diff --git a/src/script_mgr.hpp b/src/script_mgr.hpp index 6fa37441..20be1ea2 100644 --- a/src/script_mgr.hpp +++ b/src/script_mgr.hpp @@ -16,6 +16,8 @@ namespace big void remove_script(script* script); void remove_all_scripts(); + void add_on_script_batch_removed(std::function f); + inline void for_each_script(auto func) { std::lock_guard lock(m_mutex); @@ -41,6 +43,7 @@ namespace big std::recursive_mutex m_mutex; script_list m_scripts; script_list m_scripts_to_add; + std::queue> m_on_script_batch_removed; bool m_can_tick = false; };