package com.fox2code.mmm.installer; import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.fox2code.mmm.Constants; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.NotificationType; import com.fox2code.mmm.utils.io.Files; import com.topjohnwu.superuser.NoShellException; import com.topjohnwu.superuser.Shell; import java.io.File; import java.util.ArrayList; import timber.log.Timber; public class InstallerInitializer extends Shell.Initializer { public static final int ERROR_NO_PATH = 1; public static final int ERROR_NO_SU = 2; public static final int ERROR_OTHER = 3; private static final File MAGISK_SBIN = new File("/sbin/magisk"); private static final File MAGISK_SYSTEM = new File("/system/bin/magisk"); private static final File MAGISK_SYSTEM_EX = new File("/system/xbin/magisk"); private static final boolean HAS_MAGISK = MAGISK_SBIN.exists() || MAGISK_SYSTEM.exists() || MAGISK_SYSTEM_EX.exists(); private static String MAGISK_PATH; private static int MAGISK_VERSION_CODE; private static boolean HAS_RAMDISK; @Nullable public static NotificationType getErrorNotification() { Boolean hasRoot = Shell.isAppGrantedRoot(); if (MAGISK_PATH != null && hasRoot != Boolean.FALSE) { return null; } if (!HAS_MAGISK) { return NotificationType.NO_MAGISK; } else if (hasRoot != Boolean.TRUE) { return NotificationType.ROOT_DENIED; } return NotificationType.NO_ROOT; } public static String peekMagiskPath() { return InstallerInitializer.MAGISK_PATH; } public static String peekMirrorPath() { return InstallerInitializer.MAGISK_PATH == null ? null : InstallerInitializer.MAGISK_PATH + "/.magisk/mirror"; } public static String peekModulesPath() { return InstallerInitializer.MAGISK_PATH == null ? null : InstallerInitializer.MAGISK_PATH + "/.magisk/modules"; } public static int peekMagiskVersion() { return InstallerInitializer.MAGISK_VERSION_CODE; } public static boolean peekHasRamdisk() { return InstallerInitializer.HAS_RAMDISK; } public static void tryGetMagiskPathAsync(Callback callback) { tryGetMagiskPathAsync(callback, false); } public static void tryGetMagiskPathAsync(Callback callback, boolean forceCheck) { final String MAGISK_PATH = InstallerInitializer.MAGISK_PATH; Thread thread = new Thread("Magisk GetPath Thread") { @Override public void run() { if (MAGISK_PATH != null && !forceCheck) { callback.onPathReceived(MAGISK_PATH); return; } int error; String MAGISK_PATH = null; try { MAGISK_PATH = tryGetMagiskPath(forceCheck); error = ERROR_NO_PATH; } catch (NoShellException e) { error = ERROR_NO_SU; Timber.w(e); } catch (Exception e) { error = ERROR_OTHER; Timber.e(e); } if (forceCheck) { InstallerInitializer.MAGISK_PATH = MAGISK_PATH; if (MAGISK_PATH == null) { InstallerInitializer.MAGISK_VERSION_CODE = 0; } } if (MAGISK_PATH != null) { MainApplication.setHasGottenRootAccess(true); callback.onPathReceived(MAGISK_PATH); } else { MainApplication.setHasGottenRootAccess(false); callback.onFailure(error); } } }; thread.start(); } private static String tryGetMagiskPath(boolean forceCheck) { String MAGISK_PATH = InstallerInitializer.MAGISK_PATH; int MAGISK_VERSION_CODE; boolean HAS_RAMDISK = InstallerInitializer.HAS_RAMDISK; if (MAGISK_PATH != null && !forceCheck) return MAGISK_PATH; ArrayList output = new ArrayList<>(); if (!Shell.cmd("magisk -V", "magisk --path").to(output).exec().isSuccess()) { if (output.size() != 0) { HAS_RAMDISK = "false".equals(output.get(0)) || "true".equalsIgnoreCase(System.getProperty("ro.build.ab_update")); } InstallerInitializer.HAS_RAMDISK = HAS_RAMDISK; return null; } MAGISK_PATH = output.size() < 2 ? "" : output.get(1); Timber.i("Magisk runtime path: %s", MAGISK_PATH); MAGISK_VERSION_CODE = Integer.parseInt(output.get(0)); Timber.i("Magisk version code: %s", MAGISK_VERSION_CODE); if (MAGISK_VERSION_CODE >= Constants.MAGISK_VER_CODE_FLAT_MODULES && MAGISK_VERSION_CODE < Constants.MAGISK_VER_CODE_PATH_SUPPORT && (MAGISK_PATH.isEmpty() || !Files.existsSU(new File(MAGISK_PATH)))) { MAGISK_PATH = "/sbin"; } if (MAGISK_PATH.length() != 0 && Files.existsSU(new File(MAGISK_PATH))) { InstallerInitializer.MAGISK_PATH = MAGISK_PATH; } else { Timber.e("Failed to get Magisk path (Got " + MAGISK_PATH + ")"); MAGISK_PATH = null; } InstallerInitializer.MAGISK_VERSION_CODE = MAGISK_VERSION_CODE; return MAGISK_PATH; } @Override public boolean onInit(@NonNull Context context, @NonNull Shell shell) { // open a new shell shell.newJob().add("id").exec().getOut(); // if Shell.isAppGrantedRoot() returns null, loop until it doesn't if (Shell.isAppGrantedRoot() == null) { Timber.w("Waiting for root access..."); while (Shell.isAppGrantedRoot() == null) { try { //noinspection BusyWait Thread.sleep(100); } catch (InterruptedException e) { Timber.e(e); } } } boolean hasRoot = Boolean.TRUE.equals(Shell.isAppGrantedRoot()); if (!hasRoot) { Timber.w("No root access, or libsu is misreporting"); return false; } // switch to global namespace // first, try to copy /data/adb/magisk/busybox to /data/local/tmp // if that fails, return false if (shell.newJob().add("cp -f /data/adb/magisk/busybox /data/local/tmp").exec().isSuccess()) { // switch to local namespace // first check if nsenter is available if (!shell.newJob().add("which nsenter").exec().isSuccess()) { Timber.w("nsenter cmd unavailable"); return shell.newJob().add("export ASH_STANDALONE=1; /data/local/tmp/busybox ash").exec().isSuccess(); } else { return shell.newJob().add("export ASH_STANDALONE=1; nsenter -t 1 -m -u /data/local/tmp/busybox ash").exec().isSuccess(); } } else { Timber.w("Could not copy bb"); return false; } } public interface Callback { void onPathReceived(String path); void onFailure(int error); } }