-
-
Notifications
You must be signed in to change notification settings - Fork 176
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
28 changed files
with
2,740 additions
and
1,690 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,129 +1,171 @@ | ||
# 🛠️ Using ReVanced CLI | ||
|
||
Learn how to use ReVanced CLI. | ||
The following examples will show you how to perform basic operations. | ||
You can list patches, patch an app, uninstall, and install an app. | ||
|
||
## 🔨 Usage | ||
|
||
ReVanced CLI is divided into the following fundamental commands: | ||
|
||
- ### 🚀 Show all available options for ReVanced CLI | ||
|
||
```bash | ||
java -jar revanced-cli.jar -h | ||
``` | ||
|
||
- ### 📃 List patches | ||
|
||
```bash | ||
java -jar revanced-cli.jar list-patches \ | ||
--with-packages \ | ||
--with-versions \ | ||
--with-options \ | ||
revanced-patches.jar [<patch-bundle> ...] | ||
``` | ||
|
||
- ### ⚙️ Generate options | ||
|
||
This will generate an `options.json` file for the patches from a list of supplied patch bundles. | ||
The file can be supplied to ReVanced CLI later on. | ||
|
||
```bash | ||
java -jar revanced-cli.jar options \ | ||
--path options.json \ | ||
--overwrite \ | ||
revanced-patches.jar [<patch-bundle> ...] | ||
``` | ||
|
||
> **ℹ️ Note** | ||
> A default `options.json` file will be automatically created if it does not exist | ||
> without any need for intervention when using the `patch` command. | ||
- ### 💉 Patch an app | ||
|
||
You can patch apps by supplying patch bundles and the app to patch. | ||
After patching, ReVanced CLI can install the patched app on your device using two methods: | ||
|
||
> **💡 Tip** | ||
> For ReVanced CLI to be able to install the patched app on your device, make sure ADB is working: | ||
> | ||
> ```bash | ||
> adb shell exit | ||
> ``` | ||
> | ||
> If you want to mount the patched app on top of the un-patched app, make sure you have root permissions: | ||
> | ||
> ```bash | ||
> adb shell su -c exit | ||
> ``` | ||
> **⚠️ Warning** | ||
> Some patches may require integrations | ||
> such as [ReVanced Integrations](https://github.com/revanced/revanced-integrations). | ||
> Supply them with the option `--merge`. ReVanced Patcher will automatically determine if they are necessary. | ||
- #### 👾 Patch an app and install it on your device regularly | ||
```bash | ||
java -jar revanced-cli.jar patch \ | ||
--patch-bundle revanced-patches.jar \ | ||
-d \ | ||
input.apk | ||
``` | ||
- #### 👾 Patch an app and mount it on top of the un-patched app with root permissions | ||
> **❗ Caution** | ||
> Ensure that the same app you are patching and mounting over is installed on your device: | ||
> | ||
> ```bash | ||
> adb install app.apk | ||
> ``` | ||
```bash | ||
java -jar revanced-cli.jar patch \ | ||
--patch-bundle revanced-patches.jar \ | ||
--include "Some patch" \ | ||
--ii 123 \ | ||
--exclude "Some other patch" \ | ||
-d \ | ||
--mount \ | ||
app.apk | ||
``` | ||
> **💡 Tip** | ||
> You can use the option `--ii` to include or `--ie` to exclude | ||
> patches by their index in relation to supplied patch bundles, | ||
> similarly to the option `--include` and `--exclude`. | ||
> | ||
> This is useful in case two patches have the same name, and you must include or exclude one. | ||
> The patch index is calculated by the position of the patch in the list of patches | ||
> from patch bundles supplied using the option `--patch-bundle`. | ||
> | ||
> You can list all patches with their indices using the command `list-patches`. | ||
> | ||
> Keep in mind that the indices can change based on the order of the patch bundles supplied, | ||
> as well if the patch bundles are updated because patches can be added or removed. | ||
- ### 🗑️ Uninstall an app | ||
```bash | ||
java -jar revanced-cli.jar utility uninstall \ | ||
--package-name <package-name> \ | ||
[<device-serial>] | ||
``` | ||
> **💡 Tip** | ||
> You can unmount an APK file | ||
> by adding the option `--unmount`. | ||
- ### ️ 📦 Install an app | ||
```bash | ||
java -jar revanced-cli.jar utility install \ | ||
-a input.apk \ | ||
[<device-serial>] | ||
``` | ||
> **💡 Tip** | ||
> You can mount an APK file | ||
> by supplying the app's package name to mount the supplied APK file over the option `-mount`. | ||
## 🚀 Show all commands | ||
|
||
```bash | ||
java -jar revanced-cli.jar -h | ||
``` | ||
|
||
## 📃 List patches | ||
|
||
```bash | ||
java -jar revanced-cli.jar list-patches --with-packages --with-versions --with-options patches.rvp | ||
``` | ||
|
||
## 💉 Patch an app | ||
|
||
To patch an app using the default list of patches, use the `patch` command: | ||
|
||
```bash | ||
java -jar revanced-cli.jar patch -p patches.rvp input.apk | ||
``` | ||
|
||
You can also use multiple RVP files: | ||
|
||
```bash | ||
java -jar revanced-cli.jar patch -p patches.rvp -p another-patches.rvp input.apk | ||
``` | ||
|
||
To change the default set of enabled or disabled patches, use the option `-e` or `-d` to enable or disable specific patches. | ||
You can use the `list-patches` command to see which patches are enabled by default. | ||
|
||
To only enable specific patches, you can use the option `--exclusive` combined with `-e`. | ||
Remember that the options `-e` and `-d` match the patch's name exactly. Here is an example: | ||
|
||
```bash | ||
java -jar revanced-cli.jar patch -p patches.rvp --exclusive -e "Patch name" -e "Another patch name" input.apk | ||
``` | ||
|
||
You can also use the options `--ei` or `--di` to enable or disable patches by their index. | ||
This is useful, if two patches happen to have the same name, or if typing the names is too cumbersome. | ||
To know the indices of patches, use the command `list-patches`: | ||
|
||
```bash | ||
java -jar revanced-cli.jar list-patches patches.rvp | ||
``` | ||
|
||
Then you can use the indices to enable or disable patches: | ||
|
||
```bash | ||
java -jar revanced-cli.jar patch -p patches.rvp --ei 123 --di 456 input.apk | ||
``` | ||
|
||
You can combine the option `-e`, `-d`, `--ei`, `--di` and `--exclusive`. Here is an example: | ||
|
||
```bash | ||
java -jar revanced-cli.jar patch -p patches.rvp --exclusive -e "Patch name" --ei 123 input.apk | ||
``` | ||
|
||
|
||
> [!TIP] | ||
> You can use the option `-i` to automatically install the patched app after patching. | ||
> Make sure ADB is working: | ||
> | ||
> ```bash | ||
> adb shell exit | ||
> ``` | ||
> [!TIP] | ||
> You can use the option `--mount` to mount the patched app on top of the un-patched app. | ||
> Make sure you have root permissions and the same app you are patching and mounting over is installed on your device: | ||
> | ||
> ```bash | ||
> adb shell su -c exit | ||
> adb install input.apk | ||
> ``` | ||
Patches can have options you can set using the option `-O` alongside the option to include the patch by name or index. | ||
To know the options of a patch, use the option `--with-options` when listing patches: | ||
```bash | ||
java -jar revanced-cli.jar list-patches --with-options patches.rvp | ||
``` | ||
Each patch can have multiple options. You can set them using the option `-O`. | ||
For example, to set the options for the patch with the name `Patch name` | ||
with the key `key1` and `key2` to `value1` and `value2` respectively, use the following command: | ||
|
||
```bash | ||
java -jar revanced-cli.jar patch -p patches.rvp -e "Patch name" -Okey1=value1 -Okey2=value2 input.apk | ||
``` | ||
|
||
If you want to set the option value to `null`, you can omit the value: | ||
|
||
```bash | ||
java -jar revanced-cli.jar patch -p patches.rvp -i "Patch name" -Okey1 input.apk | ||
``` | ||
|
||
> [!WARNING] | ||
> Option values are usually typed. If you set a value with the wrong type, the patch can fail. | ||
> The value types can be seen when listing patches with the option `--with-options`. | ||
> | ||
> Example option values: | ||
> | ||
> - String: `string` | ||
> - Boolean: `true`, `false` | ||
> - Integer: `123` | ||
> - Double: `1.0` | ||
> - Float: `1.0f` | ||
> - Long: `1234567890`, `1L` | ||
> - List: `[item1,item2,item3]` | ||
> - List of type `Any`: `[item1,123,true,1.0]` | ||
> - Empty list of type `Any`: `[]` | ||
> - Typed empty list: `int[]` | ||
> - Typed and nested empty list: `[int[]]` | ||
> - List with null value and two empty strings: `[null,\'\',\"\"]` | ||
> | ||
> Quotes and commas escaped in strings (`\"`, `\'`, `\,`) are parsed as part of the string. | ||
> List items are recursively parsed, so you can escape values in lists: | ||
> | ||
> - Escaped integer as a string: `[\'123\']` | ||
> - Escaped boolean as a string: `[\'true\']` | ||
> - Escaped list as a string: `[\'[item1,item2]\']` | ||
> - Escaped null value as a string: `[\'null\']` | ||
> - List with an integer, an integer as a string and a string with a comma, and an escaped list: [`123,\'123\',str\,ing`,`\'[]\'`] | ||
> | ||
> Example command with an escaped integer as a string: | ||
> | ||
> ```bash | ||
> java -jar revanced-cli.jar -p patches.rvp -e "Patch name" -OstringKey=\'1\' input.apk | ||
> ``` | ||
## 📦 Install an app manually | ||
```bash | ||
java -jar revanced-cli.jar utility install -a input.apk | ||
``` | ||
> [!TIP] | ||
> You can use the option `--mount` to mount the patched app on top of the un-patched app. | ||
> Make sure you have root permissions and the same app you are patching and mounting over is installed on your device: | ||
> | ||
> ```bash | ||
> adb shell su -c exit | ||
> adb install input.apk | ||
> ``` | ||
## 🗑️ Uninstall an app manually | ||
Here `<package-name>` is the package name of the app you want to uninstall: | ||
```bash | ||
java -jar revanced-cli.jar utility uninstall --package-name <package-name> | ||
``` | ||
If the app is mounted, you need to unmount it by using the option `--unmount`: | ||
|
||
```bash | ||
java -jar revanced-cli.jar utility uninstall --package-name <package-name> --unmount | ||
``` | ||
|
||
> [!TIP] | ||
> By default, the app is installed or uninstalled to the first connected device. | ||
> You can append one or more devices by their serial to install or uninstall an app on your selected choice of devices: | ||
> | ||
> ```bash | ||
> java -jar revanced-cli.jar utility uninstall --package-name <package-name> [<device-serial> ...] | ||
> ``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
org.gradle.parallel = true | ||
org.gradle.caching = true | ||
kotlin.code.style = official | ||
version = 4.6.0 | ||
version = 5.0.0-dev.11 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,18 @@ | ||
[versions] | ||
shadow = "8.1.1" | ||
kotlin = "1.9.22" | ||
kotlinx-coroutines-core = "1.7.3" | ||
picocli = "4.7.5" | ||
revanced-patcher = "19.3.1" | ||
revanced-library = "2.3.0" | ||
kotlin = "2.0.20" | ||
kotlinx = "1.8.1" | ||
picocli = "4.7.6" | ||
revanced-patcher = "21.0.0" | ||
revanced-library = "3.0.2" | ||
|
||
[libraries] | ||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } | ||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" } | ||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" } | ||
picocli = { module = "info.picocli:picocli", version.ref = "picocli" } | ||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } | ||
revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" } | ||
revanced-library = { module = "app.revanced:revanced-library-jvm", version.ref = "revanced-library" } | ||
|
||
[plugins] | ||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } | ||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } | ||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
distributionBase=GRADLE_USER_HOME | ||
distributionPath=wrapper/dists | ||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip | ||
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c | ||
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab | ||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip | ||
networkTimeout=10000 | ||
validateDistributionUrl=true | ||
zipStoreBase=GRADLE_USER_HOME | ||
zipStorePath=wrapper/dist | ||
zipStorePath=wrapper/dists |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1 @@ | ||
rootProject.name = "revanced-cli" | ||
|
||
buildCache { | ||
local { | ||
isEnabled = "CI" !in System.getenv() | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
src/main/kotlin/app/revanced/cli/command/CommandUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package app.revanced.cli.command | ||
|
||
import picocli.CommandLine | ||
|
||
class OptionKeyConverter : CommandLine.ITypeConverter<String> { | ||
override fun convert(value: String): String = value | ||
} | ||
|
||
class OptionValueConverter : CommandLine.ITypeConverter<Any?> { | ||
override fun convert(value: String?): Any? { | ||
value ?: return null | ||
|
||
return when { | ||
value.startsWith("[") && value.endsWith("]") -> { | ||
val innerValue = value.substring(1, value.length - 1) | ||
|
||
buildList { | ||
var nestLevel = 0 | ||
var insideQuote = false | ||
var escaped = false | ||
|
||
val item = buildString { | ||
for (char in innerValue) { | ||
when (char) { | ||
'\\' -> { | ||
if (escaped || nestLevel != 0) { | ||
append(char) | ||
} | ||
|
||
escaped = !escaped | ||
} | ||
|
||
'"', '\'' -> { | ||
if (!escaped) { | ||
insideQuote = !insideQuote | ||
} else { | ||
escaped = false | ||
} | ||
|
||
append(char) | ||
} | ||
|
||
'[' -> { | ||
if (!insideQuote) { | ||
nestLevel++ | ||
} | ||
|
||
append(char) | ||
} | ||
|
||
']' -> { | ||
if (!insideQuote) { | ||
nestLevel-- | ||
|
||
if (nestLevel == -1) { | ||
return value | ||
} | ||
} | ||
|
||
append(char) | ||
} | ||
|
||
',' -> if (nestLevel == 0) { | ||
if (insideQuote) { | ||
append(char) | ||
} else { | ||
add(convert(toString())) | ||
setLength(0) | ||
} | ||
} else { | ||
append(char) | ||
} | ||
|
||
else -> append(char) | ||
} | ||
} | ||
} | ||
|
||
if (item.isNotEmpty()) { | ||
add(convert(item)) | ||
} | ||
} | ||
} | ||
|
||
value.startsWith("\"") && value.endsWith("\"") -> value.substring(1, value.length - 1) | ||
value.startsWith("'") && value.endsWith("'") -> value.substring(1, value.length - 1) | ||
value.endsWith("f") -> value.dropLast(1).toFloat() | ||
value.endsWith("L") -> value.dropLast(1).toLong() | ||
value.equals("true", ignoreCase = true) -> true | ||
value.equals("false", ignoreCase = true) -> false | ||
value.toIntOrNull() != null -> value.toInt() | ||
value.toLongOrNull() != null -> value.toLong() | ||
value.toDoubleOrNull() != null -> value.toDouble() | ||
value.toFloatOrNull() != null -> value.toFloat() | ||
value == "null" -> null | ||
value == "int[]" -> emptyList<Int>() | ||
value == "long[]" -> emptyList<Long>() | ||
value == "double[]" -> emptyList<Double>() | ||
value == "float[]" -> emptyList<Float>() | ||
value == "boolean[]" -> emptyList<Boolean>() | ||
value == "string[]" -> emptyList<String>() | ||
else -> value | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 0 additions & 62 deletions
62
src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt
This file was deleted.
Oops, something went wrong.
398 changes: 215 additions & 183 deletions
398
src/main/kotlin/app/revanced/cli/command/PatchCommand.kt
Large diffs are not rendered by default.
Oops, something went wrong.
41 changes: 30 additions & 11 deletions
41
src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,63 @@ | ||
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 | ||
|
||
@Command( | ||
name = "install", | ||
description = ["Install an APK file to devices with the supplied ADB device serials"], | ||
description = ["Install an APK file."], | ||
) | ||
internal object InstallCommand : Runnable { | ||
private val logger = Logger.getLogger(InstallCommand::class.java.name) | ||
private val logger = Logger.getLogger(this::class.java.name) | ||
|
||
@Parameters( | ||
description = ["ADB device serials. If not supplied, the first connected device will be used."], | ||
description = ["Serial of ADB devices. If not supplied, the first connected device will be used."], | ||
arity = "0..*", | ||
) | ||
private var deviceSerials: Array<String>? = null | ||
|
||
@Option( | ||
names = ["-a", "--apk"], | ||
description = ["APK file to be installed"], | ||
description = ["APK file to be installed."], | ||
required = true, | ||
) | ||
private lateinit var apk: File | ||
|
||
@Option( | ||
names = ["-m", "--mount"], | ||
description = ["Mount the supplied APK file over the app with the supplied package name"], | ||
description = ["Mount the supplied APK file over the app with the supplied package name."], | ||
) | ||
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() | ||
} | ||
} | ||
} |
43 changes: 32 additions & 11 deletions
43
src/main/kotlin/app/revanced/cli/command/utility/UninstallCommand.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,66 @@ | ||
package app.revanced.cli.command.utility | ||
|
||
import app.revanced.library.adb.AdbManager | ||
import app.revanced.library.installation.installer.AdbInstaller | ||
import app.revanced.library.installation.installer.AdbInstallerResult | ||
import app.revanced.library.installation.installer.AdbRootInstaller | ||
import app.revanced.library.installation.installer.RootInstallerResult | ||
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 | ||
|
||
@Command( | ||
name = "uninstall", | ||
description = ["Uninstall a patched app from the devices with the supplied ADB device serials"], | ||
description = ["Uninstall a patched app."], | ||
) | ||
internal object UninstallCommand : Runnable { | ||
private val logger = Logger.getLogger(UninstallCommand::class.java.name) | ||
private val logger = Logger.getLogger(this::class.java.name) | ||
|
||
@Parameters( | ||
description = ["ADB device serials. If not supplied, the first connected device will be used."], | ||
description = ["Serial of ADB devices. If not supplied, the first connected device will be used."], | ||
arity = "0..*", | ||
) | ||
private var deviceSerials: Array<String>? = null | ||
|
||
@Option( | ||
names = ["-p", "--package-name"], | ||
description = ["Package name of the app to uninstall"], | ||
description = ["Package name of the app to uninstall."], | ||
required = true, | ||
) | ||
private lateinit var packageName: String | ||
|
||
@Option( | ||
names = ["-u", "--unmount"], | ||
description = ["Uninstall by unmounting the patched APK file"], | ||
description = ["Uninstall the patched APK file by unmounting."], | ||
showDefaultValue = ALWAYS, | ||
) | ||
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() | ||
} | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
src/test/kotlin/app/revanced/cli/command/OptionValueConverterTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package app.revanced.cli.command | ||
|
||
import kotlin.test.Test | ||
|
||
class OptionValueConverterTest { | ||
@Test | ||
fun `converts to string`() { | ||
"string" convertsTo "string" because "Strings should remain the same" | ||
} | ||
|
||
@Test | ||
fun `converts to null`() { | ||
"null" convertsTo null because "null should convert to null" | ||
"\"null\"" convertsTo "null" because "Escaped null should convert to a string" | ||
} | ||
|
||
@Test | ||
fun `converts to boolean`() { | ||
"true" convertsTo true because "true should convert to a boolean true" | ||
"True" convertsTo true because "Casing should not matter" | ||
"\"true\"" convertsTo "true" because "Escaped booleans should be converted to strings" | ||
"\'True\'" convertsTo "True" because "Casing in escaped booleans should not matter" | ||
"tr ue" convertsTo "tr ue" because "Malformed booleans should be converted to strings" | ||
} | ||
|
||
@Test | ||
fun `converts to numbers`() { | ||
"1" convertsTo 1 because "Integers should convert to integers" | ||
"1.0" convertsTo 1.0 because "Doubles should convert to doubles" | ||
"1.0f" convertsTo 1.0f because "The suffix f should convert to a float" | ||
Long.MAX_VALUE.toString() convertsTo Long.MAX_VALUE because "Values that are too large for an integer should convert to longs" | ||
"1L" convertsTo 1L because "The suffix L should convert to a long" | ||
} | ||
|
||
@Test | ||
fun `converts escaped numbers to string`() { | ||
"\"1\"" convertsTo "1" because "Escaped numbers should convert to strings" | ||
"\"1.0\"" convertsTo "1.0" because "Escaped doubles should convert to strings" | ||
"\"1L\"" convertsTo "1L" because "Escaped longs should convert to strings" | ||
"\'1\'" convertsTo "1" because "Single quotes should not be treated as escape symbols" | ||
"\'.0\'" convertsTo ".0" because "Single quotes should not be treated as escape symbols" | ||
"\'1L\'" convertsTo "1L" because "Single quotes should not be treated as escape symbols" | ||
} | ||
|
||
@Test | ||
fun `trims escape symbols once`() { | ||
"\"\"\"1\"\"\"" convertsTo "\"\"1\"\"" because "The escape symbols should be trimmed once" | ||
"\'\'\'1\'\'\'" convertsTo "''1''" because "Single quotes should not be treated as escape symbols" | ||
} | ||
|
||
@Test | ||
fun `converts lists`() { | ||
"1,2" convertsTo "1,2" because "Lists without square brackets should not be converted to lists" | ||
"[1,2" convertsTo "[1,2" because "Invalid lists should not be converted to lists" | ||
"\"[1,2]\"" convertsTo "[1,2]" because "Lists with escaped square brackets should not be converted to lists" | ||
|
||
"[]" convertsTo emptyList<Any>() because "Empty untyped lists should convert to empty lists of any" | ||
"int[]" convertsTo emptyList<Int>() because "Empty typed lists should convert to lists of the specified type" | ||
"[[int[]]]" convertsTo listOf(listOf(emptyList<Int>())) because "Nested typed lists should convert to nested lists of the specified type" | ||
"[\"int[]\"]" convertsTo listOf("int[]") because "Lists of escaped empty typed lists should not be converted to lists" | ||
|
||
"[1,2,3]" convertsTo listOf(1, 2, 3) because "Lists of integers should convert to lists of integers" | ||
"[[1]]" convertsTo listOf(listOf(1)) because "Nested lists with one element should convert to nested lists" | ||
"[[1,2],[3,4]]" convertsTo listOf(listOf(1, 2), listOf(3, 4)) because "Nested lists should convert to nested lists" | ||
|
||
"[\"1,2\"]" convertsTo listOf("1,2") because "Values in lists should not be split by commas in strings" | ||
"[[\"1,2\"]]" convertsTo listOf(listOf("1,2")) because "Values in nested lists should not be split by commas in strings" | ||
|
||
"[\"\\\"\"]" convertsTo listOf("\"") because "Escaped quotes in strings should be converted to quotes" | ||
"[[\"\\\"\"]]" convertsTo listOf(listOf("\"")) because "Escaped quotes in strings nested in lists should be converted to quotes" | ||
"[.1,.2f,,true,FALSE]" convertsTo listOf(.1, .2f, "", true, false) because "Values in lists should be converted to the correct type" | ||
} | ||
|
||
private val convert = OptionValueConverter()::convert | ||
|
||
private infix fun String.convertsTo(to: Any?) = convert(this) to to | ||
private infix fun Pair<Any?, Any?>.because(reason: String) = assert(this.first == this.second) { reason } | ||
} |