Compare commits

...

24 Commits

Author SHA1 Message Date
semantic-release-bot
48e1689223 chore(release): 3.0.1-dev.1 [skip ci]
## [3.0.1-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.0.0...v3.0.1-dev.1) (2023-08-28)
2023-08-28 13:18:00 +00:00
oSumAtrIX
7580f5c2fc build(Needs bump): bump dependencies
This fixes an issue with flagging the resource table with sparse incorrectly.
2023-08-28 15:14:53 +02:00
semantic-release-bot
dd6c1392eb chore(release): 3.0.0 [skip ci]
# [3.0.0](https://github.com/ReVanced/revanced-cli/compare/v2.22.0...v3.0.0) (2023-08-26)

### Bug Fixes

* also delete temporary files when uninstalling ([52c3be2](52c3be23f2))
* delete temporary files after root installation ([a3d8705](a3d8705e89))
* do not delete output file ([0f3e090](0f3e090418))
* do not use absolute path from custom AAPT2 binary option ([a9c2a5f](a9c2a5f096))
* filtration of patches malfunctioning ([2d5a7fd](2d5a7fdf1e))
* fix running commands not running ([2c7fcaf](2c7fcaf4ad))
* only check once for patch options ([11c3a6c](11c3a6cfd4))
* print original instead of kebab cased names ([5eaad33](5eaad33dc1))
* print stack trace when a patch failed ([924c1f8](924c1f80ec))
* specify correct class containing entry-point ([1fcc591](1fcc591222))
* use correct option name ([f8972ea](f8972eac3e))

* refactor!: restructure code ([07da528](07da528ce2))

### Features

* add install command ([0350b7f](0350b7f1a2))
* add options command ([9edbbf3](9edbbf3163))
* Check for missing integrations ([c93186f](c93186fb97))
* Improve command line argument descriptions ([f9cf7d2](f9cf7d21b7))
* properly make use of logging facade ([41898d7](41898d7547))
* show full package name when listing patches ([#240](https://github.com/ReVanced/revanced-cli/issues/240)) ([7174364](7174364ef8))
* use better logging text ([b0e748d](b0e748daff))
* use friendly descriptions ([3dd875d](3dd875d14c))
* use separate command to list patches ([b74213f](b74213f66e))
* use separate command to patch ([32da961](32da961d57))
* use separate command to uninstall ([c0cc909](c0cc909626))
* use simpler log ([ba758f0](ba758f00f4))

### BREAKING CHANGES

* This introduces major changes to how ReVanced CLI is used from the command line.
2023-08-26 23:23:34 +00:00
oSumAtrIX
2a3dbafd17 chore: merge branch dev to main (#236) 2023-08-27 01:21:45 +02:00
semantic-release-bot
9e39a6f8e4 chore(release): 3.0.0-dev.10 [skip ci]
# [3.0.0-dev.10](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.9...v3.0.0-dev.10) (2023-08-25)

### Bug Fixes

* filtration of patches malfunctioning ([2d5a7fd](2d5a7fdf1e))
2023-08-25 21:48:59 +00:00
oSumAtrIX
2d5a7fdf1e fix: filtration of patches malfunctioning
Apparently, you were not able to include patches explicitly
2023-08-25 23:47:18 +02:00
semantic-release-bot
be5d812dff chore(release): 3.0.0-dev.9 [skip ci]
# [3.0.0-dev.9](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.8...v3.0.0-dev.9) (2023-08-25)

### Features

* Check for missing integrations ([c93186f](c93186fb97))
2023-08-25 00:28:57 +00:00
oSumAtrIX
c93186fb97 feat: Check for missing integrations
Check, if the integrations file exists at first.
2023-08-25 02:26:38 +02:00
semantic-release-bot
3a198052bb chore(release): 3.0.0-dev.8 [skip ci]
# [3.0.0-dev.8](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.7...v3.0.0-dev.8) (2023-08-24)

### Bug Fixes

* do not delete output file ([0f3e090](0f3e090418))
2023-08-24 23:32:29 +00:00
oSumAtrIX
0f3e090418 fix: do not delete output file
This fixes the output file to be deleted when the option `--purge` was used.
2023-08-25 01:30:28 +02:00
semantic-release-bot
6aed946183 chore(release): 3.0.0-dev.7 [skip ci]
# [3.0.0-dev.7](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.6...v3.0.0-dev.7) (2023-08-24)

### Bug Fixes

* print stack trace when a patch failed ([924c1f8](924c1f80ec))
2023-08-24 21:47:17 +00:00
oSumAtrIX
924c1f80ec fix: print stack trace when a patch failed 2023-08-24 23:45:10 +02:00
semantic-release-bot
8dd709b6ef chore(release): 3.0.0-dev.6 [skip ci]
# [3.0.0-dev.6](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.5...v3.0.0-dev.6) (2023-08-24)
2023-08-24 16:11:51 +00:00
oSumAtrIX
139e7facac build(Needs bump): depend on build task when publishing
This fixes the issue that no builds are generated
2023-08-24 18:09:35 +02:00
semantic-release-bot
1d26e572f7 chore(release): 3.0.0-dev.5 [skip ci]
# [3.0.0-dev.5](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.4...v3.0.0-dev.5) (2023-08-24)

### Bug Fixes

* also delete temporary files when uninstalling ([52c3be2](52c3be23f2))
* delete temporary files after root installation ([a3d8705](a3d8705e89))
* fix running commands not running ([2c7fcaf](2c7fcaf4ad))
* only check once for patch options ([11c3a6c](11c3a6cfd4))

### Features

* add install command ([0350b7f](0350b7f1a2))
* use friendly descriptions ([3dd875d](3dd875d14c))
2023-08-24 15:53:09 +00:00
oSumAtrIX
2c7fcaf4ad fix: fix running commands not running 2023-08-24 17:51:31 +02:00
oSumAtrIX
52c3be23f2 fix: also delete temporary files when uninstalling 2023-08-24 16:54:08 +02:00
oSumAtrIX
3dd875d14c feat: use friendly descriptions 2023-08-24 16:53:31 +02:00
oSumAtrIX
0350b7f1a2 feat: add install command
This introduces a separate utility subcommand.
2023-08-24 16:50:10 +02:00
oSumAtrIX
a3d8705e89 fix: delete temporary files after root installation 2023-08-24 16:24:33 +02:00
oSumAtrIX
a536c9f815 refactor: use better identifiers 2023-08-24 16:24:18 +02:00
oSumAtrIX
11c3a6cfd4 fix: only check once for patch options
This prevents checking for the same patches options multiple times when it is already determined to not have any options
2023-08-24 15:52:12 +02:00
oSumAtrIX
a5851f0c1a build: clean after building 2023-08-24 15:35:27 +02:00
oSumAtrIX
963ae3a5fa docs: add missing inline docs 2023-08-24 15:35:27 +02:00
17 changed files with 347 additions and 269 deletions

View File

@@ -33,10 +33,11 @@ jobs:
build build
node_modules node_modules
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }} key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
- name: Build with Gradle - name: Build
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build --no-daemon # Cleaning is necessary to avoid uploading two identical artifacts with different versions
run: ./gradlew clean --no-daemon
- name: Setup semantic-release - name: Setup semantic-release
run: npm install run: npm install
- name: Release - name: Release

View File

@@ -1,3 +1,92 @@
## [3.0.1-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.0.0...v3.0.1-dev.1) (2023-08-28)
# [3.0.0](https://github.com/ReVanced/revanced-cli/compare/v2.22.0...v3.0.0) (2023-08-26)
### Bug Fixes
* also delete temporary files when uninstalling ([52c3be2](https://github.com/ReVanced/revanced-cli/commit/52c3be23f2915dccaee7f9941413c8f81e14acc8))
* delete temporary files after root installation ([a3d8705](https://github.com/ReVanced/revanced-cli/commit/a3d8705e89732a0dd4f51de28c405b6af13c8633))
* do not delete output file ([0f3e090](https://github.com/ReVanced/revanced-cli/commit/0f3e090418771e951dfd15e5c193421f72cbe459))
* do not use absolute path from custom AAPT2 binary option ([a9c2a5f](https://github.com/ReVanced/revanced-cli/commit/a9c2a5f096627dbbf8ab1b8da26fb14529ce6bc3))
* filtration of patches malfunctioning ([2d5a7fd](https://github.com/ReVanced/revanced-cli/commit/2d5a7fdf1eb2e13f5013a790b03f09851b167fe0))
* fix running commands not running ([2c7fcaf](https://github.com/ReVanced/revanced-cli/commit/2c7fcaf4add65a12052afc5bef779dbc73debd69))
* only check once for patch options ([11c3a6c](https://github.com/ReVanced/revanced-cli/commit/11c3a6cfd4fe59ba5d703358634a1853e1cc22a5))
* print original instead of kebab cased names ([5eaad33](https://github.com/ReVanced/revanced-cli/commit/5eaad33dc1fbd24c36e1498f04e21d068e85f53e))
* print stack trace when a patch failed ([924c1f8](https://github.com/ReVanced/revanced-cli/commit/924c1f80ec0d17a3bdc07a0fb2015e44c49162e4))
* specify correct class containing entry-point ([1fcc591](https://github.com/ReVanced/revanced-cli/commit/1fcc591222ab67112f2b78174a8b94106846838c))
* use correct option name ([f8972ea](https://github.com/ReVanced/revanced-cli/commit/f8972eac3e5ee0a4a186c12cbe711925656d657b))
* refactor!: restructure code ([07da528](https://github.com/ReVanced/revanced-cli/commit/07da528ce2223582f84bf64d2fec69714c647ddc))
### Features
* add install command ([0350b7f](https://github.com/ReVanced/revanced-cli/commit/0350b7f1a276d9dc795b22442ba4f202855ea090))
* add options command ([9edbbf3](https://github.com/ReVanced/revanced-cli/commit/9edbbf31635603f89fc7bc5dcc6c023d4cdbb5a6))
* Check for missing integrations ([c93186f](https://github.com/ReVanced/revanced-cli/commit/c93186fb9700907e65f33442e88073783cc163de))
* Improve command line argument descriptions ([f9cf7d2](https://github.com/ReVanced/revanced-cli/commit/f9cf7d21b7f1c2f11234d604a1047b9d2b165f83))
* properly make use of logging facade ([41898d7](https://github.com/ReVanced/revanced-cli/commit/41898d7547690e3130372414515c5645e5dc2634))
* show full package name when listing patches ([#240](https://github.com/ReVanced/revanced-cli/issues/240)) ([7174364](https://github.com/ReVanced/revanced-cli/commit/7174364ef8ef5d6ce8351a8340f9c1a5b58eac3c))
* use better logging text ([b0e748d](https://github.com/ReVanced/revanced-cli/commit/b0e748daff527ee7f417b3069882e074896fc131))
* use friendly descriptions ([3dd875d](https://github.com/ReVanced/revanced-cli/commit/3dd875d14cca488ade6d21bbd4cce0d481692134))
* use separate command to list patches ([b74213f](https://github.com/ReVanced/revanced-cli/commit/b74213f66e0d04d3a0ae6197d069631388e06580))
* use separate command to patch ([32da961](https://github.com/ReVanced/revanced-cli/commit/32da961d57537e99b39fd92b625a1c73f8314bc6))
* use separate command to uninstall ([c0cc909](https://github.com/ReVanced/revanced-cli/commit/c0cc90962646cfffd5e2730ae556423271a7990b))
* use simpler log ([ba758f0](https://github.com/ReVanced/revanced-cli/commit/ba758f00f4ce18791439b7e72fe1ad2e7f11f8af))
### BREAKING CHANGES
* This introduces major changes to how ReVanced CLI is used from the command line.
# [3.0.0-dev.10](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.9...v3.0.0-dev.10) (2023-08-25)
### Bug Fixes
* filtration of patches malfunctioning ([2d5a7fd](https://github.com/ReVanced/revanced-cli/commit/2d5a7fdf1eb2e13f5013a790b03f09851b167fe0))
# [3.0.0-dev.9](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.8...v3.0.0-dev.9) (2023-08-25)
### Features
* Check for missing integrations ([c93186f](https://github.com/ReVanced/revanced-cli/commit/c93186fb9700907e65f33442e88073783cc163de))
# [3.0.0-dev.8](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.7...v3.0.0-dev.8) (2023-08-24)
### Bug Fixes
* do not delete output file ([0f3e090](https://github.com/ReVanced/revanced-cli/commit/0f3e090418771e951dfd15e5c193421f72cbe459))
# [3.0.0-dev.7](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.6...v3.0.0-dev.7) (2023-08-24)
### Bug Fixes
* print stack trace when a patch failed ([924c1f8](https://github.com/ReVanced/revanced-cli/commit/924c1f80ec0d17a3bdc07a0fb2015e44c49162e4))
# [3.0.0-dev.6](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.5...v3.0.0-dev.6) (2023-08-24)
# [3.0.0-dev.5](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.4...v3.0.0-dev.5) (2023-08-24)
### Bug Fixes
* also delete temporary files when uninstalling ([52c3be2](https://github.com/ReVanced/revanced-cli/commit/52c3be23f2915dccaee7f9941413c8f81e14acc8))
* delete temporary files after root installation ([a3d8705](https://github.com/ReVanced/revanced-cli/commit/a3d8705e89732a0dd4f51de28c405b6af13c8633))
* fix running commands not running ([2c7fcaf](https://github.com/ReVanced/revanced-cli/commit/2c7fcaf4add65a12052afc5bef779dbc73debd69))
* only check once for patch options ([11c3a6c](https://github.com/ReVanced/revanced-cli/commit/11c3a6cfd4fe59ba5d703358634a1853e1cc22a5))
### Features
* add install command ([0350b7f](https://github.com/ReVanced/revanced-cli/commit/0350b7f1a276d9dc795b22442ba4f202855ea090))
* use friendly descriptions ([3dd875d](https://github.com/ReVanced/revanced-cli/commit/3dd875d14cca488ade6d21bbd4cce0d481692134))
# [3.0.0-dev.4](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.3...v3.0.0-dev.4) (2023-08-24) # [3.0.0-dev.4](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.3...v3.0.0-dev.4) (2023-08-24)

View File

@@ -52,5 +52,6 @@ tasks {
register<DefaultTask>("publish") { register<DefaultTask>("publish") {
group = "publish" group = "publish"
description = "Dummy task" description = "Dummy task"
dependsOn(build)
} }
} }

View File

@@ -86,12 +86,26 @@ Learn how to ReVanced CLI.
> **Note**: Some patches may require integrations > **Note**: Some patches may require integrations
such as [ReVanced Integrations](https://github.com/revanced/revanced-integrations). such as [ReVanced Integrations](https://github.com/revanced/revanced-integrations).
Supply them with the option `-m`. If any patches accepted by ReVanced Patcher require ReVanced Integrations, Supply them with the option `--merge`. If any patches accepted by ReVanced Patcher require ReVanced Integrations,
they will be merged into the APK file automatically. they will be merged into the APK file automatically.
- ### 🗑️ Uninstall a patched APK file - ### 🗑️ Uninstall a patched APK file
```bash ```bash
java -jar revanced-cli.jar uninstall \ java -jar revanced-cli.jar utility uninstall \
--package-name <package-name> \ --package-name <package-name> \
<device-serial> <device-serial>
``` ```
> **Note**: You can unmount an APK file
with the option `--unmount`.
- ### ⚙️ Manually install an APK file
```bash
java -jar revanced-cli.jar utility install \
-a input.apk \
<device-serial>
```
> **Note**: You can mount an APK file
by supplying the package name of the app to mount the supplied APK file to over the option `--mount`.

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true org.gradle.parallel = true
org.gradle.caching = true org.gradle.caching = true
kotlin.code.style = official kotlin.code.style = official
version = 3.0.0-dev.4 version = 3.0.1-dev.1

View File

@@ -1,6 +1,6 @@
[versions] [versions]
shadow = "8.1.1" shadow = "8.1.1"
apksig = "8.1.0" apksig = "8.1.1"
bcpkix-jdk15on = "1.70" bcpkix-jdk15on = "1.70"
jackson-module-kotlin = "2.14.3" jackson-module-kotlin = "2.14.3"
jadb = "2531a28109" jadb = "2531a28109"
@@ -8,7 +8,7 @@ kotlin-reflect = "1.9.0"
kotlin-test = "1.8.20-RC" kotlin-test = "1.8.20-RC"
kotlinx-coroutines-core = "1.7.1" kotlinx-coroutines-core = "1.7.1"
picocli = "4.7.3" picocli = "4.7.3"
revanced-patcher = "14.1.0" revanced-patcher = "14.2.1"
[libraries] [libraries]
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" } apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }

View File

@@ -19,38 +19,33 @@ internal object ListPatchesCommand : Runnable {
private val logger = Logger.getLogger(ListPatchesCommand::class.java.name) private val logger = Logger.getLogger(ListPatchesCommand::class.java.name)
@Parameters( @Parameters(
description = ["Paths to patch bundles"], description = ["Paths to patch bundles"], arity = "1..*"
arity = "1..*"
) )
lateinit var patchBundles: Array<File> private lateinit var patchBundles: Array<File>
@Option( @Option(
names = ["-d", "--with-descriptions"], names = ["-d", "--with-descriptions"], description = ["List their descriptions"], showDefaultValue = ALWAYS
description = ["List their descriptions"],
showDefaultValue = ALWAYS
) )
var withDescriptions: Boolean = true private var withDescriptions: Boolean = true
@Option( @Option(
names = ["-p", "--with-packages"], names = ["-p", "--with-packages"],
description = ["List the packages the patches are compatible with"], description = ["List the packages the patches are compatible with"],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
var withPackages: Boolean = false private var withPackages: Boolean = false
@Option( @Option(
names = ["-v", "--with-versions"], names = ["-v", "--with-versions"],
description = ["List the versions of the packages the patches are compatible with"], description = ["List the versions of the apps the patches are compatible with"],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
var withVersions: Boolean = false private var withVersions: Boolean = false
@Option( @Option(
names = ["-o", "--with-options"], names = ["-o", "--with-options"], description = ["List the options of the patches"], showDefaultValue = ALWAYS
description = ["List the options of the patches"],
showDefaultValue = ALWAYS
) )
var withOptions: Boolean = false private var withOptions: Boolean = false
override fun run() { override fun run() {
fun Package.buildString() = buildString { fun Package.buildString() = buildString {
@@ -58,8 +53,7 @@ internal object ListPatchesCommand : Runnable {
appendLine("Package name: $name") appendLine("Package name: $name")
appendLine("Compatible versions:") appendLine("Compatible versions:")
append(versions.joinToString("\n") { version -> version }.prependIndent("\t")) append(versions.joinToString("\n") { version -> version }.prependIndent("\t"))
} else } else append("Package name: $name")
append("Package name: $name")
} }
fun PatchOption<*>.buildString() = buildString { fun PatchOption<*>.buildString() = buildString {

View File

@@ -1,5 +1,6 @@
package app.revanced.cli.command package app.revanced.cli.command
import app.revanced.cli.command.utility.UtilityCommand
import app.revanced.patcher.patch.PatchClass import app.revanced.patcher.patch.PatchClass
import picocli.CommandLine import picocli.CommandLine
import picocli.CommandLine.Command import picocli.CommandLine.Command
@@ -17,12 +18,11 @@ fun main(args: Array<String>) {
} }
object : Handler() { object : Handler() {
override fun publish(record: LogRecord) = formatter.format(record).let { override fun publish(record: LogRecord) = formatter.format(record).toByteArray().let {
if (record.level.intValue() > Level.INFO.intValue()) { if (record.level.intValue() > Level.INFO.intValue())
System.err.write(it.toByteArray()) System.err.write(it)
} else { else
System.out.write(it.toByteArray()) System.out.write(it)
}
} }
override fun flush() { override fun flush() {
@@ -42,7 +42,7 @@ fun main(args: Array<String>) {
internal typealias PatchList = List<PatchClass> internal typealias PatchList = List<PatchClass>
object CLIVersionProvider : IVersionProvider { private object CLIVersionProvider : IVersionProvider {
override fun getVersion(): Array<String> { override fun getVersion(): Array<String> {
Properties().apply { Properties().apply {
load(MainCommand::class.java.getResourceAsStream("/app/revanced/cli/version.properties")) load(MainCommand::class.java.getResourceAsStream("/app/revanced/cli/version.properties"))
@@ -60,8 +60,8 @@ object CLIVersionProvider : IVersionProvider {
subcommands = [ subcommands = [
ListPatchesCommand::class, ListPatchesCommand::class,
PatchCommand::class, PatchCommand::class,
UninstallCommand::class,
OptionsCommand::class, OptionsCommand::class,
UtilityCommand::class,
] ]
) )
internal object MainCommand private object MainCommand

View File

@@ -16,38 +16,31 @@ internal object OptionsCommand : Runnable {
private val logger = Logger.getLogger(OptionsCommand::class.java.name) private val logger = Logger.getLogger(OptionsCommand::class.java.name)
@CommandLine.Parameters( @CommandLine.Parameters(
description = ["Paths to patch bundles"], description = ["Paths to patch bundles"], arity = "1..*"
arity = "1..*"
) )
lateinit var patchBundles: Array<File> private lateinit var patchBundles: Array<File>
@CommandLine.Option( @CommandLine.Option(
names = ["-p", "--path"], names = ["-p", "--path"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS
description = ["Path to patch options JSON file"],
showDefaultValue = ALWAYS
) )
var path: File = File("options.json") private var path: File = File("options.json")
@CommandLine.Option( @CommandLine.Option(
names = ["-o", "--overwrite"], names = ["-o", "--overwrite"], description = ["Overwrite existing options file"], showDefaultValue = ALWAYS
description = ["Overwrite existing options file"],
showDefaultValue = ALWAYS
) )
var overwrite: Boolean = false private var overwrite: Boolean = false
@CommandLine.Option( @CommandLine.Option(
names = ["-u", "--update"], names = ["-u", "--update"],
description = ["Update existing options by adding missing and removing non-existent options"], description = ["Update existing options by adding missing and removing non-existent options"],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
var update: Boolean = false private var update: Boolean = false
override fun run() = if (!path.exists() || overwrite) override fun run() = if (!path.exists() || overwrite) with(PatchBundleLoader.Jar(*patchBundles)) {
with(PatchBundleLoader.Jar(*patchBundles)) { if (update) setOptions(path)
if (update) setOptions(path)
Options.serialize(this, prettyPrint = true) Options.serialize(this, prettyPrint = true).let(path::writeText)
.let(path::writeText) }
}
else logger.severe("Options file already exists, use --override to override it") else logger.severe("Options file already exists, use --override to override it")
} }

View File

@@ -19,88 +19,75 @@ import kotlinx.coroutines.runBlocking
import picocli.CommandLine import picocli.CommandLine
import picocli.CommandLine.Help.Visibility.ALWAYS import picocli.CommandLine.Help.Visibility.ALWAYS
import java.io.File import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.util.logging.Logger import java.util.logging.Logger
@CommandLine.Command( @CommandLine.Command(
name = "patch", name = "patch", description = ["Patch the supplied APK file with the supplied patches and integrations"]
description = ["Patch the supplied APK file with the supplied patches and integrations"]
) )
internal object PatchCommand: Runnable { internal object PatchCommand : Runnable {
private val logger = Logger.getLogger(PatchCommand::class.java.name) private val logger = Logger.getLogger(PatchCommand::class.java.name)
@CommandLine.Parameters( @CommandLine.Parameters(
description = ["APK file to be patched"], description = ["APK file to be patched"], arity = "1..1"
arity = "1..1"
) )
lateinit var apk: File private lateinit var apk: File
@CommandLine.Option( @CommandLine.Option(
names = ["-b", "--patch-bundle"], names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches"], required = true
description = ["One or more bundles of patches"],
required = true
) )
var patchBundles = emptyList<File>() private var patchBundles = emptyList<File>()
@CommandLine.Option( @CommandLine.Option(
names = ["-m", "--merge"], names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"]
description = ["One or more DEX files or containers to merge into the APK"]
) )
var integrations = listOf<File>() private var integrations = listOf<File>()
@CommandLine.Option( @CommandLine.Option(
names = ["-i", "--include"], names = ["-i", "--include"], description = ["List of patches to include"]
description = ["List of patches to include"]
) )
var includedPatches = arrayOf<String>() private var includedPatches = arrayOf<String>()
@CommandLine.Option( @CommandLine.Option(
names = ["-e", "--exclude"], names = ["-e", "--exclude"], description = ["List of patches to exclude"]
description = ["List of patches to exclude"]
) )
var excludedPatches = arrayOf<String>() private var excludedPatches = arrayOf<String>()
@CommandLine.Option( @CommandLine.Option(
names = ["--options"], names = ["--options"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS
description = ["Path to patch options JSON file"],
showDefaultValue = ALWAYS
) )
var optionsFile: File = File("options.json") private var optionsFile: File = File("options.json")
@CommandLine.Option( @CommandLine.Option(
names = ["--exclusive"], names = ["--exclusive"],
description = ["Only include patches that are explicitly specified to be included"], description = ["Only include patches that are explicitly specified to be included"],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
var exclusive = false private var exclusive = false
@CommandLine.Option( @CommandLine.Option(
names = ["--experimental"], names = ["-f","--force"],
description = ["Ignore patches incompatibility to versions"], description = ["Force inclusion of patches that are incompatible with the supplied APK file's version"],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
var experimental: Boolean = false private var force: Boolean = false
@CommandLine.Option( @CommandLine.Option(
names = ["-o", "--out"], names = ["-o", "--out"], description = ["Path to save the patched APK file to"], required = true
description = ["Path to save the patched APK file to"],
required = true
) )
lateinit var outputFilePath: File private lateinit var outputFilePath: File
@CommandLine.Option( @CommandLine.Option(
names = ["-d", "--device-serial"], names = ["-d", "--device-serial"], description = ["ADB device serial to install to"], showDefaultValue = ALWAYS
description = ["ADB device serial to install to"],
showDefaultValue = ALWAYS
) )
var deviceSerial: String? = null private var deviceSerial: String? = null
@CommandLine.Option( @CommandLine.Option(
names = ["--mount"], names = ["--mount"], description = ["Install by mounting the patched APK file"], showDefaultValue = ALWAYS
description = ["Install by mounting the patched package"],
showDefaultValue = ALWAYS
) )
var mount: Boolean = false private var mount: Boolean = false
@CommandLine.Option( @CommandLine.Option(
names = ["--common-name"], names = ["--common-name"],
@@ -108,45 +95,51 @@ internal object PatchCommand: Runnable {
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
var commonName = "ReVanced" private var commonName = "ReVanced"
@CommandLine.Option( @CommandLine.Option(
names = ["--keystore"], names = ["--keystore"], description = ["Path to the keystore to sign the patched APK file with"]
description = ["Path to the keystore to sign the patched APK file with"]
) )
var keystorePath: String? = null private var keystorePath: String? = null
@CommandLine.Option( @CommandLine.Option(
names = ["--password"], names = ["--password"], description = ["The password of the keystore to sign the patched APK file with"]
description = ["The password of the keystore to sign the patched APK file with"]
) )
var password = "ReVanced" private var password = "ReVanced"
@CommandLine.Option( @CommandLine.Option(
names = ["-r", "--resource-cache"], names = ["-r", "--resource-cache"],
description = ["Path to temporary resource cache directory"], description = ["Path to temporary resource cache directory"],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
var resourceCachePath = File("revanced-resource-cache") private var resourceCachePath = File("revanced-resource-cache")
@CommandLine.Option( @CommandLine.Option(
names = ["--custom-aapt2-binary"], names = ["--custom-aapt2-binary"], description = ["Path to a custom AAPT binary to compile resources with"]
description = ["Path to a custom AAPT binary to compile resources with"]
) )
var aaptBinaryPath = File("") private var aaptBinaryPath = File("")
@CommandLine.Option( @CommandLine.Option(
names = ["-p", "--purge"], names = ["-p", "--purge"],
description = ["Purge the temporary resource cache directory after patching"], description = ["Purge the temporary resource cache directory after patching"],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
var purge: Boolean = false private var purge: Boolean = false
override fun run() { override fun run() {
// region Prepare // region Prepare
if (!apk.exists()) { if (!apk.exists()) {
logger.severe("Input file ${apk.name} does not exist") logger.severe("APK file ${apk.name} does not exist")
return
}
integrations.filter { !it.exists() }.let {
if (it.isEmpty()) return@let
it.forEach { integration ->
logger.severe("Integration file ${integration.name} does not exist")
}
return return
} }
@@ -192,7 +185,10 @@ internal object PatchCommand: Runnable {
runBlocking { runBlocking {
apply(false).collect { patchResult -> apply(false).collect { patchResult ->
patchResult.exception?.let { patchResult.exception?.let {
logger.severe("${patchResult.patchName} failed:\n${patchResult.exception}") StringWriter().use { writer ->
it.printStackTrace(PrintWriter(writer))
logger.severe("${patchResult.patchName} failed: $writer")
}
} ?: logger.info("${patchResult.patchName} succeeded") } ?: logger.info("${patchResult.patchName} succeeded")
} }
} }
@@ -206,8 +202,7 @@ internal object PatchCommand: Runnable {
val alignAndSignedFile = sign( val alignAndSignedFile = sign(
apk.newAlignedFile( apk.newAlignedFile(
result, result, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_aligned.apk")
resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_aligned.apk")
) )
) )
@@ -218,7 +213,6 @@ internal object PatchCommand: Runnable {
if (purge) { if (purge) {
logger.info("Purging temporary files") logger.info("Purging temporary files")
outputFilePath.delete()
purge(resourceCachePath) purge(resourceCachePath)
} }
@@ -231,8 +225,8 @@ internal object PatchCommand: Runnable {
* - [includedPatches] (explicitly included) * - [includedPatches] (explicitly included)
* - [excludedPatches] (explicitly excluded) * - [excludedPatches] (explicitly excluded)
* - [exclusive] (only include patches that are explicitly included) * - [exclusive] (only include patches that are explicitly included)
* - [experimental] (ignore patches incompatibility to versions) * - [force] (ignore patches incompatibility to versions)
* - package name and version of the input APK file (if [experimental] is false) * - Package name and version of the input APK file (if [force] is false)
* *
* @param patches The patches to filter. * @param patches The patches to filter.
* @return The filtered patches. * @return The filtered patches.
@@ -244,90 +238,38 @@ internal object PatchCommand: Runnable {
patches.forEach patch@{ patch -> patches.forEach patch@{ patch ->
val formattedPatchName = patch.patchName.lowercase().replace(" ", "-") val formattedPatchName = patch.patchName.lowercase().replace(" ", "-")
/** val explicitlyExcluded = excludedPatches.contains(formattedPatchName)
* Check if the patch is explicitly excluded. if (explicitlyExcluded) return@patch logger.info("Excluding ${patch.patchName}")
*
* Cases:
* 1. -e patch.name
* 2. -i patch.name -e patch.name
*/
/** // If the patch is explicitly included, it will be included if [exclusive] is false.
* Check if the patch is explicitly excluded. val explicitlyIncluded = exclusive && includedPatches.contains(formattedPatchName)
*
* Cases:
* 1. -e patch.name
* 2. -i patch.name -e patch.name
*/
val excluded = excludedPatches.contains(formattedPatchName) // If the patch is implicitly included, it will be only included if [exclusive] is false.
if (excluded) return@patch logger.info("Excluding ${patch.patchName}") val implicitlyIncluded = !exclusive && patch.include
/** val included = implicitlyIncluded || explicitlyIncluded
* Check if the patch is constrained to packages. if (!included) return@patch logger.info("${patch.patchName} excluded by default") // Case 1.
*/
/**
* Check if the patch is constrained to packages.
*/
// At last make sure the patch is compatible with the supplied APK files package name and version.
patch.compatiblePackages?.let { packages -> patch.compatiblePackages?.let { packages ->
packages.singleOrNull { it.name == packageName }?.let { `package` -> packages.singleOrNull { it.name == packageName }?.let { `package` ->
/** val matchesVersion = force || `package`.versions.let {
* Check if the package version matches.
* If experimental is true, version matching will be skipped.
*/
/**
* Check if the package version matches.
* If experimental is true, version matching will be skipped.
*/
val matchesVersion = experimental || `package`.versions.let {
it.isEmpty() || it.any { version -> version == packageVersion } it.isEmpty() || it.any { version -> version == packageVersion }
} }
if (!matchesVersion) return@patch logger.warning( if (!matchesVersion) return@patch logger.warning("${patch.patchName} is incompatible with version $packageVersion. " + "This patch is only compatible with version " + packages.joinToString(
"${patch.patchName} is incompatible with version $packageVersion. " + ";"
"This patch is only compatible with version " + ) { pkg ->
packages.joinToString(";") { pkg -> "${pkg.name}: ${pkg.versions.joinToString(", ")}"
"${pkg.name}: ${pkg.versions.joinToString(", ")}" })
}
)
} ?: return@patch logger.fine( } ?: return@patch logger.fine("${patch.patchName} is incompatible with $packageName. "
"${patch.patchName} is incompatible with $packageName. " + + "This patch is only compatible with "
"This patch is only compatible with " + + packages.joinToString(", ") { `package` -> `package`.name })
packages.joinToString(", ") { `package` -> `package`.name }
)
return@let return@let
} ?: logger.fine("$formattedPatchName: No constraint on packages.") } ?: logger.fine("$formattedPatchName: No constraint on packages.")
/**
* Check if the patch is explicitly included.
*
* Cases:
* 1. --exclusive
* 2. --exclusive -i patch.name
*/
/**
* Check if the patch is explicitly included.
*
* Cases:
* 1. --exclusive
* 2. --exclusive -i patch.name
*/
val explicitlyIncluded = 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.fine("Adding $formattedPatchName") logger.fine("Adding $formattedPatchName")
add(patch) add(patch)
@@ -341,8 +283,7 @@ internal object PatchCommand: Runnable {
* @param outputFile The file to save the aligned APK to. * @param outputFile The file to save the aligned APK to.
*/ */
private fun File.newAlignedFile( private fun File.newAlignedFile(
result: PatcherResult, result: PatcherResult, outputFile: File
outputFile: File
): File { ): File {
logger.info("Aligning $name") logger.info("Aligning $name")
@@ -351,23 +292,20 @@ internal object PatchCommand: Runnable {
ZipFile(outputFile).use { file -> ZipFile(outputFile).use { file ->
result.dexFiles.forEach { result.dexFiles.forEach {
file.addEntryCompressData( file.addEntryCompressData(
ZipEntry.createWithName(it.name), ZipEntry.createWithName(it.name), it.stream.readBytes()
it.stream.readBytes()
) )
} }
result.resourceFile?.let { result.resourceFile?.let {
file.copyEntriesFromFileAligned( file.copyEntriesFromFileAligned(
ZipFile(it), ZipFile(it), ZipAligner::getEntryAlignment
ZipAligner::getEntryAlignment
) )
} }
// TODO: Do not compress result.doNotCompress // TODO: Do not compress result.doNotCompress
file.copyEntriesFromFileAligned( file.copyEntriesFromFileAligned(
ZipFile(this), ZipFile(this), ZipAligner::getEntryAlignment
ZipAligner::getEntryAlignment
) )
} }
@@ -380,32 +318,25 @@ internal object PatchCommand: Runnable {
* @param inputFile The APK file to sign. * @param inputFile The APK file to sign.
* @return The signed APK file. If [mount] is true, the input file will be returned. * @return The signed APK file. If [mount] is true, the input file will be returned.
*/ */
private fun sign(inputFile: File) = if (mount) private fun sign(inputFile: File) = if (mount) inputFile
inputFile
else { else {
logger.info("Signing ${inputFile.name}") logger.info("Signing ${inputFile.name}")
val keyStoreFilePath = keystorePath ?: outputFilePath val keyStoreFilePath = keystorePath
.absoluteFile.parentFile.resolve("${outputFilePath.nameWithoutExtension}.keystore").canonicalPath ?: outputFilePath.absoluteFile.parentFile.resolve("${outputFilePath.nameWithoutExtension}.keystore").canonicalPath
val options = SigningOptions( val options = SigningOptions(
commonName, commonName, password, keyStoreFilePath
password,
keyStoreFilePath
) )
ApkSigner(options) ApkSigner(options).signApk(
.signApk( inputFile, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_signed.apk")
inputFile,
resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_signed.apk")
) )
} }
private fun purge(resourceCachePath: File) { private fun purge(resourceCachePath: File) {
val result = if (resourceCachePath.deleteRecursively()) val result = if (resourceCachePath.deleteRecursively()) "Purged resource cache directory"
"Purged resource cache directory" else "Failed to purge resource cache directory"
else
"Failed to purge resource cache directory"
logger.info(result) logger.info(result)
} }
} }

View File

@@ -0,0 +1,42 @@
package app.revanced.cli.command.utility
import app.revanced.utils.adb.AdbManager
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"]
)
internal object InstallCommand : Runnable {
private val logger = Logger.getLogger(InstallCommand::class.java.name)
@Parameters(
description = ["ADB device serials"], arity = "1..*"
)
private lateinit var deviceSerials: Array<String>
@Option(
names = ["-a", "--apk"], 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"],
)
private var packageName: String? = null
override fun run() = try {
deviceSerials.forEach { deviceSerial ->
if (packageName != null) {
AdbManager.RootAdbManager(deviceSerial)
} else {
AdbManager.UserAdbManager(deviceSerial)
}.install(AdbManager.Apk(apk, packageName))
}
} catch (e: AdbManager.DeviceNotFoundException) {
logger.severe(e.toString())
}
}

View File

@@ -1,4 +1,4 @@
package app.revanced.cli.command package app.revanced.cli.command.utility
import app.revanced.utils.adb.AdbManager import app.revanced.utils.adb.AdbManager
import picocli.CommandLine.* import picocli.CommandLine.*
@@ -8,33 +8,26 @@ import java.util.logging.Logger
@Command( @Command(
name = "uninstall", name = "uninstall",
description = ["Uninstall a patched APK file from the devices with the supplied ADB device serials"] description = ["Uninstall a patched app from the devices with the supplied ADB device serials"]
) )
internal object UninstallCommand : Runnable { internal object UninstallCommand : Runnable {
private val logger = Logger.getLogger(UninstallCommand::class.java.name) private val logger = Logger.getLogger(UninstallCommand::class.java.name)
@Parameters( @Parameters(description = ["ADB device serials"], arity = "1..*")
description = ["ADB device serials"], private lateinit var deviceSerials: Array<String>
arity = "1..*"
)
lateinit var deviceSerials: Array<String>
@Option( @Option(names = ["-p", "--package-name"], description = ["Package name of the app to uninstall"], required = true)
names = ["-p", "--package-name"], private lateinit var packageName: String
description = ["Package name to uninstall"],
required = true
)
lateinit var packageName: String
@Option( @Option(
names = ["-u", "--unmount"], names = ["-u", "--unmount"],
description = ["Uninstall by unmounting the patched package"], description = ["Uninstall by unmounting the patched APK file"],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
var unmount: Boolean = false private var unmount: Boolean = false
override fun run() = try { override fun run() = try {
deviceSerials.forEach {deviceSerial -> deviceSerials.forEach { deviceSerial ->
if (unmount) { if (unmount) {
AdbManager.RootAdbManager(deviceSerial) AdbManager.RootAdbManager(deviceSerial)
} else { } else {

View File

@@ -0,0 +1,10 @@
package app.revanced.cli.command.utility
import picocli.CommandLine
@CommandLine.Command(
name = "utility",
description = ["Commands for utility purposes"],
subcommands = [InstallCommand::class, UninstallCommand::class],
)
internal object UtilityCommand

View File

@@ -62,14 +62,17 @@ internal object Options {
val patchOptions = deserialize(json) val patchOptions = deserialize(json)
patches.forEach { patch -> patches.forEach patch@{ patch ->
patchOptions.find { option -> option.patchName == patch.patchName }?.let { patchOptions.find { option -> option.patchName == patch.patchName }?.let {
it.options.forEach { option -> it.options.forEach { option ->
try { try {
patch.options?.set(option.key, option.value) patch.options?.set(option.key, option.value)
?: logger.warning("${patch.patchName} has no options") ?: run{
logger.warning("${patch.patchName} has no options")
return@patch
}
} catch (e: NoSuchOptionException) { } catch (e: NoSuchOptionException) {
logger.info(e.message ?: "Unknown error") logger.info(e.toString())
} }
} }
} }

View File

@@ -1,18 +1,18 @@
package app.revanced.utils.adb package app.revanced.utils.adb
import app.revanced.utils.adb.AdbManager.Apk import app.revanced.utils.adb.AdbManager.Apk
import app.revanced.utils.adb.Constants.COMMAND_CREATE_DIR import app.revanced.utils.adb.Constants.CREATE_DIR
import app.revanced.utils.adb.Constants.COMMAND_DELETE import app.revanced.utils.adb.Constants.DELETE
import app.revanced.utils.adb.Constants.COMMAND_INSTALL_MOUNT import app.revanced.utils.adb.Constants.INSTALLATION_PATH
import app.revanced.utils.adb.Constants.COMMAND_PREPARE_MOUNT_APK import app.revanced.utils.adb.Constants.INSTALL_MOUNT
import app.revanced.utils.adb.Constants.COMMAND_RESTART import app.revanced.utils.adb.Constants.INSTALL_PATCHED_APK
import app.revanced.utils.adb.Constants.COMMAND_UMOUNT import app.revanced.utils.adb.Constants.MOUNT_PATH
import app.revanced.utils.adb.Constants.CONTENT_MOUNT_SCRIPT import app.revanced.utils.adb.Constants.MOUNT_SCRIPT
import app.revanced.utils.adb.Constants.PATH_INIT_PUSH import app.revanced.utils.adb.Constants.PATCHED_APK_PATH
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 app.revanced.utils.adb.Constants.PLACEHOLDER
import app.revanced.utils.adb.Constants.RESTART
import app.revanced.utils.adb.Constants.TMP_PATH
import app.revanced.utils.adb.Constants.UMOUNT
import se.vidstige.jadb.JadbConnection import se.vidstige.jadb.JadbConnection
import se.vidstige.jadb.managers.Package import se.vidstige.jadb.managers.Package
import se.vidstige.jadb.managers.PackageManager import se.vidstige.jadb.managers.PackageManager
@@ -72,29 +72,31 @@ internal sealed class AdbManager(deviceSerial: String? = null) : Closeable {
apk.packageName ?: throw IllegalArgumentException("Package name is required") apk.packageName ?: throw IllegalArgumentException("Package name is required")
) )
device.copyFile(apk.file, PATH_INIT_PUSH) device.push(apk.file, TMP_PATH)
device.run("$COMMAND_CREATE_DIR $PATH_INSTALLATION") device.run("$CREATE_DIR $INSTALLATION_PATH")
device.run(COMMAND_PREPARE_MOUNT_APK.applyReplacement()) device.run(INSTALL_PATCHED_APK.applyReplacement())
device.createFile(PATH_INIT_PUSH, CONTENT_MOUNT_SCRIPT.applyReplacement()) device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement())
device.run(COMMAND_INSTALL_MOUNT.applyReplacement()) device.run(INSTALL_MOUNT.applyReplacement())
device.run(COMMAND_UMOUNT.applyReplacement()) // Sanity check. device.run(UMOUNT.applyReplacement()) // Sanity check.
device.run(PATH_MOUNT.applyReplacement()) device.run(MOUNT_PATH.applyReplacement())
device.run(COMMAND_RESTART.applyReplacement()) device.run(RESTART.applyReplacement())
device.run(DELETE.applyReplacement(TMP_PATH).applyReplacement())
super.install(apk) super.install(apk)
} }
override fun uninstall(packageName: String) { override fun uninstall(packageName: String) {
logger.info("Uninstalling $packageName by unmounting and deleting the package") logger.info("Uninstalling $packageName by unmounting")
val applyReplacement = getPlaceholderReplacement(packageName) val applyReplacement = getPlaceholderReplacement(packageName)
device.run(COMMAND_UMOUNT.applyReplacement(packageName)) device.run(UMOUNT.applyReplacement(packageName))
device.run(COMMAND_DELETE.applyReplacement(PATH_PATCHED_APK).applyReplacement()) device.run(DELETE.applyReplacement(PATCHED_APK_PATH).applyReplacement())
device.run(COMMAND_DELETE.applyReplacement(PATH_MOUNT).applyReplacement()) device.run(DELETE.applyReplacement(MOUNT_PATH).applyReplacement())
device.run(DELETE.applyReplacement(TMP_PATH).applyReplacement())
super.uninstall(packageName) super.uninstall(packageName)
} }
@@ -127,6 +129,7 @@ internal sealed class AdbManager(deviceSerial: String? = null) : Closeable {
* Apk file for [AdbManager]. * Apk file for [AdbManager].
* *
* @param file The [Apk] file. * @param file The [Apk] file.
* @param packageName The package name of the [Apk] file.
*/ */
internal class Apk(val file: File, val packageName: String? = null) internal class Apk(val file: File, val packageName: String? = null)

View File

@@ -2,24 +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
// return the input or output stream, depending on which first returns a value
internal fun JadbDevice.run(command: String, su: Boolean = false) = with(this.startCommand(command, su)) { internal fun JadbDevice.buildCommand(command: String, su: Boolean = true): ShellProcessBuilder {
Executors.newFixedThreadPool(2).let { service -> if (su) return shellProcessBuilder("su -c \'$command\'")
arrayOf(inputStream, errorStream).map { stream ->
Callable { stream.bufferedReader().use { it.readLine() } } val args = command.split(" ") as ArrayList<String>
}.let { tasks -> service.invokeAny(tasks).also { service.shutdown() } } val cmd = args.removeFirst()
}
return shellProcessBuilder(cmd, *args.toTypedArray())
}
internal fun JadbDevice.run(command: String, su: Boolean = true): Int {
return this.buildCommand(command, su).start().waitFor()
} }
internal fun JadbDevice.hasSu() = internal fun JadbDevice.hasSu() =
this.startCommand("su -h", false).waitFor() == 0 this.startCommand("su -h", false).waitFor() == 0
internal fun JadbDevice.copyFile(file: File, targetFile: String) = internal fun JadbDevice.push(file: File, targetFilePath: String) =
push(file, RemoteFile(targetFile)) push(file, RemoteFile(targetFilePath))
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))

View File

@@ -1,40 +1,40 @@
package app.revanced.utils.adb package app.revanced.utils.adb
internal object Constants { internal object Constants {
internal const val PLACEHOLDER = "TEMPLATE_PACKAGE_NAME" internal const val PLACEHOLDER = "PLACEHOLDER"
internal const val PATH_INIT_PUSH = "/data/local/tmp/revanced.delete" internal const val TMP_PATH = "/data/local/tmp/revanced.tmp"
internal const val PATH_INSTALLATION = "/data/adb/revanced/" internal const val INSTALLATION_PATH = "/data/adb/revanced/"
internal const val PATH_PATCHED_APK = "$PATH_INSTALLATION$PLACEHOLDER.apk" internal const val PATCHED_APK_PATH = "$INSTALLATION_PATH$PLACEHOLDER.apk"
internal const val PATH_MOUNT = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh" internal const val MOUNT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
internal const val COMMAND_DELETE = "rm -rf $PLACEHOLDER" internal const val DELETE = "rm -rf $PLACEHOLDER"
internal const val COMMAND_CREATE_DIR = "mkdir -p" internal const val CREATE_DIR = "mkdir -p"
internal const val COMMAND_RESTART = "pm resolve-activity --brief $PLACEHOLDER | tail -n 1 | " + internal const val RESTART = "pm resolve-activity --brief $PLACEHOLDER | tail -n 1 | " +
"xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)" "xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)"
internal const val COMMAND_PREPARE_MOUNT_APK = "base_path=\"$PATH_PATCHED_APK\" && " + internal const val INSTALL_PATCHED_APK = "base_path=\"$PATCHED_APK_PATH\" && " +
"mv $PATH_INIT_PUSH ${'$'}base_path && " + "mv $TMP_PATH ${'$'}base_path && " +
"chmod 644 ${'$'}base_path && " + "chmod 644 ${'$'}base_path && " +
"chown system:system ${'$'}base_path && " + "chown system:system ${'$'}base_path && " +
"chcon u:object_r:apk_data_file:s0 ${'$'}base_path" "chcon u:object_r:apk_data_file:s0 ${'$'}base_path"
internal const val COMMAND_UMOUNT = internal const val 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"
internal const val COMMAND_INSTALL_MOUNT = "mv $PATH_INIT_PUSH $PATH_MOUNT && chmod +x $PATH_MOUNT" internal const val INSTALL_MOUNT = "mv $TMP_PATH $MOUNT_PATH && chmod +x $MOUNT_PATH"
internal const val CONTENT_MOUNT_SCRIPT = internal val 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_PATCHED_APK" base_path="$PATCHED_APK_PATH"
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()
} }