feat: Support split APKs for RNA

This commit is contained in:
pylixonly 2025-07-13 19:17:30 +08:00
parent 69153dbe2d
commit 457347167d
21 changed files with 331 additions and 129 deletions

View file

@ -12,8 +12,7 @@ import com.aliucord.manager.BuildConfig
import com.aliucord.manager.ui.screens.patchopts.PatchOptions import com.aliucord.manager.ui.screens.patchopts.PatchOptions
import com.aliucord.manager.util.IS_PROBABLY_EMULATOR import com.aliucord.manager.util.IS_PROBABLY_EMULATOR
import com.aliucord.manager.util.isPlayProtectEnabled import com.aliucord.manager.util.isPlayProtectEnabled
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import java.io.IOException import java.io.IOException
@ -149,7 +148,7 @@ class InstallLogManager(
@Serializable @Serializable
data class InstallLogData( data class InstallLogData(
val id: String, val id: String,
val installDate: Instant, @Contextual val installDate: Instant,
val installDuration: Duration, val installDuration: Duration,
val installOptions: PatchOptions, val installOptions: PatchOptions,
val environmentInfo: String, val environmentInfo: String,

View file

@ -125,7 +125,7 @@ class PathManager(
/** /**
* The APK that is worked on during the patching process. * The APK that is worked on during the patching process.
*/ */
fun patchedApk() = patchingWorkingDir().resolve("patched.apk") fun patchedApk(part: String = "") = patchingWorkingDir().resolve("patched-${part}.apk")
private companion object { private companion object {
/** /**

View file

@ -27,15 +27,15 @@ data class InstallMetadata(
/** /**
* Version (commit hash) of the Aliuhook build that was injected into the APK. * Version (commit hash) of the Aliuhook build that was injected into the APK.
*/ */
val aliuhookVersion: SemVer, val aliuhookVersion: SemVer?,
/** /**
* Version of the injector build that was injected into the APK. * Version of the injector build that was injected into the APK.
*/ */
val injectorVersion: SemVer, val injectorVersion: SemVer?,
/** /**
* Version of the smali patches that were applied onto the APK. * Version of the smali patches that were applied onto the APK.
*/ */
val patchesVersion: SemVer, val patchesVersion: SemVer?,
) )

View file

@ -42,10 +42,7 @@ abstract class StepRunner : KoinComponent {
* @param completed Only match steps that have finished executing. * @param completed Only match steps that have finished executing.
*/ */
inline fun <reified T : Step> getStep(completed: Boolean = true): T { inline fun <reified T : Step> getStep(completed: Boolean = true): T {
val step = steps.asSequence() val step = getStepOrNull<T>(completed)
.filterIsInstance<T>()
.filter { !completed || it.state.isFinished }
.firstOrNull()
if (step == null) { if (step == null) {
throw IllegalArgumentException("No completed step ${T::class.simpleName} exists in container") throw IllegalArgumentException("No completed step ${T::class.simpleName} exists in container")
@ -54,6 +51,13 @@ abstract class StepRunner : KoinComponent {
return step return step
} }
inline fun <reified T : Step> getStepOrNull(completed: Boolean = true): T? {
return steps.asSequence()
.filterIsInstance<T>()
.filter { !completed || it.state.isFinished }
.firstOrNull()
}
/** /**
* Adds a log entry without any associated log level. * Adds a log entry without any associated log level.
*/ */

View file

@ -0,0 +1,28 @@
package com.aliucord.manager.patcher
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.*
import com.aliucord.manager.patcher.steps.install.*
import com.aliucord.manager.patcher.steps.patch.*
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
import kotlinx.collections.immutable.persistentListOf
class WintryPatchRunner(options: PatchOptions) : StepRunner() {
override val steps = persistentListOf<Step>(
DownloadDiscordRNABaseStep(),
DownloadDiscordRNALangStep(),
DownloadDiscordRNAResourcesStep(),
DownloadDiscordRNALibStep(),
CopyDependenciesStep(),
PatchIconsStep(options),
PatchManifestStep(options),
// ReorganizeDexStep(),
SaveMetadataStep(options),
AlignmentStep(),
SigningStep(),
InstallStep(options),
// CleanupStep(),
)
}

View file

@ -22,6 +22,9 @@ abstract class DownloadStep : Step(), KoinComponent {
private val context: Context by inject() private val context: Context by inject()
private val downloader: KtorDownloadManager by inject() private val downloader: KtorDownloadManager by inject()
// Temporary
protected val version: Int = 289210
/** /**
* The remote url to download * The remote url to download
*/ */

View file

@ -23,15 +23,30 @@ class CopyDependenciesStep : Step(), KoinComponent {
private val application: Application by inject() private val application: Application by inject()
/** /**
* The target APK file which can be modified during patching * The target APK files which can be modified during patching
*/ */
val patchedApk: File = paths.patchedApk() val patchedApk: File = paths.patchedApk()
var patchedLangApk: File? = null
var patchedLibApk: File? = null
var patchedResApk: File? = null
val patchedApks
get() = listOfNotNull(
patchedApk,
patchedLangApk.takeIf { it?.exists() == true },
patchedLibApk.takeIf { it?.exists() == true },
patchedResApk.takeIf { it?.exists() == true }
)
override val group = StepGroup.Download override val group = StepGroup.Download
override val localizedName = R.string.patch_step_copy_deps override val localizedName = R.string.patch_step_copy_deps
override suspend fun execute(container: StepRunner) { override suspend fun execute(container: StepRunner) {
val srcApk = container.getStep<DownloadDiscordStep>().targetFile val srcApk = container.getStep<DownloadDiscordStep>().targetFile
val langApk = container.getStepOrNull<DownloadDiscordRNALangStep>()?.targetFile
val libApk = container.getStepOrNull<DownloadDiscordRNALibStep>()?.targetFile
val resApk = container.getStepOrNull<DownloadDiscordRNAResourcesStep>()?.targetFile
val dir = paths.patchingWorkingDir() val dir = paths.patchingWorkingDir()
container.log("Clearing patched directory") container.log("Clearing patched directory")
@ -42,7 +57,7 @@ class CopyDependenciesStep : Step(), KoinComponent {
if (Build.VERSION.SDK_INT >= 26) { if (Build.VERSION.SDK_INT >= 26) {
val storageManager = application.getSystemService<StorageManager>()!! val storageManager = application.getSystemService<StorageManager>()!!
val targetFileStorageId = storageManager.getUuidForPath(patchedApk) val targetFileStorageId = storageManager.getUuidForPath(patchedApk)
val fileSize = srcApk.length() val fileSize = srcApk.length() + (langApk?.length() ?: 0) + (libApk?.length() ?: 0) + (resApk?.length() ?: 0)
// We request 3.5x the size of the APK, to give space for the following: // We request 3.5x the size of the APK, to give space for the following:
// 1) A copy of the APK // 1) A copy of the APK
@ -57,7 +72,21 @@ class CopyDependenciesStep : Step(), KoinComponent {
} }
} }
container.log("Copying patched apk from ${srcApk.absolutePath} to ${patchedApk.absolutePath}") fun copyApkSafely(src: File?, destFile: File, onCopied: (File) -> Unit) {
src?.let {
container.log("Copying patched apk from ${it.absolutePath} to ${destFile.absolutePath}")
it.copyTo(destFile)
onCopied(destFile)
}
}
// Base APK
container.log("Copying patched base apk from ${srcApk.absolutePath} to ${patchedApk.absolutePath}")
srcApk.copyTo(patchedApk) srcApk.copyTo(patchedApk)
// Optional APKs
copyApkSafely(langApk, File(dir, "lang")) { patchedLangApk = it }
copyApkSafely(libApk, File(dir, "lib")) { patchedLibApk = it }
copyApkSafely(resApk, File(dir, "res")) { patchedResApk = it }
} }
} }

View file

@ -0,0 +1,9 @@
package com.aliucord.manager.patcher.steps.download
import com.aliucord.manager.R
class DownloadDiscordRNABaseStep : DownloadDiscordStep() {
override val targetUrl = "https://tracker.vendetta.rocks/tracker/download/$version/base"
override val targetFile = paths.discordApkVersionCache(version).resolve("base.apk")
override val localizedName = R.string.downloading_base_apk
}

View file

@ -0,0 +1,15 @@
package com.aliucord.manager.patcher.steps.download
import com.aliucord.manager.R
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.patcher.steps.base.DownloadStep
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class DownloadDiscordRNALangStep : DownloadStep(), KoinComponent {
private val paths: PathManager by inject()
override val targetUrl = "https://tracker.vendetta.rocks/tracker/download/$version/config.en"
override val targetFile = paths.discordApkVersionCache(version).resolve("config.en.apk")
override val localizedName = R.string.download_libs_apk
}

View file

@ -0,0 +1,17 @@
package com.aliucord.manager.patcher.steps.download
import android.os.Build
import com.aliucord.manager.R
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.patcher.steps.base.DownloadStep
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class DownloadDiscordRNALibStep : DownloadStep(), KoinComponent {
private val paths: PathManager by inject()
private val arch = Build.SUPPORTED_ABIS.first().replace("-v", "_v")
override val targetUrl = "https://tracker.vendetta.rocks/tracker/download/$version/config.$arch"
override val targetFile = paths.discordApkVersionCache(version).resolve("config.$arch.apk")
override val localizedName = R.string.downloading_libs_apk
}

View file

@ -0,0 +1,15 @@
package com.aliucord.manager.patcher.steps.download
import com.aliucord.manager.R
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.patcher.steps.base.DownloadStep
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class DownloadDiscordRNAResourcesStep : DownloadStep(), KoinComponent {
private val paths: PathManager by inject()
override val targetUrl = "https://tracker.vendetta.rocks/tracker/download/$version/config.xxhdpi"
override val targetFile = paths.discordApkVersionCache(version).resolve("config.xxhdpi.apk")
override val localizedName = R.string.downloading_resources_apk
}

View file

@ -13,8 +13,8 @@ import org.koin.core.component.inject
* from a redirect to an APK mirror site provided by the Aliucord backend. * from a redirect to an APK mirror site provided by the Aliucord backend.
*/ */
@Stable @Stable
class DownloadDiscordStep : DownloadStep(), KoinComponent { open class DownloadDiscordStep : DownloadStep(), KoinComponent {
private val paths: PathManager by inject() protected val paths: PathManager by inject()
override val localizedName = R.string.patch_step_dl_kt_apk override val localizedName = R.string.patch_step_dl_kt_apk
override val targetUrl = getDiscordApkUrl(DISCORD_KT_VERSION) override val targetUrl = getDiscordApkUrl(DISCORD_KT_VERSION)

View file

@ -22,98 +22,100 @@ class AlignmentStep : Step(), KoinComponent {
override suspend fun execute(container: StepRunner) { override suspend fun execute(container: StepRunner) {
val currentDeviceArch = Build.SUPPORTED_ABIS.first() val currentDeviceArch = Build.SUPPORTED_ABIS.first()
val apk = container.getStep<CopyDependenciesStep>().patchedApk val apks = container.getStep<CopyDependenciesStep>().patchedApks
var resourcesArscBytes: ByteArray? = null for (apk in apks) {
var dexCount: Int = -1 var resourcesArscBytes: ByteArray? = null
var dexCount: Int = -1
// Align resources.arsc due to targeting API 30 for silent install // Align resources.arsc due to targeting API 30 for silent install
if (Build.VERSION.SDK_INT >= 30) { if (Build.VERSION.SDK_INT >= 30) {
container.log("Extracting resources.arsc to be aligned later") container.log("Extracting resources.arsc to be aligned later")
resourcesArscBytes = ZipReader(apk) resourcesArscBytes = ZipReader(apk)
.use { it.openEntry("resources.arsc")?.read() } .use { it.openEntry("resources.arsc")?.read() }
?: throw IllegalArgumentException("APK is missing resources.arsc") ?: continue // Alignment is not needed
} }
// Align dex files due to using useEmbeddedDex (ref. ManifestPatcher) // Align dex files due to using useEmbeddedDex (ref. ManifestPatcher)
if (Build.VERSION.SDK_INT >= 29) { if (Build.VERSION.SDK_INT >= 29) {
container.log("Extracting all dex files to be aligned later") container.log("Extracting all dex files to be aligned later")
ZipReader(apk).use { zip -> ZipReader(apk).use { zip ->
// Count the amount of dex files currently in the apk // Count the amount of dex files currently in the apk
dexCount = zip.entryNames.count { it.endsWith(".dex") } dexCount = zip.entryNames.count { it.endsWith(".dex") }
// Copy all the dex files that need to be moved out of the apk // Copy all the dex files that need to be moved out of the apk
for (idx in 0..<dexCount) { for (idx in 0..<dexCount) {
val bytes = zip.openEntry(getDexName(idx))!!.read() val bytes = zip.openEntry(getDexName(idx))!!.read()
val file = paths.patchingWorkingDir().resolve(getDexName(idx)) val file = paths.patchingWorkingDir().resolve(getDexName(idx))
file.writeBytes(bytes)
}
}
container.log("Extracted $dexCount dex files")
}
// Align native libs due to using extractNativeLibs
container.log("Extracting native libraries to be aligned later")
val nativeLibPaths = ZipReader(apk).use { zip ->
val libPaths = zip.entryNames.filter { it.endsWith(".so") }
// Extract to disk temporarily
for ((idx, path) in libPaths.withIndex()) {
// Ignore lib architectures that don't match this device
if (!path.startsWith("lib/$currentDeviceArch")) {
container.log("Skipping native lib $path due to incompatible architecture")
continue
}
// Index is just used as a placeholder id to cache on disk
val bytes = zip.openEntry(path)!!.read()
val file = paths.patchingWorkingDir().resolve("$idx.so")
file.writeBytes(bytes) file.writeBytes(bytes)
} container.log("Extracted native lib $file")
}
container.log("Extracted $dexCount dex files")
}
// Align native libs due to using extractNativeLibs
container.log("Extracting native libraries to be aligned later")
val nativeLibPaths = ZipReader(apk).use { zip ->
val libPaths = zip.entryNames.filter { it.endsWith(".so") }
// Extract to disk temporarily
for ((idx, path) in libPaths.withIndex()) {
// Ignore lib architectures that don't match this device
if (!path.startsWith("lib/$currentDeviceArch")) {
container.log("Skipping native lib $path due to incompatible architecture")
continue
} }
// Index is just used as a placeholder id to cache on disk libPaths
val bytes = zip.openEntry(path)!!.read()
val file = paths.patchingWorkingDir().resolve("$idx.so")
file.writeBytes(bytes)
container.log("Extracted native lib $file")
} }
libPaths container.log("Writing entries back aligned")
} ZipWriter(apk, /* append = */ true).use { zip ->
// Delete all the unaligned files from APK
container.log("Deleting resources.arsc")
if (resourcesArscBytes != null)
zip.deleteEntry("resources.arsc")
container.log("Writing entries back aligned") container.log("Deleting $dexCount dex files")
ZipWriter(apk, /* append = */ true).use { zip -> for (i in 0..<dexCount)
// Delete all the unaligned files from APK zip.deleteEntry(getDexName(i))
container.log("Deleting resources.arsc")
if (resourcesArscBytes != null)
zip.deleteEntry("resources.arsc")
container.log("Deleting $dexCount dex files") container.log("Deleting native libraries: $nativeLibPaths")
for (i in 0..<dexCount) for (path in nativeLibPaths)
zip.deleteEntry(getDexName(i)) zip.deleteEntry(path)
container.log("Deleting native libraries: $nativeLibPaths") // Write all the files back aligned this time
for (path in nativeLibPaths) if (resourcesArscBytes != null) {
zip.deleteEntry(path) container.log("Writing resources.arsc uncompressed aligned to 4 bytes")
zip.writeEntry("resources.arsc", resourcesArscBytes, ZipCompression.NONE, 4)
}
// Write all the files back aligned this time container.log("Writing dex files uncompressed aligned to 4 bytes")
if (resourcesArscBytes != null) { for (idx in 0..<dexCount) {
container.log("Writing resources.arsc uncompressed aligned to 4 bytes") val file = paths.patchingWorkingDir().resolve(getDexName(idx))
zip.writeEntry("resources.arsc", resourcesArscBytes, ZipCompression.NONE, 4) val bytes = file.readBytes()
} zip.writeEntry(getDexName(idx), bytes, ZipCompression.NONE, 4)
}
container.log("Writing dex files uncompressed aligned to 4 bytes") // Write back native libraries aligned to 16KiB page boundary
for (idx in 0..<dexCount) { for ((idx, path) in nativeLibPaths.withIndex()) {
val file = paths.patchingWorkingDir().resolve(getDexName(idx)) // Ignore lib architectures that don't match this device
val bytes = file.readBytes() if (!path.startsWith("lib/$currentDeviceArch"))
zip.writeEntry(getDexName(idx), bytes, ZipCompression.NONE, 4) continue
}
// Write back native libraries aligned to 16KiB page boundary val file = paths.patchingWorkingDir().resolve("$idx.so")
for ((idx, path) in nativeLibPaths.withIndex()) { val bytes = file.readBytes()
// Ignore lib architectures that don't match this device
if (!path.startsWith("lib/$currentDeviceArch"))
continue
val file = paths.patchingWorkingDir().resolve("$idx.so") container.log("Writing $path uncompressed aligned to 16KiB")
val bytes = file.readBytes() zip.writeEntry(path, bytes, ZipCompression.NONE, 16384)
}
container.log("Writing $path uncompressed aligned to 16KiB")
zip.writeEntry(path, bytes, ZipCompression.NONE, 16384)
} }
} }
} }

View file

@ -26,7 +26,7 @@ private const val READY_NOTIF_ID = 200001
/** /**
* Install the final APK with the system's PackageManager. * Install the final APK with the system's PackageManager.
*/ */
class InstallStep(private val options: PatchOptions) : Step(), KoinComponent { open class InstallStep(private val options: PatchOptions) : Step(), KoinComponent {
private val context: Context by inject() private val context: Context by inject()
private val installers: InstallerManager by inject() private val installers: InstallerManager by inject()
private val prefs: PreferencesManager by inject() private val prefs: PreferencesManager by inject()
@ -36,7 +36,7 @@ class InstallStep(private val options: PatchOptions) : Step(), KoinComponent {
override val localizedName = R.string.patch_step_install override val localizedName = R.string.patch_step_install
override suspend fun execute(container: StepRunner) { override suspend fun execute(container: StepRunner) {
val apk = container.getStep<CopyDependenciesStep>().patchedApk val apks = container.getStep<CopyDependenciesStep>().patchedApks
// If app backgrounded, show notification // If app backgrounded, show notification
if (ProcessLifecycleOwner.get().lifecycle.currentState == Lifecycle.State.CREATED) { if (ProcessLifecycleOwner.get().lifecycle.currentState == Lifecycle.State.CREATED) {
@ -66,9 +66,9 @@ class InstallStep(private val options: PatchOptions) : Step(), KoinComponent {
prefs.showPlayProtectWarning = !neverShowAgain prefs.showPlayProtectWarning = !neverShowAgain
} }
container.log("Installing ${apk.absolutePath}, silent: ${!prefs.devMode}") container.log("Installing ${apks.joinToString(", ") { it.name }}, silent: ${!prefs.devMode}")
val result = installers.getActiveInstaller().waitInstall( val result = installers.getActiveInstaller().waitInstall(
apks = listOf(apk), apks = apks,
silent = !prefs.devMode, silent = !prefs.devMode,
) )

View file

@ -16,9 +16,9 @@ class SigningStep : Step(), KoinComponent {
override val localizedName = R.string.patch_step_signing override val localizedName = R.string.patch_step_signing
override suspend fun execute(container: StepRunner) { override suspend fun execute(container: StepRunner) {
val apk = container.getStep<CopyDependenciesStep>().patchedApk for (apk in container.getStep<CopyDependenciesStep>().patchedApks) {
container.log("Signing apk at ${apk.absolutePath}")
container.log("Signing apk at ${apk.absolutePath}") Signer.signApk(apk)
Signer.signApk(apk) }
} }
} }

View file

@ -18,25 +18,33 @@ class PatchManifestStep(private val options: PatchOptions) : Step() {
override val localizedName = R.string.patch_step_patch_manifests override val localizedName = R.string.patch_step_patch_manifests
override suspend fun execute(container: StepRunner) { override suspend fun execute(container: StepRunner) {
val apk = container.getStep<CopyDependenciesStep>().patchedApk val deps = container.getStep<CopyDependenciesStep>()
for (apk in deps.patchedApks) {
container.log("Reading manifest from apk")
val manifest = ZipReader(apk)
.use { zip -> zip.openEntry("AndroidManifest.xml")?.read() }
?: throw IllegalArgumentException("No manifest found in APK")
container.log("Reading manifest from apk") container.log("Patching manifest")
val manifest = ZipReader(apk) val patchedManifest = if (apk == deps.patchedApk)
.use { zip -> zip.openEntry("AndroidManifest.xml")?.read() } ManifestPatcher.patchManifest(
?: throw IllegalArgumentException("No manifest found in APK") manifestBytes = manifest,
packageName = options.packageName,
appName = options.appName,
debuggable = options.debuggable,
)
else {
ManifestPatcher.patchManifestPackageName(
manifestBytes = manifest,
packageName = options.packageName,
)
}
container.log("Patching manifest") container.log("Writing patched manifest to apk unaligned compressed")
val patchedManifest = ManifestPatcher.patchManifest( ZipWriter(apk, /* append = */ true).use {
manifestBytes = manifest, it.deleteEntry("AndroidManifest.xml", apk == deps.patchedLibApk || apk == deps.patchedApk)
packageName = options.packageName, it.writeEntry("AndroidManifest.xml", patchedManifest)
appName = options.appName, }
debuggable = options.debuggable,
)
container.log("Writing patched manifest to apk unaligned compressed")
ZipWriter(apk, /* append = */ true).use {
it.deleteEntry("AndroidManifest.xml")
it.writeEntry("AndroidManifest.xml", patchedManifest)
} }
} }
} }

View file

@ -27,17 +27,17 @@ class SaveMetadataStep(private val options: PatchOptions) : Step(), KoinComponen
override suspend fun execute(container: StepRunner) { override suspend fun execute(container: StepRunner) {
val apk = container.getStep<CopyDependenciesStep>().patchedApk val apk = container.getStep<CopyDependenciesStep>().patchedApk
val aliuhook = container.getStep<DownloadAliuhookStep>() val aliuhook = container.getStepOrNull<DownloadAliuhookStep>()
val injector = container.getStep<DownloadInjectorStep>() val injector = container.getStepOrNull<DownloadInjectorStep>()
val patches = container.getStep<DownloadPatchesStep>() val patches = container.getStepOrNull<DownloadPatchesStep>()
val metadata = InstallMetadata( val metadata = InstallMetadata(
options = options, options = options,
customManager = !BuildConfig.RELEASE, customManager = !BuildConfig.RELEASE,
managerVersion = SemVer.parse(BuildConfig.VERSION_NAME), managerVersion = SemVer.parse(BuildConfig.VERSION_NAME),
aliuhookVersion = aliuhook.targetVersion, aliuhookVersion = aliuhook?.targetVersion,
injectorVersion = injector.targetVersion, injectorVersion = injector?.targetVersion,
patchesVersion = patches.targetVersion, patchesVersion = patches?.targetVersion,
) )
container.log("Writing serialized install metadata to APK") container.log("Writing serialized install metadata to APK")

View file

@ -191,6 +191,72 @@ object ManifestPatcher {
return writer.toByteArray() return writer.toByteArray()
} }
fun patchManifestPackageName(
manifestBytes: ByteArray,
packageName: String,
): ByteArray {
val reader = AxmlReader(manifestBytes)
val writer = AxmlWriter()
reader.accept(object : AxmlVisitor(writer) {
override fun ns(prefix: String?, uri: String?, ln: Int) {
val realUri = uri ?: ANDROID_NAMESPACE
super.ns(prefix, realUri, ln)
}
override fun child(ns: String?, name: String?) =
object : ReplaceAttrsVisitor(
super.child(ns, name),
mapOf(PACKAGE to packageName)
) {
override fun child(ns: String?, name: String): NodeVisitor {
val nv = super.child(ns, name)
return when (name) {
"permission" -> object : NodeVisitor(nv) {
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
super.attr(
ns, name, resourceId, type,
if (name == "name") {
(value as String).replace("com.discord", packageName)
} else {
value
}
)
}
}
"application" -> object : NodeVisitor(nv) {
override fun child(ns: String?, name: String): NodeVisitor {
val visitor = super.child(ns, name)
return when (name) {
"provider" -> object : NodeVisitor(visitor) {
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
super.attr(
ns, name, resourceId, type,
if (name == "authorities") {
(value as String).replace("com.discord", packageName)
} else {
value
}
)
}
}
else -> visitor
}
}
}
else -> nv
}
}
}
})
return writer.toByteArray()
}
private open class ReplaceAttrsVisitor( private open class ReplaceAttrsVisitor(
nv: NodeVisitor, nv: NodeVisitor,
private val attrs: Map<String, Any>, private val attrs: Map<String, Any>,

View file

@ -250,9 +250,11 @@ class HomeModel(
return false return false
} }
// Check that all the installation components are up-to-date // Check that all the installation components are up-to-date (skip not installed)
return remoteBuildData.injectorVersion == installMetadata.injectorVersion val injectorUpToDate = installMetadata.injectorVersion == null || remoteBuildData.injectorVersion == installMetadata.injectorVersion
&& remoteBuildData.patchesVersion == installMetadata.patchesVersion val patchesUpToDate = installMetadata.patchesVersion == null || remoteBuildData.patchesVersion == installMetadata.patchesVersion
&& latestAliuhookVersion == installMetadata.aliuhookVersion val aliuhookUpToDate = installMetadata.aliuhookVersion == null || latestAliuhookVersion == installMetadata.aliuhookVersion
return injectorUpToDate && patchesUpToDate && aliuhookUpToDate
} }
} }

View file

@ -10,8 +10,8 @@ import cafe.adriel.voyager.core.model.screenModelScope
import com.aliucord.manager.BuildConfig import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R import com.aliucord.manager.R
import com.aliucord.manager.manager.* import com.aliucord.manager.manager.*
import com.aliucord.manager.patcher.KotlinPatchRunner
import com.aliucord.manager.patcher.StepRunner import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.WintryPatchRunner
import com.aliucord.manager.patcher.steps.StepGroup import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.* import com.aliucord.manager.patcher.steps.base.*
import com.aliucord.manager.patcher.steps.install.InstallStep import com.aliucord.manager.patcher.steps.install.InstallStep
@ -125,7 +125,7 @@ class PatchingScreenModel(
} }
private suspend fun startPatchRunner() { private suspend fun startPatchRunner() {
val runner = KotlinPatchRunner(options) val runner = WintryPatchRunner(options)
.also { stepRunner = it } .also { stepRunner = it }
val newSteps = runner.steps.groupBy { it.group } val newSteps = runner.steps.groupBy { it.group }

View file

@ -245,4 +245,9 @@
<string name="fun_fact_10">Aliucord uses the *real* old UI, from mid-2022!</string> <string name="fun_fact_10">Aliucord uses the *real* old UI, from mid-2022!</string>
<string name="fun_fact_11">There was an attempt to make Aliucord run on iOS…</string> <string name="fun_fact_11">There was an attempt to make Aliucord run on iOS…</string>
<string name="fun_fact_12" translatable="false">veeeee 😭</string> <string name="fun_fact_12" translatable="false">veeeee 😭</string>
<string name="download_libs_apk">Download libs apk</string>
<string name="downloading_base_apk">Downloading base APK</string>
<string name="downloading_libs_apk">Downloading libs apk</string>
<string name="downloading_resources_apk">Downloading resources apk</string>
</resources> </resources>