You've already forked revanced-patcher
							
							
				mirror of
				https://github.com/revanced/revanced-patcher
				synced 2025-11-02 07:30:52 +01:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			v21.1.0-de
			...
			feat/kmp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					c2e0f57579 | ||
| 
						 | 
					242e244b18 | ||
| 
						 | 
					f338ebff6c | ||
| 
						 | 
					00e5950cf2 | ||
| 
						 | 
					5a0e3841ff | ||
| 
						 | 
					cfb459b832 | ||
| 
						 | 
					2096306107 | ||
| 
						 | 
					92eaba8081 | ||
| 
						 | 
					7be0cd8548 | 
							
								
								
									
										73
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,76 +1,3 @@
 | 
			
		||||
# [21.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0...v21.1.0-dev.1) (2024-12-07)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Add identity hash code to unnamed patches ([88a3252](https://github.com/ReVanced/revanced-patcher/commit/88a325257494939a79fb30dd51d60c5c52546755))
 | 
			
		||||
 | 
			
		||||
# [21.0.0](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0) (2024-11-05)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Match fingerprint before delegating the match property ([5d996de](https://github.com/ReVanced/revanced-patcher/commit/5d996def4d3de4e2bfc34562e5a6c7d89a8cddf0))
 | 
			
		||||
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](https://github.com/ReVanced/revanced-patcher/commit/aa472eb9857145b53b49f843406a9764fbb7e5ce))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](https://github.com/ReVanced/revanced-patcher/commit/0abf1c6c0279708fdef5cb66b141d07d17682693))
 | 
			
		||||
* Improve various APIs  ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](https://github.com/ReVanced/revanced-patcher/commit/b8249789df8b90129f7b7ad0e523a8d0ceaab848))
 | 
			
		||||
* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](https://github.com/ReVanced/revanced-patcher/commit/0746c22743a9561bae2284d234b151f2f8511ca5))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Performance Improvements
 | 
			
		||||
 | 
			
		||||
* Use smallest lookup map for strings ([1358d3f](https://github.com/ReVanced/revanced-patcher/commit/1358d3fa10cb8ba011b6b89cfe3684ecf9849d2f))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### BREAKING CHANGES
 | 
			
		||||
 | 
			
		||||
* Various APIs have been changed.
 | 
			
		||||
* Many APIs have been changed.
 | 
			
		||||
 | 
			
		||||
# [21.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.3...v21.0.0-dev.4) (2024-11-05)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Performance Improvements
 | 
			
		||||
 | 
			
		||||
* Use smallest lookup map for strings ([1358d3f](https://github.com/ReVanced/revanced-patcher/commit/1358d3fa10cb8ba011b6b89cfe3684ecf9849d2f))
 | 
			
		||||
 | 
			
		||||
# [21.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.2...v21.0.0-dev.3) (2024-11-05)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](https://github.com/ReVanced/revanced-patcher/commit/0746c22743a9561bae2284d234b151f2f8511ca5))
 | 
			
		||||
 | 
			
		||||
# [21.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.1...v21.0.0-dev.2) (2024-11-01)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Match fingerprint before delegating the match property ([5d996de](https://github.com/ReVanced/revanced-patcher/commit/5d996def4d3de4e2bfc34562e5a6c7d89a8cddf0))
 | 
			
		||||
 | 
			
		||||
# [21.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0-dev.1) (2024-10-27)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](https://github.com/ReVanced/revanced-patcher/commit/aa472eb9857145b53b49f843406a9764fbb7e5ce))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](https://github.com/ReVanced/revanced-patcher/commit/0abf1c6c0279708fdef5cb66b141d07d17682693))
 | 
			
		||||
* Improve various APIs  ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](https://github.com/ReVanced/revanced-patcher/commit/b8249789df8b90129f7b7ad0e523a8d0ceaab848))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### BREAKING CHANGES
 | 
			
		||||
 | 
			
		||||
* Various APIs have been changed.
 | 
			
		||||
* Many APIs have been changed.
 | 
			
		||||
 | 
			
		||||
## [20.0.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2) (2024-10-17)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,4 @@
 | 
			
		||||
public final class app/revanced/patcher/Fingerprint {
 | 
			
		||||
	public final fun getClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
 | 
			
		||||
	public final fun getClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
 | 
			
		||||
	public final fun getMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun getMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun getOriginalClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
 | 
			
		||||
	public final fun getOriginalClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
 | 
			
		||||
	public final fun getOriginalMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun getOriginalMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun getPatternMatch (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
 | 
			
		||||
	public final fun getPatternMatchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
 | 
			
		||||
	public final fun getStringMatches (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
 | 
			
		||||
	public final fun getStringMatchesOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/FingerprintBuilder {
 | 
			
		||||
@@ -49,11 +31,13 @@ public final class app/revanced/patcher/Match {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/Match$PatternMatch {
 | 
			
		||||
	public fun <init> (II)V
 | 
			
		||||
	public final fun getEndIndex ()I
 | 
			
		||||
	public final fun getStartIndex ()I
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/Match$StringMatch {
 | 
			
		||||
	public fun <init> (Ljava/lang/String;I)V
 | 
			
		||||
	public final fun getIndex ()I
 | 
			
		||||
	public final fun getString ()Ljava/lang/String;
 | 
			
		||||
}
 | 
			
		||||
@@ -162,6 +146,10 @@ public final class app/revanced/patcher/patch/BytecodePatchContext : app/revance
 | 
			
		||||
	public synthetic fun get ()Ljava/lang/Object;
 | 
			
		||||
	public fun get ()Ljava/util/Set;
 | 
			
		||||
	public final fun getClasses ()Lapp/revanced/patcher/util/ProxyClassList;
 | 
			
		||||
	public final fun getMatch (Lapp/revanced/patcher/Fingerprint;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun getValue (Lapp/revanced/patcher/Fingerprint;Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
 | 
			
		||||
	public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
 | 
			
		||||
}
 | 
			
		||||
@@ -480,12 +468,12 @@ public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/util/MethodNavigator {
 | 
			
		||||
	public final fun at (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public final fun at ([I)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public static synthetic fun at$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
 | 
			
		||||
	public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
 | 
			
		||||
	public final fun to (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public final fun to ([I)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
	public static synthetic fun to$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,8 +56,6 @@ dependencies {
 | 
			
		||||
kotlin {
 | 
			
		||||
    compilerOptions {
 | 
			
		||||
        jvmTarget.set(JvmTarget.JVM_11)
 | 
			
		||||
 | 
			
		||||
        freeCompilerArgs = listOf("-Xcontext-receivers")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -89,9 +89,9 @@ val patcherResult = Patcher(PatcherConfig(apkFile = File("some.apk"))).use { pat
 | 
			
		||||
    runBlocking {
 | 
			
		||||
        patcher().collect { patchResult ->
 | 
			
		||||
            if (patchResult.exception != null)
 | 
			
		||||
                logger.info { "\"${patchResult.patch}\" failed:\n${patchResult.exception}" }
 | 
			
		||||
                logger.info("\"${patchResult.patch}\" failed:\n${patchResult.exception}")
 | 
			
		||||
            else
 | 
			
		||||
                logger.info { "\"${patchResult.patch}\" succeeded" }
 | 
			
		||||
                logger.info("\"${patchResult.patch}\" succeeded")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -117,19 +117,15 @@ With this information, the original code can be reconstructed:
 | 
			
		||||
```java
 | 
			
		||||
package com.some.app.ads;
 | 
			
		||||
 | 
			
		||||
<accessFlags>
 | 
			
		||||
 | 
			
		||||
class AdsLoader {
 | 
			
		||||
    public final boolean <methodName>(boolean <parameter>)
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
<accessFlags> class AdsLoader {
 | 
			
		||||
    public final boolean <methodName>(boolean <parameter>) {
 | 
			
		||||
        // ...
 | 
			
		||||
 | 
			
		||||
        var userStatus = "pro";
 | 
			
		||||
 | 
			
		||||
        // ...
 | 
			
		||||
 | 
			
		||||
        return <returnValue >;
 | 
			
		||||
        return <returnValue>;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
@@ -138,14 +134,13 @@ Using that fingerprint, this method can be matched uniquely from all other metho
 | 
			
		||||
 | 
			
		||||
> [!TIP]
 | 
			
		||||
> A fingerprint should contain information about a method likely to remain the same across updates.
 | 
			
		||||
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated
 | 
			
		||||
> app.
 | 
			
		||||
> In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the
 | 
			
		||||
> same.
 | 
			
		||||
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated app.
 | 
			
		||||
> In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the same.
 | 
			
		||||
 | 
			
		||||
## 🔨 How to use fingerprints
 | 
			
		||||
 | 
			
		||||
After declaring a fingerprint, it can be used in a patch to find the method it matches to:
 | 
			
		||||
A fingerprint is matched to a method,
 | 
			
		||||
once the `match` property of the fingerprint is accessed in a patch's `execute` scope:
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
val fingerprint = fingerprint {
 | 
			
		||||
@@ -154,34 +149,52 @@ val fingerprint = fingerprint {
 | 
			
		||||
 | 
			
		||||
val patch = bytecodePatch {
 | 
			
		||||
    execute {
 | 
			
		||||
        fingerprint.method
 | 
			
		||||
        val match = fingerprint.match!!
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The fingerprint won't be matched again, if it has already been matched once, for performance reasons.
 | 
			
		||||
This makes it useful, to share fingerprints between multiple patches,
 | 
			
		||||
and let the first executing patch match the fingerprint:
 | 
			
		||||
The fingerprint won't be matched again, if it has already been matched once.
 | 
			
		||||
This makes it useful, to share fingerprints between multiple patches, and let the first patch match the fingerprint:
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
// Either of these two patches will match the fingerprint first and the other patch can reuse the match:
 | 
			
		||||
val mainActivityPatch1 = bytecodePatch {
 | 
			
		||||
    execute {
 | 
			
		||||
        mainActivityOnCreateFingerprint.method
 | 
			
		||||
        val match = mainActivityOnCreateFingerprint.match!!
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val mainActivityPatch2 = bytecodePatch {
 | 
			
		||||
    execute {
 | 
			
		||||
        mainActivityOnCreateFingerprint.method
 | 
			
		||||
        val match = mainActivityOnCreateFingerprint.match!!
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
A fingerprint match can also be delegated to a variable for convenience without the need to check for `null`:
 | 
			
		||||
```kt
 | 
			
		||||
val fingerprint = fingerprint {
 | 
			
		||||
    // ...
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val patch = bytecodePatch {
 | 
			
		||||
    execute {
 | 
			
		||||
        // Alternative to fingerprint.match ?: throw PatchException("No match found")
 | 
			
		||||
        val match by fingerprint.match
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            match.method
 | 
			
		||||
        } catch (e: PatchException) {
 | 
			
		||||
            // Handle the exception for example.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
> [!WARNING]
 | 
			
		||||
> If the fingerprint can not be matched to any method,
 | 
			
		||||
> accessing certain properties of the fingerprint will raise an exception.
 | 
			
		||||
> Instead, the `orNull` properties can be used to return `null` if no match is found.
 | 
			
		||||
> If the fingerprint can not be matched to any method, the match of a fingerprint is `null`. If such a match is delegated
 | 
			
		||||
> to a variable, accessing it will raise an exception.
 | 
			
		||||
 | 
			
		||||
> [!TIP]
 | 
			
		||||
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode`
 | 
			
		||||
@@ -198,43 +211,42 @@ val mainActivityPatch2 = bytecodePatch {
 | 
			
		||||
>    )
 | 
			
		||||
>}
 | 
			
		||||
> ```
 | 
			
		||||
> 
 | 
			
		||||
The match of a fingerprint contains references to the original method and class definition of the method:
 | 
			
		||||
 | 
			
		||||
The following properties can be accessed in a fingerprint:
 | 
			
		||||
```kt
 | 
			
		||||
class Match(
 | 
			
		||||
    val originalMethod: Method,
 | 
			
		||||
    val originalClassDef: ClassDef,
 | 
			
		||||
    val patternMatch: Match.PatternMatch?,
 | 
			
		||||
    val stringMatches: List<Match.StringMatch>?,
 | 
			
		||||
    // ...
 | 
			
		||||
) {
 | 
			
		||||
    val classDef by lazy { /* ... */ }
 | 
			
		||||
    val method by lazy { /* ... */ }
 | 
			
		||||
 | 
			
		||||
- `originalClassDef`: The original class definition the fingerprint matches to.
 | 
			
		||||
- `originalClassDefOrNull`: The original class definition the fingerprint matches to.
 | 
			
		||||
- `originalMethod`: The original method the fingerprint matches to.
 | 
			
		||||
- `originalMethodOrNull`: The original method the fingerprint matches to.
 | 
			
		||||
- `classDef`: The class the fingerprint matches to.
 | 
			
		||||
- `classDefOrNull`: The class the fingerprint matches to.
 | 
			
		||||
- `method`: The method the fingerprint matches to. If no match is found, an exception is raised.
 | 
			
		||||
- `methodOrNull`: The method the fingerprint matches to.
 | 
			
		||||
    // ...
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The difference between the `original` and non-`original` properties is that the `original` properties return the
 | 
			
		||||
original class or method definition, while the non-`original` properties return a mutable copy of the class or method.
 | 
			
		||||
The mutable copies can be modified. They are lazy properties, so they are only computed
 | 
			
		||||
and only then will effectively replace the `original` method or class definition when accessed.
 | 
			
		||||
 | 
			
		||||
> [!TIP]
 | 
			
		||||
> If only read-only access to the class or method is needed,
 | 
			
		||||
> the `originalClassDef` and `originalMethod` properties should be used,
 | 
			
		||||
> to avoid making a mutable copy of the class or method.
 | 
			
		||||
The `classDef` and `method` properties can be used to make changes to the class or method.
 | 
			
		||||
They are lazy properties, so they are only computed 
 | 
			
		||||
and will effectively replace the original method or class definition when accessed.
 | 
			
		||||
 | 
			
		||||
## 🏹 Manually matching fingerprints
 | 
			
		||||
 | 
			
		||||
By default, a fingerprint is matched automatically against all classes
 | 
			
		||||
when one of the fingerprint's properties is accessed.
 | 
			
		||||
By default, a fingerprint is matched automatically against all classes when the `match` property is accessed.
 | 
			
		||||
 | 
			
		||||
Instead, the fingerprint can be matched manually using various overloads of a fingerprint's `match` function:
 | 
			
		||||
 | 
			
		||||
- In a **list of classes**, if the fingerprint can match in a known subset of classes
 | 
			
		||||
 | 
			
		||||
  If you have a known list of classes you know the fingerprint can match in,
 | 
			
		||||
  you can match the fingerprint on the list of classes:
 | 
			
		||||
you can match the fingerprint on the list of classes:
 | 
			
		||||
 | 
			
		||||
  ```kt
 | 
			
		||||
  execute {
 | 
			
		||||
    val match = showAdsFingerprint(classes)
 | 
			
		||||
    val match = showAdsFingerprint.match(classes) ?: throw PatchException("No match found")
 | 
			
		||||
  }
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
@@ -246,24 +258,14 @@ Instead, the fingerprint can be matched manually using various overloads of a fi
 | 
			
		||||
  execute {
 | 
			
		||||
    val adsLoaderClass = classes.single { it.name == "Lcom/some/app/ads/Loader;" }
 | 
			
		||||
 | 
			
		||||
    val match = showAdsFingerprint.match(adsLoaderClass)
 | 
			
		||||
  }
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
  Another common usecase is to use a fingerprint to reduce the search space of a method to a single class.
 | 
			
		||||
 | 
			
		||||
  ```kt
 | 
			
		||||
  execute {
 | 
			
		||||
    // Match showAdsFingerprint in the class of the ads loader found by adsLoaderClassFingerprint.
 | 
			
		||||
    val match = showAdsFingerprint.match(adsLoaderClassFingerprint.classDef)
 | 
			
		||||
    val match = showAdsFingerprint.match(context, adsLoaderClass) ?: throw PatchException("No match found")
 | 
			
		||||
  }
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
- Match a **single method**, to extract certain information about it
 | 
			
		||||
 | 
			
		||||
  The match of a fingerprint contains useful information about the method,
 | 
			
		||||
  such as the start and end index of an opcode pattern or the indices of the instructions with certain string
 | 
			
		||||
  references.
 | 
			
		||||
  such as the start and end index of an opcode pattern or the indices of the instructions with certain string references.
 | 
			
		||||
  A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:
 | 
			
		||||
 | 
			
		||||
  ```kt
 | 
			
		||||
@@ -272,19 +274,14 @@ Instead, the fingerprint can be matched manually using various overloads of a fi
 | 
			
		||||
      strings("free", "trial")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    currentPlanFingerprint.match(adsFingerprint.method).let { match ->
 | 
			
		||||
    currentPlanFingerprint.match(adsFingerprintMatch.method)?.let { match ->
 | 
			
		||||
      match.stringMatches.forEach { match ->
 | 
			
		||||
        println("The index of the string '${match.string}' is ${match.index}")
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    } ?: throw PatchException("No match found")
 | 
			
		||||
  }
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
> [!WARNING]
 | 
			
		||||
> If the fingerprint can not be matched to any method, calling `match` will raise an
 | 
			
		||||
> exception.
 | 
			
		||||
> Instead, the `orNull` overloads can be used to return `null` if no match is found.
 | 
			
		||||
 | 
			
		||||
> [!TIP]
 | 
			
		||||
> To see real-world examples of fingerprints,
 | 
			
		||||
> check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
 | 
			
		||||
 
 | 
			
		||||
@@ -46,17 +46,17 @@ The `navigate(Method)` function allows you to navigate method calls recursively
 | 
			
		||||
```kt
 | 
			
		||||
execute {
 | 
			
		||||
    // Sequentially navigate to the instructions at index 1 within 'someMethod'.
 | 
			
		||||
    val method = navigate(someMethod).to(1).original() // original() returns the original immutable method.
 | 
			
		||||
    val method = navigate(someMethod).at(1).original() // original() returns the original immutable method.
 | 
			
		||||
    
 | 
			
		||||
    // Further navigate to the second occurrence where the instruction's opcode is 'INVOKEVIRTUAL'.
 | 
			
		||||
    // stop() returns the mutable copy of the method.
 | 
			
		||||
    val method = navigate(someMethod).to(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
 | 
			
		||||
    val method = navigate(someMethod).at(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
 | 
			
		||||
    
 | 
			
		||||
    // Alternatively, to stop(), you can delegate the method to a variable.
 | 
			
		||||
    val method by navigate(someMethod).to(1)
 | 
			
		||||
    val method by navigate(someMethod).at(1)
 | 
			
		||||
    
 | 
			
		||||
    // You can chain multiple calls to at() to navigate deeper into the method.
 | 
			
		||||
    val method by navigate(someMethod).to(1).to(2, 3, 4).to(5)
 | 
			
		||||
    val method by navigate(someMethod).at(1).at(2, 3, 4).at(5)
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -85,7 +85,7 @@ execute {
 | 
			
		||||
The `document` function is used to read and write DOM files.
 | 
			
		||||
 | 
			
		||||
```kt
 | 
			
		||||
execute {
 | 
			
		||||
execute { 
 | 
			
		||||
    document("res/values/strings.xml").use { document ->
 | 
			
		||||
        val element = doc.createElement("string").apply {
 | 
			
		||||
            textContent = "Hello, World!"
 | 
			
		||||
@@ -112,6 +112,5 @@ ReVanced Patcher is a powerful library to patch Android applications, offering a
 | 
			
		||||
that outlive app updates. Patches make up ReVanced; without you, the community of patch developers,
 | 
			
		||||
ReVanced would not be what it is today. We hope that this documentation has been helpful to you
 | 
			
		||||
and are excited to see what you will create with ReVanced Patcher. If you have any questions or need help,
 | 
			
		||||
talk to us on one of our platforms linked on [revanced.app](https://revanced.app) or open an issue in case of a bug or
 | 
			
		||||
feature request,  
 | 
			
		||||
talk to us on one of our platforms linked on [revanced.app](https://revanced.app) or open an issue in case of a bug or feature request,  
 | 
			
		||||
ReVanced
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
org.gradle.parallel = true
 | 
			
		||||
org.gradle.caching = true
 | 
			
		||||
version = 21.1.0-dev.1
 | 
			
		||||
version = 20.0.2
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,8 +1,6 @@
 | 
			
		||||
package app.revanced.patcher.patch
 | 
			
		||||
 | 
			
		||||
import app.revanced.patcher.InternalApi
 | 
			
		||||
import app.revanced.patcher.PatcherConfig
 | 
			
		||||
import app.revanced.patcher.PatcherResult
 | 
			
		||||
import app.revanced.patcher.*
 | 
			
		||||
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
 | 
			
		||||
import app.revanced.patcher.util.ClassMerger.merge
 | 
			
		||||
import app.revanced.patcher.util.MethodNavigator
 | 
			
		||||
@@ -24,6 +22,7 @@ import java.io.Closeable
 | 
			
		||||
import java.io.FileFilter
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.logging.Logger
 | 
			
		||||
import kotlin.reflect.KProperty
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A context for patches containing the current state of the bytecode.
 | 
			
		||||
@@ -34,7 +33,7 @@ import java.util.logging.Logger
 | 
			
		||||
class BytecodePatchContext internal constructor(private val config: PatcherConfig) :
 | 
			
		||||
    PatchContext<Set<PatcherResult.PatchedDexFile>>,
 | 
			
		||||
    Closeable {
 | 
			
		||||
    private val logger = Logger.getLogger(this::javaClass.name)
 | 
			
		||||
    private val logger = Logger.getLogger(BytecodePatchContext::class.java.name)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * [Opcodes] of the supplied [PatcherConfig.apkFile].
 | 
			
		||||
@@ -54,6 +53,36 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
        ).also { opcodes = it.opcodes }.classes.toMutableList(),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The match for this [Fingerprint]. Null if unmatched.
 | 
			
		||||
     */
 | 
			
		||||
    val Fingerprint.match get() = match(this@BytecodePatchContext)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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.
 | 
			
		||||
     */
 | 
			
		||||
    fun Fingerprint.match(classDef: ClassDef) = match(this@BytecodePatchContext, classDef)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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.
 | 
			
		||||
     */
 | 
			
		||||
    fun Fingerprint.match(method: Method) = match(this@BytecodePatchContext, method)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the match for this [Fingerprint].
 | 
			
		||||
     *
 | 
			
		||||
     * @throws IllegalStateException If the [Fingerprint] has not been matched.
 | 
			
		||||
     */
 | 
			
		||||
    operator fun Fingerprint.getValue(nothing: Nothing?, property: KProperty<*>): Match = _match
 | 
			
		||||
        ?: throw PatchException("No fingerprint match to delegate to \"${property.name}\".")
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The lookup maps for methods and the class they are a member of from the [classes].
 | 
			
		||||
     */
 | 
			
		||||
@@ -69,7 +98,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
        bytecodePatch.extensionInputStream?.get()?.use { extensionStream ->
 | 
			
		||||
            RawDexIO.readRawDexFile(extensionStream, 0, null).classes.forEach { classDef ->
 | 
			
		||||
                val existingClass = lookupMaps.classesByType[classDef.type] ?: run {
 | 
			
		||||
                    logger.fine { "Adding class \"$classDef\"" }
 | 
			
		||||
                    logger.fine("Adding class \"$classDef\"")
 | 
			
		||||
 | 
			
		||||
                    classes += classDef
 | 
			
		||||
                    lookupMaps.classesByType[classDef.type] = classDef
 | 
			
		||||
@@ -77,7 +106,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
                    return@forEach
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                logger.fine { "Class \"$classDef\" exists already. Adding missing methods and fields." }
 | 
			
		||||
                logger.fine("Class \"$classDef\" exists already. Adding missing methods and fields.")
 | 
			
		||||
 | 
			
		||||
                existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
 | 
			
		||||
                    // If the class was merged, replace the original class with the merged class.
 | 
			
		||||
@@ -108,9 +137,9 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
     *
 | 
			
		||||
     * @return A proxy for the class.
 | 
			
		||||
     */
 | 
			
		||||
    fun proxy(classDef: ClassDef) = classes.proxyPool.find {
 | 
			
		||||
    fun proxy(classDef: ClassDef) = this@BytecodePatchContext.classes.proxyPool.find {
 | 
			
		||||
        it.immutableClass.type == classDef.type
 | 
			
		||||
    } ?: ClassProxy(classDef).also { classes.proxyPool.add(it) }
 | 
			
		||||
    } ?: ClassProxy(classDef).also { this@BytecodePatchContext.classes.proxyPool.add(it) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Navigate a method.
 | 
			
		||||
@@ -119,7 +148,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
     *
 | 
			
		||||
     * @return A [MethodNavigator] for the method.
 | 
			
		||||
     */
 | 
			
		||||
    fun navigate(method: MethodReference) = MethodNavigator(method)
 | 
			
		||||
    fun navigate(method: MethodReference) = MethodNavigator(this@BytecodePatchContext, method)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compile bytecode from the [BytecodePatchContext].
 | 
			
		||||
@@ -150,7 +179,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
                        override fun getOpcodes() = this@BytecodePatchContext.opcodes
 | 
			
		||||
                    },
 | 
			
		||||
                    DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
 | 
			
		||||
                ) { _, entryName, _ -> logger.info { "Compiled $entryName" } }
 | 
			
		||||
                ) { _, entryName, _ -> logger.info("Compiled $entryName") }
 | 
			
		||||
            }.listFiles(FileFilter { it.isFile })!!.map {
 | 
			
		||||
                PatcherResult.PatchedDexFile(it.name, it.inputStream())
 | 
			
		||||
            }.toSet()
 | 
			
		||||
@@ -198,6 +227,28 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal companion object {
 | 
			
		||||
            /**
 | 
			
		||||
             * Appends a string based on the parameter reference types of this method.
 | 
			
		||||
             */
 | 
			
		||||
            internal 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())
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override fun close() {
 | 
			
		||||
            methodsByStrings.clear()
 | 
			
		||||
            classesByType.clear()
 | 
			
		||||
 
 | 
			
		||||
@@ -87,8 +87,7 @@ sealed class Patch<C : PatchContext<*>>(
 | 
			
		||||
        finalizeBlock?.invoke(context)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun toString() = name ?: 
 | 
			
		||||
        "Patch@${System.identityHashCode(this)}"
 | 
			
		||||
    override fun toString() = name ?: "Patch"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun Patch<*>.anyRecursively(
 | 
			
		||||
@@ -162,7 +161,7 @@ class BytecodePatch internal constructor(
 | 
			
		||||
 | 
			
		||||
    override fun finalize(context: PatcherContext) = finalize(context.bytecodeContext)
 | 
			
		||||
 | 
			
		||||
    override fun toString() = name ?: "Bytecode${super.toString()}"
 | 
			
		||||
    override fun toString() = name ?: "BytecodePatch"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -205,7 +204,7 @@ class RawResourcePatch internal constructor(
 | 
			
		||||
 | 
			
		||||
    override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
 | 
			
		||||
 | 
			
		||||
    override fun toString() = name ?: "RawResource${super.toString()}"
 | 
			
		||||
    override fun toString() = name ?: "RawResourcePatch"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -248,7 +247,7 @@ class ResourcePatch internal constructor(
 | 
			
		||||
 | 
			
		||||
    override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
 | 
			
		||||
 | 
			
		||||
    override fun toString() = name ?: "Resource${super.toString()}"
 | 
			
		||||
    override fun toString() = name ?: "ResourcePatch"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ internal object ClassMerger {
 | 
			
		||||
 | 
			
		||||
        if (missingMethods.isEmpty()) return this
 | 
			
		||||
 | 
			
		||||
        logger.fine { "Found ${missingMethods.size} missing methods" }
 | 
			
		||||
        logger.fine("Found ${missingMethods.size} missing methods")
 | 
			
		||||
 | 
			
		||||
        return asMutableClass().apply {
 | 
			
		||||
            methods.addAll(missingMethods.map { it.toMutable() })
 | 
			
		||||
@@ -80,7 +80,7 @@ internal object ClassMerger {
 | 
			
		||||
 | 
			
		||||
        if (missingFields.isEmpty()) return this
 | 
			
		||||
 | 
			
		||||
        logger.fine { "Found ${missingFields.size} missing fields" }
 | 
			
		||||
        logger.fine("Found ${missingFields.size} missing fields")
 | 
			
		||||
 | 
			
		||||
        return asMutableClass().apply {
 | 
			
		||||
            fields.addAll(missingFields.map { it.toMutable() })
 | 
			
		||||
@@ -100,7 +100,7 @@ internal object ClassMerger {
 | 
			
		||||
            context.traverseClassHierarchy(this) {
 | 
			
		||||
                if (accessFlags.isPublic()) return@traverseClassHierarchy
 | 
			
		||||
 | 
			
		||||
                logger.fine { "Publicizing ${this.type}" }
 | 
			
		||||
                logger.fine("Publicizing ${this.type}")
 | 
			
		||||
 | 
			
		||||
                accessFlags = accessFlags.toPublic()
 | 
			
		||||
            }
 | 
			
		||||
@@ -124,7 +124,7 @@ internal object ClassMerger {
 | 
			
		||||
 | 
			
		||||
        if (brokenFields.isEmpty()) return this
 | 
			
		||||
 | 
			
		||||
        logger.fine { "Found ${brokenFields.size} broken fields" }
 | 
			
		||||
        logger.fine("Found ${brokenFields.size} broken fields")
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Make a field public.
 | 
			
		||||
@@ -153,7 +153,7 @@ internal object ClassMerger {
 | 
			
		||||
 | 
			
		||||
        if (brokenMethods.isEmpty()) return this
 | 
			
		||||
 | 
			
		||||
        logger.fine { "Found ${brokenMethods.size} methods" }
 | 
			
		||||
        logger.fine("Found ${brokenMethods.size} methods")
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Make a method public.
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import kotlin.reflect.KProperty
 | 
			
		||||
/**
 | 
			
		||||
 * A navigator for methods.
 | 
			
		||||
 *
 | 
			
		||||
 * @param context The [BytecodePatchContext] to use.
 | 
			
		||||
 * @param startMethod The [Method] to start navigating from.
 | 
			
		||||
 *
 | 
			
		||||
 * @constructor Creates a new [MethodNavigator].
 | 
			
		||||
@@ -24,16 +25,12 @@ import kotlin.reflect.KProperty
 | 
			
		||||
 * @throws NavigateException If the method does not have an implementation.
 | 
			
		||||
 * @throws NavigateException If the instruction at the specified index is not a method reference.
 | 
			
		||||
 */
 | 
			
		||||
context(BytecodePatchContext)
 | 
			
		||||
class MethodNavigator internal constructor(
 | 
			
		||||
    private var startMethod: MethodReference,
 | 
			
		||||
) {
 | 
			
		||||
class MethodNavigator internal constructor(private val context: BytecodePatchContext, private var startMethod: MethodReference) {
 | 
			
		||||
    private var lastNavigatedMethodReference = startMethod
 | 
			
		||||
 | 
			
		||||
    private val lastNavigatedMethodInstructions
 | 
			
		||||
        get() = with(original()) {
 | 
			
		||||
            instructionsOrNull ?: throw NavigateException("Method $this does not have an implementation.")
 | 
			
		||||
        }
 | 
			
		||||
    private val lastNavigatedMethodInstructions get() = with(original()) {
 | 
			
		||||
        instructionsOrNull ?: throw NavigateException("Method $definingClass.$name does not have an implementation.")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Navigate to the method at the specified index.
 | 
			
		||||
@@ -42,7 +39,7 @@ class MethodNavigator internal constructor(
 | 
			
		||||
     *
 | 
			
		||||
     * @return This [MethodNavigator].
 | 
			
		||||
     */
 | 
			
		||||
    fun to(vararg index: Int): MethodNavigator {
 | 
			
		||||
    fun at(vararg index: Int): MethodNavigator {
 | 
			
		||||
        index.forEach {
 | 
			
		||||
            lastNavigatedMethodReference = lastNavigatedMethodInstructions.getMethodReferenceAt(it)
 | 
			
		||||
        }
 | 
			
		||||
@@ -56,7 +53,7 @@ class MethodNavigator internal constructor(
 | 
			
		||||
     * @param index The index of the method to navigate to.
 | 
			
		||||
     * @param predicate The predicate to match.
 | 
			
		||||
     */
 | 
			
		||||
    fun to(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator {
 | 
			
		||||
    fun at(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator {
 | 
			
		||||
        lastNavigatedMethodReference = lastNavigatedMethodInstructions.asSequence()
 | 
			
		||||
            .filter(predicate).asIterable().getMethodReferenceAt(index)
 | 
			
		||||
 | 
			
		||||
@@ -80,7 +77,7 @@ class MethodNavigator internal constructor(
 | 
			
		||||
     *
 | 
			
		||||
     * @return The last navigated method mutably.
 | 
			
		||||
     */
 | 
			
		||||
    fun stop() = classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
 | 
			
		||||
    fun stop() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
 | 
			
		||||
        as MutableMethod
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -95,7 +92,7 @@ class MethodNavigator internal constructor(
 | 
			
		||||
     *
 | 
			
		||||
     * @return The last navigated method immutably.
 | 
			
		||||
     */
 | 
			
		||||
    fun original(): Method = classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
 | 
			
		||||
    fun original() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Predicate to match the class defining the current method reference.
 | 
			
		||||
@@ -107,10 +104,9 @@ class MethodNavigator internal constructor(
 | 
			
		||||
    /**
 | 
			
		||||
     * Find the first [lastNavigatedMethodReference] in the class.
 | 
			
		||||
     */
 | 
			
		||||
    private val ClassDef.firstMethodBySignature
 | 
			
		||||
        get() = methods.first {
 | 
			
		||||
            MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
 | 
			
		||||
        }
 | 
			
		||||
    private val ClassDef.firstMethodBySignature get() = methods.first {
 | 
			
		||||
        MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An exception thrown when navigating fails.
 | 
			
		||||
 
 | 
			
		||||
@@ -3,18 +3,21 @@ 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.iface.ClassDef
 | 
			
		||||
import com.android.tools.smali.dexlib2.iface.Method
 | 
			
		||||
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
 | 
			
		||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
 | 
			
		||||
import io.mockk.*
 | 
			
		||||
import io.mockk.every
 | 
			
		||||
import io.mockk.just
 | 
			
		||||
import io.mockk.mockk
 | 
			
		||||
import io.mockk.runs
 | 
			
		||||
import jdk.internal.module.ModuleBootstrap.patcher
 | 
			
		||||
import kotlinx.coroutines.flow.toList
 | 
			
		||||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
import org.junit.jupiter.api.BeforeEach
 | 
			
		||||
import org.junit.jupiter.api.assertAll
 | 
			
		||||
import java.util.logging.Logger
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
import kotlin.test.assertNotNull
 | 
			
		||||
import kotlin.test.assertTrue
 | 
			
		||||
import kotlin.test.*
 | 
			
		||||
 | 
			
		||||
internal object PatcherTest {
 | 
			
		||||
    private lateinit var patcher: Patcher
 | 
			
		||||
@@ -150,10 +153,10 @@ internal object PatcherTest {
 | 
			
		||||
        val patch = bytecodePatch {
 | 
			
		||||
            execute {
 | 
			
		||||
                // Fingerprint can never match.
 | 
			
		||||
                val fingerprint = fingerprint { }
 | 
			
		||||
                val match by fingerprint { }
 | 
			
		||||
 | 
			
		||||
                // Throws, because the fingerprint can't be matched.
 | 
			
		||||
                fingerprint.patternMatch
 | 
			
		||||
                match.patternMatch
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -190,6 +193,11 @@ internal object PatcherTest {
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
        every { with(patcher.context.bytecodeContext) { any<Fingerprint>().match } } answers { callOriginal() }
 | 
			
		||||
        every { with(patcher.context.bytecodeContext) { any<Fingerprint>().match(any<ClassDef>()) } } answers { callOriginal() }
 | 
			
		||||
        every { with(patcher.context.bytecodeContext) { any<Fingerprint>().match(any<Method>()) } } answers { callOriginal() }
 | 
			
		||||
        every { patcher.context.bytecodeContext.classBy(any()) } answers { callOriginal() }
 | 
			
		||||
        every { patcher.context.bytecodeContext.proxy(any()) } answers { callOriginal() }
 | 
			
		||||
 | 
			
		||||
        val fingerprint = fingerprint { returns("V") }
 | 
			
		||||
        val fingerprint2 = fingerprint { returns("V") }
 | 
			
		||||
@@ -200,21 +208,19 @@ internal object PatcherTest {
 | 
			
		||||
                execute {
 | 
			
		||||
                    fingerprint.match(classes.first().methods.first())
 | 
			
		||||
                    fingerprint2.match(classes.first())
 | 
			
		||||
                    fingerprint3.originalClassDef
 | 
			
		||||
                    fingerprint3.match
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        patches()
 | 
			
		||||
 | 
			
		||||
        with(patcher.context.bytecodeContext) {
 | 
			
		||||
            assertAll(
 | 
			
		||||
                "Expected fingerprints to match.",
 | 
			
		||||
                { assertNotNull(fingerprint.originalClassDefOrNull) },
 | 
			
		||||
                { assertNotNull(fingerprint2.originalClassDefOrNull) },
 | 
			
		||||
                { assertNotNull(fingerprint3.originalClassDefOrNull) },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        assertAll(
 | 
			
		||||
            "Expected fingerprints to match.",
 | 
			
		||||
            { assertNotNull(fingerprint._match) },
 | 
			
		||||
            { assertNotNull(fingerprint2._match) },
 | 
			
		||||
            { assertNotNull(fingerprint3._match) },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user