The primary hook entry point is MainHook.kt.
Patches adhere to a specific structure:
📦your.patches.app.category
├ Fingerprints.kt
└ SomePatch.kt
- Project-specific patches: app/src/main/java/io/github/nexalloy/morphe
- Upstream patches: revanced-patches/patches/src/main/kotlin/app/revanced/patches
Upstream patches are included via Git submodule for reference and to utilize shared extension code. They are not modified within this project.
app/src/main/AndroidManifest.xml: Query package for module settingsapp/src/main/res/values/arrays.xml: Xposed scope recommendationREADME.md
package io.github.nexalloy.morphe.contoso.misc.unlock.plus
import io.github.nexalloy.morphe.AccessFlags
import io.github.nexalloy.morphe.fingerprint
import org.luckypray.dexkit.query.enums.StringMatchType
val isPlusUnlockedFingerprint = fingerprint {
returns("Z")
strings("genius")
}package io.github.nexalloy.morphe.contoso.misc.unlock.plus
import static de.robv.android.xposed.XC_MethodReplacement.returnConstant
import io.github.nexalloy.morphe.patch
val UnlockPlus = patch(name = "Unlock Plus") {
::isPlusUnlockedFingerprint.hookMethod(returnConstant(true))
}package io.github.nexalloy.morphe.contoso
import io.github.nexalloy.morphe.contoso.misc.unlock.plus.UnlockPlus
val ContosoPatches = arrayOf(UnlockPlus)import io.github.nexalloy.morphe.contoso.ContosoPatches
val appPatchConfigurations = listOf(
// ...
AppPatchInfo("Contoso", "com.contoso.app", ContosoPatches)
)
The FingerprintCompat.kt utility assists in translating ReVanced Patcher API calls to DexKit Matchers. While simple fingerprints can often be directly copied, consider the following translation patterns for complex cases:
-
Literal Mapping: If an upstream patch defines a literal within the patch file (e.g.,
aLiteral = resourceMappings["id", "aLiteral"]) for use in a fingerprint, convert it to a getter property in the correspondingFingerprints.ktfile.From (Upstream):
// In ***Patch.kt beside the Fingerprints.kt // val aLiteral = resourceMappings["id", "aLiteral"] fingerprint { literal { aLiteral } }
To (This Project's Fingerprints.kt):
val aLiteral get() = resourceMappings["id", "aLiteral"] // Defined in Fingerprints.kt fingerprint { literal { aLiteral } }
-
Custom Class and Method Matchers:
From (Upstream):
fingerprint { custom { method, classDef -> method.name == "onCreate" && classDef.endsWith("/MusicActivity;") } }To (This Project):
fingerprint { methodMatcher { name = "onCreate" } classMatcher { className(".MusicActivity", StringMatchType.EndsWith) } } -
Instruction-based Method Reference:
From (Upstream):
fun indexOfTranslationInstruction(method: Method) = method.indexOfFirstInstructionReversed { getReference<MethodReference>()?.name == "setTranslationY" } val motionEventFingerprint = fingerprint { custom { method, _ -> indexOfTranslationInstruction(method) >= 0 } }
To (This Project):
val motionEventFingerprint = fingerprint { methodMatcher { addInvoke { name = "setTranslationY" } } }
-
Matching Specific Class Types or Defining Classes: When an upstream
customblock primarily checksclassDef.type(the type of the matched class) ormethod.definingClass(the class defining the matched method), this translates to aclassMatcherin this project. TheclassMatcherdirectly specifies the target class descriptor.From (Upstream Example):
// Upstream: Using custom to check classDef.type fingerprint { custom { _, classDef -> classDef.type == "Lcom/example/SomeClass;" } // other matchers... } // Upstream: Using custom to check method.definingClass fingerprint { custom { method, _ -> method.definingClass == "Lcom/example/AnotherClass;" } // other method matchers... }
To (This Project's Fingerprints.kt Example):
// This Project: Using classMatcher for classDef.type fingerprint { classMatcher { descriptor = "Lcom/example/SomeClass;" } // methodMatcher { ... } // if needed for method properties } // This Project: Using classMatcher for method.definingClass fingerprint { // Targets methods within Lcom/example/AnotherClass; classMatcher { descriptor = "Lcom/example/AnotherClass;" } methodMatcher { // specific method properties, e.g., name = "targetMethod" } }
-
Porting Complex
customLogic or Chained Lookups with Direct Finders: For intricate upstream fingerprints with complexcustomlogic or chained lookups not easily mapped to standard matchers, this project uses direct finder functions (e.g.,findMethodDirect,findClassDirect) fromFingerprintCompat.kt. This approach combines DexKit's efficient initial filtering with the power of Kotlin's collection processing for subsequent, more granular refinement.From (Upstream Example with complex
customlogic):internal val complexCustomFingerprint = fingerprint { returns("Lcom/example/ReturnType;") custom { method, _ -> method.name.startsWith("get") && method.parameterTypes.size == 1 && method.parameterTypes.first() == "Lcom/example/ParameterType;" && method.definingClass == "Lcom/example/HostClass;" // ... potentially more complex conditions } }
To (This Project using
findMethodDirect):val complexCustomFingerprint = findMethodDirect { // Initial, broader filtering with DexKit matchers findMethod { matcher { returns("Lcom/example/ReturnType;") declaredClass { descriptor = "Lcom/example/HostClass;" } // Other simple matchers convertible from the original custom block } } // Refine results using Kotlin's collection functions for complex logic .filter { methodData -> methodData.name.startsWith("get") } .single { methodData -> // Assuming a unique result after all filters methodData.paramTypes.size == 1 && methodData.paramTypes.firstOrNull()?.descriptor == "Lcom/example/ParameterType;" // ... other Kotlin-based checks } }
This allows leveraging DexKit for initial efficient filtering, then applying precise Kotlin logic to the narrowed-down candidates.
As per ReVanced Patcher documentation:
Instead of involving many abstract changes in one patch or writing entire methods or classes in a patch, you can write code in extensions.
This project shares extension code with the upstream project, located at ./revanced-patches/extensions. Upstream organizes extensions into separate modules (e.g., ./revanced-patches/extensions/<appAlias>/src/main/java/app/revanced/extension/<appAlias>). Modifications to shared extensions and other code under revanced-patches are minimized.
Refer to FingerprintsKtTest.kt for testing examples.
For running tests, place necessary APKs into the ./app/binaries/ directory. APK filenames should be prefixed with their respective package names (e.g., com.example.app-1.0.0.apk).