Add mod IDs to mod json

Each mod must have a unique ID
This commit is contained in:
PJB3005
2026-05-15 22:40:46 +02:00
parent 012b54b325
commit 3f018204b6
5 changed files with 86 additions and 28 deletions
+6 -1
View File
@@ -22,11 +22,16 @@ struct RmlTabUpdateCallback {
void* userdata;
};
struct LoadedMod {
struct ModMetadata {
std::string id;
std::string name;
std::string version;
std::string author;
std::string description;
};
struct LoadedMod {
ModMetadata metadata;
std::string mod_path;
std::string dir;
+73 -22
View File
@@ -74,6 +74,7 @@ static constexpr const char* k_libExt = ".so";
#endif
using namespace dusk::modding;
using namespace std::string_literals;
using namespace std::string_view_literals;
#if defined(_M_ARM64) || defined(__aarch64__)
@@ -106,7 +107,7 @@ struct ModGuard {
};
static const char* modName() {
return g_currentMod ? g_currentMod->name.c_str() : "mod";
return g_currentMod ? g_currentMod->metadata.id.c_str() : "mod";
}
static void cb_log_info(const char* fmt, ...) {
@@ -156,7 +157,7 @@ static void* cb_load_resource(const char* relative_path, size_t* out_size) {
try {
data = g_currentMod->bundle->readFile(entry);
} catch (const std::runtime_error& e) {
DuskLog.error("[{}] load_resource: '{}' failed: {}", g_currentMod->name, entry, e.what());
DuskLog.error("[{}] load_resource: '{}' failed: {}", g_currentMod->metadata.id, entry, e.what());
return nullptr;
}
@@ -415,6 +416,50 @@ static DllLocateResult LocateDllInBundle(ModBundle& bundle) {
return DllLocateResult{dllEntry, dllFallback};
}
class InvalidModDataException : public std::runtime_error {
public:
explicit InvalidModDataException(const std::string& msg) : runtime_error(msg) {}
explicit InvalidModDataException(const char* msg) : runtime_error(msg) {}
};
static ModMetadata loadMetadata(const std::filesystem::path& modPath, ModBundle& bundle) {
const auto metaJson = bundle.readFile("mod.json");
auto j = nlohmann::json::parse(metaJson);
std::string metaId = j.value("id", "");
std::string metaName = j.value("name", "");
std::string metaVersion = j.value("version", "");
std::string metaAuthor = j.value("author", "");
std::string metaDescription = j.value("description", "");
if (metaId.empty()) {
throw InvalidModDataException("Missing ID value in mod metadata!");
}
if (metaName.empty()) {
metaName = io::fs_path_to_string(modPath.stem());
}
if (metaVersion.empty()) {
metaVersion = "?"s;
}
if (metaAuthor.empty()) {
metaAuthor = "unknown"s;
}
return ModMetadata{
std::move(metaId),
std::move(metaName),
std::move(metaVersion),
std::move(metaAuthor),
std::move(metaDescription),
};
}
static bool checkDuplicateMod(const ModMetadata& metadata, const std::vector<LoadedMod>& mods) {
return std::ranges::any_of(mods, [&](const LoadedMod& mod) {
return mod.metadata.id == metadata.id;
});
}
void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir) {
namespace fs = std::filesystem;
@@ -426,15 +471,10 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir)
return;
}
std::string metaName, metaVersion, metaAuthor, metaDescription;
ModMetadata metadata;
try
{
const auto metaJson = bundle->readFile("mod.json");
auto j = nlohmann::json::parse(metaJson);
metaName = j.value("name", "");
metaVersion = j.value("version", "");
metaAuthor = j.value("author", "");
metaDescription = j.value("description", "");
metadata = loadMetadata(modPath, *bundle);
}
catch (const std::runtime_error& e) {
Log.error(
@@ -442,6 +482,14 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir)
return;
}
if (checkDuplicateMod(metadata, m_mods)) {
Log.error(
"ModLoader: mod with id '{}' already exists, not loading {}",
metadata.id,
io::fs_path_to_string(modPath.filename()));
return;
}
auto [dllEntry, dllFallback] = LocateDllInBundle(*bundle);
if (dllEntry.empty()) {
dllEntry = dllFallback;
@@ -509,16 +557,19 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath, bool fromDir)
return;
}
mod.name = metaName.empty() ? io::fs_path_to_string(modPath.stem()) : metaName;
mod.version = metaVersion.empty() ? "?" : metaVersion;
mod.author = metaAuthor.empty() ? "unknown" : metaAuthor;
mod.description = metaDescription;
mod.metadata = std::move(metadata);
mod.bundle = std::move(bundle);
m_mods.push_back(std::move(mod));
DuskLog.info("ModLoader: found '{}' v{} by {} ({})", m_mods.back().name, m_mods.back().version,
m_mods.back().author, io::fs_path_to_string(modPath.filename()));
const auto& inserted = m_mods.emplace_back(std::move(mod));
DuskLog.info(
"ModLoader: found '{}' ('{}') v{} by {} ({})",
inserted.metadata.name,
inserted.metadata.id,
inserted.metadata.version,
inserted.metadata.author,
io::fs_path_to_string(modPath.filename()));
}
void ModLoader::init() {
@@ -571,14 +622,14 @@ void ModLoader::init() {
mod.fn_init(&mod.api);
if (!mod.load_failed) {
mod.active = true;
DuskLog.info("ModLoader: '{}' initialized", mod.name);
DuskLog.info("ModLoader: '{}' initialized", mod.metadata.id);
} else {
DuskLog.error("ModLoader: '{}' failed to load due to hook conflicts", mod.name);
DuskLog.error("ModLoader: '{}' failed to load due to hook conflicts", mod.metadata.id);
}
} catch (const std::exception& e) {
DuskLog.error("ModLoader: exception in {}.mod_init(): {}", mod.name, e.what());
DuskLog.error("ModLoader: exception in {}.mod_init(): {}", mod.metadata.id, e.what());
} catch (...) {
DuskLog.error("ModLoader: unknown exception in {}.mod_init()", mod.name);
DuskLog.error("ModLoader: unknown exception in {}.mod_init()", mod.metadata.id);
}
}
@@ -597,10 +648,10 @@ void ModLoader::tick() {
mod.fn_tick(&mod.api);
} catch (const std::exception& e) {
DuskLog.error(
"ModLoader: exception in {}.mod_tick(): {} — disabling", mod.name, e.what());
"ModLoader: exception in {}.mod_tick(): {} — disabling", mod.metadata.id, e.what());
mod.active = false;
} catch (...) {
DuskLog.error("ModLoader: unknown exception in {}.mod_tick() — disabling", mod.name);
DuskLog.error("ModLoader: unknown exception in {}.mod_tick() — disabling", mod.metadata.id);
mod.active = false;
}
}
+5 -5
View File
@@ -38,8 +38,8 @@ Rml::String build_mod_detail_rml(const dusk::LoadedMod& mod) {
R"(<span class="mod-info-label">Path</span>)"
R"(<span class="mod-info-value mod-path">{}</span>)"
R"(</div>)",
mod.version,
mod.author,
mod.metadata.version,
mod.metadata.author,
statusClass, statusText,
mod.mod_path
);
@@ -62,7 +62,7 @@ ModsWindow::ModsWindow() {
for (size_t i = 0; i < mods.size(); ++i) {
mSnapshot.push_back({mods[i].active, mods[i].load_failed});
add_tab(mods[i].name, [this, i](Rml::Element* content) {
add_tab(mods[i].metadata.name, [this, i](Rml::Element* content) {
mActiveModIndex = static_cast<int>(i);
const auto& curMods = dusk::ModLoader::instance().mods();
@@ -76,9 +76,9 @@ ModsWindow::ModsWindow() {
pane.add_section("Details");
pane.add_rml(build_mod_detail_rml(mod));
if (!mod.description.empty()) {
if (!mod.metadata.description.empty()) {
pane.add_section("Description");
pane.add_text(mod.description);
pane.add_text(mod.metadata.description);
}
for (const auto& cb : mod.tab_content) {
+1
View File
@@ -1,4 +1,5 @@
{
"id": "example.template_mod",
"name": "Template Mod",
"version": "1.0.0",
"author": "Maddie",
+1
View File
@@ -1,4 +1,5 @@
{
"id": "dev.twilitrealm.test_mod",
"name": "API Test Mod",
"version": "1.0.0",
"author": "dusk",