From 0026388f65dc0d859c6762100bb03ee579930413 Mon Sep 17 00:00:00 2001 From: Fox2Code Date: Tue, 3 May 2022 12:57:01 +0200 Subject: [PATCH] Add system-less AnyKernel module install support. (Experimental) --- .../mmm/installer/InstallerActivity.java | 269 +++++++++++------- .../com/fox2code/mmm/utils/PropUtils.java | 1 + 2 files changed, 161 insertions(+), 109 deletions(-) diff --git a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java index c8a2c0c..288bb53 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java @@ -35,11 +35,14 @@ import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.internal.UiThreadHandler; import com.topjohnwu.superuser.io.SuFile; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.PrintStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; @@ -62,7 +65,8 @@ public class InstallerActivity extends CompatActivity { this.setDisplayHomeAsUpEnabled(true); setActionBarBackground(null); this.setOnBackPressedCallback(a -> { - this.canceled = true; return false; + this.canceled = true; + return false; }); final Intent intent = this.getIntent(); final String target; @@ -95,7 +99,7 @@ public class InstallerActivity extends CompatActivity { setTitle(name); this.textWrap = MainApplication.isTextWrapEnabled(); setContentView(this.textWrap ? - R.layout.installer_wrap :R.layout.installer); + R.layout.installer_wrap : R.layout.installer); int background; int foreground; if (MainApplication.getINSTANCE().isLightTheme() && @@ -118,127 +122,107 @@ public class InstallerActivity extends CompatActivity { this.getWindow().setFlags( // Note: Doesn't require WAKELOCK permission WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - if (urlMode) { - this.progressIndicator.setVisibility(View.VISIBLE); - this.installerTerminal.addLine("- Downloading " + name); - new Thread(() -> { - File moduleCache = this.toDelete = - new File(this.moduleCache, "module.zip"); - if (moduleCache.exists() && !moduleCache.delete() && - !new SuFile(moduleCache.getAbsolutePath()).delete()) - Log.e(TAG, "Failed to delete module cache"); - String errMessage = "Failed to download module zip"; - try { - Log.i(TAG, "Downloading: " + target); - byte[] rawModule = Http.doHttpGet(target,(progress, max, done) -> { - if (max <= 0 && this.progressIndicator.isIndeterminate()) - return; - this.runOnUiThread(() -> { - this.progressIndicator.setIndeterminate(false); - this.progressIndicator.setMax(max); - this.progressIndicator.setProgressCompat(progress, true); - }); + final File moduleFile = urlMode ? null : new File(target); + this.progressIndicator.setVisibility(View.VISIBLE); + this.installerTerminal.addLine("- Downloading " + name); + new Thread(() -> { + File moduleCache = this.toDelete = urlMode ? + new File(this.moduleCache, "module.zip") : moduleFile; + if (moduleCache.exists() && !moduleCache.delete() && + !new SuFile(moduleCache.getAbsolutePath()).delete()) + Log.e(TAG, "Failed to delete module cache"); + String errMessage = "Failed to download module zip"; + try { + Log.i(TAG, "Downloading: " + target); + byte[] rawModule = urlMode ? Http.doHttpGet(target, (progress, max, done) -> { + if (max <= 0 && this.progressIndicator.isIndeterminate()) + return; + this.runOnUiThread(() -> { + this.progressIndicator.setIndeterminate(false); + this.progressIndicator.setMax(max); + this.progressIndicator.setProgressCompat(progress, true); }); + }) : Files.readSU(moduleFile); + this.runOnUiThread(() -> { + this.progressIndicator.setVisibility(View.GONE); + this.progressIndicator.setIndeterminate(true); + }); + if (this.canceled) return; + if (checksum != null && !checksum.isEmpty()) { + Log.d(TAG, "Checking for checksum: " + checksum); this.runOnUiThread(() -> { - this.progressIndicator.setVisibility(View.GONE); - this.progressIndicator.setIndeterminate(true); + this.installerTerminal.addLine("- Checking file integrity"); }); - if (this.canceled) return; - if (checksum != null && !checksum.isEmpty()) { - Log.d(TAG, "Checking for checksum: " + checksum); - this.runOnUiThread(() -> { - this.installerTerminal.addLine("- Checking file integrity"); - }); - if (!Hashes.checkSumMatch(rawModule, checksum)) { - this.setInstallStateFinished(false, - "! File integrity check failed", ""); - return; - } + if (!Hashes.checkSumMatch(rawModule, checksum)) { + this.setInstallStateFinished(false, + "! File integrity check failed", ""); + return; } - if (this.canceled) return; - Files.fixJavaZipHax(rawModule); - boolean noPatch = false; - boolean isModule = false; - boolean isAnyKernel = false; - errMessage = "File is not a valid zip file"; - try (ZipInputStream zipInputStream = new ZipInputStream( - new ByteArrayInputStream(rawModule))) { - ZipEntry zipEntry; - while ((zipEntry = zipInputStream.getNextEntry()) != null) { - String entryName = zipEntry.getName(); - if (entryName.equals("anykernel.sh")) { - isAnyKernel = true; - break; - } else if (entryName.equals("module.prop")) { - noPatch = true; - isModule = true; - break; - } else if (entryName.endsWith("/module.prop")) { - isModule = true; - } + } + if (this.canceled) return; + Files.fixJavaZipHax(rawModule); + boolean noPatch = false; + boolean isModule = false; + boolean isAnyKernel = false; + errMessage = "File is not a valid zip file"; + try (ZipInputStream zipInputStream = new ZipInputStream( + new ByteArrayInputStream(rawModule))) { + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + String entryName = zipEntry.getName(); + if (entryName.equals("anykernel.sh")) { + noPatch = true; + isAnyKernel = true; + break; + } else if (entryName.equals("module.prop")) { + noPatch = true; + isModule = true; + break; + } else if (entryName.endsWith("/anykernel.sh")) { + isAnyKernel = true; + } else if (entryName.endsWith("/module.prop")) { + isModule = true; } } - if (!isModule) { - this.setInstallStateFinished(false, isAnyKernel ? - "! AnyKernel modules can only be installed on recovery" : - "! File is not a valid magisk module", ""); - return; - } - if (noPatch) { + } + if (!isModule && !isAnyKernel) { + this.setInstallStateFinished(false, + "! File is not a valid magisk module", ""); + return; + } + if (noPatch) { + if (urlMode) { errMessage = "Failed to save module zip"; try (OutputStream outputStream = new FileOutputStream(moduleCache)) { outputStream.write(rawModule); outputStream.flush(); } - } else { - errMessage = "Failed to patch module zip"; - this.runOnUiThread(() -> { - this.installerTerminal.addLine("- Patching " + name); - }); - Log.i(TAG, "Patching: " + moduleCache.getName()); - try (OutputStream outputStream = new FileOutputStream(moduleCache)) { - Files.patchModuleSimple(rawModule, outputStream); - outputStream.flush(); - } } - if (this.canceled) return; - //noinspection UnusedAssignment (Important to avoid OutOfMemoryError) - rawModule = null; // Because reference is kept when calling doInstall + } else { + errMessage = "Failed to patch module zip"; this.runOnUiThread(() -> { - this.installerTerminal.addLine("- Installing " + name); + this.installerTerminal.addLine("- Patching " + name); }); - errMessage = "Failed to install module zip"; - this.doInstall(moduleCache, noExtensions, rootless); - } catch (IOException e) { - Log.e(TAG, errMessage, e); - this.setInstallStateFinished(false, - "! " + errMessage, ""); - } - }, "Module download Thread").start(); - } else { - final File moduleFile = new File(target); - if (checksum != null && !checksum.isEmpty()) { - Log.d(TAG, "Checking for checksum: " + checksum); - this.installerTerminal.addLine("- Checking file integrity"); - try { - if (!Hashes.checkSumMatch(Files.readSU(moduleFile), checksum)) { - this.setInstallStateFinished(false, - "! File integrity check failed", ""); - return; + Log.i(TAG, "Patching: " + moduleCache.getName()); + try (OutputStream outputStream = new FileOutputStream(moduleCache)) { + Files.patchModuleSimple(rawModule, outputStream); + outputStream.flush(); } - } catch (IOException e) { - Log.e(TAG, "Failed to read file for checksum check", e); - this.setInstallStateFinished(false, - "! File integrity check failed", ""); - return; } + //noinspection UnusedAssignment (Important to avoid OutOfMemoryError) + rawModule = null; // Because reference is kept when calling doInstall if (this.canceled) return; + this.runOnUiThread(() -> { + this.installerTerminal.addLine("- Installing " + name); + }); + errMessage = "Failed to install module zip"; + this.doInstall(moduleCache, noExtensions, rootless); + } catch (IOException e) { + Log.e(TAG, errMessage, e); + this.setInstallStateFinished(false, + "! " + errMessage, ""); } - this.installerTerminal.addLine("- Installing " + name); - new Thread(() -> this.doInstall( - this.toDelete = moduleFile, noExtensions, rootless), - "Install Thread").start(); - } + }, "Module install Thread").start(); } @@ -271,15 +255,63 @@ public class InstallerActivity extends CompatActivity { String arch32 = "true"; // Do nothing by default boolean needs32bit = false; String moduleId = null; + boolean anyKernel = false; + boolean magiskModule = false; + boolean anyKernelSystemLess = false; + File anyKernelInstallScript = new File(this.moduleCache, "update-binary"); try (ZipFile zipFile = new ZipFile(file)) { + ZipEntry anyKernelSh = zipFile.getEntry("anykernel.sh"); + if (anyKernelSh != null) { // Check if module is AnyKernel module + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(zipFile.getInputStream(anyKernelSh))); + String line; + // Check if AnyKernel module support system-less + while ((line = bufferedReader.readLine()) != null) { + String trimmedLine = line.trim(); + if (trimmedLine.equals("do.modules=1")) + anyKernel = true; + if (trimmedLine.equals("do.systemless=1")) + anyKernelSystemLess = true; + } + bufferedReader.close(); + if (anyKernelSystemLess && anyKernel) { + anyKernelSystemLess = false; + ZipEntry updateBinary = zipFile.getEntry( + "META-INF/com/google/android/update-binary"); + if (updateBinary != null) { + bufferedReader = new BufferedReader( + new InputStreamReader(zipFile.getInputStream(updateBinary))); + PrintStream printStream = new PrintStream( + new FileOutputStream(anyKernelInstallScript)); + while ((line = bufferedReader.readLine()) != null) { + String trimmedLine = line.trim(); + if (trimmedLine.equals("mount_all;") || + trimmedLine.equals("umount_all;")) + continue; // Do not mount anything + line = line.replace("/sbin/sh", "/system/bin/sh"); + int prePatch = line.length(); + line = line.replace("/data/adb/modules/ak3-helper", + "/data/adb/modules-update/ak3-helper"); + if (prePatch != line.length()) anyKernelSystemLess = true; + printStream.println(line); + } + printStream.close(); + bufferedReader.close(); + if (!anyKernelSystemLess) anyKernelInstallScript.delete(); + } + } + anyKernel = true; + } if (zipFile.getEntry( // Check if module hard require 32bit support "common/addon/Volume-Key-Selector/tools/arm64/keycheck") == null && zipFile.getEntry( "common/addon/Volume-Key-Selector/install.sh") != null) { needs32bit = true; } + ZipEntry moduleProp = zipFile.getEntry("module.prop"); + magiskModule = moduleProp != null; moduleId = PropUtils.readModuleId(zipFile - .getInputStream(zipFile.getEntry("module.prop"))); + .getInputStream(moduleProp)); } catch (IOException ignored) {} int compatFlags = AppUpdateManager.getFlagsForModule(moduleId); if ((compatFlags & AppUpdateManager.FLAG_COMPAT_NEED_32BIT) != 0) @@ -294,6 +326,12 @@ public class InstallerActivity extends CompatActivity { null); return; } + if (magiskModule && moduleId == null && !anyKernel) { + // Modules without module Ids are module installed by 3rd party software + this.setInstallStateFinished(false, + "! Magisk modules require a moduleId", null); + return; + } if (Build.SUPPORTED_32_BIT_ABIS.length == 0) { if (needs32bit) { this.setInstallStateFinished(false, @@ -310,14 +348,23 @@ public class InstallerActivity extends CompatActivity { } String installCommand; File installExecutable; - if (InstallerInitializer.peekMagiskVersion() >= + if (anyKernel) { + if (!anyKernelSystemLess) { + this.setInstallStateFinished(false, + "! This AnyKernel module only support recovery install", null); + return; + } + installExecutable = anyKernelInstallScript; + installCommand = "sh \"" + installExecutable.getAbsolutePath() + "\"" + + " /dev/null 0 \"" + file.getAbsolutePath() + "\""; + } else if (InstallerInitializer.peekMagiskVersion() >= Constants.MAGISK_VER_CODE_INSTALL_COMMAND && ((compatFlags & AppUpdateManager.FLAG_COMPAT_MAGISK_CMD) != 0 || noExtensions || MainApplication.isUsingMagiskCommand())) { installCommand = "magisk --install-module \"" + file.getAbsolutePath() + "\""; installExecutable = new File(InstallerInitializer.peekMagiskPath() .equals("/sbin") ? "/sbin/magisk" : "/system/bin/magisk"); - } else { + } else if (moduleId != null) { installExecutable = this.extractInstallScript("module_installer_compat.sh"); if (installExecutable == null) { this.setInstallStateFinished(false, @@ -325,7 +372,11 @@ public class InstallerActivity extends CompatActivity { return; } installCommand = "sh \"" + installExecutable.getAbsolutePath() + "\"" + - " /dev/null 1 \"" + file.getAbsolutePath() + "\""; + " /dev/null 0 \"" + file.getAbsolutePath() + "\""; + } else { + this.setInstallStateFinished(false, + "! Zip file is not a valid Magisk or a AnyKernel module!", null); + return; } installerMonitor = new InstallerMonitor(installExecutable); if (moduleId != null) installerMonitor.setForCleanUp(moduleId); diff --git a/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java b/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java index e680465..63639c2 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java +++ b/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java @@ -303,6 +303,7 @@ public class PropUtils { } public static String readModuleId(InputStream inputStream) { + if (inputStream == null) return null; String moduleId = null; try (BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {