Support fixing Magisk environment

This commit is contained in:
topjohnwu 2018-05-13 18:14:10 +08:00
parent dde0a4a7c8
commit 630f2b7d19
10 changed files with 308 additions and 186 deletions

View File

@ -8,7 +8,7 @@ android {
applicationId "com.topjohnwu.magisk"
minSdkVersion 21
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 115
versionCode 117
versionName "5.7.0"
javaCompileOptions {
annotationProcessorOptions {
@ -46,7 +46,7 @@ dependencies {
implementation "com.android.support:design:${rootProject.ext.supportLibVersion}"
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
implementation 'com.jakewharton:butterknife:8.8.1'
implementation 'com.atlassian.commonmark:commonmark:0.10.0'
implementation 'com.atlassian.commonmark:commonmark:0.11.0'
implementation 'org.kamranzafar:jtar:2.3'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

View File

@ -259,17 +259,22 @@ public class MagiskFragment extends Fragment
}
}
if (!shownDialog && (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE)) {
install();
}
magiskUpdateIcon.setImageResource(image);
magiskUpdateIcon.setColorFilter(color);
magiskUpdateIcon.setVisibility(View.VISIBLE);
magiskUpdateProgress.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
if (!shownDialog) {
if (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
install();
} else if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
!Utils.cmdResult("env_check")) {
ShowUI.envFixDialog(getActivity());
}
}
}
private void updateSafetyNetUI(int response) {

View File

@ -105,10 +105,11 @@ public class MagiskManager extends Shell.ContainerApp {
Shell.setInitializer(new Shell.Initializer() {
@Override
public void onRootShellInit(@NonNull Shell shell) {
try (InputStream utils = getAssets().open(Const.UTIL_FUNCTIONS);
InputStream magiskDB = getResources().openRawResource(R.raw.magiskdb)) {
shell.loadInputStream(null, null, utils);
shell.loadInputStream(null, null, magiskDB);
try (InputStream magiskUtils = getAssets().open(Const.UTIL_FUNCTIONS);
InputStream managerUtils = getResources().openRawResource(R.raw.utils)
) {
shell.loadInputStream(null, null, magiskUtils);
shell.loadInputStream(null, null, managerUtils);
} catch (IOException e) {
e.printStackTrace();
}

View File

@ -1,13 +1,16 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.app.ProgressDialog;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.view.View;
import android.widget.Toast;
import com.topjohnwu.magisk.FlashActivity;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.container.TarEntry;
import com.topjohnwu.magisk.utils.Const;
import com.topjohnwu.magisk.utils.Utils;
@ -30,6 +33,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.List;
@ -37,42 +41,228 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
private static final int PATCH_MODE = 0;
private static final int DIRECT_MODE = 1;
private static final int FIX_ENV_MODE = 2;
private Uri mBootImg, mZip;
private List<String> console, logs;
private String mBootLocation;
private int mode;
private File install;
private ProgressDialog dialog;
private boolean highCompression;
private InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip) {
public InstallMagisk(Activity context, Uri zip) {
super(context);
this.console = console;
this.logs = logs;
mZip = zip;
mode = FIX_ENV_MODE;
}
public InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, Uri boot) {
private InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip) {
this(context, zip);
this.console = console;
this.logs = logs;
}
public InstallMagisk(FlashActivity context, List<String> console, List<String> logs, Uri zip, Uri boot) {
this(context, console, logs, zip);
mBootImg = boot;
highCompression = false;
mode = PATCH_MODE;
}
public InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, String boot) {
public InstallMagisk(FlashActivity context, List<String> console, List<String> logs, Uri zip, String boot) {
this(context, console, logs, zip);
mBootLocation = boot;
mode = DIRECT_MODE;
}
@Override
protected void onPreExecute() {
if (mode == FIX_ENV_MODE) {
dialog = ProgressDialog.show(getActivity(),
"Additional Setup", "Running environment setup...");
console = new NOPList<>();
}
}
private void extractFiles(String arch) throws IOException {
MagiskManager mm = MagiskManager.get();
console.add("- Extracting files");
try (InputStream in = mm.getContentResolver().openInputStream(mZip)) {
if (in == null) throw new FileNotFoundException();
BufferedInputStream buf = new BufferedInputStream(in);
buf.mark(Integer.MAX_VALUE);
ZipUtils.unzip(buf, install, arch + "/", true);
buf.reset();
ZipUtils.unzip(buf, install, "common/", true);
buf.reset();
ZipUtils.unzip(buf, install, "chromeos/", false);
buf.reset();
ZipUtils.unzip(buf, install, "META-INF/com/google/android/update-binary", true);
buf.close();
} catch (FileNotFoundException e) {
console.add("! Invalid Uri");
throw e;
} catch (IOException e) {
console.add("! Cannot unzip zip");
throw e;
}
Shell.Sync.sh(Utils.fmt("chmod -R 755 %s/*; %s/magiskinit -x magisk %s/magisk",
install, install, install));
}
private boolean dumpBoot(File boot) throws IOException {
MagiskManager mm = MagiskManager.get();
switch (mode) {
case PATCH_MODE:
// Copy boot image to local
try (InputStream in = mm.getContentResolver().openInputStream(mBootImg);
OutputStream out = new FileOutputStream(boot)
) {
InputStream source;
if (in == null) throw new FileNotFoundException();
if (Utils.getNameFromUri(mm, mBootImg).endsWith(".tar")) {
// Extract boot.img from tar
TarInputStream tar = new TarInputStream(new BufferedInputStream(in));
org.kamranzafar.jtar.TarEntry entry;
while ((entry = tar.getNextEntry()) != null) {
if (entry.getName().equals("boot.img"))
break;
}
source = tar;
} else {
// Direct copy raw image
source = new BufferedInputStream(in);
}
ShellUtils.pump(source, out);
} catch (FileNotFoundException e) {
console.add("! Invalid Uri");
throw e;
} catch (IOException e) {
console.add("! Copy failed");
throw e;
}
break;
case DIRECT_MODE:
console.add("- Patch boot/ramdisk image: " + mBootLocation);
if (mm.remoteMagiskVersionCode >= 1463) {
highCompression = Integer.parseInt(Utils.cmd(Utils.fmt(
"%s/magiskboot --parse %s; echo $?",
install, mBootLocation))) == 2;
if (highCompression)
console.add("! Insufficient boot partition size detected");
}
if (boot.createNewFile()) {
Shell.Sync.su("cat " + mBootLocation + " > " + boot);
} else {
console.add("! Dump boot image failed");
return false;
}
break;
default:
return false;
}
return true;
}
private boolean patchBoot(File boot, SuFile patched_boot) throws IOException {
MagiskManager mm = MagiskManager.get();
boolean isSigned;
try (InputStream in = new FileInputStream(boot)) {
isSigned = SignBoot.verifySignature(in, null);
if (isSigned) {
console.add("- Boot image is signed with AVB 1.0");
}
} catch (IOException e) {
console.add("! Unable to check signature");
throw e;
}
// Patch boot image
Shell.Sync.sh(console, logs,
"cd " + install,
Utils.fmt("KEEPFORCEENCRYPT=%b KEEPVERITY=%b HIGHCOMP=%b " +
"sh update-binary indep boot_patch.sh %s || echo 'Failed!'",
mm.keepEnc, mm.keepVerity, highCompression, boot));
if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
return false;
Shell.Sync.sh("mv -f new-boot.img ../",
"mv bin/busybox busybox",
"rm -rf magisk.apk bin *.img update-binary",
"cd /");
if (isSigned) {
console.add("- Signing boot image with test keys");
File signed = new File(install.getParent(), "signed.img");
try (InputStream in = new SuFileInputStream(patched_boot);
OutputStream out = new BufferedOutputStream(new FileOutputStream(signed))
) {
SignBoot.doSignature("/boot", in, out, null, null);
}
Shell.Sync.sh("mv -f " + signed + " " + patched_boot);
}
return true;
}
private void outputBoot(SuFile patched_boot) throws IOException {
MagiskManager mm = MagiskManager.get();
switch (mode) {
case PATCH_MODE:
File dest = new File(Const.EXTERNAL_PATH, "patched_boot" + mm.bootFormat);
dest.getParentFile().mkdirs();
OutputStream out;
switch (mm.bootFormat) {
case ".img.tar":
out = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
((TarOutputStream) out).putNextEntry(new TarEntry(patched_boot, "boot.img"));
break;
default:
case ".img":
out = new BufferedOutputStream(new FileOutputStream(dest));
break;
}
try (InputStream in = new SuFileInputStream(patched_boot)) {
ShellUtils.pump(in, out);
out.close();
}
console.add("");
console.add("*********************************");
console.add(" Patched Boot Image is placed in ");
console.add(" " + dest + " ");
console.add("*********************************");
break;
case DIRECT_MODE:
String binPath = mm.remoteMagiskVersionCode >= Const.MAGISK_VER.HIDDEN_PATH ? "/data/adb/magisk" : "/data/magisk";
Shell.Sync.su(console, logs,
Utils.fmt("rm -rf %s/*; mkdir -p %s; chmod 700 /data/adb", binPath, binPath),
Utils.fmt("cp -af %s/* %s; rm -rf %s", install, binPath, install),
Utils.fmt("flash_boot_image %s %s", patched_boot, mBootLocation),
mm.keepVerity ? "" : "patch_dtbo_image");
break;
}
patched_boot.delete();
}
@Override
protected Boolean doInBackground(Void... voids) {
MagiskManager mm = MagiskManager.get();
install = new File(
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
mm.createDeviceProtectedStorageContext() : mm)
.getFilesDir().getParent()
, "install");
Shell.Sync.sh("rm -rf " + install);
if (mode == FIX_ENV_MODE) {
install = new File("/data/adb/magisk");
Shell.Sync.sh("rm -rf " + install + "/*");
} else {
install = new File(
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
mm.createDeviceProtectedStorageContext() : mm)
.getFilesDir().getParent()
, "install");
Shell.Sync.sh("rm -rf " + install);
}
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
String arch;
@ -91,162 +281,26 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
console.add("- Device platform: " + Build.SUPPORTED_ABIS[0]);
try {
// Unzip files
console.add("- Extracting files");
try (InputStream in = mm.getContentResolver().openInputStream(mZip)) {
if (in == null) throw new FileNotFoundException();
BufferedInputStream buf = new BufferedInputStream(in);
buf.mark(Integer.MAX_VALUE);
ZipUtils.unzip(buf, install, arch + "/", true);
buf.reset();
ZipUtils.unzip(buf, install, "common/", true);
buf.reset();
ZipUtils.unzip(buf, install, "chromeos/", false);
buf.reset();
ZipUtils.unzip(buf, install, "META-INF/com/google/android/update-binary", true);
buf.close();
} catch (FileNotFoundException e) {
console.add("! Invalid Uri");
throw e;
} catch (Exception e) {
console.add("! Cannot unzip zip");
throw e;
}
Shell.Sync.sh("chmod 755 " + install + "/*");
extractFiles(arch);
if (mode == FIX_ENV_MODE) {
Shell.Sync.sh(
"cd " + install,
"sh update-binary extract",
"rm -f update-binary magisk.apk",
"cd /",
"rm -rf /sbin/.core/busybox/*",
"/sbin/.core/mirror/bin/busybox --install -s /sbin/.core/busybox"
);
} else {
File boot = new File(install, "boot.img");
SuFile patched_boot = new SuFile(install.getParent(), "new-boot.img");
File boot = new File(install, "boot.img");
boolean highCompression = false;
switch (mode) {
case PATCH_MODE:
// Copy boot image to local
try (InputStream in = mm.getContentResolver().openInputStream(mBootImg);
OutputStream out = new FileOutputStream(boot)
) {
InputStream source;
if (in == null) throw new FileNotFoundException();
if (Utils.getNameFromUri(mm, mBootImg).endsWith(".tar")) {
// Extract boot.img from tar
TarInputStream tar = new TarInputStream(new BufferedInputStream(in));
org.kamranzafar.jtar.TarEntry entry;
while ((entry = tar.getNextEntry()) != null) {
if (entry.getName().equals("boot.img"))
break;
}
source = tar;
} else {
// Direct copy raw image
source = new BufferedInputStream(in);
}
ShellUtils.pump(source, out);
} catch (FileNotFoundException e) {
console.add("! Invalid Uri");
throw e;
} catch (IOException e) {
console.add("! Copy failed");
throw e;
}
break;
case DIRECT_MODE:
console.add("- Patch boot/ramdisk image: " + mBootLocation);
if (mm.remoteMagiskVersionCode >= 1463) {
highCompression = Integer.parseInt(Utils.cmd(Utils.fmt(
"%s/magiskboot --parse %s; echo $?",
install, mBootLocation))) == 2;
if (highCompression)
console.add("! Insufficient boot partition size detected");
}
if (boot.createNewFile()) {
Shell.Sync.su("cat " + mBootLocation + " > " + boot);
} else {
console.add("! Dump boot image failed");
return false;
}
break;
default:
if (!dumpBoot(boot) || !patchBoot(boot, patched_boot))
return false;
outputBoot(patched_boot);
console.add("- All done!");
}
boolean isSigned;
try (InputStream in = new FileInputStream(boot)) {
isSigned = SignBoot.verifySignature(in, null);
if (isSigned) {
console.add("- Boot image is signed with AVB 1.0");
}
} catch (Exception e) {
console.add("! Unable to check signature");
throw e;
}
// Patch boot image
Shell.Sync.sh(console, logs,
"cd " + install,
Utils.fmt("KEEPFORCEENCRYPT=%b KEEPVERITY=%b HIGHCOMP=%b " +
"sh update-binary indep boot_patch.sh %s || echo 'Failed!'",
mm.keepEnc, mm.keepVerity, highCompression, boot));
if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
return false;
Shell.Sync.sh("mv -f new-boot.img ../",
"mv bin/busybox busybox",
"rm -rf magisk.apk bin *.img update-binary",
"cd /");
SuFile patched_boot = new SuFile(install.getParent(), "new-boot.img");
if (isSigned) {
console.add("- Signing boot image with test keys");
File signed = new File(install.getParent(), "signed.img");
try (InputStream in = new SuFileInputStream(patched_boot);
OutputStream out = new BufferedOutputStream(new FileOutputStream(signed))
) {
SignBoot.doSignature("/boot", in, out, null, null);
}
Shell.Sync.sh("mv -f " + signed + " " + patched_boot);
}
switch (mode) {
case PATCH_MODE:
File dest = new File(Const.EXTERNAL_PATH, "patched_boot" + mm.bootFormat);
dest.getParentFile().mkdirs();
OutputStream out;
switch (mm.bootFormat) {
case ".img.tar":
out = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
((TarOutputStream) out).putNextEntry(new TarEntry(patched_boot, "boot.img"));
break;
default:
case ".img":
out = new BufferedOutputStream(new FileOutputStream(dest));
break;
}
try (InputStream in = new SuFileInputStream(patched_boot)) {
ShellUtils.pump(in, out);
out.close();
}
console.add("");
console.add("*********************************");
console.add(" Patched Boot Image is placed in ");
console.add(" " + dest + " ");
console.add("*********************************");
break;
case DIRECT_MODE:
String binPath = mm.remoteMagiskVersionCode >= 1464 ? "/data/adb/magisk" : "/data/magisk";
Shell.Sync.su(console, logs,
Utils.fmt("rm -rf %s/*; mkdir -p %s; chmod 700 /data/adb", binPath, binPath),
Utils.fmt("cp -af %s/* %s; rm -rf %s", install, binPath, install),
Utils.fmt("flash_boot_image %s %s", patched_boot, mBootLocation),
mm.remoteMagiskVersionCode >= 1464 ? "[ -L /data/magisk.img ] || cp /data/magisk.img /data/adb/magisk.img" : "",
mm.keepVerity ? "" : "patch_dtbo_image");
break;
default:
return false;
}
patched_boot.delete();
console.add("- All done!");
} catch (Exception e) {
e.printStackTrace();
return false;
@ -256,12 +310,33 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
@Override
protected void onPostExecute(Boolean result) {
FlashActivity activity = (FlashActivity) getActivity();
if (!result) {
Shell.Async.sh("rm -rf " + install);
console.add("! Installation failed");
activity.reboot.setVisibility(View.GONE);
if (mode == FIX_ENV_MODE) {
dialog.dismiss();
MagiskManager.toast(result ? R.string.setup_done : R.string.setup_fail, Toast.LENGTH_LONG);
} else {
// Running in FlashActivity
FlashActivity activity = (FlashActivity) getActivity();
if (!result) {
Shell.Async.sh("rm -rf " + install);
console.add("! Installation failed");
activity.reboot.setVisibility(View.GONE);
}
activity.buttonPanel.setVisibility(View.VISIBLE);
}
activity.buttonPanel.setVisibility(View.VISIBLE);
}
private static class NOPList<E> extends AbstractList<E> {
@Override
public E get(int index) {
return null;
}
@Override
public int size() {
return 0;
}
@Override
public void add(int index, E element) {}
}
}

View File

@ -80,6 +80,7 @@ public class Const {
public static final int HIDDEN_PATH = 1460;
public static final int REMOVE_LEGACY_LINK = 1630;
public static final int SEPOL_REFACTOR = 1640;
public static final int FIX_ENV = 1650;
}
public static class ID {

View File

@ -17,6 +17,7 @@ import com.topjohnwu.magisk.FlashActivity;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.SplashActivity;
import com.topjohnwu.magisk.asyncs.InstallMagisk;
import com.topjohnwu.magisk.asyncs.RestoreImages;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
@ -100,6 +101,26 @@ public class ShowUI {
notificationManager.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
}
public static void envFixDialog(Activity activity) {
MagiskManager mm = Utils.getMagiskManager(activity);
String filename = Utils.fmt("Magisk-v%s(%d).zip",
mm.remoteMagiskVersionString, mm.remoteMagiskVersionCode);
new AlertDialogBuilder(activity)
.setTitle(R.string.env_fix_title)
.setMessage(R.string.env_fix_msg)
.setCancelable(true)
.setPositiveButton(R.string.yes, (d, i) -> {
Utils.dlAndReceive(activity, new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
new InstallMagisk(activity, uri).exec();
}
}, mm.magiskLink, filename);
})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
public static void magiskInstallDialog(Activity activity) {
MagiskManager mm = Utils.getMagiskManager(activity);
String filename = Utils.fmt("Magisk-v%s(%d).zip",

View File

@ -51,6 +51,10 @@ public class Utils {
return ShellUtils.fastCmd(Shell.getShell(), cmd);
}
public static boolean cmdResult(String cmd) {
return ShellUtils.fastCmdResult(Shell.getShell(), cmd);
}
public static void uninstallPkg(String pkg) {
Shell.Sync.su("db_clean " + Const.USER_ID, "pm uninstall " + pkg);
}

View File

@ -1,6 +1,8 @@
package com.topjohnwu.magisk.utils;
import com.topjohnwu.superuser.ShellUtils;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileOutputStream;
import com.topjohnwu.utils.JarMap;
import com.topjohnwu.utils.SignAPK;
@ -9,6 +11,7 @@ import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
@ -16,13 +19,13 @@ import java.util.zip.ZipInputStream;
public class ZipUtils {
public static void unzip(File zip, File folder, String path, boolean junkPath) throws Exception {
public static void unzip(File zip, File folder, String path, boolean junkPath) throws IOException {
InputStream in = new BufferedInputStream(new FileInputStream(zip));
unzip(in, folder, path, junkPath);
in.close();
}
public static void unzip(InputStream zip, File folder, String path, boolean junkPath) throws Exception {
public static void unzip(InputStream zip, File folder, String path, boolean junkPath) throws IOException {
try {
ZipInputStream zipfile = new ZipInputStream(zip);
ZipEntry entry;
@ -37,13 +40,14 @@ public class ZipUtils {
} else {
name = entry.getName();
}
File dest = new File(folder, name);
SuFile dest = new SuFile(folder, name);
dest.getParentFile().mkdirs();
try (FileOutputStream out = new FileOutputStream(dest)) {
try (OutputStream out = new SuFileOutputStream(dest)) {
ShellUtils.pump(zipfile, out);
}
}
} catch(Exception e) {
}
catch(IOException e) {
e.printStackTrace();
throw e;
}

View File

@ -36,3 +36,10 @@ db_setup() {
chown $USER.$USER $DIR
chmod 666 $DIR/*
}
env_check() {
for file in busybox magisk magiskboot magiskinit util_functions.sh boot_patch.sh; do
[ -e /data/adb/magisk/$file ] || return 1
done
return 0
}

View File

@ -125,6 +125,10 @@
<string name="su_db_corrupt">SU database is corrupted, will recreate new db</string>
<string name="cannot_check_sn_title">Cannot check SafetyNet</string>
<string name="cannot_check_sn_notice">Due to some changes in Google Play Services, it is not possible to check SafetyNet on repackaged Magisk Manager</string>
<string name="setup_done">Setup done</string>
<string name="setup_fail">Setup failed</string>
<string name="env_fix_title">Require Additional Setup</string>
<string name="env_fix_msg">Your device need additional setup for Magisk to work properly. It will download the Magisk setup zip, do you want to proceed now?</string>
<!--Settings Activity -->
<string name="settings_general_category">General</string>