From 3f5345af6e45bfb6c91d52fc089ab18d81fdc998 Mon Sep 17 00:00:00 2001 From: Sculas Date: Thu, 8 Sep 2022 22:35:09 +0200 Subject: [PATCH] feat: Patch Options CLI implementation (#132) * feat: Patch Options CLI implementation * fix: remove leftover log message --- build.gradle.kts | 3 +- .../app/revanced/cli/command/MainCommand.kt | 25 +++--- .../app/revanced/cli/patcher/Patcher.kt | 6 +- .../app/revanced/utils/OptionsLoader.kt | 62 +++++++++++++ .../app/revanced/utils/patcher/Patcher.kt | 90 +++++++++---------- 5 files changed, 125 insertions(+), 61 deletions(-) create mode 100644 src/main/kotlin/app/revanced/utils/OptionsLoader.kt diff --git a/build.gradle.kts b/build.gradle.kts index 87a2b3f..bab3442 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,11 +25,12 @@ repositories { dependencies { implementation(kotlin("reflect")) - implementation("app.revanced:revanced-patcher:4.2.2") + implementation("app.revanced:revanced-patcher:4.2.3") implementation("info.picocli:picocli:4.6.3") implementation("com.android.tools.build:apksig:7.2.1") implementation("com.github.revanced:jadb:master-SNAPSHOT") // updated fork implementation("org.bouncycastle:bcpkix-jdk15on:1.70") + implementation("cc.ekblad:4koma:1.1.0") } tasks { diff --git a/src/main/kotlin/app/revanced/cli/command/MainCommand.kt b/src/main/kotlin/app/revanced/cli/command/MainCommand.kt index 25bbd5e..e1d0ae9 100644 --- a/src/main/kotlin/app/revanced/cli/command/MainCommand.kt +++ b/src/main/kotlin/app/revanced/cli/command/MainCommand.kt @@ -11,6 +11,7 @@ import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages import app.revanced.patcher.extensions.PatchExtensions.description import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.util.patch.impl.JarPatchBundle +import app.revanced.utils.OptionsLoader import app.revanced.utils.adb.Adb import picocli.CommandLine.* import java.io.File @@ -51,6 +52,9 @@ internal object MainCommand : Runnable { @Option(names = ["-b", "--bundles"], description = ["One or more bundles of patches"], required = true) var patchBundles = arrayOf() + @Option(names = ["--options"], description = ["Configuration file for all patch options"]) + var options: File = File("options.toml") + @ArgGroup(exclusive = false) var listingArgs: ListingArgs? = null @@ -123,20 +127,17 @@ internal object MainCommand : Runnable { } override fun run() { - if (args.patchArgs?.listingArgs?.listOnly == true) { - printListOfPatches() - return - } - - if (args.uninstall) { - uninstall() - return - } + if (args.patchArgs?.listingArgs?.listOnly == true) return printListOfPatches() + if (args.uninstall) return uninstall() val pArgs = this.args.patchArgs?.patchingArgs ?: return + val outputFile = File(pArgs.outputPath) // the file to write to - // the file to write to - val outputFile = File(pArgs.outputPath) + val allPatches = args.patchArgs!!.patchBundles.flatMap { bundle -> + JarPatchBundle(bundle).loadPatches() + } + + OptionsLoader.init(args.patchArgs!!.options, allPatches) val patcher = app.revanced.patcher.Patcher( PatcherOptions( @@ -157,7 +158,7 @@ internal object MainCommand : Runnable { val patchedFile = File(pArgs.cacheDirectory).resolve("${outputFile.nameWithoutExtension}_raw.apk") // start the patcher - Patcher.start(patcher, patchedFile) + Patcher.start(patcher, patchedFile, allPatches) val cacheDirectory = File(pArgs.cacheDirectory) diff --git a/src/main/kotlin/app/revanced/cli/patcher/Patcher.kt b/src/main/kotlin/app/revanced/cli/patcher/Patcher.kt index 3dc2f61..c75dd84 100644 --- a/src/main/kotlin/app/revanced/cli/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/cli/patcher/Patcher.kt @@ -2,6 +2,8 @@ package app.revanced.cli.patcher import app.revanced.cli.command.MainCommand.args import app.revanced.cli.command.MainCommand.logger +import app.revanced.patcher.data.Data +import app.revanced.patcher.patch.Patch import app.revanced.utils.filesystem.ZipFileSystemUtils import app.revanced.utils.patcher.addPatchesFiltered import app.revanced.utils.patcher.applyPatchesVerbose @@ -10,14 +12,14 @@ import java.io.File import java.nio.file.Files internal object Patcher { - internal fun start(patcher: app.revanced.patcher.Patcher, output: File) { + internal fun start(patcher: app.revanced.patcher.Patcher, output: File, allPatches: List>>) { val inputFile = args.inputFile val args = args.patchArgs?.patchingArgs!! // merge files like necessary integrations patcher.mergeFiles() // add patches, but filter incompatible or excluded patches - patcher.addPatchesFiltered() + patcher.addPatchesFiltered(allPatches) // apply patches patcher.applyPatchesVerbose() diff --git a/src/main/kotlin/app/revanced/utils/OptionsLoader.kt b/src/main/kotlin/app/revanced/utils/OptionsLoader.kt new file mode 100644 index 0000000..f04efd2 --- /dev/null +++ b/src/main/kotlin/app/revanced/utils/OptionsLoader.kt @@ -0,0 +1,62 @@ +package app.revanced.utils + +import app.revanced.cli.command.MainCommand.logger +import app.revanced.patcher.data.Data +import app.revanced.patcher.extensions.PatchExtensions.options +import app.revanced.patcher.extensions.PatchExtensions.patchName +import app.revanced.patcher.patch.Patch +import cc.ekblad.toml.encodeTo +import cc.ekblad.toml.model.TomlValue +import cc.ekblad.toml.serialization.from +import cc.ekblad.toml.tomlMapper +import java.io.File + +private typealias PatchList = List>> +private typealias OptionsMap = Map> + +private const val NULL = "null" + +object OptionsLoader { + @JvmStatic + private val mapper = tomlMapper {} + + @JvmStatic + fun init(file: File, patches: PatchList) { + if (!file.exists()) file.createNewFile() + val path = file.toPath() + val map = mapper.decodeWithDefaults( + generateDefaults(patches), + TomlValue.from(path) + ).also { mapper.encodeTo(path, it) } + readAndSet(map, patches) + } + + private fun readAndSet(map: OptionsMap, patches: PatchList) { + for ((patchName, options) in map) { + val patch = patches.find { it.patchName == patchName } ?: continue + val patchOptions = patch.options ?: continue + for ((key, value) in options) { + try { + patchOptions[key] = value.let { + if (it == NULL) null else it + } + } catch (e: Exception) { + logger.warn("Error while setting option $key for patch $patchName: ${e.message}") + e.printStackTrace() + } + } + } + } + + private fun generateDefaults(patches: PatchList) = buildMap { + for (patch in patches) { + val options = patch.options ?: continue + if (!options.iterator().hasNext()) continue + put(patch.patchName, buildMap { + for (option in options) { + put(option.key, option.value ?: NULL) + } + }) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/utils/patcher/Patcher.kt b/src/main/kotlin/app/revanced/utils/patcher/Patcher.kt index 14b76bf..f0a657c 100644 --- a/src/main/kotlin/app/revanced/utils/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/utils/patcher/Patcher.kt @@ -10,61 +10,59 @@ import app.revanced.patcher.extensions.PatchExtensions.deprecated import app.revanced.patcher.extensions.PatchExtensions.include import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.patch.Patch -import app.revanced.patcher.util.patch.impl.JarPatchBundle -fun Patcher.addPatchesFiltered() { +fun Patcher.addPatchesFiltered(allPatches: List>>) { val packageName = this.data.packageMetadata.packageName val packageVersion = this.data.packageMetadata.packageVersion - args.patchArgs?.patchBundles!!.forEach { bundle -> - val includedPatches = mutableListOf>>() - JarPatchBundle(bundle).loadPatches().forEach patch@{ patch -> - val compatiblePackages = patch.compatiblePackages - val patchName = patch.patchName + val includedPatches = mutableListOf>>() + allPatches.forEach patchLoop@{ patch -> + val compatiblePackages = patch.compatiblePackages + val patchName = patch.patchName - val prefix = "Skipping $patchName, reason" + val prefix = "Skipping $patchName, reason" - val args = MainCommand.args.patchArgs?.patchingArgs!! + val args = MainCommand.args.patchArgs?.patchingArgs!! - if (args.excludedPatches.contains(patchName)) { - logger.info("$prefix: manually excluded") - return@patch - } else if ((!patch.include || args.defaultExclude) && !args.includedPatches.contains(patchName)) { - logger.info("$prefix: excluded by default") - return@patch - } - - patch.deprecated?.let { (reason, replacement) -> - logger.warn("$prefix: deprecated: $reason") - if (replacement != null) logger.warn("Either use ${replacement.java.patchName} instead or include it manually") - return@patch - } - - if (compatiblePackages == null) logger.warn("$prefix: Missing compatibility annotation. Continuing.") - else { - if (!compatiblePackages.any { it.name == packageName }) { - logger.warn("$prefix: incompatible with $packageName. This patch is only compatible with ${ - compatiblePackages.joinToString( - ", " - ) { it.name } - }") - return@patch - } - - if (!(args.experimental || compatiblePackages.any { it.versions.isEmpty() || it.versions.any { version -> version == packageVersion } })) { - val compatibleWith = compatiblePackages.joinToString(";") { _package -> - "${_package.name}: ${_package.versions.joinToString(", ")}" - } - logger.warn("$prefix: incompatible with version $packageVersion. This patch is only compatible with version $compatibleWith") - return@patch - } - } - - logger.trace("Adding $patchName") - includedPatches.add(patch) + if (args.excludedPatches.contains(patchName)) { + logger.info("$prefix: manually excluded") + return@patchLoop + } else if ((!patch.include || args.defaultExclude) && !args.includedPatches.contains(patchName)) { + logger.info("$prefix: excluded by default") + return@patchLoop } - this.addPatches(includedPatches) + + patch.deprecated?.let { (reason, replacement) -> + logger.warn("$prefix: deprecated: $reason") + if (replacement != null) logger.warn("Either use ${replacement.java.patchName} instead or include it manually") + return@patchLoop + } + + if (compatiblePackages == null) logger.warn("$prefix: Missing compatibility annotation. Continuing.") + else { + if (!compatiblePackages.any { it.name == packageName }) { + logger.warn("$prefix: incompatible with $packageName. This patch is only compatible with ${ + compatiblePackages.joinToString( + ", " + ) { it.name } + }") + return@patchLoop + } + + if (!(args.experimental || compatiblePackages.any { it.versions.isEmpty() || it.versions.any { version -> version == packageVersion } })) { + val compatibleWith = compatiblePackages.joinToString(";") { _package -> + "${_package.name}: ${_package.versions.joinToString(", ")}" + } + logger.warn("$prefix: incompatible with version $packageVersion. This patch is only compatible with version $compatibleWith") + return@patchLoop + } + } + + logger.trace("Adding $patchName") + includedPatches.add(patch) } + + this.addPatches(includedPatches) } fun Patcher.applyPatchesVerbose() {