Merge pull request #16 from revanced/annotations

fix: support annotated patches
This commit is contained in:
oSumAtrIX 2022-05-22 17:15:56 +02:00 committed by GitHub
commit cd58fe5869
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 177 additions and 89 deletions

View File

@ -25,11 +25,13 @@ repositories {
dependencies {
implementation(kotlin("stdlib"))
implementation("app.revanced:revanced-patcher:+")
implementation("app.revanced:revanced-patches:+")
implementation("info.picocli:picocli:+")
implementation("me.tongfei:progressbar:+")
implementation("com.github.li-wjohnson:jadb:master-SNAPSHOT") // using a fork instead.
implementation("org.bouncycastle:bcpkix-jdk15on:+")
implementation(kotlin("reflect"))
}
java {

View File

@ -1,7 +1,11 @@
package app.revanced.cli
import app.revanced.patch.Patches
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.extensions.findAnnotationRecursively
import app.revanced.patcher.util.patch.PatchLoader
import app.revanced.utils.adb.Adb
import app.revanced.utils.patcher.addPatchesFiltered
import app.revanced.utils.signature.Signature
import picocli.CommandLine.*
import java.io.File
@ -33,6 +37,9 @@ internal object MainCommand : Runnable {
@Option(names = ["-l", "--list"], description = ["List patches only"])
internal var listOnly: Boolean = false
@Option(names = ["-s", "--signature-checker"], description = ["Check signatures of all patches"])
internal var signatureCheck: Boolean = false
@Option(names = ["-m", "--merge"], description = ["One or more dex file containers to merge"])
internal var mergeFiles = listOf<File>()
@ -47,36 +54,44 @@ internal object MainCommand : Runnable {
override fun run() {
if (listOnly) {
patchBundles.forEach {
Patches.load(it).forEach {
println(it().metadata)
}
}
for (patchBundle in patchBundles)
for (it in PatchLoader.loadFromFile(patchBundle))
println(
"[available] ${
it.javaClass.findAnnotationRecursively(
Name::class.java
)?.name ?: Name::class.java.name
}"
)
return
}
val patcher = app.revanced.patcher.Patcher(
inputFile,
cacheDirectory,
patchResources
inputFile, cacheDirectory, patchResources
)
if (signatureCheck) {
patcher.addPatchesFiltered()
Signature.checkSignatures(patcher)
return
}
val outputFile = File(outputPath)
var adb: Adb? = null
deploy?.let {
adb = Adb(
outputFile, patcher.packageName, deploy!!
)
}
Patcher.start(patcher)
if (clean) {
File(cacheDirectory).deleteRecursively()
outputFile.delete()
}
val outputFile = File(outputPath)
deploy?.let {
Adb(
outputFile,
patcher.packageName,
deploy!!
).deploy()
}
if (clean) outputFile.delete()
adb?.deploy()
}
}

View File

@ -1,9 +1,9 @@
package app.revanced.cli
import app.revanced.patch.Patches
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.patch.base.Patch
import app.revanced.utils.filesystem.FileSystemUtils
import app.revanced.utils.patcher.addPatchesFiltered
import app.revanced.utils.patcher.applyPatchesPrint
import app.revanced.utils.patcher.mergeFiles
import app.revanced.utils.signing.Signer
import java.io.File
@ -11,15 +11,11 @@ internal class Patcher {
internal companion object {
internal fun start(patcher: app.revanced.patcher.Patcher) {
// merge files like necessary integrations
patcher.addFiles(MainCommand.mergeFiles)
patcher.mergeFiles()
// add patches, but filter incompatible or excluded patches
patcher.addPatchesFiltered()
patcher.addPatchesFiltered(includeFilter = MainCommand.includedPatches.isNotEmpty())
// apply patches
for ((meta, result) in patcher.applyPatches {
println("Applying $it.")
}) {
println("Applied ${meta.name}. The result was $result.")
}
patcher.applyPatchesPrint()
// write output file
val outFile = File(MainCommand.outputPath)
@ -34,7 +30,7 @@ internal class Patcher {
}
if (MainCommand.patchResources) {
for (file in File(MainCommand.cacheDirectory).resolve("build/").listFiles().first().listFiles()) {
for (file in File(MainCommand.cacheDirectory).resolve("build/").listFiles()?.first()?.listFiles()!!) {
if (!file.isDirectory) {
zipFileSystem.replaceFile(file.name, file.readBytes())
continue
@ -48,40 +44,10 @@ internal class Patcher {
// and sign the apk file
Signer.signApk(outFile)
println("[done]")
}
private fun app.revanced.patcher.Patcher.addPatchesFiltered() {
val packageName = this.packageName
val packageVersion = this.packageVersion
val checkInclude = MainCommand.includedPatches.isNotEmpty()
MainCommand.patchBundles.forEach { bundle ->
val includedPatches = mutableListOf<Patch<Data>>()
Patches.load(bundle).forEach patch@{
val patch = it()
val filterOutPatches = true
if (filterOutPatches && !patch.metadata.compatiblePackages.any { packageMetadata ->
packageMetadata.name == packageName && packageMetadata.versions.any {
it == packageVersion
}
}) {
println("Skipping ${patch.metadata.name} due to incompatibility with current package $packageName.")
return@patch
}
if (checkInclude && !MainCommand.includedPatches.contains(patch.metadata.shortName)) {
return@patch
}
println("Adding ${patch.metadata.name}.")
includedPatches.add(patch)
}
this.addPatches(includedPatches)
}
}
}
}

View File

@ -1,26 +1,2 @@
package app.revanced.patch
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.patch.base.Patch
import java.io.File
import java.net.URLClassLoader
internal object Patches {
/**
* This method loads patches from a given patch file
* @return the loaded patches represented as a list of functions returning instances of [Patch]
*/
internal fun load(patchesJar: File): List<() -> Patch<Data>> {
val url = patchesJar.toURI().toURL()
val classLoader = URLClassLoader(arrayOf(url))
val indexClass = classLoader.loadClass("app.revanced.patches.Index")
val index = indexClass.declaredFields.last()
index.isAccessible = true
@Suppress("UNCHECKED_CAST")
return index.get(null) as List<() -> Patch<Data>>
}
}

View File

@ -8,7 +8,7 @@ internal object Constants {
private const val COMMAND_CHMOD_MOUNT = "chmod +x"
internal const val COMMAND_PID_OF = "pidof -s"
internal const val COMMAND_CREATE_DIR = "mkdir -p"
internal const val COMMAND_LOGCAT = "logcat -c && logcat --pid=$($COMMAND_PID_OF $PLACEHOLDER)"
internal const val COMMAND_LOGCAT = "logcat -c && logcat | grep AndroidRuntime"
internal const val COMMAND_RESTART = "monkey -p $PLACEHOLDER 1 && kill ${'$'}($COMMAND_PID_OF $PLACEHOLDER)"
// default mount file name

View File

@ -0,0 +1,71 @@
package app.revanced.utils.patcher
import app.revanced.cli.MainCommand
import app.revanced.patcher.Patcher
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.extensions.findAnnotationRecursively
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.util.patch.PatchLoader
fun Patcher.addPatchesFiltered(
packageCompatibilityFilter: Boolean = true,
packageVersionCompatibilityFilter: Boolean = true,
includeFilter: Boolean = false
) {
val packageName = this.packageName
val packageVersion = this.packageVersion
MainCommand.patchBundles.forEach { bundle ->
val includedPatches = mutableListOf<Patch<Data>>()
PatchLoader.loadFromFile(bundle).forEach patch@{ p ->
val patch = p.getDeclaredConstructor().newInstance()
val compatibilityAnnotation = patch.javaClass.findAnnotationRecursively(Compatibility::class.java)
val patchName = patch.javaClass.findAnnotationRecursively(Name::class.java)?.name ?: Name::class.java.name
val prefix = "[skipped] $patchName"
if (includeFilter && !MainCommand.includedPatches.contains(patchName)) {
println(prefix)
return@patch
}
if (packageVersionCompatibilityFilter || packageCompatibilityFilter) {
if (compatibilityAnnotation == null) {
println("$prefix: Missing compatibility annotation.")
return@patch
}
for (compatiblePackage in compatibilityAnnotation.compatiblePackages) {
if (packageCompatibilityFilter && compatiblePackage.name != packageName) {
println("$prefix: Package name not matching ${compatiblePackage.name}.")
return@patch
}
if (!packageVersionCompatibilityFilter || compatiblePackage.versions.any { it == packageVersion }) continue
println("$prefix: Unsupported version.")
return@patch
}
}
includedPatches.add(patch)
println("[added] $patchName")
}
this.addPatches(includedPatches)
}
}
fun Patcher.applyPatchesPrint() {
for ((patch, result) in this.applyPatches()) {
println("[${if (result.isFailure) "error" else "success"}] $patch")
}
}
fun Patcher.mergeFiles() {
this.addFiles(MainCommand.mergeFiles)
}

View File

@ -0,0 +1,58 @@
package app.revanced.utils.signature
import app.revanced.patcher.Patcher
import app.revanced.patcher.extensions.findAnnotationRecursively
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
import org.jf.dexlib2.iface.Method
object Signature {
fun checkSignatures(patcher: Patcher) {
val failed = mutableListOf<String>()
for (signature in patcher.resolveSignatures()) {
val signatureClass = signature::class.java
val signatureName =
signatureClass.findAnnotationRecursively(app.revanced.patcher.annotation.Name::class.java)?.name
?: signatureClass.name
if (!signature.resolved) {
failed.add(signatureName)
continue
}
val method = signature.result!!.method
val matchingMethod =
signatureClass.findAnnotationRecursively(MatchingMethod::class.java) ?: MatchingMethod()
println(
"""
[Signature] $signatureName
[Method] ${matchingMethod.definingClass}->${matchingMethod.name}
[Match] ${method.definingClass}->${method.toStr()}
""".trimIndent()
)
signatureClass.findAnnotationRecursively(FuzzyPatternScanMethod::class.java)?.let {
val warnings = signature.result!!.scanResult.warnings!!
println(
"""
[Warnings: ${warnings.count()}]
${warnings.joinToString(separator = "\n") { warning -> "${warning.instructionIndex} / ${warning.patternIndex}: ${warning.wrongOpcode} (expected: ${warning.correctOpcode})" }}
""".trimIndent()
)
}
}
println(
"""
${"=".repeat(50)}
[Failed signatures: ${failed.size}]
${failed.joinToString(separator = "\n") { it }}
""".trimIndent()
)
}
private fun Method.toStr(): String {
return "${this.name}(${this.parameterTypes.joinToString("")})${this.returnType}"
}
}