From 6965f3128b5236e4434e7c23e8ea3a4ac4ecd382 Mon Sep 17 00:00:00 2001 From: rushii <33725716+DiamondMiner88@users.noreply.github.com> Date: Sat, 22 Oct 2022 12:02:48 -0700 Subject: [PATCH] feat: replace zipflinger with libziprs (#29) --- app/build.gradle.kts | 7 +- .../aliucord/manager/installer/util/Signer.kt | 4 +- .../manager/ui/viewmodel/InstallViewModel.kt | 135 ++++++++---------- 3 files changed, 63 insertions(+), 83 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 82fe8a5..f11d363 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -68,7 +68,6 @@ android { } dependencies { - // AndroidX implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.core:core-splashscreen:1.0.0") @@ -80,7 +79,7 @@ dependencies { implementation("androidx.compose.ui:ui:$composeVersion") implementation("androidx.compose.ui:ui-tooling:$composeVersion") implementation("androidx.compose.material:material-icons-extended:$composeVersion") - implementation("androidx.compose.material3:material3:1.0.0-beta03") + implementation("androidx.compose.material3:material3:1.0.0-rc01") // accompanist dependencies val accompanistVersion = "0.26.3-beta" @@ -103,8 +102,8 @@ dependencies { implementation("io.coil-kt:coil-compose:2.2.1") implementation("com.github.X1nto:Taxi:1.2.0") implementation("org.bouncycastle:bcpkix-jdk15on:1.70") - implementation("com.android:zipflinger:8.0.0-alpha01") + implementation("io.github.diamondminer88:zip-android:2.1.0@aar") implementation("com.aliucord:axml:1.0.1") - implementation("com.android.tools.build:apksig:7.4.0-beta01") + implementation("com.android.tools.build:apksig:7.4.0-beta02") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0") } diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/util/Signer.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/Signer.kt index ad5c151..d2ea66d 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/util/Signer.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/util/Signer.kt @@ -83,9 +83,9 @@ object Signer { val outputApk = aliucordDir.resolve(apkFile.name) ApkSigner.Builder(listOf(signerConfig)) - .setV1SigningEnabled(false) + .setV1SigningEnabled(false) // TODO: enable so api <24 devices can work, however zip-alignment breaks .setV2SigningEnabled(true) - .setV3SigningEnabled(false) + .setV3SigningEnabled(true) .setInputApk(apkFile) .setOutputApk(outputApk) .build() diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt index 5f55081..983e9ff 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/InstallViewModel.kt @@ -18,13 +18,12 @@ import com.aliucord.manager.installer.util.ManifestPatcher import com.aliucord.manager.installer.util.Signer import com.aliucord.manager.network.utils.fold import com.aliucord.manager.ui.screen.InstallData -import com.android.zipflinger.* +import com.github.diamondminer88.zip.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import java.io.File import java.time.Instant -import java.util.zip.Deflater class InstallViewModel( private val application: Application, @@ -210,7 +209,7 @@ class InstallViewModel( if (preferences.replaceIcon) { log += "Replacing App Icons... " - ZipArchive(baseApkFile.toPath()).use { baseApk -> + ZipWriter(baseApkFile, true).use { baseApk -> val mipmaps = arrayOf("mipmap-xhdpi-v4", "mipmap-xxhdpi-v4", "mipmap-xxxhdpi-v4") val icons = arrayOf("ic_logo_foreground.png", "ic_logo_square.png", "ic_logo_foreground.png") @@ -220,8 +219,8 @@ class InstallViewModel( for (mipmap in mipmaps) { val path = "res/$mipmap/$icon" - baseApk.delete(path) - baseApk.add(BytesSource(newIcon, path, Deflater.DEFAULT_COMPRESSION)) + baseApk.deleteEntry(path) + baseApk.writeEntry(path, newIcon) } } } @@ -229,38 +228,27 @@ class InstallViewModel( log += "Done\n" } - log += "Patching manifest... " - + log += "Patching manifests... " try { - val byteArray = ZipRepo(baseApkFile.absolutePath) - .use { zip -> zip.getContent("AndroidManifest.xml") } - .array() - - ZipArchive(baseApkFile.toPath()).use { zip -> - val patchedBytesArray = ManifestPatcher.patchManifest( - manifestBytes = byteArray, - packageName = preferences.packageName, - appName = preferences.appName, - debuggable = preferences.debuggable, - ) - - val source = BytesSource(patchedBytesArray, "AndroidManifest.xml", Deflater.DEFAULT_COMPRESSION) - - zip.delete("AndroidManifest.xml") - zip.add(source) - } - apks.forEach { apk -> - val byteArray = ZipRepo(apk.absolutePath) - .use { zip -> zip.getContent("AndroidManifest.xml") } - .array() + val manifest = ZipReader(apk) + .use { zip -> zip.openEntry("AndroidManifest.xml")?.read() } + ?: throw IllegalStateException("No manifest in ${apk.name}") - ZipArchive(apk.toPath()).use { zip -> - val patchedBytes = ManifestPatcher.renamePackage(byteArray, preferences.packageName) - val source = BytesSource(patchedBytes, "AndroidManifest.xml", Deflater.DEFAULT_COMPRESSION) + ZipWriter(apk, true).use { zip -> + val patchedManifestBytes = if (apk == baseApkFile) { + ManifestPatcher.patchManifest( + manifestBytes = manifest, + packageName = preferences.packageName, + appName = preferences.appName, + debuggable = preferences.debuggable, + ) + } else { + ManifestPatcher.renamePackage(manifest, preferences.packageName) + } - zip.delete("AndroidManifest.xml") - zip.add(source) + zip.deleteEntry("AndroidManifest.xml", apk == libsApkFile) // Preserve alignment in libs apk + zip.writeEntry("AndroidManifest.xml", patchedManifestBytes) } } } catch (e: Exception) { @@ -273,60 +261,53 @@ class InstallViewModel( log += "Patching java classes... " - ZipArchive(baseApkFile.toPath()).use { baseApk -> - // List all files in the base apk, in order to increment the ordinals of all dex files - baseApk.listEntries().mapNotNull { - // Check if the file is a dex file, if not ignore it by returning null - val match = dexRegex.matchEntire(it) ?: return@mapNotNull null - // Return a Pair with the ordinal of the classes.dex file and the actual filename - (match.groups[1]?.value?.toInt() ?: 1) to it - }.sortedByDescending { (ordinal) -> - // Sort the dex files by ordinal (descending) so they can be renamed without any conflicts - ordinal - }.forEach { (ordinal, fileName) -> - println("Moving $fileName ($ordinal) to classes${ordinal + 1}.dex") - // Read the dex file - val inputStream = baseApk.getInputStream(fileName) - // Construct a new BytesSource with the same data, but incrementing - // the ordinal by one to make room for the Aliucord classes.dex - val incrementedDex = BytesSource(inputStream, "classes${ordinal + 1}.dex", Deflater.DEFAULT_COMPRESSION) - // Add the incremented dex file to the apk - baseApk.add(incrementedDex) - // Remove the original dex file - baseApk.delete(fileName) - } - // Add the Aliucord classes.dex file - baseApk.add( - BytesSource(aliucordDexFile.inputStream(), "classes.dex", Deflater.DEFAULT_COMPRESSION) + val (dexCount, firstDexBytes) = ZipReader(baseApkFile).use { zip -> + Pair( + // Find the amount of .dex files in apk + zip.entryNames.count { it.endsWith(".dex") }, + + // Get the first classes.dex bytes + zip.openEntry("classes.dex")?.read() + ?: throw IllegalStateException("No classes.dex in base apk") ) } + + ZipWriter(baseApkFile, true).use { zip -> + // Move first classes.dex to the dex file count + 1 to make place for Aliucord's .dex + zip.deleteEntry("classes.dex") + zip.writeEntry("classes${dexCount + 1}.dex", firstDexBytes) + + // Add Aliucord's .dex and make it load first by being the first .dex + zip.writeEntry("classes.dex", aliucordDexFile.readBytes()) + } log += "Done\n" log += "Patching libraries... " - ZipArchive(libsApkFile.toPath()).use { libsApk -> - // Loop over the aar files - listOf(hermesLibrary, cppRuntimeLibrary).forEach { - // Open each aar file as a zip file - ZipArchive(it.toPath()).use { aar -> - // Map the aar name to the library name - val lib = with(it.name) { - when { - startsWith("hermes-release") -> "libhermes.so" - startsWith("hermes-cppruntime-release") -> "libc++_shared.so" - else -> throw Error("Unable to map .aar name to .so name") - } + ZipWriter(libsApkFile, true).use { libsApk -> + // Process the hermes and cpp runtime library + for (libFile in arrayOf(hermesLibrary, cppRuntimeLibrary)) { + // Map .aar to the embedded .so inside + val binaryName = with(libFile.name) { + when { + startsWith("hermes-release") -> "libhermes.so" + startsWith("hermes-cppruntime-release") -> "libc++_shared.so" + else -> throw Error("Unable to map $this to embedded .so") } - // Delete existing library - libsApk.delete("lib/$arch/$lib") - // Add new library - val source = BytesSource(aar.getInputStream("jni/$arch/$lib"), "lib/$arch/$lib", Deflater.NO_COMPRESSION).apply { - align(4096) - } - libsApk.add(source) } + + // Read the embedded .so inside the .aar library + val libBytes = ZipReader(libFile).use { libZip -> + libZip.openEntry("jni/$arch/$binaryName")?.read() + ?: throw IllegalStateException("Failed to read jni/$arch/$binaryName from ${libFile.name}") + } + + // Delete the old binary and add the new one instead + libsApk.deleteEntry("lib/$arch/$binaryName", true) + libsApk.writeEntry("lib/$arch/$binaryName", libBytes, ZipCompression.NONE, 4096) } } + log += "Done\n" log += "Signing APKs... "