mirror of
https://github.com/revanced/revanced-patcher.git
synced 2025-05-18 20:07:04 +02:00
feat: Add warnings for Fuzzy resolver
This commit is contained in:
parent
9889ec9d03
commit
715a2ad025
@ -4,6 +4,7 @@ import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchMetadata
|
||||
import app.revanced.patcher.patch.PatchResultSuccess
|
||||
import app.revanced.patcher.proxy.ClassProxy
|
||||
import app.revanced.patcher.signature.MethodSignature
|
||||
import app.revanced.patcher.signature.resolver.SignatureResolver
|
||||
import app.revanced.patcher.util.ListBackedSet
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
@ -116,22 +117,37 @@ class Patcher(
|
||||
patcherData.patches.addAll(patches)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves all signatures.
|
||||
* @throws IllegalStateException if no patches were added or signatures have already been resolved.
|
||||
*/
|
||||
fun resolveSignatures(): List<MethodSignature> {
|
||||
if (signaturesResolved) {
|
||||
throw IllegalStateException("Signatures have already been resolved.")
|
||||
}
|
||||
val signatures = patcherData.patches.flatMap { it.signatures }
|
||||
if (signatures.isEmpty()) {
|
||||
throw IllegalStateException("No signatures found to resolve.")
|
||||
}
|
||||
SignatureResolver(patcherData.classes, signatures).resolve()
|
||||
signaturesResolved = true
|
||||
return signatures
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply patches loaded into the patcher.
|
||||
* @param stopOnError If true, the patches will stop on the first error.
|
||||
* @return A map of [PatchResultSuccess]. If the [Patch] was successfully applied,
|
||||
* [PatchResultSuccess] will always be returned to the wrapping Result object.
|
||||
* If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object.
|
||||
* @throws IllegalStateException if signatures have not been resolved.
|
||||
*/
|
||||
fun applyPatches(
|
||||
stopOnError: Boolean = false,
|
||||
callback: (String) -> Unit = {}
|
||||
): Map<PatchMetadata, Result<PatchResultSuccess>> {
|
||||
|
||||
if (!signaturesResolved) {
|
||||
val signatures = patcherData.patches.flatMap { it.signatures }
|
||||
SignatureResolver(patcherData.classes, signatures).resolve()
|
||||
signaturesResolved = true
|
||||
throw IllegalStateException("Signatures not yet resolved, please invoke Patcher#resolveSignatures() first.")
|
||||
}
|
||||
return buildMap {
|
||||
for (patch in patcherData.patches) {
|
||||
|
@ -5,14 +5,14 @@ import org.jf.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* Represents the [MethodSignature] for a method.
|
||||
* @param methodSignatureMetadata Metadata for this [MethodSignature].
|
||||
* @param metadata Metadata for this [MethodSignature].
|
||||
* @param returnType The return type of the method.
|
||||
* @param accessFlags The access flags of the method.
|
||||
* @param methodParameters The parameters of the method.
|
||||
* @param opcodes The list of opcodes of the method.
|
||||
*/
|
||||
class MethodSignature(
|
||||
val methodSignatureMetadata: MethodSignatureMetadata,
|
||||
val metadata: MethodSignatureMetadata,
|
||||
internal val returnType: String?,
|
||||
internal val accessFlags: Int?,
|
||||
internal val methodParameters: Iterable<String>?,
|
||||
@ -24,9 +24,13 @@ class MethodSignature(
|
||||
var result: SignatureResolverResult? = null // TODO: figure out how to get rid of nullable
|
||||
get() {
|
||||
return field ?: throw MethodNotFoundException(
|
||||
"Could not resolve required signature ${methodSignatureMetadata.name}"
|
||||
"Could not resolve required signature ${metadata.name}"
|
||||
)
|
||||
}
|
||||
val resolved: Boolean
|
||||
get() {
|
||||
return result != null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,5 +74,29 @@ interface PatternScanMethod {
|
||||
/**
|
||||
* When comparing the signature, if [threshold] or more of the opcodes do not match, skip.
|
||||
*/
|
||||
class Fuzzy(internal val threshold: Int) : PatternScanMethod
|
||||
class Fuzzy(internal val threshold: Int) : PatternScanMethod {
|
||||
/**
|
||||
* A list of warnings the resolver found.
|
||||
*
|
||||
* This list will be allocated when the signature has been found.
|
||||
* Meaning, if the signature was not found,
|
||||
* or the signature was not yet resolved,
|
||||
* the list will be null.
|
||||
*/
|
||||
lateinit var warnings: List<Warning>
|
||||
|
||||
/**
|
||||
* Represents a resolver warning.
|
||||
* @param expected The opcode the signature expected it to be.
|
||||
* @param actual The actual opcode it was. Always different from [expected].
|
||||
* @param expectedIndex The index for [expected]. Relative to the instruction list.
|
||||
* @param actualIndex The index for [actual]. Relative to the pattern list from the signature.
|
||||
*/
|
||||
data class Warning(
|
||||
val expected: Opcode,
|
||||
val actual: Opcode,
|
||||
val expectedIndex: Int,
|
||||
val actualIndex: Int,
|
||||
)
|
||||
}
|
||||
}
|
@ -83,24 +83,26 @@ internal class SignatureResolver(
|
||||
val count = instructions.count()
|
||||
val pattern = signature.opcodes!!
|
||||
val size = pattern.count()
|
||||
var threshold = 0
|
||||
if (signature.methodSignatureMetadata.patternScanMethod is PatternScanMethod.Fuzzy) {
|
||||
threshold = signature.methodSignatureMetadata.patternScanMethod.threshold
|
||||
}
|
||||
val method = signature.metadata.patternScanMethod
|
||||
val threshold = if (method is PatternScanMethod.Fuzzy)
|
||||
method.threshold else 0
|
||||
|
||||
for (instructionIndex in 0 until count) {
|
||||
var patternIndex = 0
|
||||
var currentThreshold = threshold
|
||||
while (instructionIndex + patternIndex < count) {
|
||||
if (
|
||||
instructions.elementAt(
|
||||
instructionIndex + patternIndex
|
||||
).opcode != pattern.elementAt(patternIndex)
|
||||
&& currentThreshold-- == 0
|
||||
) break
|
||||
val originalOpcode = instructions.elementAt(instructionIndex + patternIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
if (originalOpcode != patternOpcode && currentThreshold-- == 0) break
|
||||
if (++patternIndex < size) continue
|
||||
|
||||
return PatternScanResult(instructionIndex, instructionIndex + patternIndex)
|
||||
val result = PatternScanResult(instructionIndex, instructionIndex + patternIndex)
|
||||
if (method is PatternScanMethod.Fuzzy) {
|
||||
method.warnings = generateWarnings(
|
||||
signature, instructions, result
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,6 +115,24 @@ internal class SignatureResolver(
|
||||
): Boolean {
|
||||
return signature.count() != original.size || !(signature.all { a -> original.any { it.startsWith(a) } })
|
||||
}
|
||||
|
||||
private fun generateWarnings(
|
||||
signature: MethodSignature,
|
||||
instructions: Iterable<Instruction>,
|
||||
scanResult: PatternScanResult,
|
||||
) = buildList {
|
||||
val pattern = signature.opcodes!!
|
||||
for ((patternIndex, originalIndex) in (scanResult.startIndex until scanResult.endIndex).withIndex()) {
|
||||
val originalOpcode = instructions.elementAt(originalIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
if (originalOpcode != patternOpcode) {
|
||||
this.add(PatternScanMethod.Fuzzy.Warning(
|
||||
originalOpcode, patternOpcode,
|
||||
originalIndex, patternIndex
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
37
src/test/kotlin/app/revanced/patcher/PatcherTest.kt
Normal file
37
src/test/kotlin/app/revanced/patcher/PatcherTest.kt
Normal file
@ -0,0 +1,37 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.signature.PatternScanMethod
|
||||
import app.revanced.patcher.usage.ExamplePatch
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.File
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal class PatcherTest {
|
||||
@Test
|
||||
fun testPatcher() {
|
||||
val patcher = Patcher(File(PatcherTest::class.java.getResource("/test1.dex")!!.toURI()))
|
||||
patcher.addPatches(listOf(ExamplePatch()))
|
||||
for (signature in patcher.resolveSignatures()) {
|
||||
if (!signature.resolved) {
|
||||
throw Exception("Signature ${signature.metadata.name} was not resolved!")
|
||||
}
|
||||
val patternScanMethod = signature.metadata.patternScanMethod
|
||||
if (patternScanMethod is PatternScanMethod.Fuzzy) {
|
||||
val warnings = patternScanMethod.warnings
|
||||
println("Signature ${signature.metadata.name} had ${warnings.size} warnings!")
|
||||
for (warning in warnings) {
|
||||
println(warning.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
for ((metadata, result) in patcher.applyPatches()) {
|
||||
if (result.isFailure) {
|
||||
throw Exception("Patch ${metadata.shortName} failed", result.exceptionOrNull()!!)
|
||||
} else {
|
||||
println("Patch ${metadata.shortName} applied successfully!")
|
||||
}
|
||||
}
|
||||
val out = patcher.save()
|
||||
assertTrue(out.isNotEmpty(), "Expected the output of Patcher#save() to not be empty.")
|
||||
}
|
||||
}
|
@ -31,7 +31,6 @@ import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
||||
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
|
||||
import org.jf.dexlib2.util.Preconditions
|
||||
|
||||
@Suppress("unused") // TODO: Add tests
|
||||
class ExamplePatch : Patch(
|
||||
metadata = PatchMetadata(
|
||||
shortName = "example-patch",
|
||||
@ -48,7 +47,7 @@ class ExamplePatch : Patch(
|
||||
definingClass = "TestClass",
|
||||
name = "main",
|
||||
),
|
||||
patternScanMethod = PatternScanMethod.Fuzzy(2),
|
||||
patternScanMethod = PatternScanMethod.Fuzzy(1),
|
||||
compatiblePackages = listOf("com.example.examplePackage"),
|
||||
description = "The main method of TestClass",
|
||||
version = "1.0.0"
|
||||
@ -67,7 +66,6 @@ class ExamplePatch : Patch(
|
||||
// This function will be executed by the patcher.
|
||||
// You can treat it as a constructor
|
||||
override fun execute(patcherData: PatcherData): PatchResult {
|
||||
|
||||
// Get the resolved method for the signature from the resolver cache
|
||||
val result = signatures.first().result!!
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user