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

Compare commits

..

27 Commits

Author SHA1 Message Date
semantic-release-bot
b6dcd88495 chore(release): 6.2.0 [skip ci]
# [6.2.0](https://github.com/revanced/revanced-patcher/compare/v6.1.1...v6.2.0) (2022-12-02)

### Features

* merge classes on addition ([#127](https://github.com/revanced/revanced-patcher/issues/127)) ([a925650](a925650044))
2022-12-02 01:33:08 +00:00
oSumAtrIX
a925650044 feat: merge classes on addition (#127) 2022-12-02 02:31:47 +01:00
semantic-release-bot
77bbf6be1f chore(release): 6.1.1 [skip ci]
## [6.1.1](https://github.com/revanced/revanced-patcher/compare/v6.1.0...v6.1.1) (2022-11-25)

### Bug Fixes

* use `MethodUtil.methodSignaturesMatch` instead of `Method.softCompareTo` ([bd053b7](bd053b7e99))
2022-11-25 09:25:48 +00:00
oSumAtrIX
bd053b7e99 fix: use MethodUtil.methodSignaturesMatch instead of Method.softCompareTo 2022-11-25 10:24:13 +01:00
semantic-release-bot
fd742eba63 chore(release): 6.1.0 [skip ci]
# [6.1.0](https://github.com/revanced/revanced-patcher/compare/v6.0.2...v6.1.0) (2022-11-22)

### Features

* apply changes from ReVanced Patcher ([ba9d998](ba9d998681))
2022-11-22 23:30:33 +00:00
oSumAtrIX
ba9d998681 feat: apply changes from ReVanced Patcher
BREAKING-CHANGE: Some annotations have been removed regarding fingerprints and patches.
2022-11-23 00:29:12 +01:00
semantic-release-bot
75df245ec3 chore(release): 6.0.2 [skip ci]
## [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](4164cb0dea))
2022-11-18 01:20:19 +00:00
oSumAtrIX
4164cb0dea fix: fallback to patch class name instead of java.lang.Class class name 2022-11-18 02:14:20 +01:00
semantic-release-bot
18fe35ae73 chore(release): 6.0.1 [skip ci]
## [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](f9bc95f220))
2022-11-14 15:59:22 +00:00
MewtR
f9bc95f220 fix: remove unnecessary dummy nop instructions (#111) 2022-11-14 16:57:13 +01:00
Nico Mexis
d2f91a8545 build: update workflow actions (#121) [skip ci] 2022-11-05 15:27:56 +01:00
semantic-release-bot
4016bdc37f chore(release): 6.0.0 [skip ci]
# [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](4aa14bbb85))

### Features

* remove unused annotation `DirectPatternScanMethod` ([538b2a8](538b2a8599))
* remove unused annotation `SincePatcher` ([4ae9ad0](4ae9ad09d6))
* remove unused extension `dependsOn` ([797286b](797286b758))
* remove unused patch extensions ([5583904](5583904994))

### 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
2022-10-05 02:11:19 +00:00
oSumAtrIX
538b2a8599 feat: remove unused annotation DirectPatternScanMethod 2022-10-05 03:57:40 +02:00
oSumAtrIX
4aa14bbb85 refactor: improve structuring of classes and their implementations
BREAKING CHANGE: various changes in which packages classes previously where and their implementation
2022-10-05 03:57:40 +02:00
oSumAtrIX
d37452997b refactor: move OptionsContainer to own class file
BREAKING-CHANGE: this changes the package
2022-10-04 08:32:50 +02:00
oSumAtrIX
db21d5e953 refactor: remove unused import 2022-10-04 08:30:17 +02:00
oSumAtrIX
4d581811db feat!: seal abstract class Patch as an interface 2022-10-04 08:30:17 +02:00
oSumAtrIX
8c502448be feat!: remove unused annotation MatchingMethod 2022-10-04 08:30:16 +02:00
oSumAtrIX
fec16d9442 refactor: remove line break 2022-10-04 08:18:52 +02:00
oSumAtrIX
5583904994 feat: remove unused patch extensions
BREAKING CHANGE: These extensions do not exist anymore and any use should be removed
2022-10-04 08:18:43 +02:00
oSumAtrIX
797286b758 feat: remove unused extension dependsOn
BREAKING CHANGE: The extension does not exist anymore and any use should be removed
2022-10-04 08:18:09 +02:00
oSumAtrIX
4ae9ad09d6 feat: remove unused annotation SincePatcher
BREAKING CHANGE: The annotation does not exist anymore and any use should be removed
2022-10-04 08:16:58 +02:00
semantic-release-bot
447e1ad30e chore(release): 5.1.2 [skip ci]
## [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](9c07ffcc7a))
* use instruction index instead of strings list index for `StringMatch` ([843e62a](843e62ad29))
2022-09-29 19:29:55 +00:00
oSumAtrIX
843e62ad29 fix: use instruction index instead of strings list index for StringMatch 2022-09-29 21:27:56 +02:00
oSumAtrIX
9c07ffcc7a fix: check dependencies for resource patches 2022-09-29 21:27:12 +02:00
semantic-release-bot
438321330e chore(release): 5.1.1 [skip ci]
## [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](3ba4be240b))
2022-09-26 06:59:37 +00:00
oSumAtrIX
3ba4be240b perf: decode resources only when necessary 2022-09-26 08:57:39 +02:00
33 changed files with 756 additions and 676 deletions

View File

@@ -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

View File

@@ -1,3 +1,76 @@
# [6.2.0](https://github.com/revanced/revanced-patcher/compare/v6.1.1...v6.2.0) (2022-12-02)
### Features
* merge classes on addition ([#127](https://github.com/revanced/revanced-patcher/issues/127)) ([a925650](https://github.com/revanced/revanced-patcher/commit/a9256500440f9b4117f1b8813ba0097dafee4ebb))
## [6.1.1](https://github.com/revanced/revanced-patcher/compare/v6.1.0...v6.1.1) (2022-11-25)
### Bug Fixes
* use `MethodUtil.methodSignaturesMatch` instead of `Method.softCompareTo` ([bd053b7](https://github.com/revanced/revanced-patcher/commit/bd053b7e9974c0282d56e6762459db7070452e4a))
# [6.1.0](https://github.com/revanced/revanced-patcher/compare/v6.0.2...v6.1.0) (2022-11-22)
### Features
* apply changes from ReVanced Patcher ([ba9d998](https://github.com/revanced/revanced-patcher/commit/ba9d99868103406fe36b9aa0cfaa0ed5023edfab))
## [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) # [5.1.0](https://github.com/revanced/revanced-patcher/compare/v5.0.1...v5.1.0) (2022-09-26)

View File

@@ -1,2 +1,2 @@
kotlin.code.style = official kotlin.code.style = official
version = 5.1.0 version = 6.2.0

File diff suppressed because it is too large Load Diff

View 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)
}

View File

@@ -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))
}

View File

@@ -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

View File

@@ -1,11 +1,10 @@
package app.revanced.patcher.annotation package app.revanced.patcher.annotation
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
/** /**
* Annotation to constrain a [Patch] or [MethodFingerprint] to compatible packages. * Annotation to constrain a [Patch] to compatible packages.
* @param compatiblePackages A list of packages a [Patch] or [MethodFingerprint] is compatible with. * @param compatiblePackages A list of packages a [Patch] is compatible with.
*/ */
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@@ -17,7 +16,7 @@ annotation class Compatibility(
/** /**
* Annotation to represent packages a patch can be compatible with. * Annotation to represent packages a patch can be compatible with.
* @param name The package identifier name. * @param name The package identifier name.
* @param versions The versions of the package the [Patch] or [MethodFingerprint]is compatible with. * @param versions The versions of the package the [Patch] is compatible with.
*/ */
@Target() @Target()
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)

View File

@@ -1,19 +0,0 @@
package app.revanced.patcher.annotation
import app.revanced.patcher.data.Data
import app.revanced.patcher.patch.Patch
import kotlin.reflect.KClass
/**
* Declares a [Patch] deprecated for removal.
* @param reason The reason why the patch is deprecated.
* @param replacement The replacement for the deprecated patch, if any.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class PatchDeprecated(
val reason: String,
val replacement: KClass<out Patch<Data>> = Patch::class
// Values cannot be nullable in annotations, so this will have to do.
)

View File

@@ -1,11 +1,10 @@
package app.revanced.patcher.annotation package app.revanced.patcher.annotation
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
/** /**
* Annotation to name a [Patch] or [MethodFingerprint]. * Annotation to name a [Patch].
* @param name A suggestive name for the [Patch] or [MethodFingerprint]. * @param name A suggestive name for the [Patch].
*/ */
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@@ -15,8 +14,8 @@ annotation class Name(
) )
/** /**
* Annotation to describe a [Patch] or [MethodFingerprint]. * Annotation to describe a [Patch].
* @param description A description for the [Patch] or [MethodFingerprint]. * @param description A description for the [Patch].
*/ */
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@@ -27,8 +26,8 @@ annotation class Description(
/** /**
* Annotation to version a [Patch] or [MethodFingerprint]. * Annotation to version a [Patch].
* @param version The version of a [Patch] or [MethodFingerprint]. * @param version The version of a [Patch].
*/ */
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)

View File

@@ -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)

View File

@@ -1,6 +1,9 @@
package app.revanced.patcher.data.impl package app.revanced.patcher.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
import org.jf.dexlib2.iface.Method
import org.w3c.dom.Document import org.w3c.dom.Document
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
@@ -11,7 +14,79 @@ import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult import javax.xml.transform.stream.StreamResult
class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<File> { /**
* 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() val xmlEditor = XmlFileHolder()
operator fun get(path: String) = resourceCacheDirectory.resolve(path) operator fun get(path: String) = resourceCacheDirectory.resolve(path)
@@ -23,7 +98,7 @@ class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<Fi
DomFileEditor(inputStream) DomFileEditor(inputStream)
operator fun get(path: String): DomFileEditor { operator fun get(path: String): DomFileEditor {
return DomFileEditor(this@ResourceData[path]) return DomFileEditor(this@ResourceContext[path])
} }
} }
@@ -31,7 +106,9 @@ class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<Fi
/** /**
* Wrapper for a file that can be edited as a dom document. * Wrapper for a file that can be edited as a dom document.
* Note: This constructor does not check for locks to the file when writing. Use the secondary constructor. *
* 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 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. * @param outputStream the output stream to write the xml file to. If null, the file will be read only.
@@ -63,7 +140,8 @@ class DomFileEditor internal constructor(
/** /**
* Closes the editor. Write backs and decreases the lock count. * Closes the editor. Write backs and decreases the lock count.
* Note: Will not write back to the file if the file is still locked. *
* Will not write back to the file if the file is still locked.
*/ */
override fun close() { override fun close() {
if (closed) return if (closed) return

View File

@@ -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

View File

@@ -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
}

View File

@@ -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,40 @@ 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 {
it.reason to it.replacement.let { 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() = 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.fuzzyPatternScanMethod
val MethodFingerprint.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class)
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
val MethodFingerprint.fuzzyScanThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0 get() = fuzzyPatternScanMethod?.threshold ?: 0
} }

View File

@@ -13,7 +13,6 @@ import org.jf.dexlib2.builder.MutableMethodImplementation
import org.jf.dexlib2.builder.instruction.* import org.jf.dexlib2.builder.instruction.*
import org.jf.dexlib2.iface.Method import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.instruction.Instruction import org.jf.dexlib2.iface.instruction.Instruction
import org.jf.dexlib2.iface.reference.MethodReference
import org.jf.dexlib2.immutable.ImmutableMethod import org.jf.dexlib2.immutable.ImmutableMethod
import org.jf.dexlib2.immutable.ImmutableMethodImplementation import org.jf.dexlib2.immutable.ImmutableMethodImplementation
import java.io.OutputStream import java.io.OutputStream
@@ -45,17 +44,6 @@ fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) {
} }
} }
/**
* Compare a method to another, considering name and parameters.
* @param otherMethod The method to compare against.
* @return True if the methods match given the conditions.
*/
fun Method.softCompareTo(otherMethod: MethodReference): Boolean {
return this.name == otherMethod.name && parametersEqual(
this.parameterTypes, otherMethod.parameterTypes
)
}
/** /**
* Clones the method. * Clones the method.
* @param registerCount This parameter allows you to change the register count of the method. * @param registerCount This parameter allows you to change the register count of the method.
@@ -139,10 +127,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

View File

@@ -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.
@@ -23,10 +11,3 @@ annotation class MatchingMethod(
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

View File

@@ -1,10 +1,9 @@
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
import app.revanced.patcher.extensions.softCompareTo
import app.revanced.patcher.fingerprint.Fingerprint import app.revanced.patcher.fingerprint.Fingerprint
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.util.proxy.ClassProxy import app.revanced.patcher.util.proxy.ClassProxy
@@ -14,6 +13,7 @@ import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.instruction.Instruction import org.jf.dexlib2.iface.instruction.Instruction
import org.jf.dexlib2.iface.instruction.ReferenceInstruction import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.iface.reference.StringReference import org.jf.dexlib2.iface.reference.StringReference
import org.jf.dexlib2.util.MethodUtil
/** /**
* Represents the [MethodFingerprint] for a method. * Represents the [MethodFingerprint] for a method.
@@ -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]
@@ -293,7 +297,7 @@ data class MethodFingerprintResult(
*/ */
val mutableMethod by lazy { val mutableMethod by lazy {
mutableClass.methods.first { mutableClass.methods.first {
it.softCompareTo(this.method) MethodUtil.methodSignaturesMatch(it, this.method)
} }
} }
} }

View File

@@ -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
}
}

View File

@@ -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
}
abstract class OptionsContainer {
/** /**
* A list of [PatchOption]s. * The closing function for this patch.
* @see PatchOptions *
* This can be treated like popping the patch from the current patch stack.
*/ */
@Suppress("MemberVisibilityCanBePrivate") override fun close() {}
val options = PatchOptions()
protected fun <T> option(opt: PatchOption<T>): PatchOption<T> {
options.register(opt)
return opt
}
} }
/**
* Resource patch for the Patcher.
*/
interface ResourcePatch : Patch<ResourceContext>
/**
* 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<BytecodeContext>

View File

@@ -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>>> = []
) )

View File

@@ -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>()

View File

@@ -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>()

View File

@@ -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)
} }

View File

@@ -1,25 +1,27 @@
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.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import org.jf.dexlib2.iface.Method import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.instruction.ReferenceInstruction import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.iface.reference.MethodReference import org.jf.dexlib2.iface.reference.MethodReference
import org.jf.dexlib2.util.MethodUtil
/** /**
* 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,25 +29,28 @@ 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 {
return@first it.softCompareTo(newMethod) return@first MethodUtil.methodSignaturesMatch(it, newMethod)
} }
return this return this
} }
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)
} }

View File

@@ -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('/', '.')
})
}
} }

View File

@@ -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('/', '.')
})
}

View File

@@ -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", "")
}
)
}

View File

@@ -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)
} }
} }

View File

@@ -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(

View File

@@ -1,23 +1,11 @@
package app.revanced.patcher.usage.bytecode package app.revanced.patcher.usage.bytecode
import app.revanced.patcher.annotation.Name
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")
@MatchingMethod(
"LexampleClass;",
"exampleMehod"
)
@FuzzyPatternScanMethod(2) @FuzzyPatternScanMethod(2)
@ExampleBytecodeCompatibility
@Version("0.0.1")
object ExampleFingerprint : MethodFingerprint( object ExampleFingerprint : MethodFingerprint(
"V", "V",
AccessFlags.PUBLIC or AccessFlags.STATIC, AccessFlags.PUBLIC or AccessFlags.STATIC,

View File

@@ -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