mirror of
https://github.com/revanced/revanced-patcher
synced 2025-04-01 10:49:19 +02:00
chore: merge branch dev
to main
(#191)
This commit is contained in:
commit
a379b69eeb
29
CHANGELOG.md
29
CHANGELOG.md
@ -1,3 +1,32 @@
|
|||||||
|
## [11.0.2-dev.4](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.3...v11.0.2-dev.4) (2023-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* do not load annotations as patches ([519359a](https://github.com/revanced/revanced-patcher/commit/519359a9eb0e9dfa390c5016e9fe4a7490b8ab18))
|
||||||
|
|
||||||
|
## [11.0.2-dev.3](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.2...v11.0.2-dev.3) (2023-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* resolve fingerprints using method maps ([#185](https://github.com/revanced/revanced-patcher/issues/185)) ([d718134](https://github.com/revanced/revanced-patcher/commit/d718134ab26423e02708e01eba711737f9260ba0))
|
||||||
|
|
||||||
|
## [11.0.2-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.1...v11.0.2-dev.2) (2023-06-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use `versionCode` if `versionName` is unavailable ([6e1b647](https://github.com/revanced/revanced-patcher/commit/6e1b6479b677657c226693e9cc6b63f4ef2ee060))
|
||||||
|
|
||||||
|
## [11.0.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.1...v11.0.2-dev.1) (2023-06-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* catch exceptions from closing patches ([d5d6f85](https://github.com/revanced/revanced-patcher/commit/d5d6f85084c03ed9c776632823ca12394a716167))
|
||||||
|
* only close succeeded patches ([b8151eb](https://github.com/revanced/revanced-patcher/commit/b8151ebccb5b27dd9e06fa63235cf9baeef1c0ee))
|
||||||
|
|
||||||
## [11.0.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1) (2023-06-12)
|
## [11.0.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1) (2023-06-12)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 11.0.1
|
version = 11.0.2-dev.4
|
||||||
|
@ -4,7 +4,8 @@ import app.revanced.patcher.data.Context
|
|||||||
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
|
import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap
|
||||||
import app.revanced.patcher.patch.*
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patcher.util.VersionReader
|
import app.revanced.patcher.util.VersionReader
|
||||||
import brut.androlib.Androlib
|
import brut.androlib.Androlib
|
||||||
@ -23,6 +24,7 @@ import lanchon.multidexlib2.MultiDexIO
|
|||||||
import org.jf.dexlib2.Opcodes
|
import org.jf.dexlib2.Opcodes
|
||||||
import org.jf.dexlib2.iface.DexFile
|
import org.jf.dexlib2.iface.DexFile
|
||||||
import org.jf.dexlib2.writer.io.MemoryDataStore
|
import org.jf.dexlib2.writer.io.MemoryDataStore
|
||||||
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
@ -248,6 +250,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
axmlParser, AndrolibResources().resXmlSerializer
|
axmlParser, AndrolibResources().resXmlSerializer
|
||||||
).decodeManifest(
|
).decodeManifest(
|
||||||
extInputFile.directory.getFileInput("AndroidManifest.xml"),
|
extInputFile.directory.getFileInput("AndroidManifest.xml"),
|
||||||
|
// Older Android versions do not support OutputStream.nullOutputStream()
|
||||||
object : OutputStream() {
|
object : OutputStream() {
|
||||||
override fun write(b: Int) {
|
override fun write(b: Int) {
|
||||||
// do nothing
|
// do nothing
|
||||||
@ -260,7 +263,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
// read of the resourceTable which is created by reading the manifest file
|
// read of the resourceTable which is created by reading the manifest file
|
||||||
context.packageMetadata.let { metadata ->
|
context.packageMetadata.let { metadata ->
|
||||||
metadata.packageName = resourceTable.currentResPackage.name
|
metadata.packageName = resourceTable.currentResPackage.name
|
||||||
metadata.packageVersion = resourceTable.versionInfo.versionName
|
metadata.packageVersion = resourceTable.versionInfo.versionName ?: resourceTable.versionInfo.versionCode
|
||||||
metadata.metaInfo.versionInfo = resourceTable.versionInfo
|
metadata.metaInfo.versionInfo = resourceTable.versionInfo
|
||||||
metadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
metadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
||||||
}
|
}
|
||||||
@ -320,10 +323,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
context.resourceContext
|
context.resourceContext
|
||||||
} else {
|
} else {
|
||||||
context.bytecodeContext.also { context ->
|
context.bytecodeContext.also { context ->
|
||||||
(patchInstance as BytecodePatch).fingerprints?.resolve(
|
(patchInstance as BytecodePatch).fingerprints?.resolveUsingLookupMap(context)
|
||||||
context,
|
|
||||||
context.classes.classes
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,14 +343,17 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
return sequence {
|
return sequence {
|
||||||
if (mergeIntegrations) context.integrations.merge(logger)
|
if (mergeIntegrations) context.integrations.merge(logger)
|
||||||
|
|
||||||
|
logger.trace("Initialize lookup maps for method MethodFingerprint resolution")
|
||||||
|
|
||||||
|
MethodFingerprint.initializeFingerprintResolutionLookupMaps(context.bytecodeContext)
|
||||||
|
|
||||||
// prevent from decoding the manifest twice if it is not needed
|
// prevent from decoding the manifest twice if it is not needed
|
||||||
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
|
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
|
||||||
|
|
||||||
logger.trace("Executing all patches")
|
logger.info("Executing patches")
|
||||||
|
|
||||||
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
|
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
|
||||||
|
|
||||||
try {
|
|
||||||
context.patches.forEach { patch ->
|
context.patches.forEach { patch ->
|
||||||
val patchResult = executePatch(patch, executedPatches)
|
val patchResult = executePatch(patch, executedPatches)
|
||||||
|
|
||||||
@ -360,12 +363,30 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
Result.failure(patchResult.error()!!)
|
Result.failure(patchResult.error()!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This prints before the patch really finishes in case it is a Closeable
|
||||||
|
// because the Closeable is closed after all patches are executed.
|
||||||
yield(patch.patchName to result)
|
yield(patch.patchName to result)
|
||||||
|
|
||||||
if (stopOnError && patchResult.isError()) return@sequence
|
if (stopOnError && patchResult.isError()) return@sequence
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
executedPatches.values.reversed().forEach { (patch, _) ->
|
executedPatches.values
|
||||||
patch.close()
|
.filter(ExecutedPatch::success)
|
||||||
|
.map(ExecutedPatch::patchInstance)
|
||||||
|
.filterIsInstance(Closeable::class.java)
|
||||||
|
.asReversed().forEach {
|
||||||
|
try {
|
||||||
|
it.close()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
val patchName = (it as Patch<Context>).javaClass.patchName
|
||||||
|
|
||||||
|
logger.error("Failed to close '$patchName': ${exception.stackTraceToString()}")
|
||||||
|
|
||||||
|
yield(patchName to Result.failure(exception))
|
||||||
|
|
||||||
|
// This is not failsafe. If a patch throws an exception while closing,
|
||||||
|
// the other patches that depend on it may fail.
|
||||||
|
if (stopOnError) return@sequence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ import app.revanced.patcher.data.BytecodeContext
|
|||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||||
import app.revanced.patcher.fingerprint.Fingerprint
|
import app.revanced.patcher.fingerprint.Fingerprint
|
||||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||||
|
import app.revanced.patcher.patch.PatchResultError
|
||||||
import app.revanced.patcher.util.proxy.ClassProxy
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
|
import org.jf.dexlib2.AccessFlags
|
||||||
import org.jf.dexlib2.Opcode
|
import org.jf.dexlib2.Opcode
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
import org.jf.dexlib2.iface.Method
|
import org.jf.dexlib2.iface.Method
|
||||||
@ -12,19 +14,21 @@ import org.jf.dexlib2.iface.instruction.Instruction
|
|||||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
import org.jf.dexlib2.iface.reference.StringReference
|
import org.jf.dexlib2.iface.reference.StringReference
|
||||||
import org.jf.dexlib2.util.MethodUtil
|
import org.jf.dexlib2.util.MethodUtil
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
||||||
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
||||||
|
private typealias MethodClassPair = Pair<Method, ClassDef>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the [MethodFingerprint] for a method.
|
* A fingerprint to resolve methods.
|
||||||
* @param returnType The return type of the method.
|
*
|
||||||
* @param accessFlags The access flags of the method.
|
* @param returnType The method's return type compared using [String.startsWith].
|
||||||
* @param parameters The parameters of the method.
|
* @param accessFlags The method's exact access flags using values of [AccessFlags].
|
||||||
* @param opcodes The list of opcodes of the method.
|
* @param parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
|
||||||
* @param strings A list of strings which a method contains.
|
* @param opcodes An opcode pattern of the method's instructions. Wildcard or unknown opcodes can be specified by `null`.
|
||||||
|
* @param strings A list of the method's strings compared each using [String.contains].
|
||||||
* @param customFingerprint A custom condition for this fingerprint.
|
* @param customFingerprint A custom condition for this fingerprint.
|
||||||
* A `null` opcode is equals to an unknown opcode.
|
|
||||||
*/
|
*/
|
||||||
abstract class MethodFingerprint(
|
abstract class MethodFingerprint(
|
||||||
internal val returnType: String? = null,
|
internal val returnType: String? = null,
|
||||||
@ -40,6 +44,192 @@ abstract class MethodFingerprint(
|
|||||||
var result: MethodFingerprintResult? = null
|
var result: MethodFingerprintResult? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* A list of methods and the class they were found in.
|
||||||
|
*/
|
||||||
|
private val methods = mutableListOf<MethodClassPair>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for methods keyed to the methods access flags, return type and parameter.
|
||||||
|
*/
|
||||||
|
private val methodSignatureLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for methods keyed to the strings contained in the method.
|
||||||
|
*/
|
||||||
|
private val methodStringsLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a string based on the parameter reference types of this method.
|
||||||
|
*/
|
||||||
|
private fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
|
||||||
|
// Maximum parameters to use in the signature key.
|
||||||
|
// Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
|
||||||
|
// To keep the signature map from becoming needlessly bloated,
|
||||||
|
// group together in the same map entry all methods with the same access/return and 5 or more parameters.
|
||||||
|
// The value of 5 was chosen based on local performance testing and is not set in stone.
|
||||||
|
val maxSignatureParameters = 5
|
||||||
|
// Must append a unique value before the parameters to distinguish this key includes the parameters.
|
||||||
|
// If this is not appended, then methods with no parameters
|
||||||
|
// will collide with different keys that specify access/return but omit the parameters.
|
||||||
|
append("p:")
|
||||||
|
parameters.forEachIndexed { index, parameter ->
|
||||||
|
if (index >= maxSignatureParameters) return
|
||||||
|
append(parameter.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes lookup maps for [MethodFingerprint] resolution
|
||||||
|
* using attributes of methods such as the method signature or strings.
|
||||||
|
*
|
||||||
|
* @param context The [BytecodeContext] containing the classes to initialize the lookup maps with.
|
||||||
|
*/
|
||||||
|
internal fun initializeFingerprintResolutionLookupMaps(context: BytecodeContext) {
|
||||||
|
fun MutableMap<String, MutableList<MethodClassPair>>.add(
|
||||||
|
key: String,
|
||||||
|
methodClassPair: MethodClassPair
|
||||||
|
) {
|
||||||
|
var methodClassPairs = this[key]
|
||||||
|
|
||||||
|
methodClassPairs ?: run {
|
||||||
|
methodClassPairs = LinkedList<MethodClassPair>().also { this[key] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
methodClassPairs!!.add(methodClassPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methods.isNotEmpty()) throw PatchResultError("Map already initialized")
|
||||||
|
|
||||||
|
context.classes.classes.forEach { classDef ->
|
||||||
|
classDef.methods.forEach { method ->
|
||||||
|
val methodClassPair = method to classDef
|
||||||
|
|
||||||
|
// For fingerprints with no access or return type specified.
|
||||||
|
methods += methodClassPair
|
||||||
|
|
||||||
|
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
|
||||||
|
|
||||||
|
// Add <access><returnType> as the key.
|
||||||
|
methodSignatureLookupMap.add(accessFlagsReturnKey, methodClassPair)
|
||||||
|
|
||||||
|
// Add <access><returnType>[parameters] as the key.
|
||||||
|
methodSignatureLookupMap.add(
|
||||||
|
buildString {
|
||||||
|
append(accessFlagsReturnKey)
|
||||||
|
appendParameters(method.parameterTypes)
|
||||||
|
},
|
||||||
|
methodClassPair
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add strings contained in the method as the key.
|
||||||
|
method.implementation?.instructions?.forEach instructions@{ instruction ->
|
||||||
|
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO)
|
||||||
|
return@instructions
|
||||||
|
|
||||||
|
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||||
|
|
||||||
|
methodStringsLookupMap.add(string, methodClassPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a list of [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
|
||||||
|
*
|
||||||
|
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||||
|
* amount of time because they are resolved in sequence.
|
||||||
|
*
|
||||||
|
* For apps with many fingerprints, resolving performance can be improved by:
|
||||||
|
* - Slowest: Specify [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.
|
||||||
|
*/
|
||||||
|
internal fun Iterable<MethodFingerprint>.resolveUsingLookupMap(context: BytecodeContext) {
|
||||||
|
if (methods.isEmpty()) throw PatchResultError("lookup map not initialized")
|
||||||
|
|
||||||
|
for (fingerprint in this) {
|
||||||
|
fingerprint.resolveUsingLookupMap(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
|
||||||
|
*
|
||||||
|
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||||
|
* amount of time because they are resolved in sequence.
|
||||||
|
*
|
||||||
|
* For apps with many fingerprints, resolving performance can be improved by:
|
||||||
|
* - Slowest: Specify [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.
|
||||||
|
*/
|
||||||
|
internal fun MethodFingerprint.resolveUsingLookupMap(context: BytecodeContext): Boolean {
|
||||||
|
/**
|
||||||
|
* Lookup [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||||
|
*
|
||||||
|
* @return A list of [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.methodStringsLookup(): List<MethodClassPair>? {
|
||||||
|
strings?.forEach {
|
||||||
|
val methods = methodStringsLookupMap[it]
|
||||||
|
if (methods != null) return methods
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||||
|
*
|
||||||
|
* @return A list of [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.methodSignatureLookup(): List<MethodClassPair> {
|
||||||
|
if (accessFlags == null) return methods
|
||||||
|
|
||||||
|
var returnTypeValue = returnType
|
||||||
|
if (returnTypeValue == null) {
|
||||||
|
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
||||||
|
// Constructors always have void return type
|
||||||
|
returnTypeValue = "V"
|
||||||
|
} else {
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val key = buildString {
|
||||||
|
append(accessFlags)
|
||||||
|
append(returnTypeValue.first())
|
||||||
|
if (parameters != null) appendParameters(parameters)
|
||||||
|
}
|
||||||
|
return methodSignatureLookupMap[key]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] using a list of [MethodClassPair].
|
||||||
|
*
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.resolveUsingMethodClassPair(classMethods: Iterable<MethodClassPair>): Boolean {
|
||||||
|
classMethods.forEach { classAndMethod ->
|
||||||
|
if (resolve(context, classAndMethod.first, classAndMethod.second)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val methodsWithSameStrings = methodStringsLookup()
|
||||||
|
if (methodsWithSameStrings != null) if (resolveUsingMethodClassPair(methodsWithSameStrings)) return true
|
||||||
|
|
||||||
|
// No strings declared or none matched (partial matches are allowed).
|
||||||
|
// Use signature matching.
|
||||||
|
return resolveUsingMethodClassPair(methodSignatureLookup())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||||
*
|
*
|
||||||
@ -48,10 +238,10 @@ abstract class MethodFingerprint(
|
|||||||
* @return True if the resolution was successful, false otherwise.
|
* @return True if the resolution was successful, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) {
|
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) {
|
||||||
for (fingerprint in this) // For each fingerprint
|
for (fingerprint in this) // For each fingerprint...
|
||||||
classes@ for (classDef in classes) // search through all classes for the fingerprint
|
classes@ for (classDef in classes) // ...search through all classes for the MethodFingerprint
|
||||||
if (fingerprint.resolve(context, classDef))
|
if (fingerprint.resolve(context, classDef))
|
||||||
break@classes // if the resolution succeeded, continue with the next fingerprint
|
break@classes // ...if the resolution succeeded, continue with the next MethodFingerprint.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,23 +334,7 @@ abstract class MethodFingerprint(
|
|||||||
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||||
method.implementation?.instructions ?: return false
|
method.implementation?.instructions ?: return false
|
||||||
|
|
||||||
method.patternScan(methodFingerprint) ?: return false
|
fun Method.patternScan(
|
||||||
} else null
|
|
||||||
|
|
||||||
methodFingerprint.result = MethodFingerprintResult(
|
|
||||||
method,
|
|
||||||
forClass,
|
|
||||||
MethodFingerprintResult.MethodFingerprintScanResult(
|
|
||||||
patternScanResult,
|
|
||||||
stringsScanResult
|
|
||||||
),
|
|
||||||
context
|
|
||||||
)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Method.patternScan(
|
|
||||||
fingerprint: MethodFingerprint
|
fingerprint: MethodFingerprint
|
||||||
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
||||||
val instructions = this.implementation!!.instructions
|
val instructions = this.implementation!!.instructions
|
||||||
@ -206,6 +380,22 @@ abstract class MethodFingerprint(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
method.patternScan(methodFingerprint) ?: return false
|
||||||
|
} else null
|
||||||
|
|
||||||
|
methodFingerprint.result = MethodFingerprintResult(
|
||||||
|
method,
|
||||||
|
forClass,
|
||||||
|
MethodFingerprintResult.MethodFingerprintScanResult(
|
||||||
|
patternScanResult,
|
||||||
|
stringsScanResult
|
||||||
|
),
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.createWarnings(
|
private fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.createWarnings(
|
||||||
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
||||||
) = buildList {
|
) = buildList {
|
||||||
|
@ -12,7 +12,7 @@ import java.io.Closeable
|
|||||||
* If it implements [Closeable], it will be closed after all patches have been executed.
|
* If it implements [Closeable], it will be closed after all patches have been executed.
|
||||||
* Closing will be done in reverse execution order.
|
* Closing will be done in reverse execution order.
|
||||||
*/
|
*/
|
||||||
sealed interface Patch<out T : Context> : Closeable {
|
sealed interface Patch<out T : Context> {
|
||||||
/**
|
/**
|
||||||
* The main function of the [Patch] which the patcher will call.
|
* The main function of the [Patch] which the patcher will call.
|
||||||
*
|
*
|
||||||
@ -20,13 +20,6 @@ sealed interface Patch<out T : Context> : Closeable {
|
|||||||
* @return The result of executing the patch.
|
* @return The result of executing the patch.
|
||||||
*/
|
*/
|
||||||
fun execute(context: @UnsafeVariance T): PatchResult
|
fun execute(context: @UnsafeVariance T): PatchResult
|
||||||
|
|
||||||
/**
|
|
||||||
* The closing function for this patch.
|
|
||||||
*
|
|
||||||
* This can be treated like popping the patch from the current patch stack.
|
|
||||||
*/
|
|
||||||
override fun close() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package app.revanced.patcher.util.patch
|
package app.revanced.patcher.util.patch
|
||||||
|
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.data.Context
|
||||||
|
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import org.jf.dexlib2.DexFileFactory
|
import org.jf.dexlib2.DexFileFactory
|
||||||
@ -19,7 +20,13 @@ sealed class PatchBundle(path: String) : File(path) {
|
|||||||
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
||||||
classNames.forEach { className ->
|
classNames.forEach { className ->
|
||||||
val clazz = classLoader.loadClass(className)
|
val clazz = classLoader.loadClass(className)
|
||||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
|
|
||||||
|
// Annotations can not Patch.
|
||||||
|
if (clazz.isAnnotation) return@forEach
|
||||||
|
|
||||||
|
clazz.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)
|
||||||
|
?: return@forEach
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
||||||
}
|
}
|
||||||
}.sortedBy { it.patchName }
|
}.sortedBy { it.patchName }
|
||||||
@ -43,11 +50,9 @@ sealed class PatchBundle(path: String) : File(path) {
|
|||||||
),
|
),
|
||||||
JarFile(this)
|
JarFile(this)
|
||||||
.stream()
|
.stream()
|
||||||
.filter {it.name.endsWith(".class") && !it.name.contains("$")}
|
.filter { it.name.endsWith(".class") && !it.name.contains("$") }
|
||||||
.map({it -> it.realName.replace('/', '.').replace(".class", "")}).iterator()
|
.map { it.realName.replace('/', '.').replace(".class", "") }.iterator()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,8 +68,9 @@ sealed class PatchBundle(path: String) : File(path) {
|
|||||||
* Patches will be loaded to the provided [dexClassLoader].
|
* Patches will be loaded to the provided [dexClassLoader].
|
||||||
*/
|
*/
|
||||||
fun loadPatches() = loadPatches(dexClassLoader,
|
fun loadPatches() = loadPatches(dexClassLoader,
|
||||||
DexFileFactory.loadDexFile(path, null).classes.asSequence().map({ classDef ->
|
DexFileFactory.loadDexFile(path, null).classes.asSequence().map { classDef ->
|
||||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||||
}).iterator())
|
}.iterator()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user