build: add properties resource file

This commit is contained in:
oSumAtrIX 2023-08-23 03:06:27 +02:00
parent c0cc909626
commit 8a5daab2a3
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
8 changed files with 31 additions and 366 deletions

View File

@ -24,7 +24,7 @@ repositories {
dependencies {
implementation("app.revanced:revanced-patcher:14.0.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.22")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation("info.picocli:picocli:4.7.3")
implementation("com.github.revanced:jadb:2531a28109") // Updated fork
@ -34,9 +34,8 @@ dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
}
kotlin {
jvmToolchain(11)
}
kotlin { jvmToolchain(11) }
tasks {
test {
@ -45,9 +44,15 @@ tasks {
events("PASSED", "SKIPPED", "FAILED")
}
}
processResources {
expand("projectVersion" to project.version)
}
build {
dependsOn(shadowJar)
}
shadowJar {
manifest {
attributes("Main-Class" to "app.revanced.cli.main.MainKt")
@ -61,9 +66,5 @@ tasks {
// 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)
}
register<DefaultTask>("publish") { }
}

View File

@ -8,41 +8,41 @@ import app.revanced.patcher.extensions.PatchExtensions.options
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.PatchClass
import app.revanced.patcher.patch.PatchOption
import picocli.CommandLine
import picocli.CommandLine.*
import picocli.CommandLine.Help.Visibility.ALWAYS
import java.io.File
@CommandLine.Command(name = "list-patches", description = ["List patches from supplied patch bundles"])
class ListPatchesCommand : Runnable {
@CommandLine.Parameters(
@Command(name = "list-patches", description = ["List patches from supplied patch bundles"])
internal object ListPatchesCommand : Runnable {
@Parameters(
description = ["Paths to patch bundles"],
arity = "1..*"
)
lateinit var patchBundles: Array<File>
@CommandLine.Option(
@Option(
names = ["-d", "--with-descriptions"],
description = ["List their descriptions"],
showDefaultValue = ALWAYS
)
var withDescriptions: Boolean = true
@CommandLine.Option(
@Option(
names = ["-p", "--with-packages"],
description = ["List the packages the patches are compatible with"],
showDefaultValue = ALWAYS
)
var withPackages: Boolean = false
@CommandLine.Option(
@Option(
names = ["-v", "--with-versions"],
description = ["List the versions of the packages the patches are compatible with"],
showDefaultValue = ALWAYS
)
var withVersions: Boolean = false
@CommandLine.Option(
@Option(
names = ["-o", "--with-options"],
description = ["List the options of the patches"],
showDefaultValue = ALWAYS
@ -89,6 +89,6 @@ class ListPatchesCommand : Runnable {
}
}
MainCommand.logger.info(PatchBundleLoader.Jar(*patchBundles).joinToString("\n\n") { it.buildString() })
logger.info(PatchBundleLoader.Jar(*patchBundles).joinToString("\n\n") { it.buildString() })
}
}

View File

@ -1,318 +0,0 @@
package app.revanced.cli.command
import app.revanced.cli.aligning.Aligning
import app.revanced.cli.logging.impl.DefaultCliLogger
import app.revanced.cli.patcher.logging.impl.PatcherLogger
import app.revanced.cli.signing.Signing
import app.revanced.cli.signing.SigningOptions
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.include
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.PatchClass
import app.revanced.utils.Options
import app.revanced.utils.Options.setOptions
import app.revanced.utils.adb.AdbManager
import kotlinx.coroutines.runBlocking
import picocli.CommandLine
import picocli.CommandLine.*
import java.io.File
fun main(args: Array<String>) {
CommandLine(MainCommand).execute(*args)
}
internal typealias PatchList = List<PatchClass>
private class CLIVersionProvider : IVersionProvider {
override fun getVersion() = arrayOf(
MainCommand::class.java.`package`.implementationVersion ?: "unknown"
)
}
@Command(
name = "ReVanced CLI",
description = ["Command line application to use ReVanced"],
mixinStandardHelpOptions = true,
versionProvider = CLIVersionProvider::class,
subcommands = [ListPatchesCommand::class, UninstallCommand::class]
)
internal object MainCommand : Runnable {
internal val logger = DefaultCliLogger()
// @ArgGroup(exclusive = false, multiplicity = "1")
lateinit var args: Args
/**
* Arguments for the CLI
*/
class Args {
@Option(names = ["--uninstall"], description = ["Package name to uninstall"])
var packageName: String? = null
@Option(names = ["-d", "--device-serial"], description = ["ADB device serial number to deploy to"])
var deviceSerial: String? = null
@Option(names = ["--mount"], description = ["Handle deployments by mounting"])
var mount: Boolean = false
@ArgGroup(exclusive = false)
var patchArgs: PatchArgs? = null
/**
* Arguments for patches.
*/
class PatchArgs {
@Option(names = ["-b", "--bundle"], description = ["One or more bundles of patches"], required = true)
var patchBundles = emptyList<File>()
@ArgGroup(exclusive = false)
var patchingArgs: PatchingArgs? = null
/**
* Arguments for patching.
*/
class PatchingArgs {
@Option(names = ["-a", "--apk"], description = ["APK file to be patched"], required = true)
lateinit var inputFile: File
@Option(
names = ["-o", "--out"],
description = ["Path to save the patched APK file to"],
required = true
)
lateinit var outputFilePath: File
@Option(names = ["--options"], description = ["Path to patch options JSON file"])
var optionsFile: File = File("options.json")
@Option(names = ["-e", "--exclude"], description = ["List of patches to exclude"])
var excludedPatches = arrayOf<String>()
@Option(
names = ["--exclusive"],
description = ["Only include patches that are explicitly specified to be included"]
)
var exclusive = false
@Option(names = ["-i", "--include"], description = ["List of patches to include"])
var includedPatches = arrayOf<String>()
@Option(names = ["--experimental"], description = ["Ignore patches incompatibility to versions"])
var experimental: Boolean = false
@Option(
names = ["-m", "--merge"],
description = ["One or more DEX files or containers to merge into the APK"]
)
var integrations = listOf<File>()
@Option(names = ["--cn"], description = ["The common name of the signer of the patched APK file"])
var commonName = "ReVanced"
@Option(
names = ["--keystore"],
description = ["Path to the keystore to sign the patched APK file with"]
)
var keystorePath: String? = null
@Option(
names = ["-p", "--password"],
description = ["The password of the keystore to sign the patched APK file with"]
)
var password = "ReVanced"
@Option(
names = ["-r", "--resource-cache"],
description = ["Path to temporary resource cache directory"]
)
var resourceCachePath = File("revanced-resource-cache")
@Option(
names = ["-c", "--clean"],
description = ["Clean up the temporary resource cache directory after patching"]
)
var clean: Boolean = false
@Option(
names = ["--custom-aapt2-binary"],
description = ["Path to a custom AAPT binary to compile resources with"]
)
var aaptBinaryPath = File("")
}
}
}
fun main(args: Array<String>) {
CommandLine(MainCommand).execute(*args)
}
override fun run() {
val patchArgs = args.patchArgs
val patchingArgs = patchArgs?.patchingArgs ?: return
if (!patchingArgs.inputFile.exists()) return logger.error("Input file ${patchingArgs.inputFile} does not exist.")
logger.info("Loading patches")
val patches = PatchBundleLoader.Jar(*patchArgs.patchBundles.toTypedArray())
val integrations = patchingArgs.integrations
logger.info("Setting up patch options")
patchingArgs.optionsFile.let {
if (it.exists()) patches.setOptions(it, logger)
else Options.serialize(patches, prettyPrint = true).let(it::writeText)
}
val adbManager = args.deviceSerial?.let { serial ->
if (args.mount) AdbManager.RootAdbManager(serial, logger) else AdbManager.UserAdbManager(serial, logger)
}
val patcher = Patcher(
PatcherOptions(
patchingArgs.inputFile,
patchingArgs.resourceCachePath,
patchingArgs.aaptBinaryPath.absolutePath,
patchingArgs.resourceCachePath.absolutePath,
PatcherLogger
)
)
val result = patcher.apply {
acceptIntegrations(integrations)
acceptPatches(filterPatchSelection(patches))
// Execute patches.
runBlocking {
apply(false).collect { patchResult ->
patchResult.exception?.let {
logger.error("${patchResult.patchName} failed:\n${patchResult.exception}")
} ?: logger.info("${patchResult.patchName} succeeded")
}
}
}.get()
patcher.close()
val outputFileNameWithoutExtension = patchingArgs.outputFilePath.nameWithoutExtension
// Align the file.
val alignedFile = patchingArgs.resourceCachePath.resolve("${outputFileNameWithoutExtension}_aligned.apk")
Aligning.align(result, patchingArgs.inputFile, alignedFile)
// Sign the file if needed.
val finalFile = if (!args.mount) {
val signedOutput = patchingArgs.resourceCachePath.resolve("${outputFileNameWithoutExtension}_signed.apk")
Signing.sign(
alignedFile,
signedOutput,
SigningOptions(
patchingArgs.commonName,
patchingArgs.password,
patchingArgs.keystorePath ?: patchingArgs.outputFilePath.absoluteFile.parentFile
.resolve("${patchingArgs.outputFilePath.nameWithoutExtension}.keystore")
.canonicalPath
)
)
signedOutput
} else
alignedFile
logger.info("Copying ${finalFile.name} to ${patchingArgs.outputFilePath.name}")
finalFile.copyTo(patchingArgs.outputFilePath, overwrite = true)
adbManager?.install(AdbManager.Apk(patchingArgs.outputFilePath, patcher.context.packageMetadata.packageName))
if (patchingArgs.clean) {
logger.info("Cleaning up temporary files")
patchingArgs.outputFilePath.delete()
cleanUp(patchingArgs.resourceCachePath)
}
}
private fun cleanUp(resourceCachePath: File) {
val result = if (resourceCachePath.deleteRecursively())
"Cleaned up cache directory"
else
"Failed to clean up cache directory"
logger.info(result)
}
private fun Patcher.filterPatchSelection(patches: PatchList) = buildList {
val packageName = context.packageMetadata.packageName
val packageVersion = context.packageMetadata.packageVersion
val patchingArgs = args.patchArgs!!.patchingArgs!!
patches.forEach patch@{ patch ->
val formattedPatchName = patch.patchName.lowercase().replace(" ", "-")
/**
* Check if the patch is explicitly excluded.
*
* Cases:
* 1. -e patch.name
* 2. -i patch.name -e patch.name
*/
val excluded = patchingArgs.excludedPatches.contains(formattedPatchName)
if (excluded) return@patch logger.info("Excluding ${patch.patchName}")
/**
* Check if the patch is constrained to packages.
*/
patch.compatiblePackages?.let { packages ->
packages.singleOrNull { it.name == packageName }?.let { `package` ->
/**
* Check if the package version matches.
* If experimental is true, version matching will be skipped.
*/
val matchesVersion = patchingArgs.experimental || `package`.versions.let {
it.isEmpty() || it.any { version -> version == packageVersion }
}
if (!matchesVersion) return@patch logger.warn(
"${patch.patchName} is incompatible with version $packageVersion. " +
"This patch is only compatible with version " +
packages.joinToString(";") { `package` ->
"${`package`.name}: ${`package`.versions.joinToString(", ")}"
}
)
} ?: return@patch logger.trace(
"${patch.patchName} is incompatible with $packageName. " +
"This patch is only compatible with " +
packages.joinToString(", ") { `package` -> `package`.name }
)
return@let
} ?: logger.trace("$formattedPatchName: No constraint on packages.")
/**
* Check if the patch is explicitly included.
*
* Cases:
* 1. --exclusive
* 2. --exclusive -i patch.name
*/
val exclusive = patchingArgs.exclusive
val explicitlyIncluded = patchingArgs.includedPatches.contains(formattedPatchName)
val implicitlyIncluded = !exclusive && patch.include // Case 3.
val exclusivelyIncluded = exclusive && explicitlyIncluded // Case 2.
val included = implicitlyIncluded || exclusivelyIncluded
if (!included) return@patch logger.info("${patch.patchName} excluded by default") // Case 1.
logger.trace("Adding $formattedPatchName")
add(patch)
}
}
}

View File

@ -1,29 +1,29 @@
package app.revanced.cli.command
import app.revanced.utils.adb.AdbManager
import picocli.CommandLine
import picocli.CommandLine.*
import picocli.CommandLine.Help.Visibility.ALWAYS
@CommandLine.Command(
@Command(
name = "uninstall",
description = ["Uninstall a patched package from the devices with the supplied ADB device serials"]
)
class UninstallCommand : Runnable {
@CommandLine.Parameters(
internal object UninstallCommand : Runnable {
@Parameters(
description = ["ADB device serials"],
arity = "1..*"
)
lateinit var deviceSerials: Array<String>
@CommandLine.Option(
@Option(
names = ["-p", "--package-name"],
description = ["Package name to uninstall"],
required = true
)
lateinit var packageName: String
@CommandLine.Option(
@Option(
names = ["-u", "--unmount"],
description = ["Uninstall by unmounting the patched package"],
showDefaultValue = ALWAYS
@ -33,12 +33,12 @@ class UninstallCommand : Runnable {
override fun run() = try {
deviceSerials.forEach {deviceSerial ->
if (unmount) {
AdbManager.RootAdbManager(deviceSerial, MainCommand.logger)
AdbManager.RootAdbManager(deviceSerial, logger)
} else {
AdbManager.UserAdbManager(deviceSerial, MainCommand.logger)
AdbManager.UserAdbManager(deviceSerial, logger)
}.uninstall(packageName)
}
} catch (e: AdbManager.DeviceNotFoundException) {
MainCommand.logger.error(e.toString())
logger.error(e.toString())
}
}

View File

@ -1,12 +1,12 @@
package app.revanced.cli.logging.impl
import app.revanced.cli.command.MainCommand
import app.revanced.cli.command.Main
import app.revanced.cli.logging.CliLogger
import java.util.logging.Logger
import java.util.logging.SimpleFormatter
internal class DefaultCliLogger(
private val logger: Logger = Logger.getLogger(MainCommand::class.java.name),
private val logger: Logger = Logger.getLogger(Main::class.java.name),
private val errorLogger: Logger = Logger.getLogger(logger.name + "Err")
) : CliLogger {

View File

@ -1,12 +0,0 @@
package app.revanced.cli.signing
import app.revanced.cli.command.MainCommand.logger
import app.revanced.utils.signing.Signer
import java.io.File
object Signing {
fun sign(alignedFile: File, signedOutput: File, signingOptions: SigningOptions) {
logger.info("Signing ${alignedFile.name} to ${signedOutput.name}")
Signer(signingOptions).signApk(alignedFile, signedOutput)
}
}

View File

@ -1,7 +0,0 @@
package app.revanced.cli.signing
data class SigningOptions(
val cn: String,
val password: String,
val keyStoreFilePath: String
)

View File

@ -0,0 +1 @@
version=${projectVersion}