v2.3.0: Java virtual environment via app_process, SELinux bypass for build tools
This commit is contained in:
BIN
android/app/src/main/assets/jars/apksigner.jar
Normal file
BIN
android/app/src/main/assets/jars/apksigner.jar
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/jars/d8.jar
Normal file
BIN
android/app/src/main/assets/jars/d8.jar
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/jars/ecj.jar
Normal file
BIN
android/app/src/main/assets/jars/ecj.jar
Normal file
Binary file not shown.
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user