mirror of
https://github.com/revanced/revanced-cli.git
synced 2024-11-11 22:29:24 +01:00
refactor!: restructure code
This commit focuses on improving code quality in a couple of places and bumping the dependency to ReVanced Patcher. BREAKING CHANGE: This introduces major changes to how ReVanced CLI is used from the command line.
This commit is contained in:
parent
ef5fa9b4c9
commit
07da528ce2
@ -23,9 +23,9 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation("app.revanced:revanced-patcher:14.0.0")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.22")
|
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.22")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
||||||
implementation("app.revanced:revanced-patcher:13.0.0")
|
|
||||||
implementation("info.picocli:picocli:4.7.3")
|
implementation("info.picocli:picocli:4.7.3")
|
||||||
implementation("com.github.revanced:jadb:2531a28109") // Updated fork
|
implementation("com.github.revanced:jadb:2531a28109") // Updated fork
|
||||||
implementation("com.android.tools.build:apksig:8.1.0")
|
implementation("com.android.tools.build:apksig:8.1.0")
|
||||||
|
@ -2,28 +2,26 @@ package app.revanced.cli.command
|
|||||||
|
|
||||||
import app.revanced.cli.aligning.Aligning
|
import app.revanced.cli.aligning.Aligning
|
||||||
import app.revanced.cli.logging.impl.DefaultCliLogger
|
import app.revanced.cli.logging.impl.DefaultCliLogger
|
||||||
import app.revanced.cli.patcher.Patcher
|
|
||||||
import app.revanced.cli.patcher.logging.impl.PatcherLogger
|
import app.revanced.cli.patcher.logging.impl.PatcherLogger
|
||||||
import app.revanced.cli.signing.Signing
|
import app.revanced.cli.signing.Signing
|
||||||
import app.revanced.cli.signing.SigningOptions
|
import app.revanced.cli.signing.SigningOptions
|
||||||
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
|
import app.revanced.patcher.Patcher
|
||||||
import app.revanced.patcher.PatcherOptions
|
import app.revanced.patcher.PatcherOptions
|
||||||
import app.revanced.patcher.data.Context
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.description
|
import app.revanced.patcher.extensions.PatchExtensions.description
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.include
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.PatchClass
|
||||||
import app.revanced.patcher.util.patch.PatchBundle
|
|
||||||
import app.revanced.utils.Options
|
import app.revanced.utils.Options
|
||||||
import app.revanced.utils.Options.setOptions
|
import app.revanced.utils.Options.setOptions
|
||||||
import app.revanced.utils.adb.Adb
|
import app.revanced.utils.adb.AdbManager
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Files
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias for return type of [PatchBundle.loadPatches].
|
internal typealias PatchList = List<PatchClass>
|
||||||
*/
|
|
||||||
internal typealias PatchList = List<Class<out Patch<Context>>>
|
|
||||||
|
|
||||||
private class CLIVersionProvider : IVersionProvider {
|
private class CLIVersionProvider : IVersionProvider {
|
||||||
override fun getVersion() = arrayOf(
|
override fun getVersion() = arrayOf(
|
||||||
@ -42,81 +40,80 @@ internal object MainCommand : Runnable {
|
|||||||
@ArgGroup(exclusive = false, multiplicity = "1")
|
@ArgGroup(exclusive = false, multiplicity = "1")
|
||||||
lateinit var args: Args
|
lateinit var args: Args
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments for the CLI
|
||||||
|
*/
|
||||||
class Args {
|
class Args {
|
||||||
// TODO: Move this so it is not required when listing patches
|
@Option(names = ["--uninstall"], description = ["Package name to uninstall"])
|
||||||
@Option(names = ["-a", "--apk"], description = ["APK file to be patched"], required = true)
|
var packageName: String? = null
|
||||||
lateinit var inputFile: File
|
|
||||||
|
|
||||||
@Option(names = ["--unmount"], description = ["Unmount a patched APK file"])
|
@Option(names = ["-d", "--device-serial"], description = ["ADB device serial number to deploy to"])
|
||||||
var unmount: Boolean = false
|
var deviceSerial: String? = null
|
||||||
|
|
||||||
@Option(
|
@Option(names = ["--mount"], description = ["Handle deployments by mounting"])
|
||||||
names = ["-d", "--deploy"],
|
var mount: Boolean = false
|
||||||
description = ["Deploy to the specified device that is connected via ADB"]
|
|
||||||
)
|
|
||||||
var deploy: String? = null
|
|
||||||
|
|
||||||
@ArgGroup(exclusive = false)
|
@ArgGroup(exclusive = false)
|
||||||
var patchArgs: PatchArgs? = null
|
var patchArgs: PatchArgs? = null
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments for patches.
|
||||||
|
*/
|
||||||
class PatchArgs {
|
class PatchArgs {
|
||||||
@Option(names = ["-b", "--bundle"], description = ["One or more bundles of patches"], required = true)
|
@Option(names = ["-b", "--bundle"], description = ["One or more bundles of patches"], required = true)
|
||||||
var patchBundles = arrayOf<String>()
|
var patchBundles = emptyList<File>()
|
||||||
|
|
||||||
@Option(names = ["--options"], description = ["Path to patch options JSON file"])
|
|
||||||
var optionsFile: File = File("options.json")
|
|
||||||
|
|
||||||
@ArgGroup(exclusive = false)
|
@ArgGroup(exclusive = false)
|
||||||
var listingArgs: ListingArgs? = null
|
var listingArgs: ListingArgs? = null
|
||||||
|
|
||||||
@ArgGroup(exclusive = false)
|
@ArgGroup(exclusive = false)
|
||||||
var patchingArgs: PatchingArgs? = null
|
var patchingArgs: PatchingArgs? = null
|
||||||
}
|
|
||||||
|
|
||||||
class ListingArgs {
|
|
||||||
@Option(names = ["-l", "--list"], description = ["List patches"], required = true)
|
|
||||||
var listOnly: Boolean = false
|
|
||||||
|
|
||||||
@Option(names = ["--with-versions"], description = ["List patches with version compatibilities"])
|
|
||||||
var withVersions: Boolean = false
|
|
||||||
|
|
||||||
@Option(names = ["--with-packages"], description = ["List patches with package compatibilities"])
|
|
||||||
var withPackages: Boolean = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments for patching.
|
||||||
|
*/
|
||||||
class PatchingArgs {
|
class PatchingArgs {
|
||||||
@Option(names = ["-o", "--out"], description = ["Path to save the patched APK file to"], required = true)
|
@Option(names = ["-a", "--apk"], description = ["APK file to be patched"], required = true)
|
||||||
lateinit var outputPath: String
|
lateinit var inputFile: File
|
||||||
|
|
||||||
@Option(names = ["-e", "--exclude"], description = ["Exclude patches"])
|
@Option(
|
||||||
|
names = ["-o", "--out"],
|
||||||
|
description = ["Path to save the patched APK file to"],
|
||||||
|
required = true
|
||||||
|
)
|
||||||
|
lateinit var outputFilePath: File
|
||||||
|
|
||||||
|
@Option(names = ["--options"], description = ["Path to patch options JSON file"])
|
||||||
|
var optionsFile: File = File("options.json")
|
||||||
|
|
||||||
|
@Option(names = ["-e", "--exclude"], description = ["List of patches to exclude"])
|
||||||
var excludedPatches = arrayOf<String>()
|
var excludedPatches = arrayOf<String>()
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["--exclusive"],
|
names = ["--exclusive"],
|
||||||
description = ["Only include patches that were explicitly specified to be included"]
|
description = ["Only include patches that are explicitly specified to be included"]
|
||||||
)
|
)
|
||||||
var exclusive = false
|
var exclusive = false
|
||||||
|
|
||||||
@Option(names = ["-i", "--include"], description = ["Include patches"])
|
@Option(names = ["-i", "--include"], description = ["List of patches to include"])
|
||||||
var includedPatches = arrayOf<String>()
|
var includedPatches = arrayOf<String>()
|
||||||
|
|
||||||
@Option(names = ["--experimental"], description = ["Ignore patches incompatibility to versions"])
|
@Option(names = ["--experimental"], description = ["Ignore patches incompatibility to versions"])
|
||||||
var experimental: Boolean = false
|
var experimental: Boolean = false
|
||||||
|
|
||||||
@Option(names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"])
|
|
||||||
var mergeFiles = listOf<File>()
|
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["--mount"],
|
names = ["-m", "--merge"],
|
||||||
description = ["Mount the patched APK file over the original file instead of installing it"]
|
description = ["One or more DEX files or containers to merge into the APK"]
|
||||||
)
|
)
|
||||||
var mount: Boolean = false
|
var integrations = listOf<File>()
|
||||||
|
|
||||||
@Option(names = ["--cn"], description = ["The common name of the signer of the patched APK file"])
|
@Option(names = ["--cn"], description = ["The common name of the signer of the patched APK file"])
|
||||||
var cn = "ReVanced"
|
var commonName = "ReVanced"
|
||||||
|
|
||||||
@Option(names = ["--keystore"], description = ["Path to the keystore to sign the patched APK file with"])
|
@Option(
|
||||||
|
names = ["--keystore"],
|
||||||
|
description = ["Path to the keystore to sign the patched APK file with"]
|
||||||
|
)
|
||||||
var keystorePath: String? = null
|
var keystorePath: String? = null
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
@ -125,8 +122,11 @@ internal object MainCommand : Runnable {
|
|||||||
)
|
)
|
||||||
var password = "ReVanced"
|
var password = "ReVanced"
|
||||||
|
|
||||||
@Option(names = ["-t", "--temp-dir"], description = ["Path to temporary resource cache directory"])
|
@Option(
|
||||||
var cacheDirectory = "revanced-cache"
|
names = ["-r", "--resource-cache"],
|
||||||
|
description = ["Path to temporary resource cache directory"]
|
||||||
|
)
|
||||||
|
var resourceCachePath = File("revanced-resource-cache")
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["-c", "--clean"],
|
names = ["-c", "--clean"],
|
||||||
@ -136,62 +136,96 @@ internal object MainCommand : Runnable {
|
|||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["--custom-aapt2-binary"],
|
names = ["--custom-aapt2-binary"],
|
||||||
description = ["Path to custom AAPT binary to compile resources with"]
|
description = ["Path to a custom AAPT binary to compile resources with"]
|
||||||
)
|
)
|
||||||
var aaptPath: String = ""
|
var aaptBinaryPath = File("")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments for printing patches to the console.
|
||||||
|
*/
|
||||||
|
class ListingArgs {
|
||||||
|
@Option(names = ["-l", "--list"], description = ["List patches"], required = true)
|
||||||
|
var listOnly: Boolean = false
|
||||||
|
|
||||||
|
@Option(names = ["--with-versions"], description = ["List patches and their compatible versions"])
|
||||||
|
var withVersions: Boolean = false
|
||||||
|
|
||||||
|
@Option(names = ["--with-packages"], description = ["List patches and their compatible packages"])
|
||||||
|
var withPackages: Boolean = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (args.patchArgs?.listingArgs?.listOnly == true) return printListOfPatches()
|
val patchArgs = args.patchArgs
|
||||||
if (args.unmount) return unmount()
|
|
||||||
|
|
||||||
val pArgs = this.args.patchArgs?.patchingArgs ?: return
|
if (patchArgs?.listingArgs?.listOnly == true) return printListOfPatches()
|
||||||
val outputFile = File(pArgs.outputPath) // the file to write to
|
if (args.packageName != null) return uninstall()
|
||||||
|
|
||||||
val allPatches = args.patchArgs!!.patchBundles.flatMap { bundle ->
|
val patchingArgs = patchArgs?.patchingArgs ?: return
|
||||||
PatchBundle.Jar(bundle).loadPatches()
|
|
||||||
|
if (!patchingArgs.inputFile.exists()) return logger.error("Input file ${patchingArgs.inputFile} does not exist.")
|
||||||
|
|
||||||
|
logger.info("Loading patches")
|
||||||
|
|
||||||
|
val patches = PatchBundleLoader.Jar(*patchArgs.patchBundles.toTypedArray())
|
||||||
|
val integrations = patchingArgs.integrations
|
||||||
|
|
||||||
|
logger.info("Setting up patch options")
|
||||||
|
|
||||||
|
patchingArgs.optionsFile.let {
|
||||||
|
if (it.exists()) patches.setOptions(it, logger)
|
||||||
|
else Options.serialize(patches, prettyPrint = true).let(it::writeText)
|
||||||
}
|
}
|
||||||
|
|
||||||
args.patchArgs!!.optionsFile.let {
|
val adbManager = args.deviceSerial?.let { serial ->
|
||||||
if (it.exists()) allPatches.setOptions(it, logger)
|
if (args.mount) AdbManager.RootAdbManager(serial, logger) else AdbManager.UserAdbManager(serial, logger)
|
||||||
else Options.serialize(allPatches, prettyPrint = true).let(it::writeText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val patcher = app.revanced.patcher.Patcher(
|
val patcher = Patcher(
|
||||||
PatcherOptions(
|
PatcherOptions(
|
||||||
args.inputFile.also { if (!it.exists()) return logger.error("Input file ${args.inputFile} does not exist.") },
|
patchingArgs.inputFile,
|
||||||
pArgs.cacheDirectory,
|
patchingArgs.resourceCachePath,
|
||||||
pArgs.aaptPath,
|
patchingArgs.aaptBinaryPath.absolutePath,
|
||||||
pArgs.cacheDirectory,
|
patchingArgs.resourceCachePath.absolutePath,
|
||||||
PatcherLogger
|
PatcherLogger
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// prepare adb
|
val result = patcher.apply {
|
||||||
val adb: Adb? = args.deploy?.let {
|
acceptIntegrations(integrations)
|
||||||
Adb(outputFile, patcher.context.packageMetadata.packageName, args.deploy!!, !pArgs.mount)
|
acceptPatches(filterPatchSelection(patches))
|
||||||
|
|
||||||
|
// Execute patches.
|
||||||
|
runBlocking {
|
||||||
|
apply(false).collect { patchResult ->
|
||||||
|
patchResult.exception?.let {
|
||||||
|
logger.error("${patchResult.patchName} failed:\n${patchResult.exception}")
|
||||||
|
} ?: logger.info("${patchResult.patchName} succeeded")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}.get()
|
||||||
|
|
||||||
// start the patcher
|
patcher.close()
|
||||||
val result = Patcher.start(patcher, allPatches)
|
|
||||||
|
|
||||||
val cacheDirectory = File(pArgs.cacheDirectory)
|
val outputFileNameWithoutExtension = patchingArgs.outputFilePath.nameWithoutExtension
|
||||||
|
|
||||||
// align the file
|
// Align the file.
|
||||||
val alignedFile = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk")
|
val alignedFile = patchingArgs.resourceCachePath.resolve("${outputFileNameWithoutExtension}_aligned.apk")
|
||||||
Aligning.align(result, args.inputFile, alignedFile)
|
Aligning.align(result, patchingArgs.inputFile, alignedFile)
|
||||||
|
|
||||||
// sign the file
|
// Sign the file if needed.
|
||||||
val finalFile = if (!pArgs.mount) {
|
val finalFile = if (!args.mount) {
|
||||||
val signedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_signed.apk")
|
val signedOutput = patchingArgs.resourceCachePath.resolve("${outputFileNameWithoutExtension}_signed.apk")
|
||||||
Signing.sign(
|
Signing.sign(
|
||||||
alignedFile,
|
alignedFile,
|
||||||
signedOutput,
|
signedOutput,
|
||||||
SigningOptions(
|
SigningOptions(
|
||||||
pArgs.cn,
|
patchingArgs.commonName,
|
||||||
pArgs.password,
|
patchingArgs.password,
|
||||||
pArgs.keystorePath ?: outputFile.absoluteFile.parentFile
|
patchingArgs.keystorePath ?: patchingArgs.outputFilePath.absoluteFile.parentFile
|
||||||
.resolve("${outputFile.nameWithoutExtension}.keystore")
|
.resolve("${patchingArgs.outputFilePath.nameWithoutExtension}.keystore")
|
||||||
.canonicalPath
|
.canonicalPath
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -200,46 +234,41 @@ internal object MainCommand : Runnable {
|
|||||||
} else
|
} else
|
||||||
alignedFile
|
alignedFile
|
||||||
|
|
||||||
// finally copy to the specified output file
|
logger.info("Copying ${finalFile.name} to ${patchingArgs.outputFilePath.name}")
|
||||||
logger.info("Copying ${finalFile.name} to ${outputFile.name}")
|
|
||||||
finalFile.copyTo(outputFile, overwrite = true)
|
|
||||||
|
|
||||||
// clean up the cache directory if needed
|
finalFile.copyTo(patchingArgs.outputFilePath, overwrite = true)
|
||||||
if (pArgs.clean)
|
adbManager?.install(AdbManager.Apk(patchingArgs.outputFilePath, patcher.context.packageMetadata.packageName))
|
||||||
cleanUp(pArgs.cacheDirectory)
|
|
||||||
|
|
||||||
// deploy if specified
|
if (patchingArgs.clean) {
|
||||||
adb?.deploy()
|
logger.info("Cleaning up temporary files")
|
||||||
|
patchingArgs.outputFilePath.delete()
|
||||||
if (pArgs.clean && args.deploy != null) Files.delete(outputFile.toPath())
|
cleanUp(patchingArgs.resourceCachePath)
|
||||||
|
}
|
||||||
logger.info("Finished")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cleanUp(cacheDirectory: String) {
|
private fun cleanUp(resourceCachePath: File) {
|
||||||
val result = if (File(cacheDirectory).deleteRecursively())
|
val result = if (resourceCachePath.deleteRecursively())
|
||||||
"Cleaned up cache directory"
|
"Cleaned up cache directory"
|
||||||
else
|
else
|
||||||
"Failed to clean up cache directory"
|
"Failed to clean up cache directory"
|
||||||
logger.info(result)
|
logger.info(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unmount() {
|
/**
|
||||||
val adb: Adb? = args.deploy?.let {
|
* Uninstall the specified package from the specified device.
|
||||||
Adb(
|
*
|
||||||
File("placeholder_file"),
|
*/
|
||||||
app.revanced.patcher.Patcher(PatcherOptions(args.inputFile, "")).context.packageMetadata.packageName,
|
private fun uninstall() = args.deviceSerial?.let { serial ->
|
||||||
args.deploy!!,
|
if (args.mount) {
|
||||||
false
|
AdbManager.RootAdbManager(serial, logger)
|
||||||
)
|
} else {
|
||||||
}
|
AdbManager.UserAdbManager(serial, logger)
|
||||||
adb?.uninstall()
|
}.uninstall(args.packageName!!)
|
||||||
}
|
} ?: logger.error("No device serial specified")
|
||||||
|
|
||||||
private fun printListOfPatches() {
|
private fun printListOfPatches() {
|
||||||
val logged = mutableListOf<String>()
|
val logged = mutableListOf<String>()
|
||||||
for (patchBundlePath in args.patchArgs?.patchBundles!!) for (patch in PatchBundle.Jar(patchBundlePath)
|
for (patch in PatchBundleLoader.Jar(*args.patchArgs!!.patchBundles.toTypedArray())) {
|
||||||
.loadPatches()) {
|
|
||||||
if (patch.patchName in logged) continue
|
if (patch.patchName in logged) continue
|
||||||
for (compatiblePackage in patch.compatiblePackages ?: continue) {
|
for (compatiblePackage in patch.compatiblePackages ?: continue) {
|
||||||
val packageEntryStr = buildString {
|
val packageEntryStr = buildString {
|
||||||
@ -271,4 +300,78 @@ internal object MainCommand : Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Patcher.filterPatchSelection(patches: PatchList) = buildList {
|
||||||
|
val packageName = context.packageMetadata.packageName
|
||||||
|
val packageVersion = context.packageMetadata.packageVersion
|
||||||
|
val patchingArgs = args.patchArgs!!.patchingArgs!!
|
||||||
|
|
||||||
|
patches.forEach patch@{ patch ->
|
||||||
|
val formattedPatchName = patch.patchName.lowercase().replace(" ", "-")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the patch is explicitly excluded.
|
||||||
|
*
|
||||||
|
* Cases:
|
||||||
|
* 1. -e patch.name
|
||||||
|
* 2. -i patch.name -e patch.name
|
||||||
|
*/
|
||||||
|
|
||||||
|
val excluded = patchingArgs.excludedPatches.contains(formattedPatchName)
|
||||||
|
if (excluded) return@patch logger.info("Excluding ${patch.patchName}")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the patch is constrained to packages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
patch.compatiblePackages?.let { packages ->
|
||||||
|
packages.singleOrNull { it.name == packageName }?.let { `package` ->
|
||||||
|
/**
|
||||||
|
* Check if the package version matches.
|
||||||
|
* If experimental is true, version matching will be skipped.
|
||||||
|
*/
|
||||||
|
|
||||||
|
val matchesVersion = patchingArgs.experimental || `package`.versions.let {
|
||||||
|
it.isEmpty() || it.any { version -> version == packageVersion }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchesVersion) return@patch logger.warn(
|
||||||
|
"${patch.patchName} is incompatible with version $packageVersion. " +
|
||||||
|
"This patch is only compatible with version " +
|
||||||
|
packages.joinToString(";") { `package` ->
|
||||||
|
"${`package`.name}: ${`package`.versions.joinToString(", ")}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
} ?: return@patch logger.trace(
|
||||||
|
"${patch.patchName} is incompatible with $packageName. " +
|
||||||
|
"This patch is only compatible with " +
|
||||||
|
packages.joinToString(", ") { `package` -> `package`.name }
|
||||||
|
)
|
||||||
|
|
||||||
|
return@let
|
||||||
|
} ?: logger.trace("$formattedPatchName: No constraint on packages.")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the patch is explicitly included.
|
||||||
|
*
|
||||||
|
* Cases:
|
||||||
|
* 1. --exclusive
|
||||||
|
* 2. --exclusive -i patch.name
|
||||||
|
*/
|
||||||
|
|
||||||
|
val exclusive = patchingArgs.exclusive
|
||||||
|
val explicitlyIncluded = patchingArgs.includedPatches.contains(formattedPatchName)
|
||||||
|
|
||||||
|
val implicitlyIncluded = !exclusive && patch.include // Case 3.
|
||||||
|
val exclusivelyIncluded = exclusive && explicitlyIncluded // Case 2.
|
||||||
|
|
||||||
|
val included = implicitlyIncluded || exclusivelyIncluded
|
||||||
|
if (!included) return@patch logger.info("${patch.patchName} excluded by default") // Case 1.
|
||||||
|
|
||||||
|
logger.trace("Adding $formattedPatchName")
|
||||||
|
|
||||||
|
add(patch)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,23 +0,0 @@
|
|||||||
package app.revanced.cli.patcher
|
|
||||||
|
|
||||||
import app.revanced.cli.command.PatchList
|
|
||||||
import app.revanced.patcher.PatcherResult
|
|
||||||
import app.revanced.utils.patcher.addPatchesFiltered
|
|
||||||
import app.revanced.utils.patcher.applyPatchesVerbose
|
|
||||||
import app.revanced.utils.patcher.mergeFiles
|
|
||||||
|
|
||||||
internal object Patcher {
|
|
||||||
internal fun start(
|
|
||||||
patcher: app.revanced.patcher.Patcher,
|
|
||||||
allPatches: PatchList
|
|
||||||
): PatcherResult {
|
|
||||||
// merge files like necessary integrations
|
|
||||||
patcher.mergeFiles()
|
|
||||||
// add patches, but filter incompatible or excluded patches
|
|
||||||
patcher.addPatchesFiltered(allPatches)
|
|
||||||
// apply patches
|
|
||||||
patcher.applyPatchesVerbose()
|
|
||||||
|
|
||||||
return patcher.save()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
package app.revanced.utils.adb
|
|
||||||
|
|
||||||
import app.revanced.cli.command.MainCommand.logger
|
|
||||||
import se.vidstige.jadb.JadbConnection
|
|
||||||
import se.vidstige.jadb.JadbDevice
|
|
||||||
import se.vidstige.jadb.managers.PackageManager
|
|
||||||
import java.io.File
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
internal class Adb(
|
|
||||||
private val file: File,
|
|
||||||
private val packageName: String,
|
|
||||||
deviceName: String,
|
|
||||||
private val install: Boolean = false,
|
|
||||||
private val logging: Boolean = true
|
|
||||||
) {
|
|
||||||
private val device: JadbDevice
|
|
||||||
|
|
||||||
init {
|
|
||||||
device = JadbConnection().devices.let { device -> device.find { it.serial == deviceName } ?: device.first() }
|
|
||||||
?: throw IllegalArgumentException("No such device with name $deviceName")
|
|
||||||
|
|
||||||
if (!install && device.run("su -h", false) != 0)
|
|
||||||
throw IllegalArgumentException("Root required on $deviceName. Task failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.replacePlaceholder(with: String? = null): String {
|
|
||||||
return this.replace(Constants.PLACEHOLDER, with ?: packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun deploy() {
|
|
||||||
if (install) {
|
|
||||||
logger.info("Installing without mounting")
|
|
||||||
|
|
||||||
PackageManager(device).install(file)
|
|
||||||
} else {
|
|
||||||
logger.info("Installing by mounting")
|
|
||||||
|
|
||||||
// push patched file
|
|
||||||
device.copy(Constants.PATH_INIT_PUSH, file)
|
|
||||||
|
|
||||||
// create revanced folder path
|
|
||||||
device.run("${Constants.COMMAND_CREATE_DIR} ${Constants.PATH_REVANCED}")
|
|
||||||
|
|
||||||
// prepare mounting the apk
|
|
||||||
device.run(Constants.COMMAND_PREPARE_MOUNT_APK.replacePlaceholder())
|
|
||||||
|
|
||||||
// push mount script
|
|
||||||
device.createFile(
|
|
||||||
Constants.PATH_INIT_PUSH,
|
|
||||||
Constants.CONTENT_MOUNT_SCRIPT.replacePlaceholder()
|
|
||||||
)
|
|
||||||
// install mount script
|
|
||||||
device.run(Constants.COMMAND_INSTALL_MOUNT.replacePlaceholder())
|
|
||||||
|
|
||||||
// unmount the apk for sanity
|
|
||||||
device.run(Constants.COMMAND_UMOUNT.replacePlaceholder())
|
|
||||||
// mount the apk
|
|
||||||
device.run(Constants.PATH_MOUNT.replacePlaceholder())
|
|
||||||
|
|
||||||
// relaunch app
|
|
||||||
device.run(Constants.COMMAND_RESTART.replacePlaceholder())
|
|
||||||
|
|
||||||
// log the app
|
|
||||||
log()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun uninstall() {
|
|
||||||
logger.info("Uninstalling by unmounting")
|
|
||||||
|
|
||||||
// unmount the apk
|
|
||||||
device.run(Constants.COMMAND_UMOUNT.replacePlaceholder())
|
|
||||||
|
|
||||||
// delete revanced app
|
|
||||||
device.run(Constants.COMMAND_DELETE.replacePlaceholder(Constants.PATH_REVANCED_APP).replacePlaceholder())
|
|
||||||
|
|
||||||
// delete mount script
|
|
||||||
device.run(Constants.COMMAND_DELETE.replacePlaceholder(Constants.PATH_MOUNT).replacePlaceholder())
|
|
||||||
|
|
||||||
logger.info("Finished uninstalling")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun log() {
|
|
||||||
val executor = Executors.newSingleThreadExecutor()
|
|
||||||
val pipe = if (logging) {
|
|
||||||
ProcessBuilder.Redirect.INHERIT
|
|
||||||
} else {
|
|
||||||
ProcessBuilder.Redirect.PIPE
|
|
||||||
}
|
|
||||||
|
|
||||||
val process = device.buildCommand(Constants.COMMAND_LOGCAT.replacePlaceholder())
|
|
||||||
.redirectOutput(pipe)
|
|
||||||
.redirectError(pipe)
|
|
||||||
.useExecutor(executor)
|
|
||||||
.start()
|
|
||||||
|
|
||||||
Thread.sleep(500) // give the app some time to start up.
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
while (device.run("${Constants.COMMAND_PID_OF} $packageName") == 0) {
|
|
||||||
Thread.sleep(1000)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw RuntimeException("An error occurred while monitoring the state of app", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info("Stopped logging because the app was closed")
|
|
||||||
process.destroy()
|
|
||||||
executor.shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
130
src/main/kotlin/app/revanced/utils/adb/AdbManager.kt
Normal file
130
src/main/kotlin/app/revanced/utils/adb/AdbManager.kt
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package app.revanced.utils.adb
|
||||||
|
|
||||||
|
import app.revanced.cli.logging.CliLogger
|
||||||
|
import app.revanced.utils.adb.AdbManager.Apk
|
||||||
|
import app.revanced.utils.adb.Constants.COMMAND_CREATE_DIR
|
||||||
|
import app.revanced.utils.adb.Constants.COMMAND_DELETE
|
||||||
|
import app.revanced.utils.adb.Constants.COMMAND_INSTALL_MOUNT
|
||||||
|
import app.revanced.utils.adb.Constants.COMMAND_PREPARE_MOUNT_APK
|
||||||
|
import app.revanced.utils.adb.Constants.COMMAND_RESTART
|
||||||
|
import app.revanced.utils.adb.Constants.COMMAND_UMOUNT
|
||||||
|
import app.revanced.utils.adb.Constants.CONTENT_MOUNT_SCRIPT
|
||||||
|
import app.revanced.utils.adb.Constants.PATH_INIT_PUSH
|
||||||
|
import app.revanced.utils.adb.Constants.PATH_INSTALLATION
|
||||||
|
import app.revanced.utils.adb.Constants.PATH_MOUNT
|
||||||
|
import app.revanced.utils.adb.Constants.PATH_PATCHED_APK
|
||||||
|
import app.revanced.utils.adb.Constants.PLACEHOLDER
|
||||||
|
import se.vidstige.jadb.JadbConnection
|
||||||
|
import se.vidstige.jadb.managers.Package
|
||||||
|
import se.vidstige.jadb.managers.PackageManager
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adb manager. Used to install and uninstall [Apk] files.
|
||||||
|
*
|
||||||
|
* @param deviceSerial The serial of the device.
|
||||||
|
*/
|
||||||
|
internal sealed class AdbManager(deviceSerial: String? = null, protected val logger: CliLogger? = null) : Closeable {
|
||||||
|
protected val device = JadbConnection().devices.find { device -> device.serial == deviceSerial }
|
||||||
|
?: throw IllegalArgumentException("The device with the serial $deviceSerial can not be found.")
|
||||||
|
|
||||||
|
init {
|
||||||
|
logger?.trace("Established connection to $deviceSerial")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the [Apk] file.
|
||||||
|
*
|
||||||
|
* @param apk The [Apk] file.
|
||||||
|
*/
|
||||||
|
open fun install(apk: Apk) {
|
||||||
|
logger?.info("Finished installing ${apk.file.name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls the package.
|
||||||
|
*
|
||||||
|
* @param packageName The package name.
|
||||||
|
*/
|
||||||
|
open fun uninstall(packageName: String) {
|
||||||
|
logger?.info("Finished uninstalling $packageName")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the [AdbManager] instance.
|
||||||
|
*/
|
||||||
|
override fun close() {
|
||||||
|
logger?.trace("Closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
class RootAdbManager(deviceSerial: String, logger: CliLogger? = null) : AdbManager(deviceSerial, logger) {
|
||||||
|
init {
|
||||||
|
if (!device.hasSu()) throw IllegalArgumentException("Root required on $deviceSerial. Task failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun install(apk: Apk) {
|
||||||
|
logger?.info("Installing by mounting")
|
||||||
|
|
||||||
|
val applyReplacement = getPlaceholderReplacement(
|
||||||
|
apk.packageName ?: throw IllegalArgumentException("Package name is required")
|
||||||
|
)
|
||||||
|
|
||||||
|
device.copyFile(apk.file, PATH_INIT_PUSH)
|
||||||
|
|
||||||
|
device.run("$COMMAND_CREATE_DIR $PATH_INSTALLATION")
|
||||||
|
device.run(COMMAND_PREPARE_MOUNT_APK.applyReplacement())
|
||||||
|
|
||||||
|
device.createFile(PATH_INIT_PUSH, CONTENT_MOUNT_SCRIPT.applyReplacement())
|
||||||
|
|
||||||
|
device.run(COMMAND_INSTALL_MOUNT.applyReplacement())
|
||||||
|
device.run(COMMAND_UMOUNT.applyReplacement()) // Sanity check.
|
||||||
|
device.run(PATH_MOUNT.applyReplacement())
|
||||||
|
device.run(COMMAND_RESTART.applyReplacement())
|
||||||
|
|
||||||
|
super.install(apk)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun uninstall(packageName: String) {
|
||||||
|
logger?.info("Uninstalling $packageName by unmounting and deleting the package")
|
||||||
|
|
||||||
|
val applyReplacement = getPlaceholderReplacement(packageName)
|
||||||
|
|
||||||
|
device.run(COMMAND_UMOUNT.applyReplacement(packageName))
|
||||||
|
device.run(COMMAND_DELETE.applyReplacement(PATH_PATCHED_APK).applyReplacement())
|
||||||
|
device.run(COMMAND_DELETE.applyReplacement(PATH_MOUNT).applyReplacement())
|
||||||
|
|
||||||
|
super.uninstall(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Utils {
|
||||||
|
private fun getPlaceholderReplacement(with: String): String.() -> String = { replace(PLACEHOLDER, with) }
|
||||||
|
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserAdbManager(deviceSerial: String, logger: CliLogger? = null) : AdbManager(deviceSerial, logger) {
|
||||||
|
private val packageManager = PackageManager(device)
|
||||||
|
|
||||||
|
override fun install(apk: Apk) {
|
||||||
|
PackageManager(device).install(apk.file)
|
||||||
|
|
||||||
|
super.install(apk)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun uninstall(packageName: String) {
|
||||||
|
logger?.info("Uninstalling $packageName")
|
||||||
|
|
||||||
|
packageManager.uninstall(Package(packageName))
|
||||||
|
|
||||||
|
super.uninstall(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apk file for [AdbManager].
|
||||||
|
*
|
||||||
|
* @param file The [Apk] file.
|
||||||
|
*/
|
||||||
|
internal class Apk(val file: File, val packageName: String? = null)
|
||||||
|
}
|
@ -2,28 +2,28 @@ package app.revanced.utils.adb
|
|||||||
|
|
||||||
import se.vidstige.jadb.JadbDevice
|
import se.vidstige.jadb.JadbDevice
|
||||||
import se.vidstige.jadb.RemoteFile
|
import se.vidstige.jadb.RemoteFile
|
||||||
import se.vidstige.jadb.ShellProcessBuilder
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.concurrent.Callable
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
internal fun JadbDevice.buildCommand(command: String, su: Boolean = true): ShellProcessBuilder {
|
// return the input or output stream, depending on which first returns a value
|
||||||
if (su) {
|
internal fun JadbDevice.run(command: String, su: Boolean = false) = with(this.startCommand(command, su)) {
|
||||||
return shellProcessBuilder("su -c \'$command\'")
|
Executors.newFixedThreadPool(2).let { service ->
|
||||||
|
arrayOf(inputStream, errorStream).map { stream ->
|
||||||
|
Callable { stream.bufferedReader().use { it.readLine() } }
|
||||||
|
}.let { tasks -> service.invokeAny(tasks).also { service.shutdown() } }
|
||||||
}
|
}
|
||||||
|
|
||||||
val args = command.split(" ") as ArrayList<String>
|
|
||||||
val cmd = args.removeFirst()
|
|
||||||
|
|
||||||
return shellProcessBuilder(cmd, *args.toTypedArray())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun JadbDevice.run(command: String, su: Boolean = true): Int {
|
internal fun JadbDevice.hasSu() =
|
||||||
return this.buildCommand(command, su).start().waitFor()
|
this.startCommand("su -h", false).waitFor() == 0
|
||||||
}
|
|
||||||
|
|
||||||
internal fun JadbDevice.copy(targetPath: String, file: File) {
|
internal fun JadbDevice.copyFile(file: File, targetFile: String) =
|
||||||
push(file, RemoteFile(targetPath))
|
push(file, RemoteFile(targetFile))
|
||||||
}
|
|
||||||
|
|
||||||
internal fun JadbDevice.createFile(targetFile: String, content: String) {
|
internal fun JadbDevice.createFile(targetFile: String, content: String) =
|
||||||
push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile))
|
push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile))
|
||||||
}
|
|
||||||
|
|
||||||
|
private fun JadbDevice.startCommand(command: String, su: Boolean) =
|
||||||
|
shellProcessBuilder(if (su) "su -c '$command'" else command).start()
|
@ -1,57 +1,40 @@
|
|||||||
package app.revanced.utils.adb
|
package app.revanced.utils.adb
|
||||||
|
|
||||||
internal object Constants {
|
internal object Constants {
|
||||||
// template placeholder to replace a string in commands
|
|
||||||
internal const val PLACEHOLDER = "TEMPLATE_PACKAGE_NAME"
|
internal const val PLACEHOLDER = "TEMPLATE_PACKAGE_NAME"
|
||||||
|
|
||||||
// utility commands
|
|
||||||
private const val COMMAND_CHMOD_MOUNT = "chmod +x"
|
|
||||||
internal const val COMMAND_PID_OF = "pidof -s"
|
|
||||||
internal const val COMMAND_CREATE_DIR = "mkdir -p"
|
|
||||||
internal const val COMMAND_LOGCAT = "logcat -c && logcat | grep AndroidRuntime"
|
|
||||||
internal const val COMMAND_RESTART = "pm resolve-activity --brief $PLACEHOLDER | tail -n 1 | xargs am start -n && kill ${'$'}($COMMAND_PID_OF $PLACEHOLDER)"
|
|
||||||
|
|
||||||
// default mount file name
|
|
||||||
private const val NAME_MOUNT_SCRIPT = "mount_revanced_$PLACEHOLDER.sh"
|
|
||||||
|
|
||||||
// initial directory to push files to via adb push
|
|
||||||
internal const val PATH_INIT_PUSH = "/data/local/tmp/revanced.delete"
|
internal const val PATH_INIT_PUSH = "/data/local/tmp/revanced.delete"
|
||||||
|
internal const val PATH_INSTALLATION = "/data/adb/revanced/"
|
||||||
|
internal const val PATH_PATCHED_APK = "$PATH_INSTALLATION$PLACEHOLDER.apk"
|
||||||
|
internal const val PATH_MOUNT = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
|
||||||
|
|
||||||
// revanced path
|
|
||||||
internal const val PATH_REVANCED = "/data/adb/revanced/"
|
|
||||||
|
|
||||||
// revanced apk path
|
|
||||||
internal const val PATH_REVANCED_APP = "$PATH_REVANCED$PLACEHOLDER.apk"
|
|
||||||
|
|
||||||
// delete command
|
|
||||||
internal const val COMMAND_DELETE = "rm -rf $PLACEHOLDER"
|
internal const val COMMAND_DELETE = "rm -rf $PLACEHOLDER"
|
||||||
|
internal const val COMMAND_CREATE_DIR = "mkdir -p"
|
||||||
|
internal const val COMMAND_RESTART = "pm resolve-activity --brief $PLACEHOLDER | tail -n 1 | " +
|
||||||
|
"xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)"
|
||||||
|
|
||||||
// mount script path
|
internal const val COMMAND_PREPARE_MOUNT_APK = "base_path=\"$PATH_PATCHED_APK\" && " +
|
||||||
internal const val PATH_MOUNT = "/data/adb/service.d/$NAME_MOUNT_SCRIPT"
|
"mv $PATH_INIT_PUSH ${'$'}base_path && " +
|
||||||
|
"chmod 644 ${'$'}base_path && " +
|
||||||
|
"chown system:system ${'$'}base_path && " +
|
||||||
|
"chcon u:object_r:apk_data_file:s0 ${'$'}base_path"
|
||||||
|
|
||||||
// move to revanced apk path & set permissions
|
|
||||||
internal const val COMMAND_PREPARE_MOUNT_APK =
|
|
||||||
"base_path=\"$PATH_REVANCED_APP\" && mv $PATH_INIT_PUSH ${'$'}base_path && chmod 644 ${'$'}base_path && chown system:system ${'$'}base_path && chcon u:object_r:apk_data_file:s0 ${'$'}base_path"
|
|
||||||
|
|
||||||
// unmount command
|
|
||||||
internal const val COMMAND_UMOUNT =
|
internal const val COMMAND_UMOUNT =
|
||||||
"grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d \" \" -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done"
|
"grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d \" \" -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done"
|
||||||
|
|
||||||
// install mount script & set permissions
|
internal const val COMMAND_INSTALL_MOUNT = "mv $PATH_INIT_PUSH $PATH_MOUNT && chmod +x $PATH_MOUNT"
|
||||||
internal const val COMMAND_INSTALL_MOUNT = "mv $PATH_INIT_PUSH $PATH_MOUNT && $COMMAND_CHMOD_MOUNT $PATH_MOUNT"
|
|
||||||
|
|
||||||
// mount script
|
internal const val CONTENT_MOUNT_SCRIPT =
|
||||||
internal val CONTENT_MOUNT_SCRIPT =
|
|
||||||
"""
|
"""
|
||||||
#!/system/bin/sh
|
#!/system/bin/sh
|
||||||
MAGISKTMP="${'$'}(magisk --path)" || MAGISKTMP=/sbin
|
MAGISKTMP="${'$'}(magisk --path)" || MAGISKTMP=/sbin
|
||||||
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
|
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
|
||||||
while [ "${'$'}(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
|
while [ "${'$'}(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
|
||||||
|
|
||||||
base_path="$PATH_REVANCED_APP"
|
base_path="$PATH_PATCHED_APK"
|
||||||
stock_path=${'$'}( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
|
stock_path=${'$'}( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
|
||||||
|
|
||||||
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
|
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
|
||||||
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
|
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
|
||||||
""".trimIndent()
|
"""
|
||||||
}
|
}
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
package app.revanced.utils.patcher
|
|
||||||
|
|
||||||
import app.revanced.cli.command.MainCommand.args
|
|
||||||
import app.revanced.cli.command.MainCommand.logger
|
|
||||||
import app.revanced.cli.command.PatchList
|
|
||||||
import app.revanced.patcher.Patcher
|
|
||||||
import app.revanced.patcher.data.Context
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.include
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
|
|
||||||
fun Patcher.addPatchesFiltered(allPatches: PatchList) {
|
|
||||||
val packageName = this.context.packageMetadata.packageName
|
|
||||||
val packageVersion = this.context.packageMetadata.packageVersion
|
|
||||||
|
|
||||||
val includedPatches = mutableListOf<Class<out Patch<Context>>>()
|
|
||||||
allPatches.forEach patchLoop@{ patch ->
|
|
||||||
val compatiblePackages = patch.compatiblePackages
|
|
||||||
val args = args.patchArgs?.patchingArgs!!
|
|
||||||
|
|
||||||
val prefix = "Skipping ${patch.patchName}"
|
|
||||||
|
|
||||||
if (compatiblePackages == null) logger.trace("${patch.patchName}: No package constraints.")
|
|
||||||
else {
|
|
||||||
if (!compatiblePackages.any { it.name == packageName }) {
|
|
||||||
logger.trace("$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 $compatibleWith")
|
|
||||||
return@patchLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val kebabCasedPatchName = patch.patchName.lowercase().replace(" ", "-")
|
|
||||||
if (args.excludedPatches.contains(kebabCasedPatchName)) {
|
|
||||||
logger.info("$prefix: Manually excluded")
|
|
||||||
return@patchLoop
|
|
||||||
} else if ((!patch.include || args.exclusive) && !args.includedPatches.contains(kebabCasedPatchName)) {
|
|
||||||
logger.info("$prefix: Excluded by default")
|
|
||||||
return@patchLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace("Adding ${patch.patchName}")
|
|
||||||
includedPatches.add(patch)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addPatches(includedPatches)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Patcher.applyPatchesVerbose() {
|
|
||||||
this.executePatches().forEach { (patch, result) ->
|
|
||||||
if (result.isSuccess) {
|
|
||||||
logger.info("$patch succeeded")
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
logger.error("$patch failed:")
|
|
||||||
result.exceptionOrNull()!!.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Patcher.mergeFiles() {
|
|
||||||
this.addIntegrations(args.patchArgs?.patchingArgs!!.mergeFiles) { file ->
|
|
||||||
logger.info("Merging $file")
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,10 @@ package app.revanced.patcher.options
|
|||||||
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.data.Context
|
||||||
import app.revanced.patcher.patch.*
|
import app.revanced.patcher.patch.BytecodePatch
|
||||||
|
import app.revanced.patcher.patch.OptionsContainer
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import app.revanced.patcher.patch.PatchOption
|
||||||
import app.revanced.utils.Options
|
import app.revanced.utils.Options
|
||||||
import app.revanced.utils.Options.setOptions
|
import app.revanced.utils.Options.setOptions
|
||||||
import org.junit.jupiter.api.MethodOrderer
|
import org.junit.jupiter.api.MethodOrderer
|
||||||
@ -11,8 +14,8 @@ import org.junit.jupiter.api.Test
|
|||||||
import org.junit.jupiter.api.TestMethodOrder
|
import org.junit.jupiter.api.TestMethodOrder
|
||||||
|
|
||||||
class PatchOptionsTestPatch : BytecodePatch() {
|
class PatchOptionsTestPatch : BytecodePatch() {
|
||||||
override fun execute(context: BytecodeContext): PatchResult {
|
override fun execute(context: BytecodeContext) {
|
||||||
return PatchResultSuccess()
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : OptionsContainer() {
|
companion object : OptionsContainer() {
|
||||||
@ -32,7 +35,7 @@ class PatchOptionsTestPatch : BytecodePatch() {
|
|||||||
|
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
||||||
internal object PatchOptionOptionsTest {
|
internal object PatchOptionOptionsTest {
|
||||||
private var patches = listOf(PatchOptionsTestPatch::class.java as Class<out Patch<Context>>)
|
private var patches = listOf(PatchOptionsTestPatch::class.java as Class<out Patch<Context<*>>>)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
|
Loading…
Reference in New Issue
Block a user