Skip to content

Commit 0746c22

Browse files
committed
feat: Move fingerprint match members to fingerprint for ease of access by using context receivers
1 parent 7f55868 commit 0746c22

File tree

9 files changed

+324
-217
lines changed

9 files changed

+324
-217
lines changed

api/revanced-patcher.api

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
public final class app/revanced/patcher/Fingerprint {
2+
public final fun getClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
3+
public final fun getClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
4+
public final fun getMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
5+
public final fun getMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
6+
public final fun getOriginalClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
7+
public final fun getOriginalClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
8+
public final fun getOriginalMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
9+
public final fun getOriginalMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
10+
public final fun getPatternMatch (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
11+
public final fun getPatternMatchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
12+
public final fun getStringMatches (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
13+
public final fun getStringMatchesOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
14+
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
15+
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
16+
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;
17+
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
18+
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
19+
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;
220
}
321

422
public final class app/revanced/patcher/FingerprintBuilder {
@@ -31,13 +49,11 @@ public final class app/revanced/patcher/Match {
3149
}
3250

3351
public final class app/revanced/patcher/Match$PatternMatch {
34-
public fun <init> (II)V
3552
public final fun getEndIndex ()I
3653
public final fun getStartIndex ()I
3754
}
3855

3956
public final class app/revanced/patcher/Match$StringMatch {
40-
public fun <init> (Ljava/lang/String;I)V
4157
public final fun getIndex ()I
4258
public final fun getString ()Ljava/lang/String;
4359
}
@@ -146,10 +162,6 @@ public final class app/revanced/patcher/patch/BytecodePatchContext : app/revance
146162
public synthetic fun get ()Ljava/lang/Object;
147163
public fun get ()Ljava/util/Set;
148164
public final fun getClasses ()Lapp/revanced/patcher/util/ProxyClassList;
149-
public final fun getMatch (Lapp/revanced/patcher/Fingerprint;)Lapp/revanced/patcher/Match;
150-
public final fun getValue (Lapp/revanced/patcher/Fingerprint;Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/Match;
151-
public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
152-
public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
153165
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)Lapp/revanced/patcher/util/MethodNavigator;
154166
public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
155167
}
@@ -468,12 +480,12 @@ public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w
468480
}
469481

470482
public final class app/revanced/patcher/util/MethodNavigator {
471-
public final fun at (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
472-
public final fun at ([I)Lapp/revanced/patcher/util/MethodNavigator;
473-
public static synthetic fun at$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
474483
public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
475484
public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
476485
public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
486+
public final fun to (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
487+
public final fun to ([I)Lapp/revanced/patcher/util/MethodNavigator;
488+
public static synthetic fun to$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
477489
}
478490

479491
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ dependencies {
5656
kotlin {
5757
compilerOptions {
5858
jvmTarget.set(JvmTarget.JVM_11)
59+
60+
freeCompilerArgs = listOf("-Xcontext-receivers")
5961
}
6062
}
6163

docs/2_2_1_fingerprinting.md

Lines changed: 52 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,19 @@ With this information, the original code can be reconstructed:
117117
```java
118118
package com.some.app.ads;
119119

120-
<accessFlags> class AdsLoader {
121-
public final boolean <methodName>(boolean <parameter>) {
120+
<accessFlags>
121+
122+
class AdsLoader {
123+
public final boolean <methodName>(boolean <parameter>)
124+
125+
{
122126
// ...
123127

124128
var userStatus = "pro";
125129

126130
// ...
127131

128-
return <returnValue>;
132+
return <returnValue >;
129133
}
130134
}
131135
```
@@ -134,13 +138,14 @@ Using that fingerprint, this method can be matched uniquely from all other metho
134138

135139
> [!TIP]
136140
> A fingerprint should contain information about a method likely to remain the same across updates.
137-
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated app.
138-
> In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the same.
141+
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated
142+
> app.
143+
> In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the
144+
> same.
139145
140146
## 🔨 How to use fingerprints
141147

142-
A fingerprint is matched to a method,
143-
once the `match` property of the fingerprint is accessed in a patch's `execute` scope:
148+
After declaring a fingerprint, it can be used in a patch to find the method it matches to:
144149

145150
```kt
146151
val fingerprint = fingerprint {
@@ -149,52 +154,34 @@ val fingerprint = fingerprint {
149154

150155
val patch = bytecodePatch {
151156
execute {
152-
val match = fingerprint.match!!
157+
fingerprint.method
153158
}
154159
}
155160
```
156161

157-
The fingerprint won't be matched again, if it has already been matched once.
158-
This makes it useful, to share fingerprints between multiple patches, and let the first patch match the fingerprint:
162+
The fingerprint won't be matched again, if it has already been matched once, for performance reasons.
163+
This makes it useful, to share fingerprints between multiple patches,
164+
and let the first executing patch match the fingerprint:
159165

160166
```kt
161167
// Either of these two patches will match the fingerprint first and the other patch can reuse the match:
162168
val mainActivityPatch1 = bytecodePatch {
163169
execute {
164-
val match = mainActivityOnCreateFingerprint.match!!
170+
mainActivityOnCreateFingerprint.method
165171
}
166172
}
167173

168174
val mainActivityPatch2 = bytecodePatch {
169175
execute {
170-
val match = mainActivityOnCreateFingerprint.match!!
171-
}
172-
}
173-
```
174-
175-
A fingerprint match can also be delegated to a variable for convenience without the need to check for `null`:
176-
```kt
177-
val fingerprint = fingerprint {
178-
// ...
179-
}
180-
181-
val patch = bytecodePatch {
182-
execute {
183-
// Alternative to fingerprint.match ?: throw PatchException("No match found")
184-
val match by fingerprint.match
185-
186-
try {
187-
match.method
188-
} catch (e: PatchException) {
189-
// Handle the exception for example.
190-
}
176+
mainActivityOnCreateFingerprint.method
191177
}
192178
}
193179
```
194180

195181
> [!WARNING]
196-
> If the fingerprint can not be matched to any method, the match of a fingerprint is `null`. If such a match is delegated
197-
> to a variable, accessing it will raise an exception.
182+
> If the fingerprint can not be matched to any method,
183+
> accessing certain properties of the fingerprint will raise an exception.
184+
> Instead, the `orNull` properties can be used to return `null` if no match is found.
198185
199186
> [!TIP]
200187
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode`
@@ -211,47 +198,43 @@ val patch = bytecodePatch {
211198
> )
212199
>}
213200
> ```
214-
>
215-
The match of a fingerprint contains references to the original method and class definition of the method:
216201
217-
```kt
218-
class Match(
219-
val originalMethod: Method,
220-
val originalClassDef: ClassDef,
221-
val patternMatch: Match.PatternMatch?,
222-
val stringMatches: List<Match.StringMatch>?,
223-
// ...
224-
) {
225-
val classDef by lazy { /* ... */ }
226-
val method by lazy { /* ... */ }
202+
The following properties can be accessed in a fingerprint:
227203
228-
// ...
229-
}
230-
```
204+
- `originalClassDef`: The original class definition the fingerprint matches to.
205+
- `originalClassDefOrNull`: The original class definition the fingerprint matches to.
206+
- `originalMethod`: The original method the fingerprint matches to.
207+
- `originalMethodOrNull`: The original method the fingerprint matches to.
208+
- `classDef`: The class the fingerprint matches to.
209+
- `classDefOrNull`: The class the fingerprint matches to.
210+
- `method`: The method the fingerprint matches to. If no match is found, an exception is raised.
211+
- `methodOrNull`: The method the fingerprint matches to.
231212
232-
The `classDef` and `method` properties can be used to make changes to the class or method.
233-
They are lazy properties, so they are only computed
234-
and will effectively replace the original method or class definition when accessed.
213+
The difference between the `original` and non-`original` properties is that the `original` properties return the
214+
original class or method definition, while the non-`original` properties return a mutable copy of the class or method.
215+
The mutable copies can be modified. They are lazy properties, so they are only computed
216+
and only then will effectively replace the `original` method or class definition when accessed.
235217
236218
> [!TIP]
237-
> If only read-only access to the class or method is needed,
238-
> the `originalClassDef` and `originalMethod` properties can be used,
219+
> If only read-only access to the class or method is needed,
220+
> the `originalClassDef` and `originalMethod` properties should be used,
239221
> to avoid making a mutable copy of the class or method.
240222
241223
## 🏹 Manually matching fingerprints
242224
243-
By default, a fingerprint is matched automatically against all classes when the `match` property is accessed.
225+
By default, a fingerprint is matched automatically against all classes
226+
when one of the fingerprint's properties is accessed.
244227
245228
Instead, the fingerprint can be matched manually using various overloads of a fingerprint's `match` function:
246229
247230
- In a **list of classes**, if the fingerprint can match in a known subset of classes
248231
249232
If you have a known list of classes you know the fingerprint can match in,
250-
you can match the fingerprint on the list of classes:
233+
you can match the fingerprint on the list of classes:
251234
252235
```kt
253236
execute {
254-
val match = showAdsFingerprint.match(classes) ?: throw PatchException("No match found")
237+
val match = showAdsFingerprint(classes)
255238
}
256239
```
257240
@@ -263,23 +246,24 @@ you can match the fingerprint on the list of classes:
263246
execute {
264247
val adsLoaderClass = classes.single { it.name == "Lcom/some/app/ads/Loader;" }
265248

266-
val match = showAdsFingerprint.match(context, adsLoaderClass) ?: throw PatchException("No match found")
249+
val match = showAdsFingerprint.match(adsLoaderClass)
267250
}
268251
```
269-
252+
270253
Another common usecase is to use a fingerprint to reduce the search space of a method to a single class.
271254

272255
```kt
273256
execute {
274257
// Match showAdsFingerprint in the class of the ads loader found by adsLoaderClassFingerprint.
275-
val match by showAdsFingerprint.match(adsLoaderClassFingerprint.match!!.classDef)
258+
val match = showAdsFingerprint.match(adsLoaderClassFingerprint.classDef)
276259
}
277260
```
278261

279262
- Match a **single method**, to extract certain information about it
280263

281264
The match of a fingerprint contains useful information about the method,
282-
such as the start and end index of an opcode pattern or the indices of the instructions with certain string references.
265+
such as the start and end index of an opcode pattern or the indices of the instructions with certain string
266+
references.
283267
A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:
284268

285269
```kt
@@ -288,14 +272,19 @@ you can match the fingerprint on the list of classes:
288272
strings("free", "trial")
289273
}
290274

291-
currentPlanFingerprint.match(adsFingerprintMatch.method)?.let { match ->
275+
currentPlanFingerprint.match(adsFingerprint.method).let { match ->
292276
match.stringMatches.forEach { match ->
293277
println("The index of the string '${match.string}' is ${match.index}")
294278
}
295-
} ?: throw PatchException("No match found")
279+
}
296280
}
297281
```
298282

283+
> [!WARNING]
284+
> If the fingerprint can not be matched to any method, calling `match` will raise an
285+
> exception.
286+
> Instead, the `orNull` overloads can be used to return `null` if no match is found.
287+
299288
> [!TIP]
300289
> To see real-world examples of fingerprints,
301290
> check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).

docs/4_apis.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,17 @@ The `navigate(Method)` function allows you to navigate method calls recursively
4646
```kt
4747
execute {
4848
// Sequentially navigate to the instructions at index 1 within 'someMethod'.
49-
val method = navigate(someMethod).at(1).original() // original() returns the original immutable method.
49+
val method = navigate(someMethod).to(1).original() // original() returns the original immutable method.
5050

5151
// Further navigate to the second occurrence where the instruction's opcode is 'INVOKEVIRTUAL'.
5252
// stop() returns the mutable copy of the method.
53-
val method = navigate(someMethod).at(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
53+
val method = navigate(someMethod).to(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
5454

5555
// Alternatively, to stop(), you can delegate the method to a variable.
56-
val method by navigate(someMethod).at(1)
56+
val method by navigate(someMethod).to(1)
5757

5858
// You can chain multiple calls to at() to navigate deeper into the method.
59-
val method by navigate(someMethod).at(1).at(2, 3, 4).at(5)
59+
val method by navigate(someMethod).to(1).to(2, 3, 4).to(5)
6060
}
6161
```
6262

@@ -85,7 +85,7 @@ execute {
8585
The `document` function is used to read and write DOM files.
8686

8787
```kt
88-
execute {
88+
execute {
8989
document("res/values/strings.xml").use { document ->
9090
val element = doc.createElement("string").apply {
9191
textContent = "Hello, World!"
@@ -112,5 +112,6 @@ ReVanced Patcher is a powerful library to patch Android applications, offering a
112112
that outlive app updates. Patches make up ReVanced; without you, the community of patch developers,
113113
ReVanced would not be what it is today. We hope that this documentation has been helpful to you
114114
and are excited to see what you will create with ReVanced Patcher. If you have any questions or need help,
115-
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,
115+
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
116+
feature request,
116117
ReVanced

gradle.properties

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
org.gradle.parallel = true
2-
org.gradle.caching = true
3-
version = 21.0.0-dev.2
1+
org.gradle.parallel=true
2+
org.gradle.caching=true
3+
version=21.0.0-dev.3

0 commit comments

Comments
 (0)