package com.topjohnwu.magisk; import static android.R.string.no; import static android.R.string.ok; import static android.R.string.yes; import static com.topjohnwu.magisk.R.string.dling; import static com.topjohnwu.magisk.R.string.no_internet_msg; import static com.topjohnwu.magisk.R.string.upgrade_msg; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.content.res.loader.ResourcesLoader; import android.content.res.loader.ResourcesProvider; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.system.Os; import android.system.OsConstants; import android.util.Log; import android.view.ContextThemeWrapper; import com.topjohnwu.magisk.net.Networking; import com.topjohnwu.magisk.net.Request; import com.topjohnwu.magisk.utils.APKInstall; import org.json.JSONException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.zip.GZIPInputStream; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import io.michaelrocks.paranoid.Obfuscate; @Obfuscate public class DownloadActivity extends Activity { private static final String APP_NAME = "Magisk"; private static final String JSON_URL = BuildConfig.DEBUG ? "https://topjohnwu.github.io/magisk-files/debug.json" : "https://topjohnwu.github.io/magisk-files/canary.json"; private String apkLink = BuildConfig.APK_URL; private Context themed; private ProgressDialog dialog; private boolean dynLoad; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (DynLoad.activeClassLoader instanceof AppClassLoader) { // For some reason activity is created before Application.attach(), // relaunch the activity using the same intent finishAffinity(); startActivity(getIntent()); return; } themed = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault); // Only download and dynamic load full APK if hidden dynLoad = !getPackageName().equals(BuildConfig.APPLICATION_ID); // Inject resources try { loadResources(); } catch (Exception ignored) {} ProviderInstaller.install(this); if (Networking.checkNetworkStatus(this)) { if (apkLink == null) { fetchCanary(); } else { showDialog(); } } else { new AlertDialog.Builder(themed) .setCancelable(false) .setTitle(APP_NAME) .setMessage(getString(no_internet_msg)) .setNegativeButton(ok, (d, w) -> finish()) .show(); } } @Override public void finish() { super.finish(); Runtime.getRuntime().exit(0); } private void error(Throwable e) { Log.e(getClass().getSimpleName(), "", e); finish(); } private Request request(String url) { return Networking.get(url).setErrorHandler((conn, e) -> error(e)); } private void showDialog() { new AlertDialog.Builder(themed) .setCancelable(false) .setTitle(APP_NAME) .setMessage(getString(upgrade_msg)) .setPositiveButton(yes, (d, w) -> dlAPK()) .setNegativeButton(no, (d, w) -> finish()) .show(); } private void fetchCanary() { dialog = ProgressDialog.show(themed, "", "", true); request(JSON_URL).getAsJSONObject(json -> { dialog.dismiss(); try { apkLink = json.getJSONObject("magisk").getString("link"); showDialog(); } catch (JSONException e) { error(e); } }); } private void dlAPK() { dialog = ProgressDialog.show(themed, getString(dling), getString(dling) + " " + APP_NAME, true); // Download and upgrade the app var request = request(apkLink).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR); if (dynLoad) { request.getAsFile(StubApk.current(this), file -> StubApk.restartProcess(this)); } else { request.getAsInputStream(input -> { var session = APKInstall.startSession(this); try (input; var out = session.openStream(this)) { if (out != null) APKInstall.transfer(input, out); } catch (IOException e) { error(e); } Intent intent = session.waitIntent(); if (intent != null) startActivity(intent); }); } } private void decryptResources(OutputStream out) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKey key = new SecretKeySpec(Bytes.key(), "AES"); IvParameterSpec iv = new IvParameterSpec(Bytes.iv()); cipher.init(Cipher.DECRYPT_MODE, key, iv); var is = new GZIPInputStream(new CipherInputStream( new ByteArrayInputStream(Bytes.res()), cipher)); try (is; out) { APKInstall.transfer(is, out); } } private void loadResources() throws Exception { if (Build.VERSION.SDK_INT >= 30) { var fd = Os.memfd_create("res.apk", 0); try { decryptResources(new FileOutputStream(fd)); Os.lseek(fd, 0, OsConstants.SEEK_SET); try (var pfd = ParcelFileDescriptor.dup(fd)) { var loader = new ResourcesLoader(); loader.addProvider(ResourcesProvider.loadFromApk(pfd)); getResources().addLoaders(loader); } } finally { Os.close(fd); } } else { File apk = new File(getCacheDir(), "res.apk"); decryptResources(new FileOutputStream(apk)); StubApk.addAssetPath(getResources(), apk.getPath()); } } }