From 32069d936cbedd03b562c09268fbdf7f7f3fcf7e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 15 May 2026 23:11:29 +0200 Subject: [PATCH] Make native module handles a special type We love RAII --- files.cmake | 2 + include/dusk/mod_loader.hpp | 3 +- src/dusk/modding/mod_loader.cpp | 80 +++++----------------------- src/dusk/modding/native_module.cpp | 83 ++++++++++++++++++++++++++++++ src/dusk/modding/native_module.hpp | 35 +++++++++++++ 5 files changed, 134 insertions(+), 69 deletions(-) create mode 100644 src/dusk/modding/native_module.cpp create mode 100644 src/dusk/modding/native_module.hpp diff --git a/files.cmake b/files.cmake index 378527a6dd..a0370df464 100644 --- a/files.cmake +++ b/files.cmake @@ -1525,6 +1525,8 @@ set(DUSK_FILES src/dusk/hook_system.cpp src/dusk/modding/mod_loader.cpp src/dusk/modding/mod_loader_overlay.cpp + src/dusk/modding/native_module.cpp + src/dusk/modding/native_module.hpp src/dusk/modding/bundle_disk.cpp src/dusk/modding/bundle_zip.cpp src/dusk/gx_helper.cpp diff --git a/include/dusk/mod_loader.hpp b/include/dusk/mod_loader.hpp index e0ec830474..13624ee6eb 100644 --- a/include/dusk/mod_loader.hpp +++ b/include/dusk/mod_loader.hpp @@ -8,6 +8,7 @@ namespace dusk::modding { class ModBundle; +class NativeModule; } namespace dusk { @@ -35,7 +36,7 @@ struct LoadedMod { std::string mod_path; std::string dir; - void* handle = nullptr; + std::unique_ptr handle; bool active = false; bool load_failed = false; diff --git a/src/dusk/modding/mod_loader.cpp b/src/dusk/modding/mod_loader.cpp index 02d6759cc9..c222e33d68 100644 --- a/src/dusk/modding/mod_loader.cpp +++ b/src/dusk/modding/mod_loader.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -17,62 +16,11 @@ #include "aurora/dvd.h" #include "dusk/io.hpp" #include "miniz.h" +#include "native_module.hpp" #include "nlohmann/json.hpp" -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN -#define NOMINMAX -#include - static aurora::Module Log("dusk::modLoader"); -static void* pl_dlopen(const std::filesystem::path& p) { - return LoadLibraryW(p.wstring().c_str()); -} -static void* pl_dlsym(void* h, const char* name) { - return reinterpret_cast(GetProcAddress(static_cast(h), name)); -} -static void pl_dlclose(void* h) { - FreeLibrary(static_cast(h)); -} -static std::string pl_dlerror() { - char buf[256]{}; - FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, - GetLastError(), 0, buf, sizeof(buf), nullptr); - std::string s = buf; - while (!s.empty() && (s.back() == '\r' || s.back() == '\n')) { - s.pop_back(); - } - return s; -} -static constexpr const char* k_libExt = ".dll"; - -#else -#include -static void* pl_dlopen(const std::filesystem::path& p) { -#if defined(__linux__) - return dlopen(p.c_str(), RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); -#else - return dlopen(p.c_str(), RTLD_LAZY | RTLD_LOCAL); -#endif -} -static void* pl_dlsym(void* h, const char* name) { - return dlsym(h, name); -} -static void pl_dlclose(void* h) { - dlclose(h); -} -static std::string pl_dlerror() { - const char* e = dlerror(); - return e ? e : "(unknown error)"; -} -#if defined(__APPLE__) -static constexpr const char* k_libExt = ".dylib"; -#else -static constexpr const char* k_libExt = ".so"; -#endif -#endif - using namespace dusk::modding; using namespace std::string_literals; using namespace std::string_view_literals; @@ -497,7 +445,7 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir) if (dllEntry.empty()) { DuskLog.warn( - "ModLoader: no *{} found in {} — skipping", k_libExt, io::fs_path_to_string(modPath.filename())); + "ModLoader: no *{} found in {} — skipping", NativeModule::LibraryExtension, io::fs_path_to_string(modPath.filename())); return; } @@ -528,32 +476,32 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir) static_cast(dllData.size())); } - void* handle = pl_dlopen(dllCachePath); - if (!handle) { - DuskLog.error("ModLoader: failed to open {}: {}", io::fs_path_to_string(dllCachePath), pl_dlerror()); + NativeModule native; + try { + native = NativeModule(dllCachePath); + } catch (const std::runtime_error& e) { + DuskLog.error("ModLoader: failed to open {}: {}", io::fs_path_to_string(dllCachePath), e.what()); return; } LoadedMod mod; mod.mod_path = io::fs_path_to_string(fs::absolute(modPath)); mod.dir = io::fs_path_to_string(fs::absolute(cacheDir)); - mod.handle = handle; - auto* mod_api_ver = reinterpret_cast(pl_dlsym(handle, "mod_api_version")); + mod.handle = std::make_unique(std::move(native)); + const auto mod_api_ver = mod.handle->LookupSymbol("mod_api_version"); if (mod_api_ver && *mod_api_ver != DUSK_MOD_API_VERSION) { DuskLog.error("ModLoader: {} expects API v{} but engine is v{}, skipping", io::fs_path_to_string(fs::path(dllEntry).filename()), *mod_api_ver, DUSK_MOD_API_VERSION); - pl_dlclose(handle); return; } - mod.fn_init = reinterpret_cast(pl_dlsym(handle, "mod_init")); - mod.fn_tick = reinterpret_cast(pl_dlsym(handle, "mod_tick")); - mod.fn_cleanup = reinterpret_cast(pl_dlsym(handle, "mod_cleanup")); + mod.fn_init = mod.handle->LookupSymbol("mod_init"); + mod.fn_tick = mod.handle->LookupSymbol("mod_tick"); + mod.fn_cleanup = mod.handle->LookupSymbol("mod_cleanup"); if (!mod.fn_init || !mod.fn_tick) { DuskLog.error("ModLoader: {} missing mod_init or mod_tick — skipping", io::fs_path_to_string(fs::path(dllEntry).filename())); - pl_dlclose(handle); return; } @@ -667,10 +615,6 @@ void ModLoader::shutdown() { } catch (...) { } } - if (mod.handle) { - pl_dlclose(mod.handle); - mod.handle = nullptr; - } } m_mods.clear(); g_services.clear(); diff --git a/src/dusk/modding/native_module.cpp b/src/dusk/modding/native_module.cpp new file mode 100644 index 0000000000..82cbe501bf --- /dev/null +++ b/src/dusk/modding/native_module.cpp @@ -0,0 +1,83 @@ +#include "native_module.hpp" + +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#endif + +namespace { +#if defined(_WIN32) +void* pl_dlopen(const std::filesystem::path& p) { + return LoadLibraryW(p.wstring().c_str()); +} +void* pl_dlsym(void* h, const char* name) { + return reinterpret_cast(GetProcAddress(static_cast(h), name)); +} +void pl_dlclose(void* h) { + FreeLibrary(static_cast(h)); +} +std::string pl_dlerror() { + char buf[256]{}; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, + GetLastError(), 0, buf, sizeof(buf), nullptr); + std::string s = buf; + while (!s.empty() && (s.back() == '\r' || s.back() == '\n')) { + s.pop_back(); + } + return s; +} +#else +#include +static void* pl_dlopen(const std::filesystem::path& p) { +#if defined(__linux__) + return dlopen(p.c_str(), RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); +#else + return dlopen(p.c_str(), RTLD_LAZY | RTLD_LOCAL); +#endif +} +static void* pl_dlsym(void* h, const char* name) { + return dlsym(h, name); +} +static void pl_dlclose(void* h) { + dlclose(h); +} +static std::string pl_dlerror() { + const char* e = dlerror(); + return e ? e : "(unknown error)"; +} +#endif +} + +namespace dusk::modding { +NativeModule::NativeModule() noexcept : handle(nullptr) { +} + +NativeModule::NativeModule(NativeModule&& other) noexcept { + handle = other.handle; + other.handle = nullptr; +} + +NativeModule& NativeModule::operator=(NativeModule&& other) noexcept { + handle = other.handle; + other.handle = nullptr; + return *this; +} + +NativeModule::NativeModule(const std::filesystem::path& path) { + handle = pl_dlopen(path); + if (!handle) { + throw std::runtime_error(pl_dlerror()); + } +} + +NativeModule::~NativeModule() { + if (handle) { + pl_dlclose(handle); + } +} + +void* NativeModule::LookupSymbol(const char* name) const { + return pl_dlsym(handle, name); +} +} // namespace dusk::modding \ No newline at end of file diff --git a/src/dusk/modding/native_module.hpp b/src/dusk/modding/native_module.hpp new file mode 100644 index 0000000000..00bfb7d6dc --- /dev/null +++ b/src/dusk/modding/native_module.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace dusk::modding { +class NativeModule final { +public: + NativeModule() noexcept; + NativeModule(const NativeModule& other) = delete; + NativeModule(NativeModule&& other) noexcept; + explicit NativeModule(const std::filesystem::path& path); + ~NativeModule(); + + void* LookupSymbol(const char* name) const; + + template + T LookupSymbol(const char* name) const { + return reinterpret_cast(LookupSymbol(name)); + } + + NativeModule& operator=(NativeModule&& other) noexcept; + +#if defined(_WIN32) + static constexpr auto LibraryExtension = ".dll"; +#elif defined(__APPLE__) + static constexpr auto LibraryExtension = ".dylib"; +#else + static constexpr auto LibraryExtension = ".so"; +#endif + +private: + void* handle; +}; + +}