Skip to content

Commit

Permalink
feat: Patch Options CLI implementation (#132)
Browse files Browse the repository at this point in the history
* feat: Patch Options CLI implementation

* fix: remove leftover log message
  • Loading branch information
Sculas committed Sep 8, 2022
1 parent 649d9bd commit 3f5345a
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 56 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Expand Up @@ -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 {
Expand Down
25 changes: 13 additions & 12 deletions src/main/kotlin/app/revanced/cli/command/MainCommand.kt
Expand Up @@ -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
Expand Down Expand Up @@ -51,6 +52,9 @@ internal object MainCommand : Runnable {
@Option(names = ["-b", "--bundles"], description = ["One or more bundles of patches"], required = true)
var patchBundles = arrayOf<String>()

@Option(names = ["--options"], description = ["Configuration file for all patch options"])
var options: File = File("options.toml")

@ArgGroup(exclusive = false)
var listingArgs: ListingArgs? = null

Expand Down Expand Up @@ -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

val allPatches = args.patchArgs!!.patchBundles.flatMap { bundle ->
JarPatchBundle(bundle).loadPatches()
}

// the file to write to
val outputFile = File(pArgs.outputPath)
OptionsLoader.init(args.patchArgs!!.options, allPatches)

val patcher = app.revanced.patcher.Patcher(
PatcherOptions(
Expand All @@ -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)

Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/app/revanced/cli/patcher/Patcher.kt
Expand Up @@ -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
Expand All @@ -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<Class<out Patch<Data>>>) {
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()

Expand Down
62 changes: 62 additions & 0 deletions 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<Class<out Patch<Data>>>
private typealias OptionsMap = Map<String, Map<String, Any>>

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)
}
})
}
}
}
80 changes: 39 additions & 41 deletions src/main/kotlin/app/revanced/utils/patcher/Patcher.kt
Expand Up @@ -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<Class<out Patch<Data>>>) {
val packageName = this.data.packageMetadata.packageName
val packageVersion = this.data.packageMetadata.packageVersion

args.patchArgs?.patchBundles!!.forEach { bundle ->
val includedPatches = mutableListOf<Class<out Patch<Data>>>()
JarPatchBundle(bundle).loadPatches().forEach patch@{ patch ->
val compatiblePackages = patch.compatiblePackages
val patchName = patch.patchName
val includedPatches = mutableListOf<Class<out Patch<Data>>>()
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
}
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
}

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
}
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@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@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@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@patchLoop
}

logger.trace("Adding $patchName")
includedPatches.add(patch)
}
this.addPatches(includedPatches)

logger.trace("Adding $patchName")
includedPatches.add(patch)
}

this.addPatches(includedPatches)
}

fun Patcher.applyPatchesVerbose() {
Expand Down

0 comments on commit 3f5345a

Please sign in to comment.