Compare commits

..

14 Commits

Author SHA1 Message Date
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
16 changed files with 258 additions and 203 deletions

View File

@@ -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 --no-daemon
# 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

View File

@@ -1,3 +1,28 @@
# [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)

View File

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

View File

@@ -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`.

View File

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

View File

@@ -19,38 +19,33 @@ 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 {
@@ -58,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 {

View File

@@ -1,5 +1,6 @@
package app.revanced.cli.command
import app.revanced.cli.command.utility.UtilityCommand
import app.revanced.patcher.patch.PatchClass
import picocli.CommandLine
import picocli.CommandLine.Command
@@ -17,12 +18,11 @@ fun main(args: Array<String>) {
}
object : Handler() {
override fun publish(record: LogRecord) = formatter.format(record).let {
if (record.level.intValue() > Level.INFO.intValue()) {
System.err.write(it.toByteArray())
} else {
System.out.write(it.toByteArray())
}
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() {
@@ -42,7 +42,7 @@ fun main(args: Array<String>) {
internal typealias PatchList = List<PatchClass>
object CLIVersionProvider : IVersionProvider {
private object CLIVersionProvider : IVersionProvider {
override fun getVersion(): Array<String> {
Properties().apply {
load(MainCommand::class.java.getResourceAsStream("/app/revanced/cli/version.properties"))
@@ -60,8 +60,8 @@ object CLIVersionProvider : IVersionProvider {
subcommands = [
ListPatchesCommand::class,
PatchCommand::class,
UninstallCommand::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)
@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)
override fun run() = if (!path.exists() || overwrite) with(PatchBundleLoader.Jar(*patchBundles)) {
if (update) setOptions(path)
Options.serialize(this, prettyPrint = true)
.let(path::writeText)
}
Options.serialize(this, prettyPrint = true).let(path::writeText)
}
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.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"],
showDefaultValue = ALWAYS
)
var experimental: Boolean = false
private var experimental: 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"],
@@ -108,39 +95,36 @@ 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
@@ -192,7 +176,10 @@ internal object PatchCommand: Runnable {
runBlocking {
apply(false).collect { patchResult ->
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")
}
}
@@ -206,8 +193,7 @@ internal object PatchCommand: Runnable {
val alignAndSignedFile = sign(
apk.newAlignedFile(
result,
resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_aligned.apk")
result, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_aligned.apk")
)
)
@@ -287,19 +273,16 @@ internal object PatchCommand: Runnable {
it.isEmpty() || it.any { version -> version == packageVersion }
}
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(", ")}"
}
)
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.fine(
"${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.fine("$formattedPatchName: No constraint on packages.")
@@ -341,8 +324,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")
@@ -351,23 +333,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
)
}
@@ -380,32 +359,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)
}
}

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 picocli.CommandLine.*
@@ -8,33 +8,26 @@ import java.util.logging.Logger
@Command(
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 {
private val logger = Logger.getLogger(UninstallCommand::class.java.name)
@Parameters(
description = ["ADB device serials"],
arity = "1..*"
)
lateinit var deviceSerials: Array<String>
@Parameters(description = ["ADB device serials"], arity = "1..*")
private lateinit var deviceSerials: Array<String>
@Option(
names = ["-p", "--package-name"],
description = ["Package name to uninstall"],
required = true
)
lateinit var packageName: 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 package"],
description = ["Uninstall by unmounting the patched APK file"],
showDefaultValue = ALWAYS
)
var unmount: Boolean = false
private var unmount: Boolean = false
override fun run() = try {
deviceSerials.forEach {deviceSerial ->
deviceSerials.forEach { deviceSerial ->
if (unmount) {
AdbManager.RootAdbManager(deviceSerial)
} 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)
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.warning("${patch.patchName} has no options")
?: run{
logger.warning("${patch.patchName} has no options")
return@patch
}
} catch (e: NoSuchOptionException) {
logger.info(e.message ?: "Unknown error")
logger.info(e.toString())
}
}
}

View File

@@ -1,18 +1,18 @@
package app.revanced.utils.adb
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
@@ -72,29 +72,31 @@ internal sealed class AdbManager(deviceSerial: String? = null) : Closeable {
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)
}
@@ -127,6 +129,7 @@ internal sealed class AdbManager(deviceSerial: String? = null) : Closeable {
* 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)

View File

@@ -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))

View File

@@ -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()
}