Mod
ll/api/mod/ · Common
Overview
The Mod module manages mod lifecycle, metadata (manifest), directory structure, and provides access to mod-specific resources like loggers, config directories, and data directories.
Headers
| Header | Description |
|---|---|
ll/api/mod/Mod.h | Mod instance and lifecycle |
ll/api/mod/Manifest.h | Mod metadata structure |
ll/api/mod/ModManager.h | Base mod manager |
ll/api/mod/ModManagerRegistry.h | Registry of all mod managers |
ll/api/mod/NativeMod.h | Native (DLL) mod implementation |
Key Classes
Mod
Represents a loaded mod with lifecycle callbacks and resource access.
namespace ll::mod {
class Mod {
public:
enum class State { Enabled, Disabled };
State getState() const;
Manifest const& getManifest() const;
std::string const& getName() const;
std::string const& getType() const;
std::filesystem::path const& getModDir() const;
std::filesystem::path const& getDataDir() const;
std::filesystem::path const& getConfigDir() const;
std::optional<std::filesystem::path> getWorldDataDir() const;
std::optional<std::filesystem::path> getWorldConfigDir() const;
std::filesystem::path const& getLangDir() const;
std::filesystem::path const& getResourceDir() const;
std::filesystem::path const& getBehaviorDir() const;
io::Logger& getLogger() const;
bool isEnabled() const;
bool isDisabled() const;
void onLoad(CallbackFn func) noexcept;
void onUnload(CallbackFn func) noexcept;
void onEnable(CallbackFn func) noexcept;
void onDisable(CallbackFn func) noexcept;
};
std::filesystem::path const& getModsRoot();
}Manifest
Mod metadata loaded from manifest.json.
namespace ll::mod {
struct Dependency {
std::string name;
std::optional<data::VersionRequirement> version;
};
struct Manifest {
std::string entry;
std::string name;
std::string type;
std::optional<std::string> platform;
std::optional<bool> passive;
std::optional<data::Version> version;
std::optional<std::string> author;
std::optional<std::string> description;
std::optional<SmallStringMap<std::string>> extraInfo;
std::optional<SmallDenseSet<Dependency>> dependencies;
std::optional<SmallDenseSet<Dependency>> optionalDependencies;
std::optional<SmallDenseSet<Dependency>> conflicts;
std::optional<SmallDenseSet<Dependency>> loadBefore;
};
}Dependency entries keep the existing manifest JSON shape:
{
"dependencies": [
{ "name": "RequiredMod", "version": "^1.2.3" }
],
"optionalDependencies": [
{ "name": "OptionalIntegration", "version": ">=2.0.0 <3.0.0" }
],
"loadBefore": [
{ "name": "LateInitializer" }
]
}Omitting a dependency's version accepts any declared version, including an unversioned target. If a range is present,
the target must declare a matching version. See Data for the complete range syntax. A bare
full version such as 1.2.3 temporarily keeps the legacy meaning >=1.2.3 <2.0.0 and emits a migration warning; use
=1.2.3 for an exact match.
Mod Types
The manifest's type field decides who loads the mod:
type | Loaded by | When |
|---|---|---|
native | LeviLamina's native mod manager (NativeModManagerName) | After LeviLamina has started, together with the other LeviLamina mods. Use this for ordinary C++ mods. |
preload-native | PreLoader | Before the game starts, before LeviLamina's own mods. LeviLamina itself uses this type. LeviLamina skips these manifests in its own mod registry, so they are not LeviLamina mods. |
Any other type is loaded by the mod manager that was registered for that type through ModManagerRegistry (for
example by a script engine). If no manager is registered for it, loading fails with "Unrecognized mod type".
For every type, setting passive to true means the mod is loaded only when another mod depends on it.
Dependency Semantics
| Manifest field | Load ordering | Load failure | Disable and unload protection |
|---|---|---|---|
dependencies | Dependency first | Blocks the dependent | Yes |
optionalDependencies | Matching available dependency first | Does not block the consumer | Yes, when bound at load time |
loadBefore | Declaring mod first | Does not create a dependency | No |
loadBefore is only a startup and bulk-operation ordering constraint. It never prevents either mod from being disabled
or unloaded independently. Dynamic loading does not reorder already loaded mods; if a new mod asks to load before an
already loaded target, LeviLamina logs a warning and continues. Optional dependencies are bound only when their consumer
loads, so loading an optional provider later does not retroactively add lifecycle protection.
Usage
Accessing Mod Instance
#include "ll/api/mod/NativeMod.h"
ll::mod::NativeMod& getSelfMod() {
return ll::mod::NativeMod::current();
}Using Mod Resources
#include "ll/api/mod/Mod.h"
void setupMod(ll::mod::Mod& mod) {
auto& logger = mod.getLogger();
logger.info("Mod name: {}", mod.getName());
auto configPath = mod.getConfigDir() / "config.json";
auto dataPath = mod.getDataDir() / "database.db";
auto langPath = mod.getLangDir() / "en_US.json";
}Lifecycle Callbacks
#include "ll/api/mod/Mod.h"
void registerCallbacks(ll::mod::Mod& mod) {
mod.onLoad([](ll::mod::Mod& self) -> bool {
self.getLogger().info("Mod loading");
return true; // Return false to abort load
});
mod.onEnable([](ll::mod::Mod& self) -> bool {
self.getLogger().info("Mod enabled");
return true;
});
mod.onDisable([](ll::mod::Mod& self) -> bool {
self.getLogger().info("Mod disabled");
return true;
});
mod.onUnload([](ll::mod::Mod& self) -> bool {
self.getLogger().info("Mod unloading");
return true;
});
}Directory Structure
Mods live in the mods root (getModsRoot()): plugins/ on the server and mods/ on the client. (PreLoader uses
plugins/ when that directory exists, otherwise mods/; modsPath in PreLoaderConfig.json overrides it.) Each mod
has the following directory structure:
plugins/ # mods/ on the client
└── MyMod/
├── manifest.json # Mod metadata
├── MyMod.dll # Mod binary
├── config/ # Config files (getConfigDir())
├── data/ # Persistent data (getDataDir())
├── lang/ # Translations (getLangDir())
├── resource_packs/ # Resource packs (getResourceDir())
└── behavior_packs/ # Behavior packs (getBehaviorDir())
worlds/
└── Bedrock level/ # The current world's folder
├── ll_config/
│ └── MyMod/ # World-specific config (getWorldConfigDir())
└── ll_data/
└── MyMod/ # World-specific data (getWorldDataDir())getWorldConfigDir() and getWorldDataDir() return std::nullopt while no world is loaded. On the client, when
connected to a remote server, they are ll_config/<host>_<port>/MyMod/ and ll_data/<host>_<port>/MyMod/ in the game
directory instead.
Related
- Config — Use
getConfigDir()for config file location - Data — Use
getDataDir()for database storage - I18n — Use
getLangDir()for translation files - IO & Logger —
getLogger()returns the mod's logger