跳到正文
本頁目錄

原生模組快速開始

Android,而不是 LeviLamina

本部分介紹 Android 上 Minecraft 基岩版的原生模組。它們由 Android 版 LeviLauncher 通過 Preloader SDK(LiteLDev/preloader-android)加載,而不是由 LeviLamina 加載,並使用本部分記錄的獨立 pl:: API。

本頁面描述 Android 版 LeviLauncher 原生模組的受支持開發路徑。公開 SDK 位於 LiteLDev/preloader-android。

獨立第三方模組建議從 Android 模組模板 開始。

推薦以 examples/full-cpp-mod 作為參考實現。它包含生命週期註冊、類型化 配置、Mod Menu 集成、Android 打包和 .levipack 輸出。

構建示例

在倉庫根目錄運行:

PowerShell
.\examples\full-cpp-mod\build.ps1 -Clean

這個示例可以在當前倉庫直接構建。獨立模組工程建議從模板開始,並把 SDK 作為外部依賴引入。

輸出:

Text Only
examples\full-cpp-mod\dist\arm64-v8a\full-cpp-mod\
examples\full-cpp-mod\dist\arm64-v8a\full-cpp-mod.levipack

可以導入 .levipack,也可以把解包後的模組目錄複製到啟動器的原生模組位置。

包結構

Text Only
full-cpp-mod/
├── manifest.json
├── libfull_cpp_mod.so
└── config/
    ├── config.json
    └── config.schema.json

目錄名是運行期模組 id。發佈後保持穩定,因為它會用於路徑、Mod Menu 歸屬和用戶持久化狀態。

manifest.json

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": []
}
字段說明
type必須是 preload-native。
entry模組目錄內 Android .so 的相對路徑。
name啟動器顯示名。
author作者信息。
version模組版本。
icon可選圖標相對路徑。
minecraft_versions支持精確版本和 * 前綴通配;缺失或為空表示全部版本。
overwrite_files可選的相對文件列表;重複導入已安裝模組時,這些已存在文件允許被替換。缺失或為空時不額外覆蓋文件。
overwrite_folders可選的相對目錄列表;重複導入已安裝模組時,這些目錄內已存在的文件允許被替換。缺失或為空時,除 manifest.json 和 entry 外不覆蓋已有文件。

生命週期形態

C++
#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() 是必需的。其它生命週期函數可選,缺失時默認成功。

在構造函數中保存 loader 提供的原生模組對象:

C++
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 依賴

獨立 Android 原生模組工程使用 FetchContent 引入 preloader-android, 並把自己的模組庫鏈接到 preloader target:

CMake
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)

實際項目應把 GIT_TAG 固定到 release tag,避免構建結果漂移。

常用 SDK 頭文件:

C++
#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>

只使用 include/pl 下的公開 SDK 頭文件。

類型化配置

配置應保存在模組自有狀態中。生命週期調用期間,ConfigFile 默認會使用當前 原生模組的 config/config.json 和 config/config.schema.json 路徑:

C++
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();
}

把 config.json 和 config.schema.json 隨模組一起打包。完整示例的構建腳本會在 生成 .levipack 前產出這兩個文件。

Mod Menu

C++
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();
}

浮動按鈕使用 ButtonBuilder:

C++
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();
}

要在 HUD 中繪製已註冊圖片,請先註冊原始 RGBA 像素,再提交帶 DrawCommand::imageId 的 Image 繪製命令:

C++
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);
}

如果模塊或按鈕是臨時 UI,請在 disable() 中註銷。 完整 API 參考見 Mod Menu API。

構建選項

PowerShell
.\examples\full-cpp-mod\build.ps1
.\examples\full-cpp-mod\build.ps1 -Clean
.\examples\full-cpp-mod\build.ps1 -Ndk <path-to-android-ndk>

省略 -Ndk 時,腳本會從 ANDROID_NDK_HOME、ANDROID_NDK_ROOT、ANDROID_HOME 或 ANDROID_SDK_ROOT 解析 NDK。

檢查清單

  • 原生模組目標 ABI 為 arm64-v8a。
  • 鏈接 preloader target,並只使用 include/pl 下的公開 SDK 頭文件。
  • 使用 PL_REGISTER_MOD 註冊一個長期存活對象。
  • 從 ll::mod::NativeMod::current() 保存 ll::mod::NativeMod &mSelf。
  • 註冊運行期 UI 前先加載配置。
  • hook 和 patch handle 放在模組自有狀態中。
  • disable() 中註銷臨時 Mod Menu 項。
  • callback 在註銷或 unload 前必須保持有效。

完整 API 繼續閱讀 Mod API 參考 和 Config API 參考。