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 {
private static final ClassLoader base = Object.class.getClassLoader();
public DynamicClassLoader(File apk) {
this(apk, base);
this(apk, getSystemClassLoader());
}
public DynamicClassLoader(File apk, ClassLoader parent) {
@ -29,7 +27,7 @@ public class DynamicClassLoader extends BaseDexClassLoader {
try {
// Then check boot classpath
return base.loadClass(name);
return getSystemClassLoader().loadClass(name);
} catch (ClassNotFoundException ignored) {
try {
// Next try current dex
@ -47,7 +45,7 @@ public class DynamicClassLoader extends BaseDexClassLoader {
@Override
public URL getResource(String name) {
URL resource = base.getResource(name);
URL resource = getSystemClassLoader().getResource(name);
if (resource != null)
return resource;
resource = findResource(name);
@ -59,7 +57,7 @@ public class DynamicClassLoader extends BaseDexClassLoader {
@Override
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));
}
}

View File

@ -4,6 +4,7 @@ package com.topjohnwu.magisk.core
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.res.AssetManager
import android.content.res.Configuration
@ -13,16 +14,12 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.utils.syncLocale
import com.topjohnwu.magisk.ktx.unwrap
lateinit var AppApkPath: String
fun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path)
fun Context.patch(): Context {
resources.patch()
return this
}
fun Resources.patch(): Resources {
if (isRunningAsStub)
addAssetPath(AppApkPath)
@ -30,6 +27,21 @@ fun Resources.patch(): Resources {
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 {
val asset = AssetManager::class.java.newInstance()
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.Context
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -18,9 +17,9 @@ import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatActivity
import com.topjohnwu.magisk.R
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.UninstallPackage
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.reflectField
import com.topjohnwu.magisk.utils.Utils
import java.util.concurrent.CountDownLatch
@ -56,11 +55,7 @@ abstract class BaseActivity : AppCompatActivity() {
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.patch())
}
override fun createConfigurationContext(config: Configuration): Context {
return super.createConfigurationContext(config).patch()
super.attachBaseContext(base.wrap())
}
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.named
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.zip.Deflater
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
import java.util.zip.*
private fun Project.androidBase(configure: Action<BaseExtension>) =
extensions.configure("android", configure)
@ -219,22 +219,23 @@ fun Project.setupStub() {
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
}
val buffer = ByteArrayOutputStream(apk.length().toInt())
val newApk = ZipOutputStream(FileOutputStream(apk))
ZipFile(apkTmp).use {
newApk.use { new ->
new.setLevel(Deflater.BEST_COMPRESSION)
new.putNextEntry(ZipEntry("AndroidManifest.xml"))
it.getInputStream(it.getEntry("AndroidManifest.xml")).transferTo(new)
new.closeEntry()
new.finish()
val buffer = ByteArrayOutputStream()
apkTmp.inputStream().use {
object : GZIPOutputStream(buffer) {
init {
def.setLevel(Deflater.BEST_COMPRESSION)
}
ZipOutputStream(buffer).use { arsc ->
arsc.setLevel(Deflater.BEST_COMPRESSION)
arsc.putNextEntry(ZipEntry("resources.arsc"))
it.getInputStream(it.getEntry("resources.arsc")).transferTo(arsc)
arsc.closeEntry()
arsc.finish()
}.use { o ->
it.transferTo(o)
}
}
ZipFile(apkTmp).use { o ->
ZipOutputStream(apk.outputStream()).use { n ->
n.setLevel(Deflater.BEST_COMPRESSION)
n.putNextEntry(ZipEntry("AndroidManifest.xml"))
o.getInputStream(o.getEntry("AndroidManifest.xml")).transferTo(n)
n.closeEntry()
n.finish()
}
}
apkTmp.delete()

View File

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

View File

@ -34,6 +34,7 @@ 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;
@ -95,6 +96,12 @@ public class DownloadActivity extends Activity {
}
}
@Override
public void finish() {
super.finish();
Runtime.getRuntime().exit(0);
}
private void error(Throwable e) {
Log.e(getClass().getSimpleName(), "", e);
finish();
@ -154,7 +161,8 @@ public class DownloadActivity extends Activity {
SecretKey key = new SecretKeySpec(Bytes.key(), "AES");
IvParameterSpec iv = new IvParameterSpec(Bytes.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) {
APKInstall.transfer(is, out);
}