1
mirror of https://github.com/revanced/revanced-patcher synced 2025-09-13 18:30:49 +02:00

Compare commits

..

9 Commits

Author SHA1 Message Date
oSumAtrIX
e41504bd99 Merge branch 'dev' into feat/improve-fingerprint-api
# Conflicts:
#	src/main/kotlin/app/revanced/patcher/patch/BytecodePatchContext.kt
#	src/main/kotlin/app/revanced/patcher/patch/Patch.kt
#	src/test/kotlin/app/revanced/patcher/PatcherTest.kt
2024-10-27 16:03:43 +01:00
oSumAtrIX
35b85b2c29 Build log strings in suppliers 2024-10-27 15:59:52 +01:00
oSumAtrIX
f62e952514 add common usecase example 2024-10-27 15:48:26 +01:00
oSumAtrIX
635d23c9b7 add notice regarding using immutable references 2024-10-27 15:44:49 +01:00
oSumAtrIX
5a0e3841ff more nesting fixes 2024-10-27 04:40:06 +01:00
oSumAtrIX
cfb459b832 fix nesting 2024-10-27 04:37:40 +01:00
oSumAtrIX
2096306107 fix nesting 2024-10-27 04:37:20 +01:00
oSumAtrIX
92eaba8081 feat: Improve Fingerprint API
Fingerprints can now be matched easily without having to add them to a patch first.

BREAKING CHANGE: Many APIs have been changed.
2024-10-27 04:31:43 +01:00
oSumAtrIX
7be0cd8548 fix: Merge extension only when patch executes 2024-10-26 23:23:36 +02:00
8 changed files with 36 additions and 157 deletions

View File

@@ -1,29 +1,3 @@
# [21.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.1...v21.0.0-dev.2) (2024-11-01)
### Bug Fixes
* Match fingerprint before delegating the match property ([5d996de](https://github.com/ReVanced/revanced-patcher/commit/5d996def4d3de4e2bfc34562e5a6c7d89a8cddf0))
# [21.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0-dev.1) (2024-10-27)
### Bug Fixes
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](https://github.com/ReVanced/revanced-patcher/commit/aa472eb9857145b53b49f843406a9764fbb7e5ce))
### Features
* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](https://github.com/ReVanced/revanced-patcher/commit/0abf1c6c0279708fdef5cb66b141d07d17682693))
* Improve various APIs ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](https://github.com/ReVanced/revanced-patcher/commit/b8249789df8b90129f7b7ad0e523a8d0ceaab848))
### BREAKING CHANGES
* Various APIs have been changed.
* Many APIs have been changed.
## [20.0.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2) (2024-10-17)

View File

@@ -150,7 +150,7 @@ public final class app/revanced/patcher/patch/BytecodePatchContext : app/revance
public final fun getValue (Lapp/revanced/patcher/Fingerprint;Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/Match;
public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
public final fun match (Lapp/revanced/patcher/Fingerprint;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)Lapp/revanced/patcher/util/MethodNavigator;
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/MethodNavigator;
public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
}
@@ -386,13 +386,18 @@ public final class app/revanced/patcher/patch/ResourcePatchBuilder : app/revance
}
public final class app/revanced/patcher/patch/ResourcePatchContext : app/revanced/patcher/patch/PatchContext {
public final fun delete (Ljava/lang/String;)Z
public final fun document (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
public final fun document (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
public fun get ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
public synthetic fun get ()Ljava/lang/Object;
public final fun get (Ljava/lang/String;Z)Ljava/io/File;
public static synthetic fun get$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
public final fun getDocument ()Lapp/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable;
public final fun stageDelete (Lkotlin/jvm/functions/Function1;)Z
}
public final class app/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable {
public fun <init> (Lapp/revanced/patcher/patch/ResourcePatchContext;)V
public final fun get (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
public final fun get (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
}
public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w3c/dom/Document {
@@ -471,9 +476,8 @@ public final class app/revanced/patcher/util/MethodNavigator {
public final fun at (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
public final fun at ([I)Lapp/revanced/patcher/util/MethodNavigator;
public static synthetic fun at$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun immutable ()Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun mutable ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
}
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {

View File

@@ -6,105 +6,14 @@ A handful of APIs are available to make patch development easier and more effici
1. 👹 Create mutable replacements of classes with `proxy(ClassDef)`
2. 🔍 Find and create mutable replaces with `classBy(Predicate)`
3. 🏃‍ Navigate method calls recursively by index with `navigate(Method)`
4. 💾 Read and write resource files with `get(String, Boolean)` and `delete(String)`
5. 📃 Read and write DOM files using `document(String)` and `document(InputStream)`
3. 🏃‍ Navigate method calls recursively by index with `navigate(Method).at(index)`
4. 💾 Read and write resource files with `get(Path, Boolean)`
5. 📃 Read and write DOM files using `document`
### 🧰 APIs
#### 👹 `proxy(ClassDef)`
By default, the classes are immutable, meaning they cannot be modified.
To make a class mutable, use the `proxy(ClassDef)` function.
This function creates a lazy mutable copy of the class definition.
Accessing the property will replace the original class definition with the mutable copy,
thus allowing you to make changes to the class. Subsequent accesses will return the same mutable copy.
```kt
execute {
val mutableClass = proxy(classDef)
mutableClass.methods.add(Method())
}
```
#### 🔍 `classBy(Predicate)`
The `classBy(Predicate)` function is an alternative to finding and creating mutable classes by a predicate.
It automatically proxies the class definition, making it mutable.
```kt
execute {
// Alternative to proxy(classes.find { it.name == "Lcom/example/MyClass;" })?.classDef
val classDef = classBy { it.name == "Lcom/example/MyClass;" }?.classDef
}
```
#### 🏃‍ `navigate(Method).at(index)`
The `navigate(Method)` function allows you to navigate method calls recursively by index.
```kt
execute {
// Sequentially navigate to the instructions at index 1 within 'someMethod'.
val method = navigate(someMethod).at(1).original() // original() returns the original immutable method.
// Further navigate to the second occurrence where the instruction's opcode is 'INVOKEVIRTUAL'.
// stop() returns the mutable copy of the method.
val method = navigate(someMethod).at(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
// Alternatively, to stop(), you can delegate the method to a variable.
val method by navigate(someMethod).at(1)
// You can chain multiple calls to at() to navigate deeper into the method.
val method by navigate(someMethod).at(1).at(2, 3, 4).at(5)
}
```
#### 💾 `get(String, Boolean)` and `delete(String)`
The `get(String, Boolean)` function returns a `File` object that can be used to read and write resource files.
```kt
execute {
val file = get("res/values/strings.xml")
val content = file.readText()
file.writeText(content)
}
```
The `delete` function can mark files for deletion when the APK is rebuilt.
```kt
execute {
delete("res/values/strings.xml")
}
```
#### 📃 `document(String)` and `document(InputStream)`
The `document` function is used to read and write DOM files.
```kt
execute {
document("res/values/strings.xml").use { document ->
val element = doc.createElement("string").apply {
textContent = "Hello, World!"
}
document.documentElement.appendChild(element)
}
}
```
You can also read documents from an `InputStream`:
```kt
execute {
val inputStream = classLoader.getResourceAsStream("some.xml")
document(inputStream).use { document ->
// ...
}
}
```
> [!WARNING]
> This section is still under construction and may be incomplete.
## 🎉 Afterword

View File

@@ -1,3 +1,3 @@
org.gradle.parallel = true
org.gradle.caching = true
version = 21.0.0-dev.2
version = 20.0.2

View File

@@ -29,12 +29,12 @@ class PatcherResult internal constructor(
* @param resourcesApk The compiled resources.apk file.
* @param otherResources The directory containing other resources files.
* @param doNotCompress List of files that should not be compressed.
* @param deleteResources List of resources that should be deleted.
* @param deleteResources List of predicates about resources that should be deleted.
*/
class PatchedResources internal constructor(
val resourcesApk: File?,
val otherResources: File?,
val doNotCompress: Set<String>,
val deleteResources: Set<String>,
val deleteResources: Set<(String) -> Boolean>,
)
}

View File

@@ -12,7 +12,6 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.DexFile
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.StringReference
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.DexIO
@@ -80,7 +79,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
*
* @throws IllegalStateException If the [Fingerprint] has not been matched.
*/
operator fun Fingerprint.getValue(nothing: Nothing?, property: KProperty<*>): Match = match
operator fun Fingerprint.getValue(nothing: Nothing?, property: KProperty<*>): Match = _match
?: throw PatchException("No fingerprint match to delegate to \"${property.name}\".")
/**
@@ -148,7 +147,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
*
* @return A [MethodNavigator] for the method.
*/
fun navigate(method: MethodReference) = MethodNavigator(this@BytecodePatchContext, method)
fun navigate(method: Method) = MethodNavigator(this@BytecodePatchContext, method)
/**
* Compile bytecode from the [BytecodePatchContext].

View File

@@ -31,20 +31,15 @@ class ResourcePatchContext internal constructor(
) : PatchContext<PatcherResult.PatchedResources?> {
private val logger = Logger.getLogger(ResourcePatchContext::class.java.name)
/**
* Read a document from an [InputStream].
*/
fun document(inputStream: InputStream) = Document(inputStream)
/**
* Read and write documents in the [PatcherConfig.apkFiles].
*/
fun document(path: String) = Document(get(path))
val document = DocumentOperatable()
/**
* Set of resources from [PatcherConfig.apkFiles] to delete.
* Predicate to delete resources from [PatcherConfig.apkFiles].
*/
private val deleteResources = mutableSetOf<String>()
private val deleteResources = mutableSetOf<(String) -> Boolean>()
/**
* Decode resources of [PatcherConfig.apkFile].
@@ -206,11 +201,11 @@ class ResourcePatchContext internal constructor(
}
/**
* Mark a file for deletion when the APK is rebuilt.
* Stage a file to be deleted from [PatcherConfig.apkFile].
*
* @param name The name of the file to delete.
* @param shouldDelete The predicate to stage the file for deletion given its name.
*/
fun delete(name: String) = deleteResources.add(name)
fun stageDelete(shouldDelete: (String) -> Boolean) = deleteResources.add(shouldDelete)
/**
* How to handle resources decoding and compiling.
@@ -232,4 +227,10 @@ class ResourcePatchContext internal constructor(
*/
NONE,
}
inner class DocumentOperatable {
operator fun get(inputStream: InputStream) = Document(inputStream)
operator fun get(path: String) = Document(this@ResourcePatchContext[path])
}
}

View File

@@ -12,7 +12,6 @@ import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil
import kotlin.reflect.KProperty
/**
* A navigator for methods.
@@ -28,7 +27,7 @@ import kotlin.reflect.KProperty
class MethodNavigator internal constructor(private val context: BytecodePatchContext, private var startMethod: MethodReference) {
private var lastNavigatedMethodReference = startMethod
private val lastNavigatedMethodInstructions get() = with(original()) {
private val lastNavigatedMethodInstructions get() = with(immutable()) {
instructionsOrNull ?: throw NavigateException("Method $definingClass.$name does not have an implementation.")
}
@@ -77,22 +76,15 @@ class MethodNavigator internal constructor(private val context: BytecodePatchCon
*
* @return The last navigated method mutably.
*/
fun stop() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
fun mutable() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
as MutableMethod
/**
* Get the last navigated method mutably.
*
* @return The last navigated method mutably.
*/
operator fun getValue(nothing: Nothing?, property: KProperty<*>) = stop()
/**
* Get the last navigated method immutably.
*
* @return The last navigated method immutably.
*/
fun original() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
fun immutable() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
/**
* Predicate to match the class defining the current method reference.