Support patching full ODIN firmware

This commit is contained in:
topjohnwu 2019-04-04 07:27:43 -04:00
parent ceb21ced2b
commit c10b376575
4 changed files with 119 additions and 62 deletions

View File

@ -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";

View File

@ -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();
}
}

View File

@ -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);
}
});

View File

@ -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<String> console, logs;
protected String srcBoot;
protected File destFile;
protected File installDir;
private List<String> 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;
}