mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-11 13:56:16 +00:00
feat: More APIs/adjustments
This commit is contained in:
@@ -1,109 +1,101 @@
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "CONTEXT_RECEIVERS_DEPRECATED")
|
||||
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.dex.mutable.MutableClassDef.Companion.toMutable
|
||||
import app.revanced.patcher.Matcher.MatchContext
|
||||
import app.revanced.patcher.dex.mutable.MutableMethod
|
||||
import app.revanced.patcher.dex.mutable.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.gettingBytecodePatch
|
||||
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.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.ExceptionHandler
|
||||
import com.android.tools.smali.dexlib2.iface.Field
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.MethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.MethodParameter
|
||||
import com.android.tools.smali.dexlib2.iface.TryBlock
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.util.InstructionUtil
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import kotlin.collections.any
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
fun Iterable<ClassDef>.anyClassDef(predicate: ClassDef.() -> Boolean) =
|
||||
any(predicate)
|
||||
fun Iterable<ClassDef>.anyClassDef(predicate: ClassDef.() -> Boolean) = any(predicate)
|
||||
|
||||
fun ClassDef.anyMethod(predicate: Method.() -> Boolean) =
|
||||
methods.any(predicate)
|
||||
fun ClassDef.anyMethod(predicate: Method.() -> Boolean) = methods.any(predicate)
|
||||
|
||||
fun ClassDef.anyDirectMethod(predicate: Method.() -> Boolean) =
|
||||
directMethods.any(predicate)
|
||||
fun ClassDef.anyDirectMethod(predicate: Method.() -> Boolean) = directMethods.any(predicate)
|
||||
|
||||
fun ClassDef.anyVirtualMethod(predicate: Method.() -> Boolean) =
|
||||
virtualMethods.any(predicate)
|
||||
fun ClassDef.anyVirtualMethod(predicate: Method.() -> Boolean) = virtualMethods.any(predicate)
|
||||
|
||||
fun ClassDef.anyField(predicate: Field.() -> Boolean) =
|
||||
fields.any(predicate)
|
||||
fun ClassDef.anyField(predicate: Field.() -> Boolean) = fields.any(predicate)
|
||||
|
||||
fun ClassDef.anyInstanceField(predicate: Field.() -> Boolean) =
|
||||
instanceFields.any(predicate)
|
||||
fun ClassDef.anyInstanceField(predicate: Field.() -> Boolean) = instanceFields.any(predicate)
|
||||
|
||||
fun ClassDef.anyStaticField(predicate: Field.() -> Boolean) =
|
||||
staticFields.any(predicate)
|
||||
fun ClassDef.anyStaticField(predicate: Field.() -> Boolean) = staticFields.any(predicate)
|
||||
|
||||
fun ClassDef.anyInterface(predicate: String.() -> Boolean) =
|
||||
interfaces.any(predicate)
|
||||
fun ClassDef.anyInterface(predicate: String.() -> Boolean) = interfaces.any(predicate)
|
||||
|
||||
fun ClassDef.anyAnnotation(predicate: Annotation.() -> Boolean) =
|
||||
annotations.any(predicate)
|
||||
fun ClassDef.anyAnnotation(predicate: Annotation.() -> Boolean) = annotations.any(predicate)
|
||||
|
||||
fun Method.implementation(predicate: MethodImplementation.() -> Boolean) =
|
||||
implementation?.predicate() ?: false
|
||||
fun Method.implementation(predicate: MethodImplementation.() -> Boolean) = implementation?.predicate() ?: false
|
||||
|
||||
fun Method.anyParameter(predicate: MethodParameter.() -> Boolean) =
|
||||
parameters.any(predicate)
|
||||
fun Method.anyParameter(predicate: MethodParameter.() -> Boolean) = parameters.any(predicate)
|
||||
|
||||
fun Method.anyParameterType(predicate: CharSequence.() -> Boolean) =
|
||||
parameterTypes.any(predicate)
|
||||
fun Method.anyParameterType(predicate: CharSequence.() -> Boolean) = parameterTypes.any(predicate)
|
||||
|
||||
fun Method.anyAnnotation(predicate: Annotation.() -> Boolean) =
|
||||
annotations.any(predicate)
|
||||
fun Method.anyAnnotation(predicate: Annotation.() -> Boolean) = annotations.any(predicate)
|
||||
|
||||
fun Method.anyHiddenApiRestriction(predicate: HiddenApiRestriction.() -> Boolean) =
|
||||
hiddenApiRestrictions.any(predicate)
|
||||
fun Method.anyHiddenApiRestriction(predicate: HiddenApiRestriction.() -> Boolean) = hiddenApiRestrictions.any(predicate)
|
||||
|
||||
fun MethodImplementation.anyInstruction(predicate: Instruction.() -> Boolean) =
|
||||
instructions.any(predicate)
|
||||
fun MethodImplementation.anyInstruction(predicate: Instruction.() -> Boolean) = instructions.any(predicate)
|
||||
|
||||
fun MethodImplementation.anyTryBlock(predicate: TryBlock<out ExceptionHandler>.() -> Boolean) =
|
||||
tryBlocks.any(predicate)
|
||||
fun MethodImplementation.anyTryBlock(predicate: TryBlock<out ExceptionHandler>.() -> Boolean) = tryBlocks.any(predicate)
|
||||
|
||||
fun MethodImplementation.anyDebugItem(predicate: Any.() -> Boolean) =
|
||||
debugItems.any(predicate)
|
||||
fun MethodImplementation.anyDebugItem(predicate: Any.() -> Boolean) = debugItems.any(predicate)
|
||||
|
||||
fun Iterable<Instruction>.anyInstruction(predicate: Instruction.() -> Boolean) =
|
||||
any(predicate)
|
||||
fun Iterable<Instruction>.anyInstruction(predicate: Instruction.() -> Boolean) = any(predicate)
|
||||
|
||||
fun BytecodePatchContext.firstClassDefOrNull(predicate: MatchPredicate<ClassDef>) =
|
||||
with(predicate) { with(MatchContext()) { classDefs.firstOrNull { it.match() } } }
|
||||
|
||||
fun BytecodePatchContext.firstClassDefOrNull(predicate: ClassDef.() -> Boolean) =
|
||||
classDefs.firstOrNull { predicate(it) }
|
||||
|
||||
fun BytecodePatchContext.firstClassDef(predicate: ClassDef.() -> Boolean) =
|
||||
fun BytecodePatchContext.firstClassDef(predicate: MatchPredicate<ClassDef>) =
|
||||
requireNotNull(firstClassDefOrNull(predicate))
|
||||
|
||||
fun BytecodePatchContext.firstMutableClassDefOrNull(predicate: ClassDef.() -> Boolean) =
|
||||
fun BytecodePatchContext.firstClassDefMutableOrNull(predicate: MatchPredicate<ClassDef>) =
|
||||
firstClassDefOrNull(predicate)?.mutable()
|
||||
|
||||
fun BytecodePatchContext.firstMutableClassDef(predicate: ClassDef.() -> Boolean) =
|
||||
requireNotNull(firstMutableClassDefOrNull(predicate))
|
||||
fun BytecodePatchContext.firstClassDefMutable(predicate: MatchPredicate<ClassDef>) =
|
||||
requireNotNull(firstClassDefMutableOrNull(predicate))
|
||||
|
||||
fun BytecodePatchContext.firstMethodOrNull(predicate: Method.() -> Boolean) =
|
||||
classDefs.asSequence().flatMap { it.methods.asSequence() }
|
||||
.firstOrNull { predicate(it) }
|
||||
fun BytecodePatchContext.firstClassDefOrNull(
|
||||
type: String, predicate: (MatchPredicate<ClassDef>)? = null
|
||||
) = lookupMaps.classesByType[type]?.takeIf {
|
||||
predicate == null || with(predicate) { with(MatchContext()) { it.match() } }
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.firstMethod(predicate: Method.() -> Boolean) =
|
||||
requireNotNull(firstMethodOrNull(predicate))
|
||||
fun BytecodePatchContext.firstClassDef(
|
||||
type: String, predicate: (MatchPredicate<ClassDef>)? = null
|
||||
) = requireNotNull(firstClassDefOrNull(type, predicate))
|
||||
|
||||
fun BytecodePatchContext.firstMutableMethodOrNull(predicate: Method.() -> Boolean): MutableMethod? {
|
||||
fun BytecodePatchContext.firstClassDefMutableOrNull(
|
||||
type: String, predicate: (MatchPredicate<ClassDef>)? = null
|
||||
) = firstClassDefOrNull(type, predicate)?.mutable()
|
||||
|
||||
fun BytecodePatchContext.firstClassDefMutable(
|
||||
type: String, predicate: (MatchPredicate<ClassDef>)? = null
|
||||
) = requireNotNull(firstClassDefMutableOrNull(type, predicate))
|
||||
|
||||
fun BytecodePatchContext.firstMethodOrNull(predicate: MatchPredicate<Method>) = with(predicate) {
|
||||
with(MatchContext()) {
|
||||
classDefs.asSequence().flatMap { it.methods.asSequence() }.firstOrNull { it.match() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.firstMethod(predicate: MatchPredicate<Method>) = requireNotNull(firstMethodOrNull(predicate))
|
||||
|
||||
fun BytecodePatchContext.firstMethodMutableOrNull(predicate: MatchPredicate<Method>): MutableMethod? {
|
||||
with(predicate) {
|
||||
with(MatchContext()) {
|
||||
classDefs.forEach { classDef ->
|
||||
classDef.methods.forEach { method ->
|
||||
if (predicate(method)) return classDef.mutable().methods.first {
|
||||
MethodUtil.methodSignaturesMatch(it, method)
|
||||
classDef.methods.firstOrNull { it.match() }?.let { method ->
|
||||
return classDef.mutable().methods.first { MethodUtil.methodSignaturesMatch(it, method) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,109 +103,301 @@ fun BytecodePatchContext.firstMutableMethodOrNull(predicate: Method.() -> Boolea
|
||||
return null
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.firstMutableMethod(predicate: Method.() -> Boolean) =
|
||||
requireNotNull(firstMutableMethodOrNull(predicate))
|
||||
fun BytecodePatchContext.firstMethodMutable(predicate: MatchPredicate<Method>) =
|
||||
requireNotNull(firstMethodMutableOrNull(predicate))
|
||||
|
||||
fun gettingFirstClassDefOrNull(predicate: ClassDef.() -> Boolean) = ReadOnlyProperty<Any?, ClassDef?> { thisRef, _ ->
|
||||
require(thisRef is BytecodePatchContext)
|
||||
|
||||
thisRef.firstClassDefOrNull(predicate)
|
||||
}
|
||||
|
||||
fun gettingFirstClassDef(predicate: ClassDef.() -> Boolean) = requireNotNull(gettingFirstClassDefOrNull(predicate))
|
||||
|
||||
fun gettingFirstMutableClassDefOrNull(predicate: ClassDef.() -> Boolean) =
|
||||
ReadOnlyProperty<Any?, ClassDef?> { thisRef, _ ->
|
||||
require(thisRef is BytecodePatchContext)
|
||||
|
||||
thisRef.firstMutableClassDefOrNull(predicate)
|
||||
}
|
||||
|
||||
fun gettingFirstMutableClassDef(predicate: ClassDef.() -> Boolean) =
|
||||
requireNotNull(gettingFirstMutableClassDefOrNull(predicate))
|
||||
|
||||
fun gettingFirstMethodOrNull(predicate: Method.() -> Boolean) = ReadOnlyProperty<Any?, Method?> { thisRef, _ ->
|
||||
require(thisRef is BytecodePatchContext)
|
||||
|
||||
thisRef.firstMethodOrNull(predicate)
|
||||
}
|
||||
|
||||
fun gettingFirstMethod(predicate: Method.() -> Boolean) = requireNotNull(gettingFirstMethodOrNull(predicate))
|
||||
|
||||
fun gettingFirstMutableMethodOrNull(predicate: Method.() -> Boolean) = ReadOnlyProperty<Any?, Method?> { thisRef, _ ->
|
||||
require(thisRef is BytecodePatchContext)
|
||||
|
||||
thisRef.firstMutableMethodOrNull(predicate)
|
||||
}
|
||||
|
||||
fun gettingFirstMutableMethod(predicate: Method.() -> Boolean) =
|
||||
requireNotNull(gettingFirstMutableMethodOrNull(predicate))
|
||||
|
||||
val classDefOrNull by gettingFirstClassDefOrNull { true }
|
||||
val classDef by gettingFirstClassDef { true }
|
||||
val mutableClassDefOrNull by gettingFirstMutableClassDefOrNull { true }
|
||||
val mutableClassDef by gettingFirstMutableClassDef { true }
|
||||
val methodOrNull by gettingFirstMethodOrNull { true }
|
||||
val methodDef by gettingFirstMethod { true }
|
||||
val mutableMethodOrNull by gettingFirstMutableMethodOrNull { true }
|
||||
val mutableMethodDef by gettingFirstMutableMethod { true }
|
||||
|
||||
val `My Patch` by gettingBytecodePatch {
|
||||
execute {
|
||||
val classDefOrNull = firstClassDefOrNull { true }
|
||||
val classDef = firstClassDef { true }
|
||||
val mutableClassDefOrNull = firstMutableClassDefOrNull { true }
|
||||
val mutableClassDef = firstMutableClassDef { true }
|
||||
val methodOrNull = firstMethodOrNull { true }
|
||||
val method = firstMethod { true }
|
||||
val mutableMethodOrNull = firstMutableMethodOrNull { true }
|
||||
val mutableMethod = firstMutableMethod { true }
|
||||
val apiTest = firstMethod {
|
||||
implementation {
|
||||
instructions.matchSequentially<Instruction> {
|
||||
add { opcode == Opcode.RETURN_VOID }
|
||||
add { opcode == Opcode.NOP }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Method.matchSequentiallyInstructions(
|
||||
builder: MutableList<Instruction.() -> Boolean>.() -> Unit
|
||||
) = implementation?.instructions?.matchSequentially(builder) ?: false
|
||||
|
||||
firstMutableMethod {
|
||||
matchSequentiallyInstructions {
|
||||
add { opcode == Opcode.RETURN_VOID }
|
||||
add { opcode == Opcode.NOP }
|
||||
}
|
||||
}.addInstructions("apiTest2")
|
||||
fun BytecodePatchContext.firstMethodOrNull(
|
||||
vararg strings: String,
|
||||
predicate: MatchPredicate<Method> = MatchPredicate { true },
|
||||
) = with(predicate) {
|
||||
with(MatchContext()) {
|
||||
strings.mapNotNull { lookupMaps.methodsByStrings[it] }.minByOrNull { it.size }?.firstOrNull { it.match() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.firstMethod(
|
||||
vararg strings: String,
|
||||
predicate: MatchPredicate<Method> = MatchPredicate { true },
|
||||
) = requireNotNull(firstMethodOrNull(*strings, predicate = predicate))
|
||||
|
||||
abstract class Matcher<T> : MutableList<T.() -> Boolean> by mutableListOf() {
|
||||
fun BytecodePatchContext.firstMethodMutableOrNull(
|
||||
vararg strings: String,
|
||||
predicate: MatchPredicate<Method> = MatchPredicate { true },
|
||||
) = with(predicate) {
|
||||
with(MatchContext()) {
|
||||
strings.mapNotNull { lookupMaps.methodsByStrings[it] }.minByOrNull { it.size }?.let { methods ->
|
||||
methods.firstOrNull { it.match() }?.let { method ->
|
||||
firstClassDefMutable(method.definingClass).methods.first {
|
||||
MethodUtil.methodSignaturesMatch(
|
||||
method, it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.firstMethodMutable(
|
||||
vararg strings: String, predicate: MatchPredicate<Method> = MatchPredicate { true }
|
||||
) = requireNotNull(firstMethodMutableOrNull(*strings, predicate = predicate))
|
||||
|
||||
inline fun <reified C, T> ReadOnlyProperty(crossinline block: C.(KProperty<*>) -> T) =
|
||||
ReadOnlyProperty<Any?, T> { thisRef, property ->
|
||||
require(thisRef is C)
|
||||
|
||||
thisRef.block(property)
|
||||
}
|
||||
|
||||
fun gettingFirstClassDefOrNull(predicate: MatchPredicate<ClassDef>) =
|
||||
ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefOrNull(predicate) }
|
||||
|
||||
fun gettingFirstClassDef(predicate: MatchPredicate<ClassDef>) = requireNotNull(gettingFirstClassDefOrNull(predicate))
|
||||
|
||||
fun gettingFirstClassDefMutableOrNull(predicate: MatchPredicate<ClassDef>) =
|
||||
ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefMutableOrNull(predicate) }
|
||||
|
||||
fun gettingFirstClassDefMutable(predicate: MatchPredicate<ClassDef>) =
|
||||
requireNotNull(gettingFirstClassDefMutableOrNull(predicate))
|
||||
|
||||
fun gettingFirstClassDefOrNull(
|
||||
type: String, predicate: (MatchPredicate<ClassDef>)? = null
|
||||
) = ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefOrNull(type, predicate) }
|
||||
|
||||
fun gettingFirstClassDef(
|
||||
type: String, predicate: (MatchPredicate<ClassDef>)? = null
|
||||
) = requireNotNull(gettingFirstClassDefOrNull(type, predicate))
|
||||
|
||||
fun gettingFirstClassDefMutableOrNull(
|
||||
type: String, predicate: (MatchPredicate<ClassDef>)? = null
|
||||
) = ReadOnlyProperty<BytecodePatchContext, ClassDef?> { firstClassDefMutableOrNull(type, predicate) }
|
||||
|
||||
fun gettingFirstClassDefMutable(
|
||||
type: String, predicate: (MatchPredicate<ClassDef>)? = null
|
||||
) = requireNotNull(gettingFirstClassDefMutableOrNull(type, predicate))
|
||||
|
||||
fun gettingFirstMethodOrNull(predicate: MatchPredicate<Method>) =
|
||||
ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodOrNull(predicate) }
|
||||
|
||||
fun gettingFirstMethod(predicate: MatchPredicate<Method>) = requireNotNull(gettingFirstMethodOrNull(predicate))
|
||||
|
||||
fun gettingFirstMethodMutableOrNull(predicate: MatchPredicate<Method>) =
|
||||
ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodMutableOrNull(predicate) }
|
||||
|
||||
fun gettingFirstMethodMutable(predicate: MatchPredicate<Method>) =
|
||||
requireNotNull(gettingFirstMethodMutableOrNull(predicate))
|
||||
|
||||
fun gettingFirstMethodOrNull(
|
||||
vararg strings: String,
|
||||
predicate: MatchPredicate<Method> = MatchPredicate { true },
|
||||
) = ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodOrNull(*strings, predicate = predicate) }
|
||||
|
||||
fun gettingFirstMethod(
|
||||
vararg strings: String,
|
||||
predicate: MatchPredicate<Method> = MatchPredicate { true },
|
||||
) = requireNotNull(gettingFirstMethodOrNull(*strings, predicate = predicate))
|
||||
|
||||
fun gettingFirstMethodMutableOrNull(
|
||||
vararg strings: String,
|
||||
predicate: MatchPredicate<Method> = MatchPredicate { true },
|
||||
) = ReadOnlyProperty<BytecodePatchContext, Method?> { firstMethodMutableOrNull(*strings, predicate = predicate) }
|
||||
|
||||
fun gettingFirstMethodMutable(
|
||||
vararg strings: String,
|
||||
predicate: MatchPredicate<Method> = MatchPredicate { true },
|
||||
) = requireNotNull(gettingFirstMethodMutableOrNull(*strings, predicate = predicate))
|
||||
|
||||
fun interface MatchPredicate<T> {
|
||||
context(MatchContext) fun T.match(): Boolean
|
||||
}
|
||||
|
||||
abstract class Matcher<T, U> : MutableList<U> by mutableListOf() {
|
||||
var matchIndex = -1
|
||||
protected set
|
||||
|
||||
abstract operator fun invoke(haystack: Iterable<T>): Boolean
|
||||
|
||||
class MatchContext internal constructor() : MutableMap<String, Any> by mutableMapOf()
|
||||
}
|
||||
|
||||
|
||||
open class SequentialMatcher<T> internal constructor() : Matcher<T>() {
|
||||
override operator fun invoke(haystack: Iterable<T>) = true
|
||||
}
|
||||
fun <T> slidingWindowMatcher(builder: MutableList<T.() -> Boolean>.() -> Unit) =
|
||||
SlidingWindowMatcher<T>().apply(builder)
|
||||
|
||||
class CaptureStringIndices<T> internal constructor() : Matcher<T>() {
|
||||
val capturedStrings = mutableMapOf<String>()
|
||||
context(MatchContext)
|
||||
fun <T> Iterable<T>.matchSlidingWindow(key: String, builder: MutableList<T.() -> Boolean>.() -> Unit) =
|
||||
(getOrPut(key) { slidingWindowMatcher(builder) } as Matcher<T, T.() -> Boolean>)(this)
|
||||
|
||||
override operator fun invoke(haystack: Iterable<T>) {
|
||||
fun <T> Iterable<T>.matchSlidingWindow(builder: MutableList<T.() -> Boolean>.() -> Unit) =
|
||||
slidingWindowMatcher(builder)(this)
|
||||
|
||||
class SlidingWindowMatcher<T>() : Matcher<T, T.() -> Boolean>() {
|
||||
override operator fun invoke(haystack: Iterable<T>): Boolean {
|
||||
val haystackCount = haystack.count()
|
||||
val needleSize = size
|
||||
if (needleSize == 0) return false
|
||||
|
||||
for (i in 0..(haystackCount - needleSize)) {
|
||||
var matched = true
|
||||
for (j in 0 until needleSize) {
|
||||
if (!this[j].invoke(haystack.elementAt(i + j))) {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
matchIndex = i
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
matchIndex = -1
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> matchSequentially() = SequentialMatcher<T>()
|
||||
fun <T> sequentialMatcher(builder: MutableList<T.() -> Boolean>.() -> Unit) =
|
||||
SequentialMatcher<T>().apply(builder)
|
||||
fun findStringsMatcher(builder: MutableList<String>.() -> Unit) =
|
||||
FindStringsMatcher().apply(builder)
|
||||
|
||||
fun <T> Iterable<T>.matchSequentially(builder: MutableList<T.() -> Boolean>.() -> Unit) =
|
||||
sequentialMatcher(builder)(this)
|
||||
class FindStringsMatcher() : Matcher<Instruction, String>() {
|
||||
val matchedStrings = mutableMapOf<String, Int>()
|
||||
var needles = toMutableSet() // Reduce O(n²) to O(log n) by removing from the set
|
||||
|
||||
override fun invoke(haystack: Iterable<Instruction>): Boolean {
|
||||
needles = toMutableSet() // Reset needles for each invocation
|
||||
// (or do not use the set if set is too small for performance)
|
||||
|
||||
val foundStrings = mutableMapOf<String, Int>()
|
||||
|
||||
haystack.forEachIndexed { index, instruction ->
|
||||
if (instruction !is ReferenceInstruction) return@forEachIndexed
|
||||
val reference = instruction.reference
|
||||
if (reference !is StringReference) return@forEachIndexed
|
||||
val string = reference.string
|
||||
|
||||
if (needles.removeIf { it in string }) {
|
||||
foundStrings[string] = index
|
||||
}
|
||||
}
|
||||
|
||||
return if (foundStrings.size == size) {
|
||||
matchedStrings += foundStrings
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.findStringIndices() {
|
||||
val match = findStringsMatcher {
|
||||
add("fullstring1")
|
||||
add("fullstring2")
|
||||
add("partialString")
|
||||
}
|
||||
|
||||
firstMethod("fullstring", "fullstring") {
|
||||
implementation {
|
||||
match(instructions)
|
||||
}
|
||||
}
|
||||
|
||||
match.matchedStrings.forEach { (key, value) ->
|
||||
println("Found string '$key' at index $value")
|
||||
}
|
||||
|
||||
firstMethod {
|
||||
implementation {
|
||||
// Uncached usage
|
||||
instructions.matchSlidingWindow {
|
||||
|
||||
} || instructions.matchSlidingWindow("cached usage") {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.anotherExample() {
|
||||
val desiredStringIndices = listOf("fullstring1", "fullstring2", "partialString")
|
||||
val matchedIndices = mutableMapOf<String, Int>()
|
||||
|
||||
firstMethod("fullstring", "fullstring") {
|
||||
val remaining = desiredStringIndices.toMutableSet()
|
||||
val foundMap = mutableMapOf<String, Int>()
|
||||
|
||||
implementation {
|
||||
|
||||
instructions.withIndex().forEach { (index, instruction) ->
|
||||
val string = (instruction as? ReferenceInstruction)?.reference
|
||||
.let { it as? StringReference }?.string
|
||||
?: return@forEach
|
||||
|
||||
val iterator = remaining.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val desired = iterator.next()
|
||||
if (desired in string) {
|
||||
foundMap[desired] = index
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining.isEmpty()) return@forEach
|
||||
}
|
||||
|
||||
if (remaining.isEmpty()) {
|
||||
matchedIndices.putAll(foundMap)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BytecodePatchContext.wrapperExample() {
|
||||
fun Method.captureStrings(
|
||||
desiredStringIndices: Set<String>,
|
||||
out: MutableMap<String, Int>
|
||||
): Boolean {
|
||||
val remaining = desiredStringIndices.toMutableSet()
|
||||
val foundMap = mutableMapOf<String, Int>()
|
||||
|
||||
return implementation {
|
||||
instructions.withIndex().forEach { (index, instruction) ->
|
||||
val string = (instruction as? ReferenceInstruction)?.reference
|
||||
.let { it as? StringReference }?.string
|
||||
?: return@forEach
|
||||
|
||||
val iterator = remaining.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val desired = iterator.next()
|
||||
if (desired in string) {
|
||||
foundMap[desired] = index
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining.isEmpty()) return@forEach
|
||||
}
|
||||
|
||||
if (remaining.isEmpty()) {
|
||||
out += foundMap
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val desiredStringIndices = setOf("fullstring1", "fullstring2", "partialString")
|
||||
val matchedIndices = mutableMapOf<String, Int>()
|
||||
|
||||
val method = firstMethod {
|
||||
name == "desiredMethodName" && captureStrings(desiredStringIndices, matchedIndices)
|
||||
}
|
||||
|
||||
for ((key, value) in matchedIndices) {
|
||||
println("Found string '$key' at index $value in method '$method'")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.InternalApi
|
||||
import app.revanced.patcher.Matcher
|
||||
import app.revanced.patcher.PatcherConfig
|
||||
import app.revanced.patcher.PatcherResult
|
||||
import app.revanced.patcher.dex.mutable.MutableClassDef
|
||||
@@ -8,7 +9,6 @@ import app.revanced.patcher.dex.mutable.MutableClassDef.Companion.toMutable
|
||||
import app.revanced.patcher.extensions.instructionsOrNull
|
||||
import app.revanced.patcher.util.ClassMerger.merge
|
||||
import app.revanced.patcher.util.MethodNavigator
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.Opcodes
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.DexFile
|
||||
@@ -21,9 +21,10 @@ import lanchon.multidexlib2.DexIO
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import lanchon.multidexlib2.RawDexIO
|
||||
import java.io.Closeable
|
||||
import java.util.*
|
||||
import java.io.IOException
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
/**
|
||||
* A context for patches containing the current state of the bytecode.
|
||||
*
|
||||
@@ -54,7 +55,8 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
||||
/**
|
||||
* The lookup maps for methods and the class they are a member of from the [classDefs].
|
||||
*/
|
||||
internal val lookupMaps by lazy { LookupMaps(classDefs) }
|
||||
internal val lookupMaps by lazy { _lookupMaps ?: LookupMaps().also { _lookupMaps = it } }
|
||||
private var _lookupMaps: LookupMaps? = null // For freeing up memory when compiling.
|
||||
|
||||
/**
|
||||
* Merge the extension of [bytecodePatch] into the [BytecodePatchContext].
|
||||
@@ -69,7 +71,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
||||
logger.fine { "Adding class \"$classDef\"" }
|
||||
|
||||
classDefs += classDef
|
||||
lookupMaps.classesByType[classDef.type] = classDef
|
||||
lookupMaps += classDef
|
||||
|
||||
return@forEach
|
||||
}
|
||||
@@ -117,7 +119,8 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
||||
logger.info("Compiling patched dex files")
|
||||
|
||||
// Free up memory before compiling the dex files.
|
||||
lookupMaps.close()
|
||||
close()
|
||||
System.gc()
|
||||
|
||||
val patchedDexFileResults =
|
||||
config.patchedFiles.resolve("dex").also {
|
||||
@@ -146,75 +149,39 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
||||
return patchedDexFileResults
|
||||
}
|
||||
|
||||
/**
|
||||
* A lookup map for methods and the class they are a member of and classes.
|
||||
*
|
||||
* @param classDefs The list of classes to create the lookup maps from.
|
||||
*/
|
||||
internal class LookupMaps internal constructor(classDefs: Set<ClassDef>) : Closeable {
|
||||
/**
|
||||
* Methods associated by strings referenced in it.
|
||||
*/
|
||||
internal val methodsByStrings = MethodClassPairsLookupMap()
|
||||
internal val matchers =Map<String, Matcher<*>>
|
||||
|
||||
// Lookup map for fast checking if a class exists by its type.
|
||||
val classesByType = mutableMapOf<String, ClassDef>().apply {
|
||||
classDefs.forEach { classDef -> put(classDef.type, classDef) }
|
||||
override fun close() {
|
||||
try {
|
||||
classDefs.clear()
|
||||
_lookupMaps = null
|
||||
} catch (e: IOException) {
|
||||
logger.warning("Failed to clear BytecodePatchContext: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class LookupMaps {
|
||||
private val _classesByType = mutableMapOf<String, ClassDef>()
|
||||
val classesByType: Map<String, ClassDef> = _classesByType
|
||||
|
||||
private val _methodsByStrings = mutableMapOf<String, MutableList<Method>>()
|
||||
val methodsByStrings: Map<String, List<Method>> = _methodsByStrings
|
||||
|
||||
init {
|
||||
classDefs.forEach { classDef ->
|
||||
classDef.methods.forEach { method ->
|
||||
val methodClassPair: MethodClassPair = method to classDef
|
||||
|
||||
// Add strings contained in the method as the key.
|
||||
method.instructionsOrNull?.forEach instructions@{ instruction ->
|
||||
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) {
|
||||
return@instructions
|
||||
classDefs.forEach(::plusAssign)
|
||||
}
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
|
||||
methodsByStrings[string] = methodClassPair
|
||||
operator fun plusAssign(classDef: ClassDef) {
|
||||
classDef.methods.asSequence().forEach { method ->
|
||||
method.instructionsOrNull?.asSequence()
|
||||
?.filterIsInstance<ReferenceInstruction>()
|
||||
?.map { it.reference }
|
||||
?.filterIsInstance<StringReference>()
|
||||
?.map { it.string }
|
||||
?.forEach { string -> _methodsByStrings.getOrPut(string) { mutableListOf() } += method }
|
||||
}
|
||||
|
||||
// In the future, the class type could be added to the lookup map.
|
||||
// This would require MethodFingerprint to be changed to include the class type.
|
||||
_classesByType[classDef.type] = classDef
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
methodsByStrings.clear()
|
||||
classesByType.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
lookupMaps.close()
|
||||
classDefs.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of a [Method] and the [ClassDef] it is a member of.
|
||||
*/
|
||||
internal typealias MethodClassPair = Pair<Method, ClassDef>
|
||||
|
||||
/**
|
||||
* A list of [MethodClassPair]s.
|
||||
*/
|
||||
internal typealias MethodClassPairs = LinkedList<MethodClassPair>
|
||||
|
||||
/**
|
||||
* A lookup map for [MethodClassPairs]s.
|
||||
* The key is a string and the value is a list of [MethodClassPair]s.
|
||||
*/
|
||||
internal class MethodClassPairsLookupMap : MutableMap<String, MethodClassPairs> by mutableMapOf() {
|
||||
/**
|
||||
* Add a [MethodClassPair] associated by any key.
|
||||
* If the key does not exist, a new list is created and the [MethodClassPair] is added to it.
|
||||
*/
|
||||
internal operator fun set(key: String, methodClassPair: MethodClassPair) =
|
||||
apply { getOrPut(key) { MethodClassPairs() }.add(methodClassPair) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user