feat: replace zipflinger with libziprs (#29)

This commit is contained in:
rushii 2022-10-22 12:02:48 -07:00 • committed by GitHub
parent 3ad14acb56
commit 6965f3128b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 63 additions and 83 deletions

View file

@ -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")
}

View file

@ -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()

View file

@ -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<Int, String> 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... "