v2.3.0: Java virtual environment via app_process, SELinux bypass for build tools

This commit is contained in:
admin
2026-05-20 12:28:24 +04:00
Unverified
parent 71a26e259d
commit f86a5added
10 changed files with 417 additions and 87 deletions

View File

@@ -7,8 +7,8 @@ android {
applicationId "ai.z.chat"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 18
versionName "2.2.5"
versionCode 19
versionName "2.3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -461,6 +461,53 @@ public class BootstrapPlugin extends Plugin {
} catch (Exception e) {
try { call.reject("Download failed: " + e.getMessage()); } catch (Exception ignored) {}
}
}).start();
}
@PluginMethod
public void extractAsset(PluginCall call) {
String src = call.getString("src", "");
String dest = call.getString("dest", "");
if (src.isEmpty() || dest.isEmpty()) {
call.reject("src and dest required");
return;
}
try {
java.io.InputStream is = getContext().getAssets().open(src);
new File(dest).getParentFile().mkdirs();
java.io.FileOutputStream fos = new java.io.FileOutputStream(dest);
byte[] buf = new byte[8192];
int r;
long total = 0;
while ((r = is.read(buf)) > 0) {
fos.write(buf, 0, r);
total += r;
}
fos.close();
is.close();
call.resolve(new JSObject().put("path", dest).put("size", total));
} catch (Exception e) {
call.reject("Extract failed: " + e.getMessage());
}
}
@PluginMethod
public void downloadFile(PluginCall call) {
String url = call.getString("url", "");
String dest = call.getString("dest", "");
if (url.isEmpty() || dest.isEmpty()) {
call.reject("url and dest required");
return;
}
call.setKeepAlive(true);
new Thread(() -> {
try {
new File(dest).getParentFile().mkdirs();
downloadFile(url, new File(dest), null);
call.resolve(new JSObject().put("path", dest));
} catch (Exception e) {
try { call.reject("Download failed: " + e.getMessage()); } catch (Exception ignored) {}
}
}).start();
}
@@ -624,6 +671,170 @@ public class BootstrapPlugin extends Plugin {
}).start();
}
@PluginMethod
public void installAapt2(PluginCall call) {
call.setKeepAlive(true);
new Thread(() -> {
try {
String arch = getArch();
String toolsDir = call.getString("toolsDir", "");
if (toolsDir.isEmpty()) {
call.reject("toolsDir required");
return;
}
File aapt2File = new File(toolsDir, "bin/aapt2");
if (aapt2File.exists()) {
call.resolve(new JSObject().put("installed", true).put("path", aapt2File.getAbsolutePath()));
return;
}
String packagesUrl = "https://packages.termux.dev/apt/termux-main/dists/stable/main/binary-" + arch + "/Packages";
String packagesContent = downloadToString(packagesUrl);
String[] neededPkgs = {"aapt2", "aapt"};
java.util.Map<String, String> debUrls = new java.util.LinkedHashMap<>();
String[] blocks = packagesContent.split("\n\n");
for (String block : blocks) {
for (String pkgName : neededPkgs) {
if (block.contains("Package: " + pkgName + "\n") && !debUrls.containsKey(pkgName)) {
String filename = null;
for (String line : block.split("\n")) {
if (line.startsWith("Filename:")) {
filename = line.substring("Filename:".length()).trim();
}
}
if (filename != null) {
debUrls.put(pkgName, "https://packages.termux.dev/apt/termux-main/" + filename);
}
break;
}
}
if (debUrls.size() >= neededPkgs.length) break;
}
if (debUrls.isEmpty()) {
call.reject("aapt2 package not found in repo");
return;
}
new File(toolsDir + "/bin").mkdirs();
new File(toolsDir + "/lib").mkdirs();
new File(toolsDir + "/share/aapt2").mkdirs();
for (java.util.Map.Entry<String, String> entry : debUrls.entrySet()) {
String pkgName = entry.getKey();
String debUrl = entry.getValue();
Log.i(TAG, "Downloading " + pkgName + ": " + debUrl);
File debFile = new File(getContext().getCacheDir(), pkgName + ".deb");
downloadFile(debUrl, debFile, null);
Log.i(TAG, "Extracting " + pkgName + " from .deb...");
extractDebToDir(debFile, new File(toolsDir));
debFile.delete();
}
if (aapt2File.exists()) {
try { Os.chmod(aapt2File.getAbsolutePath(), 0755); } catch (Exception e) {}
File aaptBin = new File(toolsDir, "bin/aapt");
if (aaptBin.exists()) {
try { Os.chmod(aaptBin.getAbsolutePath(), 0755); } catch (Exception e) {}
}
call.resolve(new JSObject().put("installed", true).put("path", aapt2File.getAbsolutePath()));
} else {
call.reject("aapt2 binary not found after extraction");
}
} catch (Exception e) {
Log.e(TAG, "installAapt2 failed", e);
try { call.reject("installAapt2 failed: " + e.getMessage()); } catch (Exception ignored) {}
}
}).start();
}
private void extractDebToDir(File debFile, File destDir) throws Exception {
FileInputStream fis = new FileInputStream(debFile);
byte[] magic = new byte[8];
if (fis.read(magic) != 8 || !new String(magic).equals("!<arch>\n")) {
fis.close();
throw new RuntimeException("Not a valid .deb (AR) file");
}
while (true) {
byte[] header = new byte[60];
int read = fis.read(header);
if (read < 60) break;
String name = new String(header, 0, 16).trim();
String sizeStr = new String(header, 48, 10).trim();
long entrySize = Long.parseLong(sizeStr);
if (name.startsWith("data.tar")) {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
byte[] buf = new byte[8192];
long remaining = entrySize;
while (remaining > 0) {
int toRead = (int) Math.min(buf.length, remaining);
int r = fis.read(buf, 0, toRead);
if (r <= 0) break;
baos.write(buf, 0, r);
remaining -= r;
}
fis.close();
byte[] tarData = baos.toByteArray();
if (name.contains(".xz")) {
tarData = decompressXz(tarData);
} else if (name.contains(".gz")) {
tarData = decompressGz(tarData);
} else if (name.contains(".zst")) {
throw new RuntimeException("ZSTD compression not supported");
}
extractTarToDir(tarData, destDir);
return;
} else {
long skip = (entrySize + 1) & ~1L;
fis.skip(skip);
}
}
fis.close();
}
private void extractTarToDir(byte[] tarData, File destDir) throws Exception {
String prefix = "data/data/com.termux/files/usr/";
int offset = 0;
while (offset + 512 <= tarData.length) {
String headerName = new String(tarData, offset, 100).trim().replace("\0", "");
String sizeStr = new String(tarData, offset + 124, 12).trim().replace("\0", "");
long fileSize = 0;
try { fileSize = Long.parseLong(sizeStr, 8); } catch (Exception e) { break; }
String type = new String(tarData, offset + 156, 1);
String relPath = headerName;
if (relPath.startsWith(prefix)) {
relPath = relPath.substring(prefix.length());
} else if (relPath.startsWith("./")) {
relPath = relPath.substring(2);
if (relPath.startsWith(prefix)) relPath = relPath.substring(prefix.length());
}
if (!relPath.isEmpty() && fileSize > 0 && (type.equals("0") || type.isEmpty())) {
File outFile = new File(destDir, relPath);
outFile.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(outFile);
fos.write(tarData, offset + 512, (int) Math.min(fileSize, tarData.length - offset - 512));
fos.close();
if (relPath.startsWith("bin/") || relPath.contains("/bin/")) {
try { Os.chmod(outFile.getAbsolutePath(), 0755); } catch (Exception e) {}
}
}
long blocks = (fileSize + 511) / 512;
offset += 512 + (int)(blocks * 512);
}
}
private String downloadToString(String urlStr) throws Exception {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();

View File

@@ -347,11 +347,13 @@ public class ShellPlugin extends Plugin {
envList.add("JAVA_HOME=" + toolsDir + "/java");
envList.add("PROJECTS=" + projectsDir);
if (hasOurPrefix) {
envList.add("LD_LIBRARY_PATH=" + prefixDir + "/lib");
envList.add("LD_LIBRARY_PATH=" + prefixDir + "/lib:" + toolsDir + "/lib");
envList.add("BOOTSTRAP=zaichat");
if (prootLoaderPath != null) {
envList.add("PROOT_LOADER=" + prootLoaderPath);
}
} else {
envList.add("LD_LIBRARY_PATH=" + toolsDir + "/lib");
}
if (hasTermux) {
envList.add("TERMUX_VERSION=" + getTermuxVersion());