feat: disable AOT with useEmbeddedDex

This is a better option than enabling `vmSafeMode` for all installs, since that appears to disable many optimizations and tanks performance. Possibly old GrapheneOS builds are the exception, which still need `vmSafeMode` to disable AOT since `useEmbeddedDex` doesn't exist on API <29.
This commit is contained in:
rushiiMachine 2025-01-13 18:35:30 -08:00
parent 5f1ab76842
commit 23ef31a4bd
No known key found for this signature in database
GPG key ID: DCBE5952BB3B6420
4 changed files with 72 additions and 13 deletions

View file

@ -2,6 +2,7 @@ package com.aliucord.manager.patcher.steps.install
import android.os.Build
import com.aliucord.manager.R
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
@ -9,29 +10,69 @@ import com.aliucord.manager.patcher.steps.base.StepState
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
import com.github.diamondminer88.zip.*
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
/**
* Align certain files in the APK to a 4KiB boundary.
* Align certain files in the APK to the respective boundaries.
*/
class AlignmentStep : Step(), KoinComponent {
private val paths: PathManager by inject()
override val group = StepGroup.Install
override val localizedName = R.string.patch_step_alignment
override suspend fun execute(container: StepRunner) {
val apk = container.getStep<CopyDependenciesStep>().patchedApk
if (Build.VERSION.SDK_INT < 29) {
state = StepState.Skipped
return
}
var resourcesArscBytes: ByteArray? = null
var dexCount: Int = -1
// Align resources.arsc due to targeting API 30 for silent install
if (Build.VERSION.SDK_INT >= 30) {
val bytes = ZipReader(apk)
resourcesArscBytes = ZipReader(apk)
.use { it.openEntry("resources.arsc")?.read() }
?: throw IllegalArgumentException("APK is missing resources.arsc")
}
ZipWriter(apk, /* append = */ true).use {
it.deleteEntry("resources.arsc")
it.writeEntry("resources.arsc", bytes, ZipCompression.NONE, 4096)
// Align dex files due to using useEmbeddedDex (ref. ManifestPatcher)
if (Build.VERSION.SDK_INT >= 29) {
ZipReader(apk).use { zip ->
// Count the amount of dex files currently in the apk
dexCount = zip.entryNames.count { it.endsWith(".dex") }
// Copy all the dex files that need to be moved out of the apk
for (idx in 0..<dexCount) {
val bytes = zip.openEntry(getDexName(idx))!!.read()
val file = paths.patchingWorkingDir().resolve(getDexName(idx))
file.writeBytes(bytes)
}
}
}
ZipWriter(apk, /* append = */ true).use { zip ->
// Delete all the unaligned files from APK
if (resourcesArscBytes != null)
zip.deleteEntry("resources.arsc")
for (i in 0..<dexCount)
zip.deleteEntry(getDexName(i))
// Write all the files back aligned this time
if (resourcesArscBytes != null)
zip.writeEntry("resources.arsc", resourcesArscBytes, ZipCompression.NONE, 4)
for (idx in 0..<dexCount) {
val file = paths.patchingWorkingDir().resolve(getDexName(idx))
val bytes = file.readBytes()
zip.writeEntry(getDexName(idx), bytes, ZipCompression.NONE, 4)
}
} else {
state = StepState.Skipped
}
}
private fun getDexName(idx: Int) = "classes${if (idx == 0) "" else (idx + 1)}.dex"
}

View file

@ -1,5 +1,3 @@
@file:Suppress("NOTHING_TO_INLINE")
package com.aliucord.manager.patcher.steps.patch
import com.aliucord.manager.R
@ -88,5 +86,5 @@ class ReorganizeDexStep : Step(), KoinComponent {
}
}
private inline fun getDexName(idx: Int) = "classes${if (idx == 0) "" else (idx + 1)}.dex"
private fun getDexName(idx: Int) = "classes${if (idx == 0) "" else (idx + 1)}.dex"
}

View file

@ -7,12 +7,14 @@ package com.aliucord.manager.patcher.util
import android.Manifest
import android.os.Build
import com.aliucord.manager.util.isGrapheneOS
import pxb.android.axml.*
object ManifestPatcher {
private const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"
private const val DEBUGGABLE = "debuggable"
private const val VM_SAFE_MODE = "vmSafeMode"
private const val USE_EMBEDDED_DEX = "useEmbeddedDex"
private const val REQUEST_LEGACY_EXTERNAL_STORAGE = "requestLegacyExternalStorage"
private const val NETWORK_SECURITY_CONFIG = "networkSecurityConfig"
private const val LABEL = "label"
@ -94,12 +96,14 @@ object ManifestPatcher {
) {
private var addDebuggable = debuggable
private var addLegacyStorage = true
private var useVmSafeMode = true
private var addVmSafeMode = true
private var addUseEmbeddedDex = true
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
if (name == NETWORK_SECURITY_CONFIG) return
if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) addLegacyStorage = false
if (name == VM_SAFE_MODE) useVmSafeMode = false
if (name == VM_SAFE_MODE) addVmSafeMode = false
if (name == USE_EMBEDDED_DEX) addUseEmbeddedDex = false
if (name == DEBUGGABLE) addDebuggable = false
super.attr(ns, name, resourceId, type, value)
}
@ -137,9 +141,17 @@ object ManifestPatcher {
TYPE_INT_BOOLEAN,
1
)
if (useVmSafeMode) super.attr(ANDROID_NAMESPACE, VM_SAFE_MODE, android.R.attr.vmSafeMode, TYPE_INT_BOOLEAN, 1)
if (addDebuggable) super.attr(ANDROID_NAMESPACE, DEBUGGABLE, android.R.attr.debuggable, TYPE_INT_BOOLEAN, 1)
// Disable AOT (Necessary for AOSP Android 15)
if (Build.VERSION.SDK_INT >= 29 && addUseEmbeddedDex) {
super.attr(ANDROID_NAMESPACE, USE_EMBEDDED_DEX, android.R.attr.useEmbeddedDex, TYPE_INT_BOOLEAN, 1)
}
// GrapheneOS also forces AOT, disable it in a worse way if below API 29
else if (isGrapheneOS() && addVmSafeMode) {
super.attr(ANDROID_NAMESPACE, VM_SAFE_MODE, android.R.attr.vmSafeMode, TYPE_INT_BOOLEAN, 1)
}
super.end()
}
}

View file

@ -76,6 +76,14 @@ fun isMiui(): Boolean {
?.isNotEmpty() ?: false
}
/**
* Checks whether this device is possibly running GrapheneOS.
*/
fun isGrapheneOS(): Boolean {
return Build.USER == "grapheneos" || Build.HOST == "grapheneos"
}
/**
* Gets a system property from build.prop
*/