mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-11 13:56:16 +00:00
fix: Couple more matching fixes
This commit is contained in:
@@ -4,7 +4,7 @@ apktool-lib = "2.10.1.1"
|
||||
binary-compatibility-validator = "0.18.1"
|
||||
kotlin = "2.2.21"
|
||||
kotlinx-coroutines-core = "1.10.2"
|
||||
mockk = "1.14.5"
|
||||
mockk = "1.14.6"
|
||||
multidexlib2 = "3.0.3.r3"
|
||||
# Tracking https://github.com/google/smali/issues/64.
|
||||
#noinspection GradleDependency
|
||||
|
||||
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,7 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -82,15 +82,11 @@ class Fingerprint internal constructor(
|
||||
if (this@Fingerprint.returnType != null && this@Fingerprint.returnType != returnType)
|
||||
return false
|
||||
|
||||
if (this@Fingerprint.parameters != null && parametersStartsWith(
|
||||
this@Fingerprint.parameters,
|
||||
parameters
|
||||
)
|
||||
) return false
|
||||
|
||||
if (custom != null && !custom.invoke(this, context.lookupMaps.classDefsByType[definingClass]!!))
|
||||
if (this@Fingerprint.parameters != null && !parametersStartsWith(parameterTypes, this@Fingerprint.parameters))
|
||||
return false
|
||||
|
||||
if (custom != null && !custom(this, context.lookupMaps.classDefsByType[definingClass]!!))
|
||||
return false
|
||||
|
||||
if (strings != null && !matchStrings(instructionsOrNull ?: return false))
|
||||
return false
|
||||
@@ -98,34 +94,22 @@ class Fingerprint internal constructor(
|
||||
fun InstructionFilter.evaluate(instruction: Instruction): Boolean {
|
||||
return when (this) {
|
||||
is AnyInstruction -> filters.any { evaluate(instruction) }
|
||||
is CheckCastFilter -> {
|
||||
val type = type()
|
||||
|
||||
instruction.opcode(Opcode.CHECK_CAST) &&
|
||||
instruction.reference<TypeReference> { endsWith(type) }
|
||||
}
|
||||
is CheckCastFilter -> instruction.opcode(Opcode.CHECK_CAST) && instruction.typeReference?.endsWith(
|
||||
typeValue
|
||||
) ?: false
|
||||
|
||||
is FieldAccessFilter -> {
|
||||
val reference = instruction.fieldReference ?: return false
|
||||
|
||||
if (name != null && reference.name != name) return false
|
||||
|
||||
if (type != null && !reference.type.startsWith(type)) return false
|
||||
|
||||
if (definingClass != null) {
|
||||
if (!reference.definingClass.endsWith(definingClass))
|
||||
// else, the method call is for 'this' class.
|
||||
if (!(definingClass == "this" && reference.definingClass == method.definingClass)) return false
|
||||
}
|
||||
|
||||
true
|
||||
definingClass == null || reference.definingClass.endsWith(definingClass) ||
|
||||
(definingClass == "this" && reference.definingClass != method.definingClass)
|
||||
}
|
||||
|
||||
is LiteralFilter -> {
|
||||
instruction.wideLiteral?.equals(literal()) ?: return false
|
||||
|
||||
opcodes != null && !opcodes.contains(instruction.opcode)
|
||||
}
|
||||
is LiteralFilter -> instruction.wideLiteral == literalValue
|
||||
&& opcodes?.contains(instruction.opcode) ?: true
|
||||
|
||||
is MethodCallFilter -> {
|
||||
val reference = instruction.methodReference ?: return false
|
||||
@@ -134,25 +118,14 @@ class Fingerprint internal constructor(
|
||||
|
||||
if (returnType != null && !reference.returnType.startsWith(returnType)) return false
|
||||
|
||||
if (parameters != null && !parametersStartsWith(
|
||||
reference.parameterTypes,
|
||||
parameters
|
||||
)
|
||||
if (parameters != null && !parametersStartsWith(reference.parameterTypes, parameters))
|
||||
return false
|
||||
|
||||
if ((definingClass != null && !reference.definingClass.endsWith(definingClass)) ||
|
||||
(definingClass == "this" && reference.definingClass != method.definingClass)
|
||||
) return false
|
||||
|
||||
if (definingClass != null) {
|
||||
if (!reference.definingClass.endsWith(definingClass)) {
|
||||
// Check if 'this' defining class is used.
|
||||
// Would be nice if this also checked all super classes,
|
||||
// but doing so requires iteratively checking all superclasses
|
||||
// up to the root class since class defs are mere Strings.
|
||||
if (!(definingClass == "this" && reference.definingClass == method.definingClass)) {
|
||||
return false
|
||||
} // else, the method call is for 'this' class.
|
||||
}
|
||||
}
|
||||
|
||||
opcodes != null && !opcodes.contains(instruction.opcode)
|
||||
opcodes?.contains(instruction.opcode) ?: true
|
||||
}
|
||||
|
||||
is NewInstanceFilter -> {
|
||||
@@ -182,19 +155,15 @@ class Fingerprint internal constructor(
|
||||
filters.forEach { filter ->
|
||||
when (val location = filter.location) {
|
||||
is MatchAfterImmediately -> after { filter.evaluate(this) }
|
||||
is MatchAfterWithin -> after(1..location.matchDistance) { filter.evaluate(this) }
|
||||
is MatchAfterAnywhere -> add { filter.evaluate(this) }
|
||||
is MatchAfterWithin -> after(atLeast = 1, atMost = location.matchDistance) {
|
||||
filter.evaluate(this)
|
||||
}
|
||||
|
||||
is MatchAfterAtLeast -> after(
|
||||
atLeast = location.minimumDistanceFromLastInstruction,
|
||||
atMost = Int.MAX_VALUE
|
||||
location.minimumDistanceFromLastInstruction..Int.MAX_VALUE
|
||||
) { filter.evaluate(this) }
|
||||
|
||||
is MatchAfterRange -> after(
|
||||
atLeast = location.minimumDistanceFromLastInstruction,
|
||||
atMost = location.maximumDistanceFromLastInstruction
|
||||
location.minimumDistanceFromLastInstruction..
|
||||
location.maximumDistanceFromLastInstruction
|
||||
) { filter.evaluate(this) }
|
||||
|
||||
is MatchFirst -> first { filter.evaluate(this) }
|
||||
|
||||
@@ -371,7 +371,7 @@ class LiteralFilter internal constructor(
|
||||
/**
|
||||
* Store the lambda value instead of calling it more than once.
|
||||
*/
|
||||
private val literalValue: Long by lazy(literal)
|
||||
internal val literalValue: Long by lazy(literal)
|
||||
|
||||
override fun matches(
|
||||
enclosingMethod: Method,
|
||||
@@ -1005,7 +1005,7 @@ class CheckCastFilter internal constructor(
|
||||
/**
|
||||
* Store the lambda value instead of calling it more than once.
|
||||
*/
|
||||
private val typeValue: String by lazy {
|
||||
internal val typeValue: String by lazy {
|
||||
val typeValue = type()
|
||||
comparison.validateSearchStringForClassType(typeValue)
|
||||
typeValue
|
||||
|
||||
@@ -4,11 +4,8 @@ package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.Matcher.MatchContext
|
||||
import app.revanced.patcher.dex.mutable.MutableMethod
|
||||
import app.revanced.patcher.extensions.*
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.HiddenApiRestriction
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.*
|
||||
import com.android.tools.smali.dexlib2.iface.Annotation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
@@ -248,129 +245,62 @@ context(context: MatchContext)
|
||||
inline fun <reified V : Any> remember(key: Any, defaultValue: () -> V) =
|
||||
context[key] as? V ?: defaultValue().also { context[key] = it }
|
||||
|
||||
class IndexedMatcher<T>() : Matcher<T, T.() -> Boolean>() {
|
||||
class IndexedMatcher<T>() : Matcher<T, T.(lastMatchedIndex: Int, currentIndex: Int) -> Boolean>() {
|
||||
private val _indices: MutableList<Int> = mutableListOf()
|
||||
val indices: List<Int> = _indices
|
||||
|
||||
private var lastMatchedIndex = -1
|
||||
private var currentIndex = -1
|
||||
// TODO: Hint to stop searching for performance: private var stop = false
|
||||
// Also make the APIs advance indices (e.g. atLeast, atMost) for performance.
|
||||
|
||||
override fun invoke(haystack: Iterable<T>): Boolean {
|
||||
// Defensive, in case haystack is not a list.
|
||||
// Normalize to list
|
||||
val hayList = haystack as? List<T> ?: haystack.toList()
|
||||
|
||||
_indices.clear()
|
||||
lastMatchedIndex = -1
|
||||
currentIndex = -1
|
||||
|
||||
var firstNeedleIndex = 0
|
||||
for (predicate in this) {
|
||||
var matched = false
|
||||
|
||||
while (firstNeedleIndex <= hayList.lastIndex) {
|
||||
lastMatchedIndex = -1
|
||||
// Continue scanning from the position after the last successful match
|
||||
for (i in (lastMatchedIndex + 1) until hayList.size) {
|
||||
currentIndex = i
|
||||
val element = hayList[i]
|
||||
|
||||
val tempIndices = mutableListOf<Int>()
|
||||
|
||||
var matchedAll = true
|
||||
var subIndex = firstNeedleIndex
|
||||
|
||||
for (predicateIndex in _indices.indices) {
|
||||
var predicateMatched = false
|
||||
|
||||
while (subIndex <= hayList.lastIndex) {
|
||||
currentIndex = subIndex
|
||||
val element = hayList[subIndex]
|
||||
if (this[predicateIndex](element)) {
|
||||
tempIndices += subIndex
|
||||
lastMatchedIndex = subIndex
|
||||
predicateMatched = true
|
||||
subIndex++
|
||||
break
|
||||
}
|
||||
subIndex++
|
||||
}
|
||||
|
||||
if (!predicateMatched) {
|
||||
// Restart from next possible first match
|
||||
firstNeedleIndex = if (tempIndices.isNotEmpty()) tempIndices[0] + 1 else firstNeedleIndex + 1
|
||||
matchedAll = false
|
||||
if (element.predicate(lastMatchedIndex, currentIndex)) {
|
||||
_indices += i
|
||||
lastMatchedIndex = i
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedAll) {
|
||||
_indices += tempIndices
|
||||
return true
|
||||
if (!matched) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
fun first(predicate: T.() -> Boolean) = add {
|
||||
if (lastMatchedIndex != -1) false
|
||||
else predicate()
|
||||
}
|
||||
|
||||
fun after(atLeast: Int = 1, atMost: Int = 1, predicate: T.() -> Boolean) = add {
|
||||
val distance = currentIndex - lastMatchedIndex
|
||||
if (distance in atLeast..atMost) predicate() else false
|
||||
}
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.matchers() {
|
||||
val customMatcher = object : Matcher<Instruction, Instruction.() -> Boolean>() {
|
||||
override fun invoke(haystack: Iterable<Instruction>) = true
|
||||
}.apply { add { true } }
|
||||
|
||||
// Probably gonna make this ctor internal.
|
||||
IndexedMatcher<Instruction>().apply { add { true } }
|
||||
// Since there is a function for it.
|
||||
val m = indexedMatcher<Instruction>()
|
||||
m.apply { add { true } }
|
||||
|
||||
// You can directly use the extension function to match.
|
||||
listOf<Instruction>().matchIndexed { add { true } }
|
||||
|
||||
// Inside a match context extension functions are cacheable:
|
||||
firstMethod { instructions.matchIndexed("key") { add { true } } }
|
||||
|
||||
// Or create a matcher outside a context and use it in a MatchContext as follows:
|
||||
val match = indexedMatcher<Instruction>()
|
||||
firstMethod { match(instructions, "anotherKey") { first { opcode(Opcode.RETURN_VOID) } } }
|
||||
match.indices // so that you can access the matched indices later.
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.a() {
|
||||
val matcher = indexedMatcher<Instruction>()
|
||||
|
||||
firstMethodMutableOrNull("string to find in lookupmap") {
|
||||
returnType == "L" && matcher(instructions, "key") {
|
||||
first {
|
||||
// The first instruction is a field reference to a field in the class of the method being matched.
|
||||
// We cache the field name using remember to avoid redundant lookups.
|
||||
fieldReference!!.name == remember("fieldName") {
|
||||
firstClassDef(definingClass).fields.first().name
|
||||
}
|
||||
}
|
||||
after { fieldReference("fieldName") }
|
||||
after {
|
||||
opcode(Opcode.NEW_INSTANCE) && methodReference { toString() == "Lcom/example/MyClass;" }
|
||||
}
|
||||
// Followed by 2 to 4 string instructions starting with "test".
|
||||
after(atLeast = 1, atMost = 2) { string { startsWith("test") } }
|
||||
} && parameterTypes.matchIndexed("params") {
|
||||
// Fully dynamic environment to customize depending on your needs.
|
||||
operator fun String.plus(other: String) {
|
||||
after { this == this@plus }
|
||||
after { this == other }
|
||||
}
|
||||
|
||||
"L" + "I" + "Z" // Matches parameter types "L", "I", "Z" in order.
|
||||
fun first(predicate: T.(lastMatchedIndex: Int, currentIndex: Int) -> Boolean) =
|
||||
add { lastMatchedIndex, currentIndex ->
|
||||
currentIndex == 0 && predicate(lastMatchedIndex, currentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
firstMethodMutable {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) && instructions.matchIndexed("anotherKey") {
|
||||
first { opcode(Opcode.RETURN_VOID) }
|
||||
fun first(predicate: T.() -> Boolean) =
|
||||
first { _, _ -> predicate() }
|
||||
|
||||
fun after(range: IntRange = 1..1, predicate: T.(lastMatchedIndex: Int, currentIndex: Int) -> Boolean) =
|
||||
add { lastMatchedIndex, currentIndex ->
|
||||
currentIndex - lastMatchedIndex in range && predicate(lastMatchedIndex, currentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
fun after(range: IntRange = 1..1, predicate: T.() -> Boolean) =
|
||||
after(range) { _, _ -> predicate() }
|
||||
|
||||
fun add(predicate: T.() -> Boolean) = add { _, _ -> predicate() }
|
||||
}
|
||||
|
||||
@@ -8,12 +8,10 @@ import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.*
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Reference> Instruction.reference(predicate: T.() -> Boolean) =
|
||||
inline fun <reified T : Reference> Instruction.reference(predicate: T.() -> Boolean) =
|
||||
((this as? ReferenceInstruction)?.reference as? T)?.predicate() ?: false
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Reference> Instruction.reference2(predicate: T.() -> Boolean) =
|
||||
inline fun <reified T : Reference> Instruction.reference2(predicate: T.() -> Boolean) =
|
||||
((this as? DualReferenceInstruction)?.reference2 as? T)?.predicate() ?: false
|
||||
|
||||
fun Instruction.methodReference(predicate: MethodReference.() -> Boolean) =
|
||||
|
||||
@@ -17,8 +17,6 @@ import java.io.StringReader
|
||||
private const val CLASS_HEADER = ".class LInlineCompiler;\n.super Ljava/lang/Object;\n"
|
||||
private const val STATIC_HEADER = "$CLASS_HEADER.method public static dummyMethod("
|
||||
private const val HEADER = "$CLASS_HEADER.method public dummyMethod("
|
||||
|
||||
private val dexBuilder = DexBuilder(Opcodes.getDefault())
|
||||
private val sb by lazy { StringBuilder(512) }
|
||||
|
||||
/**
|
||||
@@ -59,7 +57,7 @@ fun String.toInstructions(templateMethod: MutableMethod? = null): List<BuilderIn
|
||||
}
|
||||
|
||||
val walker = smaliTreeWalker(treeStream)
|
||||
walker.setDexBuilder(dexBuilder)
|
||||
walker.setDexBuilder(DexBuilder(Opcodes.getDefault()))
|
||||
|
||||
val classDef = walker.smali_file()
|
||||
return classDef.methods.first().instructions.map { it as BuilderInstruction }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package app.revanced.patcher.extensions
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.dex.mutable.MutableMethod
|
||||
import app.revanced.patcher.dex.mutable.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.extensions.*
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
|
||||
@@ -1,19 +1,22 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.patch.*
|
||||
import app.revanced.patcher.patch.BytecodePatchContext.LookupMaps
|
||||
import app.revanced.patcher.util.toInstructions
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import io.mockk.*
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.assertAll
|
||||
import java.util.logging.Logger
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.*
|
||||
|
||||
internal object PatcherTest {
|
||||
private lateinit var patcher: Patcher
|
||||
@@ -162,6 +165,91 @@ internal object PatcherTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `matcher finds indices correctly`() {
|
||||
val iterable = (1..10).toList()
|
||||
val matcher = indexedMatcher<Int>()
|
||||
|
||||
matcher.apply { first { this > 5 } }
|
||||
assertFalse(
|
||||
matcher(iterable),
|
||||
"Should not match at any other index than first"
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply { first { this == 1 } }(iterable)
|
||||
assertEquals(
|
||||
listOf(0),
|
||||
matcher.indices,
|
||||
"Should match at first index."
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply { add { this > 0 } }(iterable)
|
||||
assertEquals(1, matcher.indices.size, "Should only match once.")
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply { add { this == 2 } }(iterable)
|
||||
assertEquals(
|
||||
listOf(1),
|
||||
matcher.indices,
|
||||
"Should find the index correctly."
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
first { this == 1 }
|
||||
add { this == 2 }
|
||||
add { this == 4 }
|
||||
}(iterable)
|
||||
assertEquals(
|
||||
listOf(0, 1, 3),
|
||||
matcher.indices,
|
||||
"Should match 1, 2 and 4 at indices 0, 1 and 3."
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
after { this == 1 }
|
||||
}(iterable)
|
||||
assertEquals(
|
||||
listOf(0),
|
||||
matcher.indices,
|
||||
"Should match index 0 after nothing"
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
after(2..Int.MAX_VALUE) { this == 1 }
|
||||
}
|
||||
assertFalse(
|
||||
matcher(iterable),
|
||||
"Should not match, because 1 is out of range"
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
after(1..1) { this == 2 }
|
||||
}
|
||||
assertFalse(
|
||||
matcher(iterable),
|
||||
"Should not match, because 2 is at index 1"
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
first { this == 1 }
|
||||
after(2..5) { this == 4}
|
||||
add { this == 8 }
|
||||
add { this == 9 }
|
||||
}(iterable)
|
||||
assertEquals(
|
||||
listOf(0, 3, 7, 8),
|
||||
matcher.indices,
|
||||
"Should match indices correctly."
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `matches fingerprint`() {
|
||||
every { patcher.context.bytecodeContext.classDefs } returns mutableSetOf(
|
||||
@@ -182,7 +270,22 @@ internal object PatcherTest {
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
ImmutableMethodImplementation(
|
||||
2,
|
||||
"""
|
||||
const-string v0, "Hello, World!"
|
||||
iput-object v0, p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
iget-object v0, p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
return-void
|
||||
const-string v0, "This is a test."
|
||||
return-object v0
|
||||
invoke-virtual { p0, v0 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
invoke-static { p0 }, Ljava/lang/System;->currentTimeMillis()J
|
||||
check-cast p0, Ljava/io/PrintStream;
|
||||
""".toInstructions(),
|
||||
null,
|
||||
null
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -192,12 +295,23 @@ internal object PatcherTest {
|
||||
val fingerprint2 = fingerprint { returns("V") }
|
||||
val fingerprint3 = fingerprint { returns("V") }
|
||||
|
||||
val matchIndices = indexedMatcher<Instruction>()
|
||||
val method by gettingFirstMethod {
|
||||
implementation {
|
||||
matchIndices(instructions, "match") {
|
||||
first { opcode == Opcode.CONST_STRING }
|
||||
add { opcode == Opcode.IPUT_OBJECT }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val patches = setOf(
|
||||
bytecodePatch {
|
||||
execute {
|
||||
fingerprint.match(classDefs.first().methods.first())
|
||||
fingerprint2.match(classDefs.first())
|
||||
fingerprint3.originalClassDef
|
||||
println(method)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -216,7 +330,7 @@ internal object PatcherTest {
|
||||
|
||||
private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
|
||||
every { patcher.context.executablePatches } returns toMutableSet()
|
||||
every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classDefs)
|
||||
every { patcher.context.bytecodeContext.lookupMaps } returns with(patcher.context.bytecodeContext) { LookupMaps() }
|
||||
every { with(patcher.context.bytecodeContext) { mergeExtension(any<BytecodePatch>()) } } just runs
|
||||
|
||||
return runBlocking { patcher().toList() }
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package app.revanced.patcher.util.smali
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.dex.mutable.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.extensions.*
|
||||
import app.revanced.patcher.util.toInstructions
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
|
||||
@@ -16,7 +15,7 @@ import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal object InlineSmaliCompilerTest {
|
||||
internal object SmaliTest {
|
||||
@Test
|
||||
fun `outputs valid instruction`() {
|
||||
val want = BuilderInstruction21c(Opcode.CONST_STRING, 0, ImmutableStringReference("Test")) as BuilderInstruction
|
||||
@@ -102,4 +101,4 @@ internal object InlineSmaliCompilerTest {
|
||||
assertEquals(want.format, have.format)
|
||||
assertEquals(want.codeUnits, have.codeUnits)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user