From e479d082c19c2ab7c47551b6bb563afa592eaa9e Mon Sep 17 00:00:00 2001 From: Juby210 <31005896+Juby210@users.noreply.github.com> Date: Sat, 12 Mar 2022 22:30:38 +0100 Subject: [PATCH] add manifest patching using axml --- .editorconfig | 38 ++++++++ app/build.gradle.kts | 3 +- .../manager/ui/screens/InstallScreen.kt | 41 ++++---- .../com/aliucord/manager/utils/Const.kt | 6 +- .../aliucord/manager/utils/ManifestPatcher.kt | 94 +++++++++++++++++++ .../manager/utils/ReplaceAttrsVisitor.kt | 19 ++++ build.gradle.kts | 5 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 8 files changed, 182 insertions(+), 28 deletions(-) create mode 100644 .editorconfig create mode 100644 app/src/main/kotlin/com/aliucord/manager/utils/ManifestPatcher.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/utils/ReplaceAttrsVisitor.kt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0402c9a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,38 @@ +# Android Studio: Settings > Editor > Code Style > Enable EditorConfig support + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +max_line_length = 150 +trim_trailing_whitespace = true +insert_final_newline = true +indent_size = 4 +# these are Intellij specific so they will only work in Android Studio / IDEA +ij_continuation_indent_size = 4 + +[*.java] +ij_java_names_count_to_use_import_on_demand = 3 +ij_java_class_count_to_use_import_on_demand = 3 +ij_java_blank_lines_around_method_in_interface = 0 +ij_java_doc_do_not_wrap_if_one_line = true +ij_java_keep_first_column_comment = false +ij_java_keep_simple_blocks_in_one_line = true +ij_java_keep_simple_classes_in_one_line = true +ij_java_keep_simple_lambdas_in_one_line = true +ij_java_keep_simple_methods_in_one_line = true +ij_java_line_comment_add_space = true +ij_java_line_comment_at_first_column = false +ij_java_spaces_within_array_initializer_braces = true + +[*.kt] +ij_kotlin_name_count_to_use_star_import = 3 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_keep_first_column_comment = false +ij_kotlin_line_comment_add_space = true +ij_kotlin_line_comment_at_first_column = false + +[*.{xml,yml}] +indent_size = 2 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1134a2a..799d02e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -89,9 +89,10 @@ dependencies { implementation("com.google.code.gson:gson:2.8.9") implementation("io.coil-kt:coil-compose:1.4.0") implementation("org.bouncycastle:bcpkix-jdk15on:1.65") + implementation("de.upb.cs.swt:axml:2.1.1") // 2.1.2 is broken btw // ktor implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-android:$ktorVersion") implementation("io.ktor:ktor-client-gson:$ktorVersion") -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/InstallScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/InstallScreen.kt index affab81..f41a44f 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/InstallScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/InstallScreen.kt @@ -49,21 +49,6 @@ fun InstallerScreen(navigator: DestinationsNavigator, apk: File?) { } } - val useDebuggableManifest = Prefs.debuggable.get() - val manifest = - File( - context.externalCacheDir, - if (useDebuggableManifest) "AndroidManifest-debuggable.xml" else "AndroidManifest.xml" - ).also { file -> - if (file.exists()) return@also - - log += "Downloading patched manifest...\n" - - DownloadUtils.downloadManifest(context, useDebuggableManifest) - - log += "Done\n" - } - val injector = File(context.externalCacheDir, "Injector.dex").also { file -> if (file.exists()) return@also @@ -84,13 +69,17 @@ fun InstallerScreen(navigator: DestinationsNavigator, apk: File?) { log += "Repacking APK\n" var patched = false + var manifestBytes: ByteArray? with(Zip(outputApk.absolutePath, 6, 'r')) { - repeat(totalEntries) { index -> + for (index in 0 until totalEntries) { openEntryByIndex(index) val name = entryName - if (name == "classes5.dex") patched = true closeEntry() + if (name == "classes5.dex") { + patched = true + break + } } val cacheDir = context.cacheDir.absolutePath @@ -101,6 +90,10 @@ fun InstallerScreen(navigator: DestinationsNavigator, apk: File?) { closeEntry() } + openEntry("AndroidManifest.xml") + manifestBytes = readEntry() + closeEntry() + close() } @@ -136,15 +129,19 @@ fun InstallerScreen(navigator: DestinationsNavigator, apk: File?) { writeEntry("classes5.dex", assetManager.open("pine/classes.dex")) - openEntry("AndroidManifest.xml") - compressFile(manifest.absolutePath) - closeEntry() - writeEntry("lib/arm64-v8a/libpine.so", assetManager.open("pine/arm64-v8a/libpine.so")) writeEntry("lib/armeabi-v7a/libpine.so", assetManager.open("pine/armeabi-v7a/libpine.so")) writeEntry("classes6.dex", assetManager.open("kotlin/classes.dex")) + manifestBytes?.let { bytes -> + log += "Patching manifest\n" + val newManifestBytes = patchManifest(bytes) + openEntry("AndroidManifest.xml") + writeEntry(newManifestBytes, newManifestBytes.size.toLong()) + closeEntry() + } + close() } @@ -210,4 +207,4 @@ fun InstallerScreen(navigator: DestinationsNavigator, apk: File?) { // progress = 0.5f // ) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/com/aliucord/manager/utils/Const.kt b/app/src/main/kotlin/com/aliucord/manager/utils/Const.kt index 3abe5bf..7133999 100644 --- a/app/src/main/kotlin/com/aliucord/manager/utils/Const.kt +++ b/app/src/main/kotlin/com/aliucord/manager/utils/Const.kt @@ -12,4 +12,8 @@ val httpClient = HttpClient(Android) { } val aliucordDir = File(Environment.getExternalStorageDirectory(), "Aliucord") val pluginsDir = File(aliucordDir, "plugins") -val gson = Gson() \ No newline at end of file +val gson = Gson() + +const val MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE" +const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android" +const val REQUEST_LEGACY_STORAGE = "requestLegacyExternalStorage" diff --git a/app/src/main/kotlin/com/aliucord/manager/utils/ManifestPatcher.kt b/app/src/main/kotlin/com/aliucord/manager/utils/ManifestPatcher.kt new file mode 100644 index 0000000..eecae73 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/utils/ManifestPatcher.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021 Juby210 + * Licensed under the Open Software License version 3.0 + */ + +package com.aliucord.manager.utils + +import com.aliucord.manager.preferences.Prefs +import pxb.android.axml.AxmlReader +import pxb.android.axml.AxmlVisitor +import pxb.android.axml.AxmlWriter +import pxb.android.axml.NodeVisitor + +fun patchManifest(manifestBytes: ByteArray): ByteArray { + val packageName = "com.aliucord" + val debuggable = Prefs.debuggable.get() + + val reader = AxmlReader(manifestBytes) + val writer = AxmlWriter() + reader.accept(object : AxmlVisitor(writer) { + override fun child(ns: String?, name: String?) = + object : ReplaceAttrsVisitor(super.child(ns, name), mapOf("package" to packageName)) { + private var addManagePerm = true + + override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { + super.attr(ns, name, resourceId, type, value) + if (value == MANAGE_EXTERNAL_STORAGE) addManagePerm = false + } + + override fun child(ns: String?, name: String): NodeVisitor { + val nv = super.child(ns, name) + return when (name) { + "uses-sdk" -> ReplaceAttrsVisitor(nv, mapOf("targetSdkVersion" to 29)) + "permission" -> ReplaceAttrsVisitor(nv, mapOf("name" to "$packageName.permission.CONNECT")) + "application" -> object : ReplaceAttrsVisitor(nv, mapOf(REQUEST_LEGACY_STORAGE to 1, "label" to "Aliucord", "debuggable" to debuggable)) { + private var addLegacyStorage = true + private var addDebuggable = debuggable + + override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { + super.attr(ns, name, resourceId, type, value) + if (name == REQUEST_LEGACY_STORAGE) addLegacyStorage = false + } + + override fun child(ns: String?, name: String): NodeVisitor { + val visitor = super.child(ns, name) + return when (name) { + "activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to "Aliucord")) + "service" -> object : NodeVisitor(visitor) { + override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { + super.attr(ns, name, resourceId, type, + if (value is String && value.endsWith("CONNECT")) "$packageName.permission.CONNECT" else value) + } + } + "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) + } + } + "meta-data" -> object : NodeVisitor(visitor) { + private var crashlytics = false + + override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { + if (name == "name" && value == "firebase_crashlytics_collection_enabled") crashlytics = true + super.attr(ns, name, resourceId, type, + if (name == "value" && crashlytics) 0 else value) + } + } + else -> visitor + } + } + + override fun end() { + if (addLegacyStorage) + super.attr(ANDROID_NAMESPACE, REQUEST_LEGACY_STORAGE, -1, TYPE_INT_BOOLEAN, 1) + if (addDebuggable) + super.attr(ANDROID_NAMESPACE, "debuggable", -1, TYPE_INT_BOOLEAN, 1) + super.end() + } + } + else -> nv + } + } + + override fun end() { + if (addManagePerm) super.child(null, "uses-permission").attr( + ANDROID_NAMESPACE, "name", -1, TYPE_STRING, MANAGE_EXTERNAL_STORAGE + ) + super.end() + } + } + }) + return writer.toByteArray() +} diff --git a/app/src/main/kotlin/com/aliucord/manager/utils/ReplaceAttrsVisitor.kt b/app/src/main/kotlin/com/aliucord/manager/utils/ReplaceAttrsVisitor.kt new file mode 100644 index 0000000..304cf2c --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/utils/ReplaceAttrsVisitor.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021 Juby210 + * Licensed under the Open Software License version 3.0 + */ + +package com.aliucord.manager.utils + +import pxb.android.axml.NodeVisitor + +open class ReplaceAttrsVisitor( + nv: NodeVisitor, + private val attrs: Map +) : NodeVisitor(nv) { + override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { + val replace = attrs.containsKey(name) + val newValue = attrs[name] + super.attr(ns, name, resourceId, if (newValue is String) TYPE_STRING else type, if (replace) newValue else value) + } +} diff --git a/build.gradle.kts b/build.gradle.kts index cc1bdf7..68dc499 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,8 @@ buildscript { } dependencies { - classpath("com.android.tools.build:gradle:7.1.2") + // please do not update to 7.1+, it breaks compatibility with arctic fox android studio + classpath("com.android.tools.build:gradle:7.0.4") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") // NOTE: Do not place your application dependencies here; they belong @@ -29,4 +30,4 @@ allprojects { tasks.register("clean") { delete(rootProject.buildDir) -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5500c88..1c566d4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 25 18:24:43 EST 2022 +#Sat Mar 12 19:39:38 CET 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME