diff --git a/.github/workflows/update_documentation.yml b/.github/workflows/update_documentation.yml
new file mode 100644
index 0000000..77097e2
--- /dev/null
+++ b/.github/workflows/update_documentation.yml
@@ -0,0 +1,19 @@
+name: Update documentation
+
+on:
+ push:
+ paths:
+ - docs/**
+
+jobs:
+ trigger:
+ runs-on: ubuntu-latest
+ name: Dispatch event to documentation repository
+ if: github.ref == 'refs/heads/main'
+ steps:
+ - uses: peter-evans/repository-dispatch@v2
+ with:
+ token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
+ repository: revanced/revanced-documentation
+ event-type: update-documentation
+ client-payload: '{"repo": "${{ github.event.repository.name }}", "ref": "${{ github.ref }}"}'
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e44be5a..a33b315 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -64,7 +64,7 @@ This document describes how to contribute to ReVanced Patcher.
## π Resources to help you get started
-- The [documentation](https://github.com/ReVanced/revanced-patcher/tree/docs) contains the fundamentals
+- The [documentation](https://github.com/ReVanced/revanced-patcher/tree/docs/docs) contains the fundamentals
of ReVanced Patcher and how to use ReVanced Patcher to create patches
- [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
- [Issues](https://github.com/ReVanced/revanced-patcher/issues) are where we keep track of bugs and feature requests
diff --git a/README.md b/README.md
index 353926d..d30613e 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,7 @@ To use ReVanced Patcher in your project, follow these steps:
to your project
2. Add the dependency to your project:
- ```kotlin
+ ```kt
dependencies {
implementation("app.revanced:revanced-patcher:{$version}")
}
@@ -115,7 +115,7 @@ you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced
### π Documentation
The documentation contains the fundamentals of ReVanced Patcher and how to use ReVanced Patcher to create patches.
-You can find it [here](https://github.com/ReVanced/revanced-patcher/tree/docs).
+You can find it [here](https://github.com/ReVanced/revanced-patcher/tree/docs/docs).
## π Licence
diff --git a/docs/1_patcher_intro.md b/docs/1_patcher_intro.md
new file mode 100644
index 0000000..983d758
--- /dev/null
+++ b/docs/1_patcher_intro.md
@@ -0,0 +1,108 @@
+
+
+# π Fingerprinting
+
+In the context of ReVanced, fingerprinting is primarily used to resolve methods with a limited amount of known information.
+Methods with obfuscated names that change with each update are primary candidates for fingerprinting.
+The goal of fingerprinting is to uniquely identify a method by capturing various attributes, such as the return type, access flags, an opcode pattern, strings, and more.
+
+## β³οΈ Example fingerprint
+
+Throughout the documentation, the following example will be used to demonstrate the concepts of fingerprints:
+
+```kt
+
+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;" }
+)
+```
+
+## π Reconstructing the original code from a fingerprint
+
+The following code is reconstructed from the fingerprint to understand how a fingerprint is created.
+
+The fingerprint contains the following information:
+
+- Method signature:
+
+ ```kt
+ returnType = "Z",
+ access = AccessFlags.PUBLIC or AccessFlags.FINAL,
+ parameters = listOf("Z"),
+ ```
+
+- Method implementation:
+
+ ```kt
+ opcodes = listOf(Opcode.RETURN)
+ strings = listOf("pro"),
+ ```
+
+- Package and class name:
+
+ ```kt
+ customFingerprint = { (methodDef, classDef) -> methodDef.definingClass == "Lcom/some/app/ads/AdsLoader;"}
+ ```
+
+With this information, the original code can be reconstructed:
+
+```java
+ package com.some.app.ads;
+
+ class AdsLoader {
+ public final boolean (boolean ) {
+ // ...
+
+ var userStatus = "pro";
+
+ // ...
+
+ return ;
+ }
+ }
+```
+
+> [!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 is likely to 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 creating a fingerprint, add it to the constructor of a `BytecodePatch`:
+
+```kt
+object DisableAdsPatch : BytecodePatch(
+ setOf(ShowAdsFingerprint)
+) {
+ // ...
+ }
+```
+
+> [!NOTE]
+> Fingerprints passed to the constructor of `BytecodePatch` are resolved by ReVanced Patcher before the patch is executed.
+
+> [!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.
+> 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`.
+
+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")
+
+ // ...
+ }
+}
+```
+
+The result of a fingerprint that resolved successfully contains mutable and immutable references to the method and the class it is defined in.
+
+```kt
+class MethodFingerprintResult(
+ val method: Method,
+ val classDef: ClassDef,
+ val scanResult: MethodFingerprintScanResult,
+ // ...
+) {
+ val mutableClass by lazy { /* ... */ }
+ val mutableMethod by lazy { /* ... */ }
+
+ // ...
+}
+
+class MethodFingerprintScanResult(
+ val patternScanResult: PatternScanResult?,
+ val stringsScanResult: StringsScanResult?,
+) {
+ class StringsScanResult(val matches: List) {
+ class StringMatch(val string: String, val index: Int)
+ }
+
+ class PatternScanResult(
+ val startIndex: Int,
+ val endIndex: Int,
+ // ...
+ ) {
+ // ...
+ }
+}
+```
+
+## πΉ 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.
+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:
+
+- On a **list of classes**, if the fingerprint can resolve on a known subset of classes
+
+ 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")
+
+ // ...
+ }
+ ```
+
+- 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;" }
+
+ val result = ShowAdsFingerprint.also { it.resolve(context, adsLoaderClass) }.result
+ ?: throw PatchException("ShowAdsFingerprint not found")
+
+ // ...
+ }
+ ```
+
+- On a **single method**, to extract certain information about a method
+
+ The result 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.
+ 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")
+
+ val proStringsFingerprint = object : MethodFingerprint(
+ strings = listOf("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")
+ }
+ ```
+
+> [!TIP]
+> To see real-world examples of fingerprints, check out the [ReVanced Patches](https://github.com/revanced/revanced-patches) repository.
+
+## βοΈ What's next
+
+The next page discusses the structure and conventions of patches.
+
+Continue: [π Project structure and conventions](3_structure_and_conventions.md)
diff --git a/docs/2_2_patch_anatomy.md b/docs/2_2_patch_anatomy.md
new file mode 100644
index 0000000..9b941f3
--- /dev/null
+++ b/docs/2_2_patch_anatomy.md
@@ -0,0 +1,211 @@
+