Skip to content

Commit

Permalink
chore: Merge branch dev to main (#328)
Browse files Browse the repository at this point in the history
oSumAtrIX authored Nov 10, 2024
2 parents 1496e82 + dbbc007 commit 5d8beae
Showing 28 changed files with 2,740 additions and 1,690 deletions.
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -70,7 +70,7 @@ body:
Before creating a new bug report, please keep the following in mind:
- **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/ReVanced/revanced-cli/labels/Bug%20report).
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-cli/issues?q=label%3A%22Bug+report%22).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
@@ -102,7 +102,7 @@ body:
label: Acknowledgements
description: Your bug report will be closed if you don't follow the checklist below.
options:
- label: This issue is not a duplicate of an existing bug report.
- label: I have checked all open and closed bug reports and this is not a duplicate.
required: true
- label: I have chosen an appropriate title.
required: true
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -70,8 +70,8 @@ body:
Before creating a new feature request, please keep the following in mind:
- **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/ReVanced/revanced-cli//labels/Feature%20request).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli//blob/main/CONTRIBUTING.md).
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-cli/issues?q=label%3A%22Feature+request%22).
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli//blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:
@@ -98,7 +98,7 @@ body:
label: Acknowledgements
description: Your feature request will be closed if you don't follow the checklist below.
options:
- label: This issue is not a duplicate of an existing feature request.
- label: I have checked all open and closed feature requests and this is not a duplicate.
required: true
- label: I have chosen an appropriate title.
required: true
6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@ on:
jobs:
release:
name: Release
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -42,9 +44,9 @@ jobs:
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
fingerprint: ${{ env.GPG_FINGERPRINT }}
fingerprint: ${{ vars.GPG_FINGERPRINT }}

- name: Release
env:
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm exec semantic-release
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -121,5 +121,5 @@ node_modules/
revanced-cache/
options.toml

# Generated by an Android project (such as ReVanced Integrations)
# Generated by Android projects
local.properties
3 changes: 2 additions & 1 deletion .releaserc
Original file line number Diff line number Diff line change
@@ -23,7 +23,8 @@
"assets": [
"CHANGELOG.md",
"gradle.properties"
]
],
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
83 changes: 83 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,86 @@
# [5.0.0-dev.11](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.10...v5.0.0-dev.11) (2024-11-10)


### Bug Fixes

* List if patch option is required ([#346](https://github.com/ReVanced/revanced-cli/issues/346)) ([98ff0c3](https://github.com/ReVanced/revanced-cli/commit/98ff0c34fa71c3b3ecd96157d45a30ee2b8979c6))

# [5.0.0-dev.10](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.9...v5.0.0-dev.10) (2024-11-05)


### Bug Fixes

* Make CLI ArgGroup non-exclusive to be able to disable and enable patches at the same time ([1bb0d13](https://github.com/ReVanced/revanced-cli/commit/1bb0d13726fd5790c59cb6d28df3618c7606710d))

# [5.0.0-dev.9](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.8...v5.0.0-dev.9) (2024-11-05)


### Bug Fixes

* Print in new line correctly ([c2dc9d7](https://github.com/ReVanced/revanced-cli/commit/c2dc9d76be33c98284741e23c406500483c47753))

# [5.0.0-dev.8](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.7...v5.0.0-dev.8) (2024-10-17)

# [5.0.0-dev.7](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.6...v5.0.0-dev.7) (2024-10-16)


### Bug Fixes

* Check for null when no device serial was specified ([1da8ae9](https://github.com/ReVanced/revanced-cli/commit/1da8ae9e46000dd3c4eecd793c559e75012cf535))

# [5.0.0-dev.6](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.5...v5.0.0-dev.6) (2024-10-10)


### Bug Fixes

* Use the first connected device when no ADB device is specified ([5f952f3](https://github.com/ReVanced/revanced-cli/commit/5f952f35f5cb388b6509b2b4d905b8143ebc7996))

# [5.0.0-dev.5](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.4...v5.0.0-dev.5) (2024-09-30)

# [5.0.0-dev.4](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.3...v5.0.0-dev.4) (2024-09-17)


### Bug Fixes

* Make patches selectable by using a mutable collection for the selection option ([751fa1d](https://github.com/ReVanced/revanced-cli/commit/751fa1d889f40c51b291116029fd84f2b051f2f0))
* Make the patch command work without specifying any selection ([ba159a3](https://github.com/ReVanced/revanced-cli/commit/ba159a35a9a99d18a4c1e04128b08ae336a49b3e))


### Features

* Show error about no installation device found at the beginning ([3300e6b](https://github.com/ReVanced/revanced-cli/commit/3300e6b4333ed1c4e6785cb82eca9016fc6d4a20))

# [5.0.0-dev.3](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.2...v5.0.0-dev.3) (2024-08-14)


### Features

* Simplify option descriptions ([45c998b](https://github.com/ReVanced/revanced-cli/commit/45c998b335b65ac233fece8b804dc7410142691c))

# [5.0.0-dev.2](https://github.com/ReVanced/revanced-cli/compare/v5.0.0-dev.1...v5.0.0-dev.2) (2024-08-14)


### Features

* Simplify command and option names and descriptions ([#338](https://github.com/ReVanced/revanced-cli/issues/338)) ([6e7797a](https://github.com/ReVanced/revanced-cli/commit/6e7797a3f0525a8f48af7182157da0d045600ac2))


### BREAKING CHANGES

* Options have been renamed.

# [5.0.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.6.0...v5.0.0-dev.1) (2024-08-12)


### Features

* Set patch options via CLI ([#336](https://github.com/ReVanced/revanced-cli/issues/336)) ([2300243](https://github.com/ReVanced/revanced-cli/commit/23002434b2d51c2a3b30b33dd0526261432d90ce))


### BREAKING CHANGES

* This commit changes various CLI options and removes the `options.json` file. Instead, patch options can now be passed via CLI options

# [4.6.0](https://github.com/ReVanced/revanced-cli/compare/v4.5.0...v4.6.0) (2024-04-01)


6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -63,19 +63,19 @@
![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/ReVanced/revanced-cli/release.yml)
![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)

Command line application to use ReVanced.
Command-line application to use ReVanced.

## ❓ About

ReVanced CLI is a command line application that uses [ReVanced Patcher](https://github.com/revanced/revanced-patcher) to patch Android apps.
ReVanced CLI is a command-line application that uses [ReVanced Patcher](https://github.com/revanced/revanced-patcher) to patch Android apps.

## 💪 Features

Some of the features ReVanced CLI provides are:

- 💉 **Patch apps**: Harness ReVanced Patcher to patch Android apps
- 💾 **Install and uninstall apps**: Install and uninstall Apps via ADB,
using the Android package manager, or by mounting using root permissions
using the Android package manager or by mounting using root permissions
- 📃 **List patches from patch bundles**: List available patches, compatible packages, and versions
- 💪 **Flexibility and functionality**: Apply any combination of patches to any version of Android apps

3 changes: 1 addition & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -16,10 +16,9 @@ application {

repositories {
mavenCentral()
mavenLocal()
google()
maven {
// A repository must be speficied for some reason. "registry" is a dummy.
// A repository must be specified for some reason. "registry" is a dummy.
url = uri("https://maven.pkg.github.com/revanced/registry")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
4 changes: 2 additions & 2 deletions docs/0_prerequisites.md
Original file line number Diff line number Diff line change
@@ -4,9 +4,9 @@ To use ReVanced CLI, you will need to fulfill specific requirements.

## 🤝 Requirements

- Java Runtime Environment 11 ([Azul Zulu JRE](https://www.azul.com/downloads/?version=java-11-lts&package=jdk#zulu) or [OpenJDK](https://jdk.java.net/archive/))
- Java Runtime Environment 11 ([Azul Zulu JRE](https://www.azul.com/downloads/?version=java-11-lts&package=jre#zulu) or [OpenJDK](https://jdk.java.net/archive/))
- [Android Debug Bridge (ADB)](https://developer.android.com/studio/command-line/adb) if you want to install the patched APK file on your device
- An ABI other than ARMv7 such as x86 or x86-64 (or a custom AAPT binary that supports ARMv7)
- x86 or x86-64 (For [other architectures](https://github.com/ReVanced/revanced-manager/tree/main/android/app/src/main/jniLibs) use the `--custom-aapt2-binary` option)

## ⏭️ Whats next

292 changes: 167 additions & 125 deletions docs/1_usage.md
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> ...]
> ```
2 changes: 1 addition & 1 deletion gradle.properties
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
16 changes: 8 additions & 8 deletions gradle/libs.versions.toml
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 modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
8 changes: 5 additions & 3 deletions gradle/wrapper/gradle-wrapper.properties
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
17 changes: 9 additions & 8 deletions gradlew
Original file line number Diff line number Diff line change
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -144,15 +145,15 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -201,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.

set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
20 changes: 10 additions & 10 deletions gradlew.bat
Original file line number Diff line number Diff line change
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2

goto fail

@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2

goto fail

3,166 changes: 1,946 additions & 1,220 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
"@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.9.1",
"semantic-release": "^23.0.2"
"gradle-semantic-release-plugin": "^1.10.1",
"semantic-release": "^24.1.2"
}
}
6 changes: 0 additions & 6 deletions settings.gradle.kts
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 src/main/kotlin/app/revanced/cli/command/CommandUtils.kt
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
}
}
}
19 changes: 9 additions & 10 deletions src/main/kotlin/app/revanced/cli/command/ListCompatibleVersions.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package app.revanced.cli.command

import app.revanced.library.PackageName
import app.revanced.library.PatchUtils
import app.revanced.library.VersionMap
import app.revanced.patcher.PatchBundleLoader
import app.revanced.library.mostCommonCompatibleVersions
import app.revanced.patcher.patch.loadPatchesFromJar
import picocli.CommandLine
import java.io.File
import java.util.logging.Logger
@@ -12,17 +12,17 @@ import java.util.logging.Logger
name = "list-versions",
description = [
"List the most common compatible versions of apps that are compatible " +
"with the patches in the supplied patch bundles.",
"with the patches from RVP files.",
],
)
internal class ListCompatibleVersions : Runnable {
private val logger = Logger.getLogger(ListCompatibleVersions::class.java.name)
private val logger = Logger.getLogger(this::class.java.name)

@CommandLine.Parameters(
description = ["Paths to patch bundles."],
description = ["Paths to RVP files."],
arity = "1..*",
)
private lateinit var patchBundles: Array<File>
private lateinit var patchesFiles: Set<File>

@CommandLine.Option(
names = ["-f", "--filter-package-names"],
@@ -38,8 +38,6 @@ internal class ListCompatibleVersions : Runnable {
private var countUnusedPatches: Boolean = false

override fun run() {
val patches = PatchBundleLoader.Jar(*patchBundles)

fun VersionMap.buildVersionsString(): String {
if (isEmpty()) return "Any"

@@ -58,8 +56,9 @@ internal class ListCompatibleVersions : Runnable {
appendLine(versions.buildVersionsString().prependIndent("\t"))
}

PatchUtils.getMostCommonCompatibleVersions(
patches,
val patches = loadPatchesFromJar(patchesFiles)

patches.mostCommonCompatibleVersions(
packageNames,
countUnusedPatches,
).entries.joinToString("\n", transform = ::buildString).let(logger::info)
35 changes: 22 additions & 13 deletions src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
package app.revanced.cli.command

import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.patch.Package
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
import app.revanced.patcher.patch.loadPatchesFromJar
import picocli.CommandLine.*
import picocli.CommandLine.Help.Visibility.ALWAYS
import java.io.File
import java.util.logging.Logger
import app.revanced.patcher.patch.Option as PatchOption

@Command(
name = "list-patches",
description = ["List patches from supplied patch bundles."],
description = ["List patches from supplied RVP files."],
)
internal object ListPatchesCommand : Runnable {
private val logger = Logger.getLogger(ListPatchesCommand::class.java.name)
private val logger = Logger.getLogger(this::class.java.name)

@Parameters(
description = ["Paths to patch bundles."],
description = ["Paths to RVP files."],
arity = "1..*",
)
private lateinit var patchBundles: Array<File>
private lateinit var patchesFiles: Set<File>

@Option(
names = ["-d", "--with-descriptions"],
@@ -58,7 +59,7 @@ internal object ListPatchesCommand : Runnable {

@Option(
names = ["-i", "--index"],
description = ["List the index of each patch in relation to the supplied patch bundles."],
description = ["List the index of each patch in relation to the supplied RVP files."],
showDefaultValue = ALWAYS,
)
private var withIndex: Boolean = true
@@ -70,30 +71,36 @@ internal object ListPatchesCommand : Runnable {
private var packageName: String? = null

override fun run() {
fun Patch.CompatiblePackage.buildString() =
buildString {
fun Package.buildString(): String {
val (name, versions) = this

return buildString {
if (withVersions && versions != null) {
appendLine("Package name: $name")
appendLine("Compatible versions:")
append(versions!!.joinToString("\n") { version -> version }.prependIndent("\t"))
append(versions.joinToString("\n") { version -> version }.prependIndent("\t"))
} else {
append("Package name: $name")
}
}
}

fun PatchOption<*>.buildString() =
buildString {
appendLine("Title: $title")
description?.let { appendLine("Description: $it") }
appendLine("Required: $required")
default?.let {
appendLine("Key: $key")
append("Default: $it")
} ?: append("Key: $key")

values?.let { values ->
appendLine("\nValid values:")
appendLine("\nPossible values:")
append(values.map { "${it.value} (${it.key})" }.joinToString("\n").prependIndent("\t"))
}

append("\nType: $type")
}

fun IndexedValue<Patch<*>>.buildString() =
@@ -105,6 +112,8 @@ internal object ListPatchesCommand : Runnable {

if (withDescriptions) append("\nDescription: ${patch.description}")

append("\nEnabled: ${patch.use}")

if (withOptions && patch.options.isNotEmpty()) {
appendLine("\nOptions:")
append(
@@ -126,10 +135,10 @@ internal object ListPatchesCommand : Runnable {
}

fun Patch<*>.filterCompatiblePackages(name: String) =
compatiblePackages?.any { it.name == name }
compatiblePackages?.any { (compatiblePackageName, _) -> compatiblePackageName == name }
?: withUniversalPatches

val patches = PatchBundleLoader.Jar(*patchBundles).withIndex().toList()
val patches = loadPatchesFromJar(patchesFiles).withIndex().toList()

val filtered =
packageName?.let { patches.filter { (_, patch) -> patch.filterCompatiblePackages(it) } } ?: patches
1 change: 0 additions & 1 deletion src/main/kotlin/app/revanced/cli/command/MainCommand.kt
Original file line number Diff line number Diff line change
@@ -34,7 +34,6 @@ private object CLIVersionProvider : IVersionProvider {
versionProvider = CLIVersionProvider::class,
subcommands = [
PatchCommand::class,
OptionsCommand::class,
ListPatchesCommand::class,
ListCompatibleVersions::class,
UtilityCommand::class,
62 changes: 0 additions & 62 deletions src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt

This file was deleted.

398 changes: 215 additions & 183 deletions src/main/kotlin/app/revanced/cli/command/PatchCommand.kt

Large diffs are not rendered by default.

41 changes: 30 additions & 11 deletions src/main/kotlin/app/revanced/cli/command/utility/InstallCommand.kt
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()
}
}
}
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()
}
}
}
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 }
}

0 comments on commit 5d8beae

Please sign in to comment.