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
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
75df245ec3 | ||
![]() |
4164cb0dea | ||
![]() |
18fe35ae73 | ||
![]() |
f9bc95f220 | ||
![]() |
d2f91a8545 | ||
![]() |
4016bdc37f | ||
![]() |
538b2a8599 | ||
![]() |
4aa14bbb85 | ||
![]() |
d37452997b | ||
![]() |
db21d5e953 | ||
![]() |
4d581811db | ||
![]() |
8c502448be | ||
![]() |
fec16d9442 | ||
![]() |
5583904994 | ||
![]() |
797286b758 | ||
![]() |
4ae9ad09d6 | ||
![]() |
447e1ad30e | ||
![]() |
843e62ad29 | ||
![]() |
9c07ffcc7a | ||
![]() |
438321330e | ||
![]() |
3ba4be240b | ||
![]() |
98ce0abfa9 | ||
![]() |
db4348c4fa | ||
![]() |
4839f87519 | ||
![]() |
809862c997 | ||
![]() |
fd5c878cee | ||
![]() |
124332f0e9 |
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -15,17 +15,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup JDK
|
- name: Setup JDK
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'temurin'
|
||||||
cache: gradle
|
cache: gradle
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
|
66
CHANGELOG.md
66
CHANGELOG.md
@@ -1,3 +1,69 @@
|
|||||||
|
## [6.0.2](https://github.com/revanced/revanced-patcher/compare/v6.0.1...v6.0.2) (2022-11-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fallback to patch class name instead of `java.lang.Class` class name ([4164cb0](https://github.com/revanced/revanced-patcher/commit/4164cb0deacc7e1eed9fce63dab030180f28b762))
|
||||||
|
|
||||||
|
## [6.0.1](https://github.com/revanced/revanced-patcher/compare/v6.0.0...v6.0.1) (2022-11-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* remove unnecessary dummy nop instructions ([#111](https://github.com/revanced/revanced-patcher/issues/111)) ([f9bc95f](https://github.com/revanced/revanced-patcher/commit/f9bc95f220aa434308ce6950ba6ad2e7efac9c8a))
|
||||||
|
|
||||||
|
# [6.0.0](https://github.com/revanced/revanced-patcher/compare/v5.1.2...v6.0.0) (2022-10-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* improve structuring of classes and their implementations ([4aa14bb](https://github.com/revanced/revanced-patcher/commit/4aa14bbb858af9253eae9328b759f3298b65a215))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* remove unused annotation `DirectPatternScanMethod` ([538b2a8](https://github.com/revanced/revanced-patcher/commit/538b2a859962570c700362afc88704ed3611aa87))
|
||||||
|
* remove unused annotation `SincePatcher` ([4ae9ad0](https://github.com/revanced/revanced-patcher/commit/4ae9ad09d64a3f69512ccb037f816cb847d7350f))
|
||||||
|
* remove unused extension `dependsOn` ([797286b](https://github.com/revanced/revanced-patcher/commit/797286b7588646272dea2fd35e8e78b0ffb18a0f))
|
||||||
|
* remove unused patch extensions ([5583904](https://github.com/revanced/revanced-patcher/commit/55839049948033ad02414517fd3ba03619216aec))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* various changes in which packages classes previously where and their implementation
|
||||||
|
* These extensions do not exist anymore and any use should be removed
|
||||||
|
* The extension does not exist anymore and any use should be removed
|
||||||
|
* The annotation does not exist anymore and any use should be removed
|
||||||
|
|
||||||
|
## [5.1.2](https://github.com/revanced/revanced-patcher/compare/v5.1.1...v5.1.2) (2022-09-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* check dependencies for resource patches ([9c07ffc](https://github.com/revanced/revanced-patcher/commit/9c07ffcc7af9f088426528561f4321c5cc6b5b15))
|
||||||
|
* use instruction index instead of strings list index for `StringMatch` ([843e62a](https://github.com/revanced/revanced-patcher/commit/843e62ad290ee0a707be9322ee943921da3ea420))
|
||||||
|
|
||||||
|
## [5.1.1](https://github.com/revanced/revanced-patcher/compare/v5.1.0...v5.1.1) (2022-09-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* decode resources only when necessary ([3ba4be2](https://github.com/revanced/revanced-patcher/commit/3ba4be240bf0a424e4bbfbaca9605644fda0984e))
|
||||||
|
|
||||||
|
# [5.1.0](https://github.com/revanced/revanced-patcher/compare/v5.0.1...v5.1.0) (2022-09-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* RwLock for opening files in `DomFileEditor` ([db4348c](https://github.com/revanced/revanced-patcher/commit/db4348c4faf51bfe29678baacfbe76ba645ec0b9))
|
||||||
|
|
||||||
|
## [5.0.1](https://github.com/revanced/revanced-patcher/compare/v5.0.0...v5.0.1) (2022-09-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* revert breaking changes ([#106](https://github.com/revanced/revanced-patcher/issues/106)) ([124332f](https://github.com/revanced/revanced-patcher/commit/124332f0e9bbdaf4f1aeeb6a31333093eeba1642))
|
||||||
|
|
||||||
# [5.0.0](https://github.com/revanced/revanced-patcher/compare/v4.5.0...v5.0.0) (2022-09-21)
|
# [5.0.0](https://github.com/revanced/revanced-patcher/compare/v4.5.0...v5.0.0) (2022-09-21)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ dependencies {
|
|||||||
implementation("xpp3:xpp3:1.1.4c")
|
implementation("xpp3:xpp3:1.1.4c")
|
||||||
implementation("org.smali:smali:2.5.2")
|
implementation("org.smali:smali:2.5.2")
|
||||||
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
||||||
implementation("org.apktool:apktool-lib:2.7.0-SNAPSHOT")
|
implementation("org.apktool:apktool-lib:2.8.1-SNAPSHOT")
|
||||||
|
|
||||||
implementation(kotlin("reflect"))
|
implementation(kotlin("reflect"))
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.0.0
|
version = 6.0.2
|
||||||
|
File diff suppressed because it is too large
Load Diff
19
src/main/kotlin/app/revanced/patcher/PatcherContext.kt
Normal file
19
src/main/kotlin/app/revanced/patcher/PatcherContext.kt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
|
import app.revanced.patcher.data.Context
|
||||||
|
import app.revanced.patcher.data.PackageMetadata
|
||||||
|
import app.revanced.patcher.data.ResourceContext
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
data class PatcherContext(
|
||||||
|
val classes: MutableList<ClassDef>,
|
||||||
|
val resourceCacheDirectory: File,
|
||||||
|
) {
|
||||||
|
val packageMetadata = PackageMetadata()
|
||||||
|
internal val patches = mutableListOf<Class<out Patch<Context>>>()
|
||||||
|
internal val bytecodeContext = BytecodeContext(classes)
|
||||||
|
internal val resourceContext = ResourceContext(resourceCacheDirectory)
|
||||||
|
}
|
@@ -1,19 +0,0 @@
|
|||||||
package app.revanced.patcher
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
data class PatcherData(
|
|
||||||
internal val internalClasses: MutableList<ClassDef>,
|
|
||||||
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))
|
|
||||||
}
|
|
@@ -8,7 +8,6 @@ import java.io.File
|
|||||||
* Options for the [Patcher].
|
* Options for the [Patcher].
|
||||||
* @param inputFile The input file (usually an apk file).
|
* @param inputFile The input file (usually an apk file).
|
||||||
* @param resourceCacheDirectory Directory to cache resources.
|
* @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 aaptPath Optional path to a custom aapt binary.
|
||||||
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
||||||
* @param logger Custom logger implementation for the [Patcher].
|
* @param logger Custom logger implementation for the [Patcher].
|
||||||
@@ -16,7 +15,6 @@ import java.io.File
|
|||||||
data class PatcherOptions(
|
data class PatcherOptions(
|
||||||
internal val inputFile: File,
|
internal val inputFile: File,
|
||||||
internal val resourceCacheDirectory: String,
|
internal val resourceCacheDirectory: String,
|
||||||
internal val patchResources: Boolean = false,
|
|
||||||
internal val aaptPath: String = "",
|
internal val aaptPath: String = "",
|
||||||
internal val frameworkFolderLocation: String? = null,
|
internal val frameworkFolderLocation: String? = null,
|
||||||
internal val logger: Logger = NopLogger
|
internal val logger: Logger = NopLogger
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package app.revanced.patcher.annotation
|
package app.revanced.patcher.annotation
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
import app.revanced.patcher.data.Context
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@@ -14,6 +14,6 @@ import kotlin.reflect.KClass
|
|||||||
@MustBeDocumented
|
@MustBeDocumented
|
||||||
annotation class PatchDeprecated(
|
annotation class PatchDeprecated(
|
||||||
val reason: String,
|
val reason: String,
|
||||||
val replacement: KClass<out Patch<Data>> = Patch::class
|
val replacement: KClass<out Patch<Context>> = Patch::class
|
||||||
// Values cannot be nullable in annotations, so this will have to do.
|
// Values cannot be nullable in annotations, so this will have to do.
|
||||||
)
|
)
|
@@ -1,13 +0,0 @@
|
|||||||
package app.revanced.patcher.annotation
|
|
||||||
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
import app.revanced.patcher.Patcher
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Declares a [Patch] deprecated for removal.
|
|
||||||
* @param version The minimum version of the [Patcher] this [Patch] supports.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class SincePatcher(val version: String)
|
|
181
src/main/kotlin/app/revanced/patcher/data/Context.kt
Normal file
181
src/main/kotlin/app/revanced/patcher/data/Context.kt
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
|
import app.revanced.patcher.util.ProxyBackedClassList
|
||||||
|
import app.revanced.patcher.util.method.MethodWalker
|
||||||
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
|
import org.jf.dexlib2.iface.Method
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common interface to constrain [Context] to [BytecodeContext] and [ResourceContext].
|
||||||
|
*/
|
||||||
|
|
||||||
|
sealed interface Context
|
||||||
|
|
||||||
|
class BytecodeContext internal constructor(classes: MutableList<ClassDef>) : Context {
|
||||||
|
/**
|
||||||
|
* The list of classes.
|
||||||
|
*/
|
||||||
|
val classes = ProxyBackedClassList(classes)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a class by a given class name.
|
||||||
|
*
|
||||||
|
* @param className The name of the class.
|
||||||
|
* @return A proxy for the first class that matches the class name.
|
||||||
|
*/
|
||||||
|
fun findClass(className: String) = findClass { it.type.contains(className) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a class by a given predicate.
|
||||||
|
*
|
||||||
|
* @param predicate A predicate to match the class.
|
||||||
|
* @return A proxy for the first class that matches the predicate.
|
||||||
|
*/
|
||||||
|
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||||
|
// if we already proxied the class matching the predicate...
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
|
||||||
|
for (element in this) {
|
||||||
|
if (predicate(element)) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [MethodWalker] instance for the current [BytecodeContext].
|
||||||
|
*
|
||||||
|
* @param startMethod The method to start at.
|
||||||
|
* @return A [MethodWalker] instance.
|
||||||
|
*/
|
||||||
|
fun BytecodeContext.toMethodWalker(startMethod: Method): MethodWalker {
|
||||||
|
return MethodWalker(this, startMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
|
||||||
|
for ((index, element) in this.withIndex()) {
|
||||||
|
if (predicate(element)) {
|
||||||
|
return element to index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResourceContext internal constructor(private val resourceCacheDirectory: File) : Context, 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@ResourceContext[path])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for a file that can be edited as a dom document.
|
||||||
|
*
|
||||||
|
* This constructor does not check for locks to the file when writing.
|
||||||
|
* Use the secondary constructor.
|
||||||
|
*
|
||||||
|
* @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 be read only.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
private var closed: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() }) {
|
||||||
|
// increase the lock
|
||||||
|
locks.merge(file.path, 1, Integer::sum)
|
||||||
|
filePath = file.path
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the editor. Write backs and decreases the lock count.
|
||||||
|
*
|
||||||
|
* Will not write back to the file if the file is still locked.
|
||||||
|
*/
|
||||||
|
override fun close() {
|
||||||
|
if (closed) return
|
||||||
|
|
||||||
|
inputStream.close()
|
||||||
|
|
||||||
|
// if the output stream is not null, do not close it
|
||||||
|
outputStream?.let {
|
||||||
|
// prevent writing to same file, if it is being locked
|
||||||
|
// isLocked will be false if the editor was created through a stream
|
||||||
|
val isLocked = filePath?.let { path ->
|
||||||
|
val isLocked = locks[path]!! > 1
|
||||||
|
// decrease the lock count if the editor was opened for a file
|
||||||
|
locks.merge(path, -1, Integer::sum)
|
||||||
|
isLocked
|
||||||
|
} ?: false
|
||||||
|
|
||||||
|
// if unlocked, write back to the file
|
||||||
|
if (!isLocked) {
|
||||||
|
it.value.use { stream ->
|
||||||
|
val result = StreamResult(stream)
|
||||||
|
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.value.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
// map of concurrent open files
|
||||||
|
val locks = mutableMapOf<String, Int>()
|
||||||
|
}
|
||||||
|
}
|
@@ -1,9 +0,0 @@
|
|||||||
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,69 +0,0 @@
|
|||||||
package app.revanced.patcher.data.impl
|
|
||||||
|
|
||||||
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
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
|
|
||||||
class BytecodeData(
|
|
||||||
internalClasses: MutableList<ClassDef>
|
|
||||||
) : Data {
|
|
||||||
val classes = ProxyBackedClassList(internalClasses)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a class by a given class name.
|
|
||||||
* @param className The name of the class.
|
|
||||||
* @return A proxy for the first class that matches the class name.
|
|
||||||
*/
|
|
||||||
fun findClass(className: String) = findClass { it.type.contains(className) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a class by a given predicate.
|
|
||||||
* @param predicate A predicate to match the class.
|
|
||||||
* @return A proxy for the first class that matches the predicate.
|
|
||||||
*/
|
|
||||||
fun findClass(predicate: (ClassDef) -> Boolean) =
|
|
||||||
// if we already proxied the class matching the predicate...
|
|
||||||
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)
|
|
||||||
|
|
||||||
internal inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
|
|
||||||
for (element in this) {
|
|
||||||
if (predicate(element)) {
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [MethodWalker] instance for the current [BytecodeData].
|
|
||||||
* @param startMethod The method to start at.
|
|
||||||
* @return A [MethodWalker] instance.
|
|
||||||
*/
|
|
||||||
fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
|
|
||||||
return MethodWalker(this, startMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
|
|
||||||
for ((index, element) in this.withIndex()) {
|
|
||||||
if (predicate(element)) {
|
|
||||||
return element to index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
@@ -1,84 +0,0 @@
|
|||||||
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,11 +1,13 @@
|
|||||||
package app.revanced.patcher.extensions
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
import app.revanced.patcher.annotation.*
|
import app.revanced.patcher.annotation.*
|
||||||
import app.revanced.patcher.data.Data
|
import app.revanced.patcher.data.Context
|
||||||
|
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
import app.revanced.patcher.patch.OptionsContainer
|
import app.revanced.patcher.patch.OptionsContainer
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.patch.PatchOptions
|
import app.revanced.patcher.patch.PatchOptions
|
||||||
|
import app.revanced.patcher.patch.annotations.DependsOn
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KVisibility
|
import kotlin.reflect.KVisibility
|
||||||
import kotlin.reflect.full.companionObject
|
import kotlin.reflect.full.companionObject
|
||||||
@@ -16,7 +18,7 @@ import kotlin.reflect.full.companionObjectInstance
|
|||||||
* @param targetAnnotation The annotation to find.
|
* @param targetAnnotation The annotation to find.
|
||||||
* @return The annotation.
|
* @return The annotation.
|
||||||
*/
|
*/
|
||||||
private fun <T : Annotation> Class<*>.recursiveAnnotation(targetAnnotation: KClass<T>) =
|
private fun <T : Annotation> Class<*>.findAnnotationRecursively(targetAnnotation: KClass<T>) =
|
||||||
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||||
|
|
||||||
|
|
||||||
@@ -38,42 +40,53 @@ private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
|||||||
}
|
}
|
||||||
|
|
||||||
object PatchExtensions {
|
object PatchExtensions {
|
||||||
val Class<*>.patchName: String
|
val Class<out Patch<Context>>.patchName: String
|
||||||
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName
|
||||||
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<Context>>.version
|
||||||
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
get() = findAnnotationRecursively(Version::class)?.version
|
||||||
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
|
val Class<out Patch<Context>>.include
|
||||||
val Class<out Patch<Data>>.options: PatchOptions?
|
get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include
|
||||||
|
|
||||||
|
val Class<out Patch<Context>>.description
|
||||||
|
get() = findAnnotationRecursively(Description::class)?.description
|
||||||
|
|
||||||
|
val Class<out Patch<Context>>.dependencies
|
||||||
|
get() = findAnnotationRecursively(DependsOn::class)?.dependencies
|
||||||
|
|
||||||
|
val Class<out Patch<Context>>.compatiblePackages
|
||||||
|
get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages
|
||||||
|
|
||||||
|
val Class<out Patch<Context>>.options: PatchOptions?
|
||||||
get() = kotlin.companionObject?.let { cl ->
|
get() = kotlin.companionObject?.let { cl ->
|
||||||
if (cl.visibility != KVisibility.PUBLIC) return null
|
if (cl.visibility != KVisibility.PUBLIC) return null
|
||||||
kotlin.companionObjectInstance?.let {
|
kotlin.companionObjectInstance?.let {
|
||||||
(it as? OptionsContainer)?.options
|
(it as? OptionsContainer)?.options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val Class<out Patch<Data>>.deprecated: Pair<String, KClass<out Patch<Data>>?>?
|
|
||||||
get() = recursiveAnnotation(PatchDeprecated::class)?.let {
|
val Class<out Patch<Context>>.deprecated: Pair<String, KClass<out Patch<Context>>?>?
|
||||||
|
get() = findAnnotationRecursively(PatchDeprecated::class)?.let {
|
||||||
it.reason to it.replacement.let { cl ->
|
it.reason to it.replacement.let { cl ->
|
||||||
if (cl == Patch::class) null else cl
|
if (cl == Patch::class) null else cl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val Class<out Patch<Data>>.sincePatcherVersion get() = recursiveAnnotation(SincePatcher::class)?.version
|
|
||||||
|
|
||||||
@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 MethodFingerprintExtensions {
|
object MethodFingerprintExtensions {
|
||||||
val MethodFingerprint.name: String
|
val MethodFingerprint.name: String
|
||||||
get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
get() = javaClass.findAnnotationRecursively(Name::class)?.name ?: this.javaClass.simpleName
|
||||||
val MethodFingerprint.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
|
||||||
val MethodFingerprint.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
val MethodFingerprint.version
|
||||||
val MethodFingerprint.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
get() = javaClass.findAnnotationRecursively(Version::class)?.version ?: "0.0.1"
|
||||||
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.description
|
||||||
val MethodFingerprint.fuzzyScanThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
get() = javaClass.findAnnotationRecursively(Description::class)?.description
|
||||||
|
|
||||||
|
val MethodFingerprint.fuzzyPatternScanMethod
|
||||||
|
get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class)
|
||||||
|
|
||||||
|
val MethodFingerprint.fuzzyScanThreshold
|
||||||
|
get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||||
}
|
}
|
@@ -139,10 +139,10 @@ fun MutableMethod.addInstructions(index: Int, smali: String, externalLabels: Lis
|
|||||||
|
|
||||||
// Add the compiled list of instructions to the method.
|
// Add the compiled list of instructions to the method.
|
||||||
val methodImplementation = this.implementation!!
|
val methodImplementation = this.implementation!!
|
||||||
methodImplementation.addInstructions(index, compiledInstructions)
|
methodImplementation.addInstructions(index, compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size))
|
||||||
|
|
||||||
val methodInstructions = methodImplementation.instructions
|
val methodInstructions = methodImplementation.instructions
|
||||||
methodInstructions.subList(index, index + compiledInstructions.size)
|
methodInstructions.subList(index, index + compiledInstructions.size - externalLabels.size)
|
||||||
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
|
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
|
||||||
// If the compiled instruction is not an offset instruction, skip it.
|
// If the compiled instruction is not an offset instruction, skip it.
|
||||||
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
|
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
|
||||||
|
@@ -2,18 +2,6 @@ package app.revanced.patcher.fingerprint.method.annotation
|
|||||||
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 [MethodFingerprint] was created for.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class MatchingMethod(
|
|
||||||
val definingClass: String = "L<unspecified-class>;",
|
|
||||||
val name: String = "<unspecified-method>"
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotations to scan a pattern [MethodFingerprint] 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.
|
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||||
@@ -22,11 +10,4 @@ annotation class MatchingMethod(
|
|||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class FuzzyPatternScanMethod(
|
annotation class FuzzyPatternScanMethod(
|
||||||
val threshold: Int = 1
|
val threshold: Int = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotations to scan a pattern [MethodFingerprint] directly.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class DirectPatternScanMethod
|
|
@@ -1,6 +1,6 @@
|
|||||||
package app.revanced.patcher.fingerprint.method.impl
|
package app.revanced.patcher.fingerprint.method.impl
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
||||||
import app.revanced.patcher.extensions.parametersEqual
|
import app.revanced.patcher.extensions.parametersEqual
|
||||||
@@ -41,78 +41,81 @@ abstract class MethodFingerprint(
|
|||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
* 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.
|
* @param classes The classes on which to resolve the [MethodFingerprint] in.
|
||||||
|
* @param context The [BytecodeContext] to host proxies.
|
||||||
* @return True if the resolution was successful, false otherwise.
|
* @return True if the resolution was successful, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun Iterable<MethodFingerprint>.resolve(forData: BytecodeData, context: Iterable<ClassDef>) {
|
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) {
|
||||||
for (fingerprint in this) // For each fingerprint
|
for (fingerprint in this) // For each fingerprint
|
||||||
classes@ for (classDef in context) // search through all classes for the fingerprint
|
classes@ for (classDef in classes) // search through all classes for the fingerprint
|
||||||
if (fingerprint.resolve(forData, classDef))
|
if (fingerprint.resolve(context, classDef))
|
||||||
break@classes // if the resolution succeeded, continue with the next fingerprint
|
break@classes // if the resolution succeeded, continue with the next fingerprint
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a [MethodFingerprint] against a [ClassDef].
|
* Resolve a [MethodFingerprint] against a [ClassDef].
|
||||||
* @param context The class on which to resolve the [MethodFingerprint].
|
*
|
||||||
* @param forData The [BytecodeData] to host proxies.
|
* @param forClass The class on which to resolve the [MethodFingerprint] in.
|
||||||
|
* @param context The [BytecodeContext] to host proxies.
|
||||||
* @return True if the resolution was successful, false otherwise.
|
* @return True if the resolution was successful, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean {
|
fun MethodFingerprint.resolve(context: BytecodeContext, forClass: ClassDef): Boolean {
|
||||||
for (method in context.methods)
|
for (method in forClass.methods)
|
||||||
if (this.resolve(forData, method, context))
|
if (this.resolve(context, method, forClass))
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a [MethodFingerprint] against a [Method].
|
* 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 method The class on which to resolve the [MethodFingerprint] in.
|
||||||
* @param forData The [BytecodeData] to host proxies.
|
* @param forClass The class on which to resolve the [MethodFingerprint].
|
||||||
|
* @param context The [BytecodeContext] to host proxies.
|
||||||
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
|
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean {
|
fun MethodFingerprint.resolve(context: BytecodeContext, method: Method, forClass: ClassDef): Boolean {
|
||||||
val methodFingerprint = this
|
val methodFingerprint = this
|
||||||
|
|
||||||
if (methodFingerprint.result != null) return true
|
if (methodFingerprint.result != null) return true
|
||||||
|
|
||||||
if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType))
|
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags)
|
if (methodFingerprint.access != null && methodFingerprint.access != method.accessFlags)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
if (methodFingerprint.parameters != null && !parametersEqual(
|
if (methodFingerprint.parameters != null && !parametersEqual(
|
||||||
methodFingerprint.parameters, // TODO: parseParameters()
|
methodFingerprint.parameters, // TODO: parseParameters()
|
||||||
context.parameterTypes
|
method.parameterTypes
|
||||||
)
|
)
|
||||||
) return false
|
) return false
|
||||||
|
|
||||||
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
||||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context))
|
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
val stringsScanResult: StringsScanResult? =
|
val stringsScanResult: StringsScanResult? =
|
||||||
if (methodFingerprint.strings != null) {
|
if (methodFingerprint.strings != null) {
|
||||||
StringsScanResult(
|
StringsScanResult(
|
||||||
buildList {
|
buildList {
|
||||||
val implementation = context.implementation ?: return false
|
val implementation = method.implementation ?: return false
|
||||||
|
|
||||||
val stringsList = methodFingerprint.strings.toMutableList()
|
val stringsList = methodFingerprint.strings.toMutableList()
|
||||||
|
|
||||||
implementation.instructions.forEach { instruction ->
|
implementation.instructions.forEachIndexed { instructionIndex, instruction ->
|
||||||
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEach
|
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEachIndexed
|
||||||
|
|
||||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||||
val index = stringsList.indexOfFirst { it == string }
|
val index = stringsList.indexOfFirst { it == string }
|
||||||
if (index == -1) return@forEach
|
if (index == -1) return@forEachIndexed
|
||||||
|
|
||||||
add(
|
add(
|
||||||
StringMatch(
|
StringMatch(
|
||||||
string,
|
string,
|
||||||
index
|
instructionIndex
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
stringsList.removeAt(index)
|
stringsList.removeAt(index)
|
||||||
@@ -124,19 +127,19 @@ abstract class MethodFingerprint(
|
|||||||
} else null
|
} else null
|
||||||
|
|
||||||
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||||
context.implementation?.instructions ?: return false
|
method.implementation?.instructions ?: return false
|
||||||
|
|
||||||
context.patternScan(methodFingerprint) ?: return false
|
method.patternScan(methodFingerprint) ?: return false
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
methodFingerprint.result = MethodFingerprintResult(
|
methodFingerprint.result = MethodFingerprintResult(
|
||||||
context,
|
method,
|
||||||
classDef,
|
forClass,
|
||||||
MethodFingerprintResult.MethodFingerprintScanResult(
|
MethodFingerprintResult.MethodFingerprintScanResult(
|
||||||
patternScanResult,
|
patternScanResult,
|
||||||
stringsScanResult
|
stringsScanResult
|
||||||
),
|
),
|
||||||
forData
|
context
|
||||||
)
|
)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -215,16 +218,17 @@ private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintS
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the result of a [MethodFingerprintResult].
|
* Represents the result of a [MethodFingerprintResult].
|
||||||
|
*
|
||||||
* @param method The matching method.
|
* @param method The matching method.
|
||||||
* @param classDef The [ClassDef] that contains the matching [method].
|
* @param classDef The [ClassDef] that contains the matching [method].
|
||||||
* @param scanResult The result of scanning for the [MethodFingerprint].
|
* @param scanResult The result of scanning for the [MethodFingerprint].
|
||||||
* @param data The [BytecodeData] this [MethodFingerprintResult] is attached to, to create proxies.
|
* @param context The [BytecodeContext] this [MethodFingerprintResult] is attached to, to create proxies.
|
||||||
*/
|
*/
|
||||||
data class MethodFingerprintResult(
|
data class MethodFingerprintResult(
|
||||||
val method: Method,
|
val method: Method,
|
||||||
val classDef: ClassDef,
|
val classDef: ClassDef,
|
||||||
val scanResult: MethodFingerprintScanResult,
|
val scanResult: MethodFingerprintScanResult,
|
||||||
internal val data: BytecodeData
|
internal val context: BytecodeContext
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -283,7 +287,7 @@ data class MethodFingerprintResult(
|
|||||||
* Use [classDef] where possible.
|
* Use [classDef] where possible.
|
||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
val mutableClass by lazy { data.proxy(classDef).resolve() }
|
val mutableClass by lazy { context.proxy(classDef).mutableClass }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a mutable clone of [method]
|
* Returns a mutable clone of [method]
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container for patch options.
|
||||||
|
*/
|
||||||
|
abstract class OptionsContainer {
|
||||||
|
/**
|
||||||
|
* A list of [PatchOption]s.
|
||||||
|
* @see PatchOptions
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
val options = PatchOptions()
|
||||||
|
|
||||||
|
protected fun <T> option(opt: PatchOption<T>): PatchOption<T> {
|
||||||
|
options.register(opt)
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
}
|
@@ -1,34 +1,44 @@
|
|||||||
package app.revanced.patcher.patch
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
import app.revanced.patcher.data.Context
|
||||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
import app.revanced.patcher.data.ResourceContext
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ReVanced patch.
|
* 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.
|
* If it implements [Closeable], it will be closed after all patches have been executed.
|
||||||
* Closing will be done in reverse execution order.
|
* Closing will be done in reverse execution order.
|
||||||
*/
|
*/
|
||||||
abstract class Patch<out T : Data> {
|
sealed interface Patch<out T : Context> : Closeable {
|
||||||
/**
|
/**
|
||||||
* The main function of the [Patch] which the patcher will call.
|
* The main function of the [Patch] which the patcher will call.
|
||||||
|
*
|
||||||
|
* @param context The [Context] the patch will work on.
|
||||||
|
* @return The result of executing the patch.
|
||||||
*/
|
*/
|
||||||
abstract fun execute(data: @UnsafeVariance T): PatchResult
|
fun execute(context: @UnsafeVariance T): PatchResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The closing function for this patch.
|
||||||
|
*
|
||||||
|
* This can be treated like popping the patch from the current patch stack.
|
||||||
|
*/
|
||||||
|
override fun close() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class OptionsContainer {
|
/**
|
||||||
/**
|
* Resource patch for the Patcher.
|
||||||
* A list of [PatchOption]s.
|
*/
|
||||||
* @see PatchOptions
|
interface ResourcePatch : Patch<ResourceContext>
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
|
||||||
val options = PatchOptions()
|
|
||||||
|
|
||||||
protected fun <T> option(opt: PatchOption<T>): PatchOption<T> {
|
/**
|
||||||
options.register(opt)
|
* Bytecode patch for the Patcher.
|
||||||
return opt
|
*
|
||||||
}
|
* @param fingerprints A list of [MethodFingerprint] this patch relies on.
|
||||||
}
|
*/
|
||||||
|
abstract class BytecodePatch(
|
||||||
|
internal val fingerprints: Iterable<MethodFingerprint>? = null
|
||||||
|
) : Patch<BytecodeContext>
|
@@ -1,6 +1,6 @@
|
|||||||
package app.revanced.patcher.patch.annotations
|
package app.revanced.patcher.patch.annotations
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
import app.revanced.patcher.data.Context
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@@ -20,5 +20,5 @@ annotation class Patch(val include: Boolean = true)
|
|||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@MustBeDocumented
|
@MustBeDocumented
|
||||||
annotation class DependsOn(
|
annotation class DependsOn(
|
||||||
val dependencies: Array<KClass<out Patch<Data>>> = []
|
val dependencies: Array<KClass<out Patch<Context>>> = []
|
||||||
)
|
)
|
@@ -1,13 +0,0 @@
|
|||||||
package app.revanced.patcher.patch.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bytecode patch for the Patcher.
|
|
||||||
* @param fingerprints A list of [MethodFingerprint] this patch relies on.
|
|
||||||
*/
|
|
||||||
abstract class BytecodePatch(
|
|
||||||
internal val fingerprints: Iterable<MethodFingerprint>? = null
|
|
||||||
) : Patch<BytecodeData>()
|
|
@@ -1,9 +0,0 @@
|
|||||||
package app.revanced.patcher.patch.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.ResourceData
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource patch for the Patcher.
|
|
||||||
*/
|
|
||||||
abstract class ResourcePatch : Patch<ResourceData>()
|
|
@@ -3,40 +3,44 @@ package app.revanced.patcher.util
|
|||||||
import app.revanced.patcher.util.proxy.ClassProxy
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
|
|
||||||
class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> {
|
/**
|
||||||
private val internalProxies = mutableListOf<ClassProxy>()
|
* A class that represents a set of classes and proxies.
|
||||||
internal val proxies: List<ClassProxy> = internalProxies
|
*
|
||||||
|
* @param classes The classes to be backed by proxies.
|
||||||
fun add(classDef: ClassDef) = internalClasses.add(classDef)
|
*/
|
||||||
fun add(classProxy: ClassProxy) = internalProxies.add(classProxy)
|
class ProxyBackedClassList(internal val classes: MutableList<ClassDef>) : Set<ClassDef> {
|
||||||
|
internal val proxies = mutableListOf<ClassProxy>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply all resolved classes into [internalClasses] and clean the [proxies] list.
|
* Add a [ClassDef].
|
||||||
*/
|
*/
|
||||||
internal fun applyProxies() {
|
fun add(classDef: ClassDef) = classes.add(classDef)
|
||||||
// FIXME: check if this could cause issues when multiple patches use the same proxy
|
|
||||||
internalProxies.removeIf { proxy ->
|
|
||||||
// if the proxy is unused, keep it in the list
|
|
||||||
if (!proxy.proxyUsed) return@removeIf false
|
|
||||||
|
|
||||||
// if it has been used, replace the internal class which it proxied
|
/**
|
||||||
val index = internalClasses.indexOfFirst { it.type == proxy.immutableClass.type }
|
* Add a [ClassProxy].
|
||||||
internalClasses[index] = proxy.mutatedClass
|
*/
|
||||||
|
fun add(classProxy: ClassProxy) = proxies.add(classProxy)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace all classes with their mutated versions.
|
||||||
|
*/
|
||||||
|
internal fun replaceClasses() =
|
||||||
|
proxies.removeIf { proxy ->
|
||||||
|
// if the proxy is unused, keep it in the list
|
||||||
|
if (!proxy.resolved) return@removeIf false
|
||||||
|
|
||||||
|
// if it has been used, replace the original class with the new class
|
||||||
|
val index = classes.indexOfFirst { it.type == proxy.immutableClass.type }
|
||||||
|
classes[index] = proxy.mutableClass
|
||||||
|
|
||||||
// return true to remove it from the proxies list
|
// return true to remove it from the proxies list
|
||||||
return@removeIf true
|
return@removeIf true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override val size get() = internalClasses.size
|
|
||||||
override fun contains(element: ClassDef) = internalClasses.contains(element)
|
override val size get() = classes.size
|
||||||
override fun containsAll(elements: Collection<ClassDef>) = internalClasses.containsAll(elements)
|
override fun contains(element: ClassDef) = classes.contains(element)
|
||||||
override fun get(index: Int) = internalClasses[index]
|
override fun containsAll(elements: Collection<ClassDef>) = classes.containsAll(elements)
|
||||||
override fun indexOf(element: ClassDef) = internalClasses.indexOf(element)
|
override fun isEmpty() = classes.isEmpty()
|
||||||
override fun isEmpty() = internalClasses.isEmpty()
|
override fun iterator() = classes.iterator()
|
||||||
override fun iterator() = internalClasses.iterator()
|
|
||||||
override fun lastIndexOf(element: ClassDef) = internalClasses.lastIndexOf(element)
|
|
||||||
override fun listIterator() = internalClasses.listIterator()
|
|
||||||
override fun listIterator(index: Int) = internalClasses.listIterator(index)
|
|
||||||
override fun subList(fromIndex: Int, toIndex: Int) = internalClasses.subList(fromIndex, toIndex)
|
|
||||||
}
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
package app.revanced.patcher.util.method
|
package app.revanced.patcher.util.method
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.data.impl.MethodNotFoundException
|
|
||||||
import app.revanced.patcher.extensions.softCompareTo
|
import app.revanced.patcher.extensions.softCompareTo
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
import org.jf.dexlib2.iface.Method
|
import org.jf.dexlib2.iface.Method
|
||||||
@@ -10,16 +9,19 @@ import org.jf.dexlib2.iface.reference.MethodReference
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a method from another method via instruction offsets.
|
* Find a method from another method via instruction offsets.
|
||||||
* @param bytecodeData The bytecodeData to use when resolving the next method reference.
|
* @param bytecodeContext The context to use when resolving the next method reference.
|
||||||
* @param currentMethod The method to start from.
|
* @param currentMethod The method to start from.
|
||||||
*/
|
*/
|
||||||
class MethodWalker internal constructor(
|
class MethodWalker internal constructor(
|
||||||
private val bytecodeData: BytecodeData,
|
private val bytecodeContext: BytecodeContext,
|
||||||
private var currentMethod: Method
|
private var currentMethod: Method
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Get the method which was walked last.
|
* Get the method which was walked last.
|
||||||
|
*
|
||||||
* It is possible to cast this method to a [MutableMethod], if the method has been walked mutably.
|
* It is possible to cast this method to a [MutableMethod], if the method has been walked mutably.
|
||||||
|
*
|
||||||
|
* @return The method which was walked last.
|
||||||
*/
|
*/
|
||||||
fun getMethod(): Method {
|
fun getMethod(): Method {
|
||||||
return currentMethod
|
return currentMethod
|
||||||
@@ -27,18 +29,21 @@ class MethodWalker internal constructor(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Walk to a method defined at the offset in the instruction list of the current method.
|
* Walk to a method defined at the offset in the instruction list of the current method.
|
||||||
|
*
|
||||||
|
* The current method will be mutable.
|
||||||
|
*
|
||||||
* @param offset The offset of the instruction. This instruction must be of format 35c.
|
* @param offset The offset of the instruction. This instruction must be of format 35c.
|
||||||
* @param walkMutable If this is true, the class of the method will be resolved mutably.
|
* @param walkMutable If this is true, the class of the method will be resolved mutably.
|
||||||
* The current method will be mutable.
|
* @return The same [MethodWalker] instance with the method at [offset].
|
||||||
*/
|
*/
|
||||||
fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
||||||
currentMethod.implementation?.instructions?.let { instructions ->
|
currentMethod.implementation?.instructions?.let { instructions ->
|
||||||
val instruction = instructions.elementAt(offset)
|
val instruction = instructions.elementAt(offset)
|
||||||
|
|
||||||
val newMethod = (instruction as ReferenceInstruction).reference as MethodReference
|
val newMethod = (instruction as ReferenceInstruction).reference as MethodReference
|
||||||
val proxy = bytecodeData.findClass(newMethod.definingClass)!!
|
val proxy = bytecodeContext.findClass(newMethod.definingClass)!!
|
||||||
|
|
||||||
val methods = if (walkMutable) proxy.resolve().methods else proxy.immutableClass.methods
|
val methods = if (walkMutable) proxy.mutableClass.methods else proxy.immutableClass.methods
|
||||||
currentMethod = methods.first { it ->
|
currentMethod = methods.first { it ->
|
||||||
return@first it.softCompareTo(newMethod)
|
return@first it.softCompareTo(newMethod)
|
||||||
}
|
}
|
||||||
@@ -47,5 +52,5 @@ class MethodWalker internal constructor(
|
|||||||
throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
|
throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class MethodNotFoundException(exception: String) : Exception(exception)
|
||||||
}
|
}
|
@@ -1,18 +1,74 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package app.revanced.patcher.util.patch
|
package app.revanced.patcher.util.patch
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
import app.revanced.patcher.data.Context
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import org.jf.dexlib2.DexFileFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.util.jar.JarFile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A patch bundle.
|
||||||
|
|
||||||
* @param path The path to the patch bundle.
|
* @param path The path to the patch bundle.
|
||||||
*/
|
*/
|
||||||
abstract class PatchBundle(path: String) : File(path) {
|
sealed class PatchBundle(path: String) : File(path) {
|
||||||
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
||||||
for (className in classNames) {
|
for (className in classNames) {
|
||||||
val clazz = classLoader.loadClass(className)
|
val clazz = classLoader.loadClass(className)
|
||||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) continue
|
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) continue
|
||||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
|
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A patch bundle of type [Jar].
|
||||||
|
*
|
||||||
|
* @param patchBundlePath The path to the patch bundle.
|
||||||
|
*/
|
||||||
|
class Jar(patchBundlePath: String) : PatchBundle(patchBundlePath) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load patches from the patch bundle.
|
||||||
|
*
|
||||||
|
* Patches will be loaded with a new [URLClassLoader].
|
||||||
|
*/
|
||||||
|
fun loadPatches() = loadPatches(
|
||||||
|
URLClassLoader(
|
||||||
|
arrayOf(this.toURI().toURL()),
|
||||||
|
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
||||||
|
),
|
||||||
|
StringIterator(
|
||||||
|
JarFile(this)
|
||||||
|
.entries()
|
||||||
|
.toList() // TODO: find a cleaner solution than that to filter non class files
|
||||||
|
.filter {
|
||||||
|
it.name.endsWith(".class") && !it.name.contains("$")
|
||||||
|
}
|
||||||
|
.iterator()
|
||||||
|
) {
|
||||||
|
it.realName.replace('/', '.').replace(".class", "")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A patch bundle of type [Dex] format.
|
||||||
|
*
|
||||||
|
* @param patchBundlePath The path to a patch bundle of dex format.
|
||||||
|
* @param dexClassLoader The dex class loader.
|
||||||
|
*/
|
||||||
|
class Dex(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
||||||
|
/**
|
||||||
|
* Load patches from the patch bundle.
|
||||||
|
*
|
||||||
|
* Patches will be loaded to the provided [dexClassLoader].
|
||||||
|
*/
|
||||||
|
fun loadPatches() = loadPatches(dexClassLoader,
|
||||||
|
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
||||||
|
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,17 +0,0 @@
|
|||||||
package app.revanced.patcher.util.patch.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.util.patch.PatchBundle
|
|
||||||
import app.revanced.patcher.util.patch.StringIterator
|
|
||||||
import org.jf.dexlib2.DexFileFactory
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A patch bundle of the ReVanced [DexPatchBundle] format.
|
|
||||||
* @param patchBundlePath The path to a patch bundle of dex format.
|
|
||||||
* @param dexClassLoader The dex class loader.
|
|
||||||
*/
|
|
||||||
class DexPatchBundle(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
|
||||||
fun loadPatches() = loadPatches(dexClassLoader,
|
|
||||||
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
|
||||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
|
||||||
})
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
package app.revanced.patcher.util.patch.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.util.patch.PatchBundle
|
|
||||||
import app.revanced.patcher.util.patch.StringIterator
|
|
||||||
import java.net.URLClassLoader
|
|
||||||
import java.util.jar.JarFile
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A patch bundle of the ReVanced [JarPatchBundle] format.
|
|
||||||
* @param patchBundlePath The path to the patch bundle.
|
|
||||||
*/
|
|
||||||
class JarPatchBundle(patchBundlePath: String) : PatchBundle(patchBundlePath) {
|
|
||||||
fun loadPatches() = loadPatches(
|
|
||||||
URLClassLoader(
|
|
||||||
arrayOf(this.toURI().toURL()),
|
|
||||||
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
|
||||||
),
|
|
||||||
StringIterator(
|
|
||||||
JarFile(this)
|
|
||||||
.entries()
|
|
||||||
.toList() // TODO: find a cleaner solution than that to filter non class files
|
|
||||||
.filter {
|
|
||||||
it.name.endsWith(".class") && !it.name.contains("$")
|
|
||||||
}
|
|
||||||
.iterator()
|
|
||||||
) {
|
|
||||||
it.realName.replace('/', '.').replace(".class", "")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
@@ -8,34 +8,26 @@ import org.jf.dexlib2.iface.ClassDef
|
|||||||
*
|
*
|
||||||
* A class proxy simply holds a reference to the original class
|
* A class proxy simply holds a reference to the original class
|
||||||
* and allocates a mutable clone for the original class if needed.
|
* and allocates a mutable clone for the original class if needed.
|
||||||
* @param immutableClass The class to proxy
|
* @param immutableClass The class to proxy.
|
||||||
*/
|
*/
|
||||||
class ClassProxy(
|
class ClassProxy internal constructor(
|
||||||
val immutableClass: ClassDef,
|
val immutableClass: ClassDef,
|
||||||
) {
|
) {
|
||||||
internal var proxyUsed = false
|
/**
|
||||||
internal lateinit var mutatedClass: MutableClass
|
* Weather the proxy was actually used.
|
||||||
|
*/
|
||||||
init {
|
internal var resolved = false
|
||||||
// in the instance, that a [MutableClass] is being proxied,
|
|
||||||
// do not create an additional clone and reuse the [MutableClass] instance
|
|
||||||
if (immutableClass is MutableClass) {
|
|
||||||
mutatedClass = immutableClass
|
|
||||||
proxyUsed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates and returns a mutable clone of the original class.
|
* The mutable clone of the original class.
|
||||||
* A patch should always use the original immutable class reference
|
*
|
||||||
* to avoid unnecessary allocations for the mutable class.
|
* Note: This is only allocated if the proxy is actually used.
|
||||||
* @return A mutable clone of the original class.
|
|
||||||
*/
|
*/
|
||||||
fun resolve(): MutableClass {
|
val mutableClass by lazy {
|
||||||
if (!proxyUsed) {
|
resolved = true
|
||||||
proxyUsed = true
|
if (immutableClass is MutableClass) {
|
||||||
mutatedClass = MutableClass(immutableClass)
|
immutableClass
|
||||||
}
|
} else
|
||||||
return mutatedClass
|
MutableClass(immutableClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,18 +3,15 @@ package app.revanced.patcher.usage.bytecode
|
|||||||
import app.revanced.patcher.annotation.Description
|
import app.revanced.patcher.annotation.Description
|
||||||
import app.revanced.patcher.annotation.Name
|
import app.revanced.patcher.annotation.Name
|
||||||
import app.revanced.patcher.annotation.Version
|
import app.revanced.patcher.annotation.Version
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.addInstructions
|
import app.revanced.patcher.extensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.or
|
import app.revanced.patcher.extensions.or
|
||||||
import app.revanced.patcher.extensions.replaceInstruction
|
import app.revanced.patcher.extensions.replaceInstruction
|
||||||
import app.revanced.patcher.patch.OptionsContainer
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patcher.patch.PatchOption
|
|
||||||
import app.revanced.patcher.patch.PatchResult
|
|
||||||
import app.revanced.patcher.patch.PatchResultSuccess
|
|
||||||
import app.revanced.patcher.patch.annotations.DependsOn
|
import app.revanced.patcher.patch.annotations.DependsOn
|
||||||
import app.revanced.patcher.patch.annotations.Patch
|
import app.revanced.patcher.patch.annotations.Patch
|
||||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
|
||||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||||
|
import app.revanced.patcher.usage.resource.patch.ExampleResourcePatch
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
@@ -39,11 +36,11 @@ import kotlin.io.path.Path
|
|||||||
@Description("Example demonstration of a bytecode patch.")
|
@Description("Example demonstration of a bytecode patch.")
|
||||||
@ExampleResourceCompatibility
|
@ExampleResourceCompatibility
|
||||||
@Version("0.0.1")
|
@Version("0.0.1")
|
||||||
@DependsOn([ExampleBytecodePatch::class])
|
@DependsOn([ExampleResourcePatch::class])
|
||||||
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||||
// This function will be executed by the patcher.
|
// This function will be executed by the patcher.
|
||||||
// You can treat it as a constructor
|
// You can treat it as a constructor
|
||||||
override fun execute(data: BytecodeData): PatchResult {
|
override fun execute(context: BytecodeContext): PatchResult {
|
||||||
// Get the resolved method by its fingerprint from the resolver cache
|
// Get the resolved method by its fingerprint from the resolver cache
|
||||||
val result = ExampleFingerprint.result!!
|
val result = ExampleFingerprint.result!!
|
||||||
|
|
||||||
@@ -63,9 +60,9 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
|||||||
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
||||||
|
|
||||||
// Get the class in which the method matching our fingerprint is defined in.
|
// Get the class in which the method matching our fingerprint is defined in.
|
||||||
val mainClass = data.findClass {
|
val mainClass = context.findClass {
|
||||||
it.type == result.classDef.type
|
it.type == result.classDef.type
|
||||||
}!!.resolve()
|
}!!.mutableClass
|
||||||
|
|
||||||
// Add a new method returning a string
|
// Add a new method returning a string
|
||||||
mainClass.methods.add(
|
mainClass.methods.add(
|
||||||
@@ -169,6 +166,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
companion object : OptionsContainer() {
|
companion object : OptionsContainer() {
|
||||||
private var key1 by option(
|
private var key1 by option(
|
||||||
PatchOption.StringOption(
|
PatchOption.StringOption(
|
||||||
|
@@ -4,17 +4,11 @@ import app.revanced.patcher.annotation.Name
|
|||||||
import app.revanced.patcher.annotation.Version
|
import app.revanced.patcher.annotation.Version
|
||||||
import app.revanced.patcher.extensions.or
|
import app.revanced.patcher.extensions.or
|
||||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||||
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
import app.revanced.patcher.usage.bytecode.ExampleBytecodeCompatibility
|
|
||||||
import org.jf.dexlib2.AccessFlags
|
import org.jf.dexlib2.AccessFlags
|
||||||
import org.jf.dexlib2.Opcode
|
import org.jf.dexlib2.Opcode
|
||||||
|
|
||||||
@Name("example-fingerprint")
|
@Name("example-fingerprint")
|
||||||
@MatchingMethod(
|
|
||||||
"LexampleClass;",
|
|
||||||
"exampleMehod"
|
|
||||||
)
|
|
||||||
@FuzzyPatternScanMethod(2)
|
@FuzzyPatternScanMethod(2)
|
||||||
@ExampleBytecodeCompatibility
|
@ExampleBytecodeCompatibility
|
||||||
@Version("0.0.1")
|
@Version("0.0.1")
|
||||||
|
@@ -3,11 +3,11 @@ package app.revanced.patcher.usage.resource.patch
|
|||||||
import app.revanced.patcher.annotation.Description
|
import app.revanced.patcher.annotation.Description
|
||||||
import app.revanced.patcher.annotation.Name
|
import app.revanced.patcher.annotation.Name
|
||||||
import app.revanced.patcher.annotation.Version
|
import app.revanced.patcher.annotation.Version
|
||||||
import app.revanced.patcher.data.impl.ResourceData
|
import app.revanced.patcher.data.ResourceContext
|
||||||
import app.revanced.patcher.patch.PatchResult
|
import app.revanced.patcher.patch.PatchResult
|
||||||
import app.revanced.patcher.patch.PatchResultSuccess
|
import app.revanced.patcher.patch.PatchResultSuccess
|
||||||
|
import app.revanced.patcher.patch.ResourcePatch
|
||||||
import app.revanced.patcher.patch.annotations.Patch
|
import app.revanced.patcher.patch.annotations.Patch
|
||||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
|
||||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
@@ -16,9 +16,9 @@ import org.w3c.dom.Element
|
|||||||
@Description("Example demonstration of a resource patch.")
|
@Description("Example demonstration of a resource patch.")
|
||||||
@ExampleResourceCompatibility
|
@ExampleResourceCompatibility
|
||||||
@Version("0.0.1")
|
@Version("0.0.1")
|
||||||
class ExampleResourcePatch : ResourcePatch() {
|
class ExampleResourcePatch : ResourcePatch {
|
||||||
override fun execute(data: ResourceData): PatchResult {
|
override fun execute(context: ResourceContext): PatchResult {
|
||||||
data.xmlEditor["AndroidManifest.xml"].use { editor ->
|
context.xmlEditor["AndroidManifest.xml"].use { editor ->
|
||||||
val element = editor // regular DomFileEditor
|
val element = editor // regular DomFileEditor
|
||||||
.file
|
.file
|
||||||
.getElementsByTagName("application")
|
.getElementsByTagName("application")
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user