From c10b376575c3e30f62be338275758d04985c9171 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Thu, 4 Apr 2019 07:27:43 -0400 Subject: [PATCH] Support patching full ODIN firmware --- .../main/java/com/topjohnwu/magisk/Const.java | 7 +- .../com/topjohnwu/magisk/FlashActivity.java | 10 +- .../magisk/dialogs/InstallMethodDialog.java | 2 +- .../magisk/tasks/MagiskInstaller.java | 162 ++++++++++++------ 4 files changed, 119 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/com/topjohnwu/magisk/Const.java b/app/src/main/java/com/topjohnwu/magisk/Const.java index d7f8a396a..16d4b8eb0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Const.java +++ b/app/src/main/java/com/topjohnwu/magisk/Const.java @@ -25,14 +25,12 @@ public class Const { EXTERNAL_PATH.mkdirs(); } - public static final String BUSYBOX_PATH = "/sbin/.magisk/busybox"; public static final String TMP_FOLDER_PATH = "/dev/tmp"; public static final String MAGISK_LOG = "/cache/magisk.log"; public static final String MANAGER_CONFIGS = ".tmp.magisk.config"; // Versions public static final int UPDATE_SERVICE_VER = 1; - public static final int MIN_MODULE_VER = 1500; public static final int SNET_EXT_VER = 12; public static final int USER_ID = Process.myUid() / 100000; @@ -42,10 +40,8 @@ public class Const { } public static class ID { - public static final int UPDATE_SERVICE_ID = 1; public static final int FETCH_ZIP = 2; public static final int SELECT_BOOT = 3; - public static final int ONBOOT_SERVICE_ID = 6; // notifications public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4; @@ -88,14 +84,13 @@ public class Const { public static final String INTENT_SET_NAME = "filename"; public static final String INTENT_SET_LINK = "link"; public static final String FLASH_ACTION = "action"; - public static final String FLASH_SET_BOOT = "boot"; public static final String BROADCAST_MANAGER_UPDATE = "manager_update"; public static final String BROADCAST_REBOOT = "reboot"; } public static class Value { public static final String FLASH_ZIP = "flash"; - public static final String PATCH_BOOT = "patch"; + public static final String PATCH_FILE = "patch"; public static final String FLASH_MAGISK = "magisk"; public static final String FLASH_INACTIVE_SLOT = "slot"; public static final String UNINSTALL = "uninstall"; diff --git a/app/src/main/java/com/topjohnwu/magisk/FlashActivity.java b/app/src/main/java/com/topjohnwu/magisk/FlashActivity.java index e779016ae..b77477f81 100644 --- a/app/src/main/java/com/topjohnwu/magisk/FlashActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/FlashActivity.java @@ -125,8 +125,8 @@ public class FlashActivity extends BaseActivity { case Const.Value.FLASH_INACTIVE_SLOT: new SecondSlot().exec(); break; - case Const.Value.PATCH_BOOT: - new PatchBoot(uri).exec(); + case Const.Value.PATCH_FILE: + new PatchFile(uri).exec(); break; } } @@ -254,17 +254,17 @@ public class FlashActivity extends BaseActivity { } } - private class PatchBoot extends BaseInstaller { + private class PatchFile extends BaseInstaller { private Uri uri; - PatchBoot(Uri u) { + PatchFile(Uri u) { uri = u; } @Override protected boolean operations() { - return copyBoot(uri) && extractZip() && patchBoot() && storeBoot(); + return extractZip() && handleFile(uri) && patchBoot() && storeBoot(); } } diff --git a/app/src/main/java/com/topjohnwu/magisk/dialogs/InstallMethodDialog.java b/app/src/main/java/com/topjohnwu/magisk/dialogs/InstallMethodDialog.java index ecb2c8fe0..83c3acf7b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/dialogs/InstallMethodDialog.java +++ b/app/src/main/java/com/topjohnwu/magisk/dialogs/InstallMethodDialog.java @@ -59,7 +59,7 @@ class InstallMethodDialog extends AlertDialog.Builder { if (resultCode == Activity.RESULT_OK && data != null) { Intent i = new Intent(activity, ClassMap.get(FlashActivity.class)) .setData(data.getData()) - .putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT); + .putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_FILE); activity.startActivity(i); } }); diff --git a/app/src/main/java/com/topjohnwu/magisk/tasks/MagiskInstaller.java b/app/src/main/java/com/topjohnwu/magisk/tasks/MagiskInstaller.java index bc0339cab..2e2334472 100644 --- a/app/src/main/java/com/topjohnwu/magisk/tasks/MagiskInstaller.java +++ b/app/src/main/java/com/topjohnwu/magisk/tasks/MagiskInstaller.java @@ -23,6 +23,7 @@ import com.topjohnwu.superuser.io.SuFileInputStream; import com.topjohnwu.superuser.io.SuFileOutputStream; import org.kamranzafar.jtar.TarEntry; +import org.kamranzafar.jtar.TarHeader; import org.kamranzafar.jtar.TarInputStream; import org.kamranzafar.jtar.TarOutputStream; @@ -30,11 +31,11 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import java.util.zip.ZipEntry; @@ -42,10 +43,13 @@ import java.util.zip.ZipInputStream; public abstract class MagiskInstaller { - private List console, logs; protected String srcBoot; + protected File destFile; protected File installDir; + private List console, logs; + private boolean isTar = false; + private class ProgressLog implements DownloadProgressListener { private int prev = -1; @@ -166,38 +170,101 @@ public abstract class MagiskInstaller { } else { init64.delete(); } + Shell.sh("cd " + installDir, "chmod 755 *").exec(); return true; } - protected boolean copyBoot(Uri bootUri) { - srcBoot = new File(installDir, "boot.img").getPath(); - console.add("- Copying image to cache"); - // Copy boot image to local - try (InputStream in = App.self.getContentResolver().openInputStream(bootUri); - OutputStream out = new FileOutputStream(srcBoot)) { - if (in == null) - throw new FileNotFoundException(); + private TarEntry newEntry(String name, long size) { + console.add("-- Writing: " + name); + return new TarEntry(TarHeader.createHeader(name, size, 0, false, 0644)); + } - InputStream src; - if (Utils.getNameFromUri(App.self, bootUri).endsWith(".tar")) { - // Extract boot.img from tar - TarInputStream tar = new TarInputStream(new BufferedInputStream(in)); - TarEntry entry; - while ((entry = tar.getNextEntry()) != null) { - if (entry.getName().equals("boot.img")) - break; + private void handleTar(InputStream in) throws IOException { + console.add("- Processing tar file"); + boolean vbmeta = false; + try (TarInputStream tarIn = new TarInputStream(in); + TarOutputStream tarOut = new TarOutputStream(destFile)) { + TarEntry entry; + while ((entry = tarIn.getNextEntry()) != null) { + if (entry.getName().contains("boot.img") + || entry.getName().contains("recovery.img")) { + String name = entry.getName(); + console.add("-- Extracting: " + name); + File extract = new File(installDir, name); + try (FileOutputStream fout = new FileOutputStream(extract)) { + ShellUtils.pump(tarIn, fout); + } + if (name.contains(".lz4")) { + console.add("-- Decompressing: " + name); + Shell.sh("./magiskboot --decompress " + extract).to(console).exec(); + } + } else if (entry.getName().contains("vbmeta.img")) { + vbmeta = true; + ByteBuffer buf = ByteBuffer.allocate(256); + buf.put("AVB0".getBytes()); // magic + buf.putInt(1); // required_libavb_version_major + buf.putInt(120, 2); // flags + buf.position(128); // release_string + buf.put("avbtool 1.1.0".getBytes()); + tarOut.putNextEntry(newEntry("vbmeta.img", 256)); + tarOut.write(buf.array()); + } else { + console.add("-- Writing: " + entry.getName()); + tarOut.putNextEntry(entry); + ShellUtils.pump(tarIn, tarOut); + } + } + SuFile boot = new SuFile(installDir, "boot.img"); + SuFile recovery = new SuFile(installDir, "recovery.img"); + if (vbmeta && recovery.exists() && boot.exists()) { + // Install Magisk to recovery + srcBoot = recovery.getPath(); + // Repack boot image to prevent restore + Shell.sh( + "./magiskboot --unpack boot.img", + "./magiskboot --repack boot.img", + "./magiskboot --cleanup", + "mv new-boot.img boot.img").exec(); + try (InputStream sin = new SuFileInputStream(boot)) { + tarOut.putNextEntry(newEntry("boot.img", boot.length())); + ShellUtils.pump(sin, tarOut); + } + boot.delete(); + } else { + if (!boot.exists()) { + console.add("! No boot image found"); + throw new IOException(); + } + srcBoot = boot.getPath(); + } + } + } + + protected boolean handleFile(Uri uri) { + try (InputStream in = new BufferedInputStream(App.self.getContentResolver().openInputStream(uri))) { + in.mark(500); + byte[] magic = new byte[5]; + if (in.skip(257) != 257 || in.read(magic) != magic.length) { + console.add("! Invalid file"); + return false; + } + in.reset(); + if (Arrays.equals(magic, "ustar".getBytes())) { + isTar = true; + destFile = new File(Const.EXTERNAL_PATH, "magisk_patched.tar"); + handleTar(in); + } else { + // Raw image + srcBoot = new File(installDir, "boot.img").getPath(); + destFile = new File(Const.EXTERNAL_PATH, "magisk_patched.img"); + console.add("- Copying image to cache"); + try (OutputStream out = new FileOutputStream(srcBoot)) { + ShellUtils.pump(in, out); } - src = tar; - } else { - // Direct copy raw image - src = new BufferedInputStream(in); } - ShellUtils.pump(src, out); - } catch (FileNotFoundException e) { - console.add("! Invalid Uri"); - return false; } catch (IOException e) { - console.add("! Copy failed"); + console.add("! Process error"); + e.printStackTrace(); return false; } return true; @@ -215,7 +282,7 @@ public abstract class MagiskInstaller { return false; } - if (!Shell.sh("cd " + installDir, Utils.fmt( + if (!Shell.sh(Utils.fmt( "KEEPFORCEENCRYPT=%b KEEPVERITY=%b RECOVERYMODE=%b " + "sh update-binary sh boot_patch.sh %s", Config.keepEnc, Config.keepVerity, Config.recovery, srcBoot)) @@ -253,35 +320,30 @@ public abstract class MagiskInstaller { } protected boolean storeBoot() { - File patched = new File(installDir, "new-boot.img"); - String fmt = Config.get(Config.Key.BOOT_FORMAT); - File dest = new File(Const.EXTERNAL_PATH, "patched_boot" + fmt); - dest.getParentFile().mkdirs(); - OutputStream os; + SuFile patched = new SuFile(installDir, "new-boot.img"); try { - switch (fmt) { - case ".img.tar": - os = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest))); - ((TarOutputStream) os).putNextEntry(new TarEntry(patched, "boot.img")); - break; - default: - case ".img": - os = new BufferedOutputStream(new FileOutputStream(dest)); - break; + OutputStream os; + if (isTar) { + os = new TarOutputStream(destFile, true); + ((TarOutputStream) os).putNextEntry(newEntry( + srcBoot.contains("recovery") ? "recovery.img" : "boot.img", + patched.length())); + } else { + os = new BufferedOutputStream(new FileOutputStream(destFile)); } - try (InputStream in = new SuFileInputStream(patched)) { - ShellUtils.pump(in, os); - os.close(); + try (InputStream in = new SuFileInputStream(patched); + OutputStream out = os) { + ShellUtils.pump(in, out); } } catch (IOException e) { - console.add("! Failed to store boot to " + dest); - return false; + console.add("! Failed to output to " + destFile); + e.printStackTrace(); } - Shell.sh("rm -f " + patched).exec(); + patched.delete(); console.add(""); console.add("****************************"); - console.add(" Patched image is placed in "); - console.add(" " + dest + " "); + console.add(" Output file is placed in "); + console.add(" " + destFile + " "); console.add("****************************"); return true; }