Skip to content

Commit b824978

Browse files
authored
feat: Improve various APIs (#317)
Some APIs have been slightly changed, and API docs have been added. BREAKING CHANGE: Various APIs have been changed.
1 parent 0abf1c6 commit b824978

File tree

6 files changed

+129
-34
lines changed

6 files changed

+129
-34
lines changed

api/revanced-patcher.api

+7-11
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public final class app/revanced/patcher/patch/BytecodePatchContext : app/revance
150150
public final fun getValue (Lapp/revanced/patcher/Fingerprint;Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/Match;
151151
public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
152152
public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
153-
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/MethodNavigator;
153+
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)Lapp/revanced/patcher/util/MethodNavigator;
154154
public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
155155
}
156156

@@ -386,18 +386,13 @@ public final class app/revanced/patcher/patch/ResourcePatchBuilder : app/revance
386386
}
387387

388388
public final class app/revanced/patcher/patch/ResourcePatchContext : app/revanced/patcher/patch/PatchContext {
389+
public final fun delete (Ljava/lang/String;)Z
390+
public final fun document (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
391+
public final fun document (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
389392
public fun get ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
390393
public synthetic fun get ()Ljava/lang/Object;
391394
public final fun get (Ljava/lang/String;Z)Ljava/io/File;
392395
public static synthetic fun get$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
393-
public final fun getDocument ()Lapp/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable;
394-
public final fun stageDelete (Lkotlin/jvm/functions/Function1;)Z
395-
}
396-
397-
public final class app/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable {
398-
public fun <init> (Lapp/revanced/patcher/patch/ResourcePatchContext;)V
399-
public final fun get (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
400-
public final fun get (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
401396
}
402397

403398
public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w3c/dom/Document {
@@ -476,8 +471,9 @@ public final class app/revanced/patcher/util/MethodNavigator {
476471
public final fun at (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
477472
public final fun at ([I)Lapp/revanced/patcher/util/MethodNavigator;
478473
public static synthetic fun at$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
479-
public final fun immutable ()Lcom/android/tools/smali/dexlib2/iface/Method;
480-
public final fun mutable ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
474+
public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
475+
public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
476+
public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
481477
}
482478

483479
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {

docs/4_apis.md

+96-5
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,105 @@ A handful of APIs are available to make patch development easier and more effici
66

77
1. 👹 Create mutable replacements of classes with `proxy(ClassDef)`
88
2. 🔍 Find and create mutable replaces with `classBy(Predicate)`
9-
3. 🏃‍ Navigate method calls recursively by index with `navigate(Method).at(index)`
10-
4. 💾 Read and write resource files with `get(Path, Boolean)`
11-
5. 📃 Read and write DOM files using `document`
9+
3. 🏃‍ Navigate method calls recursively by index with `navigate(Method)`
10+
4. 💾 Read and write resource files with `get(String, Boolean)` and `delete(String)`
11+
5. 📃 Read and write DOM files using `document(String)` and `document(InputStream)`
1212

1313
### 🧰 APIs
1414

15-
> [!WARNING]
16-
> This section is still under construction and may be incomplete.
15+
#### 👹 `proxy(ClassDef)`
16+
17+
By default, the classes are immutable, meaning they cannot be modified.
18+
To make a class mutable, use the `proxy(ClassDef)` function.
19+
This function creates a lazy mutable copy of the class definition.
20+
Accessing the property will replace the original class definition with the mutable copy,
21+
thus allowing you to make changes to the class. Subsequent accesses will return the same mutable copy.
22+
23+
```kt
24+
execute {
25+
val mutableClass = proxy(classDef)
26+
mutableClass.methods.add(Method())
27+
}
28+
```
29+
30+
#### 🔍 `classBy(Predicate)`
31+
32+
The `classBy(Predicate)` function is an alternative to finding and creating mutable classes by a predicate.
33+
It automatically proxies the class definition, making it mutable.
34+
35+
```kt
36+
execute {
37+
// Alternative to proxy(classes.find { it.name == "Lcom/example/MyClass;" })?.classDef
38+
val classDef = classBy { it.name == "Lcom/example/MyClass;" }?.classDef
39+
}
40+
```
41+
42+
#### 🏃‍ `navigate(Method).at(index)`
43+
44+
The `navigate(Method)` function allows you to navigate method calls recursively by index.
45+
46+
```kt
47+
execute {
48+
// Sequentially navigate to the instructions at index 1 within 'someMethod'.
49+
val method = navigate(someMethod).at(1).original() // original() returns the original immutable method.
50+
51+
// Further navigate to the second occurrence where the instruction's opcode is 'INVOKEVIRTUAL'.
52+
// stop() returns the mutable copy of the method.
53+
val method = navigate(someMethod).at(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
54+
55+
// Alternatively, to stop(), you can delegate the method to a variable.
56+
val method by navigate(someMethod).at(1)
57+
58+
// You can chain multiple calls to at() to navigate deeper into the method.
59+
val method by navigate(someMethod).at(1).at(2, 3, 4).at(5)
60+
}
61+
```
62+
63+
#### 💾 `get(String, Boolean)` and `delete(String)`
64+
65+
The `get(String, Boolean)` function returns a `File` object that can be used to read and write resource files.
66+
67+
```kt
68+
execute {
69+
val file = get("res/values/strings.xml")
70+
val content = file.readText()
71+
file.writeText(content)
72+
}
73+
```
74+
75+
The `delete` function can mark files for deletion when the APK is rebuilt.
76+
77+
```kt
78+
execute {
79+
delete("res/values/strings.xml")
80+
}
81+
```
82+
83+
#### 📃 `document(String)` and `document(InputStream)`
84+
85+
The `document` function is used to read and write DOM files.
86+
87+
```kt
88+
execute {
89+
document("res/values/strings.xml").use { document ->
90+
val element = doc.createElement("string").apply {
91+
textContent = "Hello, World!"
92+
}
93+
document.documentElement.appendChild(element)
94+
}
95+
}
96+
```
97+
98+
You can also read documents from an `InputStream`:
99+
100+
```kt
101+
execute {
102+
val inputStream = classLoader.getResourceAsStream("some.xml")
103+
document(inputStream).use { document ->
104+
// ...
105+
}
106+
}
107+
```
17108

18109
## 🎉 Afterword
19110

src/main/kotlin/app/revanced/patcher/PatcherResult.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ class PatcherResult internal constructor(
2929
* @param resourcesApk The compiled resources.apk file.
3030
* @param otherResources The directory containing other resources files.
3131
* @param doNotCompress List of files that should not be compressed.
32-
* @param deleteResources List of predicates about resources that should be deleted.
32+
* @param deleteResources List of resources that should be deleted.
3333
*/
3434
class PatchedResources internal constructor(
3535
val resourcesApk: File?,
3636
val otherResources: File?,
3737
val doNotCompress: Set<String>,
38-
val deleteResources: Set<(String) -> Boolean>,
38+
val deleteResources: Set<String>,
3939
)
4040
}

src/main/kotlin/app/revanced/patcher/patch/BytecodePatchContext.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
1212
import com.android.tools.smali.dexlib2.iface.DexFile
1313
import com.android.tools.smali.dexlib2.iface.Method
1414
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
15+
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
1516
import com.android.tools.smali.dexlib2.iface.reference.StringReference
1617
import lanchon.multidexlib2.BasicDexFileNamer
1718
import lanchon.multidexlib2.DexIO
@@ -147,7 +148,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
147148
*
148149
* @return A [MethodNavigator] for the method.
149150
*/
150-
fun navigate(method: Method) = MethodNavigator(this@BytecodePatchContext, method)
151+
fun navigate(method: MethodReference) = MethodNavigator(this@BytecodePatchContext, method)
151152

152153
/**
153154
* Compile bytecode from the [BytecodePatchContext].

src/main/kotlin/app/revanced/patcher/patch/ResourcePatchContext.kt

+11-12
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,20 @@ class ResourcePatchContext internal constructor(
3131
) : PatchContext<PatcherResult.PatchedResources?> {
3232
private val logger = Logger.getLogger(ResourcePatchContext::class.java.name)
3333

34+
/**
35+
* Read a document from an [InputStream].
36+
*/
37+
fun document(inputStream: InputStream) = Document(inputStream)
38+
3439
/**
3540
* Read and write documents in the [PatcherConfig.apkFiles].
3641
*/
37-
val document = DocumentOperatable()
42+
fun document(path: String) = Document(get(path))
3843

3944
/**
40-
* Predicate to delete resources from [PatcherConfig.apkFiles].
45+
* Set of resources from [PatcherConfig.apkFiles] to delete.
4146
*/
42-
private val deleteResources = mutableSetOf<(String) -> Boolean>()
47+
private val deleteResources = mutableSetOf<String>()
4348

4449
/**
4550
* Decode resources of [PatcherConfig.apkFile].
@@ -201,11 +206,11 @@ class ResourcePatchContext internal constructor(
201206
}
202207

203208
/**
204-
* Stage a file to be deleted from [PatcherConfig.apkFile].
209+
* Mark a file for deletion when the APK is rebuilt.
205210
*
206-
* @param shouldDelete The predicate to stage the file for deletion given its name.
211+
* @param name The name of the file to delete.
207212
*/
208-
fun stageDelete(shouldDelete: (String) -> Boolean) = deleteResources.add(shouldDelete)
213+
fun delete(name: String) = deleteResources.add(name)
209214

210215
/**
211216
* How to handle resources decoding and compiling.
@@ -227,10 +232,4 @@ class ResourcePatchContext internal constructor(
227232
*/
228233
NONE,
229234
}
230-
231-
inner class DocumentOperatable {
232-
operator fun get(inputStream: InputStream) = Document(inputStream)
233-
234-
operator fun get(path: String) = Document(this@ResourcePatchContext[path])
235-
}
236235
}

src/main/kotlin/app/revanced/patcher/util/MethodNavigator.kt

+11-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.Instruction
1212
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
1313
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
1414
import com.android.tools.smali.dexlib2.util.MethodUtil
15+
import kotlin.reflect.KProperty
1516

1617
/**
1718
* A navigator for methods.
@@ -27,7 +28,7 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
2728
class MethodNavigator internal constructor(private val context: BytecodePatchContext, private var startMethod: MethodReference) {
2829
private var lastNavigatedMethodReference = startMethod
2930

30-
private val lastNavigatedMethodInstructions get() = with(immutable()) {
31+
private val lastNavigatedMethodInstructions get() = with(original()) {
3132
instructionsOrNull ?: throw NavigateException("Method $definingClass.$name does not have an implementation.")
3233
}
3334

@@ -76,15 +77,22 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
7677
*
7778
* @return The last navigated method mutably.
7879
*/
79-
fun mutable() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
80+
fun stop() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
8081
as MutableMethod
8182

83+
/**
84+
* Get the last navigated method mutably.
85+
*
86+
* @return The last navigated method mutably.
87+
*/
88+
operator fun getValue(nothing: Nothing?, property: KProperty<*>) = stop()
89+
8290
/**
8391
* Get the last navigated method immutably.
8492
*
8593
* @return The last navigated method immutably.
8694
*/
87-
fun immutable() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
95+
fun original() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
8896

8997
/**
9098
* Predicate to match the class defining the current method reference.

0 commit comments

Comments
 (0)