diff --git a/include/dusk/mod_loader.hpp b/include/dusk/mod_loader.hpp index 3b467b6db0..04c4e4906e 100644 --- a/include/dusk/mod_loader.hpp +++ b/include/dusk/mod_loader.hpp @@ -45,6 +45,41 @@ struct NativeMod { FnCleanup fn_cleanup = nullptr; }; +enum class NativeModStatus : u8 { + /** + * Mod does not have native code included. + */ + None, + + /** + * Native code mod loaded successfully. + * + * Note that this only indicates load status of the native library. If the native lib throws in + * its init function, it will still be disabled! + */ + Loaded, + + /** + * This build was compiled without native mod support! + */ + BuildDisabled, + + /** + * Mod does not have a native library suitable for this build's platform. + */ + ModMissingPlatform, + + /** + * Mod is built for a different API version than this build of the game. + */ + ApiVersionMismatch, + + /** + * Unknown error loading the native mod. + */ + Unknown, +}; + struct LoadedMod { ModMetadata metadata; std::string mod_path; @@ -53,7 +88,9 @@ struct LoadedMod { bool active = false; bool load_failed = false; + NativeModStatus native_status = NativeModStatus::None; std::unique_ptr native; + std::unique_ptr bundle; std::vector tab_content; @@ -77,7 +114,7 @@ private: bool m_initialized = false; void tryLoadDusk(const std::filesystem::path& modPath, bool fromDir); - bool tryLoadNativeMod(LoadedMod& mod); + void tryLoadNativeMod(LoadedMod& mod); void buildAPI(LoadedMod& mod); void initOverlayFiles(); }; diff --git a/src/dusk/modding/mod_loader.cpp b/src/dusk/modding/mod_loader.cpp index 25d6fe3ffc..f2499131d2 100644 --- a/src/dusk/modding/mod_loader.cpp +++ b/src/dusk/modding/mod_loader.cpp @@ -119,10 +119,11 @@ static bool checkDuplicateMod(const ModMetadata& metadata, const std::vectorhandle = std::make_unique(dllCachePath); } catch (const std::runtime_error& e) { Log.error("failed to open {}: {}", io::fs_path_to_string(dllCachePath), e.what()); - return false; + return; } const auto mod_api_ver = nativeMod->handle->LookupSymbol("mod_api_version"); if (mod_api_ver && *mod_api_ver != DUSK_MOD_API_VERSION) { Log.error("{} expects API v{} but engine is v{}, skipping", io::fs_path_to_string(fs::path(dllEntry).filename()), *mod_api_ver, DUSK_MOD_API_VERSION); - return false; + mod.native_status = NativeModStatus::ApiVersionMismatch; + return; } nativeMod->fn_init = nativeMod->handle->LookupSymbol("mod_init"); @@ -187,12 +190,12 @@ bool ModLoader::tryLoadNativeMod(LoadedMod& mod) { if (!nativeMod->fn_init || !nativeMod->fn_tick) { Log.error("{} missing mod_init or mod_tick — skipping", io::fs_path_to_string(fs::path(dllEntry).filename())); - return false; + return; } mod.dir = io::fs_path_to_string(fs::absolute(cacheDir)); mod.native = std::move(nativeMod); - return true; + mod.native_status = NativeModStatus::Loaded; } void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir) { @@ -226,12 +229,21 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir) } LoadedMod mod; + mod.active = true; mod.mod_path = io::fs_path_to_string(fs::absolute(modPath)); mod.metadata = std::move(metadata); mod.bundle = std::move(bundle); - if (mod.metadata.hasCode && !tryLoadNativeMod(mod)) { - return; + if (mod.metadata.hasCode) { + mod.native_status = NativeModStatus::Unknown; + tryLoadNativeMod(mod); + // Native mod lod failure DOES NOT block insertion into m_mods. + // We still want to be able to present the failed load in the UI! + + if (mod.native_status != NativeModStatus::Loaded) { + Log.error("Native mod '{}' failed to load, disabling", metadata.id); + mod.active = false; + } } const auto& inserted = m_mods.emplace_back(std::move(mod)); @@ -282,36 +294,41 @@ void ModLoader::init() { return; } - initOverlayFiles(); Log.info("initializing {} mod(s)...", m_mods.size()); for (auto& mod : m_mods) { - if (mod.native) { + if (mod.native && mod.active) { buildAPI(mod); } } for (auto& mod : m_mods) { - if (!mod.native) { + if (!mod.native || !mod.active) { continue; } + Log.debug("Initializing '{}'", mod.metadata.id); + ModGuard guard(&mod); try { mod.native->fn_init(&mod.native->api); if (!mod.load_failed) { - mod.active = true; Log.info("'{}' initialized", mod.metadata.id); } else { + mod.active = false; Log.error("'{}' failed to load due to hook conflicts", mod.metadata.id); } } catch (const std::exception& e) { + mod.active = false; Log.error("exception in {}.mod_init(): {}", mod.metadata.id, e.what()); } catch (...) { + mod.active = false; Log.error("unknown exception in {}.mod_init()", mod.metadata.id); } } + initOverlayFiles(); + auto active = std::count_if(m_mods.begin(), m_mods.end(), [](const LoadedMod& m) { return m.active; }); Log.info("{}/{} mod(s) active", active, m_mods.size()); diff --git a/src/dusk/modding/mod_loader_overlay.cpp b/src/dusk/modding/mod_loader_overlay.cpp index 8f6953650e..fef860c489 100644 --- a/src/dusk/modding/mod_loader_overlay.cpp +++ b/src/dusk/modding/mod_loader_overlay.cpp @@ -115,7 +115,9 @@ void ModLoader::initOverlayFiles() { std::vector files; for (auto& mod : m_mods) { - findOverlayFiles(files, mod); + if (mod.active) { + findOverlayFiles(files, mod); + } } Log.debug("Found {} overlay files.", files.size());