mirror of
https://github.com/revanced/revanced-cli.git
synced 2024-11-04 02:48:53 +01:00
add: signature checker and compatibility filters
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
parent
51d250491f
commit
f297f7d1ef
@ -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(
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
71
src/main/kotlin/app/revanced/utils/patcher/Patcher.kt
Normal file
71
src/main/kotlin/app/revanced/utils/patcher/Patcher.kt
Normal 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)
|
||||||
|
}
|
58
src/main/kotlin/app/revanced/utils/signature/Signature.kt
Normal file
58
src/main/kotlin/app/revanced/utils/signature/Signature.kt
Normal 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}"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user