From 8471fa01070dfe80311630f22490c2ca749cb42a Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 14 Jul 2026 09:22:50 -0600 Subject: [PATCH] Android: Bundle in-tree mods into the APK --- .github/workflows/build.yml | 4 ++ platforms/android/.gitignore | 1 + platforms/android/app/build.gradle | 5 ++ .../com/twilitrealm/dusk/DuskActivity.java | 49 +++++++++++++++++++ platforms/android/scripts/stage-jni-libs.sh | 24 +++++++++ 5 files changed, 83 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a8c505c52..2f9f666700 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -205,6 +205,9 @@ jobs: - name: Build native library run: cmake --build --preset ${{matrix.preset}} --target dusklight + - name: Build bundled mods + run: cmake --build --preset ${{matrix.preset}} --target dusklight_mods + - name: Stage stripped JNI library run: ANDROID_STAGE_ABIS="${{matrix.abi}}" platforms/android/scripts/stage-jni-libs.sh @@ -289,5 +292,6 @@ jobs: build/install/*.dll build/install/*.symdb build/install/res/ + build/install/mods/ build/install/debug.7z build/install/sdk/ diff --git a/platforms/android/.gitignore b/platforms/android/.gitignore index 7775e3c7f4..002aaa86aa 100644 --- a/platforms/android/.gitignore +++ b/platforms/android/.gitignore @@ -3,3 +3,4 @@ build/ app/build/ local.properties app/src/main/jniLibs/*/*.so +app/src/main/bundled_mods/ diff --git a/platforms/android/app/build.gradle b/platforms/android/app/build.gradle index 8527cd1f4d..c7a25e732b 100644 --- a/platforms/android/app/build.gradle +++ b/platforms/android/app/build.gradle @@ -12,6 +12,11 @@ def syncDuskAssets = tasks.register('syncDuskAssets', Sync) { into 'res' exclude '**/.DS_Store' } + // Staged by platforms/android/scripts/stage-jni-libs.sh + from(new File(projectDir, 'src/main/bundled_mods')) { + into 'mods' + include '*.dusk' + } into duskGeneratedAssetsDir } diff --git a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskActivity.java b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskActivity.java index 96fc302d9e..2fa6cbacb3 100644 --- a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskActivity.java +++ b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskActivity.java @@ -27,6 +27,10 @@ import org.libsdl.app.SDLActivity; import org.libsdl.app.SDLSurface; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -90,10 +94,55 @@ public class DuskActivity extends SDLActivity { @Override protected void onCreate(Bundle savedInstanceState) { + extractBundledMods(); super.onCreate(savedInstanceState); hideSystemBars(); } + // Bundled mod packages ship as APK assets, which the native loader cannot read directly; + // mirror them into internal storage (the loader's CachePath/bundled_mods search dir) + // before SDL_main starts. + private void extractBundledMods() { + File outDir = new File(getFilesDir(), "bundled_mods"); + try { + deleteRecursively(outDir); // drop packages removed by an app update + String[] names = getAssets().list("mods"); + if (names == null || names.length == 0) { + return; + } + if (!outDir.mkdirs()) { + Log.w(TAG, "Unable to create " + outDir); + return; + } + byte[] buffer = new byte[65536]; + for (String name : names) { + if (!name.endsWith(".dusk")) { + continue; + } + try (InputStream in = getAssets().open("mods/" + name); + OutputStream out = new FileOutputStream(new File(outDir, name))) + { + int count; + while ((count = in.read(buffer)) > 0) { + out.write(buffer, 0, count); + } + } + } + } catch (IOException e) { + Log.w(TAG, "Failed to extract bundled mods", e); + } + } + + private static void deleteRecursively(File file) { + File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + deleteRecursively(child); + } + } + file.delete(); + } + @Override protected SDLSurface createSDLSurface(Context context) { return new DuskSurface(context); diff --git a/platforms/android/scripts/stage-jni-libs.sh b/platforms/android/scripts/stage-jni-libs.sh index 42b08c13e6..a30d961156 100755 --- a/platforms/android/scripts/stage-jni-libs.sh +++ b/platforms/android/scripts/stage-jni-libs.sh @@ -70,3 +70,27 @@ for abi in $ANDROID_STAGE_ABIS; do esac copy_lib "$abi" "$src" done + +# Stage bundled mod packages into the app's assets source dir. +MODS_STAGING_DIR="$ROOT_DIR/platforms/android/app/src/main/bundled_mods" +rm -rf "$MODS_STAGING_DIR" +mkdir -p "$MODS_STAGING_DIR" +for abi in $ANDROID_STAGE_ABIS; do + case "$abi" in + arm64-v8a) build_dir="$ROOT_DIR/build/android-arm64" ;; + x86_64) build_dir="$ROOT_DIR/build/android-x86_64" ;; + esac + [[ -d "$build_dir/bundled_mods" ]] || continue + for pkg in "$build_dir/bundled_mods"/*.dusk; do + [[ -f "$pkg" ]] || continue + name="$(basename "$pkg")" + if [[ ! -f "$MODS_STAGING_DIR/$name" ]]; then + cp -f "$pkg" "$MODS_STAGING_DIR/$name" + echo "Staged bundled mod $pkg" + else + stage_dir="$build_dir/mods/${name%.dusk}/${name%.dusk}_stage" + (cd "$stage_dir" && zip -q -r "$MODS_STAGING_DIR/$name" lib) + echo "Appended $abi libraries to bundled mod $name" + fi + done +done