You've already forked revanced-patcher
mirror of
https://github.com/revanced/revanced-patcher
synced 2025-09-06 16:38:50 +02:00
Compare commits
159 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6c5f9d4198 | ||
![]() |
7b2d058144 | ||
![]() |
db2804270e | ||
![]() |
2572cd04b5 | ||
![]() |
5eb8b428b9 | ||
![]() |
3a118d9b9d | ||
![]() |
14a73bfcaf | ||
![]() |
567bf52e16 | ||
![]() |
35c6489dba | ||
![]() |
371f0c4d0b | ||
![]() |
1b42f65d95 | ||
![]() |
2aee0cbd0f | ||
![]() |
19256b5437 | ||
![]() |
67a5237541 | ||
![]() |
4e2e772389 | ||
![]() |
799bc9e163 | ||
![]() |
2431785d0e | ||
![]() |
fb3c0e87d4 | ||
![]() |
5f7ef2dbff | ||
![]() |
ec1d8a8fba | ||
![]() |
814ce0b9ae | ||
![]() |
8d95b14f35 | ||
![]() |
711b8a25a7 | ||
![]() |
fbb09f38dc | ||
![]() |
9a4d30e152 | ||
![]() |
368c61c1bf | ||
![]() |
bbd40bf2f6 | ||
![]() |
3de999a2d3 | ||
![]() |
83cbb2f110 | ||
![]() |
fcc7fa75d0 | ||
![]() |
495ebface8 | ||
![]() |
8c12f8d488 | ||
![]() |
523f67b238 | ||
![]() |
4813a8b48e | ||
![]() |
d5c66022c9 | ||
![]() |
1d4034b36c | ||
![]() |
dd2d696d00 | ||
![]() |
6326321b65 | ||
![]() |
f291a4ae3e | ||
![]() |
817b8db019 | ||
![]() |
a321b8971b | ||
![]() |
783b2de9db | ||
![]() |
77604d4078 | ||
![]() |
21b5404180 | ||
![]() |
9ac6d5c7da | ||
![]() |
1b39278b24 | ||
![]() |
0ebab8bf59 | ||
![]() |
112bc998f4 | ||
![]() |
12c96bf818 | ||
![]() |
91298a8790 | ||
![]() |
f2a7cff41c | ||
![]() |
dd941233ca | ||
![]() |
fc06dd1c29 | ||
![]() |
482af78f2b | ||
![]() |
89a27dfbe6 | ||
![]() |
4ea030d0a0 | ||
![]() |
4cc2fa17f5 | ||
![]() |
48068cb3d7 | ||
![]() |
d107c7245c | ||
![]() |
4b2e3230ec | ||
![]() |
fb5b82da4e | ||
![]() |
5970e32aa5 | ||
![]() |
0f00d33f4e | ||
![]() |
83187c9edd | ||
![]() |
79d70cff4b | ||
![]() |
6f72c4c4c0 | ||
![]() |
c8eedac4d9 | ||
![]() |
60a8278ae8 | ||
![]() |
ada5a033de | ||
![]() |
109b8a296d | ||
![]() |
e2faf4ca9b | ||
![]() |
2134182a0e | ||
![]() |
d8b5b8bb7c | ||
![]() |
49970b5926 | ||
![]() |
c1fbd8cf8c | ||
![]() |
a467fbb704 | ||
![]() |
5a4bd7a76e | ||
![]() |
e5ca86fac6 | ||
![]() |
68d9e9f02c | ||
![]() |
494a9a09ac | ||
![]() |
06a88839de | ||
![]() |
4c41b955df | ||
![]() |
614e555f4c | ||
![]() |
6bfe5716c3 | ||
![]() |
d6ed06a327 | ||
![]() |
2fc4ec4021 | ||
![]() |
f565c4f6a7 | ||
![]() |
35749454ab | ||
![]() |
2b492e7a5e | ||
![]() |
081a5a6849 | ||
![]() |
852ae7d8d1 | ||
![]() |
52f8a6a2eb | ||
![]() |
efa8ea1445 | ||
![]() |
c828fa2a27 | ||
![]() |
92ac5e4dc2 | ||
![]() |
070c09cf71 | ||
![]() |
b738dcd7ea | ||
![]() |
8efcf329bb | ||
![]() |
0e87ef56c4 | ||
![]() |
e3bf367ad6 | ||
![]() |
3d61dacbda | ||
![]() |
c20dfe12d5 | ||
![]() |
78663cde88 | ||
![]() |
dde5385232 | ||
![]() |
930768dfb3 | ||
![]() |
1f4bc5079f | ||
![]() |
caf2745805 | ||
![]() |
a4529c3fee | ||
![]() |
835c0f9f7a | ||
![]() |
34f607aa24 | ||
![]() |
0f38b94701 | ||
![]() |
39bb1b25dc | ||
![]() |
4fc63a4d8a | ||
![]() |
6037397bc2 | ||
![]() |
273dd86b65 | ||
![]() |
e8488b3e86 | ||
![]() |
c13361823d | ||
![]() |
8f66f9f606 | ||
![]() |
a123026f46 | ||
![]() |
9c39c9efdb | ||
![]() |
3ee1c01430 | ||
![]() |
64bae884dc | ||
![]() |
e94a706949 | ||
![]() |
89bb43066b | ||
![]() |
68174bbd6b | ||
![]() |
d05c9416d6 | ||
![]() |
5a2f02b97d | ||
![]() |
a3005fa08e | ||
![]() |
6628b7870f | ||
![]() |
a6411245aa | ||
![]() |
365e1d7a45 | ||
![]() |
4507cd2353 | ||
![]() |
1f75777cf9 | ||
![]() |
28d5468b07 | ||
![]() |
746496125d | ||
![]() |
835a421cc0 | ||
![]() |
99342fe033 | ||
![]() |
e47b67d7ec | ||
![]() |
ad6c5c8273 | ||
![]() |
fd690acd61 | ||
![]() |
e698b02bf6 | ||
![]() |
ab866bb8ef | ||
![]() |
714a98422d | ||
![]() |
051afd98d0 | ||
![]() |
d38cf6a229 | ||
![]() |
03f5ee088b | ||
![]() |
5d0fd48b15 | ||
![]() |
56f6ca3891 | ||
![]() |
9e0a74fcfb | ||
![]() |
8f3ac7702a | ||
![]() |
7b65f2d02c | ||
![]() |
2a1b2df56b | ||
![]() |
dfd8a24512 | ||
![]() |
a3efd212fc | ||
![]() |
3e610f7ba9 | ||
![]() |
cff87ff077 | ||
![]() |
54aa04ca34 | ||
![]() |
0eda84eaef | ||
![]() |
b68b0bf3d7 |
60
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: 🐞 Bug report
|
||||
description: Report a very clearly broken issue.
|
||||
title: 'bug: <title>'
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# ReVanced bug report
|
||||
|
||||
Important to note that your issue may have already been reported before. Please check for existing issues [here](https://github.com/revanced/revanced-patcher/labels/bug).
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Type
|
||||
options:
|
||||
- Crash
|
||||
- Cosmetic
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: How did you find the bug? Any additional details that might help?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Add the steps to reproduce this bug including your environment.
|
||||
placeholder: Step 1. Download some files. Step 2. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Screenshots or videos
|
||||
description: Add screenshots or videos that show the bug here.
|
||||
placeholder: Drag and drop the screenshots/videos into this box.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Solution
|
||||
description: If applicable, add a possible solution.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add additional context here.
|
||||
validations:
|
||||
required: false
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 📃 Documentation
|
||||
url: https://github.com/revanced/revanced-documentation/
|
||||
about: Don't know how or where to start? Check out our documentation!
|
||||
- name: 🗨 Discussions
|
||||
url: https://github.com/revanced/revanced-suggestions/discussions
|
||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
46
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
Normal file
46
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: ⭐ Feature request
|
||||
description: Create a detailed feature request.
|
||||
title: 'feat: <title>'
|
||||
labels: [feature-request]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# ReVanced feature request
|
||||
|
||||
Do not submit requests for patches here. Please submit them [here](https://github.com/orgs/revanced/discussions/categories/patches) instead.
|
||||
Important to note that your feature request may have already been made before. Please check for existing feature requests [here](https://github.com/revanced/revanced-patcher/labels/feature-request).
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Type
|
||||
options:
|
||||
- Functionality
|
||||
- Cosmetic
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Issue
|
||||
description: What is the current problem. Why does it require a feature request?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature
|
||||
description: Describe your feature in detail. How does it solve the issue?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: Why should your feature should be considered?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add additional context here.
|
||||
validations:
|
||||
required: false
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -28,8 +28,6 @@ jobs:
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
- name: Make gradlew executable
|
||||
run: chmod +x gradlew
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@@ -7,7 +7,11 @@
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
["@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{"type": "build", "release": "patch"}
|
||||
]
|
||||
}],
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"gradle-semantic-release-plugin",
|
||||
|
405
CHANGELOG.md
405
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -21,15 +21,13 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
|
||||
api("xpp3:xpp3:1.1.4c")
|
||||
api("org.apktool:apktool-lib:2.6.2-SNAPSHOT")
|
||||
api("app.revanced:multidexlib2:2.5.2.r2")
|
||||
api("org.smali:smali:2.5.2")
|
||||
implementation("xpp3:xpp3:1.1.4c")
|
||||
implementation("org.smali:smali:2.5.2")
|
||||
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
||||
implementation("org.apktool:apktool-lib:2.7.0-SNAPSHOT")
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
implementation(kotlin("reflect"))}
|
||||
}
|
||||
|
||||
tasks {
|
||||
test {
|
||||
@@ -42,11 +40,11 @@ tasks {
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
if (System.getenv("GITHUB_ACTOR") != null)
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||
@@ -55,10 +53,12 @@ publishing {
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
else
|
||||
mavenLocal()
|
||||
}
|
||||
publications {
|
||||
register<MavenPublication>("gpr") {
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,2 +1,2 @@
|
||||
kotlin.code.style = official
|
||||
version = 1.2.1
|
||||
version = 3.3.1
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,10 @@
|
||||
package app.revanced.patcher.data
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.data.base.Data
|
||||
import app.revanced.patcher.data.implementation.BytecodeData
|
||||
import app.revanced.patcher.data.implementation.ResourceData
|
||||
import app.revanced.patcher.patch.base.Patch
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.data.PackageMetadata
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import java.io.File
|
||||
|
||||
@@ -12,9 +13,7 @@ data class PatcherData(
|
||||
internal val resourceCacheDirectory: String,
|
||||
val packageMetadata: PackageMetadata
|
||||
) {
|
||||
|
||||
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
||||
|
||||
internal val bytecodeData = BytecodeData(internalClasses)
|
||||
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
||||
}
|
@@ -1,19 +1,23 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.logging.Logger
|
||||
import app.revanced.patcher.logging.impl.NopLogger
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Options for a patcher.
|
||||
* Options for the [Patcher].
|
||||
* @param inputFile The input file (usually an apk file).
|
||||
* @param resourceCacheDirectory Directory to cache resources.
|
||||
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
|
||||
* @param aaptPath Optional path to a custom aapt binary.
|
||||
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
||||
* @param logger Custom logger implementation for the [Patcher].
|
||||
*/
|
||||
data class PatcherOptions(
|
||||
internal val inputFile: File,
|
||||
internal val resourceCacheDirectory: String,
|
||||
internal val patchResources: Boolean = false,
|
||||
internal val aaptPath: String = "",
|
||||
internal val frameworkFolderLocation: String? = null
|
||||
internal val frameworkFolderLocation: String? = null,
|
||||
internal val logger: Logger = NopLogger
|
||||
)
|
||||
|
@@ -1,13 +1,16 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.util.dex.DexFile
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The result of a patcher.
|
||||
* @param dexFiles The patched dex files.
|
||||
* @param doNotCompress List of relative paths to files to exclude from compressing.
|
||||
* @param resourceFile File containing resources that need to be extracted into the APK.
|
||||
*/
|
||||
data class PatcherResult(
|
||||
val dexFiles: List<DexFile>,
|
||||
val doNotCompress: List<String>? = null
|
||||
val doNotCompress: List<String>? = null,
|
||||
val resourceFile: File?
|
||||
)
|
@@ -1,11 +1,11 @@
|
||||
package app.revanced.patcher.annotation
|
||||
|
||||
import app.revanced.patcher.patch.base.Patch
|
||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Annotation to constrain a [Patch] or [MethodSignature] to compatible packages.
|
||||
* @param compatiblePackages A list of packages a [Patch] or [MethodSignature] is compatible with.
|
||||
* Annotation to constrain a [Patch] or [MethodFingerprint] to compatible packages.
|
||||
* @param compatiblePackages A list of packages a [Patch] or [MethodFingerprint] is compatible with.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -17,7 +17,7 @@ annotation class Compatibility(
|
||||
/**
|
||||
* Annotation to represent packages a patch can be compatible with.
|
||||
* @param name The package identifier name.
|
||||
* @param versions The versions of the package the [Patch] or [MethodSignature]is compatible with.
|
||||
* @param versions The versions of the package the [Patch] or [MethodFingerprint]is compatible with.
|
||||
*/
|
||||
@Target()
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package app.revanced.patcher.annotation
|
||||
|
||||
import app.revanced.patcher.patch.base.Patch
|
||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Annotation to name a [Patch] or [MethodSignature].
|
||||
* @param name A suggestive name for the [Patch] or [MethodSignature].
|
||||
* Annotation to name a [Patch] or [MethodFingerprint].
|
||||
* @param name A suggestive name for the [Patch] or [MethodFingerprint].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -15,8 +15,8 @@ annotation class Name(
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotation to describe a [Patch] or [MethodSignature].
|
||||
* @param description A description for the [Patch] or [MethodSignature].
|
||||
* Annotation to describe a [Patch] or [MethodFingerprint].
|
||||
* @param description A description for the [Patch] or [MethodFingerprint].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -27,8 +27,8 @@ annotation class Description(
|
||||
|
||||
|
||||
/**
|
||||
* Annotation to version a [Patch] or [MethodSignature].
|
||||
* @param version The version of a [Patch] or [MethodSignature].
|
||||
* Annotation to version a [Patch] or [MethodFingerprint].
|
||||
* @param version The version of a [Patch] or [MethodFingerprint].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
|
9
src/main/kotlin/app/revanced/patcher/data/Data.kt
Normal file
9
src/main/kotlin/app/revanced/patcher/data/Data.kt
Normal file
@@ -0,0 +1,9 @@
|
||||
package app.revanced.patcher.data
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
|
||||
/**
|
||||
* Constraint interface for [BytecodeData] and [ResourceData]
|
||||
*/
|
||||
interface Data
|
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patcher.data.base
|
||||
|
||||
import app.revanced.patcher.data.implementation.BytecodeData
|
||||
import app.revanced.patcher.data.implementation.ResourceData
|
||||
|
||||
/**
|
||||
* Constraint interface for [BytecodeData] and [ResourceData]
|
||||
*/
|
||||
interface Data
|
@@ -1,6 +1,6 @@
|
||||
package app.revanced.patcher.data.implementation
|
||||
package app.revanced.patcher.data.impl
|
||||
|
||||
import app.revanced.patcher.data.base.Data
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.util.ProxyBackedClassList
|
||||
import app.revanced.patcher.util.method.MethodWalker
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
@@ -28,6 +28,15 @@ class BytecodeData(
|
||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||
classes.find(predicate)?.let { proxy(it) }
|
||||
|
||||
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||
if (proxy == null) {
|
||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||
this.classes.add(proxy)
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
internal class MethodNotFoundException(s: String) : Exception(s)
|
||||
@@ -57,13 +66,4 @@ internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun BytecodeData.proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||
if (proxy == null) {
|
||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||
this.classes.add(proxy)
|
||||
}
|
||||
return proxy
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
package app.revanced.patcher.data.impl
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import org.w3c.dom.Document
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
|
||||
class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<File> {
|
||||
val xmlEditor = XmlFileHolder()
|
||||
|
||||
operator fun get(path: String) = resourceCacheDirectory.resolve(path)
|
||||
|
||||
override fun iterator() = resourceCacheDirectory.walkTopDown().iterator()
|
||||
|
||||
inner class XmlFileHolder {
|
||||
operator fun get(inputStream: InputStream) =
|
||||
DomFileEditor(inputStream)
|
||||
|
||||
operator fun get(path: String): DomFileEditor {
|
||||
return DomFileEditor(this@ResourceData[path])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DomFileEditor is a wrapper for a file that can be edited as a dom document.
|
||||
*
|
||||
* @param inputStream the input stream to read the xml file from.
|
||||
* @param outputStream the output stream to write the xml file to. If null, the file will not be written.
|
||||
*/
|
||||
class DomFileEditor internal constructor(
|
||||
private val inputStream: InputStream,
|
||||
private val outputStream: Lazy<OutputStream>? = null,
|
||||
) : Closeable {
|
||||
// path to the xml file to unlock the resource when closing the editor
|
||||
private var filePath: String? = null
|
||||
|
||||
/**
|
||||
* The document of the xml file
|
||||
*/
|
||||
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
|
||||
.also(Document::normalize)
|
||||
|
||||
// lazily open an output stream
|
||||
// this is required because when constructing a DomFileEditor the output stream is created along with the input stream, which is not allowed
|
||||
// the workaround is to lazily create the output stream. This way it would be used after the input stream is closed, which happens in the constructor
|
||||
constructor(file: File) : this(file.inputStream(), lazy { file.outputStream() }) {
|
||||
filePath = file.path
|
||||
|
||||
// prevent sharing mutability of the same file between multiple instances of DomFileEditor
|
||||
if (locks.contains(filePath))
|
||||
throw IllegalStateException("Can not create a DomFileEditor for that file because it is already locked by another instance of DomFileEditor.")
|
||||
locks.add(filePath!!)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
inputStream.close()
|
||||
|
||||
// if the output stream is not null, do not close it
|
||||
outputStream?.let {
|
||||
val result = StreamResult(it.value)
|
||||
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)
|
||||
|
||||
it.value.close()
|
||||
}
|
||||
|
||||
// remove the lock, if it exists
|
||||
filePath?.let {
|
||||
locks.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
// list of locked file paths
|
||||
val locks = mutableListOf<String>()
|
||||
}
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
package app.revanced.patcher.data.implementation
|
||||
|
||||
import app.revanced.patcher.data.base.Data
|
||||
import org.w3c.dom.Document
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import javax.xml.XMLConstants
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
|
||||
class ResourceData(private val resourceCacheDirectory: File) : Data {
|
||||
private fun resolve(path: String) = resourceCacheDirectory.resolve(path)
|
||||
|
||||
fun forEach(action: (File) -> Unit) = resourceCacheDirectory.walkTopDown().forEach(action)
|
||||
fun get(path: String) = resolve(path)
|
||||
|
||||
fun replace(path: String, oldValue: String, newValue: String, oldValueIsRegex: Boolean = false) {
|
||||
// TODO: buffer this somehow
|
||||
val content = resolve(path).readText()
|
||||
|
||||
if (oldValueIsRegex) {
|
||||
content.replace(Regex(oldValue), newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun getXmlEditor(path: String) = DomFileEditor(resolve(path))
|
||||
}
|
||||
|
||||
class DomFileEditor internal constructor(private val domFile: File) : Closeable {
|
||||
val file: Document
|
||||
|
||||
init {
|
||||
val factory = DocumentBuilderFactory.newInstance()
|
||||
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
|
||||
|
||||
val builder = factory.newDocumentBuilder()
|
||||
|
||||
// this will expectedly throw
|
||||
file = builder.parse(domFile)
|
||||
file.normalize()
|
||||
}
|
||||
|
||||
override fun close() = TransformerFactory.newInstance().newTransformer()
|
||||
.transform(DOMSource(file), StreamResult(domFile.outputStream()))
|
||||
}
|
@@ -4,11 +4,9 @@ import app.revanced.patcher.annotation.Compatibility
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.base.Data
|
||||
import app.revanced.patcher.patch.base.Patch
|
||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
||||
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
@@ -43,17 +41,23 @@ object PatchExtensions {
|
||||
val Class<out Patch<Data>>.version get() = recursiveAnnotation(Version::class)?.version
|
||||
val Class<out Patch<Data>>.include get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)!!.include
|
||||
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
||||
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Dependencies::class)?.dependencies
|
||||
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.DependsOn::class)?.dependencies
|
||||
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
|
||||
@JvmStatic
|
||||
fun Class<out Patch<Data>>.dependsOn(patch: Class<out Patch<Data>>): Boolean {
|
||||
if (this.patchName == patch.patchName) throw IllegalArgumentException("thisval and patch may not be the same")
|
||||
return this.dependencies?.any { it.java.patchName == this@dependsOn.patchName } == true
|
||||
}
|
||||
}
|
||||
|
||||
object MethodSignatureExtensions {
|
||||
val MethodSignature.name: String
|
||||
object MethodFingerprintExtensions {
|
||||
val MethodFingerprint.name: String
|
||||
get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||
val MethodSignature.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
||||
val MethodSignature.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
||||
val MethodSignature.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
val MethodSignature.matchingMethod get() = javaClass.recursiveAnnotation(MatchingMethod::class)
|
||||
val MethodSignature.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(FuzzyPatternScanMethod::class)
|
||||
val MethodSignature.fuzzyThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||
val MethodFingerprint.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
||||
val MethodFingerprint.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
||||
val MethodFingerprint.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
val MethodFingerprint.matchingMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.MatchingMethod::class)
|
||||
val MethodFingerprint.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod::class)
|
||||
val MethodFingerprint.fuzzyScanThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
package app.revanced.patcher.fingerprint
|
||||
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
|
||||
/**
|
||||
* A ReVanced fingerprint.
|
||||
* Can be a [MethodFingerprint].
|
||||
*/
|
||||
interface Fingerprint
|
@@ -1,11 +1,11 @@
|
||||
package app.revanced.patcher.signature.implementation.method.annotation
|
||||
package app.revanced.patcher.fingerprint.method.annotation
|
||||
|
||||
import app.revanced.patcher.signature.implementation.method.MethodSignature
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
|
||||
/**
|
||||
* Annotations for a method which matches to a [MethodSignature].
|
||||
* Annotations for a method which matches to a [MethodFingerprint].
|
||||
* @param definingClass The defining class name of the method.
|
||||
* @param name A suggestive name for the method which the [MethodSignature] was created for.
|
||||
* @param name A suggestive name for the method which the [MethodFingerprint] was created for.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -15,7 +15,7 @@ annotation class MatchingMethod(
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotations to scan a pattern [MethodSignature] with fuzzy algorithm.
|
||||
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.
|
||||
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@@ -25,7 +25,7 @@ annotation class FuzzyPatternScanMethod(
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotations to scan a pattern [MethodSignature] directly.
|
||||
* Annotations to scan a pattern [MethodFingerprint] directly.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
@@ -0,0 +1,98 @@
|
||||
package app.revanced.patcher.fingerprint.method.impl
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.MethodNotFoundException
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
|
||||
import app.revanced.patcher.extensions.softCompareTo
|
||||
import app.revanced.patcher.fingerprint.Fingerprint
|
||||
import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
|
||||
/**
|
||||
* Represents the [MethodFingerprint] for a method.
|
||||
* @param returnType The return type of the method.
|
||||
* @param access The access flags of the method.
|
||||
* @param parameters The parameters of the method.
|
||||
* @param opcodes The list of opcodes of the method.
|
||||
* @param strings A list of strings which a method contains.
|
||||
* @param customFingerprint A custom condition for this fingerprint.
|
||||
* A `null` opcode is equals to an unknown opcode.
|
||||
*/
|
||||
abstract class MethodFingerprint(
|
||||
internal val returnType: String?,
|
||||
internal val access: Int?,
|
||||
internal val parameters: Iterable<String>?,
|
||||
internal val opcodes: Iterable<Opcode?>?,
|
||||
internal val strings: Iterable<String>? = null,
|
||||
internal val customFingerprint: ((methodDef: Method) -> Boolean)? = null
|
||||
) : Fingerprint {
|
||||
/**
|
||||
* The result of the [MethodFingerprint] the [Method].
|
||||
* @throws MethodNotFoundException If the resolution of the [Method] has not happened.
|
||||
*/
|
||||
var result: MethodFingerprintResult? = null
|
||||
get() = field ?: throw Exception("${this.name} has not been resolved yet.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the result of a [MethodFingerprintUtils].
|
||||
* @param method The matching method.
|
||||
* @param classDef The [ClassDef] that contains the matching [method].
|
||||
* @param patternScanResult Opcodes pattern scan result.
|
||||
* @param data The [BytecodeData] this [MethodFingerprintResult] is attached to, to create proxies.
|
||||
*/
|
||||
data class MethodFingerprintResult(
|
||||
val method: Method,
|
||||
val classDef: ClassDef,
|
||||
val patternScanResult: PatternScanResult?,
|
||||
internal val data: BytecodeData
|
||||
) {
|
||||
/**
|
||||
* Returns a mutable clone of [classDef]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [classDef] where possible.
|
||||
*/
|
||||
val mutableClass by lazy { data.proxy(classDef).resolve() }
|
||||
|
||||
/**
|
||||
* Returns a mutable clone of [method]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [method] where possible.
|
||||
*/
|
||||
val mutableMethod by lazy {
|
||||
mutableClass.methods.first {
|
||||
it.softCompareTo(this.method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a pattern scan.
|
||||
* @param startIndex The start index of the instructions where to which this pattern matches.
|
||||
* @param endIndex The end index of the instructions where to which this pattern matches.
|
||||
* @param warnings A list of warnings considering this [PatternScanResult].
|
||||
*/
|
||||
data class PatternScanResult(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int,
|
||||
var warnings: List<Warning>? = null
|
||||
) {
|
||||
/**
|
||||
* Represents warnings of the pattern scan.
|
||||
* @param correctOpcode The opcode the instruction list has.
|
||||
* @param wrongOpcode The opcode the pattern list of the signature currently has.
|
||||
* @param instructionIndex The index of the opcode relative to the instruction list.
|
||||
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
|
||||
*/
|
||||
data class Warning(
|
||||
val correctOpcode: Opcode,
|
||||
val wrongOpcode: Opcode,
|
||||
val instructionIndex: Int,
|
||||
val patternIndex: Int,
|
||||
)
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
package app.revanced.patcher.fingerprint.method.utils
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
||||
import app.revanced.patcher.extensions.parametersEqual
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult
|
||||
import app.revanced.patcher.fingerprint.method.impl.PatternScanResult
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import org.jf.dexlib2.iface.reference.StringReference
|
||||
|
||||
/**
|
||||
* Utility class for [MethodFingerprint]
|
||||
*/
|
||||
object MethodFingerprintUtils {
|
||||
/**
|
||||
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||
* @param context The classes on which to resolve the [MethodFingerprint].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun Iterable<MethodFingerprint>.resolve(forData: BytecodeData, context: Iterable<ClassDef>) {
|
||||
for (fingerprint in this) // For each fingerprint
|
||||
classes@ for (classDef in context) // search through all classes for the fingerprint
|
||||
if (fingerprint.resolve(forData, classDef))
|
||||
break@classes // if the resolution succeeded, continue with the next fingerprint
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] against a [ClassDef].
|
||||
* @param context The class on which to resolve the [MethodFingerprint].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean {
|
||||
for (method in context.methods)
|
||||
if (this.resolve(forData, method, context))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] against a [Method].
|
||||
* @param context The context on which to resolve the [MethodFingerprint].
|
||||
* @param classDef The class of the matching [Method].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean {
|
||||
val methodFingerprint = this
|
||||
|
||||
if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType))
|
||||
return false
|
||||
|
||||
if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags)
|
||||
return false
|
||||
|
||||
|
||||
if (methodFingerprint.parameters != null && !parametersEqual(
|
||||
methodFingerprint.parameters, // TODO: parseParameters()
|
||||
context.parameterTypes
|
||||
)
|
||||
) return false
|
||||
|
||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context))
|
||||
return false
|
||||
|
||||
if (methodFingerprint.strings != null) {
|
||||
val implementation = context.implementation ?: return false
|
||||
|
||||
val stringsList = methodFingerprint.strings.toMutableList()
|
||||
|
||||
implementation.instructions.forEach { instruction ->
|
||||
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEach
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
val index = stringsList.indexOfFirst { it == string }
|
||||
if (index != -1) stringsList.removeAt(index)
|
||||
}
|
||||
|
||||
if (stringsList.isNotEmpty()) return false
|
||||
}
|
||||
|
||||
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||
context.implementation?.instructions ?: return false
|
||||
|
||||
context.patternScan(methodFingerprint) ?: return false
|
||||
} else null
|
||||
|
||||
methodFingerprint.result = MethodFingerprintResult(context, classDef, patternScanResult, forData)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Method.patternScan(
|
||||
fingerprint: MethodFingerprint
|
||||
): PatternScanResult? {
|
||||
val instructions = this.implementation!!.instructions
|
||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold
|
||||
|
||||
val pattern = fingerprint.opcodes!!
|
||||
val instructionLength = instructions.count()
|
||||
val patternLength = pattern.count()
|
||||
|
||||
for (index in 0 until instructionLength) {
|
||||
var patternIndex = 0
|
||||
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||
|
||||
while (index + patternIndex < instructionLength) {
|
||||
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||
// reaching maximum threshold (0) means,
|
||||
// the pattern does not match to the current instructions
|
||||
if (threshold-- == 0) break
|
||||
}
|
||||
|
||||
if (patternIndex < patternLength - 1) {
|
||||
// if the entire pattern has not been scanned yet
|
||||
// continue the scan
|
||||
patternIndex++
|
||||
continue
|
||||
}
|
||||
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
||||
val result = PatternScanResult(index, index + patternIndex)
|
||||
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
||||
result.warnings = result.createWarnings(pattern, instructions)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun PatternScanResult.createWarnings(
|
||||
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
||||
) = buildList {
|
||||
for ((patternIndex, instructionIndex) in (this@createWarnings.startIndex until this@createWarnings.endIndex).withIndex()) {
|
||||
val originalOpcode = instructions.elementAt(instructionIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue
|
||||
|
||||
this.add(PatternScanResult.Warning(originalOpcode, patternOpcode, instructionIndex, patternIndex))
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun ClassDef.component1() = this
|
||||
private operator fun ClassDef.component2() = this.methods
|
8
src/main/kotlin/app/revanced/patcher/logging/Logger.kt
Normal file
8
src/main/kotlin/app/revanced/patcher/logging/Logger.kt
Normal file
@@ -0,0 +1,8 @@
|
||||
package app.revanced.patcher.logging
|
||||
|
||||
interface Logger {
|
||||
fun error(msg: String) {}
|
||||
fun warn(msg: String) {}
|
||||
fun info(msg: String) {}
|
||||
fun trace(msg: String) {}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package app.revanced.patcher.logging.impl
|
||||
|
||||
import app.revanced.patcher.logging.Logger
|
||||
|
||||
object NopLogger : Logger
|
25
src/main/kotlin/app/revanced/patcher/patch/Patch.kt
Normal file
25
src/main/kotlin/app/revanced/patcher/patch/Patch.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* A ReVanced patch.
|
||||
*
|
||||
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
||||
* If it implements [Closeable], it will be closed after all patches have been executed.
|
||||
* Closing will be done in reverse execution order.
|
||||
*/
|
||||
abstract class Patch<out T : Data> {
|
||||
/**
|
||||
* The main function of the [Patch] which the patcher will call.
|
||||
*/
|
||||
abstract fun execute(data: @UnsafeVariance T): PatchResult
|
||||
|
||||
/**
|
||||
* A list of [PatchOption]s.
|
||||
*/
|
||||
open val options = PatchOptions()
|
||||
}
|
192
src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt
Normal file
192
src/main/kotlin/app/revanced/patcher/patch/PatchOption.kt
Normal file
@@ -0,0 +1,192 @@
|
||||
@file:Suppress("CanBeParameter", "MemberVisibilityCanBePrivate", "UNCHECKED_CAST")
|
||||
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class NoSuchOptionException(val option: String) : Exception("No such option: $option")
|
||||
class IllegalValueException(val value: Any?) : Exception("Illegal value: $value")
|
||||
class InvalidTypeException(val got: String, val expected: String) :
|
||||
Exception("Invalid option value type: $got, expected $expected")
|
||||
|
||||
class RequirementNotMetException : Exception("null was passed into an option that requires a value")
|
||||
|
||||
/**
|
||||
* A registry for an array of [PatchOption]s.
|
||||
* @param options An array of [PatchOption]s.
|
||||
*/
|
||||
class PatchOptions(vararg val options: PatchOption<*>) : Iterable<PatchOption<*>> {
|
||||
private val register = buildMap {
|
||||
for (option in options) {
|
||||
if (containsKey(option.key)) {
|
||||
throw IllegalStateException("Multiple options found with the same key")
|
||||
}
|
||||
put(option.key, option)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a [PatchOption] by its key.
|
||||
* @param key The key of the [PatchOption].
|
||||
*/
|
||||
operator fun get(key: String) = register[key] ?: throw NoSuchOptionException(key)
|
||||
|
||||
/**
|
||||
* Set the value of a [PatchOption].
|
||||
* @param key The key of the [PatchOption].
|
||||
* @param value The value you want it to be.
|
||||
* Please note that using the wrong value type results in a runtime error.
|
||||
*/
|
||||
inline operator fun <reified T> set(key: String, value: T) {
|
||||
@Suppress("UNCHECKED_CAST") val opt = get(key) as PatchOption<T>
|
||||
if (opt.value !is T) throw InvalidTypeException(
|
||||
T::class.java.canonicalName,
|
||||
opt.value?.let { it::class.java.canonicalName } ?: "null"
|
||||
)
|
||||
opt.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a [PatchOption] to `null`.
|
||||
* @param key The key of the [PatchOption].
|
||||
*/
|
||||
fun nullify(key: String) {
|
||||
get(key).value = null
|
||||
}
|
||||
|
||||
override fun iterator() = options.iterator()
|
||||
}
|
||||
|
||||
/**
|
||||
* A [Patch] option.
|
||||
* @param key Unique identifier of the option. Example: _`settings.microg.enabled`_
|
||||
* @param default The default value of the option.
|
||||
* @param title A human-readable title of the option. Example: _MicroG Settings_
|
||||
* @param description A human-readable description of the option. Example: _Settings integration for MicroG._
|
||||
* @param required Whether the option is required.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
sealed class PatchOption<T>(
|
||||
val key: String,
|
||||
default: T?,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val required: Boolean,
|
||||
val validator: (T?) -> Boolean
|
||||
) {
|
||||
var value: T? = default
|
||||
set(value) {
|
||||
if (value == null && required) {
|
||||
throw RequirementNotMetException()
|
||||
}
|
||||
if (!validator(value)) {
|
||||
throw IllegalValueException(value)
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the option.
|
||||
* Please note that using the wrong value type results in a runtime error.
|
||||
*/
|
||||
operator fun <T> getValue(thisRef: Nothing?, property: KProperty<*>) = value as T
|
||||
|
||||
/**
|
||||
* Gets the value of the option.
|
||||
* Please note that using the wrong value type results in a runtime error.
|
||||
*/
|
||||
inline operator fun <reified V> setValue(thisRef: Any?, property: KProperty<*>, new: V) {
|
||||
if (value !is V) throw InvalidTypeException(
|
||||
V::class.java.canonicalName,
|
||||
value?.let { it::class.java.canonicalName } ?: "null"
|
||||
)
|
||||
value = new as T
|
||||
}
|
||||
|
||||
/**
|
||||
* A [PatchOption] representing a [String].
|
||||
* @see PatchOption
|
||||
*/
|
||||
class StringOption(
|
||||
key: String,
|
||||
default: String?,
|
||||
title: String,
|
||||
description: String,
|
||||
required: Boolean = false,
|
||||
validator: (String?) -> Boolean = { true }
|
||||
) : PatchOption<String>(
|
||||
key, default, title, description, required, validator
|
||||
)
|
||||
|
||||
/**
|
||||
* A [PatchOption] representing a [Boolean].
|
||||
* @see PatchOption
|
||||
*/
|
||||
class BooleanOption(
|
||||
key: String,
|
||||
default: Boolean?,
|
||||
title: String,
|
||||
description: String,
|
||||
required: Boolean = false,
|
||||
validator: (Boolean?) -> Boolean = { true }
|
||||
) : PatchOption<Boolean>(
|
||||
key, default, title, description, required, validator
|
||||
)
|
||||
|
||||
/**
|
||||
* A [PatchOption] with a list of allowed options.
|
||||
* @param options A list of allowed options for the [ListOption].
|
||||
* @see PatchOption
|
||||
*/
|
||||
sealed class ListOption<E>(
|
||||
key: String,
|
||||
default: E?,
|
||||
val options: Iterable<E>,
|
||||
title: String,
|
||||
description: String,
|
||||
required: Boolean = false,
|
||||
validator: (E?) -> Boolean = { true }
|
||||
) : PatchOption<E>(
|
||||
key, default, title, description, required, {
|
||||
(it?.let { it in options } ?: true) && validator(it)
|
||||
}
|
||||
) {
|
||||
init {
|
||||
if (default !in options) {
|
||||
throw IllegalStateException("Default option must be an allowed options")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [ListOption] of type [String].
|
||||
* @see ListOption
|
||||
*/
|
||||
class StringListOption(
|
||||
key: String,
|
||||
default: String?,
|
||||
options: Iterable<String>,
|
||||
title: String,
|
||||
description: String,
|
||||
required: Boolean = false,
|
||||
validator: (String?) -> Boolean = { true }
|
||||
) : ListOption<String>(
|
||||
key, default, options, title, description, required, validator
|
||||
)
|
||||
|
||||
/**
|
||||
* A [ListOption] of type [Int].
|
||||
* @see ListOption
|
||||
*/
|
||||
class IntListOption(
|
||||
key: String,
|
||||
default: Int?,
|
||||
options: Iterable<Int>,
|
||||
title: String,
|
||||
description: String,
|
||||
required: Boolean = false,
|
||||
validator: (Int?) -> Boolean = { true }
|
||||
) : ListOption<Int>(
|
||||
key, default, options, title, description, required, validator
|
||||
)
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package app.revanced.patcher.patch.implementation.misc
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
interface PatchResult {
|
||||
fun error(): PatchResultError? {
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user