diff --git a/docs/modding.md b/docs/modding.md index 406a632229..bdb8618456 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -75,6 +75,8 @@ All fields are optional but recommended. `name` falls back to the filename, `ver ```cpp #include "dusk/mod_api.h" +DUSK_REQUIRE_API_VERSION // declares mod_api_version; loader rejects the mod if the engine is older + extern "C" { void mod_init (DuskModAPI* api); // required, called once at startup @@ -84,6 +86,8 @@ void mod_cleanup(DuskModAPI* api); // optional, called on shutdown } ``` +`DUSK_REQUIRE_API_VERSION` is optional but recommended. When present, the loader will refuse to initialize the mod if its API version doesn't exactly match the engine's. + --- ## DuskModAPI Reference diff --git a/include/dusk/mod_api.h b/include/dusk/mod_api.h index 6d7d75414d..8fa2f557f7 100644 --- a/include/dusk/mod_api.h +++ b/include/dusk/mod_api.h @@ -17,6 +17,11 @@ extern "C" { # define DUSK_MOD_EXPORT __attribute__((visibility("default"))) #endif +// Place this once at file scope in your mod to declare the minimum API version required. +// The loader will refuse to initialize the mod if the engine's API version is older. +#define DUSK_REQUIRE_API_VERSION \ + DUSK_MOD_EXPORT uint32_t mod_api_version = DUSK_MOD_API_VERSION; + typedef struct DuskModAPI { uint32_t api_version; const char* mod_dir; diff --git a/src/dusk/mod_loader.cpp b/src/dusk/mod_loader.cpp index 6094e2cb0c..416e6c15e7 100644 --- a/src/dusk/mod_loader.cpp +++ b/src/dusk/mod_loader.cpp @@ -274,6 +274,14 @@ void ModLoader::tryLoadDusk(const std::filesystem::path& modPath) { mod.mod_path = fs::absolute(modPath).string(); mod.dir = fs::absolute(cacheDir).string(); mod.handle = handle; + auto* mod_api_ver = reinterpret_cast(pl_dlsym(handle, "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", + fs::path(dllEntry).filename().string(), *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"));