mirror of
https://github.com/revanced/revanced-cli.git
synced 2024-12-11 20:57:49 +01:00
feat: Add ReVanced Library subproject (#265)
This commit is contained in:
commit
157278c9ba
@ -31,7 +31,7 @@
|
|||||||
{
|
{
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
"path": "build/libs/*all.jar"
|
"path": "revanced-cli/build/libs/*all.jar"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
successComment: false
|
successComment: false
|
||||||
|
@ -1,57 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.8.20"
|
kotlin("jvm") version "1.9.0" apply false
|
||||||
alias(libs.plugins.shadow)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "app.revanced"
|
allprojects {
|
||||||
|
group = "app.revanced"
|
||||||
dependencies {
|
}
|
||||||
implementation(libs.revanced.patcher)
|
|
||||||
implementation(libs.kotlin.reflect)
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
|
||||||
implementation(libs.picocli)
|
|
||||||
implementation(libs.jadb) // Updated fork
|
|
||||||
implementation(libs.apksig)
|
|
||||||
implementation(libs.bcpkix.jdk15on)
|
|
||||||
implementation(libs.jackson.module.kotlin)
|
|
||||||
testImplementation(libs.kotlin.test)
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin { jvmToolchain(11) }
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
testLogging {
|
|
||||||
events("PASSED", "SKIPPED", "FAILED")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processResources {
|
|
||||||
expand("projectVersion" to project.version)
|
|
||||||
}
|
|
||||||
|
|
||||||
shadowJar {
|
|
||||||
manifest {
|
|
||||||
attributes("Main-Class" to "app.revanced.cli.command.MainCommandKt")
|
|
||||||
}
|
|
||||||
minimize {
|
|
||||||
exclude(dependency("org.jetbrains.kotlin:.*"))
|
|
||||||
exclude(dependency("org.bouncycastle:.*"))
|
|
||||||
exclude(dependency("app.revanced:.*"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build {
|
|
||||||
dependsOn(shadowJar)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dummy task to fix the Gradle semantic-release plugin.
|
|
||||||
// Remove this if you forked it to support building only.
|
|
||||||
// Tracking issue: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
|
||||||
register<DefaultTask>("publish") {
|
|
||||||
group = "publish"
|
|
||||||
description = "Dummy task"
|
|
||||||
dependsOn(build)
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,9 +6,10 @@ jackson-module-kotlin = "2.14.3"
|
|||||||
jadb = "2531a28109"
|
jadb = "2531a28109"
|
||||||
kotlin-reflect = "1.9.0"
|
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.3"
|
||||||
picocli = "4.7.3"
|
picocli = "4.7.3"
|
||||||
revanced-patcher = "15.0.0-dev.2"
|
revanced-patcher = "15.0.0"
|
||||||
|
binary-compatibility-validator = "0.13.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
||||||
@ -23,3 +24,4 @@ revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "re
|
|||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
||||||
|
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
||||||
|
52
revanced-cli/build.gradle.kts
Normal file
52
revanced-cli/build.gradle.kts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm") version "1.9.0"
|
||||||
|
alias(libs.plugins.shadow)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":revanced-lib"))
|
||||||
|
implementation(libs.revanced.patcher)
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
implementation(libs.picocli)
|
||||||
|
|
||||||
|
testImplementation(libs.kotlin.test)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin { jvmToolchain(11) }
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
testLogging {
|
||||||
|
events("PASSED", "SKIPPED", "FAILED")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
expand("projectVersion" to project.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
manifest {
|
||||||
|
attributes("Main-Class" to "app.revanced.cli.command.MainCommandKt")
|
||||||
|
}
|
||||||
|
minimize {
|
||||||
|
exclude(dependency("org.jetbrains.kotlin:.*"))
|
||||||
|
exclude(dependency("org.bouncycastle:.*"))
|
||||||
|
exclude(dependency("app.revanced:.*"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
dependsOn(shadowJar)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dummy task to fix the Gradle semantic-release plugin.
|
||||||
|
// Remove this if you forked it to support building only.
|
||||||
|
// Tracking issue: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
||||||
|
register<DefaultTask>("publish") {
|
||||||
|
group = "publish"
|
||||||
|
description = "Dummy task"
|
||||||
|
dependsOn(build)
|
||||||
|
}
|
||||||
|
}
|
1
revanced-cli/settings.gradle.kts
Normal file
1
revanced-cli/settings.gradle.kts
Normal file
@ -0,0 +1 @@
|
|||||||
|
rootProject.name = "revanced-cli"
|
@ -0,0 +1,39 @@
|
|||||||
|
package app.revanced.cli.command
|
||||||
|
|
||||||
|
import app.revanced.cli.command.utility.UtilityCommand
|
||||||
|
import app.revanced.lib.logging.Logger
|
||||||
|
import picocli.CommandLine
|
||||||
|
import picocli.CommandLine.Command
|
||||||
|
import picocli.CommandLine.IVersionProvider
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
Logger.setDefault()
|
||||||
|
CommandLine(MainCommand).execute(*args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private object CLIVersionProvider : IVersionProvider {
|
||||||
|
override fun getVersion() = arrayOf(
|
||||||
|
MainCommand::class.java.getResourceAsStream(
|
||||||
|
"/app/revanced/cli/version.properties"
|
||||||
|
)?.use { stream ->
|
||||||
|
Properties().apply { load(stream) }.let {
|
||||||
|
"ReVanced CLI v${it.getProperty("version")}"
|
||||||
|
}
|
||||||
|
} ?: "ReVanced CLI")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = "revanced-cli",
|
||||||
|
description = ["Command line application to use ReVanced"],
|
||||||
|
mixinStandardHelpOptions = true,
|
||||||
|
versionProvider = CLIVersionProvider::class,
|
||||||
|
subcommands = [
|
||||||
|
ListPatchesCommand::class,
|
||||||
|
PatchCommand::class,
|
||||||
|
OptionsCommand::class,
|
||||||
|
UtilityCommand::class,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
private object MainCommand
|
@ -1,8 +1,8 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
|
import app.revanced.lib.Options
|
||||||
|
import app.revanced.lib.Options.setOptions
|
||||||
import app.revanced.patcher.PatchBundleLoader
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
import app.revanced.utils.Options
|
|
||||||
import app.revanced.utils.Options.setOptions
|
|
||||||
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
|
||||||
@ -37,10 +37,17 @@ internal object OptionsCommand : Runnable {
|
|||||||
)
|
)
|
||||||
private var update: Boolean = false
|
private var update: Boolean = false
|
||||||
|
|
||||||
override fun run() = if (!filePath.exists() || overwrite) with(PatchBundleLoader.Jar(*patchBundles)) {
|
override fun run() = try {
|
||||||
if (update && filePath.exists()) setOptions(filePath)
|
PatchBundleLoader.Jar(*patchBundles).let { patches ->
|
||||||
|
if (!filePath.exists() || overwrite) {
|
||||||
|
if (update && filePath.exists()) patches.setOptions(filePath)
|
||||||
|
|
||||||
Options.serialize(this, prettyPrint = true).let(filePath::writeText)
|
Options.serialize(patches, prettyPrint = true).let(filePath::writeText)
|
||||||
|
} else throw OptionsFileAlreadyExistsException()
|
||||||
|
}
|
||||||
|
} catch (ex: OptionsFileAlreadyExistsException) {
|
||||||
|
logger.severe("Options file already exists, use --overwrite to override it")
|
||||||
}
|
}
|
||||||
else logger.severe("Options file already exists, use --overwrite to override it")
|
|
||||||
|
class OptionsFileAlreadyExistsException : Exception()
|
||||||
}
|
}
|
@ -1,17 +1,19 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
import app.revanced.patcher.*
|
import app.revanced.lib.ApkUtils
|
||||||
import app.revanced.utils.Options
|
import app.revanced.lib.Options
|
||||||
import app.revanced.utils.Options.setOptions
|
import app.revanced.lib.Options.setOptions
|
||||||
import app.revanced.utils.adb.AdbManager
|
import app.revanced.lib.adb.AdbManager
|
||||||
import app.revanced.utils.align.ZipAligner
|
import app.revanced.lib.signing.SigningOptions
|
||||||
import app.revanced.utils.align.zip.ZipFile
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
import app.revanced.utils.align.zip.structures.ZipEntry
|
import app.revanced.patcher.PatchSet
|
||||||
import app.revanced.utils.signing.ApkSigner
|
import app.revanced.patcher.Patcher
|
||||||
import app.revanced.utils.signing.SigningOptions
|
import app.revanced.patcher.PatcherOptions
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||||
|
import picocli.CommandLine.Model.CommandSpec
|
||||||
|
import picocli.CommandLine.Spec
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
@ -24,21 +26,15 @@ import java.util.logging.Logger
|
|||||||
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(
|
@Spec
|
||||||
description = ["APK file to be patched"], arity = "1..1"
|
lateinit var spec: CommandSpec // injected by picocli
|
||||||
)
|
|
||||||
private lateinit var apk: File
|
private lateinit var apk: File
|
||||||
|
|
||||||
@CommandLine.Option(
|
|
||||||
names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches"], required = true
|
|
||||||
)
|
|
||||||
private var patchBundles = emptyList<File>()
|
|
||||||
|
|
||||||
@CommandLine.Option(
|
|
||||||
names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"]
|
|
||||||
)
|
|
||||||
private var integrations = listOf<File>()
|
private var integrations = listOf<File>()
|
||||||
|
|
||||||
|
private var patchBundles = emptyList<File>()
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-i", "--include"], description = ["List of patches to include"]
|
names = ["-i", "--include"], description = ["List of patches to include"]
|
||||||
)
|
)
|
||||||
@ -94,7 +90,7 @@ internal object PatchCommand : Runnable {
|
|||||||
@CommandLine.Option(
|
@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"]
|
||||||
)
|
)
|
||||||
private var keystorePath: String? = null
|
private var keystoreFilePath: File? = null
|
||||||
|
|
||||||
@CommandLine.Option(
|
@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"]
|
||||||
@ -108,10 +104,7 @@ internal object PatchCommand : Runnable {
|
|||||||
)
|
)
|
||||||
private var resourceCachePath = File("revanced-resource-cache")
|
private var resourceCachePath = File("revanced-resource-cache")
|
||||||
|
|
||||||
@CommandLine.Option(
|
private var aaptBinaryPath: File? = null
|
||||||
names = ["--custom-aapt2-binary"], description = ["Path to a custom AAPT binary to compile resources with"]
|
|
||||||
)
|
|
||||||
private var aaptBinaryPath = File("")
|
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-p", "--purge"],
|
names = ["-p", "--purge"],
|
||||||
@ -120,36 +113,60 @@ internal object PatchCommand : Runnable {
|
|||||||
)
|
)
|
||||||
private var purge: Boolean = false
|
private var purge: Boolean = false
|
||||||
|
|
||||||
|
@CommandLine.Parameters(
|
||||||
|
description = ["APK file to be patched"], arity = "1..1"
|
||||||
|
)
|
||||||
|
@Suppress("unused")
|
||||||
|
private fun setApk(apk: File) {
|
||||||
|
if (!apk.exists()) throw CommandLine.ParameterException(
|
||||||
|
spec.commandLine(),
|
||||||
|
"APK file ${apk.name} does not exist"
|
||||||
|
)
|
||||||
|
this.apk = apk
|
||||||
|
}
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"]
|
||||||
|
)
|
||||||
|
@Suppress("unused")
|
||||||
|
private fun setIntegrations(integrations: Array<File>) {
|
||||||
|
integrations.firstOrNull { !it.exists() }?.let {
|
||||||
|
throw CommandLine.ParameterException(spec.commandLine(), "Integrations file ${it.name} does not exist")
|
||||||
|
}
|
||||||
|
this.integrations += integrations
|
||||||
|
}
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches"], required = true
|
||||||
|
)
|
||||||
|
@Suppress("unused")
|
||||||
|
private fun setPatchBundles(patchBundles: Array<File>) {
|
||||||
|
patchBundles.firstOrNull { !it.exists() }?.let {
|
||||||
|
throw CommandLine.ParameterException(spec.commandLine(), "Patch bundle ${it.name} does not exist")
|
||||||
|
}
|
||||||
|
this.patchBundles = patchBundles.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["--custom-aapt2-binary"], description = ["Path to a custom AAPT binary to compile resources with"]
|
||||||
|
)
|
||||||
|
@Suppress("unused")
|
||||||
|
private fun setAaptBinaryPath(aaptBinaryPath: File) {
|
||||||
|
if (!aaptBinaryPath.exists()) throw CommandLine.ParameterException(
|
||||||
|
spec.commandLine(),
|
||||||
|
"AAPT binary ${aaptBinaryPath.name} does not exist"
|
||||||
|
)
|
||||||
|
this.aaptBinaryPath = aaptBinaryPath
|
||||||
|
}
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
// region Prepare
|
val adbManager = deviceSerial?.let { serial -> AdbManager.getAdbManager(serial, mount) }
|
||||||
|
|
||||||
if (!apk.exists()) {
|
|
||||||
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)
|
|
||||||
else AdbManager.UserAdbManager(serial)
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Load patches
|
// region Load patches
|
||||||
|
|
||||||
logger.info("Loading patches")
|
logger.info("Loading patches")
|
||||||
|
|
||||||
val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray())
|
val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray())
|
||||||
val integrations = integrations
|
|
||||||
|
|
||||||
logger.info("Setting patch options")
|
logger.info("Setting patch options")
|
||||||
|
|
||||||
@ -160,57 +177,63 @@ internal object PatchCommand : Runnable {
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Patch
|
Patcher(
|
||||||
|
|
||||||
val patcher = Patcher(
|
|
||||||
PatcherOptions(
|
PatcherOptions(
|
||||||
apk,
|
apk,
|
||||||
resourceCachePath,
|
resourceCachePath,
|
||||||
aaptBinaryPath.path,
|
aaptBinaryPath?.path,
|
||||||
resourceCachePath.absolutePath,
|
resourceCachePath.absolutePath,
|
||||||
)
|
)
|
||||||
)
|
).use { patcher ->
|
||||||
|
// region Patch
|
||||||
|
|
||||||
val result = patcher.apply {
|
val patcherResult = patcher.apply {
|
||||||
acceptIntegrations(integrations)
|
acceptIntegrations(integrations)
|
||||||
acceptPatches(filterPatchSelection(patches))
|
acceptPatches(filterPatchSelection(patches))
|
||||||
|
|
||||||
// Execute patches.
|
// Execute patches.
|
||||||
runBlocking {
|
runBlocking {
|
||||||
apply(false).collect { patchResult ->
|
apply(false).collect { patchResult ->
|
||||||
patchResult.exception?.let {
|
patchResult.exception?.let {
|
||||||
StringWriter().use { writer ->
|
StringWriter().use { writer ->
|
||||||
it.printStackTrace(PrintWriter(writer))
|
it.printStackTrace(PrintWriter(writer))
|
||||||
logger.severe("${patchResult.patch.name} failed:\n$writer")
|
logger.severe("${patchResult.patch.name} failed:\n$writer")
|
||||||
}
|
}
|
||||||
} ?: logger.info("${patchResult.patch.name} succeeded")
|
} ?: logger.info("${patchResult.patch.name} succeeded")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}.get()
|
||||||
}.get()
|
|
||||||
|
|
||||||
patcher.close()
|
// endregion
|
||||||
|
|
||||||
// endregion
|
// region Save
|
||||||
|
|
||||||
// region Finish
|
val tempFile = resourceCachePath.resolve(apk.name)
|
||||||
|
ApkUtils.copyAligned(apk, tempFile, patcherResult)
|
||||||
val alignAndSignedFile = sign(
|
if (!mount) ApkUtils.sign(
|
||||||
apk.newAlignedFile(
|
tempFile,
|
||||||
result, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_aligned.apk")
|
outputFilePath,
|
||||||
|
SigningOptions(
|
||||||
|
commonName,
|
||||||
|
password,
|
||||||
|
keystoreFilePath ?: outputFilePath.absoluteFile.parentFile
|
||||||
|
.resolve("${outputFilePath.nameWithoutExtension}.keystore"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("Copying to ${outputFilePath.name}")
|
// endregion
|
||||||
alignAndSignedFile.copyTo(outputFilePath, overwrite = true)
|
|
||||||
|
|
||||||
adbManager?.install(AdbManager.Apk(outputFilePath, patcher.context.packageMetadata.packageName))
|
// region Install
|
||||||
|
|
||||||
|
adbManager?.install(AdbManager.Apk(outputFilePath, patcher.context.packageMetadata.packageName))
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
|
||||||
if (purge) {
|
if (purge) {
|
||||||
logger.info("Purging temporary files")
|
logger.info("Purging temporary files")
|
||||||
purge(resourceCachePath)
|
purge(resourceCachePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -279,64 +302,6 @@ internal object PatchCommand : Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new aligned APK file.
|
|
||||||
*
|
|
||||||
* @param result The result of the patching process.
|
|
||||||
* @param outputFile The file to save the aligned APK to.
|
|
||||||
*/
|
|
||||||
private fun File.newAlignedFile(
|
|
||||||
result: PatcherResult, outputFile: File
|
|
||||||
): File {
|
|
||||||
logger.info("Aligning $name")
|
|
||||||
|
|
||||||
if (outputFile.exists()) outputFile.delete()
|
|
||||||
|
|
||||||
ZipFile(outputFile).use { file ->
|
|
||||||
result.dexFiles.forEach {
|
|
||||||
file.addEntryCompressData(
|
|
||||||
ZipEntry.createWithName(it.name), it.stream.readBytes()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.resourceFile?.let {
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(it), ZipAligner::getEntryAlignment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Do not compress result.doNotCompress
|
|
||||||
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(this), ZipAligner::getEntryAlignment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputFile
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign the APK file.
|
|
||||||
*
|
|
||||||
* @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
|
|
||||||
else {
|
|
||||||
logger.info("Signing ${inputFile.name}")
|
|
||||||
|
|
||||||
val keyStoreFilePath = keystorePath
|
|
||||||
?: outputFilePath.absoluteFile.parentFile.resolve("${outputFilePath.nameWithoutExtension}.keystore").canonicalPath
|
|
||||||
|
|
||||||
val options = SigningOptions(
|
|
||||||
commonName, password, keyStoreFilePath
|
|
||||||
)
|
|
||||||
|
|
||||||
ApkSigner(options).signApk(
|
|
||||||
inputFile, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_signed.apk")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun purge(resourceCachePath: File) {
|
private fun purge(resourceCachePath: File) {
|
||||||
val result = if (resourceCachePath.deleteRecursively()) "Purged resource cache directory"
|
val result = if (resourceCachePath.deleteRecursively()) "Purged resource cache directory"
|
||||||
else "Failed to purge resource cache directory"
|
else "Failed to purge resource cache directory"
|
@ -1,6 +1,6 @@
|
|||||||
package app.revanced.cli.command.utility
|
package app.revanced.cli.command.utility
|
||||||
|
|
||||||
import app.revanced.utils.adb.AdbManager
|
import app.revanced.lib.adb.AdbManager
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
@ -28,15 +28,11 @@ internal object InstallCommand : Runnable {
|
|||||||
)
|
)
|
||||||
private var packageName: String? = null
|
private var packageName: String? = null
|
||||||
|
|
||||||
override fun run() = try {
|
override fun run() = deviceSerials.forEach { deviceSerial ->
|
||||||
deviceSerials.forEach { deviceSerial ->
|
try {
|
||||||
if (packageName != null) {
|
AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName))
|
||||||
AdbManager.RootAdbManager(deviceSerial)
|
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||||
} else {
|
logger.severe(e.toString())
|
||||||
AdbManager.UserAdbManager(deviceSerial)
|
|
||||||
}.install(AdbManager.Apk(apk, packageName))
|
|
||||||
}
|
}
|
||||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
|
||||||
logger.severe(e.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package app.revanced.cli.command.utility
|
package app.revanced.cli.command.utility
|
||||||
|
|
||||||
import app.revanced.utils.adb.AdbManager
|
import app.revanced.lib.adb.AdbManager
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
@ -26,15 +26,11 @@ internal object UninstallCommand : Runnable {
|
|||||||
)
|
)
|
||||||
private var unmount: Boolean = false
|
private var unmount: Boolean = false
|
||||||
|
|
||||||
override fun run() = try {
|
override fun run() = deviceSerials.forEach { deviceSerial ->
|
||||||
deviceSerials.forEach { deviceSerial ->
|
try {
|
||||||
if (unmount) {
|
AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName)
|
||||||
AdbManager.RootAdbManager(deviceSerial)
|
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||||
} else {
|
logger.severe(e.toString())
|
||||||
AdbManager.UserAdbManager(deviceSerial)
|
|
||||||
}.uninstall(packageName)
|
|
||||||
}
|
}
|
||||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
|
||||||
logger.severe(e.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
118
revanced-lib/api/revanced-lib.api
Normal file
118
revanced-lib/api/revanced-lib.api
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
public final class app/revanced/lib/ApkUtils {
|
||||||
|
public static final field INSTANCE Lapp/revanced/lib/ApkUtils;
|
||||||
|
public final fun copyAligned (Ljava/io/File;Ljava/io/File;Lapp/revanced/patcher/PatcherResult;)V
|
||||||
|
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/lib/signing/SigningOptions;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/Options {
|
||||||
|
public static final field INSTANCE Lapp/revanced/lib/Options;
|
||||||
|
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/lib/Options$Patch;
|
||||||
|
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
|
||||||
|
public static synthetic fun serialize$default (Lapp/revanced/lib/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
|
||||||
|
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
|
||||||
|
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/Options$Patch {
|
||||||
|
public final fun getOptions ()Ljava/util/List;
|
||||||
|
public final fun getPatchName ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/Options$Patch$Option {
|
||||||
|
public final fun getKey ()Ljava/lang/String;
|
||||||
|
public final fun getValue ()Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/lib/adb/AdbManager {
|
||||||
|
public static final field Companion Lapp/revanced/lib/adb/AdbManager$Companion;
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
protected final fun getDevice ()Lse/vidstige/jadb/JadbDevice;
|
||||||
|
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||||
|
public fun install (Lapp/revanced/lib/adb/AdbManager$Apk;)V
|
||||||
|
public fun uninstall (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/adb/AdbManager$Apk {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getFile ()Ljava/io/File;
|
||||||
|
public final fun getPackageName ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/adb/AdbManager$Companion {
|
||||||
|
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/lib/adb/AdbManager;
|
||||||
|
public static synthetic fun getAdbManager$default (Lapp/revanced/lib/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/lib/adb/AdbManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/adb/AdbManager$RootAdbManager : app/revanced/lib/adb/AdbManager {
|
||||||
|
public static final field Utils Lapp/revanced/lib/adb/AdbManager$RootAdbManager$Utils;
|
||||||
|
public fun install (Lapp/revanced/lib/adb/AdbManager$Apk;)V
|
||||||
|
public fun uninstall (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/adb/AdbManager$RootAdbManager$Utils {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/adb/AdbManager$UserAdbManager : app/revanced/lib/adb/AdbManager {
|
||||||
|
public fun install (Lapp/revanced/lib/adb/AdbManager$Apk;)V
|
||||||
|
public fun uninstall (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/logging/Logger {
|
||||||
|
public static final field INSTANCE Lapp/revanced/lib/logging/Logger;
|
||||||
|
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
|
||||||
|
public final fun removeAllHandlers ()V
|
||||||
|
public final fun setDefault ()V
|
||||||
|
public final fun setFormat (Ljava/lang/String;)V
|
||||||
|
public static synthetic fun setFormat$default (Lapp/revanced/lib/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/signing/ApkSigner {
|
||||||
|
public fun <init> (Lapp/revanced/lib/signing/SigningOptions;)V
|
||||||
|
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/signing/SigningOptions {
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)V
|
||||||
|
public final fun component1 ()Ljava/lang/String;
|
||||||
|
public final fun component2 ()Ljava/lang/String;
|
||||||
|
public final fun component3 ()Ljava/io/File;
|
||||||
|
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Lapp/revanced/lib/signing/SigningOptions;
|
||||||
|
public static synthetic fun copy$default (Lapp/revanced/lib/signing/SigningOptions;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;ILjava/lang/Object;)Lapp/revanced/lib/signing/SigningOptions;
|
||||||
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
|
public final fun getCommonName ()Ljava/lang/String;
|
||||||
|
public final fun getKeyStoreOutputFilePath ()Ljava/io/File;
|
||||||
|
public final fun getPassword ()Ljava/lang/String;
|
||||||
|
public fun hashCode ()I
|
||||||
|
public fun toString ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/zip/ZipFile : java/io/Closeable {
|
||||||
|
public static final field ApkZipFile Lapp/revanced/lib/zip/ZipFile$ApkZipFile;
|
||||||
|
public fun <init> (Ljava/io/File;)V
|
||||||
|
public final fun addEntryCompressData (Lapp/revanced/lib/zip/structures/ZipEntry;[B)V
|
||||||
|
public fun close ()V
|
||||||
|
public final fun copyEntriesFromFileAligned (Lapp/revanced/lib/zip/ZipFile;Lkotlin/jvm/functions/Function1;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/zip/ZipFile$ApkZipFile {
|
||||||
|
public final fun getApkZipEntryAlignment ()Lkotlin/jvm/functions/Function1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/zip/structures/ZipEntry {
|
||||||
|
public static final field Companion Lapp/revanced/lib/zip/structures/ZipEntry$Companion;
|
||||||
|
public fun <init> (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/lib/zip/structures/ZipEntry$Companion {
|
||||||
|
}
|
||||||
|
|
78
revanced-lib/build.gradle.kts
Normal file
78
revanced-lib/build.gradle.kts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm") version "1.9.0"
|
||||||
|
alias(libs.plugins.binary.compatibility.validator)
|
||||||
|
`maven-publish`
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.revanced.patcher)
|
||||||
|
implementation(libs.kotlin.reflect)
|
||||||
|
implementation(libs.jadb) // Updated fork
|
||||||
|
implementation(libs.apksig)
|
||||||
|
implementation(libs.bcpkix.jdk15on)
|
||||||
|
implementation(libs.jackson.module.kotlin)
|
||||||
|
|
||||||
|
testImplementation(libs.revanced.patcher)
|
||||||
|
testImplementation(libs.kotlin.test)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
testLogging {
|
||||||
|
events("PASSED", "SKIPPED", "FAILED")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin { jvmToolchain(11) }
|
||||||
|
|
||||||
|
java {
|
||||||
|
withSourcesJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
maven {
|
||||||
|
name = "GitHubPackages"
|
||||||
|
url = uri("https://maven.pkg.github.com/revanced/revanced-cli")
|
||||||
|
credentials {
|
||||||
|
username = System.getenv("GITHUB_ACTOR")
|
||||||
|
password = System.getenv("GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("gpr") {
|
||||||
|
from(components["java"])
|
||||||
|
|
||||||
|
version = project.version.toString()
|
||||||
|
|
||||||
|
pom {
|
||||||
|
name = "ReVanced Library"
|
||||||
|
description = "Library containing common utilities for ReVanced"
|
||||||
|
url = "https://revanced.app"
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = "GNU General Public License v3.0"
|
||||||
|
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = "ReVanced"
|
||||||
|
name = "ReVanced"
|
||||||
|
email = "contact@revanced.app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
connection = "scm:git:git://github.com/revanced/revanced-cli.git"
|
||||||
|
developerConnection = "scm:git:git@github.com:revanced/revanced-cli.git"
|
||||||
|
url = "https://github.com/revanced/revanced-cli"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
revanced-lib/settings.gradle.kts
Normal file
1
revanced-lib/settings.gradle.kts
Normal file
@ -0,0 +1 @@
|
|||||||
|
rootProject.name = "revanced-lib"
|
67
revanced-lib/src/main/kotlin/app/revanced/lib/ApkUtils.kt
Normal file
67
revanced-lib/src/main/kotlin/app/revanced/lib/ApkUtils.kt
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package app.revanced.lib
|
||||||
|
|
||||||
|
import app.revanced.lib.signing.ApkSigner
|
||||||
|
import app.revanced.lib.signing.SigningOptions
|
||||||
|
import app.revanced.lib.zip.ZipFile
|
||||||
|
import app.revanced.lib.zip.structures.ZipEntry
|
||||||
|
import app.revanced.patcher.PatcherResult
|
||||||
|
import java.io.File
|
||||||
|
import java.util.logging.Logger
|
||||||
|
import kotlin.io.path.deleteIfExists
|
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||||
|
object ApkUtils {
|
||||||
|
private val logger = Logger.getLogger(ApkUtils::class.java.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new apk from [apkFile] and [patchedEntriesSource] and writes it to [outputFile].
|
||||||
|
*
|
||||||
|
* @param apkFile The apk to copy entries from.
|
||||||
|
* @param outputFile The apk to write the new entries to.
|
||||||
|
* @param patchedEntriesSource The result of the patcher to add the patched dex files and resources.
|
||||||
|
*/
|
||||||
|
fun copyAligned(apkFile: File, outputFile: File, patchedEntriesSource: PatcherResult) {
|
||||||
|
logger.info("Aligning ${apkFile.name}")
|
||||||
|
|
||||||
|
outputFile.toPath().deleteIfExists()
|
||||||
|
|
||||||
|
ZipFile(outputFile).use { file ->
|
||||||
|
patchedEntriesSource.dexFiles.forEach {
|
||||||
|
file.addEntryCompressData(
|
||||||
|
ZipEntry(it.name), it.stream.readBytes()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchedEntriesSource.resourceFile?.let {
|
||||||
|
file.copyEntriesFromFileAligned(
|
||||||
|
ZipFile(it), ZipFile.apkZipEntryAlignment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Do not compress result.doNotCompress
|
||||||
|
|
||||||
|
// TODO: Fix copying resources that are not needed anymore.
|
||||||
|
file.copyEntriesFromFileAligned(
|
||||||
|
ZipFile(apkFile), ZipFile.apkZipEntryAlignment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the apk at [apk] and writes it to [output].
|
||||||
|
*
|
||||||
|
* @param apk The apk to sign.
|
||||||
|
* @param output The apk to write the signed apk to.
|
||||||
|
* @param signingOptions The options to use for signing.
|
||||||
|
*/
|
||||||
|
fun sign(
|
||||||
|
apk: File,
|
||||||
|
output: File,
|
||||||
|
signingOptions: SigningOptions,
|
||||||
|
) {
|
||||||
|
logger.info("Signing ${apk.name}")
|
||||||
|
|
||||||
|
ApkSigner(signingOptions).signApk(apk, output)
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,19 @@
|
|||||||
package app.revanced.utils
|
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
|
package app.revanced.lib
|
||||||
|
|
||||||
|
|
||||||
|
import app.revanced.lib.Options.Patch.Option
|
||||||
|
import app.revanced.patcher.PatchClass
|
||||||
import app.revanced.patcher.PatchSet
|
import app.revanced.patcher.PatchSet
|
||||||
import app.revanced.patcher.patch.options.PatchOptionException
|
import app.revanced.patcher.patch.options.PatchOptionException
|
||||||
import app.revanced.utils.Options.PatchOption.Option
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
private typealias PatchList = List<PatchClass>
|
||||||
|
|
||||||
internal object Options {
|
object Options {
|
||||||
private val logger = Logger.getLogger(Options::class.java.name)
|
private val logger = Logger.getLogger(Options::class.java.name)
|
||||||
|
|
||||||
private var mapper = jacksonObjectMapper()
|
private var mapper = jacksonObjectMapper()
|
||||||
@ -24,7 +28,7 @@ internal object Options {
|
|||||||
fun serialize(patches: PatchSet, prettyPrint: Boolean = false): String = patches
|
fun serialize(patches: PatchSet, prettyPrint: Boolean = false): String = patches
|
||||||
.filter { it.options.any() }
|
.filter { it.options.any() }
|
||||||
.map { patch ->
|
.map { patch ->
|
||||||
PatchOption(
|
Patch(
|
||||||
patch.name!!,
|
patch.name!!,
|
||||||
patch.options.values.map { option -> Option(option.key, option.value) }
|
patch.options.values.map { option -> Option(option.key, option.value) }
|
||||||
)
|
)
|
||||||
@ -42,12 +46,11 @@ internal object Options {
|
|||||||
* Deserializes the options for the patches in the list.
|
* Deserializes the options for the patches in the list.
|
||||||
*
|
*
|
||||||
* @param json The JSON string containing the options.
|
* @param json The JSON string containing the options.
|
||||||
* @return The list of [PatchOption]s.
|
* @return The list of [Patch]s.
|
||||||
* @see PatchOption
|
* @see Patch
|
||||||
* @see PatchList
|
* @see PatchList
|
||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
fun deserialize(json: String): Array<Patch> = mapper.readValue(json, Array<Patch>::class.java)
|
||||||
fun deserialize(json: String): Array<PatchOption> = mapper.readValue(json, Array<PatchOption>::class.java)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the options for the patches in the list.
|
* Sets the options for the patches in the list.
|
||||||
@ -88,7 +91,7 @@ internal object Options {
|
|||||||
* @property patchName The name of the patch.
|
* @property patchName The name of the patch.
|
||||||
* @property options The [Option]s for the patch.
|
* @property options The [Option]s for the patch.
|
||||||
*/
|
*/
|
||||||
internal data class PatchOption(
|
class Patch internal constructor(
|
||||||
val patchName: String,
|
val patchName: String,
|
||||||
val options: List<Option>
|
val options: List<Option>
|
||||||
) {
|
) {
|
||||||
@ -99,6 +102,6 @@ internal object Options {
|
|||||||
* @property key The name of the option.
|
* @property key The name of the option.
|
||||||
* @property value The value of the option.
|
* @property value The value of the option.
|
||||||
*/
|
*/
|
||||||
internal data class Option(val key: String, val value: Any?)
|
class Option internal constructor(val key: String, val value: Any?)
|
||||||
}
|
}
|
||||||
}
|
}
|
154
revanced-lib/src/main/kotlin/app/revanced/lib/adb/AdbManager.kt
Normal file
154
revanced-lib/src/main/kotlin/app/revanced/lib/adb/AdbManager.kt
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package app.revanced.lib.adb
|
||||||
|
|
||||||
|
import app.revanced.lib.adb.AdbManager.Apk
|
||||||
|
import app.revanced.lib.adb.Constants.CREATE_DIR
|
||||||
|
import app.revanced.lib.adb.Constants.DELETE
|
||||||
|
import app.revanced.lib.adb.Constants.INSTALLATION_PATH
|
||||||
|
import app.revanced.lib.adb.Constants.INSTALL_MOUNT
|
||||||
|
import app.revanced.lib.adb.Constants.INSTALL_PATCHED_APK
|
||||||
|
import app.revanced.lib.adb.Constants.MOUNT_PATH
|
||||||
|
import app.revanced.lib.adb.Constants.MOUNT_SCRIPT
|
||||||
|
import app.revanced.lib.adb.Constants.PATCHED_APK_PATH
|
||||||
|
import app.revanced.lib.adb.Constants.PLACEHOLDER
|
||||||
|
import app.revanced.lib.adb.Constants.RESOLVE_ACTIVITY
|
||||||
|
import app.revanced.lib.adb.Constants.RESTART
|
||||||
|
import app.revanced.lib.adb.Constants.TMP_PATH
|
||||||
|
import app.revanced.lib.adb.Constants.UMOUNT
|
||||||
|
import se.vidstige.jadb.JadbConnection
|
||||||
|
import se.vidstige.jadb.JadbDevice
|
||||||
|
import se.vidstige.jadb.managers.Package
|
||||||
|
import se.vidstige.jadb.managers.PackageManager
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
sealed class AdbManager private constructor(deviceSerial: String? = null) {
|
||||||
|
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.fine("Established connection to $deviceSerial")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the [Apk] file.
|
||||||
|
*
|
||||||
|
* @param apk The [Apk] file.
|
||||||
|
*/
|
||||||
|
open fun install(apk: Apk) {
|
||||||
|
logger.info("Finished installing ${apk.file.name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls the package.
|
||||||
|
*
|
||||||
|
* @param packageName The package name.
|
||||||
|
*/
|
||||||
|
open fun uninstall(packageName: String) {
|
||||||
|
logger.info("Finished uninstalling $packageName")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Gets an [AdbManager] for the supplied device serial.
|
||||||
|
*
|
||||||
|
* @param deviceSerial The device serial.
|
||||||
|
* @param root Whether to use root or not.
|
||||||
|
* @return The [AdbManager].
|
||||||
|
* @throws DeviceNotFoundException If the device can not be found.
|
||||||
|
*/
|
||||||
|
fun getAdbManager(deviceSerial: String, root: Boolean = false): AdbManager =
|
||||||
|
if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial)
|
||||||
|
}
|
||||||
|
|
||||||
|
class RootAdbManager internal constructor(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")
|
||||||
|
|
||||||
|
val packageName = apk.packageName ?: throw PackageNameRequiredException()
|
||||||
|
|
||||||
|
device.run(RESOLVE_ACTIVITY, packageName).inputStream.bufferedReader().readLine().let { line ->
|
||||||
|
if (line != "No activity found") return@let
|
||||||
|
throw throw FailedToFindInstalledPackageException(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
device.push(apk.file, TMP_PATH)
|
||||||
|
|
||||||
|
device.run("$CREATE_DIR $INSTALLATION_PATH")
|
||||||
|
device.run(INSTALL_PATCHED_APK, packageName)
|
||||||
|
|
||||||
|
device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement(packageName))
|
||||||
|
|
||||||
|
device.run(INSTALL_MOUNT, packageName)
|
||||||
|
device.run(UMOUNT, packageName) // Sanity check.
|
||||||
|
device.run(MOUNT_PATH, packageName)
|
||||||
|
device.run(RESTART, packageName)
|
||||||
|
device.run(DELETE, TMP_PATH)
|
||||||
|
|
||||||
|
super.install(apk)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun uninstall(packageName: String) {
|
||||||
|
logger.info("Uninstalling $packageName by unmounting")
|
||||||
|
|
||||||
|
device.run(UMOUNT, packageName)
|
||||||
|
device.run(DELETE.applyReplacement(PATCHED_APK_PATH), packageName)
|
||||||
|
device.run(DELETE, MOUNT_PATH)
|
||||||
|
device.run(DELETE, TMP_PATH)
|
||||||
|
|
||||||
|
super.uninstall(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Utils {
|
||||||
|
private fun JadbDevice.run(command: String, with: String) = run(command.applyReplacement(with))
|
||||||
|
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserAdbManager internal constructor(deviceSerial: String) : AdbManager(deviceSerial) {
|
||||||
|
private val packageManager = PackageManager(device)
|
||||||
|
|
||||||
|
override fun install(apk: Apk) {
|
||||||
|
PackageManager(device).install(apk.file)
|
||||||
|
|
||||||
|
super.install(apk)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun uninstall(packageName: String) {
|
||||||
|
logger.info("Uninstalling $packageName")
|
||||||
|
|
||||||
|
packageManager.uninstall(Package(packageName))
|
||||||
|
|
||||||
|
super.uninstall(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apk file for [AdbManager].
|
||||||
|
*
|
||||||
|
* @param file The [Apk] file.
|
||||||
|
* @param packageName The package name of the [Apk] file.
|
||||||
|
*/
|
||||||
|
class Apk(val file: File, val packageName: String? = null)
|
||||||
|
|
||||||
|
class DeviceNotFoundException internal constructor(deviceSerial: String?) :
|
||||||
|
Exception(deviceSerial?.let {
|
||||||
|
"The device with the ADB device serial \"$deviceSerial\" can not be found"
|
||||||
|
} ?: "No ADB device found")
|
||||||
|
|
||||||
|
class FailedToFindInstalledPackageException internal constructor(packageName: String) :
|
||||||
|
Exception("Failed to find installed package \"$packageName\" because no activity was found")
|
||||||
|
|
||||||
|
class PackageNameRequiredException internal constructor() :
|
||||||
|
Exception("Package name is required")
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
package app.revanced.utils.adb
|
package app.revanced.lib.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.ShellProcess
|
||||||
import se.vidstige.jadb.ShellProcessBuilder
|
import se.vidstige.jadb.ShellProcessBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -15,8 +16,8 @@ internal fun JadbDevice.buildCommand(command: String, su: Boolean = true): Shell
|
|||||||
return shellProcessBuilder(cmd, *args.toTypedArray())
|
return shellProcessBuilder(cmd, *args.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun JadbDevice.run(command: String, su: Boolean = true): Int {
|
internal fun JadbDevice.run(command: String, su: Boolean = true): ShellProcess {
|
||||||
return this.buildCommand(command, su).start().waitFor()
|
return this.buildCommand(command, su).start()!!
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun JadbDevice.hasSu() =
|
internal fun JadbDevice.hasSu() =
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.utils.adb
|
package app.revanced.lib.adb
|
||||||
|
|
||||||
internal object Constants {
|
internal object Constants {
|
||||||
internal const val PLACEHOLDER = "PLACEHOLDER"
|
internal const val PLACEHOLDER = "PLACEHOLDER"
|
||||||
@ -10,7 +10,8 @@ internal object Constants {
|
|||||||
|
|
||||||
internal const val DELETE = "rm -rf $PLACEHOLDER"
|
internal const val DELETE = "rm -rf $PLACEHOLDER"
|
||||||
internal const val CREATE_DIR = "mkdir -p"
|
internal const val CREATE_DIR = "mkdir -p"
|
||||||
internal const val RESTART = "pm resolve-activity --brief $PLACEHOLDER | tail -n 1 | " +
|
internal const val RESOLVE_ACTIVITY = "pm resolve-activity --brief $PLACEHOLDER"
|
||||||
|
internal const val RESTART = "$RESOLVE_ACTIVITY | tail -n 1 | " +
|
||||||
"xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)"
|
"xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)"
|
||||||
|
|
||||||
internal const val INSTALL_PATCHED_APK = "base_path=\"$PATCHED_APK_PATH\" && " +
|
internal const val INSTALL_PATCHED_APK = "base_path=\"$PATCHED_APK_PATH\" && " +
|
@ -0,0 +1,84 @@
|
|||||||
|
package app.revanced.lib.logging
|
||||||
|
|
||||||
|
import java.util.logging.Handler
|
||||||
|
import java.util.logging.Level
|
||||||
|
import java.util.logging.LogRecord
|
||||||
|
import java.util.logging.SimpleFormatter
|
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
object Logger {
|
||||||
|
private val rootLogger = java.util.logging.Logger.getLogger("")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the format for the logger.
|
||||||
|
*
|
||||||
|
* @param format The format to use.
|
||||||
|
*/
|
||||||
|
fun setFormat(format: String = "%4\$s: %5\$s %n") {
|
||||||
|
System.setProperty("java.util.logging.SimpleFormatter.format", format)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all handlers from the logger.
|
||||||
|
*/
|
||||||
|
fun removeAllHandlers() {
|
||||||
|
rootLogger.let { logger ->
|
||||||
|
logger.handlers.forEach { handler ->
|
||||||
|
handler.close()
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a handler to the logger.
|
||||||
|
*
|
||||||
|
* @param publishHandler The handler for publishing the log.
|
||||||
|
* @param flushHandler The handler for flushing the log.
|
||||||
|
* @param closeHandler The handler for closing the log.
|
||||||
|
*/
|
||||||
|
fun addHandler(
|
||||||
|
publishHandler: (log: String, level: Level, loggerName: String?) -> Unit,
|
||||||
|
flushHandler: () -> Unit,
|
||||||
|
closeHandler: () -> Unit
|
||||||
|
) = object : Handler() {
|
||||||
|
override fun publish(record: LogRecord) = publishHandler(
|
||||||
|
formatter.format(record),
|
||||||
|
record.level,
|
||||||
|
record.loggerName
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun flush() = flushHandler()
|
||||||
|
|
||||||
|
override fun close() = closeHandler()
|
||||||
|
}.also {
|
||||||
|
it.level = Level.ALL
|
||||||
|
it.formatter = SimpleFormatter()
|
||||||
|
}.let(rootLogger::addHandler)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log to "standard" (error) output streams.
|
||||||
|
*/
|
||||||
|
fun setDefault() {
|
||||||
|
setFormat()
|
||||||
|
removeAllHandlers()
|
||||||
|
|
||||||
|
val publishHandler = handler@{ log: String, level: Level, loggerName: String? ->
|
||||||
|
if (loggerName?.startsWith("app.revanced") != true) return@handler
|
||||||
|
|
||||||
|
log.toByteArray().let {
|
||||||
|
if (level.intValue() > Level.WARNING.intValue())
|
||||||
|
System.err.write(it)
|
||||||
|
else
|
||||||
|
System.out.write(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val flushHandler = {
|
||||||
|
System.out.flush()
|
||||||
|
System.err.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
addHandler(publishHandler, flushHandler, flushHandler)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.utils.signing
|
package app.revanced.lib.signing
|
||||||
|
|
||||||
import com.android.apksig.ApkSigner
|
import com.android.apksig.ApkSigner
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
@ -17,10 +17,10 @@ import java.security.cert.X509Certificate
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
internal class ApkSigner(
|
class ApkSigner(
|
||||||
private val signingOptions: SigningOptions
|
private val signingOptions: SigningOptions
|
||||||
) {
|
) {
|
||||||
private val logger = Logger.getLogger(ApkSigner::class.java.name)
|
private val logger = Logger.getLogger(app.revanced.lib.signing.ApkSigner::class.java.name)
|
||||||
|
|
||||||
private val signer: ApkSigner.Builder
|
private val signer: ApkSigner.Builder
|
||||||
private val passwordCharArray = signingOptions.password.toCharArray()
|
private val passwordCharArray = signingOptions.password.toCharArray()
|
||||||
@ -30,12 +30,12 @@ internal class ApkSigner(
|
|||||||
|
|
||||||
val keyStore = KeyStore.getInstance("BKS", "BC")
|
val keyStore = KeyStore.getInstance("BKS", "BC")
|
||||||
val alias = keyStore.let { store ->
|
val alias = keyStore.let { store ->
|
||||||
FileInputStream(File(signingOptions.keyStoreFilePath).also {
|
FileInputStream(signingOptions.keyStoreOutputFilePath.also {
|
||||||
if (!it.exists()) {
|
if (!it.exists()) {
|
||||||
logger.info("Creating keystore at ${it.absolutePath}")
|
logger.info("Creating keystore at ${it.absolutePath}")
|
||||||
newKeystore(it)
|
newKeystore(it)
|
||||||
} else {
|
} else {
|
||||||
logger.info("Using keystore at ${it.absolutePath}")
|
logger.info("Using keystore ${it.absolutePath}")
|
||||||
}
|
}
|
||||||
}).use { fis -> store.load(fis, null) }
|
}).use { fis -> store.load(fis, null) }
|
||||||
store.aliases().nextElement()
|
store.aliases().nextElement()
|
||||||
@ -43,13 +43,13 @@ internal class ApkSigner(
|
|||||||
|
|
||||||
with(
|
with(
|
||||||
ApkSigner.SignerConfig.Builder(
|
ApkSigner.SignerConfig.Builder(
|
||||||
signingOptions.cn,
|
signingOptions.commonName,
|
||||||
keyStore.getKey(alias, passwordCharArray) as PrivateKey,
|
keyStore.getKey(alias, passwordCharArray) as PrivateKey,
|
||||||
listOf(keyStore.getCertificate(alias) as X509Certificate)
|
listOf(keyStore.getCertificate(alias) as X509Certificate)
|
||||||
).build()
|
).build()
|
||||||
) {
|
) {
|
||||||
this@ApkSigner.signer = ApkSigner.Builder(listOf(this))
|
this@ApkSigner.signer = ApkSigner.Builder(listOf(this))
|
||||||
signer.setCreatedBy(signingOptions.cn)
|
signer.setCreatedBy(signingOptions.commonName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ internal class ApkSigner(
|
|||||||
val pair = gen.generateKeyPair()
|
val pair = gen.generateKeyPair()
|
||||||
var serialNumber: BigInteger
|
var serialNumber: BigInteger
|
||||||
do serialNumber = BigInteger.valueOf(SecureRandom().nextLong()) while (serialNumber < BigInteger.ZERO)
|
do serialNumber = BigInteger.valueOf(SecureRandom().nextLong()) while (serialNumber < BigInteger.ZERO)
|
||||||
val x500Name = X500Name("CN=${signingOptions.cn}")
|
val x500Name = X500Name("CN=${signingOptions.commonName}")
|
||||||
val builder = X509v3CertificateBuilder(
|
val builder = X509v3CertificateBuilder(
|
||||||
x500Name,
|
x500Name,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
@ -81,12 +81,10 @@ internal class ApkSigner(
|
|||||||
return JcaX509CertificateConverter().getCertificate(builder.build(signer)) to pair.private
|
return JcaX509CertificateConverter().getCertificate(builder.build(signer)) to pair.private
|
||||||
}
|
}
|
||||||
|
|
||||||
fun signApk(input: File, output: File): File {
|
fun signApk(input: File, output: File) {
|
||||||
signer.setInputApk(input)
|
signer.setInputApk(input)
|
||||||
signer.setOutputApk(output)
|
signer.setOutputApk(output)
|
||||||
|
|
||||||
signer.build().sign()
|
signer.build().sign()
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.lib.signing
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
data class SigningOptions(
|
||||||
|
val commonName: String,
|
||||||
|
val password: String,
|
||||||
|
val keyStoreOutputFilePath: File
|
||||||
|
)
|
@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.lib.zip
|
||||||
|
|
||||||
|
import java.io.DataInput
|
||||||
|
import java.io.DataOutput
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
internal fun UInt.toLittleEndian() =
|
||||||
|
(((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt()
|
||||||
|
|
||||||
|
internal fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
|
||||||
|
|
||||||
|
internal fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8)
|
||||||
|
or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt()
|
||||||
|
|
||||||
|
internal fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
|
||||||
|
|
||||||
|
internal fun ByteBuffer.getUShort() = this.getShort().toUShort()
|
||||||
|
internal fun ByteBuffer.getUInt() = this.getInt().toUInt()
|
||||||
|
|
||||||
|
internal fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort())
|
||||||
|
internal fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt())
|
||||||
|
|
||||||
|
internal fun DataInput.readUShort() = this.readShort().toUShort()
|
||||||
|
internal fun DataInput.readUInt() = this.readInt().toUInt()
|
||||||
|
|
||||||
|
internal fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
|
||||||
|
internal fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
|
||||||
|
|
||||||
|
internal fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
|
||||||
|
internal fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
|
||||||
|
|
||||||
|
internal fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
|
||||||
|
internal fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.utils.align.zip
|
package app.revanced.lib.zip
|
||||||
|
|
||||||
import app.revanced.utils.align.zip.structures.ZipEndRecord
|
import app.revanced.lib.zip.structures.ZipEndRecord
|
||||||
import app.revanced.utils.align.zip.structures.ZipEntry
|
import app.revanced.lib.zip.structures.ZipEntry
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.RandomAccessFile
|
import java.io.RandomAccessFile
|
||||||
@ -178,4 +178,15 @@ class ZipFile(file: File) : Closeable {
|
|||||||
if (centralDirectoryNeedsRewrite) writeCD()
|
if (centralDirectoryNeedsRewrite) writeCD()
|
||||||
filePointer.close()
|
filePointer.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object ApkZipFile {
|
||||||
|
private const val DEFAULT_ALIGNMENT = 4
|
||||||
|
private const val LIBRARY_ALIGNMENT = 4096
|
||||||
|
|
||||||
|
val apkZipEntryAlignment = { entry: ZipEntry ->
|
||||||
|
if (entry.compression.toUInt() != 0u) null
|
||||||
|
else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT
|
||||||
|
else DEFAULT_ALIGNMENT
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,14 +1,14 @@
|
|||||||
package app.revanced.utils.align.zip.structures
|
package app.revanced.lib.zip.structures
|
||||||
|
|
||||||
import app.revanced.utils.align.zip.putUInt
|
import app.revanced.lib.zip.putUInt
|
||||||
import app.revanced.utils.align.zip.putUShort
|
import app.revanced.lib.zip.putUShort
|
||||||
import app.revanced.utils.align.zip.readUIntLE
|
import app.revanced.lib.zip.readUIntLE
|
||||||
import app.revanced.utils.align.zip.readUShortLE
|
import app.revanced.lib.zip.readUShortLE
|
||||||
import java.io.DataInput
|
import java.io.DataInput
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
data class ZipEndRecord(
|
internal class ZipEndRecord(
|
||||||
val diskNumber: UShort,
|
val diskNumber: UShort,
|
||||||
val startingDiskNumber: UShort,
|
val startingDiskNumber: UShort,
|
||||||
val diskEntries: UShort,
|
val diskEntries: UShort,
|
@ -1,64 +1,62 @@
|
|||||||
package app.revanced.utils.align.zip.structures
|
package app.revanced.lib.zip.structures
|
||||||
|
|
||||||
import app.revanced.utils.align.zip.*
|
import app.revanced.lib.zip.*
|
||||||
import java.io.DataInput
|
import java.io.DataInput
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
data class ZipEntry(
|
class ZipEntry private constructor(
|
||||||
val version: UShort,
|
internal val version: UShort,
|
||||||
val versionNeeded: UShort,
|
internal val versionNeeded: UShort,
|
||||||
val flags: UShort,
|
internal val flags: UShort,
|
||||||
var compression: UShort,
|
internal var compression: UShort,
|
||||||
val modificationTime: UShort,
|
internal val modificationTime: UShort,
|
||||||
val modificationDate: UShort,
|
internal val modificationDate: UShort,
|
||||||
var crc32: UInt,
|
internal var crc32: UInt,
|
||||||
var compressedSize: UInt,
|
internal var compressedSize: UInt,
|
||||||
var uncompressedSize: UInt,
|
internal var uncompressedSize: UInt,
|
||||||
val diskNumber: UShort,
|
internal val diskNumber: UShort,
|
||||||
val internalAttributes: UShort,
|
internal val internalAttributes: UShort,
|
||||||
val externalAttributes: UInt,
|
internal val externalAttributes: UInt,
|
||||||
var localHeaderOffset: UInt,
|
internal var localHeaderOffset: UInt,
|
||||||
val fileName: String,
|
internal val fileName: String,
|
||||||
val extraField: ByteArray,
|
internal val extraField: ByteArray,
|
||||||
val fileComment: String,
|
internal val fileComment: String,
|
||||||
var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
internal var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
||||||
) {
|
) {
|
||||||
val LFHSize: Int
|
internal val LFHSize: Int
|
||||||
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
||||||
|
|
||||||
val dataOffset: UInt
|
internal val dataOffset: UInt
|
||||||
get() = localHeaderOffset + LFHSize.toUInt()
|
get() = localHeaderOffset + LFHSize.toUInt()
|
||||||
|
|
||||||
|
constructor(fileName: String) : this(
|
||||||
|
0x1403u, //made by unix, version 20
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0x0821u, //seems to be static time google uses, no idea
|
||||||
|
0x0221u, //same as above
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
fileName,
|
||||||
|
ByteArray(0),
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CDE_HEADER_SIZE = 46
|
internal const val CDE_HEADER_SIZE = 46
|
||||||
const val CDE_SIGNATURE = 0x02014b50u
|
internal const val CDE_SIGNATURE = 0x02014b50u
|
||||||
|
|
||||||
const val LFH_HEADER_SIZE = 30
|
internal const val LFH_HEADER_SIZE = 30
|
||||||
const val LFH_SIGNATURE = 0x04034b50u
|
internal const val LFH_SIGNATURE = 0x04034b50u
|
||||||
|
|
||||||
fun createWithName(fileName: String): ZipEntry {
|
internal fun fromCDE(input: DataInput): ZipEntry {
|
||||||
return ZipEntry(
|
|
||||||
0x1403u, //made by unix, version 20
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0x0821u, //seems to be static time google uses, no idea
|
|
||||||
0x0221u, //same as above
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
fileName,
|
|
||||||
ByteArray(0),
|
|
||||||
""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromCDE(input: DataInput): ZipEntry {
|
|
||||||
val signature = input.readUIntLE()
|
val signature = input.readUIntLE()
|
||||||
|
|
||||||
if (signature != CDE_SIGNATURE)
|
if (signature != CDE_SIGNATURE)
|
||||||
@ -123,12 +121,12 @@ data class ZipEntry(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readLocalExtra(buffer: ByteBuffer) {
|
internal fun readLocalExtra(buffer: ByteBuffer) {
|
||||||
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
||||||
localExtraField = ByteArray(buffer.getUShort().toInt())
|
localExtraField = ByteArray(buffer.getUShort().toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toLFH(): ByteBuffer {
|
internal fun toLFH(): ByteBuffer {
|
||||||
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size)
|
val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size)
|
||||||
@ -153,7 +151,7 @@ data class ZipEntry(
|
|||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toCDE(): ByteBuffer {
|
internal fun toCDE(): ByteBuffer {
|
||||||
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
||||||
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
||||||
|
|
@ -1,11 +1,11 @@
|
|||||||
package app.revanced.patcher.options
|
package app.revanced.patcher.options
|
||||||
|
|
||||||
|
import app.revanced.lib.Options
|
||||||
|
import app.revanced.lib.Options.setOptions
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.patch.BytecodePatch
|
import app.revanced.patcher.patch.BytecodePatch
|
||||||
import app.revanced.patcher.patch.options.types.BooleanPatchOption.Companion.booleanPatchOption
|
import app.revanced.patcher.patch.options.types.BooleanPatchOption.Companion.booleanPatchOption
|
||||||
import app.revanced.patcher.patch.options.types.StringPatchOption.Companion.stringPatchOption
|
import app.revanced.patcher.patch.options.types.StringPatchOption.Companion.stringPatchOption
|
||||||
import app.revanced.utils.Options
|
|
||||||
import app.revanced.utils.Options.setOptions
|
|
||||||
import org.junit.jupiter.api.MethodOrderer
|
import org.junit.jupiter.api.MethodOrderer
|
||||||
import org.junit.jupiter.api.Order
|
import org.junit.jupiter.api.Order
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@ -22,7 +22,7 @@ object PatchOptionsTestPatch : BytecodePatch(name = "PatchOptionsTestPatch") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
||||||
internal object PatchOptionOptionsTest {
|
internal object PatchOptionsTest {
|
||||||
private var patches = setOf(PatchOptionsTestPatch)
|
private var patches = setOf(PatchOptionsTestPatch)
|
||||||
|
|
||||||
@Test
|
@Test
|
@ -20,4 +20,4 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "revanced-cli"
|
include("revanced-cli", "revanced-lib")
|
@ -1,64 +0,0 @@
|
|||||||
package app.revanced.cli.command
|
|
||||||
|
|
||||||
import app.revanced.cli.command.utility.UtilityCommand
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
private object CLIVersionProvider : IVersionProvider {
|
|
||||||
override fun getVersion(): Array<String> {
|
|
||||||
Properties().apply {
|
|
||||||
load(MainCommand::class.java.getResourceAsStream("/app/revanced/cli/version.properties"))
|
|
||||||
}.let {
|
|
||||||
return arrayOf("ReVanced CLI v${it.getProperty("version")}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command(
|
|
||||||
name = "revanced-cli",
|
|
||||||
description = ["Command line application to use ReVanced"],
|
|
||||||
mixinStandardHelpOptions = true,
|
|
||||||
versionProvider = CLIVersionProvider::class,
|
|
||||||
subcommands = [
|
|
||||||
ListPatchesCommand::class,
|
|
||||||
PatchCommand::class,
|
|
||||||
OptionsCommand::class,
|
|
||||||
UtilityCommand::class,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
private object MainCommand
|
|
@ -1,140 +0,0 @@
|
|||||||
package app.revanced.utils.adb
|
|
||||||
|
|
||||||
import app.revanced.utils.adb.AdbManager.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) : 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.fine("Established connection to $deviceSerial")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs the [Apk] file.
|
|
||||||
*
|
|
||||||
* @param apk The [Apk] file.
|
|
||||||
*/
|
|
||||||
open fun install(apk: Apk) {
|
|
||||||
logger.info("Finished installing ${apk.file.name}")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstalls the package.
|
|
||||||
*
|
|
||||||
* @param packageName The package name.
|
|
||||||
*/
|
|
||||||
open fun uninstall(packageName: String) {
|
|
||||||
logger.info("Finished uninstalling $packageName")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the [AdbManager] instance.
|
|
||||||
*/
|
|
||||||
override fun close() {
|
|
||||||
logger.fine("Closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
val applyReplacement = getPlaceholderReplacement(
|
|
||||||
apk.packageName ?: throw IllegalArgumentException("Package name is required")
|
|
||||||
)
|
|
||||||
|
|
||||||
device.push(apk.file, TMP_PATH)
|
|
||||||
|
|
||||||
device.run("$CREATE_DIR $INSTALLATION_PATH")
|
|
||||||
device.run(INSTALL_PATCHED_APK.applyReplacement())
|
|
||||||
|
|
||||||
device.createFile(TMP_PATH, MOUNT_SCRIPT.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")
|
|
||||||
|
|
||||||
val applyReplacement = getPlaceholderReplacement(packageName)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object Utils {
|
|
||||||
private fun getPlaceholderReplacement(with: String): String.() -> String = { replace(PLACEHOLDER, with) }
|
|
||||||
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserAdbManager(deviceSerial: String) : AdbManager(deviceSerial) {
|
|
||||||
private val packageManager = PackageManager(device)
|
|
||||||
|
|
||||||
override fun install(apk: Apk) {
|
|
||||||
PackageManager(device).install(apk.file)
|
|
||||||
|
|
||||||
super.install(apk)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun uninstall(packageName: String) {
|
|
||||||
logger.info("Uninstalling $packageName")
|
|
||||||
|
|
||||||
packageManager.uninstall(Package(packageName))
|
|
||||||
|
|
||||||
super.uninstall(packageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
|
|
||||||
internal class DeviceNotFoundException(deviceSerial: String?) :
|
|
||||||
Exception(deviceSerial?.let {
|
|
||||||
"The device with the ADB device serial \"$deviceSerial\" can not be found"
|
|
||||||
} ?: "No ADB device found")
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package app.revanced.utils.align
|
|
||||||
|
|
||||||
import app.revanced.utils.align.zip.structures.ZipEntry
|
|
||||||
|
|
||||||
internal object ZipAligner {
|
|
||||||
private const val DEFAULT_ALIGNMENT = 4
|
|
||||||
private const val LIBRARY_ALIGNMENT = 4096
|
|
||||||
|
|
||||||
fun getEntryAlignment(entry: ZipEntry): Int? =
|
|
||||||
if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package app.revanced.utils.align.zip
|
|
||||||
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.io.DataOutput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
fun UInt.toLittleEndian() =
|
|
||||||
(((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt()
|
|
||||||
|
|
||||||
fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
|
|
||||||
|
|
||||||
fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8)
|
|
||||||
or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt()
|
|
||||||
|
|
||||||
fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
|
|
||||||
|
|
||||||
fun ByteBuffer.getUShort() = this.getShort().toUShort()
|
|
||||||
fun ByteBuffer.getUInt() = this.getInt().toUInt()
|
|
||||||
|
|
||||||
fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort())
|
|
||||||
fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt())
|
|
||||||
|
|
||||||
fun DataInput.readUShort() = this.readShort().toUShort()
|
|
||||||
fun DataInput.readUInt() = this.readInt().toUInt()
|
|
||||||
|
|
||||||
fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
|
|
||||||
fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
|
|
||||||
|
|
||||||
fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
|
|
||||||
fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
|
|
||||||
|
|
||||||
fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
|
|
||||||
fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
|
|
@ -1,7 +0,0 @@
|
|||||||
package app.revanced.utils.signing
|
|
||||||
|
|
||||||
data class SigningOptions(
|
|
||||||
val cn: String,
|
|
||||||
val password: String,
|
|
||||||
val keyStoreFilePath: String
|
|
||||||
)
|
|
Loading…
Reference in New Issue
Block a user