Skip to content

Commit

Permalink
docs: Update docs to match new changes
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Apr 28, 2024
1 parent 85c83e0 commit 2503283
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 214 deletions.
29 changes: 13 additions & 16 deletions docs/1_patcher_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,26 @@ ReVanced Patcher has a simple API that allows you to load patches and integratio
Later on, you will learn how to create patches.

```kt
// Executed patches do not necessarily reset their state.
// For that reason it is important to create a new instance of the PatchBundleLoader
// once the patches are executed instead of reusing the same instance of patches loaded by PatchBundleLoader.
val patches: PatchSet /* = Set<Patch<*>> */ = PatchBundleLoader.Jar(File("revanced-patches.jar"))
val patches = loadPatchesFromJar(File("revanced-patches.jar"))
val integrations = setOf(File("integrations.apk"))

// Instantiating the patcher will decode the manifest of the APK file to read the package and version name.
val patcherConfig = PatcherConfig(apkFile = File("some.apk"))
val patcherResult = Patcher(patcherConfig).use { patcher ->
patcher.apply {
acceptIntegrations(integrations)
acceptPatches(patches)
patcher.accept(patches, integrations)

// Execute patches.
runBlocking {
patcher.apply(returnOnError = false).collect { patchResult ->
if (patchResult.exception != null)
println("${patchResult.patchName} failed:\n${patchResult.exception}")
else
println("${patchResult.patchName} succeeded")
}
// Execute patches.
patcher.runBlocking {
patcher.apply(returnOnError = false).collect { patchResult ->
if (patchResult.exception != null)
println("${patchResult.patchName} failed:\n${patchResult.exception}")
else
println("${patchResult.patchName} succeeded")
}
}.get()
}

// Compile and save the patched APK file.
patcher.get()
}

// The result of the patcher contains the modified components of the APK file that can be repackaged into a new APK file.
Expand Down
124 changes: 70 additions & 54 deletions docs/2_2_1_fingerprinting.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,19 @@ Throughout the documentation, the following example will be used to demonstrate

package app.revanced.patches.ads.fingerprints

object ShowAdsFingerprint : MethodFingerprint(
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
opcodes = listOf(Opcode.RETURN),
strings = listOf("pro"),
customFingerprint = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;" }
)
methodFingerprint {
returns("Z")

accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)

parameters("Z")

opcodes(Opcode.RETURN)

strings("pro")

custom { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;" }
}
```

## 馃攷 Reconstructing the original code from a fingerprint
Expand All @@ -91,22 +96,22 @@ The fingerprint contains the following information:
- Method signature:

```kt
returnType = "Z",
access = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
returns("Z")
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters("Z")
```

- Method implementation:

```kt
opcodes = listOf(Opcode.RETURN)
strings = listOf("pro"),
opcodes(Opcode.RETURN)
strings("pro"),
```

- Package and class name:

```kt
customFingerprint = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;"}
custom = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;"}
```

With this information, the original code can be reconstructed:
Expand All @@ -133,45 +138,59 @@ With this information, the original code can be reconstructed:
## 馃敤 How to use fingerprints

After creating a fingerprint, add it to the constructor of a `BytecodePatch`:
Fingerprints can be added to a patch by directly creating and adding them or by invoking them manually. Fingerprints added to a patch are resolved by ReVanced Patcher before the patch is executed.

```kt
object DisableAdsPatch : BytecodePatch(
setOf(ShowAdsFingerprint)
) {
val fingerprint = methodFingerprint {
// ...
}
```
}

val patch = bytecodePatch {
// Directly create and add a fingerprint.
methodFingerprint {
// ...
}

> [!NOTE]
> Fingerprints passed to the constructor of `BytecodePatch` are resolved by ReVanced Patcher before the patch is executed.
// Add a fingerprint manually by invoking it.
fingerprint()
}
```

> [!TIP]
> Multiple patches can share fingerprints. If a fingerprint is resolved once, it will not be resolved again.
> [!TIP]
> If a fingerprint has an opcode pattern, you can use the `FuzzyPatternScanMethod` annotation to fuzzy match the pattern.
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode` function to fuzzy match the pattern.
> Opcode pattern arrays can contain `null` values to indicate that the opcode at the index is unknown.
> Any opcode will match to a `null` value.
> [!WARNING]
> If the fingerprint can not be resolved because it does not match any method, the result of a fingerprint is `null`.
> Any opcode will match to a `null` value:
>
> ```kt
> methodFingerprint(fuzzyPatternScanThreshhold = 0.5) {
> opcodes(
> Opcode.ICONST_0,
> null,
> Opcode.ICONST_1,
> Opcode.IRETURN,
> )
>}
> ```
Once the fingerprint is resolved, the result can be used in the patch:

```kt
object DisableAdsPatch : BytecodePatch(
setOf(ShowAdsFingerprint)
) {
override fun execute(context: BytecodeContext) {
val result = ShowAdsFingerprint.result
?: throw PatchException("ShowAdsFingerprint not found")
val patch = bytecodePatch {
// Add a fingerprint and delegate it's result to a variable.
val result by showAdsFingerprint

// ...
execute {
val method = result.method
}
}
```

> [!WARNING]
> If the fingerprint can not be resolved because it does not match any method, the result of a fingerprint is `null`. If the result is delegated to a variable, accessing it will raise an exception.
The result of a fingerprint that resolved successfully contains mutable and immutable references to the method and the class it is defined in.

```kt
Expand Down Expand Up @@ -207,7 +226,7 @@ class MethodFingerprintScanResult(

## 馃徆 Manual resolution of fingerprints

Unless a fingerprint is added to the constructor of `BytecodePatch`, the fingerprint will not be resolved automatically by ReVanced Patcher before the patch is executed.
Unless a fingerprint is added to a patch, the fingerprint will not be resolved automatically by ReVanced Patcher before the patch is executed.
Instead, the fingerprint can be resolved manually using various overloads of the `resolve` function of a fingerprint.

You can resolve a fingerprint in the following ways:
Expand All @@ -217,26 +236,24 @@ You can resolve a fingerprint in the following ways:
If you have a known list of classes you know the fingerprint can resolve on, you can resolve the fingerprint on the list of classes:

```kt
override fun execute(context: BytecodeContext) {
val result = ShowAdsFingerprint.also { it.resolve(context, context.classes) }.result
?: throw PatchException("ShowAdsFingerprint not found")

// ...
}
execute {
val result = showAdsFingerprint.also {
it.resolve(context, this.classes)
}.result ?: throw PatchException("showAdsFingerprint not found")
}
```

- On a **single class**, if the fingerprint can resolve on a single known class

If you know the fingerprint can resolve to a method in a specific class, you can resolve the fingerprint on the class:

```kt
override fun execute(context: BytecodeContext) {
val adsLoaderClass = context.classes.single { it.name == "Lcom/some/app/ads/Loader;" }
execute {
val adsLoaderClass = classes.single { it.name == "Lcom/some/app/ads/Loader;" }

val result = ShowAdsFingerprint.also { it.resolve(context, adsLoaderClass) }.result
?: throw PatchException("ShowAdsFingerprint not found")

// ...
val result = showAdsFingerprint.also {
it.resolve(context, adsLoaderClass)
}.result ?: throw PatchException("showAdsFingerprint not found")
}
```

Expand All @@ -246,21 +263,20 @@ You can resolve a fingerprint in the following ways:
A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:

```kt
override fun execute(context: BytecodeContext) {
val adsFingerprintResult = ShowAdsFingerprint.result
?: throw PatchException("ShowAdsFingerprint not found")
execute {
val adsFingerprintResult = showAdsFingerprint.result
?: throw PatchException("showAdsFingerprint not found")

val proStringsFingerprint = object : MethodFingerprint(
strings = listOf("free", "trial")
) {}
val proStringsFingerprint = methodFingerprint {
strings("free", "trial")
}

proStringsFingerprint.also {
it.resolve(context, adsFingerprintResult.method)
}.result?.let { result ->
result.scanResult.stringsScanResult!!.matches.forEach { match ->
println("The index of the string '${match.string}' is ${match.index}")
}

} ?: throw PatchException("pro strings fingerprint not found")
}
```
Expand Down
Loading

0 comments on commit 2503283

Please sign in to comment.