Skip to content

Commit

Permalink
Fix docs on how to add fingerprints while delegating result
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Jun 16, 2024
1 parent 27794c9 commit 0403287
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 147 deletions.
4 changes: 4 additions & 0 deletions api/revanced-patcher.api
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$Meth
}

public final class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch {
public final fun getFingerprints ()Ljava/util/Set;
public fun toString ()Ljava/lang/String;
}

public final class app/revanced/patcher/patch/BytecodePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
Expand Down Expand Up @@ -377,13 +379,15 @@ public final class app/revanced/patcher/patch/PatchResult {
}

public final class app/revanced/patcher/patch/RawResourcePatch : app/revanced/patcher/patch/Patch {
public fun toString ()Ljava/lang/String;
}

public final class app/revanced/patcher/patch/RawResourcePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch;
}

public final class app/revanced/patcher/patch/ResourcePatch : app/revanced/patcher/patch/Patch {
public fun toString ()Ljava/lang/String;
}

public final class app/revanced/patcher/patch/ResourcePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
Expand Down
4 changes: 2 additions & 2 deletions docs/2_2_1_fingerprinting.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ Once the fingerprint is resolved, the result can be used in the patch:
```kt
val patch = bytecodePatch {
// Add a fingerprint and delegate it's result to a variable.
val result by showAdsFingerprint
// Add a fingerprint and delegate its result to a variable.
val result by showAdsFingerprint()
execute {
val method = result.method
Expand Down
36 changes: 13 additions & 23 deletions src/main/kotlin/app/revanced/patcher/Patcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import java.util.logging.Logger

@FunctionalInterface
interface PatchesConsumer {
fun accept(patches: PatchSet, integrations: Set<File> = emptySet())
fun accept(patches: Set<Patch<*>>, integrations: Set<File> = emptySet())
}

@FunctionalInterface
interface PatcherResultSupplier : Supplier<PatcherResult>, Closeable
interface PatcherResultSupplier :
Supplier<PatcherResult>,
Closeable

@FunctionalInterface
interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>
Expand All @@ -29,7 +31,9 @@ interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>
*/
class Patcher(
private val config: PatcherConfig,
) : PatchExecutorFunction, PatchesConsumer, PatcherResultSupplier {
) : PatchExecutorFunction,
PatchesConsumer,
PatcherResultSupplier {
private val logger = Logger.getLogger(Patcher::class.java.name)

/**
Expand All @@ -48,7 +52,7 @@ class Patcher(
* @param integrations The integrations to add. Must be a DEX file or container of DEX files.
*/
@Suppress("NAME_SHADOWING")
override fun accept(patches: PatchSet, integrations: Set<File>) {
override fun accept(patches: Set<Patch<*>>, integrations: Set<File>) {
// region Add patches

// Add all patches to the executablePatches set.
Expand Down Expand Up @@ -83,11 +87,12 @@ class Patcher(
}

// Check, if integrations need to be merged.
for (patch in patches)
for (patch in patches) {
if (patch.anyRecursively { it.requiresIntegrations }) {
context.bytecodePatchContext.integrations.merge = true
break
}
}
}

// endregion
Expand All @@ -113,7 +118,7 @@ class Patcher(
executedPatches[this]?.let { patchResult ->
patchResult.exception ?: return patchResult

return PatchResult(this, PatchException("'$this' failed previously"))
return PatchResult(this, PatchException("The patch '$this' failed previously"))
}

// Recursively execute all dependency patches.
Expand All @@ -122,7 +127,7 @@ class Patcher(
return PatchResult(
this,
PatchException(
"'$this' depends on '$dependency' that raised an exception:\n${it.stackTraceToString()}",
"The patch \"$this\" depends on \"$dependency\" which raised an exception:\n${it.stackTraceToString()}",
),
)
}
Expand Down Expand Up @@ -187,7 +192,7 @@ class Patcher(
PatchResult(
patch,
PatchException(
"'$patch' raised an exception while being closed: ${it.stackTraceToString()}",
"The patch \"$patch\" raised an exception: ${it.stackTraceToString()}",
result.exception,
),
),
Expand Down Expand Up @@ -216,18 +221,3 @@ class Patcher(
context.resourcePatchContext.get(),
)
}

/**
* An exception thrown by [Patcher].
*
* @param errorMessage The exception message.
* @param cause The corresponding [Throwable].
*/
sealed class PatcherException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
constructor(errorMessage: String) : this(errorMessage, null)

// TODO: Implement circular dependency detection.
class CircularDependencyException internal constructor(dependant: String) : PatcherException(
"Patch '$dependant' causes a circular dependency",
)
}
156 changes: 70 additions & 86 deletions src/main/kotlin/app/revanced/patcher/fingerprint/MethodFingerprint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

package app.revanced.patcher.fingerprint

import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
import app.revanced.patcher.fingerprint.LookupMap.Maps.appendParameters
import app.revanced.patcher.fingerprint.LookupMap.Maps.initializeLookupMaps
Expand Down Expand Up @@ -104,20 +103,17 @@ class MethodFingerprint internal constructor(
/**
* Resolve a [MethodFingerprint] using a list of [MethodClassPair].
*
* @return True if the resolution was successful, false otherwise.
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
*/
fun MethodFingerprint.resolveUsingMethodClassPair(methodClasses: LookupMap.MethodClassList): Boolean {
methodClasses.forEach { classAndMethod ->
if (resolve(context, classAndMethod.first, classAndMethod.second)) return true
methodClasses.forEach { (classDef, method) ->
if (resolve(context, classDef, method)) return true
}
return false
}

val methodsWithSameStrings = methodStringsLookup()
if (methodsWithSameStrings != null) {
if (resolveUsingMethodClassPair(methodsWithSameStrings)) {
return true
}
if (methodStringsLookup()?.let(::resolveUsingMethodClassPair) == true) {
return true
}

// No strings declared or none matched (partial matches are allowed).
Expand All @@ -128,43 +124,42 @@ class MethodFingerprint internal constructor(
/**
* Resolve a [MethodFingerprint] against a [ClassDef].
*
* @param forClass The class on which to resolve the [MethodFingerprint] in.
* @param context The [BytecodePatchContext] to host proxies.
* @return True if the resolution was successful, false otherwise.
* @param forClass The class in which to resolve the [MethodFingerprint].
* @param context The [BytecodePatchContext] to create mutable proxies.
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
*/
fun resolve(
context: BytecodePatchContext,
forClass: ClassDef,
): Boolean {
for (method in forClass.methods)
for (method in forClass.methods) {
if (resolve(context, method, forClass)) {
return true
}
}
return false
}

/**
* Resolve a [MethodFingerprint] against a [Method].
*
* @param method The class on which to resolve the [MethodFingerprint] in.
* @param forClass The class on which to resolve the [MethodFingerprint].
* @param context The [BytecodePatchContext] to host proxies.
* @param method The method in which to resolve the [MethodFingerprint].
* @param classDef The class in which to resolve the [MethodFingerprint].
* @param context The [BytecodePatchContext] to create mutable proxies.
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
*/
fun resolve(
context: BytecodePatchContext,
method: Method,
forClass: ClassDef,
classDef: ClassDef,
): Boolean {
val methodFingerprint = this

if (methodFingerprint.result != null) return true
if (result != null) return true

if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType)) {
if (returnType != null && !method.returnType.startsWith(returnType)) {
return false
}

if (methodFingerprint.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags) {
if (accessFlags != null && accessFlags != method.accessFlags) {
return false
}

Expand All @@ -180,28 +175,22 @@ class MethodFingerprint internal constructor(
return true
}

if (methodFingerprint.parameters != null &&
!parametersEqual(
// TODO: parseParameters()
methodFingerprint.parameters,
method.parameterTypes,
)
) {
// TODO: parseParameters()
if (parameters != null && !parametersEqual(parameters, method.parameterTypes)) {
return false
}

@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
if (methodFingerprint.custom != null && !methodFingerprint.custom!!(method, forClass)) {
if (custom != null && !custom.invoke(method, classDef)) {
return false
}

val stringsScanResult: StringsScanResult? =
if (methodFingerprint.strings != null) {
val stringsScanResult =
if (strings != null) {
StringsScanResult(
buildList {
val instructions = method.instructionsOrNull ?: return false

val stringsList = methodFingerprint.strings.toMutableList()
val stringsList = strings.toMutableList()

instructions.forEachIndexed { instructionIndex, instruction ->
if (
Expand All @@ -226,65 +215,60 @@ class MethodFingerprint internal constructor(
null
}

val patternScanResult =
if (methodFingerprint.opcodes != null) {
method.instructionsOrNull ?: return false
val patternScanResult = if (opcodes != null) {
val instructions = method.instructionsOrNull ?: return false

fun Method.patternScan(
fingerprint: MethodFingerprint,
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
val instructions = instructions
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanThreshold
fun patternScan(): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
val fingerprintFuzzyPatternScanThreshold = fuzzyPatternScanThreshold

val pattern = fingerprint.opcodes!!
val instructionLength = instructions.count()
val patternLength = pattern.count()
val instructionLength = instructions.count()
val patternLength = opcodes.size

for (index in 0 until instructionLength) {
var patternIndex = 0
var threshold = fingerprintFuzzyPatternScanThreshold
for (index in 0 until instructionLength) {
var patternIndex = 0
var threshold = fingerprintFuzzyPatternScanThreshold

while (index + patternIndex < instructionLength) {
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
val patternOpcode = pattern.elementAt(patternIndex)
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 (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
// Reaching maximum threshold (0) means,
// the pattern does not match to the current instructions.
if (threshold-- == 0) break
}

if (patternIndex < patternLength - 1) {
// If the entire pattern has not been scanned yet, continue the scan.
patternIndex++
continue
}
// The pattern is valid.
return MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
index,
index + patternIndex,
)
if (patternIndex < patternLength - 1) {
// If the entire pattern has not been scanned yet, continue the scan.
patternIndex++
continue
}
}

return null
// The entire pattern has been scanned.
return MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
index,
index + patternIndex,
)
}
}

method.patternScan(methodFingerprint) ?: return false
} else {
null
return null
}

methodFingerprint.result =
MethodFingerprintResult(
method,
forClass,
MethodFingerprintResult.MethodFingerprintScanResult(
patternScanResult,
stringsScanResult,
),
context,
)
patternScan() ?: return false
} else {
null
}

result = MethodFingerprintResult(
method,
classDef,
MethodFingerprintResult.MethodFingerprintScanResult(
patternScanResult,
stringsScanResult,
),
context,
)

return true
}
Expand All @@ -309,16 +293,16 @@ internal fun Set<MethodFingerprint>.resolveUsingLookupMap(context: BytecodePatch
/**
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
*
* @param classes The classes on which to resolve the [MethodFingerprint] in.
* @param context The [BytecodePatchContext] to host proxies.
* @return True if the resolution was successful, false otherwise.
* @param classes The classes in which to resolve the [MethodFingerprint].
* @param context The [BytecodePatchContext] to create mutable proxies.
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
*/
fun Iterable<MethodFingerprint>.resolve(
context: BytecodePatchContext,
classes: Iterable<ClassDef>,
) = forEach { fingerprint ->
for (classDef in classes) {
if (fingerprint.resolve(context, classDef)) break
classes.forEach {
if (fingerprint.resolve(context, it)) return@resolve
}
}

Expand Down
Loading

0 comments on commit 0403287

Please sign in to comment.