Commit c8492b0c authored by topjohnwu's avatar topjohnwu

Use official APIs to load dynamic resources

parent 083ef803
package com.topjohnwu.magisk; package com.topjohnwu.magisk;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.loader.ResourcesLoader;
import android.content.res.loader.ResourcesProvider;
import android.os.ParcelFileDescriptor;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
...@@ -50,12 +56,21 @@ public class StubApk { ...@@ -50,12 +56,21 @@ public class StubApk {
return new File(getDynDir(info), "update.apk"); return new File(getDynDir(info), "update.apk");
} }
public static void addAssetPath(AssetManager asset, String path) { public static void addAssetPath(Resources res, String path) {
try { if (SDK_INT >= 30) {
if (addAssetPath == null) try (var fd = ParcelFileDescriptor.open(new File(path), MODE_READ_ONLY)) {
addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class); var loader = new ResourcesLoader();
addAssetPath.invoke(asset, path); loader.addProvider(ResourcesProvider.loadFromApk(fd));
} catch (Exception ignored) {} res.addLoaders(loader);
} catch (IOException ignored) {}
} else {
AssetManager asset = res.getAssets();
try {
if (addAssetPath == null)
addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(asset, path);
} catch (Exception ignored) {}
}
} }
public static void restartProcess(Activity activity) { public static void restartProcess(Activity activity) {
......
...@@ -18,7 +18,7 @@ import com.topjohnwu.magisk.di.AppContext ...@@ -18,7 +18,7 @@ import com.topjohnwu.magisk.di.AppContext
lateinit var AppApkPath: String lateinit var AppApkPath: String
fun AssetManager.addAssetPath(path: String) = StubApk.addAssetPath(this, path) fun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path)
fun Context.wrap(): Context = if (this is PatchedContext) this else PatchedContext(this) fun Context.wrap(): Context = if (this is PatchedContext) this else PatchedContext(this)
...@@ -32,17 +32,18 @@ private class PatchedContext(base: Context) : ContextWrapper(base) { ...@@ -32,17 +32,18 @@ private class PatchedContext(base: Context) : ContextWrapper(base) {
fun Resources.patch(): Resources { fun Resources.patch(): Resources {
syncLocale() syncLocale()
if (isRunningAsStub) if (isRunningAsStub)
assets.addAssetPath(AppApkPath) addAssetPath(AppApkPath)
return this return this
} }
fun createNewResources(): Resources { fun createNewResources(): Resources {
val asset = AssetManager::class.java.newInstance() val asset = AssetManager::class.java.newInstance()
asset.addAssetPath(AppApkPath)
val config = Configuration(AppContext.resources.configuration) val config = Configuration(AppContext.resources.configuration)
val metrics = DisplayMetrics() val metrics = DisplayMetrics()
metrics.setTo(AppContext.resources.displayMetrics) metrics.setTo(AppContext.resources.displayMetrics)
return Resources(asset, metrics, config) val res = Resources(asset, metrics, config)
res.addAssetPath(AppApkPath)
return res
} }
fun Class<*>.cmp(pkg: String) = fun Class<*>.cmp(pkg: String) =
......
...@@ -12,8 +12,14 @@ import android.app.AlertDialog; ...@@ -12,8 +12,14 @@ import android.app.AlertDialog;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.loader.ResourcesLoader;
import android.content.res.loader.ResourcesProvider;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log; import android.util.Log;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
...@@ -27,6 +33,7 @@ import java.io.ByteArrayInputStream; ...@@ -27,6 +33,7 @@ import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.CipherInputStream; import javax.crypto.CipherInputStream;
...@@ -66,7 +73,9 @@ public class DownloadActivity extends Activity { ...@@ -66,7 +73,9 @@ public class DownloadActivity extends Activity {
dynLoad = !getPackageName().equals(BuildConfig.APPLICATION_ID); dynLoad = !getPackageName().equals(BuildConfig.APPLICATION_ID);
// Inject resources // Inject resources
loadResources(); try {
loadResources();
} catch (Exception ignored) {}
ProviderInstaller.install(this); ProviderInstaller.install(this);
...@@ -140,21 +149,35 @@ public class DownloadActivity extends Activity { ...@@ -140,21 +149,35 @@ public class DownloadActivity extends Activity {
} }
} }
private void loadResources() { private void decryptResources(OutputStream out) throws Exception {
File apk = new File(getCacheDir(), "res.apk"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
try { SecretKey key = new SecretKeySpec(Bytes.key(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec iv = new IvParameterSpec(Bytes.iv());
SecretKey key = new SecretKeySpec(Bytes.key(), "AES"); cipher.init(Cipher.DECRYPT_MODE, key, iv);
IvParameterSpec iv = new IvParameterSpec(Bytes.iv()); var is = new CipherInputStream(new ByteArrayInputStream(Bytes.res()), cipher);
cipher.init(Cipher.DECRYPT_MODE, key, iv); try (is; out) {
var is = new CipherInputStream(new ByteArrayInputStream(Bytes.res()), cipher); APKInstall.transfer(is, out);
var out = new FileOutputStream(apk);
try (is; out) {
APKInstall.transfer(is, out);
}
StubApk.addAssetPath(getResources().getAssets(), apk.getPath());
} catch (Exception ignored) {
} }
} }
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());
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment