refactor: combine new dex files into one step

This commit is contained in:
rushiiMachine 2024-08-14 15:02:25 -07:00
parent 30add80afb
commit 67decbe8e5
No known key found for this signature in database
GPG key ID: DCBE5952BB3B6420
17 changed files with 188 additions and 98 deletions

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2022 Juby210 & zt
* Licensed under the Open Software License version 3.0
*/
package com.aliucord.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class BuildInfo(
@SerialName("versionCode")
val discordVersionCode: String,
@SerialName("versionName")
val discordVersionName: String,
@SerialName("aliucordHash")
val injectorVersion: String,
)

View file

@ -1,15 +0,0 @@
/*
* Copyright (c) 2022 Juby210 & zt
* Licensed under the Open Software License version 3.0
*/
package com.aliucord.manager.network.dto
import kotlinx.serialization.Serializable
@Serializable
data class Version(
val versionCode: String,
val versionName: String,
val aliucordHash: String,
)

View file

@ -1,6 +1,6 @@
package com.aliucord.manager.network.service
import com.aliucord.manager.network.dto.Version
import com.aliucord.manager.network.dto.BuildInfo
import com.aliucord.manager.network.utils.ApiResponse
import io.ktor.client.request.url
import kotlinx.coroutines.Dispatchers
@ -10,7 +10,7 @@ class AliucordGithubService(
val github: GithubService,
val http: HttpService,
) {
suspend fun getDataJson(): ApiResponse<Version> {
suspend fun getDataJson(): ApiResponse<BuildInfo> {
return withContext(Dispatchers.IO) {
http.request {
url("https://raw.githubusercontent.com/$ORG/$MAIN_REPO/builds/data.json")

View file

@ -24,12 +24,12 @@ data class InstallMetadata(
val customManager: Boolean,
/**
* Aliuhook release version injected into the APK.
* Version of the Aliucord release build that was injected into the APK.
*/
val aliuhookVersion: String,
/**
* Short commit hash of the commit the injector was built from.
* Version of the injector build that was injected into the APK.
*/
val injectorCommitHash: String,
val injectorVersion: String,
)

View file

@ -27,8 +27,8 @@ class KotlinPatchRunner(options: PatchOptions) : StepRunner() {
// Patch
ReplaceIconStep(options),
PatchManifestStep(options),
AddInjectorStep(),
AddAliuhookStep(),
ReorganizeDexStep(),
AddAliuhookLibsStep(),
SaveMetadataStep(options),
// Install

View file

@ -0,0 +1,20 @@
package com.aliucord.manager.patcher.steps.base
interface IDexProvider {
/**
* The priority of the .dex files supplied by [getDexFiles].
* Higher number leads to a higher overwrite priority.
* .dex files already included in the APK have a priority of `0`.
*/
val dexPriority: Int
/**
* The amount of files returned by [getDexFiles]
*/
val dexCount: Int
/**
* Any dex files to be added into the APK.
*/
fun getDexFiles(): List<ByteArray>
}

View file

@ -7,14 +7,18 @@ import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.network.utils.getOrThrow
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.base.DownloadStep
import com.aliucord.manager.patcher.steps.base.IDexProvider
import com.aliucord.manager.patcher.steps.patch.ReorganizeDexStep
import com.github.diamondminer88.zip.ZipReader
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
/**
* Download a packaged AAR of the latest Aliuhook build from the Aliucord maven.
* Provides [ReorganizeDexStep] with the dex through the [IDexProvider] implementation.
*/
@Stable
class DownloadAliuhookStep : DownloadStep(), KoinComponent {
class DownloadAliuhookStep : DownloadStep(), IDexProvider, KoinComponent {
private val paths: PathManager by inject()
private val maven: AliucordMavenRepository by inject()
@ -33,4 +37,15 @@ class DownloadAliuhookStep : DownloadStep(), KoinComponent {
super.execute(container)
}
override val dexPriority = 0
override val dexCount = 1
override fun getDexFiles(): List<ByteArray> {
val dexBytes = ZipReader(targetFile).use { zip ->
zip.openEntry("classes.dex")?.read()
?: throw IllegalStateException("No prebuilt classes.dex in downloaded aliuhook build")
}
return listOf(dexBytes)
}
}

View file

@ -3,36 +3,36 @@ package com.aliucord.manager.patcher.steps.download
import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.network.dto.Version
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.base.DownloadStep
import com.aliucord.manager.patcher.steps.base.IDexProvider
import com.aliucord.manager.patcher.steps.patch.ReorganizeDexStep
import com.aliucord.manager.patcher.steps.prepare.FetchInfoStep
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
/**
* Download a compiled dex file to be injected into the APK as the first `classes.dex` to override an entry point class.
* Provides [ReorganizeDexStep] with the dex through the [IDexProvider] implementation.
*/
@Stable
class DownloadInjectorStep : DownloadStep(), KoinComponent {
class DownloadInjectorStep : DownloadStep(), IDexProvider, KoinComponent {
private val paths: PathManager by inject()
/**
* Populated from a dependency step ([FetchInfoStep]).
* This is used as cache invalidation (ref: [Version.aliucordHash])
* This is populated right before the download starts (ref: [execute])
*/
lateinit var aliucordHash: String
lateinit var targetVersion: String
private set
override val localizedName = R.string.patch_step_dl_injector
override val targetUrl = URL
override val targetFile
get() = paths.cachedInjectorDex(aliucordHash)
get() = paths.cachedInjectorDex(targetVersion)
override suspend fun execute(container: StepRunner) {
aliucordHash = container
.getStep<FetchInfoStep>()
.data.aliucordHash
targetVersion = container.getStep<FetchInfoStep>()
.data.injectorVersion
super.execute(container)
}
@ -43,4 +43,8 @@ class DownloadInjectorStep : DownloadStep(), KoinComponent {
const val URL = "https://raw.githubusercontent.com/$ORG/$MAIN_REPO/builds/Injector.dex"
}
override val dexCount = 1
override val dexPriority = 10
override fun getDexFiles() = listOf(targetFile.readBytes())
}

View file

@ -4,14 +4,17 @@ import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.patcher.steps.base.DownloadStep
import com.aliucord.manager.patcher.steps.base.IDexProvider
import com.aliucord.manager.patcher.steps.patch.ReorganizeDexStep
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
/**
* Download the most recent available Kotlin stdlib build that is supported.
* Provides [ReorganizeDexStep] with the dex through the [IDexProvider] implementation.
*/
@Stable
class DownloadKotlinStep : DownloadStep(), KoinComponent {
class DownloadKotlinStep : DownloadStep(), IDexProvider, KoinComponent {
private val paths: PathManager by inject()
override val localizedName = R.string.patch_step_dl_kotlin
@ -24,4 +27,8 @@ class DownloadKotlinStep : DownloadStep(), KoinComponent {
const val URL = "https://raw.githubusercontent.com/$ORG/$MAIN_REPO/main/installer/android/app/src/main/assets/kotlin/classes.dex"
}
override val dexCount = 1
override val dexPriority = -1
override fun getDexFiles() = listOf(targetFile.readBytes())
}

View file

@ -4,6 +4,7 @@ import android.os.Build
import com.aliucord.manager.R
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.IDexProvider
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
import com.aliucord.manager.patcher.steps.download.DownloadAliuhookStep
@ -12,23 +13,18 @@ import com.github.diamondminer88.zip.ZipWriter
import org.koin.core.component.KoinComponent
/**
* Add the Aliuhook library's native libs along with dex
* Add the Aliuhook library's native libs.
* The dex is handled by [ReorganizeDexStep] through the [IDexProvider] implementation of [DownloadAliuhookStep].
*/
class AddAliuhookStep : Step(), KoinComponent {
private val currentDeviceArch = Build.SUPPORTED_ABIS.first()
class AddAliuhookLibsStep : Step(), KoinComponent {
override val group = StepGroup.Patch
override val localizedName = R.string.patch_step_add_aliuhook
override suspend fun execute(container: StepRunner) {
val currentDeviceArch = Build.SUPPORTED_ABIS.first()
val apk = container.getStep<CopyDependenciesStep>().patchedApk
val aliuhook = container.getStep<DownloadAliuhookStep>().targetFile
// Find the amount of .dex files in the apk
val dexCount = ZipReader(apk).use {
it.entryNames.count { name -> name.endsWith(".dex") }
}
ZipWriter(apk, /* append = */ true).use { patchedApk ->
ZipReader(aliuhook).use { aliuhook ->
for (libFile in arrayOf("libaliuhook.so", "libc++_shared.so", "liblsplant.so")) {
@ -37,11 +33,6 @@ class AddAliuhookStep : Step(), KoinComponent {
patchedApk.writeEntry("lib/$currentDeviceArch/$libFile", bytes)
}
val aliuhookDex = aliuhook.openEntry("classes.dex")?.read()
?: throw IllegalStateException("No classes.dex in aliuhook aar")
patchedApk.writeEntry("classes${dexCount + 1}.dex", aliuhookDex)
}
}
}

View file

@ -1,45 +0,0 @@
package com.aliucord.manager.patcher.steps.patch
import com.aliucord.manager.R
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.*
import com.github.diamondminer88.zip.ZipReader
import com.github.diamondminer88.zip.ZipWriter
import org.koin.core.component.KoinComponent
/**
* Reorder the existing dex files to add the Aliucord injector as the first `classes.dex` file.
*/
class AddInjectorStep : Step(), KoinComponent {
override val group = StepGroup.Patch
override val localizedName = R.string.patch_step_add_injector
override suspend fun execute(container: StepRunner) {
val apk = container.getStep<CopyDependenciesStep>().patchedApk
val injector = container.getStep<DownloadInjectorStep>().targetFile
val kotlinStdlib = container.getStep<DownloadKotlinStep>().targetFile
val (dexCount, firstDexBytes) = ZipReader(apk).use {
Pair(
// Find the amount of .dex files in apk
it.entryNames.count { name -> name.endsWith(".dex") },
// Get the first dex
it.openEntry("classes.dex")?.read()
?: throw IllegalStateException("No classes.dex in base apk")
)
}
ZipWriter(apk, /* append = */ true).use {
// Move copied dex to end of dex list
it.deleteEntry("classes.dex")
it.writeEntry("classes${dexCount + 1}.dex", firstDexBytes)
// Add Kotlin & Aliucord's dex
it.writeEntry("classes.dex", injector.readBytes())
it.writeEntry("classes${dexCount + 2}.dex", kotlinStdlib.readBytes())
}
}
}

View file

@ -0,0 +1,92 @@
@file:Suppress("NOTHING_TO_INLINE")
package com.aliucord.manager.patcher.steps.patch
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.IDexProvider
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
import com.github.diamondminer88.zip.ZipReader
import com.github.diamondminer88.zip.ZipWriter
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class ReorganizeDexStep : Step(), KoinComponent {
private val paths: PathManager by inject()
override val group = StepGroup.Patch
override val localizedName = R.string.patch_step_reorganize_dex
override suspend fun execute(container: StepRunner) {
val apk = container.getStep<CopyDependenciesStep>().patchedApk
val dexProviders = container.steps
.filterIsInstance<IDexProvider>()
.sortedByDescending { it.dexPriority }
val priorityDexCount = dexProviders
.filter { it.dexPriority > 0 }
.sumOf { it.dexCount }
var dexCount = 0
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..<priorityDexCount) {
// Not enough dex files to move
if (idx + 1 > dexCount) break
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 old dex files from the apk
for (idx in 0..<priorityDexCount) {
// Not enough dex files to move
if (idx + 1 > dexCount) break
zip.deleteEntry(getDexName(idx))
}
// Copy all of the high priority dex files to the apk
var idx = 0
for (dexProvider in dexProviders) {
if (dexProvider.dexPriority <= 0) continue
for (dexBytes in dexProvider.getDexFiles()) {
zip.writeEntry(getDexName(idx++), dexBytes)
}
}
// Copy back the dex files that were moved out of the apk
for (idx in 0..<priorityDexCount) {
// Not enough dex files to move
if (idx + 1 > dexCount) break
val file = paths.patchingWorkingDir().resolve(getDexName(idx))
val bytes = file.readBytes()
zip.writeEntry(getDexName(dexCount + idx), bytes)
}
dexCount += idx
// Copy the rest of the injected dex files
for (dexProvider in dexProviders) {
if (dexProvider.dexPriority > 0) continue
for (dexBytes in dexProvider.getDexFiles()) {
zip.writeEntry(getDexName(dexCount++), dexBytes)
}
}
}
}
private inline fun getDexName(idx: Int) = "classes${if (idx == 0) "" else (idx + 1)}.dex"
}

View file

@ -36,7 +36,7 @@ class SaveMetadataStep(private val options: PatchOptions) : Step() {
managerVersionName = BuildConfig.VERSION_NAME,
customManager = IS_CUSTOM_BUILD,
aliuhookVersion = aliuhook.targetVersion,
injectorCommitHash = injector.aliucordHash,
injectorVersion = injector.targetVersion,
)
ZipWriter(apk, /* append = */ true).use {

View file

@ -40,7 +40,7 @@ class DowngradeCheckStep(private val options: PatchOptions) : Step(), KoinCompon
val targetVersion = container
.getStep<FetchInfoStep>()
.data.versionCode.toIntOrNull()
.data.discordVersionCode.toIntOrNull()
?: throw IllegalArgumentException("Invalid remote target Discord version")
if (currentVersion > targetVersion) {

View file

@ -2,7 +2,7 @@ package com.aliucord.manager.patcher.steps.prepare
import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.network.dto.Version
import com.aliucord.manager.network.dto.BuildInfo
import com.aliucord.manager.network.service.AliucordGithubService
import com.aliucord.manager.network.utils.getOrThrow
import com.aliucord.manager.patcher.StepRunner
@ -21,7 +21,8 @@ class FetchInfoStep : Step(), KoinComponent {
/**
* Fetched data about the latest Aliucord commit and supported Discord version.
*/
lateinit var data: Version
lateinit var data: BuildInfo
private set
override suspend fun execute(container: StepRunner) {
data = github.getDataJson().getOrThrow()

View file

@ -147,11 +147,11 @@ class HomeModel(
private fun fetchSupportedVersion() = screenModelScope.launchBlock {
github.getDataJson().fold(
success = {
val versionCode = it.versionCode.toIntOrNull() ?: return@fold
val versionCode = it.discordVersionCode.toIntOrNull() ?: return@fold
supportedVersion = DiscordVersion.Existing(
type = DiscordVersion.parseVersionType(versionCode),
name = it.versionName.split("-")[0].trim(),
name = it.discordVersionName.split("-")[0].trim(),
code = versionCode,
)
},

View file

@ -114,6 +114,7 @@
<string name="patch_step_patch_icon">Patching app icon</string>
<string name="patch_step_add_injector">Adding Aliucord injector</string>
<string name="patch_step_add_aliuhook">Adding Aliuhook library</string>
<string name="patch_step_reorganize_dex">Adding .dex files to APK</string>
<string name="patch_step_save_metadata">Store metadata</string>
<string name="patch_step_alignment">Aligning APKs</string>
<string name="patch_step_signing">Signing APKs</string>