mirror of
https://github.com/ReVanced/revanced-cli.git
synced 2026-01-11 13:56:18 +00:00
Compare commits
28 Commits
v3.0.0-dev
...
v3.0.1-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48e1689223 | ||
|
|
7580f5c2fc | ||
|
|
dd6c1392eb | ||
|
|
2a3dbafd17 | ||
|
|
9e39a6f8e4 | ||
|
|
2d5a7fdf1e | ||
|
|
be5d812dff | ||
|
|
c93186fb97 | ||
|
|
3a198052bb | ||
|
|
0f3e090418 | ||
|
|
6aed946183 | ||
|
|
924c1f80ec | ||
|
|
8dd709b6ef | ||
|
|
139e7facac | ||
|
|
1d26e572f7 | ||
|
|
2c7fcaf4ad | ||
|
|
52c3be23f2 | ||
|
|
3dd875d14c | ||
|
|
0350b7f1a2 | ||
|
|
a3d8705e89 | ||
|
|
a536c9f815 | ||
|
|
11c3a6cfd4 | ||
|
|
a5851f0c1a | ||
|
|
963ae3a5fa | ||
|
|
93f338a731 | ||
|
|
7dcf15e03b | ||
|
|
41898d7547 | ||
|
|
45dd15f679 |
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -33,10 +33,11 @@ jobs:
|
||||
build
|
||||
node_modules
|
||||
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
|
||||
- name: Build with Gradle
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build
|
||||
# Cleaning is necessary to avoid uploading two identical artifacts with different versions
|
||||
run: ./gradlew clean --no-daemon
|
||||
- name: Setup semantic-release
|
||||
run: npm install
|
||||
- name: Release
|
||||
|
||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -1,3 +1,99 @@
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* properly make use of logging facade ([41898d7](https://github.com/ReVanced/revanced-cli/commit/41898d7547690e3130372414515c5645e5dc2634))
|
||||
|
||||
# [3.0.0-dev.3](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.2...v3.0.0-dev.3) (2023-08-23)
|
||||
|
||||
# [3.0.0-dev.2](https://github.com/ReVanced/revanced-cli/compare/v3.0.0-dev.1...v3.0.0-dev.2) (2023-08-23)
|
||||
|
||||
@@ -52,5 +52,6 @@ tasks {
|
||||
register<DefaultTask>("publish") {
|
||||
group = "publish"
|
||||
description = "Dummy task"
|
||||
dependsOn(build)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,12 +86,26 @@ Learn how to ReVanced CLI.
|
||||
|
||||
> **Note**: Some patches may require 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.
|
||||
|
||||
- ### 🗑️ Uninstall a patched APK file
|
||||
```bash
|
||||
java -jar revanced-cli.jar uninstall \
|
||||
java -jar revanced-cli.jar utility uninstall \
|
||||
--package-name <package-name> \
|
||||
<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`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 3.0.0-dev.3
|
||||
version = 3.0.1-dev.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[versions]
|
||||
shadow = "8.1.1"
|
||||
apksig = "8.1.0"
|
||||
apksig = "8.1.1"
|
||||
bcpkix-jdk15on = "1.70"
|
||||
jackson-module-kotlin = "2.14.3"
|
||||
jadb = "2531a28109"
|
||||
@@ -8,7 +8,7 @@ kotlin-reflect = "1.9.0"
|
||||
kotlin-test = "1.8.20-RC"
|
||||
kotlinx-coroutines-core = "1.7.1"
|
||||
picocli = "4.7.3"
|
||||
revanced-patcher = "14.0.0"
|
||||
revanced-patcher = "14.2.1"
|
||||
|
||||
[libraries]
|
||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
||||
|
||||
@@ -11,43 +11,41 @@ import app.revanced.patcher.patch.PatchOption
|
||||
import picocli.CommandLine.*
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
@Command(name = "list-patches", description = ["List patches from supplied patch bundles"])
|
||||
internal object ListPatchesCommand : Runnable {
|
||||
private val logger = Logger.getLogger(ListPatchesCommand::class.java.name)
|
||||
|
||||
@Parameters(
|
||||
description = ["Paths to patch bundles"],
|
||||
arity = "1..*"
|
||||
description = ["Paths to patch bundles"], arity = "1..*"
|
||||
)
|
||||
lateinit var patchBundles: Array<File>
|
||||
private lateinit var patchBundles: Array<File>
|
||||
|
||||
@Option(
|
||||
names = ["-d", "--with-descriptions"],
|
||||
description = ["List their descriptions"],
|
||||
showDefaultValue = ALWAYS
|
||||
names = ["-d", "--with-descriptions"], description = ["List their descriptions"], showDefaultValue = ALWAYS
|
||||
)
|
||||
var withDescriptions: Boolean = true
|
||||
private var withDescriptions: Boolean = true
|
||||
|
||||
@Option(
|
||||
names = ["-p", "--with-packages"],
|
||||
description = ["List the packages the patches are compatible with"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
var withPackages: Boolean = false
|
||||
private var withPackages: Boolean = false
|
||||
|
||||
@Option(
|
||||
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
|
||||
)
|
||||
var withVersions: Boolean = false
|
||||
private var withVersions: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-o", "--with-options"],
|
||||
description = ["List the options of the patches"],
|
||||
showDefaultValue = ALWAYS
|
||||
names = ["-o", "--with-options"], description = ["List the options of the patches"], showDefaultValue = ALWAYS
|
||||
)
|
||||
var withOptions: Boolean = false
|
||||
private var withOptions: Boolean = false
|
||||
|
||||
override fun run() {
|
||||
fun Package.buildString() = buildString {
|
||||
@@ -55,8 +53,7 @@ internal object ListPatchesCommand : Runnable {
|
||||
appendLine("Package name: $name")
|
||||
appendLine("Compatible versions:")
|
||||
append(versions.joinToString("\n") { version -> version }.prependIndent("\t"))
|
||||
} else
|
||||
append("Package name: $name")
|
||||
} else append("Package name: $name")
|
||||
}
|
||||
|
||||
fun PatchOption<*>.buildString() = buildString {
|
||||
|
||||
@@ -1,21 +1,48 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.cli.logging.impl.DefaultCliLogger
|
||||
import app.revanced.cli.command.utility.UtilityCommand
|
||||
import app.revanced.patcher.patch.PatchClass
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.IVersionProvider
|
||||
import java.util.*
|
||||
import java.util.logging.*
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
System.setProperty("java.util.logging.SimpleFormatter.format", "%4\$s: %5\$s %n")
|
||||
Logger.getLogger("").apply {
|
||||
handlers.forEach {
|
||||
it.close()
|
||||
removeHandler(it)
|
||||
}
|
||||
|
||||
object : Handler() {
|
||||
override fun publish(record: LogRecord) = formatter.format(record).toByteArray().let {
|
||||
if (record.level.intValue() > Level.INFO.intValue())
|
||||
System.err.write(it)
|
||||
else
|
||||
System.out.write(it)
|
||||
}
|
||||
|
||||
override fun flush() {
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
}
|
||||
|
||||
override fun close() = flush()
|
||||
}.also {
|
||||
it.level = Level.ALL
|
||||
it.formatter = SimpleFormatter()
|
||||
}.let(::addHandler)
|
||||
}
|
||||
|
||||
CommandLine(MainCommand).execute(*args)
|
||||
}
|
||||
|
||||
internal typealias PatchList = List<PatchClass>
|
||||
|
||||
internal val logger = DefaultCliLogger()
|
||||
|
||||
object CLIVersionProvider : IVersionProvider {
|
||||
private object CLIVersionProvider : IVersionProvider {
|
||||
override fun getVersion(): Array<String> {
|
||||
Properties().apply {
|
||||
load(MainCommand::class.java.getResourceAsStream("/app/revanced/cli/version.properties"))
|
||||
@@ -33,8 +60,8 @@ object CLIVersionProvider : IVersionProvider {
|
||||
subcommands = [
|
||||
ListPatchesCommand::class,
|
||||
PatchCommand::class,
|
||||
UninstallCommand::class,
|
||||
OptionsCommand::class,
|
||||
UtilityCommand::class,
|
||||
]
|
||||
)
|
||||
internal object MainCommand
|
||||
private object MainCommand
|
||||
@@ -6,45 +6,41 @@ import app.revanced.utils.Options.setOptions
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "options",
|
||||
description = ["Generate options file from patches"],
|
||||
)
|
||||
internal object OptionsCommand : Runnable {
|
||||
private val logger = Logger.getLogger(OptionsCommand::class.java.name)
|
||||
|
||||
@CommandLine.Parameters(
|
||||
description = ["Paths to patch bundles"],
|
||||
arity = "1..*"
|
||||
description = ["Paths to patch bundles"], arity = "1..*"
|
||||
)
|
||||
lateinit var patchBundles: Array<File>
|
||||
private lateinit var patchBundles: Array<File>
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-p", "--path"],
|
||||
description = ["Path to patch options JSON file"],
|
||||
showDefaultValue = ALWAYS
|
||||
names = ["-p", "--path"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS
|
||||
)
|
||||
var path: File = File("options.json")
|
||||
private var path: File = File("options.json")
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-o", "--overwrite"],
|
||||
description = ["Overwrite existing options file"],
|
||||
showDefaultValue = ALWAYS
|
||||
names = ["-o", "--overwrite"], description = ["Overwrite existing options file"], showDefaultValue = ALWAYS
|
||||
)
|
||||
var overwrite: Boolean = false
|
||||
private var overwrite: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-u", "--update"],
|
||||
description = ["Update existing options by adding missing and removing non-existent options"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
var update: Boolean = false
|
||||
private var update: Boolean = false
|
||||
|
||||
override fun run() = if (!path.exists() || overwrite)
|
||||
with(PatchBundleLoader.Jar(*patchBundles)) {
|
||||
if (update) setOptions(path, logger)
|
||||
override fun run() = if (!path.exists() || overwrite) with(PatchBundleLoader.Jar(*patchBundles)) {
|
||||
if (update) setOptions(path)
|
||||
|
||||
Options.serialize(this, prettyPrint = true)
|
||||
.let(path::writeText)
|
||||
}
|
||||
else logger.error("Options file already exists, use --override to override it")
|
||||
Options.serialize(this, prettyPrint = true).let(path::writeText)
|
||||
}
|
||||
else logger.severe("Options file already exists, use --override to override it")
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.cli.patcher.logging.impl.PatcherLogger
|
||||
import app.revanced.patcher.PatchBundleLoader
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.PatcherOptions
|
||||
@@ -20,85 +19,75 @@ import kotlinx.coroutines.runBlocking
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "patch",
|
||||
description = ["Patch the supplied APK file with the supplied patches and integrations"]
|
||||
name = "patch", 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)
|
||||
|
||||
@CommandLine.Parameters(
|
||||
description = ["APK file to be patched"],
|
||||
arity = "1..1"
|
||||
description = ["APK file to be patched"], arity = "1..1"
|
||||
)
|
||||
lateinit var apk: File
|
||||
private lateinit var apk: File
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-b", "--patch-bundle"],
|
||||
description = ["One or more bundles of patches"],
|
||||
required = true
|
||||
names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches"], required = true
|
||||
)
|
||||
var patchBundles = emptyList<File>()
|
||||
private var patchBundles = emptyList<File>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-m", "--merge"],
|
||||
description = ["One or more DEX files or containers to merge into the APK"]
|
||||
names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"]
|
||||
)
|
||||
var integrations = listOf<File>()
|
||||
private var integrations = listOf<File>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-i", "--include"],
|
||||
description = ["List of patches to include"]
|
||||
names = ["-i", "--include"], description = ["List of patches to include"]
|
||||
)
|
||||
var includedPatches = arrayOf<String>()
|
||||
private var includedPatches = arrayOf<String>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-e", "--exclude"],
|
||||
description = ["List of patches to exclude"]
|
||||
names = ["-e", "--exclude"], description = ["List of patches to exclude"]
|
||||
)
|
||||
var excludedPatches = arrayOf<String>()
|
||||
private var excludedPatches = arrayOf<String>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--options"],
|
||||
description = ["Path to patch options JSON file"],
|
||||
showDefaultValue = ALWAYS
|
||||
names = ["--options"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS
|
||||
)
|
||||
var optionsFile: File = File("options.json")
|
||||
private var optionsFile: File = File("options.json")
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--exclusive"],
|
||||
description = ["Only include patches that are explicitly specified to be included"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
var exclusive = false
|
||||
private var exclusive = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--experimental"],
|
||||
description = ["Ignore patches incompatibility to versions"],
|
||||
names = ["-f","--force"],
|
||||
description = ["Force inclusion of patches that are incompatible with the supplied APK file's version"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
var experimental: Boolean = false
|
||||
private var force: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-o", "--out"],
|
||||
description = ["Path to save the patched APK file to"],
|
||||
required = true
|
||||
names = ["-o", "--out"], description = ["Path to save the patched APK file to"], required = true
|
||||
)
|
||||
lateinit var outputFilePath: File
|
||||
private lateinit var outputFilePath: File
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-d", "--device-serial"],
|
||||
description = ["ADB device serial to install to"],
|
||||
showDefaultValue = ALWAYS
|
||||
names = ["-d", "--device-serial"], description = ["ADB device serial to install to"], showDefaultValue = ALWAYS
|
||||
)
|
||||
var deviceSerial: String? = null
|
||||
private var deviceSerial: String? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--mount"],
|
||||
description = ["Install by mounting the patched package"],
|
||||
showDefaultValue = ALWAYS
|
||||
names = ["--mount"], description = ["Install by mounting the patched APK file"], showDefaultValue = ALWAYS
|
||||
)
|
||||
var mount: Boolean = false
|
||||
private var mount: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--common-name"],
|
||||
@@ -106,53 +95,57 @@ internal object PatchCommand: Runnable {
|
||||
showDefaultValue = ALWAYS
|
||||
|
||||
)
|
||||
var commonName = "ReVanced"
|
||||
private var commonName = "ReVanced"
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--keystore"],
|
||||
description = ["Path to the keystore to sign the patched APK file with"]
|
||||
names = ["--keystore"], description = ["Path to the keystore to sign the patched APK file with"]
|
||||
)
|
||||
var keystorePath: String? = null
|
||||
private var keystorePath: String? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--password"],
|
||||
description = ["The password of the keystore to sign the patched APK file with"]
|
||||
names = ["--password"], description = ["The password of the keystore to sign the patched APK file with"]
|
||||
)
|
||||
var password = "ReVanced"
|
||||
private var password = "ReVanced"
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-r", "--resource-cache"],
|
||||
description = ["Path to temporary resource cache directory"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
var resourceCachePath = File("revanced-resource-cache")
|
||||
private var resourceCachePath = File("revanced-resource-cache")
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--custom-aapt2-binary"],
|
||||
description = ["Path to a custom AAPT binary to compile resources with"]
|
||||
names = ["--custom-aapt2-binary"], description = ["Path to a custom AAPT binary to compile resources with"]
|
||||
)
|
||||
var aaptBinaryPath = File("")
|
||||
private var aaptBinaryPath = File("")
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-p", "--purge"],
|
||||
description = ["Purge the temporary resource cache directory after patching"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
var purge: Boolean = false
|
||||
private var purge: Boolean = false
|
||||
|
||||
override fun run() {
|
||||
// region Prepare
|
||||
|
||||
if (!apk.exists()) {
|
||||
logger.error("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
|
||||
}
|
||||
|
||||
val adbManager = deviceSerial?.let { serial ->
|
||||
if (mount) AdbManager.RootAdbManager(serial, logger) else AdbManager.UserAdbManager(
|
||||
serial,
|
||||
logger
|
||||
)
|
||||
if (mount) AdbManager.RootAdbManager(serial)
|
||||
else AdbManager.UserAdbManager(serial)
|
||||
}
|
||||
|
||||
// endregion
|
||||
@@ -167,7 +160,7 @@ internal object PatchCommand: Runnable {
|
||||
logger.info("Setting patch options")
|
||||
|
||||
optionsFile.let {
|
||||
if (it.exists()) patches.setOptions(it, logger)
|
||||
if (it.exists()) patches.setOptions(it)
|
||||
else Options.serialize(patches, prettyPrint = true).let(it::writeText)
|
||||
}
|
||||
|
||||
@@ -181,7 +174,6 @@ internal object PatchCommand: Runnable {
|
||||
resourceCachePath,
|
||||
aaptBinaryPath.path,
|
||||
resourceCachePath.absolutePath,
|
||||
PatcherLogger
|
||||
)
|
||||
)
|
||||
|
||||
@@ -193,7 +185,10 @@ internal object PatchCommand: Runnable {
|
||||
runBlocking {
|
||||
apply(false).collect { patchResult ->
|
||||
patchResult.exception?.let {
|
||||
logger.error("${patchResult.patchName} failed:\n${patchResult.exception}")
|
||||
StringWriter().use { writer ->
|
||||
it.printStackTrace(PrintWriter(writer))
|
||||
logger.severe("${patchResult.patchName} failed: $writer")
|
||||
}
|
||||
} ?: logger.info("${patchResult.patchName} succeeded")
|
||||
}
|
||||
}
|
||||
@@ -207,8 +202,7 @@ internal object PatchCommand: Runnable {
|
||||
|
||||
val alignAndSignedFile = sign(
|
||||
apk.newAlignedFile(
|
||||
result,
|
||||
resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_aligned.apk")
|
||||
result, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_aligned.apk")
|
||||
)
|
||||
)
|
||||
|
||||
@@ -219,7 +213,6 @@ internal object PatchCommand: Runnable {
|
||||
|
||||
if (purge) {
|
||||
logger.info("Purging temporary files")
|
||||
outputFilePath.delete()
|
||||
purge(resourceCachePath)
|
||||
}
|
||||
|
||||
@@ -232,8 +225,8 @@ internal object PatchCommand: Runnable {
|
||||
* - [includedPatches] (explicitly included)
|
||||
* - [excludedPatches] (explicitly excluded)
|
||||
* - [exclusive] (only include patches that are explicitly included)
|
||||
* - [experimental] (ignore patches incompatibility to versions)
|
||||
* - package name and version of the input APK file (if [experimental] is false)
|
||||
* - [force] (ignore patches incompatibility to versions)
|
||||
* - Package name and version of the input APK file (if [force] is false)
|
||||
*
|
||||
* @param patches The patches to filter.
|
||||
* @return The filtered patches.
|
||||
@@ -245,91 +238,39 @@ internal object PatchCommand: Runnable {
|
||||
patches.forEach patch@{ patch ->
|
||||
val formattedPatchName = patch.patchName.lowercase().replace(" ", "-")
|
||||
|
||||
/**
|
||||
* Check if the patch is explicitly excluded.
|
||||
*
|
||||
* Cases:
|
||||
* 1. -e patch.name
|
||||
* 2. -i patch.name -e patch.name
|
||||
*/
|
||||
val explicitlyExcluded = excludedPatches.contains(formattedPatchName)
|
||||
if (explicitlyExcluded) return@patch logger.info("Excluding ${patch.patchName}")
|
||||
|
||||
/**
|
||||
* Check if the patch is explicitly excluded.
|
||||
*
|
||||
* 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.
|
||||
val explicitlyIncluded = exclusive && includedPatches.contains(formattedPatchName)
|
||||
|
||||
val excluded = excludedPatches.contains(formattedPatchName)
|
||||
if (excluded) return@patch logger.info("Excluding ${patch.patchName}")
|
||||
// If the patch is implicitly included, it will be only included if [exclusive] is false.
|
||||
val implicitlyIncluded = !exclusive && patch.include
|
||||
|
||||
/**
|
||||
* Check if the patch is constrained to packages.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if the patch is constrained to packages.
|
||||
*/
|
||||
val included = implicitlyIncluded || explicitlyIncluded
|
||||
if (!included) return@patch logger.info("${patch.patchName} excluded by default") // Case 1.
|
||||
|
||||
// At last make sure the patch is compatible with the supplied APK files package name and version.
|
||||
patch.compatiblePackages?.let { packages ->
|
||||
packages.singleOrNull { it.name == packageName }?.let { `package` ->
|
||||
/**
|
||||
* Check if the package version matches.
|
||||
* If experimental is true, version matching will be skipped.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if the package version matches.
|
||||
* If experimental is true, version matching will be skipped.
|
||||
*/
|
||||
|
||||
val matchesVersion = experimental || `package`.versions.let {
|
||||
val matchesVersion = force || `package`.versions.let {
|
||||
it.isEmpty() || it.any { version -> version == packageVersion }
|
||||
}
|
||||
|
||||
if (!matchesVersion) return@patch logger.warn(
|
||||
"${patch.patchName} is incompatible with version $packageVersion. " +
|
||||
"This patch is only compatible with version " +
|
||||
packages.joinToString(";") { pkg ->
|
||||
"${pkg.name}: ${pkg.versions.joinToString(", ")}"
|
||||
}
|
||||
)
|
||||
if (!matchesVersion) return@patch logger.warning("${patch.patchName} is incompatible with version $packageVersion. " + "This patch is only compatible with version " + packages.joinToString(
|
||||
";"
|
||||
) { pkg ->
|
||||
"${pkg.name}: ${pkg.versions.joinToString(", ")}"
|
||||
})
|
||||
|
||||
} ?: return@patch logger.trace(
|
||||
"${patch.patchName} is incompatible with $packageName. " +
|
||||
"This patch is only compatible with " +
|
||||
packages.joinToString(", ") { `package` -> `package`.name }
|
||||
)
|
||||
} ?: return@patch logger.fine("${patch.patchName} is incompatible with $packageName. "
|
||||
+ "This patch is only compatible with "
|
||||
+ packages.joinToString(", ") { `package` -> `package`.name })
|
||||
|
||||
return@let
|
||||
} ?: logger.trace("$formattedPatchName: No constraint on packages.")
|
||||
} ?: 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.trace("Adding $formattedPatchName")
|
||||
logger.fine("Adding $formattedPatchName")
|
||||
|
||||
add(patch)
|
||||
}
|
||||
@@ -342,8 +283,7 @@ internal object PatchCommand: Runnable {
|
||||
* @param outputFile The file to save the aligned APK to.
|
||||
*/
|
||||
private fun File.newAlignedFile(
|
||||
result: PatcherResult,
|
||||
outputFile: File
|
||||
result: PatcherResult, outputFile: File
|
||||
): File {
|
||||
logger.info("Aligning $name")
|
||||
|
||||
@@ -352,23 +292,20 @@ internal object PatchCommand: Runnable {
|
||||
ZipFile(outputFile).use { file ->
|
||||
result.dexFiles.forEach {
|
||||
file.addEntryCompressData(
|
||||
ZipEntry.createWithName(it.name),
|
||||
it.stream.readBytes()
|
||||
ZipEntry.createWithName(it.name), it.stream.readBytes()
|
||||
)
|
||||
}
|
||||
|
||||
result.resourceFile?.let {
|
||||
file.copyEntriesFromFileAligned(
|
||||
ZipFile(it),
|
||||
ZipAligner::getEntryAlignment
|
||||
ZipFile(it), ZipAligner::getEntryAlignment
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Do not compress result.doNotCompress
|
||||
|
||||
file.copyEntriesFromFileAligned(
|
||||
ZipFile(this),
|
||||
ZipAligner::getEntryAlignment
|
||||
ZipFile(this), ZipAligner::getEntryAlignment
|
||||
)
|
||||
}
|
||||
|
||||
@@ -381,32 +318,25 @@ internal object PatchCommand: Runnable {
|
||||
* @param inputFile The APK file to sign.
|
||||
* @return The signed APK file. If [mount] is true, the input file will be returned.
|
||||
*/
|
||||
private fun sign(inputFile: File) = if (mount)
|
||||
inputFile
|
||||
private fun sign(inputFile: File) = if (mount) inputFile
|
||||
else {
|
||||
logger.info("Signing ${inputFile.name}")
|
||||
|
||||
val keyStoreFilePath = keystorePath ?: outputFilePath
|
||||
.absoluteFile.parentFile.resolve("${outputFilePath.nameWithoutExtension}.keystore").canonicalPath
|
||||
val keyStoreFilePath = keystorePath
|
||||
?: outputFilePath.absoluteFile.parentFile.resolve("${outputFilePath.nameWithoutExtension}.keystore").canonicalPath
|
||||
|
||||
val options = SigningOptions(
|
||||
commonName,
|
||||
password,
|
||||
keyStoreFilePath
|
||||
commonName, password, keyStoreFilePath
|
||||
)
|
||||
|
||||
ApkSigner(options)
|
||||
.signApk(
|
||||
inputFile,
|
||||
resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_signed.apk")
|
||||
ApkSigner(options).signApk(
|
||||
inputFile, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_signed.apk")
|
||||
)
|
||||
}
|
||||
|
||||
private fun purge(resourceCachePath: File) {
|
||||
val result = if (resourceCachePath.deleteRecursively())
|
||||
"Purged resource cache directory"
|
||||
else
|
||||
"Failed to purge resource cache directory"
|
||||
val result = if (resourceCachePath.deleteRecursively()) "Purged resource cache directory"
|
||||
else "Failed to purge resource cache directory"
|
||||
logger.info(result)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.utils.adb.AdbManager
|
||||
import picocli.CommandLine.*
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
|
||||
|
||||
@Command(
|
||||
name = "uninstall",
|
||||
description = ["Uninstall a patched APK file from the devices with the supplied ADB device serials"]
|
||||
)
|
||||
internal object UninstallCommand : Runnable {
|
||||
@Parameters(
|
||||
description = ["ADB device serials"],
|
||||
arity = "1..*"
|
||||
)
|
||||
lateinit var deviceSerials: Array<String>
|
||||
|
||||
@Option(
|
||||
names = ["-p", "--package-name"],
|
||||
description = ["Package name to uninstall"],
|
||||
required = true
|
||||
)
|
||||
lateinit var packageName: String
|
||||
|
||||
@Option(
|
||||
names = ["-u", "--unmount"],
|
||||
description = ["Uninstall by unmounting the patched package"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
var unmount: Boolean = false
|
||||
|
||||
override fun run() = try {
|
||||
deviceSerials.forEach {deviceSerial ->
|
||||
if (unmount) {
|
||||
AdbManager.RootAdbManager(deviceSerial, logger)
|
||||
} else {
|
||||
AdbManager.UserAdbManager(deviceSerial, logger)
|
||||
}.uninstall(packageName)
|
||||
}
|
||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||
logger.error(e.toString())
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package app.revanced.cli.command.utility
|
||||
|
||||
import app.revanced.utils.adb.AdbManager
|
||||
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"]
|
||||
)
|
||||
internal object UninstallCommand : Runnable {
|
||||
private val logger = Logger.getLogger(UninstallCommand::class.java.name)
|
||||
|
||||
@Parameters(description = ["ADB device serials"], arity = "1..*")
|
||||
private lateinit var deviceSerials: Array<String>
|
||||
|
||||
@Option(names = ["-p", "--package-name"], 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"],
|
||||
showDefaultValue = ALWAYS
|
||||
)
|
||||
private var unmount: Boolean = false
|
||||
|
||||
override fun run() = try {
|
||||
deviceSerials.forEach { deviceSerial ->
|
||||
if (unmount) {
|
||||
AdbManager.RootAdbManager(deviceSerial)
|
||||
} else {
|
||||
AdbManager.UserAdbManager(deviceSerial)
|
||||
}.uninstall(packageName)
|
||||
}
|
||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||
logger.severe(e.toString())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,8 +0,0 @@
|
||||
package app.revanced.cli.logging
|
||||
|
||||
internal interface CliLogger {
|
||||
fun error(msg: String)
|
||||
fun info(msg: String)
|
||||
fun trace(msg: String)
|
||||
fun warn(msg: String)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package app.revanced.cli.logging.impl
|
||||
|
||||
import app.revanced.cli.command.MainCommand
|
||||
import app.revanced.cli.logging.CliLogger
|
||||
import java.util.logging.Logger
|
||||
import java.util.logging.SimpleFormatter
|
||||
|
||||
internal class DefaultCliLogger(
|
||||
private val logger: Logger = Logger.getLogger(MainCommand::class.java.name),
|
||||
private val errorLogger: Logger = Logger.getLogger(logger.name + "Err")
|
||||
) : CliLogger {
|
||||
|
||||
init {
|
||||
logger.useParentHandlers = false
|
||||
if (logger.handlers.isEmpty()) {
|
||||
logger.addHandler(FlushingStreamHandler(System.out, SimpleFormatter()))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.setProperty("java.util.logging.SimpleFormatter.format", "%4\$s: %5\$s %n")
|
||||
}
|
||||
}
|
||||
|
||||
override fun error(msg: String) = errorLogger.severe(msg)
|
||||
override fun info(msg: String) = logger.info(msg)
|
||||
override fun trace(msg: String) = logger.finest(msg)
|
||||
override fun warn(msg: String) = errorLogger.warning(msg)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.cli.logging.impl
|
||||
|
||||
import java.io.OutputStream
|
||||
import java.util.logging.Formatter
|
||||
import java.util.logging.LogRecord
|
||||
import java.util.logging.StreamHandler
|
||||
|
||||
internal class FlushingStreamHandler(out: OutputStream, format: Formatter) : StreamHandler(out, format) {
|
||||
override fun publish(record: LogRecord) {
|
||||
super.publish(record)
|
||||
flush()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.cli.patcher.logging.impl
|
||||
|
||||
import app.revanced.cli.logging.impl.DefaultCliLogger
|
||||
import java.util.logging.Logger
|
||||
|
||||
internal object PatcherLogger : app.revanced.patcher.logging.Logger{
|
||||
private val logger = DefaultCliLogger(Logger.getLogger(app.revanced.patcher.Patcher::class.java.name))
|
||||
|
||||
override fun error(msg: String) = logger.error(msg)
|
||||
override fun info(msg: String) = logger.info(msg)
|
||||
override fun warn(msg: String)= logger.warn(msg)
|
||||
override fun trace(msg: String)= logger.trace(msg)
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
package app.revanced.utils
|
||||
|
||||
import app.revanced.cli.command.PatchList
|
||||
import app.revanced.cli.logging.CliLogger
|
||||
import app.revanced.patcher.extensions.PatchExtensions.options
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.NoSuchOptionException
|
||||
import app.revanced.utils.Options.PatchOption.Option
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
internal object Options {
|
||||
private val logger = Logger.getLogger(Options::class.java.name)
|
||||
|
||||
private var mapper = jacksonObjectMapper()
|
||||
|
||||
/**
|
||||
@@ -53,22 +55,24 @@ internal object Options {
|
||||
* Sets the options for the patches in the list.
|
||||
*
|
||||
* @param json The JSON string containing the options.
|
||||
* @param logger The logger to use for logging.
|
||||
*/
|
||||
fun PatchList.setOptions(json: String, logger: CliLogger? = null) {
|
||||
fun PatchList.setOptions(json: String) {
|
||||
filter { it.options?.any() == true }.let { patches ->
|
||||
if (patches.isEmpty()) return
|
||||
|
||||
val patchOptions = deserialize(json)
|
||||
|
||||
patches.forEach { patch ->
|
||||
patches.forEach patch@{ patch ->
|
||||
patchOptions.find { option -> option.patchName == patch.patchName }?.let {
|
||||
it.options.forEach { option ->
|
||||
try {
|
||||
patch.options?.set(option.key, option.value)
|
||||
?: logger?.warn("${patch.patchName} has no options")
|
||||
?: run{
|
||||
logger.warning("${patch.patchName} has no options")
|
||||
return@patch
|
||||
}
|
||||
} catch (e: NoSuchOptionException) {
|
||||
logger?.error(e.message ?: "Unknown error")
|
||||
logger.info(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,10 +84,9 @@ internal object Options {
|
||||
* Sets the options for the patches in the list.
|
||||
*
|
||||
* @param file The file containing the JSON string containing the options.
|
||||
* @param logger The logger to use for logging.
|
||||
* @see setOptions
|
||||
*/
|
||||
fun PatchList.setOptions(file: File, logger: CliLogger? = null) = setOptions(file.readText(), logger)
|
||||
fun PatchList.setOptions(file: File) = setOptions(file.readText())
|
||||
|
||||
/**
|
||||
* Data class for a patch and its [Option]s.
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
package app.revanced.utils.adb
|
||||
|
||||
import app.revanced.cli.logging.CliLogger
|
||||
import app.revanced.utils.adb.AdbManager.Apk
|
||||
import app.revanced.utils.adb.Constants.COMMAND_CREATE_DIR
|
||||
import app.revanced.utils.adb.Constants.COMMAND_DELETE
|
||||
import app.revanced.utils.adb.Constants.COMMAND_INSTALL_MOUNT
|
||||
import app.revanced.utils.adb.Constants.COMMAND_PREPARE_MOUNT_APK
|
||||
import app.revanced.utils.adb.Constants.COMMAND_RESTART
|
||||
import app.revanced.utils.adb.Constants.COMMAND_UMOUNT
|
||||
import app.revanced.utils.adb.Constants.CONTENT_MOUNT_SCRIPT
|
||||
import app.revanced.utils.adb.Constants.PATH_INIT_PUSH
|
||||
import app.revanced.utils.adb.Constants.PATH_INSTALLATION
|
||||
import app.revanced.utils.adb.Constants.PATH_MOUNT
|
||||
import app.revanced.utils.adb.Constants.PATH_PATCHED_APK
|
||||
import app.revanced.utils.adb.Constants.CREATE_DIR
|
||||
import app.revanced.utils.adb.Constants.DELETE
|
||||
import app.revanced.utils.adb.Constants.INSTALLATION_PATH
|
||||
import app.revanced.utils.adb.Constants.INSTALL_MOUNT
|
||||
import app.revanced.utils.adb.Constants.INSTALL_PATCHED_APK
|
||||
import app.revanced.utils.adb.Constants.MOUNT_PATH
|
||||
import app.revanced.utils.adb.Constants.MOUNT_SCRIPT
|
||||
import app.revanced.utils.adb.Constants.PATCHED_APK_PATH
|
||||
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.managers.Package
|
||||
import se.vidstige.jadb.managers.PackageManager
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* Adb manager. Used to install and uninstall [Apk] files.
|
||||
*
|
||||
* @param deviceSerial The serial of the device.
|
||||
*/
|
||||
internal sealed class AdbManager(deviceSerial: String? = null, protected val logger: CliLogger? = null) : Closeable {
|
||||
internal sealed class AdbManager(deviceSerial: String? = null) : Closeable {
|
||||
protected val logger: Logger = Logger.getLogger(AdbManager::class.java.name)
|
||||
|
||||
protected val device = JadbConnection().devices.find { device -> device.serial == deviceSerial }
|
||||
?: throw DeviceNotFoundException(deviceSerial)
|
||||
|
||||
init {
|
||||
logger?.trace("Established connection to $deviceSerial")
|
||||
logger.fine("Established connection to $deviceSerial")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +41,7 @@ internal sealed class AdbManager(deviceSerial: String? = null, protected val log
|
||||
* @param apk The [Apk] file.
|
||||
*/
|
||||
open fun install(apk: Apk) {
|
||||
logger?.info("Finished installing ${apk.file.name}")
|
||||
logger.info("Finished installing ${apk.file.name}")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,51 +50,53 @@ internal sealed class AdbManager(deviceSerial: String? = null, protected val log
|
||||
* @param packageName The package name.
|
||||
*/
|
||||
open fun uninstall(packageName: String) {
|
||||
logger?.info("Finished uninstalling $packageName")
|
||||
logger.info("Finished uninstalling $packageName")
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the [AdbManager] instance.
|
||||
*/
|
||||
override fun close() {
|
||||
logger?.trace("Closed")
|
||||
logger.fine("Closed")
|
||||
}
|
||||
|
||||
class RootAdbManager(deviceSerial: String, logger: CliLogger? = null) : AdbManager(deviceSerial, logger) {
|
||||
class RootAdbManager(deviceSerial: String) : AdbManager(deviceSerial) {
|
||||
init {
|
||||
if (!device.hasSu()) throw IllegalArgumentException("Root required on $deviceSerial. Task failed")
|
||||
}
|
||||
|
||||
override fun install(apk: Apk) {
|
||||
logger?.info("Installing by mounting")
|
||||
logger.info("Installing by mounting")
|
||||
|
||||
val applyReplacement = getPlaceholderReplacement(
|
||||
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(COMMAND_PREPARE_MOUNT_APK.applyReplacement())
|
||||
device.run("$CREATE_DIR $INSTALLATION_PATH")
|
||||
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(COMMAND_UMOUNT.applyReplacement()) // Sanity check.
|
||||
device.run(PATH_MOUNT.applyReplacement())
|
||||
device.run(COMMAND_RESTART.applyReplacement())
|
||||
device.run(INSTALL_MOUNT.applyReplacement())
|
||||
device.run(UMOUNT.applyReplacement()) // Sanity check.
|
||||
device.run(MOUNT_PATH.applyReplacement())
|
||||
device.run(RESTART.applyReplacement())
|
||||
device.run(DELETE.applyReplacement(TMP_PATH).applyReplacement())
|
||||
|
||||
super.install(apk)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
device.run(COMMAND_UMOUNT.applyReplacement(packageName))
|
||||
device.run(COMMAND_DELETE.applyReplacement(PATH_PATCHED_APK).applyReplacement())
|
||||
device.run(COMMAND_DELETE.applyReplacement(PATH_MOUNT).applyReplacement())
|
||||
device.run(UMOUNT.applyReplacement(packageName))
|
||||
device.run(DELETE.applyReplacement(PATCHED_APK_PATH).applyReplacement())
|
||||
device.run(DELETE.applyReplacement(MOUNT_PATH).applyReplacement())
|
||||
device.run(DELETE.applyReplacement(TMP_PATH).applyReplacement())
|
||||
|
||||
super.uninstall(packageName)
|
||||
}
|
||||
@@ -103,7 +107,7 @@ internal sealed class AdbManager(deviceSerial: String? = null, protected val log
|
||||
}
|
||||
}
|
||||
|
||||
class UserAdbManager(deviceSerial: String, logger: CliLogger? = null) : AdbManager(deviceSerial, logger) {
|
||||
class UserAdbManager(deviceSerial: String) : AdbManager(deviceSerial) {
|
||||
private val packageManager = PackageManager(device)
|
||||
|
||||
override fun install(apk: Apk) {
|
||||
@@ -113,7 +117,7 @@ internal sealed class AdbManager(deviceSerial: String? = null, protected val log
|
||||
}
|
||||
|
||||
override fun uninstall(packageName: String) {
|
||||
logger?.info("Uninstalling $packageName")
|
||||
logger.info("Uninstalling $packageName")
|
||||
|
||||
packageManager.uninstall(Package(packageName))
|
||||
|
||||
@@ -125,6 +129,7 @@ internal sealed class AdbManager(deviceSerial: String? = null, protected val log
|
||||
* Apk file for [AdbManager].
|
||||
*
|
||||
* @param file The [Apk] file.
|
||||
* @param packageName The package name of the [Apk] file.
|
||||
*/
|
||||
internal class Apk(val file: File, val packageName: String? = null)
|
||||
|
||||
|
||||
@@ -2,24 +2,28 @@ package app.revanced.utils.adb
|
||||
|
||||
import se.vidstige.jadb.JadbDevice
|
||||
import se.vidstige.jadb.RemoteFile
|
||||
import se.vidstige.jadb.ShellProcessBuilder
|
||||
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)) {
|
||||
Executors.newFixedThreadPool(2).let { service ->
|
||||
arrayOf(inputStream, errorStream).map { stream ->
|
||||
Callable { stream.bufferedReader().use { it.readLine() } }
|
||||
}.let { tasks -> service.invokeAny(tasks).also { service.shutdown() } }
|
||||
}
|
||||
|
||||
internal fun JadbDevice.buildCommand(command: String, su: Boolean = true): ShellProcessBuilder {
|
||||
if (su) return shellProcessBuilder("su -c \'$command\'")
|
||||
|
||||
val args = command.split(" ") as ArrayList<String>
|
||||
val cmd = args.removeFirst()
|
||||
|
||||
return shellProcessBuilder(cmd, *args.toTypedArray())
|
||||
}
|
||||
|
||||
internal fun JadbDevice.run(command: String, su: Boolean = true): Int {
|
||||
return this.buildCommand(command, su).start().waitFor()
|
||||
}
|
||||
|
||||
internal fun JadbDevice.hasSu() =
|
||||
this.startCommand("su -h", false).waitFor() == 0
|
||||
|
||||
internal fun JadbDevice.copyFile(file: File, targetFile: String) =
|
||||
push(file, RemoteFile(targetFile))
|
||||
internal fun JadbDevice.push(file: File, targetFilePath: String) =
|
||||
push(file, RemoteFile(targetFilePath))
|
||||
|
||||
internal fun JadbDevice.createFile(targetFile: String, content: String) =
|
||||
push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile))
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
package app.revanced.utils.adb
|
||||
|
||||
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 PATH_INSTALLATION = "/data/adb/revanced/"
|
||||
internal const val PATH_PATCHED_APK = "$PATH_INSTALLATION$PLACEHOLDER.apk"
|
||||
internal const val PATH_MOUNT = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
|
||||
internal const val TMP_PATH = "/data/local/tmp/revanced.tmp"
|
||||
internal const val INSTALLATION_PATH = "/data/adb/revanced/"
|
||||
internal const val PATCHED_APK_PATH = "$INSTALLATION_PATH$PLACEHOLDER.apk"
|
||||
internal const val MOUNT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
|
||||
|
||||
internal const val COMMAND_DELETE = "rm -rf $PLACEHOLDER"
|
||||
internal const val COMMAND_CREATE_DIR = "mkdir -p"
|
||||
internal const val COMMAND_RESTART = "pm resolve-activity --brief $PLACEHOLDER | tail -n 1 | " +
|
||||
internal const val DELETE = "rm -rf $PLACEHOLDER"
|
||||
internal const val CREATE_DIR = "mkdir -p"
|
||||
internal const val RESTART = "pm resolve-activity --brief $PLACEHOLDER | tail -n 1 | " +
|
||||
"xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)"
|
||||
|
||||
internal const val COMMAND_PREPARE_MOUNT_APK = "base_path=\"$PATH_PATCHED_APK\" && " +
|
||||
"mv $PATH_INIT_PUSH ${'$'}base_path && " +
|
||||
internal const val INSTALL_PATCHED_APK = "base_path=\"$PATCHED_APK_PATH\" && " +
|
||||
"mv $TMP_PATH ${'$'}base_path && " +
|
||||
"chmod 644 ${'$'}base_path && " +
|
||||
"chown system:system ${'$'}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"
|
||||
|
||||
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
|
||||
MAGISKTMP="${'$'}(magisk --path)" || MAGISKTMP=/sbin
|
||||
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
|
||||
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' )
|
||||
|
||||
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
|
||||
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
|
||||
"""
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package app.revanced.utils.signing
|
||||
|
||||
import app.revanced.cli.command.logger
|
||||
import com.android.apksig.ApkSigner
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
@@ -16,10 +15,13 @@ import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.logging.Logger
|
||||
|
||||
internal class ApkSigner(
|
||||
private val signingOptions: SigningOptions
|
||||
) {
|
||||
private val logger = Logger.getLogger(ApkSigner::class.java.name)
|
||||
|
||||
private val signer: ApkSigner.Builder
|
||||
private val passwordCharArray = signingOptions.password.toCharArray()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user