diff --git a/src/main/kotlin/app/revanced/cli/command/ListCompatibleVersions.kt b/src/main/kotlin/app/revanced/cli/command/ListCompatibleVersions.kt index ab21eee..78013b8 100644 --- a/src/main/kotlin/app/revanced/cli/command/ListCompatibleVersions.kt +++ b/src/main/kotlin/app/revanced/cli/command/ListCompatibleVersions.kt @@ -3,7 +3,7 @@ package app.revanced.cli.command import app.revanced.library.PackageName import app.revanced.library.PatchUtils import app.revanced.library.VersionMap -import app.revanced.patcher.PatchBundleLoader +import app.revanced.patcher.patch.loadPatchesFromJar import picocli.CommandLine import java.io.File import java.util.logging.Logger @@ -22,7 +22,7 @@ internal class ListCompatibleVersions : Runnable { description = ["Paths to patch bundles."], arity = "1..*", ) - private lateinit var patchBundles: Array + private lateinit var patchBundles: Set @CommandLine.Option( names = ["-f", "--filter-package-names"], @@ -38,8 +38,6 @@ internal class ListCompatibleVersions : Runnable { private var countUnusedPatches: Boolean = false override fun run() { - val patches = PatchBundleLoader.Jar(*patchBundles) - fun VersionMap.buildVersionsString(): String { if (isEmpty()) return "Any" @@ -58,6 +56,8 @@ internal class ListCompatibleVersions : Runnable { appendLine(versions.buildVersionsString().prependIndent("\t")) } + val patches = loadPatchesFromJar(patchBundles) + PatchUtils.getMostCommonCompatibleVersions( patches, packageNames, diff --git a/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt b/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt index 1886613..d3c66a1 100644 --- a/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt @@ -1,12 +1,13 @@ package app.revanced.cli.command -import app.revanced.patcher.PatchBundleLoader +import app.revanced.patcher.patch.Package import app.revanced.patcher.patch.Patch -import app.revanced.patcher.patch.options.PatchOption +import app.revanced.patcher.patch.loadPatchesFromJar import picocli.CommandLine.* import picocli.CommandLine.Help.Visibility.ALWAYS import java.io.File import java.util.logging.Logger +import app.revanced.patcher.patch.Option as PatchOption @Command( name = "list-patches", @@ -19,7 +20,7 @@ internal object ListPatchesCommand : Runnable { description = ["Paths to patch bundles."], arity = "1..*", ) - private lateinit var patchBundles: Array + private lateinit var patchBundles: Set @Option( names = ["-d", "--with-descriptions"], @@ -70,16 +71,19 @@ internal object ListPatchesCommand : Runnable { private var packageName: String? = null override fun run() { - fun Patch.CompatiblePackage.buildString() = - buildString { + fun Package.buildString(): String { + val (name, versions) = this + + return buildString { if (withVersions && versions != null) { appendLine("Package name: $name") appendLine("Compatible versions:") - append(versions!!.joinToString("\n") { version -> version }.prependIndent("\t")) + append(versions.joinToString("\n") { version -> version }.prependIndent("\t")) } else { append("Package name: $name") } } + } fun PatchOption<*>.buildString() = buildString { @@ -126,10 +130,10 @@ internal object ListPatchesCommand : Runnable { } fun Patch<*>.filterCompatiblePackages(name: String) = - compatiblePackages?.any { it.name == name } + compatiblePackages?.any { (compatiblePackageName, _) -> compatiblePackageName == name } ?: withUniversalPatches - val patches = PatchBundleLoader.Jar(*patchBundles).withIndex().toList() + val patches = loadPatchesFromJar(patchBundles).withIndex().toList() val filtered = packageName?.let { patches.filter { (_, patch) -> patch.filterCompatiblePackages(it) } } ?: patches diff --git a/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt b/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt index 72d4240..dbc1ada 100644 --- a/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt @@ -2,7 +2,7 @@ package app.revanced.cli.command import app.revanced.library.Options import app.revanced.library.Options.setOptions -import app.revanced.patcher.PatchBundleLoader +import app.revanced.patcher.patch.loadPatchesFromJar import picocli.CommandLine import picocli.CommandLine.Help.Visibility.ALWAYS import java.io.File @@ -19,7 +19,7 @@ internal object OptionsCommand : Runnable { description = ["Paths to patch bundles."], arity = "1..*", ) - private lateinit var patchBundles: Array + private lateinit var patchBundles: Set @CommandLine.Option( names = ["-p", "--path"], @@ -44,7 +44,7 @@ internal object OptionsCommand : Runnable { override fun run() = try { - PatchBundleLoader.Jar(*patchBundles).let { patches -> + loadPatchesFromJar(patchBundles).let { patches -> val exists = filePath.exists() if (!exists || overwrite) { if (exists && update) patches.setOptions(filePath) diff --git a/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt b/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt index 05238c2..66cccbf 100644 --- a/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/PatchCommand.kt @@ -4,11 +4,11 @@ import app.revanced.library.ApkUtils import app.revanced.library.ApkUtils.applyTo import app.revanced.library.Options import app.revanced.library.Options.setOptions -import app.revanced.library.adb.AdbManager -import app.revanced.patcher.PatchBundleLoader -import app.revanced.patcher.PatchSet +import app.revanced.library.installation.installer.* import app.revanced.patcher.Patcher import app.revanced.patcher.PatcherConfig +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.loadPatchesFromJar import kotlinx.coroutines.runBlocking import picocli.CommandLine import picocli.CommandLine.Help.Visibility.ALWAYS @@ -31,7 +31,7 @@ internal object PatchCommand : Runnable { private lateinit var apk: File - private var integrations = setOf() + private var integrations = emptySet() private var patchBundles = emptySet() @@ -193,7 +193,7 @@ internal object PatchCommand : Runnable { description = ["One or more DEX files or containers to merge into the APK."], ) @Suppress("unused") - private fun setIntegrations(integrations: Array) { + private fun setIntegrations(integrations: Set) { integrations.firstOrNull { !it.exists() }?.let { throw CommandLine.ParameterException(spec.commandLine(), "Integrations file ${it.path} does not exist.") } @@ -256,7 +256,7 @@ internal object PatchCommand : Runnable { logger.info("Loading patches") - val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray()) + val patches = loadPatchesFromJar(patchBundles) // Warn if a patch can not be found in the supplied patch bundles. if (warn) { @@ -271,6 +271,7 @@ internal object PatchCommand : Runnable { } // endregion + val patcherTemporaryFilesPath = temporaryFilesPath.resolve("patcher") val (packageName, patcherResult) = Patcher( PatcherConfig( @@ -295,8 +296,7 @@ internal object PatchCommand : Runnable { // region Patch patcher.context.packageMetadata.packageName to patcher.apply { - acceptIntegrations(integrations) - acceptPatches(filteredPatches) + accept(filteredPatches, integrations) // Execute patches. runBlocking { @@ -304,16 +304,18 @@ internal object PatchCommand : Runnable { patchResult.exception?.let { StringWriter().use { writer -> it.printStackTrace(PrintWriter(writer)) - logger.severe("${patchResult.patch.name} failed:\n$writer") + logger.severe("\"${patchResult.patch.name}\" failed:\n$writer") } - } ?: logger.info("${patchResult.patch.name} succeeded") + } ?: logger.info("\"${patchResult.patch.name}\" succeeded") } } }.get() + // endregion } // region Save + apk.copyTo(temporaryFilesPath.resolve(apk.name), overwrite = true).apply { patcherResult.applyTo(this) }.let { patchedApkFile -> @@ -340,9 +342,23 @@ internal object PatchCommand : Runnable { // region Install - deviceSerial?.let { serial -> - AdbManager.getAdbManager(deviceSerial = serial.ifEmpty { null }, mount) - }?.install(AdbManager.Apk(outputFilePath, packageName)) + deviceSerial?.let { it -> + val deviceSerial = it.ifEmpty { null } + + runBlocking { + val result = if (mount) { + AdbRootInstaller(deviceSerial) + } else { + AdbInstaller(deviceSerial) + }.install(Installer.Apk(outputFilePath, packageName)) + + when (result) { + RootInstallerResult.FAILURE -> logger.severe("Failed to mount the patched APK file") + is AdbInstallerResult.Failure -> logger.severe(result.exception.toString()) + else -> logger.info("Installed the patched APK file") + } + } + } // endregion @@ -358,7 +374,7 @@ internal object PatchCommand : Runnable { * @param patches The patches to filter. * @return The filtered patches. */ - private fun Patcher.filterPatchSelection(patches: PatchSet): PatchSet = + private fun Patcher.filterPatchSelection(patches: Set>): Set> = buildSet { val packageName = context.packageMetadata.packageName val packageVersion = context.packageMetadata.packageVersion @@ -367,33 +383,32 @@ internal object PatchCommand : Runnable { val patchName = patch.name!! val explicitlyExcluded = excludedPatches.contains(patchName) || excludedPatchesByIndex.contains(i) - if (explicitlyExcluded) return@patch logger.info("Excluding $patchName") + if (explicitlyExcluded) return@patch logger.info("Excluding \"$patchName\"") // Make sure the patch is compatible with the supplied APK files package name and version. patch.compatiblePackages?.let { packages -> - packages.singleOrNull { it.name == packageName }?.let { `package` -> - val matchesVersion = - force || `package`.versions?.let { - it.any { version -> version == packageVersion } - } ?: true + packages.singleOrNull { (name, _) -> name == packageName }?.let { (_, versions) -> + val matchesVersion = force || + versions?.let { it.any { version -> version == packageVersion } } + ?: true if (!matchesVersion) { return@patch logger.warning( - "$patchName is incompatible with version $packageVersion. " + + "The patch \"$patchName\" is incompatible with version $packageVersion. " + "This patch is only compatible with version " + - packages.joinToString(";") { pkg -> - pkg.versions!!.joinToString(", ") + packages.joinToString(";") { (_, versions) -> + versions!!.joinToString(", ") }, ) } } ?: return@patch logger.fine( - "$patchName is incompatible with $packageName. " + + "The patch \"$patchName\" is incompatible with $packageName. " + "This patch is only compatible with " + - packages.joinToString(", ") { `package` -> `package`.name }, + packages.joinToString(", ") { (name, _) -> name }, ) return@let - } ?: logger.fine("$patchName has no constraint on packages.") + } ?: logger.fine("\"$patchName\" has no constraint on packages.") // If the patch is implicitly used, it will be only included if [exclusive] is false. val implicitlyIncluded = !exclusive && patch.use @@ -401,9 +416,9 @@ internal object PatchCommand : Runnable { val explicitlyIncluded = includedPatches.contains(patchName) || includedPatchesByIndex.contains(i) val included = implicitlyIncluded || explicitlyIncluded - if (!included) return@patch logger.info("$patchName excluded") // Case 1. + if (!included) return@patch logger.info("\"$patchName\" excluded") // Case 1. - logger.fine("Adding $patchName") + logger.fine("Adding \"$patchName\"") add(patch) } diff --git a/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt b/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt index 508bf1c..573205e 100644 --- a/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt @@ -1,6 +1,9 @@ package app.revanced.cli.command.utility -import app.revanced.library.adb.AdbManager +import app.revanced.library.installation.installer.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking import picocli.CommandLine.* import java.io.File import java.util.logging.Logger @@ -32,13 +35,29 @@ internal object InstallCommand : Runnable { private var packageName: String? = null override fun run() { - fun install(deviceSerial: String? = null) = - try { - AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName)) - } catch (e: AdbManager.DeviceNotFoundException) { + suspend fun install(deviceSerial: String? = null) { + val result = try { + if (packageName != null) { + AdbRootInstaller(deviceSerial) + } else { + AdbInstaller(deviceSerial) + }.install(Installer.Apk(apk, packageName)) + } catch (e: Exception) { logger.severe(e.toString()) } - deviceSerials?.forEach(::install) ?: install() + when (result) { + RootInstallerResult.FAILURE -> + logger.severe("Failed to mount the APK file") + is AdbInstallerResult.Failure -> + logger.severe(result.exception.toString()) + else -> + logger.info("Installed the APK file") + } + } + + runBlocking { + deviceSerials?.map { async { install(it) } }?.awaitAll() ?: install() + } } } diff --git a/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt b/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt index 49a36f2..e446e00 100644 --- a/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt @@ -1,6 +1,9 @@ package app.revanced.cli.command.utility -import app.revanced.library.adb.AdbManager +import app.revanced.library.installation.installer.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking import picocli.CommandLine.* import picocli.CommandLine.Help.Visibility.ALWAYS import java.util.logging.Logger @@ -33,13 +36,28 @@ internal object UninstallCommand : Runnable { private var unmount: Boolean = false override fun run() { - fun uninstall(deviceSerial: String? = null) = - try { - AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName) - } catch (e: AdbManager.DeviceNotFoundException) { + suspend fun uninstall(deviceSerial: String? = null) { + val result = try { + if (unmount) { + AdbRootInstaller(deviceSerial) + } else { + AdbInstaller(deviceSerial) + }.uninstall(packageName) + } catch (e: Exception) { logger.severe(e.toString()) } - deviceSerials?.forEach { uninstall(it) } ?: uninstall() + when (result) { + RootInstallerResult.FAILURE -> + logger.severe("Failed to unmount the patched APK file") + is AdbInstallerResult.Failure -> + logger.severe(result.exception.toString()) + else -> logger.info("Uninstalled the patched APK file") + } + } + + runBlocking { + deviceSerials?.map { async { uninstall(it) } }?.awaitAll() ?: uninstall() + } } }