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.PatchMetadata
|
||||||
import app.revanced.patcher.patch.PatchResultSuccess
|
import app.revanced.patcher.patch.PatchResultSuccess
|
||||||
import app.revanced.patcher.proxy.ClassProxy
|
import app.revanced.patcher.proxy.ClassProxy
|
||||||
|
import app.revanced.patcher.signature.MethodSignature
|
||||||
import app.revanced.patcher.signature.resolver.SignatureResolver
|
import app.revanced.patcher.signature.resolver.SignatureResolver
|
||||||
import app.revanced.patcher.util.ListBackedSet
|
import app.revanced.patcher.util.ListBackedSet
|
||||||
import lanchon.multidexlib2.BasicDexFileNamer
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
@ -116,22 +117,37 @@ class Patcher(
|
|||||||
patcherData.patches.addAll(patches)
|
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.
|
* Apply patches loaded into the patcher.
|
||||||
* @param stopOnError If true, the patches will stop on the first error.
|
* @param stopOnError If true, the patches will stop on the first error.
|
||||||
* @return A map of [PatchResultSuccess]. If the [Patch] was successfully applied,
|
* @return A map of [PatchResultSuccess]. If the [Patch] was successfully applied,
|
||||||
* [PatchResultSuccess] will always be returned to the wrapping Result object.
|
* [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.
|
* 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(
|
fun applyPatches(
|
||||||
stopOnError: Boolean = false,
|
stopOnError: Boolean = false,
|
||||||
callback: (String) -> Unit = {}
|
callback: (String) -> Unit = {}
|
||||||
): Map<PatchMetadata, Result<PatchResultSuccess>> {
|
): Map<PatchMetadata, Result<PatchResultSuccess>> {
|
||||||
|
|
||||||
if (!signaturesResolved) {
|
if (!signaturesResolved) {
|
||||||
val signatures = patcherData.patches.flatMap { it.signatures }
|
throw IllegalStateException("Signatures not yet resolved, please invoke Patcher#resolveSignatures() first.")
|
||||||
SignatureResolver(patcherData.classes, signatures).resolve()
|
|
||||||
signaturesResolved = true
|
|
||||||
}
|
}
|
||||||
return buildMap {
|
return buildMap {
|
||||||
for (patch in patcherData.patches) {
|
for (patch in patcherData.patches) {
|
||||||
|
@ -5,14 +5,14 @@ import org.jf.dexlib2.Opcode
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the [MethodSignature] for a method.
|
* 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 returnType The return type of the method.
|
||||||
* @param accessFlags The access flags of the method.
|
* @param accessFlags The access flags of the method.
|
||||||
* @param methodParameters The parameters of the method.
|
* @param methodParameters The parameters of the method.
|
||||||
* @param opcodes The list of opcodes of the method.
|
* @param opcodes The list of opcodes of the method.
|
||||||
*/
|
*/
|
||||||
class MethodSignature(
|
class MethodSignature(
|
||||||
val methodSignatureMetadata: MethodSignatureMetadata,
|
val metadata: MethodSignatureMetadata,
|
||||||
internal val returnType: String?,
|
internal val returnType: String?,
|
||||||
internal val accessFlags: Int?,
|
internal val accessFlags: Int?,
|
||||||
internal val methodParameters: Iterable<String>?,
|
internal val methodParameters: Iterable<String>?,
|
||||||
@ -24,9 +24,13 @@ class MethodSignature(
|
|||||||
var result: SignatureResolverResult? = null // TODO: figure out how to get rid of nullable
|
var result: SignatureResolverResult? = null // TODO: figure out how to get rid of nullable
|
||||||
get() {
|
get() {
|
||||||
return field ?: throw MethodNotFoundException(
|
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.
|
* 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 count = instructions.count()
|
||||||
val pattern = signature.opcodes!!
|
val pattern = signature.opcodes!!
|
||||||
val size = pattern.count()
|
val size = pattern.count()
|
||||||
var threshold = 0
|
val method = signature.metadata.patternScanMethod
|
||||||
if (signature.methodSignatureMetadata.patternScanMethod is PatternScanMethod.Fuzzy) {
|
val threshold = if (method is PatternScanMethod.Fuzzy)
|
||||||
threshold = signature.methodSignatureMetadata.patternScanMethod.threshold
|
method.threshold else 0
|
||||||
}
|
|
||||||
|
|
||||||
for (instructionIndex in 0 until count) {
|
for (instructionIndex in 0 until count) {
|
||||||
var patternIndex = 0
|
var patternIndex = 0
|
||||||
var currentThreshold = threshold
|
var currentThreshold = threshold
|
||||||
while (instructionIndex + patternIndex < count) {
|
while (instructionIndex + patternIndex < count) {
|
||||||
if (
|
val originalOpcode = instructions.elementAt(instructionIndex + patternIndex).opcode
|
||||||
instructions.elementAt(
|
val patternOpcode = pattern.elementAt(patternIndex)
|
||||||
instructionIndex + patternIndex
|
if (originalOpcode != patternOpcode && currentThreshold-- == 0) break
|
||||||
).opcode != pattern.elementAt(patternIndex)
|
|
||||||
&& currentThreshold-- == 0
|
|
||||||
) break
|
|
||||||
if (++patternIndex < size) continue
|
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 {
|
): Boolean {
|
||||||
return signature.count() != original.size || !(signature.all { a -> original.any { it.startsWith(a) } })
|
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.immutable.value.ImmutableFieldEncodedValue
|
||||||
import org.jf.dexlib2.util.Preconditions
|
import org.jf.dexlib2.util.Preconditions
|
||||||
|
|
||||||
@Suppress("unused") // TODO: Add tests
|
|
||||||
class ExamplePatch : Patch(
|
class ExamplePatch : Patch(
|
||||||
metadata = PatchMetadata(
|
metadata = PatchMetadata(
|
||||||
shortName = "example-patch",
|
shortName = "example-patch",
|
||||||
@ -48,7 +47,7 @@ class ExamplePatch : Patch(
|
|||||||
definingClass = "TestClass",
|
definingClass = "TestClass",
|
||||||
name = "main",
|
name = "main",
|
||||||
),
|
),
|
||||||
patternScanMethod = PatternScanMethod.Fuzzy(2),
|
patternScanMethod = PatternScanMethod.Fuzzy(1),
|
||||||
compatiblePackages = listOf("com.example.examplePackage"),
|
compatiblePackages = listOf("com.example.examplePackage"),
|
||||||
description = "The main method of TestClass",
|
description = "The main method of TestClass",
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -67,7 +66,6 @@ class ExamplePatch : Patch(
|
|||||||
// This function will be executed by the patcher.
|
// This function will be executed by the patcher.
|
||||||
// You can treat it as a constructor
|
// You can treat it as a constructor
|
||||||
override fun execute(patcherData: PatcherData): PatchResult {
|
override fun execute(patcherData: PatcherData): PatchResult {
|
||||||
|
|
||||||
// Get the resolved method for the signature from the resolver cache
|
// Get the resolved method for the signature from the resolver cache
|
||||||
val result = signatures.first().result!!
|
val result = signatures.first().result!!
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user