add: signature checker and compatibility filters

Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
oSumAtrIX 2022-05-22 17:10:43 +02:00
parent 51d250491f
commit f297f7d1ef
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
5 changed files with 158 additions and 95 deletions

View File

@ -1,9 +1,11 @@
package app.revanced.cli package app.revanced.cli
import app.revanced.patch.Patches
import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Name
import app.revanced.patcher.extensions.findAnnotationRecursively import app.revanced.patcher.extensions.findAnnotationRecursively
import app.revanced.patcher.util.patch.PatchLoader
import app.revanced.utils.adb.Adb import app.revanced.utils.adb.Adb
import app.revanced.utils.patcher.addPatchesFiltered
import app.revanced.utils.signature.Signature
import picocli.CommandLine.* import picocli.CommandLine.*
import java.io.File import java.io.File
@ -35,6 +37,9 @@ internal object MainCommand : Runnable {
@Option(names = ["-l", "--list"], description = ["List patches only"]) @Option(names = ["-l", "--list"], description = ["List patches only"])
internal var listOnly: Boolean = false 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"]) @Option(names = ["-m", "--merge"], description = ["One or more dex file containers to merge"])
internal var mergeFiles = listOf<File>() internal var mergeFiles = listOf<File>()
@ -49,22 +54,30 @@ internal object MainCommand : Runnable {
override fun run() { override fun run() {
if (listOnly) { if (listOnly) {
for (patchBundle in patchBundles) for (it in Patches.load(patchBundle)) println( for (patchBundle in patchBundles)
"[available] ${ for (it in PatchLoader.loadFromFile(patchBundle))
it.javaClass.findAnnotationRecursively( println(
Name::class.java "[available] ${
)?.name ?: Name::class.java.name it.javaClass.findAnnotationRecursively(
}" Name::class.java
) )?.name ?: Name::class.java.name
}"
)
return return
} }
val outputFile = File(outputPath)
val patcher = app.revanced.patcher.Patcher( 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 var adb: Adb? = null
deploy?.let { deploy?.let {
adb = Adb( adb = Adb(

View File

@ -1,12 +1,9 @@
package app.revanced.cli package app.revanced.cli
import app.revanced.patch.Patches
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.utils.filesystem.FileSystemUtils 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 app.revanced.utils.signing.Signer
import java.io.File import java.io.File
@ -14,13 +11,11 @@ internal class Patcher {
internal companion object { internal companion object {
internal fun start(patcher: app.revanced.patcher.Patcher) { internal fun start(patcher: app.revanced.patcher.Patcher) {
// merge files like necessary integrations // merge files like necessary integrations
patcher.addFiles(MainCommand.mergeFiles) patcher.mergeFiles()
// add patches, but filter incompatible or excluded patches // add patches, but filter incompatible or excluded patches
patcher.addPatchesFiltered() patcher.addPatchesFiltered(includeFilter = MainCommand.includedPatches.isNotEmpty())
// apply patches // apply patches
for ((patch, result) in patcher.applyPatches()) { patcher.applyPatchesPrint()
println("[error: ${result.isFailure}] $patch")
}
// write output file // write output file
val outFile = File(MainCommand.outputPath) val outFile = File(MainCommand.outputPath)
@ -53,47 +48,6 @@ internal class Patcher {
println("[done]") 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@{ it ->
val patch = it.getDeclaredConstructor().newInstance()
val filterOutPatches = true
val compatibilityAnnotation = patch.javaClass.findAnnotationRecursively(Compatibility::class.java)
val patchName =
patch.javaClass.findAnnotationRecursively(Name::class.java)?.name ?: Name::class.java.name
if (checkInclude && !MainCommand.includedPatches.contains(patchName)) {
return@patch
}
if (filterOutPatches) {
if (compatibilityAnnotation == null || !(compatibilityAnnotation.compatiblePackages.any { packageMetadata ->
packageMetadata.name == packageName && packageMetadata.versions.any {
it == packageVersion
}
})) {
// TODO: misleading error message
println("[Skipped] $patchName: Incompatible with current package.")
return@patch
}
}
println("[loaded] $patchName")
includedPatches.add(patch)
}
this.addPatches(includedPatches)
}
}
} }
} }

View File

@ -1,35 +1,2 @@
package app.revanced.patch package app.revanced.patch
import app.revanced.patcher.patch.base.Patch
import java.io.File
import java.net.URLClassLoader
import java.util.jar.JarFile
internal object Patches {
/**
* This method loads patches from a given jar file containing [Patch]es
* @return the loaded patches represented as a list of [Patch] classes
*/
internal fun load(patchesJar: File) = buildList {
val jarFile = JarFile(patchesJar)
val classLoader = URLClassLoader(arrayOf(patchesJar.toURI().toURL()))
val entries = jarFile.entries()
while (entries.hasMoreElements()) {
val entry = entries.nextElement()
if (!entry.name.endsWith(".class") || entry.name.contains("$")) continue
val clazz = classLoader.loadClass(entry.realName.replace('/', '.').replace(".class", ""))
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) continue
@Suppress("UNCHECKED_CAST")
val patch = clazz as Class<Patch<*>>
// TODO: include declared classes from patch
this.add(patch)
}
}
}

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}"
}
}