Fix stub app loading on older Android versions

This commit is contained in:
topjohnwu 2022-06-05 01:09:30 -07:00
parent a3381da7ed
commit 9016e6727d
6 changed files with 54 additions and 40 deletions

View File

@ -9,10 +9,8 @@ import dalvik.system.BaseDexClassLoader;
public class DynamicClassLoader extends BaseDexClassLoader { public class DynamicClassLoader extends BaseDexClassLoader {
private static final ClassLoader base = Object.class.getClassLoader();
public DynamicClassLoader(File apk) { public DynamicClassLoader(File apk) {
this(apk, base); this(apk, getSystemClassLoader());
} }
public DynamicClassLoader(File apk, ClassLoader parent) { public DynamicClassLoader(File apk, ClassLoader parent) {
@ -29,7 +27,7 @@ public class DynamicClassLoader extends BaseDexClassLoader {
try { try {
// Then check boot classpath // Then check boot classpath
return base.loadClass(name); return getSystemClassLoader().loadClass(name);
} catch (ClassNotFoundException ignored) { } catch (ClassNotFoundException ignored) {
try { try {
// Next try current dex // Next try current dex
@ -47,7 +45,7 @@ public class DynamicClassLoader extends BaseDexClassLoader {
@Override @Override
public URL getResource(String name) { public URL getResource(String name) {
URL resource = base.getResource(name); URL resource = getSystemClassLoader().getResource(name);
if (resource != null) if (resource != null)
return resource; return resource;
resource = findResource(name); resource = findResource(name);
@ -59,7 +57,7 @@ public class DynamicClassLoader extends BaseDexClassLoader {
@Override @Override
public Enumeration<URL> getResources(String name) throws IOException { public Enumeration<URL> getResources(String name) throws IOException {
return new CompoundEnumeration<>(base.getResources(name), return new CompoundEnumeration<>(getSystemClassLoader().getResources(name),
findResources(name), getParent().getResources(name)); findResources(name), getParent().getResources(name));
} }
} }

View File

@ -4,6 +4,7 @@ package com.topjohnwu.magisk.core
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import android.content.res.AssetManager import android.content.res.AssetManager
import android.content.res.Configuration import android.content.res.Configuration
@ -13,16 +14,12 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.AppContext import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.utils.syncLocale import com.topjohnwu.magisk.core.utils.syncLocale
import com.topjohnwu.magisk.ktx.unwrap
lateinit var AppApkPath: String lateinit var AppApkPath: String
fun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path) fun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path)
fun Context.patch(): Context {
resources.patch()
return this
}
fun Resources.patch(): Resources { fun Resources.patch(): Resources {
if (isRunningAsStub) if (isRunningAsStub)
addAssetPath(AppApkPath) addAssetPath(AppApkPath)
@ -30,6 +27,21 @@ fun Resources.patch(): Resources {
return this return this
} }
fun Context.patch(): Context {
unwrap().resources.patch()
return this
}
// Wrapping is only necessary for ContextThemeWrapper to support configuration overrides
fun Context.wrap(): Context {
patch()
return object : ContextWrapper(this) {
override fun createConfigurationContext(config: Configuration): Context {
return super.createConfigurationContext(config).wrap()
}
}
}
fun createNewResources(): Resources { fun createNewResources(): Resources {
val asset = AssetManager::class.java.newInstance() val asset = AssetManager::class.java.newInstance()
val config = Configuration(AppContext.resources.configuration) val config = Configuration(AppContext.resources.configuration)

View File

@ -5,7 +5,6 @@ import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -18,9 +17,9 @@ import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.patch
import com.topjohnwu.magisk.core.utils.RequestInstall import com.topjohnwu.magisk.core.utils.RequestInstall
import com.topjohnwu.magisk.core.utils.UninstallPackage import com.topjohnwu.magisk.core.utils.UninstallPackage
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.reflectField import com.topjohnwu.magisk.ktx.reflectField
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -56,11 +55,7 @@ abstract class BaseActivity : AppCompatActivity() {
} }
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.patch()) super.attachBaseContext(base.wrap())
}
override fun createConfigurationContext(config: Configuration): Context {
return super.createConfigurationContext(config).patch()
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -12,12 +12,12 @@ import org.gradle.api.tasks.Sync
import org.gradle.kotlin.dsl.filter import org.gradle.kotlin.dsl.filter
import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import java.io.* import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import java.util.* import java.util.*
import java.util.zip.Deflater import java.util.zip.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
private fun Project.androidBase(configure: Action<BaseExtension>) = private fun Project.androidBase(configure: Action<BaseExtension>) =
extensions.configure("android", configure) extensions.configure("android", configure)
@ -219,22 +219,23 @@ fun Project.setupStub() {
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk) commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
} }
val buffer = ByteArrayOutputStream(apk.length().toInt()) val buffer = ByteArrayOutputStream()
val newApk = ZipOutputStream(FileOutputStream(apk)) apkTmp.inputStream().use {
ZipFile(apkTmp).use { object : GZIPOutputStream(buffer) {
newApk.use { new -> init {
new.setLevel(Deflater.BEST_COMPRESSION) def.setLevel(Deflater.BEST_COMPRESSION)
new.putNextEntry(ZipEntry("AndroidManifest.xml")) }
it.getInputStream(it.getEntry("AndroidManifest.xml")).transferTo(new) }.use { o ->
new.closeEntry() it.transferTo(o)
new.finish()
} }
ZipOutputStream(buffer).use { arsc -> }
arsc.setLevel(Deflater.BEST_COMPRESSION) ZipFile(apkTmp).use { o ->
arsc.putNextEntry(ZipEntry("resources.arsc")) ZipOutputStream(apk.outputStream()).use { n ->
it.getInputStream(it.getEntry("resources.arsc")).transferTo(arsc) n.setLevel(Deflater.BEST_COMPRESSION)
arsc.closeEntry() n.putNextEntry(ZipEntry("AndroidManifest.xml"))
arsc.finish() o.getInputStream(o.getEntry("AndroidManifest.xml")).transferTo(n)
n.closeEntry()
n.finish()
} }
} }
apkTmp.delete() apkTmp.delete()

View File

@ -131,7 +131,7 @@ class StubClassLoader extends ClassLoader {
class DelegateClassLoader extends ClassLoader { class DelegateClassLoader extends ClassLoader {
DelegateClassLoader() { DelegateClassLoader() {
super(null); super();
} }
@Override @Override

View File

@ -34,6 +34,7 @@ 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 java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.CipherInputStream; import javax.crypto.CipherInputStream;
@ -95,6 +96,12 @@ public class DownloadActivity extends Activity {
} }
} }
@Override
public void finish() {
super.finish();
Runtime.getRuntime().exit(0);
}
private void error(Throwable e) { private void error(Throwable e) {
Log.e(getClass().getSimpleName(), "", e); Log.e(getClass().getSimpleName(), "", e);
finish(); finish();
@ -154,7 +161,8 @@ public class DownloadActivity extends Activity {
SecretKey key = new SecretKeySpec(Bytes.key(), "AES"); SecretKey key = new SecretKeySpec(Bytes.key(), "AES");
IvParameterSpec iv = new IvParameterSpec(Bytes.iv()); IvParameterSpec iv = new IvParameterSpec(Bytes.iv());
cipher.init(Cipher.DECRYPT_MODE, key, iv); cipher.init(Cipher.DECRYPT_MODE, key, iv);
var is = new CipherInputStream(new ByteArrayInputStream(Bytes.res()), cipher); var is = new GZIPInputStream(new CipherInputStream(
new ByteArrayInputStream(Bytes.res()), cipher));
try (is; out) { try (is; out) {
APKInstall.transfer(is, out); APKInstall.transfer(is, out);
} }