Skip to content

Commit b60f3df

Browse files
committed
Add Unit Test
1 parent a73bd02 commit b60f3df

File tree

36 files changed

+465
-45
lines changed

36 files changed

+465
-45
lines changed

DEVELOPMENT.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# Development Guide
2+
3+
## Project Structure
4+
5+
The primary hook entry point is [MainHook.kt](app/src/main/java/io/github/chsbuffer/revancedxposed/MainHook.kt).
6+
7+
### Patch Organization and Conventions
8+
9+
Patches adhere to a specific structure:
10+
11+
```text
12+
📦your.patches.app.category
13+
├ Fingerprints.kt
14+
└ SomePatch.kt
15+
```
16+
17+
- **Project-specific patches:** [app/src/main/java/io/github/chsbuffer/revancedxposed](app/src/main/java/io/github/chsbuffer/revancedxposed)
18+
- **Upstream patches:** [revanced-patches/patches/src/main/kotlin/app/revanced/patches](revanced-patches/patches/src/main/kotlin/app/revanced/patches)
19+
20+
Upstream patches are included via Git submodule for reference and to utilize shared extension code. They are not modified within this project.
21+
22+
### Example: Patch Implementation (Contoso App)
23+
24+
#### `Fingerprints.kt`
25+
```kotlin
26+
package io.github.chsbuffer.revancedxposed.contoso.misc.unlock.plus
27+
28+
import io.github.chsbuffer.revancedxposed.AccessFlags
29+
import io.github.chsbuffer.revancedxposed.fingerprint
30+
import org.luckypray.dexkit.query.enums.StringMatchType
31+
32+
val isPlusUnlockedFingerprint = fingerprint {
33+
returns("Z")
34+
strings("genius")
35+
}
36+
```
37+
38+
#### `UnlockPlusPatch.kt`
39+
```kotlin
40+
package io.github.chsbuffer.revancedxposed.contoso.misc.unlock.plus
41+
42+
import static de.robv.android.xposed.XC_MethodReplacement.returnConstant
43+
import io.github.chsbuffer.revancedxposed.contoso.ContosoHook
44+
45+
fun ContosoHook.UnlockPlus() {
46+
::isPlusUnlockedFingerprint.hookMethod(returnConstant(true))
47+
}
48+
```
49+
50+
#### `ContosoHook.kt`
51+
```kotlin
52+
package io.github.chsbuffer.revancedxposed.contoso
53+
54+
import android.app.Application
55+
import de.robv.android.xposed.callbacks.XC_LoadPackage
56+
import io.github.chsbuffer.revancedxposed.BaseHook
57+
import io.github.chsbuffer.revancedxposed.contoso.misc.unlock.plus.UnlockPlus
58+
59+
class ContosoHook(app: Application, lpparam: XC_LoadPackage.LoadPackageParam) : BaseHook(
60+
app, lpparam
61+
) {
62+
override val hooks = arrayOf(::UnlockPlus)
63+
}
64+
```
65+
66+
### Porting Upstream Patches
67+
68+
The [FingerprintCompat.kt](app/src/main/java/io/github/chsbuffer/revancedxposed/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:
69+
70+
1. **Literal Mapping:**
71+
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 corresponding `Fingerprints.kt` file.
72+
73+
*From (Upstream):*
74+
```kotlin
75+
// In ***Patch.kt beside the Fingerprints.kt
76+
// val aLiteral = resourceMappings["id", "aLiteral"]
77+
fingerprint { literal { aLiteral } }
78+
```
79+
80+
*To (This Project's Fingerprints.kt):*
81+
```kotlin
82+
val aLiteral get() = resourceMappings["id", "aLiteral"] // Defined in Fingerprints.kt
83+
fingerprint { literal { aLiteral } }
84+
```
85+
86+
2. **Custom Class and Method Matchers:**
87+
88+
*From (Upstream):*
89+
```kotlin
90+
fingerprint {
91+
custom { method, classDef ->
92+
method.name == "onCreate" && classDef.endsWith("/MusicActivity;")
93+
}
94+
}
95+
```
96+
97+
*To (This Project):*
98+
```kotlin
99+
fingerprint {
100+
methodMatcher { name = "onCreate" }
101+
classMatcher { className(".MusicActivity", StringMatchType.EndsWith) }
102+
}
103+
```
104+
105+
3. **Instruction-based Method Reference:**
106+
107+
*From (Upstream):*
108+
```kotlin
109+
fun indexOfTranslationInstruction(method: Method) =
110+
method.indexOfFirstInstructionReversed {
111+
getReference<MethodReference>()?.name == "setTranslationY"
112+
}
113+
114+
val motionEventFingerprint = fingerprint {
115+
custom { method, _ ->
116+
indexOfTranslationInstruction(method) >= 0
117+
}
118+
}
119+
```
120+
121+
*To (This Project):*
122+
```kotlin
123+
val motionEventFingerprint = fingerprint {
124+
methodMatcher { addInvoke { name = "setTranslationY" } }
125+
}
126+
```
127+
128+
4. **Matching Specific Class Types or Defining Classes:**
129+
When an upstream `custom` block primarily checks `classDef.type` (the type of the matched class) or `method.definingClass` (the class defining the matched method), this translates to a `classMatcher` in this project. The `classMatcher` directly specifies the target class descriptor.
130+
131+
*From (Upstream Example):*
132+
```kotlin
133+
// Upstream: Using custom to check classDef.type
134+
fingerprint {
135+
custom { _, classDef ->
136+
classDef.type == "Lcom/example/SomeClass;"
137+
}
138+
// other matchers...
139+
}
140+
141+
// Upstream: Using custom to check method.definingClass
142+
fingerprint {
143+
custom { method, _ ->
144+
method.definingClass == "Lcom/example/AnotherClass;"
145+
}
146+
// other method matchers...
147+
}
148+
```
149+
*To (This Project's Fingerprints.kt Example):*
150+
```kotlin
151+
// This Project: Using classMatcher for classDef.type
152+
fingerprint {
153+
classMatcher { descriptor = "Lcom/example/SomeClass;" }
154+
// methodMatcher { ... } // if needed for method properties
155+
}
156+
157+
// This Project: Using classMatcher for method.definingClass
158+
fingerprint {
159+
// Targets methods within Lcom/example/AnotherClass;
160+
classMatcher { descriptor = "Lcom/example/AnotherClass;" }
161+
methodMatcher {
162+
// specific method properties, e.g., name = "targetMethod"
163+
}
164+
}
165+
```
166+
5. **Porting Complex `custom` Logic or Chained Lookups with Direct Finders:**
167+
For intricate upstream fingerprints with complex `custom` logic or chained lookups not easily mapped to standard matchers, this project uses direct finder functions (e.g., `findMethodDirect`, `findClassDirect`) from `FingerprintCompat.kt`. This approach combines DexKit's efficient initial filtering with the power of Kotlin's collection processing for subsequent, more granular refinement.
168+
169+
*From (Upstream Example with complex `custom` logic):*
170+
```kotlin
171+
internal val complexCustomFingerprint = fingerprint {
172+
returns("Lcom/example/ReturnType;")
173+
custom { method, _ ->
174+
method.name.startsWith("get") &&
175+
method.parameterTypes.size == 1 &&
176+
method.parameterTypes.first() == "Lcom/example/ParameterType;" &&
177+
method.definingClass == "Lcom/example/HostClass;"
178+
// ... potentially more complex conditions
179+
}
180+
}
181+
```
182+
*To (This Project using `findMethodDirect`):*
183+
```kotlin
184+
val complexCustomFingerprint = findMethodDirect {
185+
// Initial, broader filtering with DexKit matchers
186+
findMethod {
187+
matcher {
188+
returns("Lcom/example/ReturnType;")
189+
declaredClass { descriptor = "Lcom/example/HostClass;" }
190+
// Other simple matchers convertible from the original custom block
191+
}
192+
}
193+
// Refine results using Kotlin's collection functions for complex logic
194+
.filter { methodData -> methodData.name.startsWith("get") }
195+
.single { methodData -> // Assuming a unique result after all filters
196+
methodData.paramTypes.size == 1 &&
197+
methodData.paramTypes.firstOrNull()?.descriptor == "Lcom/example/ParameterType;"
198+
// ... other Kotlin-based checks
199+
}
200+
}
201+
```
202+
This allows leveraging DexKit for initial efficient filtering, then applying precise Kotlin logic to the narrowed-down candidates.
203+
204+
### Extension Modules
205+
206+
As per ReVanced Patcher documentation:
207+
> Instead of involving many abstract changes in one patch or writing entire methods or classes in a patch, you can write code in extensions.
208+
209+
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.
210+
211+
## Unit Testing
212+
213+
Refer to [FingerprintsKtTest.kt](app/src/test/java/io/github/chsbuffer/revancedxposed/FingerprintsKtTest.kt) for testing examples.
214+
215+
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`).

app/binaries/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.apk

app/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ android {
101101
}
102102
}
103103

104+
tasks.withType<Test> {
105+
useJUnitPlatform()
106+
}
107+
104108
dependencies {
105109
// implementation(libs.dexkit)
106110
implementation(group = "", name = "dexkit-android", ext = "aar")
@@ -109,6 +113,10 @@ dependencies {
109113
implementation(libs.gson)
110114
implementation(libs.kotlinx.coroutines.android)
111115
implementation(libs.fuel)
116+
testImplementation(kotlin("test-junit5"))
117+
testImplementation(libs.junit.jupiter.params)
118+
testImplementation(libs.jadx.core)
119+
testImplementation(libs.slf4j.simple)
112120
debugImplementation(kotlin("reflect"))
113121
compileOnly(libs.xposed)
114122
compileOnly(project(":stub"))
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.github.chsbuffer.revancedxposed
2+
3+
// Skip Unit Test on unused fingerprint.
4+
// Use with caution!!!
5+
@Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.FUNCTION)
6+
annotation class SkipTest()

app/src/main/java/io/github/chsbuffer/revancedxposed/spotify/layout/hide/createbutton/Fingerprints.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package io.github.chsbuffer.revancedxposed.spotify.layout.hide.createbutton
22

33
import io.github.chsbuffer.revancedxposed.AccessFlags
44
import io.github.chsbuffer.revancedxposed.Opcode
5+
import io.github.chsbuffer.revancedxposed.SkipTest
56
import io.github.chsbuffer.revancedxposed.findClassDirect
67
import io.github.chsbuffer.revancedxposed.fingerprint
78

9+
@get:SkipTest
810
val oldNavigationBarAddItemFingerprint = fingerprint {
911
strings("Bottom navigation tabs exceeds maximum of 5 tabs")
1012
}

app/src/main/java/io/github/chsbuffer/revancedxposed/spotify/misc/Fingerprints.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.github.chsbuffer.revancedxposed.spotify.misc
22

33
import io.github.chsbuffer.revancedxposed.FindClassFunc
44
import io.github.chsbuffer.revancedxposed.Opcode
5+
import io.github.chsbuffer.revancedxposed.SkipTest
56
import io.github.chsbuffer.revancedxposed.findClassDirect
67
import io.github.chsbuffer.revancedxposed.findFieldDirect
78
import io.github.chsbuffer.revancedxposed.findMethodDirect
@@ -53,6 +54,7 @@ val oldContextMenuViewModelAddItemFingerprint = fingerprint {
5354
methodMatcher { addInvoke { name = "add" } }
5455
}
5556

57+
@SkipTest
5658
fun structureGetSectionsFingerprint(className: String) = fingerprint {
5759
classMatcher { className(className, StringMatchType.EndsWith) }
5860
methodMatcher {

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/ad/general/Fingerprints.kt

Lines changed: 0 additions & 2 deletions
This file was deleted.

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/interaction/swipecontrols/Fingerprints.kt

Lines changed: 0 additions & 1 deletion
This file was deleted.

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/layout/buttons/navigation/Fingerprints.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import io.github.chsbuffer.revancedxposed.strings
88

99
internal const val ANDROID_AUTOMOTIVE_STRING = "Android Automotive"
1010

11-
internal val addCreateButtonViewFingerprint = fingerprint {
11+
val addCreateButtonViewFingerprint = fingerprint {
1212
strings("Android Wear", ANDROID_AUTOMOTIVE_STRING)
1313
}
1414

@@ -19,7 +19,7 @@ val AutoMotiveFeatureMethod = findMethodDirect {
1919
}.single()
2020
}
2121

22-
internal val createPivotBarFingerprint = fingerprint {
22+
val createPivotBarFingerprint = fingerprint {
2323
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
2424
returns("V")
2525
parameters(

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/layout/hide/shorts/Fingerprints.kt

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)