feat(LuaMgr): Disable and Enable scripts from UI and prevent crash when renaming the scripts/ dir. (#2493)

This commit is contained in:
Andreas Maerten 2023-12-13 23:19:52 +01:00 committed by GitHub
parent 3bf7c034d2
commit 2def629d3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 326 additions and 91 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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:

View File

@ -4,9 +4,29 @@
namespace big
{
std::optional<std::filesystem::path> 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<std::filesystem::path> 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<std::filesystem::path> 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_module> 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_module> 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_module> lua_manager::load_module(const std::filesystem::path& module_path)
{
if (!std::filesystem::exists(module_path))
{
LOG(WARNING) << reinterpret_cast<const char*>(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<lua_module>(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<lua_module>(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_module> 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<std::string_view>() << ": " << 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();
}
}

View File

@ -4,15 +4,18 @@
namespace big
{
class lua_manager
class lua_manager final
{
private:
std::mutex m_module_lock;
std::vector<std::shared_ptr<lua_module>> m_modules;
std::mutex m_disabled_module_lock;
std::vector<std::shared_ptr<lua_module>> 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<lua_module> get_module(rage::joaat_t module_id);
std::weak_ptr<lua_module> 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<lua_module> enable_module(rage::joaat_t module_id);
std::weak_ptr<lua_module> 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<lua_module> 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;

View File

@ -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<decltype(&panic_handler), &panic_handler>);
lua_CFunction traceback_function = sol::c_call<decltype(&traceback_error_handler), &traceback_error_handler>;
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<decltype(&panic_handler), &panic_handler>);
lua_CFunction traceback_function = sol::c_call<decltype(&traceback_error_handler), &traceback_error_handler>;
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;";

View File

@ -21,6 +21,7 @@ namespace big
std::chrono::time_point<std::chrono::file_clock> m_last_write_time;
bool m_disabled;
std::mutex m_registered_scripts_mutex;
public:
@ -36,7 +37,7 @@ namespace big
std::unordered_map<menu_event, std::vector<sol::protected_function>> m_event_callbacks;
std::vector<void*> 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<std::chrono::file_clock> 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);

View File

@ -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<ImVec2{0, 0}, ImVec4{0.58f, 0.15f, 0.15f, 1.f}>("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<ImVec2{0, 0}, ImVec4{0.58f, 0.15f, 0.15f, 1.f}>("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();
}
}
}