From fd5c878ceed2307cfc99a337dc13385867276cbd Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 21 Sep 2022 16:15:34 +0200 Subject: [PATCH] fix!: revert reverting changes BREAKING-CHANGE: Imports will have to be updated from `MethodFingerprintUtils` to `MethodFingerprint.Companion`. --- .../kotlin/app/revanced/patcher/Patcher.kt | 2 +- .../method/impl/MethodFingerprint.kt | 265 ++++++++++++++++-- .../method/utils/MethodFingerprintUtils.kt | 160 ----------- .../usage/bytecode/ExampleBytecodePatch.kt | 2 +- 4 files changed, 237 insertions(+), 192 deletions(-) delete mode 100644 src/main/kotlin/app/revanced/patcher/fingerprint/method/utils/MethodFingerprintUtils.kt diff --git a/src/main/kotlin/app/revanced/patcher/Patcher.kt b/src/main/kotlin/app/revanced/patcher/Patcher.kt index f2439c9..13a16d0 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.utils.MethodFingerprintUtils.resolve +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.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 f11d8d1..2049511 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,13 +1,19 @@ 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.utils.MethodFingerprintUtils +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod 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. @@ -31,27 +37,252 @@ 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 [MethodFingerprintUtils]. + * Represents the result of a [MethodFingerprintResult]. * @param method The matching method. * @param classDef The [ClassDef] that contains the matching [method]. - * @param patternScanResult Opcodes pattern scan result. + * @param scanResult The result of scanning for the [MethodFingerprint]. * @param data The [BytecodeData] this [MethodFingerprintResult] is attached to, to create proxies. */ data class MethodFingerprintResult( val method: Method, val classDef: ClassDef, - val patternScanResult: PatternScanResult?, + val scanResult: MethodFingerprintScanResult, 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() } /** @@ -65,30 +296,4 @@ 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 deleted file mode 100644 index 5e1aaa5..0000000 --- a/src/main/kotlin/app/revanced/patcher/fingerprint/method/utils/MethodFingerprintUtils.kt +++ /dev/null @@ -1,160 +0,0 @@ -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 587e3c3..2066cac 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.patternScanResult!!.startIndex + val startIndex = result.scanResult.patternScanResult!!.startIndex implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")