Skip to content

Commit

Permalink
Fix stub app loading on older Android versions
Browse files Browse the repository at this point in the history
  • Loading branch information
topjohnwu committed Jun 5, 2022
1 parent a3381da commit 9016e67
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@

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) {
Expand All @@ -29,7 +27,7 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE

try {
// Then check boot classpath
return base.loadClass(name);
return getSystemClassLoader().loadClass(name);
} catch (ClassNotFoundException ignored) {
try {
// Next try current dex
Expand All @@ -47,7 +45,7 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE

@Override
public URL getResource(String name) {
URL resource = base.getResource(name);
URL resource = getSystemClassLoader().getResource(name);
if (resource != null)
return resource;
resource = findResource(name);
Expand All @@ -59,7 +57,7 @@ public URL getResource(String name) {

@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));
}
}
22 changes: 17 additions & 5 deletions app/src/main/java/com/topjohnwu/magisk/core/Hacks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -13,23 +14,34 @@ 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)
syncLocale()
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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?) {
Expand Down
41 changes: 21 additions & 20 deletions buildSrc/src/main/java/Setup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
}.use { o ->
it.transferTo(o)
}
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()
}
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()
Expand Down
2 changes: 1 addition & 1 deletion stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
class DelegateClassLoader extends ClassLoader {

DelegateClassLoader() {
super(null);
super();
}

@Override
Expand Down
10 changes: 9 additions & 1 deletion stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
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;
Expand Down Expand Up @@ -95,6 +96,12 @@ protected void onCreate(Bundle savedInstanceState) {
}
}

@Override
public void finish() {
super.finish();
Runtime.getRuntime().exit(0);
}

private void error(Throwable e) {
Log.e(getClass().getSimpleName(), "", e);
finish();
Expand Down Expand Up @@ -154,7 +161,8 @@ private void decryptResources(OutputStream out) throws Exception {
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);
}
Expand Down

0 comments on commit 9016e67

Please sign in to comment.