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
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d2d93cd075 | ||
![]() |
26b8621ac8 | ||
![]() |
f365a41741 | ||
![]() |
9ec720e983 | ||
![]() |
0f432b3fdd | ||
![]() |
96cd5618dd | ||
![]() |
c2a5a55e67 | ||
![]() |
6c5de8b414 | ||
![]() |
ea773cfa56 | ||
![]() |
a306561b55 | ||
![]() |
b6dcd88495 | ||
![]() |
a925650044 | ||
![]() |
77bbf6be1f | ||
![]() |
bd053b7e99 | ||
![]() |
fd742eba63 | ||
![]() |
ba9d998681 | ||
![]() |
75df245ec3 | ||
![]() |
4164cb0dea | ||
![]() |
18fe35ae73 | ||
![]() |
f9bc95f220 | ||
![]() |
d2f91a8545 | ||
![]() |
4016bdc37f | ||
![]() |
538b2a8599 | ||
![]() |
4aa14bbb85 | ||
![]() |
d37452997b | ||
![]() |
db21d5e953 | ||
![]() |
4d581811db | ||
![]() |
8c502448be | ||
![]() |
fec16d9442 | ||
![]() |
5583904994 | ||
![]() |
797286b758 | ||
![]() |
4ae9ad09d6 | ||
![]() |
447e1ad30e | ||
![]() |
843e62ad29 | ||
![]() |
9c07ffcc7a | ||
![]() |
438321330e | ||
![]() |
3ba4be240b | ||
![]() |
98ce0abfa9 | ||
![]() |
db4348c4fa |
24
.github/workflows/pull_request.yml
vendored
Normal file
24
.github/workflows/pull_request.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: PR to main
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
MESSAGE: merge branch \`${{ github.head_ref || github.ref_name }}\` to \`main\`
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pull-request:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Open pull request
|
||||||
|
uses: repo-sync/pull-request@v2
|
||||||
|
with:
|
||||||
|
destination_branch: 'main'
|
||||||
|
pr_title: 'chore: ${{ env.MESSAGE }}'
|
||||||
|
pr_body: 'This pull request will ${{ env.MESSAGE }}.'
|
||||||
|
pr_draft: true
|
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -15,25 +15,26 @@ 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: 'zulu'
|
||||||
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: "latest"
|
||||||
|
cache: 'npm'
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew build
|
run: ./gradlew build --no-daemon
|
||||||
- name: Setup semantic-release
|
- name: Setup semantic-release
|
||||||
run: npm install -g semantic-release @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin -D
|
run: npm install semantic-release @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin -D
|
||||||
- name: Release
|
- name: Release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -115,3 +115,6 @@ gradle-app.setting
|
|||||||
|
|
||||||
# Avoid ignoring test resources
|
# Avoid ignoring test resources
|
||||||
!src/test/resources/*
|
!src/test/resources/*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
101
CHANGELOG.md
101
CHANGELOG.md
@@ -1,3 +1,104 @@
|
|||||||
|
## [6.3.1](https://github.com/revanced/revanced-patcher/compare/v6.3.0...v6.3.1) (2022-12-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* publicize types when merging files if necessary ([#137](https://github.com/revanced/revanced-patcher/issues/137)) ([9ec720e](https://github.com/revanced/revanced-patcher/commit/9ec720e983785d8b1dde330cc0e0e0f914c1803c))
|
||||||
|
|
||||||
|
## [6.3.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.0...v6.3.1-dev.1) (2022-12-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* publicize types when merging files if necessary ([#137](https://github.com/revanced/revanced-patcher/issues/137)) ([9ec720e](https://github.com/revanced/revanced-patcher/commit/9ec720e983785d8b1dde330cc0e0e0f914c1803c))
|
||||||
|
|
||||||
|
# [6.3.0](https://github.com/revanced/revanced-patcher/compare/v6.2.0...v6.3.0) (2022-12-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* sort patches in lexicographical order ([a306561](https://github.com/revanced/revanced-patcher/commit/a306561b55ac848792046378f582a036f7ffab03)), closes [#125](https://github.com/revanced/revanced-patcher/issues/125)
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
### 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)
|
## [5.0.1](https://github.com/revanced/revanced-patcher/compare/v5.0.0...v5.0.1) (2022-09-23)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.0.1
|
version = 6.3.1
|
||||||
|
6107
package-lock.json
generated
Normal file
6107
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
package.json
Normal file
8
package.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@semantic-release/changelog": "^6.0.2",
|
||||||
|
"@semantic-release/git": "^10.0.1",
|
||||||
|
"gradle-semantic-release-plugin": "^1.7.4",
|
||||||
|
"semantic-release": "^19.0.5"
|
||||||
|
}
|
||||||
|
}
|
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,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)
|
||||||
|
@@ -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.
|
|
||||||
)
|
|
@@ -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)
|
||||||
|
@@ -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,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
|
||||||
}
|
}
|
@@ -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
|
||||||
|
@@ -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,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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)
|
|
||||||
}
|
}
|
19
src/main/kotlin/app/revanced/patcher/util/TypeUtil.kt
Normal file
19
src/main/kotlin/app/revanced/patcher/util/TypeUtil.kt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||||
|
|
||||||
|
object TypeUtil {
|
||||||
|
/**
|
||||||
|
* traverse the class hierarchy starting from the given root class
|
||||||
|
*
|
||||||
|
* @param targetClass the class to start traversing the class hierarchy from
|
||||||
|
* @param callback function that is called for every class in the hierarchy
|
||||||
|
*/
|
||||||
|
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
|
||||||
|
callback(targetClass)
|
||||||
|
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
|
||||||
|
traverseClassHierarchy(it, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
||||||
}
|
}
|
@@ -1,18 +1,75 @@
|
|||||||
|
@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.extensions.PatchExtensions.patchName
|
||||||
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) {
|
classNames.forEach { className ->
|
||||||
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)) return@forEach
|
||||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
|
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
||||||
}
|
}
|
||||||
|
}.sortedBy { it.patchName }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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('/', '.')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user