diff --git a/README.md b/README.md index 3704065..67d8a38 100644 --- a/README.md +++ b/README.md @@ -631,6 +631,13 @@ data: [DONE] ## Changelog +### v2.2.4 (2026-05-19) +- **Bundled PRoot** — proot binary (v5.1.107) included in APK as native library with executable SELinux label +- **Auto-Proot Install** — `ShellPlugin.refreshShell()` copies proot + loader from APK's `nativeLibraryDir` to `$PREFIX/bin/` and `$PREFIX/libexec/proot/` on first use +- **Auto-Proot Wrapping** — commands using `$PREFIX/bin/` paths (pkg/apt/dpkg) automatically wrapped with `proot -0 -b /dev -b /proc -b /sys -r $PREFIX` +- **PROOT_LOADER env** — `PROOT_LOADER` environment variable set so proot finds its loader binary +- Supports arm64-v8a, armeabi-v7a, x86_64, x86 architectures (~350KB total added to APK) + ### v2.2.3 (2026-05-19) - **Os.chmod() Fix** — uses `android.system.Os.chmod()` (direct syscall) instead of `Runtime.exec("chmod")` for reliable execute permissions - **PRoot Fallback** — auto-downloads PRoot from Termux package repo when SELinux blocks binary execution diff --git a/android/app/build.gradle b/android/app/build.gradle index b86e157..3dcf654 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "ai.z.chat" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 16 - versionName "2.2.3" + versionCode 17 + versionName "2.2.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' diff --git a/android/app/src/main/java/ai/z/chat/ShellPlugin.java b/android/app/src/main/java/ai/z/chat/ShellPlugin.java index 12004b4..34c6ffc 100644 --- a/android/app/src/main/java/ai/z/chat/ShellPlugin.java +++ b/android/app/src/main/java/ai/z/chat/ShellPlugin.java @@ -49,20 +49,41 @@ public class ShellPlugin extends Plugin { } private String prootPath = null; + private String prootLoaderPath = null; private void refreshShell() { - File proot = new File(prefixDir + "/bin/proot"); - if (proot.exists()) { - try { Os.chmod(proot.getAbsolutePath(), 0755); } catch (Exception e) {} - prootPath = proot.getAbsolutePath(); - } else { - String nativeLibDir = getNativeLibDir(); - if (nativeLibDir != null) { - File nativeProot = new File(nativeLibDir, "libproot.so"); - if (nativeProot.exists()) { - prootPath = nativeProot.getAbsolutePath(); + String nativeLibDir = getNativeLibDir(); + + File prootInPrefix = new File(prefixDir + "/bin/proot"); + if (!prootInPrefix.exists() && nativeLibDir != null) { + File bundledProot = new File(nativeLibDir, "libproot.so"); + File bundledLoader = new File(nativeLibDir, "libproot-loader.so"); + if (bundledProot.exists()) { + try { + new File(prefixDir + "/bin").mkdirs(); + new File(prefixDir + "/libexec").mkdirs(); + new File(prefixDir + "/libexec/proot").mkdirs(); + copyFile(bundledProot, new File(prefixDir + "/bin/proot")); + Os.chmod(prefixDir + "/bin/proot", 0755); + if (bundledLoader.exists()) { + copyFile(bundledLoader, new File(prefixDir + "/libexec/proot/loader")); + Os.chmod(prefixDir + "/libexec/proot/loader", 0755); + File loader32 = new File(nativeLibDir, "libproot-loader32.so"); + if (loader32.exists()) { + copyFile(loader32, new File(prefixDir + "/libexec/proot/loader32")); + Os.chmod(prefixDir + "/libexec/proot/loader32", 0755); + } + } + prootPath = prefixDir + "/bin/proot"; + prootLoaderPath = prefixDir + "/libexec/proot/loader"; + Log.i(TAG, "Installed bundled proot from APK: " + prootPath); + } catch (Exception e) { + Log.w(TAG, "Failed to install bundled proot: " + e.getMessage()); } } + } else if (prootInPrefix.exists()) { + try { Os.chmod(prootInPrefix.getAbsolutePath(), 0755); } catch (Exception e) {} + prootPath = prootInPrefix.getAbsolutePath(); } File bash = new File(prefixDir + "/bin/bash"); @@ -77,6 +98,16 @@ public class ShellPlugin extends Plugin { shellPath = "/system/bin/sh"; } + private void copyFile(File src, File dst) throws Exception { + java.io.FileInputStream fis = new java.io.FileInputStream(src); + java.io.FileOutputStream fos = new java.io.FileOutputStream(dst); + byte[] buf = new byte[8192]; + int r; + while ((r = fis.read(buf)) > 0) fos.write(buf, 0, r); + fos.close(); + fis.close(); + } + private String getNativeLibDir() { try { return getContext().getApplicationInfo().nativeLibraryDir; @@ -106,6 +137,8 @@ public class ShellPlugin extends Plugin { if (useProot && prootPath != null) { actualCommand = prootPath + " -0 -b /dev -b /proc -b /sys -r " + prefixDir + " /bin/sh -c " + bashEscape(command); + } else if (prootPath != null && isTermuxCommand(command)) { + actualCommand = prootPath + " -0 -b /dev -b /proc -b /sys -r " + prefixDir + " /bin/sh -c " + bashEscape(command); } ProcessBuilder pb = new ProcessBuilder(shell, "-c", actualCommand); @@ -141,6 +174,11 @@ public class ShellPlugin extends Plugin { return "'" + s.replace("'", "'\\''") + "'"; } + private boolean isTermuxCommand(String cmd) { + if (prefixDir == null) return false; + return cmd.contains(prefixDir + "/bin/") || cmd.contains("pkg ") || cmd.contains("apt ") || cmd.contains("dpkg "); + } + @PluginMethod public void kill(PluginCall call) { String processId = call.getString("pid", ""); @@ -334,6 +372,9 @@ public class ShellPlugin extends Plugin { if (hasOurPrefix) { envList.add("LD_LIBRARY_PATH=" + prefixDir + "/lib"); envList.add("BOOTSTRAP=zaichat"); + if (prootLoaderPath != null) { + envList.add("PROOT_LOADER=" + prootLoaderPath); + } } if (hasTermux) { envList.add("TERMUX_VERSION=" + getTermuxVersion()); diff --git a/android/app/src/main/jniLibs/arm64-v8a/libproot-loader.so b/android/app/src/main/jniLibs/arm64-v8a/libproot-loader.so new file mode 100755 index 0000000..f123455 Binary files /dev/null and b/android/app/src/main/jniLibs/arm64-v8a/libproot-loader.so differ diff --git a/android/app/src/main/jniLibs/arm64-v8a/libproot-loader32.so b/android/app/src/main/jniLibs/arm64-v8a/libproot-loader32.so new file mode 100755 index 0000000..8367cbb Binary files /dev/null and b/android/app/src/main/jniLibs/arm64-v8a/libproot-loader32.so differ diff --git a/android/app/src/main/jniLibs/arm64-v8a/libproot.so b/android/app/src/main/jniLibs/arm64-v8a/libproot.so new file mode 100755 index 0000000..3205aca Binary files /dev/null and b/android/app/src/main/jniLibs/arm64-v8a/libproot.so differ diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libproot-loader.so b/android/app/src/main/jniLibs/armeabi-v7a/libproot-loader.so new file mode 100755 index 0000000..e7449aa Binary files /dev/null and b/android/app/src/main/jniLibs/armeabi-v7a/libproot-loader.so differ diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libproot.so b/android/app/src/main/jniLibs/armeabi-v7a/libproot.so new file mode 100755 index 0000000..957ea8a Binary files /dev/null and b/android/app/src/main/jniLibs/armeabi-v7a/libproot.so differ diff --git a/android/app/src/main/jniLibs/x86/libproot-loader.so b/android/app/src/main/jniLibs/x86/libproot-loader.so new file mode 100755 index 0000000..a145ef7 Binary files /dev/null and b/android/app/src/main/jniLibs/x86/libproot-loader.so differ diff --git a/android/app/src/main/jniLibs/x86/libproot.so b/android/app/src/main/jniLibs/x86/libproot.so new file mode 100755 index 0000000..ea7170c Binary files /dev/null and b/android/app/src/main/jniLibs/x86/libproot.so differ diff --git a/android/app/src/main/jniLibs/x86_64/libproot-loader.so b/android/app/src/main/jniLibs/x86_64/libproot-loader.so new file mode 100755 index 0000000..391b6b5 Binary files /dev/null and b/android/app/src/main/jniLibs/x86_64/libproot-loader.so differ diff --git a/android/app/src/main/jniLibs/x86_64/libproot-loader32.so b/android/app/src/main/jniLibs/x86_64/libproot-loader32.so new file mode 100755 index 0000000..6f0aae8 Binary files /dev/null and b/android/app/src/main/jniLibs/x86_64/libproot-loader32.so differ diff --git a/android/app/src/main/jniLibs/x86_64/libproot.so b/android/app/src/main/jniLibs/x86_64/libproot.so new file mode 100755 index 0000000..9a31fcb Binary files /dev/null and b/android/app/src/main/jniLibs/x86_64/libproot.so differ diff --git a/package.json b/package.json index 8432363..f572991 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zai-chat", - "version": "2.2.3", + "version": "2.2.4", "description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan", "main": "index.js", "scripts": { diff --git a/www/index.html b/www/index.html index 12ff0d9..56d5ecd 100644 --- a/www/index.html +++ b/www/index.html @@ -327,13 +327,24 @@

About

-

Z.AI Chat v2.2.3

+

Z.AI Chat v2.2.4

Built with Z.AI SDK & GLM-5.1

Compatible with Android 15/16

Changelog