Native Mod Quick Start
This page describes the supported developer path for LeviLaunchroid native mods. The public SDK is published in LiteLDev/preloader-android.
For standalone third-party mods, start from the LeviLauncher Android mod template.
Use examples/full-cpp-mod as the reference implementation. It includes lifecycle registration, typed config, Mod Menu integration, Android packaging, and .levipack output.
Build the Example
From the repository root:
.\examples\full-cpp-mod\build.ps1 -CleanThe example is ready to build in this repository. Standalone mod projects should start from the template and import the SDK as an external dependency.
Output:
examples\full-cpp-mod\dist\arm64-v8a\full-cpp-mod\
examples\full-cpp-mod\dist\arm64-v8a\full-cpp-mod.levipackImport the .levipack, or copy the unpacked mod directory into the launcher native mod location.
Package Layout
full-cpp-mod/
├── manifest.json
├── libfull_cpp_mod.so
└── config/
├── config.json
└── config.schema.jsonThe directory name is the runtime mod id. Keep it stable after release because it is used for paths, Mod Menu ownership, and persisted user state.
manifest.json
{
"type": "preload-native",
"name": "Full C++ Lifecycle Mod Example",
"author": "LiteLDev",
"version": "1.0.0",
"entry": "libfull_cpp_mod.so",
"minecraft_versions": [],
"overwrite_files": [],
"overwrite_folders": []
}| Field | Notes |
|---|---|
type | Must be preload-native. |
entry | Relative path to the Android .so inside the mod directory. |
name | Display name shown by the launcher. |
author | Author text shown by the launcher. |
version | Mod version. |
icon | Optional relative path to an icon. |
minecraft_versions | Exact versions and * prefix wildcards are supported. Missing or empty means all versions. |
overwrite_files | Optional relative files that may be replaced when importing over an installed mod. Missing or empty means no extra file replacement. |
overwrite_folders | Optional relative folders whose existing files may be replaced when importing over an installed mod. Missing or empty keeps existing files outside manifest.json and entry. |
Lifecycle Shape
#include <pl/Mod.hpp>
class FullCppMod {
public:
static FullCppMod &instance();
FullCppMod();
[[nodiscard]] ll::mod::NativeMod &getSelf() const { return mSelf; }
bool load();
bool enable();
bool disable();
bool unload();
private:
ll::mod::NativeMod &mSelf;
};
PL_REGISTER_MOD(FullCppMod, FullCppMod::instance())load() is required. The other lifecycle methods are optional and default to success when absent.
Store the loader-provided native mod object in the constructor:
FullCppMod::FullCppMod() : mSelf(*ll::mod::NativeMod::current()) {}
bool FullCppMod::load() {
auto &self = getSelf();
std::filesystem::create_directories(self.getConfigDir());
self.getLogger().info("Loaded {}", self.getName());
return true;
}SDK Dependency
In a standalone Android native mod project, import preloader-android with FetchContent and link your mod library to the preloader target:
include(FetchContent)
FetchContent_Declare(
preloader_android
GIT_REPOSITORY https://github.com/LiteLDev/preloader-android.git
GIT_TAG 0.2.2)
FetchContent_MakeAvailable(preloader_android)
target_link_libraries(my_mod PRIVATE preloader)Pin GIT_TAG to a release tag for reproducible builds.
Common SDK headers:
#include <pl/Mod.hpp>
#include <pl/ModMenu.hpp>
#include <pl/Input.hpp>
#include <pl/Config.hpp>
#include <pl/memory/Hook.hpp>
#include <pl/memory/Patch.hpp>
#include <pl/memory/Signature.hpp>Use only public SDK headers from include/pl.
Typed Config
Keep config in mod-owned state. During lifecycle calls, ConfigFile can use the current native mod's config/config.json and config/config.schema.json paths by default:
struct ExampleConfig {
int version = 1;
bool showOverlay = true;
int opacity = 80;
};
class FullCppMod {
private:
std::optional<pl::config::ConfigFile<ExampleConfig>> mConfig;
};
bool FullCppMod::load() {
mConfig.emplace();
return mConfig->load();
}Package config.json and config.schema.json with your mod. The full example build script produces both files before creating the .levipack.
Mod Menu
bool FullCppMod::enable() {
const auto &config = mConfig->value();
return pl::modmenu::ModuleBuilder("full_cpp_mod.hud",
"Full C++ Config Demo")
.modId(getSelf().getId())
.description("Pure C++ lifecycle module with persistent typed config.")
.defaultEnabled(config.showOverlay)
.config("opacity", "Opacity", pl::modmenu::ConfigType::SliderInt,
std::to_string(config.opacity), "0", "100")
.registerModule();
}Use ButtonBuilder for floating buttons:
bool registerQuickButton(ll::mod::NativeMod &self) {
return pl::modmenu::ButtonBuilder("full_cpp_mod.quick_drop",
"Full C++ Quick Drop")
.moduleId("full_cpp_mod.hud")
.modId(self.getId())
.label("Q")
.androidKeyCode(45)
.behavior(pl::modmenu::ButtonBehavior::Click)
.registerButton();
}To draw registered images in the HUD, register raw RGBA pixels and submit an Image draw command with DrawCommand::imageId:
bool registerLogoImage(std::span<const unsigned char> rgbaPixels) {
return pl::modmenu::registerImage("full_cpp_mod.logo", rgbaPixels, 64, 64);
}
void submitLogoOverlay() {
const std::vector<pl::modmenu::DrawCommand> commands = {
{
.type = pl::modmenu::DrawCommandType::Image,
.x = 16.0f,
.y = 16.0f,
.w = 32.0f,
.h = 32.0f,
.imageId = "full_cpp_mod.logo",
},
};
pl::modmenu::submitDrawCommands("full_cpp_mod.hud", commands);
}Unregister temporary modules/buttons in disable(). For the full API reference, see Mod Menu API.
Build Options
.\examples\full-cpp-mod\build.ps1
.\examples\full-cpp-mod\build.ps1 -Clean
.\examples\full-cpp-mod\build.ps1 -Ndk <path-to-android-ndk>If -Ndk is omitted, the script resolves it from ANDROID_NDK_HOME, ANDROID_NDK_ROOT, ANDROID_HOME, or ANDROID_SDK_ROOT.
Checklist
- Build native mods for
arm64-v8a. - Link the
preloadertarget and use public SDK headers frominclude/pl. - Register one long-lived object with
PL_REGISTER_MOD. - Keep
ll::mod::NativeMod &mSelffromll::mod::NativeMod::current(). - Load config before registering runtime UI.
- Store hook and patch handles in mod-owned state.
- Unregister temporary Mod Menu entries during
disable(). - Keep callbacks valid until they are unregistered or the mod unloads.
Continue with the Mod API Reference and Config API Reference for the complete API surface.