diff --git a/src/main/kotlin/app/revanced/patcher/Patcher.kt b/src/main/kotlin/app/revanced/patcher/Patcher.kt index 13a16d0..f2439c9 100644 --- a/src/main/kotlin/app/revanced/patcher/Patcher.kt +++ b/src/main/kotlin/app/revanced/patcher/Patcher.kt @@ -8,7 +8,7 @@ import app.revanced.patcher.extensions.PatchExtensions.deprecated import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.sincePatcherVersion import app.revanced.patcher.extensions.nullOutputStream -import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve +import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils.resolve import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResultError diff --git a/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt b/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt index 2049511..f11d8d1 100644 --- a/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt +++ b/src/main/kotlin/app/revanced/patcher/fingerprint/method/impl/MethodFingerprint.kt @@ -1,19 +1,13 @@ package app.revanced.patcher.fingerprint.method.impl import app.revanced.patcher.data.impl.BytecodeData -import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod -import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold -import app.revanced.patcher.extensions.parametersEqual import app.revanced.patcher.extensions.softCompareTo import app.revanced.patcher.fingerprint.Fingerprint -import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils import app.revanced.patcher.util.proxy.ClassProxy import org.jf.dexlib2.Opcode import org.jf.dexlib2.iface.ClassDef import org.jf.dexlib2.iface.Method -import org.jf.dexlib2.iface.instruction.Instruction -import org.jf.dexlib2.iface.instruction.ReferenceInstruction -import org.jf.dexlib2.iface.reference.StringReference /** * Represents the [MethodFingerprint] for a method. @@ -37,252 +31,27 @@ abstract class MethodFingerprint( * The result of the [MethodFingerprint] the [Method]. */ var result: MethodFingerprintResult? = null - - companion object { - /** - * Resolve a list of [MethodFingerprint] against a list of [ClassDef]. - * @param context The classes on which to resolve the [MethodFingerprint]. - * @param forData The [BytecodeData] to host proxies. - * @return True if the resolution was successful, false otherwise. - */ - fun Iterable.resolve(forData: BytecodeData, context: Iterable) { - for (fingerprint in this) // For each fingerprint - classes@ for (classDef in context) // search through all classes for the fingerprint - if (fingerprint.resolve(forData, classDef)) - break@classes // if the resolution succeeded, continue with the next fingerprint - } - - /** - * Resolve a [MethodFingerprint] against a [ClassDef]. - * @param context The class on which to resolve the [MethodFingerprint]. - * @param forData The [BytecodeData] to host proxies. - * @return True if the resolution was successful, false otherwise. - */ - fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean { - for (method in context.methods) - if (this.resolve(forData, method, context)) - return true - return false - } - - /** - * Resolve a [MethodFingerprint] against a [Method]. - * @param context The context on which to resolve the [MethodFingerprint]. - * @param classDef The class of the matching [Method]. - * @param forData The [BytecodeData] to host proxies. - * @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise. - */ - fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean { - val methodFingerprint = this - - if (methodFingerprint.result != null) return true - - if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType)) - return false - - if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags) - return false - - - if (methodFingerprint.parameters != null && !parametersEqual( - methodFingerprint.parameters, // TODO: parseParameters() - context.parameterTypes - ) - ) return false - - @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") - if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context)) - return false - - val stringsScanResult: StringsScanResult? = - if (methodFingerprint.strings != null) { - StringsScanResult( - buildList { - val implementation = context.implementation ?: return false - - val stringsList = methodFingerprint.strings.toMutableList() - - implementation.instructions.forEach { instruction -> - if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEach - - val string = ((instruction as ReferenceInstruction).reference as StringReference).string - val index = stringsList.indexOfFirst { it == string } - if (index == -1) return@forEach - - add( - StringMatch( - string, - index - ) - ) - stringsList.removeAt(index) - } - - if (stringsList.isNotEmpty()) return false - } - ) - } else null - - val patternScanResult = if (methodFingerprint.opcodes != null) { - context.implementation?.instructions ?: return false - - context.patternScan(methodFingerprint) ?: return false - } else null - - methodFingerprint.result = MethodFingerprintResult( - context, - classDef, - MethodFingerprintResult.MethodFingerprintScanResult( - patternScanResult, - stringsScanResult - ), - forData - ) - - return true - } - - private fun Method.patternScan( - fingerprint: MethodFingerprint - ): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? { - val instructions = this.implementation!!.instructions - val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold - - val pattern = fingerprint.opcodes!! - val instructionLength = instructions.count() - val patternLength = pattern.count() - - for (index in 0 until instructionLength) { - var patternIndex = 0 - var threshold = fingerprintFuzzyPatternScanThreshold - - while (index + patternIndex < instructionLength) { - val originalOpcode = instructions.elementAt(index + patternIndex).opcode - val patternOpcode = pattern.elementAt(patternIndex) - - if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) { - // reaching maximum threshold (0) means, - // the pattern does not match to the current instructions - if (threshold-- == 0) break - } - - if (patternIndex < patternLength - 1) { - // if the entire pattern has not been scanned yet - // continue the scan - patternIndex++ - continue - } - // the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod - val result = - MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult( - index, - index + patternIndex - ) - if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result - result.warnings = result.createWarnings(pattern, instructions) - - return result - } - } - - return null - } - - private fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.createWarnings( - pattern: Iterable, instructions: Iterable - ) = buildList { - for ((patternIndex, instructionIndex) in (this@createWarnings.startIndex until this@createWarnings.endIndex).withIndex()) { - val originalOpcode = instructions.elementAt(instructionIndex).opcode - val patternOpcode = pattern.elementAt(patternIndex) - - if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue - - this.add( - MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.Warning( - originalOpcode, - patternOpcode, - instructionIndex, - patternIndex - ) - ) - } - } - } } -private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch -private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult - /** - * Represents the result of a [MethodFingerprintResult]. + * Represents the result of a [MethodFingerprintUtils]. * @param method The matching method. * @param classDef The [ClassDef] that contains the matching [method]. - * @param scanResult The result of scanning for the [MethodFingerprint]. + * @param patternScanResult Opcodes pattern scan result. * @param data The [BytecodeData] this [MethodFingerprintResult] is attached to, to create proxies. */ data class MethodFingerprintResult( val method: Method, val classDef: ClassDef, - val scanResult: MethodFingerprintScanResult, + val patternScanResult: PatternScanResult?, internal val data: BytecodeData ) { - - /** - * The result of scanning on the [MethodFingerprint]. - * @param patternScanResult The result of the pattern scan. - * @param stringsScanResult The result of the string scan. - */ - data class MethodFingerprintScanResult( - val patternScanResult: PatternScanResult?, - val stringsScanResult: StringsScanResult? - ) { - /** - * The result of scanning strings on the [MethodFingerprint]. - * @param matches The list of strings that were matched. - */ - data class StringsScanResult(val matches: List){ - /** - * Represents a match for a string at an index. - * @param string The string that was matched. - * @param index The index of the string. - */ - data class StringMatch(val string: String, val index: Int) - } - - /** - * The result of a pattern scan. - * @param startIndex The start index of the instructions where to which this pattern matches. - * @param endIndex The end index of the instructions where to which this pattern matches. - * @param warnings A list of warnings considering this [PatternScanResult]. - */ - data class PatternScanResult( - val startIndex: Int, - val endIndex: Int, - var warnings: List? = null - ) { - /** - * Represents warnings of the pattern scan. - * @param correctOpcode The opcode the instruction list has. - * @param wrongOpcode The opcode the pattern list of the signature currently has. - * @param instructionIndex The index of the opcode relative to the instruction list. - * @param patternIndex The index of the opcode relative to the pattern list from the signature. - */ - data class Warning( - val correctOpcode: Opcode, - val wrongOpcode: Opcode, - val instructionIndex: Int, - val patternIndex: Int, - ) - } - } - /** * Returns a mutable clone of [classDef] * * Please note, this method allocates a [ClassProxy]. * Use [classDef] where possible. */ - @Suppress("MemberVisibilityCanBePrivate") val mutableClass by lazy { data.proxy(classDef).resolve() } /** @@ -296,4 +65,30 @@ data class MethodFingerprintResult( it.softCompareTo(this.method) } } +} + +/** + * The result of a pattern scan. + * @param startIndex The start index of the instructions where to which this pattern matches. + * @param endIndex The end index of the instructions where to which this pattern matches. + * @param warnings A list of warnings considering this [PatternScanResult]. + */ +data class PatternScanResult( + val startIndex: Int, + val endIndex: Int, + var warnings: List? = null +) { + /** + * Represents warnings of the pattern scan. + * @param correctOpcode The opcode the instruction list has. + * @param wrongOpcode The opcode the pattern list of the signature currently has. + * @param instructionIndex The index of the opcode relative to the instruction list. + * @param patternIndex The index of the opcode relative to the pattern list from the signature. + */ + data class Warning( + val correctOpcode: Opcode, + val wrongOpcode: Opcode, + val instructionIndex: Int, + val patternIndex: Int, + ) } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patcher/fingerprint/method/utils/MethodFingerprintUtils.kt b/src/main/kotlin/app/revanced/patcher/fingerprint/method/utils/MethodFingerprintUtils.kt new file mode 100644 index 0000000..5e1aaa5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patcher/fingerprint/method/utils/MethodFingerprintUtils.kt @@ -0,0 +1,160 @@ +package app.revanced.patcher.fingerprint.method.utils + +import app.revanced.patcher.data.impl.BytecodeData +import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod +import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold +import app.revanced.patcher.extensions.parametersEqual +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult +import app.revanced.patcher.fingerprint.method.impl.PatternScanResult +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.ClassDef +import org.jf.dexlib2.iface.Method +import org.jf.dexlib2.iface.instruction.Instruction +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.StringReference + +/** + * Utility class for [MethodFingerprint] + */ +object MethodFingerprintUtils { + /** + * Resolve a list of [MethodFingerprint] against a list of [ClassDef]. + * @param context The classes on which to resolve the [MethodFingerprint]. + * @param forData The [BytecodeData] to host proxies. + * @return True if the resolution was successful, false otherwise. + */ + fun Iterable.resolve(forData: BytecodeData, context: Iterable) { + for (fingerprint in this) // For each fingerprint + classes@ for (classDef in context) // search through all classes for the fingerprint + if (fingerprint.resolve(forData, classDef)) + break@classes // if the resolution succeeded, continue with the next fingerprint + } + + /** + * Resolve a [MethodFingerprint] against a [ClassDef]. + * @param context The class on which to resolve the [MethodFingerprint]. + * @param forData The [BytecodeData] to host proxies. + * @return True if the resolution was successful, false otherwise. + */ + fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean { + for (method in context.methods) + if (this.resolve(forData, method, context)) + return true + return false + } + + /** + * Resolve a [MethodFingerprint] against a [Method]. + * @param context The context on which to resolve the [MethodFingerprint]. + * @param classDef The class of the matching [Method]. + * @param forData The [BytecodeData] to host proxies. + * @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise. + */ + fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean { + val methodFingerprint = this + + if (methodFingerprint.result != null) return true + + if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType)) + return false + + if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags) + return false + + + if (methodFingerprint.parameters != null && !parametersEqual( + methodFingerprint.parameters, // TODO: parseParameters() + context.parameterTypes + ) + ) return false + + if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context)) + return false + + if (methodFingerprint.strings != null) { + val implementation = context.implementation ?: return false + + val stringsList = methodFingerprint.strings.toMutableList() + + implementation.instructions.forEach { instruction -> + if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEach + + val string = ((instruction as ReferenceInstruction).reference as StringReference).string + val index = stringsList.indexOfFirst { it == string } + if (index != -1) stringsList.removeAt(index) + } + + if (stringsList.isNotEmpty()) return false + } + + val patternScanResult = if (methodFingerprint.opcodes != null) { + context.implementation?.instructions ?: return false + + context.patternScan(methodFingerprint) ?: return false + } else null + + methodFingerprint.result = MethodFingerprintResult(context, classDef, patternScanResult, forData) + + return true + } + + private fun Method.patternScan( + fingerprint: MethodFingerprint + ): PatternScanResult? { + val instructions = this.implementation!!.instructions + val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold + + val pattern = fingerprint.opcodes!! + val instructionLength = instructions.count() + val patternLength = pattern.count() + + for (index in 0 until instructionLength) { + var patternIndex = 0 + var threshold = fingerprintFuzzyPatternScanThreshold + + while (index + patternIndex < instructionLength) { + val originalOpcode = instructions.elementAt(index + patternIndex).opcode + val patternOpcode = pattern.elementAt(patternIndex) + + if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) { + // reaching maximum threshold (0) means, + // the pattern does not match to the current instructions + if (threshold-- == 0) break + } + + if (patternIndex < patternLength - 1) { + // if the entire pattern has not been scanned yet + // continue the scan + patternIndex++ + continue + } + // the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod + val result = PatternScanResult(index, index + patternIndex) + if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result + result.warnings = result.createWarnings(pattern, instructions) + + return result + } + } + + return null + } +} + +private fun PatternScanResult.createWarnings( + pattern: Iterable, instructions: Iterable +) = buildList { + for ((patternIndex, instructionIndex) in (this@createWarnings.startIndex until this@createWarnings.endIndex).withIndex()) { + val originalOpcode = instructions.elementAt(instructionIndex).opcode + val patternOpcode = pattern.elementAt(patternIndex) + + if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue + + this.add(PatternScanResult.Warning(originalOpcode, patternOpcode, instructionIndex, patternIndex)) + } +} + +private operator fun ClassDef.component1() = this +private operator fun ClassDef.component2() = this.methods \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt index 2066cac..587e3c3 100644 --- a/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt +++ b/src/test/kotlin/app/revanced/patcher/usage/bytecode/ExampleBytecodePatch.kt @@ -58,7 +58,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) { // Let's modify it, so it prints "Hello, ReVanced! Editing bytecode." // Get the start index of our opcode pattern. // This will be the index of the instruction with the opcode CONST_STRING. - val startIndex = result.scanResult.patternScanResult!!.startIndex + val startIndex = result.patternScanResult!!.startIndex implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")