From 5ef7946a9c4173d4b98f92f8f690342e3e08c1e5 Mon Sep 17 00:00:00 2001 From: pylixonly <82711525+pylixonly@users.noreply.github.com> Date: Sun, 13 Jul 2025 23:02:58 +0800 Subject: [PATCH] feat(wintry): Implement Wintry patcher This commit introduces a new patcher for WintryXposed. `minSdk` is bumped to 28 to match LSPosed deps --- app/build.gradle.kts | 26 +- .../aliucord/manager/manager/PathManager.kt | 2 +- .../manager/patcher/WintryPatchRunner.kt | 11 +- .../steps/download/CopyDependenciesStep.kt | 11 +- .../steps/download/DownloadWintryXposed.kt | 14 + .../steps/install/InjectWintryXposedStep.kt | 99 +++++++ .../patcher/steps/patch/PatchManifestStep.kt | 32 +- .../steps/patch/PatchWintryManifestStep.kt | 18 ++ .../manager/patcher/util/ManifestPatcher.kt | 279 ++++++++---------- .../aliucord/manager/patcher/util/Signer.kt | 7 +- app/src/main/res/values/strings.xml | 2 + 11 files changed, 309 insertions(+), 192 deletions(-) create mode 100644 app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/DownloadWintryXposed.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/InjectWintryXposedStep.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchWintryManifestStep.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index eba7fd5..200d3de 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,7 +22,7 @@ android { compileSdk = 36 defaultConfig { - minSdk = 24 + minSdk = 28 targetSdk = 36 versionCode = 10_00_02 versionName = "1.0.2" @@ -169,6 +169,8 @@ dependencies { implementation(libs.bundles.ktor) implementation(libs.bundles.voyager) + implementation(files("libs/lspatch.aar")) + implementation(libs.compose.ui.tooling.preview) debugImplementation(libs.compose.ui.tooling) debugImplementation(libs.compose.runtime.tracing) @@ -191,6 +193,28 @@ dependencies { coreLibraryDesugaring(libs.desugaring) } +configurations.all { + // ---- Previous Exclusions ---- + exclude(group = "org.bouncycastle") + exclude(group = "org.checkerframework", module = "checker-qual") + exclude(group = "com.aliucord", module = "axml") + + // ---- New Exclusions ---- + // Exclude apksig, as it's bundled in LSPatch + exclude(group = "com.android.tools.build", module = "apksig") + + // Exclude jcommander + exclude(group = "com.beust", module = "jcommander") + + // Exclude Google Guava and its related modules + exclude(group = "com.google.guava") + + // Exclude other common utility/annotation libraries + exclude(group = "com.google.errorprone", module = "error_prone_annotations") + exclude(group = "com.google.j2objc", module = "j2objc-annotations") + exclude(group = "com.google.code.findbugs", module = "jsr305") +} + fun ProviderFactory.execIgnoreCode(vararg command: String): String = run { val result = exec { commandLine = command.toList() diff --git a/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt b/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt index 34d18f5..0227147 100644 --- a/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt @@ -125,7 +125,7 @@ class PathManager( /** * The APK that is worked on during the patching process. */ - fun patchedApk(part: String = "") = patchingWorkingDir().resolve("patched-${part}.apk") + fun patchedApk(part: String = "base") = patchingWorkingDir().resolve("patched-$part.apk") private companion object { /** diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/WintryPatchRunner.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/WintryPatchRunner.kt index 09ac717..8b8a0b5 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/WintryPatchRunner.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/WintryPatchRunner.kt @@ -1,6 +1,5 @@ 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.* @@ -8,21 +7,23 @@ import com.aliucord.manager.ui.screens.patchopts.PatchOptions import kotlinx.collections.immutable.persistentListOf class WintryPatchRunner(options: PatchOptions) : StepRunner() { - override val steps = persistentListOf( + override val steps = persistentListOf( DownloadDiscordRNABaseStep(), DownloadDiscordRNALangStep(), DownloadDiscordRNAResourcesStep(), DownloadDiscordRNALibStep(), + DownloadWintryXposed(), CopyDependenciesStep(), PatchIconsStep(options), - PatchManifestStep(options), - // ReorganizeDexStep(), + PatchWintryManifestStep(options), SaveMetadataStep(options), AlignmentStep(), SigningStep(), + InjectWintryXposedStep(), + InstallStep(options), - // CleanupStep(), + CleanupStep(), ) } diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/CopyDependenciesStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/CopyDependenciesStep.kt index 0e504ec..070b5a8 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/CopyDependenciesStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/CopyDependenciesStep.kt @@ -72,9 +72,10 @@ class CopyDependenciesStep : Step(), KoinComponent { } } - fun copyApkSafely(src: File?, destFile: File, onCopied: (File) -> Unit) { + fun copyApkSafely(src: File?, part: String, onCopied: (File) -> Unit) { src?.let { - container.log("Copying patched apk from ${it.absolutePath} to ${destFile.absolutePath}") + container.log("Copying patched apk from ${it.absolutePath} to $part") + val destFile = File(dir, paths.patchedApk(part).name) it.copyTo(destFile) onCopied(destFile) } @@ -85,8 +86,8 @@ class CopyDependenciesStep : Step(), KoinComponent { 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 } + copyApkSafely(langApk, "lang") { patchedLangApk = it } + copyApkSafely(libApk, "lib") { patchedLibApk = it } + copyApkSafely(resApk, "res") { patchedResApk = it } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/DownloadWintryXposed.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/DownloadWintryXposed.kt new file mode 100644 index 0000000..5f5b906 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/download/DownloadWintryXposed.kt @@ -0,0 +1,14 @@ +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.inject + +class DownloadWintryXposed : DownloadStep() { + private val paths: PathManager by inject() + + override val localizedName = R.string.downloading_wintryxposed + override val targetFile = paths.patchingDownloadDir.resolve("xposed.apk") + override val targetUrl = "https://github.com/wtcord/wt-xposed/releases/download/1002/app-release.apk" +} diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/InjectWintryXposedStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/InjectWintryXposedStep.kt new file mode 100644 index 0000000..7d4d5ce --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/InjectWintryXposedStep.kt @@ -0,0 +1,99 @@ +package com.aliucord.manager.patcher.steps.install + +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.CopyDependenciesStep +import com.aliucord.manager.patcher.steps.download.DownloadWintryXposed +import com.aliucord.manager.patcher.util.Signer +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.lsposed.lspatch.share.LSPConfig +import org.lsposed.patch.LSPatch +import org.lsposed.patch.util.Logger +import java.io.File + +class InjectWintryXposedStep : Step() { + override val group: StepGroup = StepGroup.Install + override val localizedName: Int = R.string.injecting_wintryxposed_with_lspatch + + suspend fun patch( + container: StepRunner, + outputDir: File, + apkPaths: List, + embeddedModules: List, + ) { + withContext(Dispatchers.IO) { + LSPatch( + object : Logger() { + override fun d(p0: String?) { + container.log("[LSPatch:D] $p0") + } + + override fun e(p0: String?) { + container.log("[LSPatch:E] $p0") + } + + override fun i(p0: String?) { + container.log("[LSPatch] $p0") + } + }, + *apkPaths.toTypedArray(), + "-o", + outputDir.absolutePath, + "-l", + "0", + "-v", + "-m", + *embeddedModules.toTypedArray(), + "-k", + Signer.getKeystoreFile().absolutePath, + "password", + "alias", + "password" + ).doCommandLine() + } + } + + override suspend fun execute(container: StepRunner) { + val apks = container.getStep().patchedApks + val xposed = container.getStep().targetFile + + container.log("Adding WintryXposed module with LSPatch") + container.log("WintryXposed path = ${xposed.absolutePath}") + + // Create temporary folder in working directory + val tempDir = apks.first().parentFile!!.resolve("lspatched") + + patch( + container, + outputDir = tempDir, + apkPaths = apks.map { it.absolutePath }, + embeddedModules = listOf(xposed.absolutePath) + ) + + // Process each APK and replace with patched version + apks.forEach { originalApk -> + val baseName = originalApk.nameWithoutExtension + // https://github.com/JingMatrix/LSPatch/blob/b98eaf805018c4cc258ded12efe89212a4855e6a/patch/src/main/java/org/lsposed/patch/LSPatch.java#L159-L163 + // String.format( + // Locale.getDefault(), "%s-%d-lspatched.apk", + // FilenameUtils.getBaseName(apkFileName), + // LSPConfig.instance.VERSION_CODE) + // ) + val patchedApkName = "${baseName}-${LSPConfig.instance.VERSION_CODE}-lspatched.apk" + val patchedApk = File(tempDir, patchedApkName) + + if (patchedApk.exists()) { + patchedApk.copyTo(originalApk, overwrite = true) + container.log("Replaced ${originalApk.name} with ${patchedApk.name}") + } else { + container.log("Warning: Could not find patched APK for ${originalApk.name}") + container.log("Expected patched APK at: ${patchedApk.absolutePath}") + } + } + + tempDir.deleteRecursively() + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchManifestStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchManifestStep.kt index 9314304..5f5903b 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchManifestStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchManifestStep.kt @@ -13,36 +13,32 @@ import com.github.diamondminer88.zip.ZipWriter /** * Patch the APK's AndroidManifest.xml */ -class PatchManifestStep(private val options: PatchOptions) : Step() { +open class PatchManifestStep(private val options: PatchOptions) : Step() { override val group = StepGroup.Patch override val localizedName = R.string.patch_step_patch_manifests + protected open fun patchManifest(manifestBytes: ByteArray, isBase: Boolean) = ManifestPatcher.patchManifest( + manifestBytes = manifestBytes, + packageName = options.packageName, + appName = options.appName, + debuggable = options.debuggable, + ) + override suspend fun execute(container: StepRunner) { - val deps = container.getStep() - for (apk in deps.patchedApks) { + val dependenciesStep = container.getStep() + for (apk in dependenciesStep.patchedApks) { container.log("Reading manifest from apk") - val manifest = ZipReader(apk) + val manifestBytes = ZipReader(apk) .use { zip -> zip.openEntry("AndroidManifest.xml")?.read() } ?: throw IllegalArgumentException("No manifest found in APK") container.log("Patching manifest") - val patchedManifest = if (apk == deps.patchedApk) - ManifestPatcher.patchManifest( - manifestBytes = manifest, - packageName = options.packageName, - appName = options.appName, - debuggable = options.debuggable, - ) - else { - ManifestPatcher.patchManifestPackageName( - manifestBytes = manifest, - packageName = options.packageName, - ) - } + val isBase = apk == dependenciesStep.patchedApk + val patchedManifest = patchManifest(manifestBytes, isBase) container.log("Writing patched manifest to apk unaligned compressed") ZipWriter(apk, /* append = */ true).use { - it.deleteEntry("AndroidManifest.xml", apk == deps.patchedLibApk || apk == deps.patchedApk) + it.deleteEntry("AndroidManifest.xml", apk == dependenciesStep.patchedLibApk || apk == dependenciesStep.patchedApk) it.writeEntry("AndroidManifest.xml", patchedManifest) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchWintryManifestStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchWintryManifestStep.kt new file mode 100644 index 0000000..a86bceb --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchWintryManifestStep.kt @@ -0,0 +1,18 @@ +package com.aliucord.manager.patcher.steps.patch + +import com.aliucord.manager.patcher.util.ManifestPatcher +import com.aliucord.manager.ui.screens.patchopts.PatchOptions + +class PatchWintryManifestStep(val options: PatchOptions) : PatchManifestStep(options) { + override fun patchManifest(manifestBytes: ByteArray, isBase: Boolean) = ManifestPatcher.patchManifest( + manifestBytes = manifestBytes, + packageName = options.packageName, + compileSdkVersion = null, + compileSdkVersionCodename = null, + targetSdkVersion = null, + useEmbeddedDex = false, + requestLegacyExternalStorage = false, + appName = if (isBase) options.appName else null, + debuggable = options.debuggable, + ) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/util/ManifestPatcher.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/util/ManifestPatcher.kt index c7b87aa..f6c2b5b 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/util/ManifestPatcher.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/util/ManifestPatcher.kt @@ -24,16 +24,26 @@ object ManifestPatcher { fun patchManifest( manifestBytes: ByteArray, - packageName: String, - appName: String, - debuggable: Boolean, + packageName: String? = null, + appName: String? = null, + debuggable: Boolean? = null, + compileSdkVersion: Int? = 23, + compileSdkVersionCodename: String? = "6.0-2438415", + addManageExternalStorage: Boolean? = true, + targetSdkVersion: Int? = if (Build.VERSION.SDK_INT >= 31) 30 else 28, + requestLegacyExternalStorage: Boolean? = true, + vmSafeMode: Boolean? = true, + useEmbeddedDex: Boolean? = true, + extractNativeLibs: Boolean? = false, + addAliucordMetadata: Boolean? = true, + modifyPermissions: Boolean? = true, + modifyProviders: Boolean? = true, + modifyActivities: Boolean? = true, ): ByteArray { val reader = AxmlReader(manifestBytes) val writer = AxmlWriter() reader.accept(object : AxmlVisitor(writer) { - // Without this, decompiling the finished manifest has the android namespace - // under an autogenerated name like axml_00 or something. override fun ns(prefix: String?, uri: String?, ln: Int) { val realUri = uri ?: ANDROID_NAMESPACE super.ns(prefix, realUri, ln) @@ -42,94 +52,98 @@ object ManifestPatcher { override fun child(ns: String?, name: String?) = object : ReplaceAttrsVisitor( super.child(ns, name), - mapOf( - PACKAGE to packageName, - COMPILE_SDK_VERSION to 23, - COMPILE_SDK_VERSION_CODENAME to "6.0-2438415" - ) + buildMap { + packageName?.let { put(PACKAGE, it) } + compileSdkVersion?.let { put(COMPILE_SDK_VERSION, it) } + compileSdkVersionCodename?.let { put(COMPILE_SDK_VERSION_CODENAME, it) } + } ) { - private var addExternalStoragePerm = false + private var shouldAddExternalStoragePerm = false override fun child(ns: String?, name: String): NodeVisitor { val nv = super.child(ns, name) // Add MANAGE_EXTERNAL_STORAGE when necessary - if (addExternalStoragePerm) { + if (shouldAddExternalStoragePerm && addManageExternalStorage == true) { super .child(null, "uses-permission") .attr(ANDROID_NAMESPACE, "name", android.R.attr.name, TYPE_STRING, Manifest.permission.MANAGE_EXTERNAL_STORAGE) - addExternalStoragePerm = false + shouldAddExternalStoragePerm = false } return when (name) { - "uses-permission" -> object : NodeVisitor(nv) { - override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) { - if (name != "maxSdkVersion") { - super.attr(ns, name, resourceId, type, value) - } - - // Set the add external storage permission to be added after WRITE_EXTERNAL_STORAGE (which is after read) - if (name == "name" && value == Manifest.permission.READ_EXTERNAL_STORAGE) { - addExternalStoragePerm = true - } - } - } - - "uses-sdk" -> object : NodeVisitor(nv) { - override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) { - if (name == "targetSdkVersion") { - val version = if (Build.VERSION.SDK_INT >= 31) 30 else 28 - super.attr(ns, name, resourceId, type, version) - } else { - super.attr(ns, name, resourceId, type, value) - } - } - } - - "permission" -> object : NodeVisitor(nv) { - override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { - super.attr( - ns, name, resourceId, type, - when (name) { - "name" -> (value as String).replace("com.discord", packageName) - else -> value + "uses-permission" -> if (modifyPermissions != false) { + object : NodeVisitor(nv) { + override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) { + if (name != "maxSdkVersion") { + super.attr(ns, name, resourceId, type, value) } - ) + + // Set the add external storage permission to be added after WRITE_EXTERNAL_STORAGE (which is after read) + if (name == "name" && value == Manifest.permission.READ_EXTERNAL_STORAGE) { + shouldAddExternalStoragePerm = true + } + } } - } + } else nv + + "uses-sdk" -> if (targetSdkVersion != null) { + object : NodeVisitor(nv) { + override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) { + if (name == "targetSdkVersion") { + super.attr(ns, name, resourceId, type, targetSdkVersion) + } else { + super.attr(ns, name, resourceId, type, value) + } + } + } + } else nv + + "permission" -> if (modifyPermissions != false && packageName != null) { + object : NodeVisitor(nv) { + override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { + super.attr( + ns, name, resourceId, type, + when (name) { + "name" -> (value as String).replace("com.discord", packageName) + else -> value + } + ) + } + } + } else nv "application" -> object : ReplaceAttrsVisitor( nv, - mapOf( - LABEL to appName, - DEBUGGABLE to debuggable, - REQUEST_LEGACY_EXTERNAL_STORAGE to true, - VM_SAFE_MODE to true, - USE_EMBEDDED_DEX to true, - EXTRACT_NATIVE_LIBS to false, - ) + buildMap { + appName?.let { put(LABEL, it) } + debuggable?.let { put(DEBUGGABLE, it) } + requestLegacyExternalStorage?.let { put(REQUEST_LEGACY_EXTERNAL_STORAGE, it) } + vmSafeMode?.let { put(VM_SAFE_MODE, it) } + useEmbeddedDex?.let { put(USE_EMBEDDED_DEX, it) } + extractNativeLibs?.let { put(EXTRACT_NATIVE_LIBS, !it) } // Inverted since we set to false originally + } ) { - private var addDebuggable = debuggable - private var addLegacyStorage = true - private var addUseEmbeddedDex = true - private var addExtractNativeLibs = true - private var addMetadata = true + private var shouldAddDebuggable = debuggable ?: false + private var shouldAddLegacyStorage = requestLegacyExternalStorage ?: false + private var shouldAddUseEmbeddedDex = useEmbeddedDex ?: false + private var shouldAddExtractNativeLibs = extractNativeLibs ?: false + private var shouldAddMetadata = addAliucordMetadata ?: false 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 == USE_EMBEDDED_DEX) addUseEmbeddedDex = false - if (name == EXTRACT_NATIVE_LIBS) addExtractNativeLibs = false - if (name == DEBUGGABLE) addDebuggable = false + if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) shouldAddLegacyStorage = false + if (name == USE_EMBEDDED_DEX) shouldAddUseEmbeddedDex = false + if (name == EXTRACT_NATIVE_LIBS) shouldAddExtractNativeLibs = false + if (name == DEBUGGABLE) shouldAddDebuggable = false super.attr(ns, name, resourceId, type, value) } override fun child(ns: String?, name: String): NodeVisitor { val visitor = super.child(ns, name) - // Adds a tag to make multi-install detection by HomeScreen work - if (addMetadata) { - addMetadata = false + if (shouldAddMetadata && addAliucordMetadata == true) { + shouldAddMetadata = false super.child(ANDROID_NAMESPACE, "meta-data").apply { attr(ANDROID_NAMESPACE, "name", android.R.attr.name, TYPE_STRING, "isAliucord") attr(ANDROID_NAMESPACE, "value", android.R.attr.value, TYPE_INT_BOOLEAN, 1) @@ -137,46 +151,57 @@ object ManifestPatcher { } return when (name) { - "activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to appName)) - "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 - } - ) + "activity" -> if (modifyActivities != false && appName != null) { + ReplaceAttrsVisitor(visitor, mapOf("label" to appName)) + } else visitor + + "provider" -> if (modifyProviders != false && packageName != null) { + 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 -> visitor } } override fun end() { - if (addLegacyStorage && Build.VERSION.SDK_INT >= 29) super.attr( - ANDROID_NAMESPACE, - REQUEST_LEGACY_EXTERNAL_STORAGE, - android.R.attr.requestLegacyExternalStorage, - TYPE_INT_BOOLEAN, - 1 - ) - if (addDebuggable) super.attr(ANDROID_NAMESPACE, DEBUGGABLE, android.R.attr.debuggable, TYPE_INT_BOOLEAN, 1) + if (shouldAddLegacyStorage && Build.VERSION.SDK_INT >= 29 && requestLegacyExternalStorage == true) { + super.attr( + ANDROID_NAMESPACE, + REQUEST_LEGACY_EXTERNAL_STORAGE, + android.R.attr.requestLegacyExternalStorage, + TYPE_INT_BOOLEAN, + 1 + ) + } + if (shouldAddDebuggable && debuggable == true) { + 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) { + if (Build.VERSION.SDK_INT >= 29 && shouldAddUseEmbeddedDex && useEmbeddedDex == true) { super.attr(ANDROID_NAMESPACE, USE_EMBEDDED_DEX, android.R.attr.useEmbeddedDex, TYPE_INT_BOOLEAN, 1) } - if (addExtractNativeLibs) super.attr( - ANDROID_NAMESPACE, - EXTRACT_NATIVE_LIBS, - android.R.attr.extractNativeLibs, - TYPE_INT_BOOLEAN, - 0 - ) + if (shouldAddExtractNativeLibs && extractNativeLibs == false) { + super.attr( + ANDROID_NAMESPACE, + EXTRACT_NATIVE_LIBS, + android.R.attr.extractNativeLibs, + TYPE_INT_BOOLEAN, + 0 + ) + } super.end() } @@ -191,72 +216,6 @@ object ManifestPatcher { 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( nv: NodeVisitor, private val attrs: Map, diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/util/Signer.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/util/Signer.kt index f8bf9b0..20e29c6 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/util/Signer.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/util/Signer.kt @@ -7,7 +7,6 @@ package com.aliucord.manager.patcher.util import com.aliucord.manager.manager.PathManager import com.android.apksig.ApkSigner -import com.android.apksig.KeyConfig import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.cert.X509v3CertificateBuilder @@ -44,7 +43,7 @@ object Signer : KoinComponent { ApkSigner.SignerConfig.Builder( "Aliucord Manager signer", - KeyConfig.Jca(keyStore.getKey(alias, password) as PrivateKey), + keyStore.getKey(alias, password) as PrivateKey, listOf(certificate) ).build() } @@ -99,5 +98,9 @@ object Signer : KoinComponent { tmpApk.renameTo(apkFile) } + fun getKeystoreFile(): File { + return paths.keystoreFile + } + private class KeySet(val publicKey: X509Certificate, val privateKey: PrivateKey) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08f97b7..c212da3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -250,4 +250,6 @@ Downloading base APK Downloading libs apk Downloading resources apk + Injecting WintryXposed with LSPatch + Downloading WintryXposed