9.6 KiB
Continuing the legacy of Vanced
🧩 Anatomy of a ReVanced patch
Learn the API to create patches using ReVanced Patcher.
⛳️ Example patch
Throughout the documentation, the following example will be used to demonstrate the concepts of patches:
package app.revanced.patches.ads
@Patch(
name = "Disable ads",
description = "Disable ads in the app.",
dependencies = [DisableAdsResourcePatch::class],
compatiblePackages = [CompatiblePackage("com.some.app", ["1.3.0"])]
)
object DisableAdsPatch : BytecodePatch(
setOf(ShowAdsFingerprint)
) {
override fun execute(context: BytecodeContext) {
ShowAdsFingerprint.result?.let { result ->
result.mutableMethod.addInstructions(
0,
"""
# Return false.
const/4 v0, 0x0
return v0
"""
)
} ?: throw PatchException("ShowAdsFingerprint not found")
}
}
🔎 Breakdown
The example patch consists of the following parts:
📝 Patch annotation
@Patch(
name = "Disable ads",
description = "Disable ads in the app.",
dependencies = [DisableAdsResourcePatch::class],
compatiblePackages = [CompatiblePackage("com.some.app", ["1.3.0"])]
)
The @Patch
annotation is used to provide metadata about the patch.
Notable annotation parameters are:
name
: The name of the patch. This is used as an identifier for the patch. If this parameter is not set,PatchBundleLoader
will not load the patch. Other patches can still use this patch as a dependencydescription
: A description of the patch. Can be unset if the name is descriptive enoughdependencies
: A set of patches which the patch depends on. The patches in this set will be executed before this patch. If a dependency patch raises an exception, this patch will not be executed; subsquently, other patches that depend on this patch will not be executed.compatiblePackages
: A set ofCompatiblePackage
objects. EachCompatiblePackage
object contains the package name and a set of compatible version names. This parameter can specify the packages and versions the patch is compatible with. Patches can still execute on incompatible packages, but it is recommended to use this parameter to list known compatible packages- If unset, it is implied that the patch is compatible with all packages
- If the set of versions is unset, it is implied that the patch is compatible with all versions of the package
- If the set of versions is empty, it is implied that the patch is not compatible with any version of the package. This can be useful, for example, to prevent a patch from executing on specific packages that are known to be incompatible
Warning
Circular dependencies are not allowed. If a patch depends on another patch, the other patch cannot depend on the first patch.
Note
The
@Patch
annotation is optional. If the patch does not require any metadata, it can be omitted. If the patch is only used as a dependency, the metadata, such as thecompatiblePackages
parameter, has no effect, as every dependency patch inherits the compatible packages of the patches that depend on it.
Tip
An abstract patch class can be annotated with
@Patch
. Patches extending off the abstract patch class will inherit the metadata of the abstract patch class.
Tip
Instead of the
@Patch
annotation, the superclass's constructor can be used. This is useful in the example scenario where you want to create an abstract patch class.Example:
abstract class AbstractDisableAdsPatch( fingerprints: Set<Fingerprint> ) : BytecodePatch( name = "Disable ads", description = "Disable ads in the app.", fingerprints ) { // ... }
Remember that this constructor has precedence over the
@Patch
annotation.
🏗️ Patch class
object DisableAdsPatch : BytecodePatch( /* Parameters */ ) {
// ...
}
Each patch class extends off a base class that implements the Patch
interface.
The interface requires the execute
method to be implemented.
Depending on which base class is extended, the patch can modify different parts of the APK as described in 🧩 Introduction to ReVanced Patches.
Tip
A patch is usually a singleton object, meaning only one patch instance exists in the JVM. Because dependencies are executed before the patch itself, a patch can rely on the state of the dependency patch. This is useful in the example scenario, where the
DisableAdsPatch
depends on theDisableAdsResourcePatch
. TheDisableAdsResourcePatch
can, for example, be used to read the decoded resources of the app and provide theDisableAdsPatch
with the necessary information to disable ads because theDisableAdsResourcePatch
is executed before theDisableAdsPatch
and is a singleton object.
🏁 The execute
function
The execute
function is declared in the Patch
interface and needs to be implemented.
The execute
function receives an instance of a context object that provides access to the APK. The patch can use this context to modify the APK as described in 🧩 Introduction to ReVanced Patches.
In the current example, the patch adds instructions at the beginning of a method implementation in the Dalvik VM bytecode. The added instructions return false
to disable ads in the current example:
val result = LoadAdsFingerprint.result
?: throw PatchException("LoadAdsFingerprint not found")
result.mutableMethod.addInstructions(
0,
"""
# Return false.
const/4 v0, 0x0
return v0
"""
)
Note
This patch uses a fingerprint to find the method and replaces the method's instructions with new instructions. The fingerprint is resolved on the classes present in
BytecodeContext
. Fingerprints will be explained in more detail on the next page.
Tip
The patch can also raise any
Exception
orThrowable
at any time to indicate that the patch failed to execute. APatchException
is recommended to be raised if the patch fails to execute. If any patch depends on this patch, the dependent patch will not be executed, whereas other patches that do not depend on this patch can still be executed. ReVanced Patcher will handle any exception raised by a patch.
Tip
To see real-world examples of patches, check out the ReVanced Patches repository.
⏭️ What's next
The next page explains the concept of fingerprinting in ReVanced Patcher.
Continue: 🔎 Fingerprinting