feat: Modernize APIs

This commit is contained in:
oSumAtrIX
2025-11-19 00:20:41 +01:00
parent 3a8b2ba935
commit 79d3640186
42 changed files with 762 additions and 1230 deletions

View File

@@ -2,7 +2,7 @@
android = "4.1.1.4"
apktool-lib = "2.10.1.1"
binary-compatibility-validator = "0.18.1"
kotlin = "2.0.20"
kotlin = "2.2.21"
kotlinx-coroutines-core = "1.10.2"
mockk = "1.14.5"
multidexlib2 = "3.0.3.r3"

View File

@@ -1,599 +0,0 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package app.revanced.patcher
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
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
/**
* A fingerprint for a method. A fingerprint is a partial description of a method.
* It is used to uniquely match a method by its characteristics.
*
* An example fingerprint for a public method that takes a single string parameter and returns void:
* ```
* fingerprint {
* accessFlags(AccessFlags.PUBLIC)
* returns("V")
* parameters("Ljava/lang/String;")
* }
* ```
*
* @param accessFlags The exact access flags using values of [AccessFlags].
* @param returnType The return type. Compared using [String.startsWith].
* @param parameters The parameters. Partial matches allowed and follow the same rules as [returnType].
* @param opcodes A pattern of instruction opcodes. `null` can be used as a wildcard.
* @param strings A list of the strings. Compared using [String.contains].
* @param custom A custom condition for this fingerprint.
* @param fuzzyPatternScanThreshold The threshold for fuzzy scanning the [opcodes] pattern.
*/
class Fingerprint internal constructor(
internal val accessFlags: Int?,
internal val returnType: String?,
internal val parameters: List<String>?,
internal val opcodes: List<Opcode?>?,
internal val strings: List<String>?,
internal val custom: ((method: Method, classDef: ClassDef) -> Boolean)?,
private val fuzzyPatternScanThreshold: Int,
) {
@Suppress("ktlint:standard:backing-property-naming")
// Backing field needed for lazy initialization.
private var _matchOrNull: Match? = null
/**
* The match for this [Fingerprint]. Null if unmatched.
*/
context(BytecodePatchContext)
private val matchOrNull: Match?
get() = matchOrNull()
/**
* Match using [BytecodePatchContext.lookupMaps].
*
* Generally faster than the other [matchOrNull] overloads when there are many methods to check for a match.
*
* Fingerprints can be optimized for performance:
* - Slowest: Specify [custom] or [opcodes] and nothing else.
* - Fast: Specify [accessFlags], [returnType].
* - Faster: Specify [accessFlags], [returnType] and [parameters].
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
*
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
*/
context(BytecodePatchContext)
internal fun matchOrNull(): Match? {
if (_matchOrNull != null) return _matchOrNull
var match = strings?.mapNotNull {
lookupMaps.methodsByStrings[it]
}?.minByOrNull { it.size }?.let { methodClasses ->
methodClasses.forEach { (classDef, method) ->
val match = matchOrNull(classDef, method)
if (match != null) return@let match
}
null
}
if (match != null) return match
classes.forEach { classDef ->
match = matchOrNull(classDef)
if (match != null) return match
}
return null
}
/**
* Match using a [ClassDef].
*
* @param classDef The class to match against.
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
*/
context(BytecodePatchContext)
fun matchOrNull(
classDef: ClassDef,
): Match? {
if (_matchOrNull != null) return _matchOrNull
for (method in classDef.methods) {
val match = matchOrNull(method, classDef)
if (match != null) return match
}
return null
}
/**
* Match using a [Method].
* The class is retrieved from the method.
*
* @param method The method to match against.
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
*/
context(BytecodePatchContext)
fun matchOrNull(
method: Method,
) = matchOrNull(method, classBy { method.definingClass == it.type }!!.immutableClass)
/**
* Match using a [Method].
*
* @param method The method to match against.
* @param classDef The class the method is a member of.
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
*/
context(BytecodePatchContext)
fun matchOrNull(
method: Method,
classDef: ClassDef,
): Match? {
if (_matchOrNull != null) return _matchOrNull
if (returnType != null && !method.returnType.startsWith(returnType)) {
return null
}
if (accessFlags != null && accessFlags != method.accessFlags) {
return null
}
fun parametersEqual(
parameters1: Iterable<CharSequence>,
parameters2: Iterable<CharSequence>,
): Boolean {
if (parameters1.count() != parameters2.count()) return false
val iterator1 = parameters1.iterator()
parameters2.forEach {
if (!it.startsWith(iterator1.next())) return false
}
return true
}
// TODO: parseParameters()
if (parameters != null && !parametersEqual(parameters, method.parameterTypes)) {
return null
}
if (custom != null && !custom.invoke(method, classDef)) {
return null
}
val stringMatches: List<Match.StringMatch>? =
if (strings != null) {
buildList {
val instructions = method.instructionsOrNull ?: return null
val stringsList = strings.toMutableList()
instructions.forEachIndexed { instructionIndex, instruction ->
if (
instruction.opcode != Opcode.CONST_STRING &&
instruction.opcode != Opcode.CONST_STRING_JUMBO
) {
return@forEachIndexed
}
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
val index = stringsList.indexOfFirst(string::contains)
if (index == -1) return@forEachIndexed
add(Match.StringMatch(string, instructionIndex))
stringsList.removeAt(index)
}
if (stringsList.isNotEmpty()) return null
}
} else {
null
}
val patternMatch = if (opcodes != null) {
val instructions = method.instructionsOrNull ?: return null
fun patternScan(): Match.PatternMatch? {
val fingerprintFuzzyPatternScanThreshold = fuzzyPatternScanThreshold
val instructionLength = instructions.count()
val patternLength = opcodes.size
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 = opcodes.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 entire pattern has been scanned.
return Match.PatternMatch(
index,
index + patternIndex,
)
}
}
return null
}
patternScan() ?: return null
} else {
null
}
_matchOrNull = Match(
method,
patternMatch,
stringMatches,
classDef,
)
return _matchOrNull
}
private val exception get() = PatchException("Failed to match the fingerprint: $this")
/**
* The match for this [Fingerprint].
*
* @throws PatchException If the [Fingerprint] has not been matched.
*/
context(BytecodePatchContext)
private val match
get() = matchOrNull ?: throw exception
/**
* Match using a [ClassDef].
*
* @param classDef The class to match against.
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
* @throws PatchException If the fingerprint has not been matched.
*/
context(BytecodePatchContext)
fun match(
classDef: ClassDef,
) = matchOrNull(classDef) ?: throw exception
/**
* Match using a [Method].
* The class is retrieved from the method.
*
* @param method The method to match against.
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
* @throws PatchException If the fingerprint has not been matched.
*/
context(BytecodePatchContext)
fun match(
method: Method,
) = matchOrNull(method) ?: throw exception
/**
* Match using a [Method].
*
* @param method The method to match against.
* @param classDef The class the method is a member of.
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
* @throws PatchException If the fingerprint has not been matched.
*/
context(BytecodePatchContext)
fun match(
method: Method,
classDef: ClassDef,
) = matchOrNull(method, classDef) ?: throw exception
/**
* The class the matching method is a member of.
*/
context(BytecodePatchContext)
val originalClassDefOrNull
get() = matchOrNull?.originalClassDef
/**
* The matching method.
*/
context(BytecodePatchContext)
val originalMethodOrNull
get() = matchOrNull?.originalMethod
/**
* The mutable version of [originalClassDefOrNull].
*
* Accessing this property allocates a [ClassProxy].
* Use [originalClassDefOrNull] if mutable access is not required.
*/
context(BytecodePatchContext)
val classDefOrNull
get() = matchOrNull?.classDef
/**
* The mutable version of [originalMethodOrNull].
*
* Accessing this property allocates a [ClassProxy].
* Use [originalMethodOrNull] if mutable access is not required.
*/
context(BytecodePatchContext)
val methodOrNull
get() = matchOrNull?.method
/**
* The match for the opcode pattern.
*/
context(BytecodePatchContext)
val patternMatchOrNull
get() = matchOrNull?.patternMatch
/**
* The matches for the strings.
*/
context(BytecodePatchContext)
val stringMatchesOrNull
get() = matchOrNull?.stringMatches
/**
* The class the matching method is a member of.
*
* @throws PatchException If the fingerprint has not been matched.
*/
context(BytecodePatchContext)
val originalClassDef
get() = match.originalClassDef
/**
* The matching method.
*
* @throws PatchException If the fingerprint has not been matched.
*/
context(BytecodePatchContext)
val originalMethod
get() = match.originalMethod
/**
* The mutable version of [originalClassDef].
*
* Accessing this property allocates a [ClassProxy].
* Use [originalClassDef] if mutable access is not required.
*
* @throws PatchException If the fingerprint has not been matched.
*/
context(BytecodePatchContext)
val classDef
get() = match.classDef
/**
* The mutable version of [originalMethod].
*
* Accessing this property allocates a [ClassProxy].
* Use [originalMethod] if mutable access is not required.
*
* @throws PatchException If the fingerprint has not been matched.
*/
context(BytecodePatchContext)
val method
get() = match.method
/**
* The match for the opcode pattern.
*
* @throws PatchException If the fingerprint has not been matched.
*/
context(BytecodePatchContext)
val patternMatch
get() = match.patternMatch
/**
* The matches for the strings.
*
* @throws PatchException If the fingerprint has not been matched.
*/
context(BytecodePatchContext)
val stringMatches
get() = match.stringMatches
}
/**
* A match of a [Fingerprint].
*
* @param originalClassDef The class the matching method is a member of.
* @param originalMethod The matching method.
* @param patternMatch The match for the opcode pattern.
* @param stringMatches The matches for the strings.
*/
context(BytecodePatchContext)
class Match internal constructor(
val originalMethod: Method,
val patternMatch: PatternMatch?,
val stringMatches: List<StringMatch>?,
val originalClassDef: ClassDef,
) {
/**
* The mutable version of [originalClassDef].
*
* Accessing this property allocates a [ClassProxy].
* Use [originalClassDef] if mutable access is not required.
*/
val classDef by lazy { proxy(originalClassDef).mutableClass }
/**
* The mutable version of [originalMethod].
*
* Accessing this property allocates a [ClassProxy].
* Use [originalMethod] if mutable access is not required.
*/
val method by lazy { classDef.methods.first { MethodUtil.methodSignaturesMatch(it, originalMethod) } }
/**
* A match for an opcode pattern.
* @param startIndex The index of the first opcode of the pattern in the method.
* @param endIndex The index of the last opcode of the pattern in the method.
*/
class PatternMatch internal constructor(
val startIndex: Int,
val endIndex: Int,
)
/**
* A match for a string.
*
* @param string The string that matched.
* @param index The index of the instruction in the method.
*/
class StringMatch internal constructor(val string: String, val index: Int)
}
/**
* A builder for [Fingerprint].
*
* @property accessFlags The exact access flags using values of [AccessFlags].
* @property returnType The return type compared using [String.startsWith].
* @property parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
* @property opcodes An opcode pattern of the instructions. Wildcard or unknown opcodes can be specified by `null`.
* @property strings A list of the strings compared each using [String.contains].
* @property customBlock A custom condition for this fingerprint.
* @property fuzzyPatternScanThreshold The threshold for fuzzy pattern scanning.
*
* @constructor Create a new [FingerprintBuilder].
*/
class FingerprintBuilder internal constructor(
private val fuzzyPatternScanThreshold: Int = 0,
) {
private var accessFlags: Int? = null
private var returnType: String? = null
private var parameters: List<String>? = null
private var opcodes: List<Opcode?>? = null
private var strings: List<String>? = null
private var customBlock: ((method: Method, classDef: ClassDef) -> Boolean)? = null
/**
* Set the access flags.
*
* @param accessFlags The exact access flags using values of [AccessFlags].
*/
fun accessFlags(accessFlags: Int) {
this.accessFlags = accessFlags
}
/**
* Set the access flags.
*
* @param accessFlags The exact access flags using values of [AccessFlags].
*/
fun accessFlags(vararg accessFlags: AccessFlags) {
this.accessFlags = accessFlags.fold(0) { acc, it -> acc or it.value }
}
/**
* Set the return type.
*
* @param returnType The return type compared using [String.startsWith].
*/
fun returns(returnType: String) {
this.returnType = returnType
}
/**
* Set the parameters.
*
* @param parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
*/
fun parameters(vararg parameters: String) {
this.parameters = parameters.toList()
}
/**
* Set the opcodes.
*
* @param opcodes An opcode pattern of instructions.
* Wildcard or unknown opcodes can be specified by `null`.
*/
fun opcodes(vararg opcodes: Opcode?) {
this.opcodes = opcodes.toList()
}
/**
* Set the opcodes.
*
* @param instructions A list of instructions or opcode names in SMALI format.
* - Wildcard or unknown opcodes can be specified by `null`.
* - Empty lines are ignored.
* - Each instruction must be on a new line.
* - The opcode name is enough, no need to specify the operands.
*
* @throws Exception If an unknown opcode is used.
*/
fun opcodes(instructions: String) {
this.opcodes = instructions.trimIndent().split("\n").filter {
it.isNotBlank()
}.map {
// Remove any operands.
val name = it.split(" ", limit = 1).first().trim()
if (name == "null") return@map null
opcodesByName[name] ?: throw Exception("Unknown opcode: $name")
}
}
/**
* Set the strings.
*
* @param strings A list of strings compared each using [String.contains].
*/
fun strings(vararg strings: String) {
this.strings = strings.toList()
}
/**
* Set a custom condition for this fingerprint.
*
* @param customBlock A custom condition for this fingerprint.
*/
fun custom(customBlock: (method: Method, classDef: ClassDef) -> Boolean) {
this.customBlock = customBlock
}
internal fun build() = Fingerprint(
accessFlags,
returnType,
parameters,
opcodes,
strings,
customBlock,
fuzzyPatternScanThreshold,
)
private companion object {
val opcodesByName = Opcode.entries.associateBy { it.name }
}
}
/**
* Create a [Fingerprint].
*
* @param fuzzyPatternScanThreshold The threshold for fuzzy pattern scanning. Default is 0.
* @param block The block to build the [Fingerprint].
*
* @return The created [Fingerprint].
*/
fun fingerprint(
fuzzyPatternScanThreshold: Int = 0,
block: FingerprintBuilder.() -> Unit,
) = FingerprintBuilder(fuzzyPatternScanThreshold).apply(block).build()

View File

@@ -0,0 +1,219 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package app.revanced.patcher
import app.revanced.patcher.dex.mutable.MutableClassDef.Companion.toMutable
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.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.util.MethodUtil
import kotlin.collections.any
import kotlin.properties.ReadOnlyProperty
fun Iterable<ClassDef>.anyClassDef(predicate: ClassDef.() -> Boolean) =
any(predicate)
fun ClassDef.anyMethod(predicate: Method.() -> Boolean) =
methods.any(predicate)
fun ClassDef.anyDirectMethod(predicate: Method.() -> Boolean) =
directMethods.any(predicate)
fun ClassDef.anyVirtualMethod(predicate: Method.() -> Boolean) =
virtualMethods.any(predicate)
fun ClassDef.anyField(predicate: Field.() -> Boolean) =
fields.any(predicate)
fun ClassDef.anyInstanceField(predicate: Field.() -> Boolean) =
instanceFields.any(predicate)
fun ClassDef.anyStaticField(predicate: Field.() -> Boolean) =
staticFields.any(predicate)
fun ClassDef.anyInterface(predicate: String.() -> Boolean) =
interfaces.any(predicate)
fun ClassDef.anyAnnotation(predicate: Annotation.() -> Boolean) =
annotations.any(predicate)
fun Method.implementation(predicate: MethodImplementation.() -> Boolean) =
implementation?.predicate() ?: false
fun Method.anyParameter(predicate: MethodParameter.() -> Boolean) =
parameters.any(predicate)
fun Method.anyParameterType(predicate: CharSequence.() -> Boolean) =
parameterTypes.any(predicate)
fun Method.anyAnnotation(predicate: Annotation.() -> Boolean) =
annotations.any(predicate)
fun Method.anyHiddenApiRestriction(predicate: HiddenApiRestriction.() -> Boolean) =
hiddenApiRestrictions.any(predicate)
fun MethodImplementation.anyInstruction(predicate: Instruction.() -> Boolean) =
instructions.any(predicate)
fun MethodImplementation.anyTryBlock(predicate: TryBlock<out ExceptionHandler>.() -> Boolean) =
tryBlocks.any(predicate)
fun MethodImplementation.anyDebugItem(predicate: Any.() -> Boolean) =
debugItems.any(predicate)
fun Iterable<Instruction>.anyInstruction(predicate: Instruction.() -> Boolean) =
any(predicate)
fun BytecodePatchContext.firstClassDefOrNull(predicate: ClassDef.() -> Boolean) =
classDefs.firstOrNull { predicate(it) }
fun BytecodePatchContext.firstClassDef(predicate: ClassDef.() -> Boolean) =
requireNotNull(firstClassDefOrNull(predicate))
fun BytecodePatchContext.firstMutableClassDefOrNull(predicate: ClassDef.() -> Boolean) =
firstClassDefOrNull(predicate)?.mutable()
fun BytecodePatchContext.firstMutableClassDef(predicate: ClassDef.() -> Boolean) =
requireNotNull(firstMutableClassDefOrNull(predicate))
fun BytecodePatchContext.firstMethodOrNull(predicate: Method.() -> Boolean) =
classDefs.asSequence().flatMap { it.methods.asSequence() }
.firstOrNull { predicate(it) }
fun BytecodePatchContext.firstMethod(predicate: Method.() -> Boolean) =
requireNotNull(firstMethodOrNull(predicate))
fun BytecodePatchContext.firstMutableMethodOrNull(predicate: Method.() -> Boolean): MutableMethod? {
classDefs.forEach { classDef ->
classDef.methods.forEach { method ->
if (predicate(method)) return classDef.mutable().methods.first {
MethodUtil.methodSignaturesMatch(it, method)
}
}
}
return null
}
fun BytecodePatchContext.firstMutableMethod(predicate: Method.() -> Boolean) =
requireNotNull(firstMutableMethodOrNull(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")
}
}
abstract class Matcher<T> : MutableList<T.() -> Boolean> by mutableListOf() {
var matchIndex = -1
protected set
abstract operator fun invoke(haystack: Iterable<T>): Boolean
}
open class SequentialMatcher<T> internal constructor() : Matcher<T>() {
override operator fun invoke(haystack: Iterable<T>) = true
}
class CaptureStringIndices<T> internal constructor() : Matcher<T>() {
val capturedStrings = mutableMapOf<String>()
override operator fun invoke(haystack: Iterable<T>) {
}
}
fun <T> matchSequentially() = SequentialMatcher<T>()
fun <T> sequentialMatcher(builder: MutableList<T.() -> Boolean>.() -> Unit) =
SequentialMatcher<T>().apply(builder)
fun <T> Iterable<T>.matchSequentially(builder: MutableList<T.() -> Boolean>.() -> Unit) =
sequentialMatcher(builder)(this)

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.util.proxy.mutableTypes
package app.revanced.patcher.dex.mutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableAnnotationElement.Companion.toMutable
import com.android.tools.smali.dexlib2.base.BaseAnnotation
import com.android.tools.smali.dexlib2.iface.Annotation

View File

@@ -1,7 +1,9 @@
package app.revanced.patcher.util.proxy.mutableTypes
package app.revanced.patcher.dex.mutable
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
import app.revanced.patcher.dex.mutable.encodedValue.MutableEncodedValue
import app.revanced.patcher.dex.mutable.encodedValue.MutableEncodedValue.Companion.toMutable
import app.revanced.patcher.dex.mutable.encodedValue.MutableEncodedValue
import app.revanced.patcher.dex.mutable.encodedValue.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.base.BaseAnnotationElement
import com.android.tools.smali.dexlib2.iface.AnnotationElement
import com.android.tools.smali.dexlib2.iface.value.EncodedValue

View File

@@ -1,14 +1,14 @@
package app.revanced.patcher.util.proxy.mutableTypes
package app.revanced.patcher.dex.mutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableField.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableMethod.Companion.toMutable
import com.android.tools.smali.dexlib2.base.reference.BaseTypeReference
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.util.FieldUtil
import com.android.tools.smali.dexlib2.util.MethodUtil
class MutableClass(classDef: ClassDef) :
class MutableClassDef(classDef: ClassDef) :
BaseTypeReference(),
ClassDef {
// Class
@@ -73,6 +73,6 @@ class MutableClass(classDef: ClassDef) :
override fun getMethods(): MutableSet<MutableMethod> = _methods
companion object {
fun ClassDef.toMutable(): MutableClass = MutableClass(this)
fun ClassDef.toMutable(): MutableClassDef = MutableClassDef(this)
}
}

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher.util.proxy.mutableTypes
package app.revanced.patcher.dex.mutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.dex.mutable.encodedValue.MutableEncodedValue
import app.revanced.patcher.dex.mutable.encodedValue.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.HiddenApiRestriction
import com.android.tools.smali.dexlib2.base.reference.BaseFieldReference
import com.android.tools.smali.dexlib2.iface.Field

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.proxy.mutableTypes
package app.revanced.patcher.dex.mutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethodParameter.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableMethodParameter.Companion.toMutable
import com.android.tools.smali.dexlib2.HiddenApiRestriction
import com.android.tools.smali.dexlib2.base.reference.BaseMethodReference
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.util.proxy.mutableTypes
package app.revanced.patcher.dex.mutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableAnnotation.Companion.toMutable
import com.android.tools.smali.dexlib2.base.BaseMethodParameter
import com.android.tools.smali.dexlib2.iface.MethodParameter

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableAnnotationElement.Companion.toMutable
import com.android.tools.smali.dexlib2.base.value.BaseAnnotationEncodedValue
import com.android.tools.smali.dexlib2.iface.AnnotationElement
import com.android.tools.smali.dexlib2.iface.value.AnnotationEncodedValue

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
import app.revanced.patcher.dex.mutable.encodedValue.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.base.value.BaseArrayEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ArrayEncodedValue
import com.android.tools.smali.dexlib2.iface.value.EncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseBooleanEncodedValue
import com.android.tools.smali.dexlib2.iface.value.BooleanEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseByteEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseCharEncodedValue
import com.android.tools.smali.dexlib2.iface.value.CharEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseDoubleEncodedValue
import com.android.tools.smali.dexlib2.iface.value.DoubleEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.ValueType
import com.android.tools.smali.dexlib2.iface.value.*

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseEnumEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.FieldReference

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.ValueType
import com.android.tools.smali.dexlib2.base.value.BaseFieldEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseFloatEncodedValue
import com.android.tools.smali.dexlib2.iface.value.FloatEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseIntEncodedValue
import com.android.tools.smali.dexlib2.iface.value.IntEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseLongEncodedValue
import com.android.tools.smali.dexlib2.iface.value.LongEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseMethodEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodReference

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseMethodHandleEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodHandleReference

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseMethodTypeEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodProtoReference

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseNullEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseShortEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ShortEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseStringEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
package app.revanced.patcher.dex.mutable.encodedValue
import com.android.tools.smali.dexlib2.base.value.BaseTypeEncodedValue
import com.android.tools.smali.dexlib2.iface.value.TypeEncodedValue

View File

@@ -1,11 +0,0 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
/**
* Create a label for the instruction at given index.
*
* @param index The index to create the label for the instruction at.
* @return The label.
*/
fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index)

View File

@@ -0,0 +1,14 @@
package app.revanced.patcher.extensions
import com.android.tools.smali.dexlib2.iface.instruction.DualReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.Reference
@Suppress("UNCHECKED_CAST")
fun <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) =
((this as? DualReferenceInstruction)?.reference2 as? T)?.predicate() ?: false

View File

@@ -1,434 +0,0 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patcher.util.smali.toInstruction
import app.revanced.patcher.util.smali.toInstructions
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
import com.android.tools.smali.dexlib2.builder.Label
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.builder.instruction.*
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.MethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
object InstructionExtensions {
/**
* Add instructions to a method at the given index.
*
* @param index The index to add the instructions at.
* @param instructions The instructions to add.
*/
fun MutableMethodImplementation.addInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) = instructions.asReversed().forEach { addInstruction(index, it) }
/**
* Add instructions to a method.
* The instructions will be added at the end of the method.
*
* @param instructions The instructions to add.
*/
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) =
instructions.forEach { addInstruction(it) }
/**
* Remove instructions from a method at the given index.
*
* @param index The index to remove the instructions at.
* @param count The amount of instructions to remove.
*/
fun MutableMethodImplementation.removeInstructions(
index: Int,
count: Int,
) = repeat(count) {
removeInstruction(index)
}
/**
* Remove the first instructions from a method.
*
* @param count The amount of instructions to remove.
*/
fun MutableMethodImplementation.removeInstructions(count: Int) = removeInstructions(0, count)
/**
* Replace instructions at the given index with the given instructions.
* The amount of instructions to replace is the amount of instructions in the given list.
*
* @param index The index to replace the instructions at.
* @param instructions The instructions to replace the instructions with.
*/
fun MutableMethodImplementation.replaceInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) {
// Remove the instructions at the given index.
removeInstructions(index, instructions.size)
// Add the instructions at the given index.
addInstructions(index, instructions)
}
/**
* Add an instruction to a method at the given index.
*
* @param index The index to add the instruction at.
* @param instruction The instruction to add.
*/
fun MutableMethod.addInstruction(
index: Int,
instruction: BuilderInstruction,
) = implementation!!.addInstruction(index, instruction)
/**
* Add an instruction to a method.
*
* @param instruction The instructions to add.
*/
fun MutableMethod.addInstruction(instruction: BuilderInstruction) = implementation!!.addInstruction(instruction)
/**
* Add an instruction to a method at the given index.
*
* @param index The index to add the instruction at.
* @param smaliInstructions The instruction to add.
*/
fun MutableMethod.addInstruction(
index: Int,
smaliInstructions: String,
) = implementation!!.addInstruction(index, smaliInstructions.toInstruction(this))
/**
* Add an instruction to a method.
*
* @param smaliInstructions The instruction to add.
*/
fun MutableMethod.addInstruction(smaliInstructions: String) = implementation!!.addInstruction(smaliInstructions.toInstruction(this))
/**
* Add instructions to a method at the given index.
*
* @param index The index to add the instructions at.
* @param instructions The instructions to add.
*/
fun MutableMethod.addInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) = implementation!!.addInstructions(index, instructions)
/**
* Add instructions to a method.
*
* @param instructions The instructions to add.
*/
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) = implementation!!.addInstructions(instructions)
/**
* Add instructions to a method.
*
* @param smaliInstructions The instructions to add.
*/
fun MutableMethod.addInstructions(
index: Int,
smaliInstructions: String,
) = implementation!!.addInstructions(index, smaliInstructions.toInstructions(this))
/**
* Add instructions to a method.
*
* @param smaliInstructions The instructions to add.
*/
fun MutableMethod.addInstructions(smaliInstructions: String) = implementation!!.addInstructions(smaliInstructions.toInstructions(this))
/**
* Add instructions to a method at the given index.
*
* @param index The index to add the instructions at.
* @param smaliInstructions The instructions to add.
* @param externalLabels A list of [ExternalLabel] for instructions outside of [smaliInstructions].
*/
// Special function for adding instructions with external labels.
fun MutableMethod.addInstructionsWithLabels(
index: Int,
smaliInstructions: String,
vararg externalLabels: ExternalLabel,
) {
// Create reference dummy instructions for the instructions.
val nopSmali =
StringBuilder(smaliInstructions).also { builder ->
externalLabels.forEach { (name, _) ->
builder.append("\n:$name\nnop")
}
}.toString()
// Compile the instructions with the dummy labels
val compiledInstructions = nopSmali.toInstructions(this)
// Add the compiled list of instructions to the method.
addInstructions(
index,
compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size),
)
implementation!!.apply {
this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size)
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
// If the compiled instruction is not an offset instruction, skip it.
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
/**
* Create a new label for the instruction
* and replace it with the label of the [compiledInstruction] at [compiledInstructionIndex].
*/
fun Instruction.makeNewLabel() {
fun replaceOffset(
i: BuilderOffsetInstruction,
label: Label,
): BuilderOffsetInstruction {
return when (i) {
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
is BuilderInstruction22t ->
BuilderInstruction22t(
i.opcode,
i.registerA,
i.registerB,
label,
)
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
else -> throw IllegalStateException(
"A non-offset instruction was given, this should never happen!",
)
}
}
// Create the final label.
val label = newLabelForIndex(this@apply.instructions.indexOf(this))
// Create the final instruction with the new label.
val newInstruction =
replaceOffset(
compiledInstruction,
label,
)
// Replace the instruction pointing to the dummy label
// with the new instruction pointing to the real instruction.
replaceInstruction(index + compiledInstructionIndex, newInstruction)
}
// If the compiled instruction targets its own instruction,
// which means it points to some of its own, simply an offset has to be applied.
val labelIndex = compiledInstruction.target.location.index
if (labelIndex < compiledInstructions.size - externalLabels.size) {
// Get the targets index (insertion index + the index of the dummy instruction).
this.instructions[index + labelIndex].makeNewLabel()
return@forEachIndexed
}
// Since the compiled instruction points to a dummy instruction,
// we can find the real instruction which it was created for by calculation.
// Get the index of the instruction in the externalLabels list
// which the dummy instruction was created for.
// This works because we created the dummy instructions in the same order as the externalLabels list.
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
instruction.makeNewLabel()
}
}
}
/**
* Remove an instruction at the given index.
*
* @param index The index to remove the instruction at.
*/
fun MutableMethod.removeInstruction(index: Int) = implementation!!.removeInstruction(index)
/**
* Remove instructions at the given index.
*
* @param index The index to remove the instructions at.
* @param count The amount of instructions to remove.
*/
fun MutableMethod.removeInstructions(
index: Int,
count: Int,
) = implementation!!.removeInstructions(index, count)
/**
* Remove instructions at the given index.
*
* @param count The amount of instructions to remove.
*/
fun MutableMethod.removeInstructions(count: Int) = implementation!!.removeInstructions(count)
/**
* Replace an instruction at the given index.
*
* @param index The index to replace the instruction at.
* @param instruction The instruction to replace the instruction with.
*/
fun MutableMethod.replaceInstruction(
index: Int,
instruction: BuilderInstruction,
) = implementation!!.replaceInstruction(index, instruction)
/**
* Replace an instruction at the given index.
*
* @param index The index to replace the instruction at.
* @param smaliInstruction The smali instruction to replace the instruction with.
*/
fun MutableMethod.replaceInstruction(
index: Int,
smaliInstruction: String,
) = implementation!!.replaceInstruction(index, smaliInstruction.toInstruction(this))
/**
* Replace instructions at the given index.
*
* @param index The index to replace the instructions at.
* @param instructions The instructions to replace the instructions with.
*/
fun MutableMethod.replaceInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) = implementation!!.replaceInstructions(index, instructions)
/**
* Replace instructions at the given index.
*
* @param index The index to replace the instructions at.
* @param smaliInstructions The smali instructions to replace the instructions with.
*/
fun MutableMethod.replaceInstructions(
index: Int,
smaliInstructions: String,
) = implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this))
/**
* Get an instruction at the given index.
*
* @param index The index to get the instruction at.
* @return The instruction.
*/
fun MethodImplementation.getInstruction(index: Int) = instructions.elementAt(index)
/**
* Get an instruction at the given index.
*
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction.
*/
@Suppress("UNCHECKED_CAST")
fun <T> MethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
/**
* Get an instruction at the given index.
*
* @param index The index to get the instruction at.
* @return The instruction.
*/
fun MutableMethodImplementation.getInstruction(index: Int): BuilderInstruction = instructions[index]
/**
* Get an instruction at the given index.
*
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction.
*/
@Suppress("UNCHECKED_CAST")
fun <T> MutableMethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @return The instruction or null if the method has no implementation.
*/
fun Method.getInstructionOrNull(index: Int): Instruction? = implementation?.getInstruction(index)
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @return The instruction.
*/
fun Method.getInstruction(index: Int): Instruction = getInstructionOrNull(index)!!
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction or null if the method has no implementation.
*/
fun <T> Method.getInstructionOrNull(index: Int): T? = implementation?.getInstruction<T>(index)
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction.
*/
fun <T> Method.getInstruction(index: Int): T = getInstructionOrNull<T>(index)!!
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @return The instruction or null if the method has no implementation.
*/
fun MutableMethod.getInstructionOrNull(index: Int): BuilderInstruction? = implementation?.getInstruction(index)
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @return The instruction.
*/
fun MutableMethod.getInstruction(index: Int): BuilderInstruction = getInstructionOrNull(index)!!
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction or null if the method has no implementation.
*/
fun <T> MutableMethod.getInstructionOrNull(index: Int): T? = implementation?.getInstruction<T>(index)
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction.
*/
fun <T> MutableMethod.getInstruction(index: Int): T = getInstructionOrNull<T>(index)!!
/**
* The instructions of a method.
* @return The instructions or null if the method has no implementation.
*/
val Method.instructionsOrNull: Iterable<Instruction>? get() = implementation?.instructions
/**
* The instructions of a method.
* @return The instructions.
*/
val Method.instructions: Iterable<Instruction> get() = instructionsOrNull!!
/**
* The instructions of a method.
* @return The instructions or null if the method has no implementation.
*/
val MutableMethod.instructionsOrNull: MutableList<BuilderInstruction>? get() = implementation?.instructions
/**
* The instructions of a method.
* @return The instructions.
*/
val MutableMethod.instructions: MutableList<BuilderInstruction> get() = instructionsOrNull!!
}

View File

@@ -0,0 +1,384 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.dex.mutable.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patcher.util.smali.toInstructions
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
import com.android.tools.smali.dexlib2.builder.Label
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction10t
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction20t
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21t
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction22t
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction30t
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction31t
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.MethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import kotlin.collections.forEach
import kotlin.collections.indexOf
/**
* Add instructions to a method at the given index.
*
* @param index The index to add the instructions at.
* @param instructions The instructions to add.
*/
fun MutableMethodImplementation.addInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) = instructions.asReversed().forEach { addInstruction(index, it) }
/**
* Add instructions to a method.
* The instructions will be added at the end of the method.
*
* @param instructions The instructions to add.
*/
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) =
instructions.forEach { addInstruction(it) }
/**
* Remove instructions from a method at the given index.
*
* @param index The index to remove the instructions at.
* @param count The amount of instructions to remove.
*/
fun MutableMethodImplementation.removeInstructions(
index: Int,
count: Int,
) = repeat(count) {
removeInstruction(index)
}
/**
* Remove the first instructions from a method.
*
* @param count The amount of instructions to remove.
*/
fun MutableMethodImplementation.removeInstructions(count: Int) = removeInstructions(0, count)
/**
* Replace instructions at the given index with the given instructions.
* The amount of instructions to replace is the amount of instructions in the given list.
*
* @param index The index to replace the instructions at.
* @param instructions The instructions to replace the instructions with.
*/
fun MutableMethodImplementation.replaceInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) {
// Remove the instructions at the given index.
removeInstructions(index, instructions.size)
// Add the instructions at the given index.
addInstructions(index, instructions)
}
/**
* Add instructions to a method at the given index.
*
* @param index The index to add the instructions at.
* @param instructions The instructions to add.
*/
fun MutableMethod.addInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) = implementation!!.addInstructions(index, instructions)
/**
* Add instructions to a method.
*
* @param instructions The instructions to add.
*/
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) =
implementation!!.addInstructions(instructions)
/**
* Add instructions to a method.
*
* @param smaliInstructions The instructions to add.
*/
fun MutableMethod.addInstructions(
index: Int,
smaliInstructions: String,
) = implementation!!.addInstructions(index, smaliInstructions.toInstructions(this))
/**
* Add instructions to a method.
*
* @param smaliInstructions The instructions to add.
*/
fun MutableMethod.addInstructions(smaliInstructions: String) =
implementation!!.addInstructions(smaliInstructions.toInstructions(this))
/**
* Add instructions to a method at the given index.
*
* @param index The index to add the instructions at.
* @param smaliInstructions The instructions to add.
* @param externalLabels A list of [ExternalLabel] for instructions outside of [smaliInstructions].
*/
// Special function for adding instructions with external labels.
fun MutableMethod.addInstructionsWithLabels(
index: Int,
smaliInstructions: String,
vararg externalLabels: ExternalLabel,
) {
// Create reference dummy instructions for the instructions.
val nopSmali =
StringBuilder(smaliInstructions).also { builder ->
externalLabels.forEach { (name, _) ->
builder.append("\n:$name\nnop")
}
}.toString()
// Compile the instructions with the dummy labels
val compiledInstructions = nopSmali.toInstructions(this)
// Add the compiled list of instructions to the method.
addInstructions(
index,
compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size),
)
implementation!!.apply {
this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size)
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
// If the compiled instruction is not an offset instruction, skip it.
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
/**
* Create a new label for the instruction
* and replace it with the label of the [compiledInstruction] at [compiledInstructionIndex].
*/
fun Instruction.makeNewLabel() {
fun replaceOffset(
i: BuilderOffsetInstruction,
label: Label,
): BuilderOffsetInstruction {
return when (i) {
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
is BuilderInstruction22t ->
BuilderInstruction22t(
i.opcode,
i.registerA,
i.registerB,
label,
)
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
else -> throw IllegalStateException(
"A non-offset instruction was given, this should never happen!",
)
}
}
// Create the final label.
val label = newLabelForIndex(this@apply.instructions.indexOf(this))
// Create the final instruction with the new label.
val newInstruction =
replaceOffset(
compiledInstruction,
label,
)
// Replace the instruction pointing to the dummy label
// with the new instruction pointing to the real instruction.
replaceInstruction(index + compiledInstructionIndex, newInstruction)
}
// If the compiled instruction targets its own instruction,
// which means it points to some of its own, simply an offset has to be applied.
val labelIndex = compiledInstruction.target.location.index
if (labelIndex < compiledInstructions.size - externalLabels.size) {
// Get the targets index (insertion index + the index of the dummy instruction).
this.instructions[index + labelIndex].makeNewLabel()
return@forEachIndexed
}
// Since the compiled instruction points to a dummy instruction,
// we can find the real instruction which it was created for by calculation.
// Get the index of the instruction in the externalLabels list
// which the dummy instruction was created for.
// This works because we created the dummy instructions in the same order as the externalLabels list.
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
instruction.makeNewLabel()
}
}
}
/**
* Remove instructions at the given index.
*
* @param index The index to remove the instructions at.
* @param count The amount of instructions to remove.
*/
fun MutableMethod.removeInstructions(
index: Int,
count: Int,
) = implementation!!.removeInstructions(index, count)
/**
* Remove instructions at the given index.
*
* @param count The amount of instructions to remove.
*/
fun MutableMethod.removeInstructions(count: Int) = implementation!!.removeInstructions(count)
/**
* Replace instructions at the given index.
*
* @param index The index to replace the instructions at.
* @param instructions The instructions to replace the instructions with.
*/
fun MutableMethod.replaceInstructions(
index: Int,
instructions: List<BuilderInstruction>,
) = implementation!!.replaceInstructions(index, instructions)
/**
* Replace instructions at the given index.
*
* @param index The index to replace the instructions at.
* @param smaliInstructions The smali instructions to replace the instructions with.
*/
fun MutableMethod.replaceInstructions(
index: Int,
smaliInstructions: String,
) = implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this))
/**
* Get an instruction at the given index.
*
* @param index The index to get the instruction at.
* @return The instruction.
*/
fun MethodImplementation.getInstruction(index: Int) = instructions.elementAt(index)
/**
* Get an instruction at the given index.
*
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction.
*/
@Suppress("UNCHECKED_CAST")
fun <T> MethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
/**
* Get an instruction at the given index.
*
* @param index The index to get the instruction at.
* @return The instruction.
*/
fun MutableMethodImplementation.getInstruction(index: Int): BuilderInstruction = instructions[index]
/**
* Get an instruction at the given index.
*
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction.
*/
@Suppress("UNCHECKED_CAST")
fun <T> MutableMethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @return The instruction or null if the method has no implementation.
*/
fun Method.getInstructionOrNull(index: Int): Instruction? = implementation?.getInstruction(index)
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @return The instruction.
*/
fun Method.getInstruction(index: Int): Instruction = getInstructionOrNull(index)!!
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction or null if the method has no implementation.
*/
fun <T> Method.getInstructionOrNull(index: Int): T? = implementation?.getInstruction<T>(index)
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction.
*/
fun <T> Method.getInstruction(index: Int): T = getInstructionOrNull<T>(index)!!
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @return The instruction or null if the method has no implementation.
*/
fun MutableMethod.getInstructionOrNull(index: Int): BuilderInstruction? = implementation?.getInstruction(index)
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @return The instruction.
*/
fun MutableMethod.getInstruction(index: Int): BuilderInstruction = getInstructionOrNull(index)!!
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction or null if the method has no implementation.
*/
fun <T> MutableMethod.getInstructionOrNull(index: Int): T? = implementation?.getInstruction<T>(index)
/**
* Get an instruction at the given index.
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction.
*/
fun <T> MutableMethod.getInstruction(index: Int): T = getInstructionOrNull<T>(index)!!
/**
* The instructions of a method.
* @return The instructions or null if the method has no implementation.
*/
val Method.instructionsOrNull: Iterable<Instruction>? get() = implementation?.instructions
/**
* The instructions of a method.
* @return The instructions.
*/
val Method.instructions: Iterable<Instruction> get() = instructionsOrNull!!
/**
* The instructions of a method.
* @return The instructions or null if the method has no implementation.
*/
val MutableMethod.instructionsOrNull: MutableList<BuilderInstruction>? get() = implementation?.instructions
/**
* The instructions of a method.
* @return The instructions.
*/
val MutableMethod.instructions: MutableList<BuilderInstruction> get() = instructionsOrNull!!
/**
* Create a label for the instruction at given index.
*
* @param index The index to create the label for the instruction at.
* @return The label.
*/
fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index)

View File

@@ -3,11 +3,11 @@ package app.revanced.patcher.patch
import app.revanced.patcher.InternalApi
import app.revanced.patcher.PatcherConfig
import app.revanced.patcher.PatcherResult
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
import app.revanced.patcher.dex.mutable.MutableClassDef
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 app.revanced.patcher.util.ProxyClassList
import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.Opcodes
import com.android.tools.smali.dexlib2.iface.ClassDef
@@ -21,7 +21,6 @@ import lanchon.multidexlib2.DexIO
import lanchon.multidexlib2.MultiDexIO
import lanchon.multidexlib2.RawDexIO
import java.io.Closeable
import java.io.FileFilter
import java.util.*
import java.util.logging.Logger
@@ -44,20 +43,18 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
/**
* The list of classes.
*/
val classes = ProxyClassList(
MultiDexIO.readDexFile(
true,
config.apkFile,
BasicDexFileNamer(),
null,
null,
).also { opcodes = it.opcodes }.classes.toMutableList(),
)
val classDefs = MultiDexIO.readDexFile(
true,
config.apkFile,
BasicDexFileNamer(),
null,
null,
).also { opcodes = it.opcodes }.classes.toMutableSet()
/**
* The lookup maps for methods and the class they are a member of from the [classes].
* The lookup maps for methods and the class they are a member of from the [classDefs].
*/
internal val lookupMaps by lazy { LookupMaps(classes) }
internal val lookupMaps by lazy { LookupMaps(classDefs) }
/**
* Merge the extension of [bytecodePatch] into the [BytecodePatchContext].
@@ -71,7 +68,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
val existingClass = lookupMaps.classesByType[classDef.type] ?: run {
logger.fine { "Adding class \"$classDef\"" }
classes += classDef
classDefs += classDef
lookupMaps.classesByType[classDef.type] = classDef
return@forEach
@@ -85,32 +82,21 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
return@let
}
classes -= existingClass
classes += mergedClass
classDefs -= existingClass
classDefs += mergedClass
}
}
} ?: logger.fine("No extension to merge")
}
/**
* Find a class with a predicate.
* Convert a [ClassDef] to a [MutableClassDef].
* If the [ClassDef] is already a [MutableClassDef], it is returned as is.
*
* @param predicate A predicate to match the class.
* @return A proxy for the first class that matches the predicate.
* @return The mutable version of the [ClassDef].
*/
fun classBy(predicate: (ClassDef) -> Boolean) =
classes.proxyPool.find { predicate(it.immutableClass) } ?: classes.find(predicate)?.let { proxy(it) }
/**
* Proxy the class to allow mutation.
*
* @param classDef The class to proxy.
*
* @return A proxy for the class.
*/
fun proxy(classDef: ClassDef) = classes.proxyPool.find {
it.immutableClass.type == classDef.type
} ?: ClassProxy(classDef).also { classes.proxyPool.add(it) }
fun ClassDef.mutable(): MutableClassDef =
this as? MutableClassDef ?: also(classDefs::remove).toMutable().also(classDefs::add)
/**
* Navigate a method.
@@ -145,13 +131,13 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
BasicDexFileNamer(),
object : DexFile {
override fun getClasses() =
this@BytecodePatchContext.classes.also(ProxyClassList::replaceClasses).toSet()
this@BytecodePatchContext.classDefs.toSet()
override fun getOpcodes() = this@BytecodePatchContext.opcodes
},
DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
) { _, entryName, _ -> logger.info { "Compiled $entryName" } }
}.listFiles(FileFilter { it.isFile })!!.map {
}.listFiles { it.isFile }!!.map {
PatcherResult.PatchedDexFile(it.name, it.inputStream())
}.toSet()
@@ -163,9 +149,9 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
/**
* A lookup map for methods and the class they are a member of and classes.
*
* @param classes The list of classes to create the lookup maps from.
* @param classDefs The list of classes to create the lookup maps from.
*/
internal class LookupMaps internal constructor(classes: List<ClassDef>) : Closeable {
internal class LookupMaps internal constructor(classDefs: Set<ClassDef>) : Closeable {
/**
* Methods associated by strings referenced in it.
*/
@@ -173,11 +159,11 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
// Lookup map for fast checking if a class exists by its type.
val classesByType = mutableMapOf<String, ClassDef>().apply {
classes.forEach { classDef -> put(classDef.type, classDef) }
classDefs.forEach { classDef -> put(classDef.type, classDef) }
}
init {
classes.forEach { classDef ->
classDefs.forEach { classDef ->
classDef.methods.forEach { method ->
val methodClassPair: MethodClassPair = method to classDef
@@ -206,7 +192,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
override fun close() {
lookupMaps.close()
classes.clear()
classDefs.clear()
}
}

View File

@@ -15,6 +15,7 @@ import java.lang.reflect.Modifier
import java.net.URLClassLoader
import java.util.function.Supplier
import java.util.jar.JarFile
import kotlin.properties.ReadOnlyProperty
typealias PackageName = String
typealias VersionName = String
@@ -87,8 +88,7 @@ sealed class Patch<C : PatchContext<*>>(
finalizeBlock?.invoke(context)
}
override fun toString() = name ?:
"Patch@${System.identityHashCode(this)}"
override fun toString() = name ?: "Patch@${System.identityHashCode(this)}"
}
internal fun Patch<*>.anyRecursively(
@@ -437,6 +437,21 @@ fun bytecodePatch(
block: BytecodePatchBuilder.() -> Unit = {},
) = BytecodePatchBuilder(name, description, use).buildPatch(block) as BytecodePatch
/**
* Create a [ReadOnlyProperty] that creates a new [BytecodePatch] with the name of the property.
*
* @param description The description of the patch.
* @param use Weather or not the patch should be used.
* @param block The block to build the patch.
*
* @return The created [ReadOnlyProperty] that creates a new [BytecodePatch].
*/
fun gettingBytecodePatch(
description: String? = null,
use: Boolean = true,
block: BytecodePatchBuilder.() -> Unit = {},
) = ReadOnlyProperty<Any?, BytecodePatch> { _, property -> bytecodePatch(property.name, description, use, block) }
/**
* A [RawResourcePatch] builder.
*
@@ -481,6 +496,21 @@ fun rawResourcePatch(
block: RawResourcePatchBuilder.() -> Unit = {},
) = RawResourcePatchBuilder(name, description, use).buildPatch(block) as RawResourcePatch
/**
* Create a [ReadOnlyProperty] that creates a new [RawResourcePatch] with the name of the property.
*
* @param description The description of the patch.
* @param use Weather or not the patch should be used.
* @param block The block to build the patch.
*
* @return The created [ReadOnlyProperty] that creates a new [RawResourcePatch].
*/
fun gettingRawResourcePatch(
description: String? = null,
use: Boolean = true,
block: RawResourcePatchBuilder.() -> Unit = {},
) = ReadOnlyProperty<Any?, RawResourcePatch> { _, property -> rawResourcePatch(property.name, description, use, block) }
/**
* A [ResourcePatch] builder.
*
@@ -526,6 +556,21 @@ fun resourcePatch(
block: ResourcePatchBuilder.() -> Unit = {},
) = ResourcePatchBuilder(name, description, use).buildPatch(block) as ResourcePatch
/**
* Create a [ReadOnlyProperty] that creates a new [ResourcePatch] with the name of the property.
*
* @param description The description of the patch.
* @param use Weather or not the patch should be used.
* @param block The block to build the patch.
*
* @return The created [ReadOnlyProperty] that creates a new [ResourcePatch].
*/
fun gettingResourcePatch(
description: String? = null,
use: Boolean = true,
block: ResourcePatchBuilder.() -> Unit = {},
) = ReadOnlyProperty<Any?, ResourcePatch> { _, property -> resourcePatch(property.name, description, use, block) }
/**
* An exception thrown when patching.
*

View File

@@ -7,12 +7,12 @@ import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny
import app.revanced.patcher.util.ClassMerger.Utils.isPublic
import app.revanced.patcher.util.ClassMerger.Utils.toPublic
import app.revanced.patcher.util.ClassMerger.Utils.traverseClassHierarchy
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableField
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableClassDef
import app.revanced.patcher.dex.mutable.MutableClassDef.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableField
import app.revanced.patcher.dex.mutable.MutableField.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableMethod
import app.revanced.patcher.dex.mutable.MutableMethod.Companion.toMutable
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.util.MethodUtil
@@ -175,18 +175,18 @@ internal object ClassMerger {
* @param callback function that is called for every class in the hierarchy
*/
fun BytecodePatchContext.traverseClassHierarchy(
targetClass: MutableClass,
callback: MutableClass.() -> Unit,
targetClass: MutableClassDef,
callback: MutableClassDef.() -> Unit,
) {
callback(targetClass)
targetClass.superclass ?: return
this.classBy { targetClass.superclass == it.type }?.mutableClass?.let {
classDefs.find { targetClass.superclass == it.type }?.mutable()?.let {
traverseClassHierarchy(it, callback)
}
}
fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable()
fun ClassDef.asMutableClass() = if (this is MutableClassDef) this else this.toMutable()
/**
* Check if the [AccessFlags.PUBLIC] flag is set.

View File

@@ -2,10 +2,9 @@
package app.revanced.patcher.util
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.util.MethodNavigator.NavigateException
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.dex.mutable.MutableMethod
import app.revanced.patcher.extensions.instructionsOrNull
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
@@ -80,7 +79,7 @@ class MethodNavigator internal constructor(
*
* @return The last navigated method mutably.
*/
fun stop() = classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
fun stop() = classDefs.find(matchesCurrentMethodReferenceDefiningClass)!!.mutable().firstMethodBySignature
as MutableMethod
/**
@@ -95,7 +94,7 @@ class MethodNavigator internal constructor(
*
* @return The last navigated method immutably.
*/
fun original(): Method = classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
fun original(): Method = classDefs.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
/**
* Predicate to match the class defining the current method reference.

View File

@@ -1,29 +0,0 @@
package app.revanced.patcher.util
import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.iface.ClassDef
/**
* A list of classes and proxies.
*
* @param classes The classes to be backed by proxies.
*/
class ProxyClassList internal constructor(classes: MutableList<ClassDef>) : MutableList<ClassDef> by classes {
internal val proxyPool = mutableListOf<ClassProxy>()
/**
* Replace all classes with their mutated versions.
*/
internal fun replaceClasses() =
proxyPool.removeIf { proxy ->
// If the proxy is unused, return false to keep it in the proxies list.
if (!proxy.resolved) return@removeIf false
// If it has been used, replace the original class with the mutable class.
remove(proxy.immutableClass)
add(proxy.mutableClass)
// Return true to remove the proxy from the proxies list.
return@removeIf true
}
}

View File

@@ -1,35 +0,0 @@
package app.revanced.patcher.util.proxy
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import com.android.tools.smali.dexlib2.iface.ClassDef
/**
* A proxy class for a [ClassDef].
*
* A class proxy simply holds a reference to the original class
* and allocates a mutable clone for the original class if needed.
*
* @param immutableClass The class to proxy.
*/
class ClassProxy internal constructor(
val immutableClass: ClassDef,
) {
/**
* Weather the proxy was actually used.
*/
internal var resolved = false
/**
* The mutable clone of the original class.
*
* Note: This is only allocated if the proxy is actually used.
*/
val mutableClass by lazy {
resolved = true
if (immutableClass is MutableClass) {
immutableClass
} else {
MutableClass(immutableClass)
}
}
}

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.util.smali
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.dex.mutable.MutableMethod
import app.revanced.patcher.extensions.instructions
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcodes
import com.android.tools.smali.dexlib2.builder.BuilderInstruction

View File

@@ -2,7 +2,6 @@ package app.revanced.patcher
import app.revanced.patcher.patch.*
import app.revanced.patcher.patch.BytecodePatchContext.LookupMaps
import app.revanced.patcher.util.ProxyClassList
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import io.mockk.*
@@ -34,7 +33,7 @@ internal object PatcherTest {
Logger.getAnonymousLogger(),
)
every { context.bytecodeContext.classes } returns mockk(relaxed = true)
every { context.bytecodeContext.classDefs } returns mockk(relaxed = true)
every { this@mockk() } answers { callOriginal() }
}
}
@@ -165,7 +164,7 @@ internal object PatcherTest {
@Test
fun `matches fingerprint`() {
every { patcher.context.bytecodeContext.classes } returns ProxyClassList(
every { patcher.context.bytecodeContext.classDefs } returns ProxyClassDefSet(
mutableListOf(
ImmutableClassDef(
"class",
@@ -198,8 +197,8 @@ internal object PatcherTest {
val patches = setOf(
bytecodePatch {
execute {
fingerprint.match(classes.first().methods.first())
fingerprint2.match(classes.first())
fingerprint.match(classDefs.first().methods.first())
fingerprint2.match(classDefs.first())
fingerprint3.originalClassDef
}
},
@@ -219,7 +218,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.classes)
every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classDefs)
every { with(patcher.context.bytecodeContext) { mergeExtension(any<BytecodePatch>()) } } just runs
return runBlocking { patcher().toList() }

View File

@@ -1,15 +1,7 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.dex.mutable.MutableMethod
import app.revanced.patcher.dex.mutable.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.ExternalLabel
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode

View File

@@ -1,10 +1,10 @@
package app.revanced.patcher.util.smali
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.dex.mutable.MutableMethod.Companion.toMutable
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.newLabel
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.BuilderInstruction