2023-03-19 23:20:19 -07:00

196 lines
7.0 KiB
Java

package com.topjohnwu.magisk;
import static com.topjohnwu.magisk.BuildConfig.APPLICATION_ID;
import android.app.AppComponentFactory;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import com.topjohnwu.magisk.utils.APKInstall;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
@SuppressWarnings("ResultOfMethodCallIgnored")
public class DynLoad {
static Object componentFactory;
static ClassLoader activeClassLoader = DynLoad.class.getClassLoader();
static StubApk.Data createApkData() {
var data = new StubApk.Data();
data.setVersion(BuildConfig.STUB_VERSION);
data.setClassToComponent(new HashMap<>());
data.setRootService(DelegateRootService.class);
return data;
}
static void attachContext(Object o, Context context) {
if (!(o instanceof ContextWrapper))
return;
try {
Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
m.setAccessible(true);
m.invoke(o, context);
} catch (Exception ignored) { /* Impossible */ }
}
// Dynamically load APK from internal, external storage, or previous app
static AppClassLoader loadApk(Context context) {
File apk = StubApk.current(context);
File update = StubApk.update(context);
if (update.exists()) {
// Rename from update
update.renameTo(apk);
}
// Copy from external for easier development
if (BuildConfig.DEBUG) {
try {
File external = new File(context.getExternalFilesDir(null), "magisk.apk");
if (external.exists()) {
try {
var in = new FileInputStream(external);
var out = new FileOutputStream(apk);
try (in; out) {
APKInstall.transfer(in, out);
}
} catch (IOException e) {
Log.e(DynLoad.class.getSimpleName(), "", e);
apk.delete();
} finally {
external.delete();
}
}
} catch (SecurityException e) {
// Do not crash in root service
}
}
if (apk.exists()) {
return new AppClassLoader(apk);
}
// If no APK is loaded, attempt to copy from previous app
if (!context.getPackageName().equals(APPLICATION_ID)) {
try {
var info = context.getPackageManager().getApplicationInfo(APPLICATION_ID, 0);
var src = new FileInputStream(info.sourceDir);
var out = new FileOutputStream(apk);
try (src; out) {
APKInstall.transfer(src, out);
}
return new AppClassLoader(apk);
} catch (PackageManager.NameNotFoundException ignored) {
} catch (IOException e) {
Log.e(DynLoad.class.getSimpleName(), "", e);
apk.delete();
}
}
return null;
}
// Dynamically load APK and create the Application instance from the loaded APK
static Application createAndSetupApp(Application context) {
// On API >= 29, AppComponentFactory will replace the ClassLoader for us
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
replaceClassLoader(context);
// noinspection InlinedApi
int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES
| PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
| PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
var pm = context.getPackageManager();
final PackageInfo info;
try {
// noinspection WrongConstant
info = pm.getPackageInfo(context.getPackageName(), flags);
} catch (PackageManager.NameNotFoundException e) {
// Impossible
throw new RuntimeException(e);
}
File apk = StubApk.current(context);
final var cl = loadApk(context);
if (cl != null) try {
// noinspection WrongConstant
var pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), flags);
cl.updateComponentMap(info, pkgInfo);
var appInfo = pkgInfo.applicationInfo;
var data = createApkData();
var map = data.getClassToComponent();
// Create the inverse mapping (class to component name)
for (var e : cl.mapping.entrySet()) {
map.put(e.getValue(), e.getKey());
}
// Create the receiver Application
var app = (Application) cl.loadClass(appInfo.className)
.getConstructor(Object.class)
.newInstance(data.getObject());
// Create the receiver component factory
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && componentFactory != null) {
var delegate = (DelegateComponentFactory) componentFactory;
if (appInfo.appComponentFactory == null) {
delegate.receiver = new AppComponentFactory();
} else {
Object factory = cl.loadClass(appInfo.appComponentFactory).newInstance();
delegate.receiver = (AppComponentFactory) factory;
}
}
activeClassLoader = cl;
// Send real application to attachBaseContext
attachContext(app, context);
return app;
} catch (Exception e) {
Log.e(DynLoad.class.getSimpleName(), "", e);
apk.delete();
}
activeClassLoader = new StubClassLoader(info);
return null;
}
// Replace LoadedApk mClassLoader
private static void replaceClassLoader(Context context) {
// Get ContextImpl
while (context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
}
try {
Field mInfo = context.getClass().getDeclaredField("mPackageInfo");
mInfo.setAccessible(true);
Object loadedApk = mInfo.get(context);
assert loadedApk != null;
Field mcl = loadedApk.getClass().getDeclaredField("mClassLoader");
mcl.setAccessible(true);
mcl.set(loadedApk, new DelegateClassLoader());
} catch (Exception e) {
// Actually impossible as this method is only called on API < 29,
// and API 23 - 28 do not restrict access to these fields.
Log.e(DynLoad.class.getSimpleName(), "", e);
}
}
}