diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a6c4bfc..6412ebf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,13 +1,12 @@ -val pagingVersion = "3.1.0" -val accompanistVersion = "0.23.0" -val composeVersion = "1.1.1" +val accompanistVersion = "0.24.7-alpha" +val composeVersion = "1.2.0-alpha08" val ktorVersion = "2.0.0" plugins { id("com.android.application") id("kotlin-android") - id("com.google.devtools.ksp") version "1.6.10-1.0.4" - kotlin("plugin.serialization") version "1.6.10" + id("com.google.devtools.ksp") version "1.6.20-1.0.4" + kotlin("plugin.serialization") version "1.6.20" } android { @@ -40,24 +39,21 @@ android { kotlinOptions { jvmTarget = "11" - freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn" + freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" + } + + applicationVariants.all { + kotlin.sourceSets { + getByName(name) { + kotlin.srcDir("build/generated/ksp/$name/kotlin") + } + } } buildFeatures.compose = true composeOptions.kotlinCompilerExtensionVersion = composeVersion } -kotlin { - sourceSets { - debug { - kotlin.srcDir("build/generated/ksp/debug/kotlin") - } - release { - kotlin.srcDir("build/generated/ksp/release/kotlin") - } - } -} - dependencies { implementation(fileTree("./libs")) @@ -65,12 +61,15 @@ dependencies { implementation("androidx.appcompat:appcompat:1.4.1") implementation("androidx.core:core-ktx:1.7.0") implementation("androidx.core:core-splashscreen:1.0.0-beta02") + implementation("com.android:zipflinger:7.3.0-alpha09") +// implementation("com.github.Aliucord:libzip:1.0.0") // compose dependencies implementation("androidx.compose.ui:ui:${composeVersion}") implementation("androidx.compose.ui:ui-tooling:${composeVersion}") implementation("androidx.compose.material:material:${composeVersion}") - implementation("androidx.compose.material3:material3:1.0.0-alpha09") + implementation("androidx.compose.material:material-icons-extended:${composeVersion}") + implementation("androidx.compose.material3:material3:1.0.0-alpha10") implementation("androidx.paging:paging-compose:1.0.0-alpha14") // accompanist dependencies @@ -78,11 +77,11 @@ dependencies { implementation("com.google.accompanist:accompanist-permissions:${accompanistVersion}") // compose destinations - implementation("io.github.raamcosta.compose-destinations:core:1.4.2-beta") - ksp("io.github.raamcosta.compose-destinations:ksp:1.4.2-beta") + implementation("io.github.raamcosta.compose-destinations:core:1.5.2-beta") + ksp("io.github.raamcosta.compose-destinations:ksp:1.5.2-beta") // other dependencies - implementation("io.coil-kt:coil-compose:1.4.0") + implementation("io.coil-kt:coil-compose:2.0.0-rc03") implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("de.upb.cs.swt:axml:2.1.1") // 2.1.2 is broken btw implementation("com.android.tools.build:apksig:7.1.3") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eedcb50..6544d1a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + + , LGPL3.0, see https://www.gnu.org/licenses/lgpl-3.0.en.html - -See https://github.com/Aliucord/hook diff --git a/app/src/main/assets/aliuhook/arm64-v8a/libaliuhook.so b/app/src/main/assets/aliuhook/arm64-v8a/libaliuhook.so deleted file mode 100644 index 34ec1a4..0000000 Binary files a/app/src/main/assets/aliuhook/arm64-v8a/libaliuhook.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/arm64-v8a/libc++_shared.so b/app/src/main/assets/aliuhook/arm64-v8a/libc++_shared.so deleted file mode 100644 index 6bdccac..0000000 Binary files a/app/src/main/assets/aliuhook/arm64-v8a/libc++_shared.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/arm64-v8a/liblsplant.so b/app/src/main/assets/aliuhook/arm64-v8a/liblsplant.so deleted file mode 100644 index d53ba87..0000000 Binary files a/app/src/main/assets/aliuhook/arm64-v8a/liblsplant.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/armeabi-v7a/libaliuhook.so b/app/src/main/assets/aliuhook/armeabi-v7a/libaliuhook.so deleted file mode 100644 index ad4d85b..0000000 Binary files a/app/src/main/assets/aliuhook/armeabi-v7a/libaliuhook.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/armeabi-v7a/libc++_shared.so b/app/src/main/assets/aliuhook/armeabi-v7a/libc++_shared.so deleted file mode 100644 index f633c88..0000000 Binary files a/app/src/main/assets/aliuhook/armeabi-v7a/libc++_shared.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/armeabi-v7a/liblsplant.so b/app/src/main/assets/aliuhook/armeabi-v7a/liblsplant.so deleted file mode 100644 index 1557380..0000000 Binary files a/app/src/main/assets/aliuhook/armeabi-v7a/liblsplant.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/classes.dex b/app/src/main/assets/aliuhook/classes.dex deleted file mode 100644 index 105afcf..0000000 Binary files a/app/src/main/assets/aliuhook/classes.dex and /dev/null differ diff --git a/app/src/main/assets/aliuhook/x86/libaliuhook.so b/app/src/main/assets/aliuhook/x86/libaliuhook.so deleted file mode 100644 index b91f567..0000000 Binary files a/app/src/main/assets/aliuhook/x86/libaliuhook.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/x86/libc++_shared.so b/app/src/main/assets/aliuhook/x86/libc++_shared.so deleted file mode 100644 index 99d0522..0000000 Binary files a/app/src/main/assets/aliuhook/x86/libc++_shared.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/x86/liblsplant.so b/app/src/main/assets/aliuhook/x86/liblsplant.so deleted file mode 100644 index 075dfae..0000000 Binary files a/app/src/main/assets/aliuhook/x86/liblsplant.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/x86_64/libaliuhook.so b/app/src/main/assets/aliuhook/x86_64/libaliuhook.so deleted file mode 100644 index c1fa2d5..0000000 Binary files a/app/src/main/assets/aliuhook/x86_64/libaliuhook.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/x86_64/libc++_shared.so b/app/src/main/assets/aliuhook/x86_64/libc++_shared.so deleted file mode 100644 index 0ca6a48..0000000 Binary files a/app/src/main/assets/aliuhook/x86_64/libc++_shared.so and /dev/null differ diff --git a/app/src/main/assets/aliuhook/x86_64/liblsplant.so b/app/src/main/assets/aliuhook/x86_64/liblsplant.so deleted file mode 100644 index 43fe031..0000000 Binary files a/app/src/main/assets/aliuhook/x86_64/liblsplant.so and /dev/null differ diff --git a/app/src/main/assets/kotlin/LICENSE.txt b/app/src/main/assets/kotlin/LICENSE.txt deleted file mode 100644 index 9c308d9..0000000 --- a/app/src/main/assets/kotlin/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2000-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/app/src/main/assets/kotlin/classes.dex b/app/src/main/assets/kotlin/classes.dex deleted file mode 100644 index add9e1b..0000000 Binary files a/app/src/main/assets/kotlin/classes.dex and /dev/null differ diff --git a/app/src/main/assets/lib/arm64-v8a/libc++_shared.so b/app/src/main/assets/lib/arm64-v8a/libc++_shared.so new file mode 100644 index 0000000..42b418c Binary files /dev/null and b/app/src/main/assets/lib/arm64-v8a/libc++_shared.so differ diff --git a/app/src/main/assets/lib/arm64-v8a/libhermes.so b/app/src/main/assets/lib/arm64-v8a/libhermes.so new file mode 100644 index 0000000..3ca47a9 Binary files /dev/null and b/app/src/main/assets/lib/arm64-v8a/libhermes.so differ diff --git a/app/src/main/assets/lib/armeabi-v7a/libc++_shared.so b/app/src/main/assets/lib/armeabi-v7a/libc++_shared.so new file mode 100644 index 0000000..906f251 Binary files /dev/null and b/app/src/main/assets/lib/armeabi-v7a/libc++_shared.so differ diff --git a/app/src/main/assets/lib/armeabi-v7a/libhermes.so b/app/src/main/assets/lib/armeabi-v7a/libhermes.so new file mode 100644 index 0000000..72a0e8f Binary files /dev/null and b/app/src/main/assets/lib/armeabi-v7a/libhermes.so differ diff --git a/app/src/main/assets/lib/x86/libc++_shared.so b/app/src/main/assets/lib/x86/libc++_shared.so new file mode 100644 index 0000000..379ead8 Binary files /dev/null and b/app/src/main/assets/lib/x86/libc++_shared.so differ diff --git a/app/src/main/assets/lib/x86/libhermes.so b/app/src/main/assets/lib/x86/libhermes.so new file mode 100644 index 0000000..ac63754 Binary files /dev/null and b/app/src/main/assets/lib/x86/libhermes.so differ diff --git a/app/src/main/assets/lib/x86_64/libc++_shared.so b/app/src/main/assets/lib/x86_64/libc++_shared.so new file mode 100644 index 0000000..e37b298 Binary files /dev/null and b/app/src/main/assets/lib/x86_64/libc++_shared.so differ diff --git a/app/src/main/assets/lib/x86_64/libhermes.so b/app/src/main/assets/lib/x86_64/libhermes.so new file mode 100644 index 0000000..f6b0320 Binary files /dev/null and b/app/src/main/assets/lib/x86_64/libhermes.so differ diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt b/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt new file mode 100644 index 0000000..e232f02 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt @@ -0,0 +1,41 @@ +package com.aliucord.manager.installer.service + +import android.app.Service +import android.content.Intent +import android.content.pm.PackageInstaller +import android.os.IBinder +import android.util.Log +import android.widget.Toast + +class InstallService : Service() { + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + when (val statusCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999)) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + val confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT) + confirmationIntent!!.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + startActivity(confirmationIntent) + } + PackageInstaller.STATUS_SUCCESS -> Toast.makeText( + this, + "Successfully installed Aliucord!", + Toast.LENGTH_LONG + ).show() + PackageInstaller.STATUS_FAILURE_ABORTED -> Toast.makeText( + this, + "Aborted installation", + Toast.LENGTH_LONG + ).show() + else -> { + Log.i("Installer", "Installation failed: error code $statusCode") + Toast.makeText(this, "Failed: Error $statusCode", Toast.LENGTH_LONG).show() + } + } + + stopSelf() + + return START_NOT_STICKY + } + + override fun onBind(intent: Intent): IBinder? = null +} diff --git a/app/src/main/kotlin/com/aliucord/manager/utils/DownloadUtils.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/DownloadUtils.kt similarity index 68% rename from app/src/main/kotlin/com/aliucord/manager/utils/DownloadUtils.kt rename to app/src/main/kotlin/com/aliucord/manager/installer/util/DownloadUtils.kt index dbdd320..6855bd4 100644 --- a/app/src/main/kotlin/com/aliucord/manager/utils/DownloadUtils.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/util/DownloadUtils.kt @@ -3,7 +3,7 @@ * Licensed under the Open Software License version 3.0 */ -package com.aliucord.manager.utils +package com.aliucord.manager.installer.util import android.app.DownloadManager import android.content.* @@ -13,24 +13,13 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine object DownloadUtils { - private const val org = "Aliucord" - private const val repo = "Aliucord" - private const val backendHost = "https://aliucord.com/" - private const val contentUrl = "https://raw.githubusercontent.com/${org}/${repo}" suspend fun downloadDiscordApk(ctx: Context, version: String) = - download(ctx, "$backendHost/download/discord?v=$version", "discord-$version.apk") + download(ctx, "$backendHost/download/discord?v=$version", "base-$version.apk") - suspend fun downloadManifest(ctx: Context, useDebuggableManifest: Boolean) { - if (useDebuggableManifest) - download(ctx, "$contentUrl/main/.assets/AndroidManifest-debuggable.xml", "AndroidManifest-debuggable.xml") - else { - download(ctx, "$contentUrl/main/.assets/AndroidManifest.xml", "AndroidManifest.xml") - } - } - - suspend fun downloadInjector(ctx: Context) = download(ctx, "$contentUrl/builds/Injector.dex", "Injector.dex") + suspend fun downloadSplit(ctx: Context, version: String, split: String) = + download(ctx, "$backendHost/download/discord?v=$version&split=$split", "$split-$version.apk") private suspend fun download(ctx: Context, url: String, fileName: String): File { val downloadManager = ctx.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager @@ -52,7 +41,7 @@ object DownloadUtils { ctx.registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) receiver.downloadId = DownloadManager.Request(url.toUri()).run { - setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) + setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) setDestinationUri(ctx.externalCacheDir!!.resolve(fileName).toUri()) downloadManager.enqueue(this) } diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt new file mode 100644 index 0000000..518b11d --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2022 Juby210 & zt + * Licensed under the Open Software License version 3.0 + */ + +package com.aliucord.manager.installer.util + +import com.aliucord.manager.preferences.Prefs +import pxb.android.axml.* + +object ManifestPatcher { + private const val MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE" + private const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android" + private const val USES_CLEARTEXT_TRAFFIC = "usesCleartextTraffic" + private const val DEBUGGABLE = "debuggable" + private const val LABEL = "label" + + fun patchManifest(manifestBytes: ByteArray): ByteArray { + val packageName = Prefs.packageName.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) { + "application" -> object : ReplaceAttrsVisitor( + nv, + mapOf( + LABEL to Prefs.appName.get(), + DEBUGGABLE to Prefs.debuggable.get(), + USES_CLEARTEXT_TRAFFIC to true + ) + ) { + private var addDebuggable = Prefs.debuggable.get() + private var addUseClearTextTraffic = true + + override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { + super.attr(ns, name, resourceId, type, value) + if (name == DEBUGGABLE) addDebuggable = false + if (name == USES_CLEARTEXT_TRAFFIC) addUseClearTextTraffic = false + } + + override fun child(ns: String?, name: String): NodeVisitor { + val visitor = super.child(ns, name) + + return when (name) { + "activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to Prefs.appName.get())) + "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 + } + } + + override fun end() { + if (addDebuggable) super.attr(ANDROID_NAMESPACE, DEBUGGABLE, -1, TYPE_INT_BOOLEAN, 1) + if (addUseClearTextTraffic) super.attr(ANDROID_NAMESPACE, USES_CLEARTEXT_TRAFFIC, -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() + } + + fun renamePackage(manifestBytes: ByteArray): ByteArray { + val packageName = Prefs.packageName.get() + val reader = AxmlReader(manifestBytes) + val writer = AxmlWriter() + + reader.accept(object : AxmlVisitor(writer) { + override fun child(ns: String?, name: String?): ReplaceAttrsVisitor { + return ReplaceAttrsVisitor(super.child(ns, name), mapOf("package" to packageName)) + } + }) + + return writer.toByteArray() + } + + private 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/app/src/main/kotlin/com/aliucord/manager/models/Plugin.kt b/app/src/main/kotlin/com/aliucord/manager/models/Plugin.kt index ad13d86..9153204 100644 --- a/app/src/main/kotlin/com/aliucord/manager/models/Plugin.kt +++ b/app/src/main/kotlin/com/aliucord/manager/models/Plugin.kt @@ -7,8 +7,8 @@ package com.aliucord.manager.models import android.util.Log import com.aliucord.manager.BuildConfig +import com.aliucord.manager.utils.aliucordDir import com.aliucord.manager.utils.json -import com.aliucord.manager.utils.pluginsDir import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.decodeFromStream import java.io.File @@ -29,6 +29,8 @@ data class Plugin(val file: File) { } companion object { + private val pluginsDir = aliucordDir.resolve("plugins") + fun loadAll(): List { if (!pluginsDir.exists() && !pluginsDir.mkdirs()) { Log.e(BuildConfig.TAG, "Failed to create plugins dir. Missing Permissions?") diff --git a/app/src/main/kotlin/com/aliucord/manager/models/github/Commit.kt b/app/src/main/kotlin/com/aliucord/manager/models/github/Commit.kt index d70aa23..96ec796 100644 --- a/app/src/main/kotlin/com/aliucord/manager/models/github/Commit.kt +++ b/app/src/main/kotlin/com/aliucord/manager/models/github/Commit.kt @@ -10,7 +10,8 @@ import kotlinx.serialization.Serializable @Serializable data class Commit( - @SerialName("html_url") val htmlUrl: String, + @SerialName("html_url") + val htmlUrl: String, val sha: String, val commit: Commit, val author: Author diff --git a/app/src/main/kotlin/com/aliucord/manager/models/github/GithubUser.kt b/app/src/main/kotlin/com/aliucord/manager/models/github/GithubUser.kt index ee6f2be..a9701f3 100644 --- a/app/src/main/kotlin/com/aliucord/manager/models/github/GithubUser.kt +++ b/app/src/main/kotlin/com/aliucord/manager/models/github/GithubUser.kt @@ -10,6 +10,7 @@ import kotlinx.serialization.Serializable @Serializable data class GithubUser( - @SerialName("login") val name: String, + @SerialName("login") + val name: String, val contributions: Int ) diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/ManagerScaffold.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/ManagerScaffold.kt index 28bc7ea..81a6a22 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/ManagerScaffold.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/ManagerScaffold.kt @@ -30,22 +30,22 @@ fun ManagerScaffold() { val storagePermissionState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) - Scaffold( - topBar = { if (storagePermissionState.hasPermission) AppBar(navController) } - ) { paddingValues -> - PermissionRequired( - permissionState = storagePermissionState, - permissionNotAvailableContent = { GrantPermission(storagePermissionState) }, - permissionNotGrantedContent = { GrantPermission(storagePermissionState) } - ) { - DestinationsNavHost( - modifier = Modifier - .padding(paddingValues) - .padding(horizontal = 12.dp, vertical = 14.dp), - navGraph = NavGraphs.root, - navController = navController, - engine = rememberNavHostEngine() - ) + when (storagePermissionState.status) { + is PermissionStatus.Denied -> GrantPermission(storagePermissionState) + PermissionStatus.Granted -> { + Scaffold( + topBar = { AppBar(navController) } + ) { paddingValues -> + DestinationsNavHost( + modifier = Modifier + .padding(paddingValues) + .padding(horizontal = 12.dp, vertical = 14.dp), + navGraph = NavGraphs.root, + navController = navController, + engine = rememberNavHostEngine() + ) + } } } + } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/PluginsList.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/PluginsList.kt index ca86335..64bf98e 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/PluginsList.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/PluginsList.kt @@ -83,9 +83,7 @@ fun PluginsList() = Column( }, dismissButton = { Button( - onClick = { - showDeleteDialog = false - }, + onClick = { showDeleteDialog = false }, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondaryContainer, contentColor = MaterialTheme.colorScheme.onSecondaryContainer diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/about/ContributorEntry.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/about/ContributorEntry.kt index 7af4779..a601a30 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/about/ContributorEntry.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/about/ContributorEntry.kt @@ -5,32 +5,27 @@ package com.aliucord.manager.ui.components.about -import androidx.compose.foundation.Image import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import coil.compose.rememberImagePainter -import coil.transform.CircleCropTransformation +import coil.compose.AsyncImage import com.aliucord.manager.R @Composable fun ContributorEntry(name: String) { val uriHandler = LocalUriHandler.current - Image( + AsyncImage( modifier = Modifier - .size(48.dp) + .fillMaxSize() + .clip(CircleShape) .clickable { uriHandler.openUri("https://github.com/$name") }, - painter = rememberImagePainter( - data = "https://github.com/$name.png", - builder = { - transformations(CircleCropTransformation()) - } - ), + model = "https://github.com/$name.png", contentDescription = stringResource(R.string.contributor) ) } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/about/ContributorsCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/about/ContributorsCard.kt index dc07817..123a982 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/about/ContributorsCard.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/about/ContributorsCard.kt @@ -7,7 +7,7 @@ package com.aliucord.manager.ui.components.about import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.* +import androidx.compose.foundation.lazy.grid.* import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -19,35 +19,42 @@ import com.aliucord.manager.models.github.GithubUser @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable -fun ContributorsCard( - contributors: List, -) = ElevatedCard( - modifier = Modifier.wrapContentHeight().fillMaxWidth() -) { - Column( - modifier = Modifier.padding(16.dp).fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) +fun ContributorsCard(contributors: List) { + ElevatedCard( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() ) { - Text( - text = stringResource(R.string.contributors), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary - ) - - if (contributors.isNotEmpty()) { - LazyVerticalGrid( - cells = GridCells.Adaptive(48.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - items(contributors) { user -> ContributorEntry(user.name) } - } - } else { - Box( - modifier = Modifier.fillMaxWidth().padding(12.dp), - contentAlignment = Alignment.Center, - content = { CircularProgressIndicator() } + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = stringResource(R.string.contributors), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary ) + + if (contributors.isNotEmpty()) { + LazyVerticalGrid( + columns = GridCells.Adaptive(48.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + userScrollEnabled = false + ) { + items(contributors) { user -> ContributorEntry(user.name) } + } + } else { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + contentAlignment = Alignment.Center, + content = { CircularProgressIndicator() } + ) + } } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/about/TeamCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/about/TeamCard.kt index 45145c2..a5e3fed 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/about/TeamCard.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/about/TeamCard.kt @@ -33,7 +33,6 @@ fun TeamCard() = ElevatedCard( ) { UserEntry("Juby210", "Fox") UserEntry("Vendicated", "the ven") - UserEntry("mwittrien", "your memory gon") } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/Changelog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/Changelog.kt index af4e97e..697f294 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/Changelog.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/plugins/Changelog.kt @@ -6,14 +6,19 @@ package com.aliucord.manager.ui.components.plugins import android.annotation.SuppressLint -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.ClickableText +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Person import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -21,8 +26,8 @@ import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp -import coil.compose.rememberImagePainter -import coil.transform.RoundedCornersTransformation +import coil.compose.AsyncImage +import coil.request.ImageRequest import com.aliucord.manager.R import com.aliucord.manager.models.Plugin @@ -81,11 +86,15 @@ fun Changelog( text = { Column { plugin.manifest.changelogMedia?.let { mediaUrl -> - Image( - modifier = Modifier.fillMaxWidth().heightIn(max = 90.dp), - painter = rememberImagePainter(mediaUrl) { - transformations(RoundedCornersTransformation(14.dp.value)) - }, + AsyncImage( + modifier = Modifier + .fillMaxWidth() + .heightIn(max = 90.dp) + .clip(RoundedCornerShape(14.dp)), + model = ImageRequest.Builder(LocalContext.current) + .data(mediaUrl) + .build(), + placeholder = rememberVectorPainter(image = Icons.Default.Person), contentDescription = stringResource(R.string.changelog_media) ) } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/ThemeDialog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/ThemeDialog.kt index 0ddce97..a98125c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/ThemeDialog.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/settings/ThemeDialog.kt @@ -7,6 +7,8 @@ package com.aliucord.manager.ui.components.settings import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Style import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -26,6 +28,12 @@ fun ThemeDialog( AlertDialog( onDismissRequest = onDismissRequest, + icon = { + Icon( + imageVector = Icons.Default.Style, + contentDescription = "Theme" + ) + }, title = { Text(stringResource(R.string.theme)) }, text = { Column { diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/AboutScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/AboutScreen.kt index 5cff35e..48b934c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/AboutScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/AboutScreen.kt @@ -25,6 +25,7 @@ import com.ramcosta.composedestinations.annotation.Destination fun AboutScreen() { val uriHandler = LocalUriHandler.current val viewModel: AboutViewModel = viewModel() + Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { @@ -42,7 +43,7 @@ fun AboutScreen() { contentDescription = "GitHub" ) Spacer(Modifier.padding(4.dp)) - Text(stringResource(R.string.source_code)) + Text(stringResource(R.string.github)) } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/CommitsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/CommitsScreen.kt index 549261c..66661f1 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/CommitsScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/CommitsScreen.kt @@ -77,14 +77,12 @@ fun CommitsScreen() { } items(lazyPagingItems) { commitData -> - val localUriHandler = LocalUriHandler.current - if (commitData == null) return@items + val localUriHandler = LocalUriHandler.current + ListItem( - modifier = Modifier.clickable { - localUriHandler.openUri(commitData.htmlUrl) - }, + modifier = Modifier.clickable { localUriHandler.openUri(commitData.htmlUrl) }, overlineText = { Text(commitData.sha.substring(0, 7)) }, text = { Text("${commitData.commit.message.split("\n").first()} - ${commitData.author.name}") } ) diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/HomeScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/HomeScreen.kt index e1fea42..d79e014 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/HomeScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/HomeScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.core.net.toFile import androidx.lifecycle.viewmodel.compose.viewModel import com.aliucord.manager.R import com.aliucord.manager.preferences.Prefs @@ -31,10 +30,12 @@ import com.aliucord.manager.ui.screens.destinations.CommitsScreenDestination import com.aliucord.manager.ui.screens.destinations.InstallerScreenDestination import com.aliucord.manager.ui.viewmodels.HomeViewModel import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootNavGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator @OptIn(ExperimentalMaterial3Api::class) -@Destination(start = true) +@RootNavGraph(start = true) +@Destination @Composable fun HomeScreen(navigator: DestinationsNavigator) { val viewModel: HomeViewModel = viewModel() @@ -45,7 +46,7 @@ fun HomeScreen(navigator: DestinationsNavigator) { val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> if (uri == null) return@rememberLauncherForActivityResult - navigator.navigate(InstallerScreenDestination(uri.toFile())) + navigator.navigate(InstallerScreenDestination) } InstallerDialog( 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 a2b6247..f73d264 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 @@ -9,7 +9,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.* @@ -20,21 +19,18 @@ import com.aliucord.manager.ui.screens.destinations.HomeScreenDestination import com.aliucord.manager.ui.viewmodels.InstallViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import java.io.File @OptIn(ExperimentalFoundationApi::class) @Destination @Composable -fun InstallerScreen(navigator: DestinationsNavigator, apk: File?) { +fun InstallerScreen(navigator: DestinationsNavigator) { val viewModel: InstallViewModel = viewModel() val navigateMain by viewModel.returnToHome.collectAsState(initial = false) - if (navigateMain) { - navigator.navigate(HomeScreenDestination) - } + if (navigateMain) navigator.navigate(HomeScreenDestination) LaunchedEffect(Unit) { - viewModel.startInstallation(apk) + viewModel.startInstallation() } LazyColumn( @@ -45,9 +41,9 @@ fun InstallerScreen(navigator: DestinationsNavigator, apk: File?) { modifier = Modifier.fillParentMaxWidth() ) } - items(viewModel.logs) { log -> + item { Text( - text = log, + text = viewModel.log, modifier = Modifier.padding(8.dp) ) } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/SettingsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/SettingsScreen.kt index c16ef05..b880617 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/SettingsScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/SettingsScreen.kt @@ -5,25 +5,21 @@ package com.aliucord.manager.ui.screens -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.material.ContentAlpha +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.aliucord.manager.R import com.aliucord.manager.preferences.Prefs -import com.aliucord.manager.ui.components.ListItem import com.aliucord.manager.ui.components.settings.* import com.aliucord.manager.ui.viewmodels.SettingsViewModel import com.ramcosta.composedestinations.annotation.Destination @@ -102,32 +98,6 @@ fun SettingsScreen() = Column( description = { Text(stringResource(R.string.debuggable_description)) }, preference = Prefs.debuggable ) - - val udfs = Prefs.useDexFromStorage - - PreferenceItem( - title = { Text(stringResource(R.string.use_dex_from_storage)) }, - preference = Prefs.useDexFromStorage - ) - - val picker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> - if (uri != null) Prefs.dexLocation.set(uri.path!!) - } - - val color = if (udfs.get()) { - Color.Unspecified - } else { - MaterialTheme.colorScheme.onBackground.copy(ContentAlpha.disabled) - } - - // TODO: Make installer use selected dex file - ListItem( - modifier = if (udfs.get()) Modifier.clickable { - picker.launch(arrayOf("application/octet-stream")) - } else Modifier, - text = { Text(stringResource(R.string.dex_location), color = color) }, - secondaryText = { Text(Prefs.dexLocation.get(), color = color) } - ) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt b/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt index 7704053..5c1c640 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/theme/Theme.kt @@ -60,7 +60,9 @@ fun ManagerTheme( ) } -enum class Theme(val displayName: String) { +enum class Theme( + val displayName: String +) { SYSTEM("System"), LIGHT("Light"), DARK("Dark"); diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodels/HomeViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodels/HomeViewModel.kt index 10a1659..030d8a5 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodels/HomeViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodels/HomeViewModel.kt @@ -12,12 +12,10 @@ import com.aliucord.manager.preferences.Prefs import com.aliucord.manager.utils.* import io.ktor.client.request.* import io.ktor.client.statement.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import kotlinx.serialization.decodeFromString class HomeViewModel(application: Application) : AndroidViewModel(application) { - private val packageManager = getApplication().packageManager var supportedVersion by mutableStateOf("") @@ -26,20 +24,22 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { var installedVersion by mutableStateOf("-") private set - fun load() { + private fun load() { viewModelScope.launch(Dispatchers.IO) { val version = json.decodeFromString(httpClient.get(Github.dataUrl).bodyAsText()) - supportedVersion = "${version.versionName} - " + when (version.versionCode[3]) { - '0' -> "Stable" - '1' -> "Beta" - '2' -> "Alpha" - else -> throw NoWhenBranchMatchedException() - } - installedVersion = try { - packageManager.getPackageInfo(Prefs.packageName.get(), 0).versionName - } catch (th: Throwable) { - "-" + withContext(Dispatchers.Main) { + supportedVersion = "${version.versionName} - " + when (version.versionCode[3]) { + '0' -> "Stable" + '1' -> "Beta" + '2' -> "Alpha" + else -> throw NoWhenBranchMatchedException() + } + installedVersion = try { + packageManager.getPackageInfo(Prefs.packageName.get(), 0).versionName + } catch (th: Throwable) { + "-" + } } } } @@ -63,5 +63,4 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { init { load() } - } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodels/InstallViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodels/InstallViewModel.kt index fdd0cb8..bae4aad 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodels/InstallViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodels/InstallViewModel.kt @@ -1,200 +1,178 @@ package com.aliucord.manager.ui.viewmodels +import android.annotation.SuppressLint import android.app.Application +import android.app.PendingIntent import android.content.Intent -import androidx.compose.runtime.mutableStateListOf -import androidx.core.content.FileProvider +import android.content.pm.PackageInstaller +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.runtime.* import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.aliucord.libzip.Zip -import com.aliucord.manager.BuildConfig -import com.aliucord.manager.preferences.Prefs -import com.aliucord.manager.utils.* +import com.aliucord.manager.installer.service.InstallService +import com.aliucord.manager.installer.util.DownloadUtils +import com.aliucord.manager.installer.util.ManifestPatcher +import com.aliucord.manager.utils.Signer +import com.android.zipflinger.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import java.io.File -import java.io.FileNotFoundException +import java.util.zip.Deflater class InstallViewModel(application: Application) : AndroidViewModel(application) { - private val _returnToHome = MutableSharedFlow() val returnToHome = _returnToHome.asSharedFlow() private var installationRunning: Boolean = false - val logs = mutableStateListOf() + var log by mutableStateOf("") + private set - fun startInstallation(apk: File?) { + @RequiresApi(Build.VERSION_CODES.O) + fun startInstallation() { if (installationRunning) return viewModelScope.launch(Dispatchers.IO) { installationRunning = true - val supportedVersion = Github.version.versionCode - val discordApk = apk ?: run { - logs.add("Checking for cached APK...") + val context = getApplication().applicationContext + val assetManager = context.assets + val externalCacheDir = context.externalCacheDir!! + val arch = Build.SUPPORTED_ABIS.first() + val supportedVersion = "124206" - getApplication().externalCacheDir!!.resolve("discord-${supportedVersion}.apk").also { file -> - val archiveInfo = getApplication().packageManager.getPackageArchiveInfo(file.path, 0) + val baseApk = externalCacheDir.resolve("base-${supportedVersion}.apk").also { file -> + val archiveInfo = context.packageManager.getPackageArchiveInfo(file.path, 0) - if (archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@also + if (archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@also run { log += "Using cached base APK\n"} - logs.add("Downloading Discord APK...") + log += "Downloading Discord APK... " - DownloadUtils.downloadDiscordApk(getApplication(), supportedVersion) + DownloadUtils.downloadDiscordApk(context, supportedVersion) - logs.add("Done") + log += "Done\n" + } + + val libArch = arch.replace("-v", "_v") + val libsApk = externalCacheDir.resolve("config.$libArch-${supportedVersion}.apk").also { file -> + if (file.exists()) return@also run { log += "Used cached libs APK\n" } + + log += "Downloading libs APK... " + + DownloadUtils.downloadSplit(context, supportedVersion, "config.$libArch") + + log += "Done\n" + } + + val localeApk = externalCacheDir.resolve("config.en-${supportedVersion}.apk").also { file -> + if (file.exists()) return@also run { log += "Using cached locale APK\n" } + + log += "Downloading locale APK... " + + DownloadUtils.downloadSplit(context, supportedVersion, "config.en") + + log += "Done\n" + } + + val xxhdpiApk = externalCacheDir.resolve("config.xxhdpi-${supportedVersion}.apk").also { file -> + if (file.exists()) return@also run { log += "Using cached drawables APK\n"} + + log += "Downloading drawables APK... " + + DownloadUtils.downloadSplit(context, supportedVersion, "config.xxhdpi") + + log += "Done\n" + } + + val apks = listOf(baseApk, libsApk, localeApk, xxhdpiApk) + + log += "Patching manifest...\n" + + try { + val byteArray = ZipRepo(baseApk.absolutePath) + .use { zip -> zip.getContent("AndroidManifest.xml") } + .array() + + ZipArchive(baseApk.toPath()).use { zip -> + val source = BytesSource(ManifestPatcher.patchManifest(byteArray), "AndroidManifest.xml", Deflater.DEFAULT_COMPRESSION) + + zip.delete("AndroidManifest.xml") + zip.add(source) } - } - val injector = getApplication().externalCacheDir!!.resolve("Injector.dex").also { file -> - if (file.exists()) return@also + apks.forEach { apk -> + val byteArray = ZipRepo(apk.absolutePath) + .use { zip -> zip.getContent("AndroidManifest.xml") } + .array() - logs.add("Downloading injector...") + ZipArchive(apk.toPath()).use { zip -> + val source = BytesSource(ManifestPatcher.renamePackage(byteArray), "AndroidManifest.xml", Deflater.DEFAULT_COMPRESSION) - DownloadUtils.downloadInjector(getApplication()) - - logs.add("Done") - } - - val outputDir = aliucordDir.also { - if (!it.exists() && !it.mkdirs()) throw FileNotFoundException() - } - - val outputApk = outputDir.resolve("Aliucord.apk") - discordApk.copyTo(outputApk, true) - - logs.add("Repacking APK") - - var patched = false - var manifestBytes: ByteArray? - - with(Zip(outputApk.absolutePath, 6, 'r')) { - for (index in 0 until totalEntries) { - openEntryByIndex(index) - val name = entryName - closeEntry() - if (name == "classes5.dex") { - patched = true - break + zip.delete("AndroidManifest.xml") + zip.add(source) } } - - val cacheDir = getApplication().cacheDir.absolutePath - - if (!patched) (1..3).forEach { i -> - openEntry("classes${if (i == 1) "" else i}.dex") - extractEntry("$cacheDir/classes${i + 1}.dex") - closeEntry() - } - - openEntry("AndroidManifest.xml") - manifestBytes = readEntry() - closeEntry() - - close() + } catch (e: Exception) { + log += "An error has occurred" + e.printStackTrace() } - val assetManager = getApplication().assets + log += "Patching libraries...\n" - with(Zip(outputApk.absolutePath, 6, 'a')) { - if (patched) { - deleteEntry("classes.dex") - deleteEntry("classes5.dex") - deleteEntry("classes6.dex") - } else { - (1..3).forEach { i -> deleteEntry("classes${if (i == 1) "" else i}.dex") } - } + ZipArchive(libsApk.toPath()).use { zip -> + val libs = listOf("libhermes.so", "libc++_shared.so") - deleteEntry("AndroidManifest.xml") - - listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64").forEach { arch -> - listOf("/libaliuhook.so", "/liblsplant.so", "/libc++_shared.so").forEach { file -> - deleteEntry("lib/$arch$file") + libs.forEach { lib -> zip.delete("lib/$arch/$lib") } + libs.forEach { lib -> + val source = BytesSource(assetManager.open("lib/$arch/$lib"), "lib/$arch/$lib", Deflater.NO_COMPRESSION).apply { + align(4096) } - } - if (!patched) for (i in 2..4) { - val name = "classes$i.dex" - val cacheFile = getApplication().cacheDir.resolve(name) - - openEntry(name) - compressFile(cacheFile.absolutePath) - closeEntry() - cacheFile.delete() - } - - openEntry("classes.dex") - compressFile(injector.absolutePath) - closeEntry() - - writeEntry("classes5.dex", assetManager.open("aliuhook/classes.dex")) - - listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64").forEach { arch -> - listOf("/libaliuhook.so", "/liblsplant.so", "/libc++_shared.so").forEach { file -> - writeEntry("lib/$arch$file", assetManager.open("aliuhook/$arch$file")) - } - } - - writeEntry("classes6.dex", assetManager.open("kotlin/classes.dex")) - - manifestBytes?.let { bytes -> - logs.add("Patching manifest") - val newManifestBytes = patchManifest(bytes) - openEntry("AndroidManifest.xml") - writeEntry(newManifestBytes, newManifestBytes.size.toLong()) - closeEntry() - } - - close() - } - - if (Prefs.replaceBg.get()) { - logs.add("Replacing icon background") - - val icon1Bytes = assetManager.open("icon1.png").readBytes() - val icon2Bytes = assetManager.open("icon2.png").readBytes() - - // use androguard to figure out entries - // androguard arsc resources.arsc --id 0x7f0f0000 (icon1) - // androguard arsc resources.arsc --id 0x7f0f0002 and androguard arsc resources.arsc --id 0x7f0f0006 (icon2) - val icon1Entries = listOf("MbV.png", "kbF.png", "_eu.png", "EtS.png") - val icon2Entries = - listOf("_h_.png", "9MB.png", "Dy7.png", "kC0.png", "oEH.png", "RG0.png", "ud_.png", "W_3.png") - - with(Zip(outputApk.absolutePath, 0, 'a')) { - icon1Entries.forEach { entry -> deleteEntry("res/$entry") } - icon2Entries.forEach { entry -> deleteEntry("res/$entry") } - - icon1Entries.forEach { entry -> writeEntry("res/$entry", icon1Bytes) } - icon2Entries.forEach { entry -> writeEntry("res/$entry", icon2Bytes) } - - close() + zip.add(source) } } - logs.add("Signing APK...") + log += "Signing APKs... " - Signer.signApk(outputApk) + apks.forEach(Signer::signApk) - logs.add("Signed APK") + log += "Done\n" - val intent = Intent(Intent.ACTION_VIEW).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - - setDataAndType( - FileProvider.getUriForFile(getApplication(), "${BuildConfig.APPLICATION_ID}.provider", outputApk), - "application/vnd.android.package-archive" - ) - } - - getApplication().startActivity(intent) + installApks(*apks.toTypedArray()) _returnToHome.emit(true) installationRunning = false } } + private fun installApks(vararg apks: File) { + val context = getApplication().applicationContext + val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + val packageInstaller = context.packageManager.packageInstaller + val sessionId = packageInstaller.createSession(params) + val session = packageInstaller.openSession(sessionId) + + apks.forEach { apk -> + session.openWrite(apk.name, 0, apk.length()).use { + it.write(apk.readBytes()) + + session.fsync(it) + } + } + + val callbackIntent = Intent(context, InstallService::class.java) + + @SuppressLint("UnspecifiedImmutableFlag") + val contentIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.getService(context, 0, callbackIntent, PendingIntent.FLAG_MUTABLE) + } else { + PendingIntent.getService(context, 0, callbackIntent, 0) + } + + session.commit(contentIntent.intentSender) + session.close() + } } 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 4e1f7cb..61c841e 100644 --- a/app/src/main/kotlin/com/aliucord/manager/utils/Const.kt +++ b/app/src/main/kotlin/com/aliucord/manager/utils/Const.kt @@ -11,11 +11,6 @@ import io.ktor.client.plugins.contentnegotiation.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json -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" -const val DEBUGGABLE = "debuggable" - val json = Json { prettyPrint = true ignoreUnknownKeys = true @@ -27,4 +22,3 @@ val httpClient = HttpClient { } } val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord") -val pluginsDir = aliucordDir.resolve("plugins") diff --git a/app/src/main/kotlin/com/aliucord/manager/utils/ManifestPatcher.kt b/app/src/main/kotlin/com/aliucord/manager/utils/ManifestPatcher.kt deleted file mode 100644 index 290a208..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/utils/ManifestPatcher.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2022 Juby210 & zt - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.utils - -import com.aliucord.manager.preferences.Prefs -import pxb.android.axml.* - -fun patchManifest(manifestBytes: ByteArray): ByteArray { - val packageName = Prefs.packageName.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 Prefs.appName.get(), DEBUGGABLE to Prefs.debuggable.get()) - ) { - private var addLegacyStorage = true - private var addDebuggable = Prefs.debuggable.get() - - 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 - else if (name == DEBUGGABLE) addDebuggable = false - } - - override fun child(ns: String?, name: String): NodeVisitor { - val visitor = super.child(ns, name) - return when (name) { - "activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to Prefs.appName.get())) - "service" -> object : NodeVisitor(visitor) { - private var disableService = false - - override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) { - if (name == "name" && ( - value == "com.google.android.gms.analytics.AnalyticsService" || - value == "com.google.android.gms.analytics.AnalyticsJobService") - ) disableService = true - super.attr( - ns, name, resourceId, type, - if (value is String && value.endsWith("CONNECT")) - "$packageName.permission.CONNECT" - else if (disableService && name == "enabled") 0 - 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 deleted file mode 100644 index a076ee3..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/utils/ReplaceAttrsVisitor.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2022 Juby210 & zt - * 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/app/src/main/kotlin/com/aliucord/manager/utils/Signer.kt b/app/src/main/kotlin/com/aliucord/manager/utils/Signer.kt index cfb613e..b9a5bdb 100644 --- a/app/src/main/kotlin/com/aliucord/manager/utils/Signer.kt +++ b/app/src/main/kotlin/com/aliucord/manager/utils/Signer.kt @@ -76,13 +76,12 @@ object Signer { } fun signApk(apkFile: File) { - val outputApk = aliucordDir.resolve("Aliucord-Signed.apk") + val outputApk = aliucordDir.resolve(apkFile.name) ApkSigner.Builder(listOf(signerConfig)) .setV1SigningEnabled(false) .setV2SigningEnabled(true) .setV3SigningEnabled(false) - .setOtherSignersSignaturesPreserved(false) .setInputApk(apkFile) .setOutputApk(outputApk) .build() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6daa6c7..901785e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,7 +10,7 @@ Settings Show menu Theme - Use Aliucord.dex from storage + Use Injector.dex from storage Plugins Uninstall %1$s Search @@ -25,12 +25,12 @@ Update Developer Mode Debuggable - Use debuggable manifest + Enable debuggable flag About Team Contributors Search Plugins - Source Code + GitHub Installer Use black Use black colors for dark mode diff --git a/build.gradle.kts b/build.gradle.kts index c08ff27..49d45e0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ buildscript { dependencies { classpath("com.android.tools.build:gradle:7.1.3") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle.kts files