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
84 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4016bdc37f | ||
![]() |
538b2a8599 | ||
![]() |
4aa14bbb85 | ||
![]() |
d37452997b | ||
![]() |
db21d5e953 | ||
![]() |
4d581811db | ||
![]() |
8c502448be | ||
![]() |
fec16d9442 | ||
![]() |
5583904994 | ||
![]() |
797286b758 | ||
![]() |
4ae9ad09d6 | ||
![]() |
447e1ad30e | ||
![]() |
843e62ad29 | ||
![]() |
9c07ffcc7a | ||
![]() |
438321330e | ||
![]() |
3ba4be240b | ||
![]() |
98ce0abfa9 | ||
![]() |
db4348c4fa | ||
![]() |
4839f87519 | ||
![]() |
809862c997 | ||
![]() |
fd5c878cee | ||
![]() |
124332f0e9 | ||
![]() |
d4cf0cea52 | ||
![]() |
76676fb567 | ||
![]() |
d802ef844e | ||
![]() |
90fc547673 | ||
![]() |
3813e28ac2 | ||
![]() |
a2bb4004c7 | ||
![]() |
a0cb449c60 | ||
![]() |
e0271790b8 | ||
![]() |
4bfd7ebff8 | ||
![]() |
2f7e62ef65 | ||
![]() |
4485af8036 | ||
![]() |
085a3a479d | ||
![]() |
f75c9a78b8 | ||
![]() |
172655bde0 | ||
![]() |
456db7289a | ||
![]() |
e722e3f4f9 | ||
![]() |
c348c1f0a0 | ||
![]() |
ed1851013e | ||
![]() |
e31ac1f132 | ||
![]() |
8f78f85e4a | ||
![]() |
0be2677519 | ||
![]() |
b873228ef0 | ||
![]() |
639ff1c0ba | ||
![]() |
f30671ddd1 | ||
![]() |
76c45dd7c1 | ||
![]() |
1bafb77355 | ||
![]() |
25f74dc5e9 | ||
![]() |
6e73631d4d | ||
![]() |
7761d5b85e | ||
![]() |
62aa295e73 | ||
![]() |
596ede1b12 | ||
![]() |
7debe62738 | ||
![]() |
002f84da1a | ||
![]() |
aff4968e6f | ||
![]() |
1d989abd55 | ||
![]() |
f1775f83d0 | ||
![]() |
4055939c08 | ||
![]() |
85120374d6 | ||
![]() |
8b4819faa1 | ||
![]() |
d219276298 | ||
![]() |
79f91e0e5a | ||
![]() |
fadf62f594 | ||
![]() |
ad3d332e27 | ||
![]() |
8f66df7666 | ||
![]() |
80c2e80925 | ||
![]() |
c3db23d3c7 | ||
![]() |
c28584736e | ||
![]() |
6b909c1ee6 | ||
![]() |
0e8446516e | ||
![]() |
aa46b953db | ||
![]() |
a562e476c0 | ||
![]() |
75d2be8803 | ||
![]() |
d6308e126c | ||
![]() |
bb97af4d86 | ||
![]() |
392164862c | ||
![]() |
53e807dec1 | ||
![]() |
288d50a8b4 | ||
![]() |
131dedd4b0 | ||
![]() |
5a92d5c29d | ||
![]() |
4b81318710 | ||
![]() |
44f6a3ebc5 | ||
![]() |
7882a8d928 |
12
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
12
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
@@ -58,3 +58,15 @@ body:
|
||||
description: Add additional context here.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I filled out all of the requested information in this issue properly.
|
||||
required: true
|
14
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
14
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
@@ -43,4 +43,16 @@ body:
|
||||
label: Additional context
|
||||
description: Add additional context here.
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I filled out all of the requested information in this issue properly.
|
||||
required: true
|
1
.idea/.gitignore
generated
vendored
1
.idea/.gitignore
generated
vendored
@@ -6,3 +6,4 @@
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
/kotlinc.xml
|
||||
|
235
CHANGELOG.md
235
CHANGELOG.md
@@ -1,3 +1,238 @@
|
||||
# [6.0.0](https://github.com/revanced/revanced-patcher/compare/v5.1.2...v6.0.0) (2022-10-05)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* improve structuring of classes and their implementations ([4aa14bb](https://github.com/revanced/revanced-patcher/commit/4aa14bbb858af9253eae9328b759f3298b65a215))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* remove unused annotation `DirectPatternScanMethod` ([538b2a8](https://github.com/revanced/revanced-patcher/commit/538b2a859962570c700362afc88704ed3611aa87))
|
||||
* remove unused annotation `SincePatcher` ([4ae9ad0](https://github.com/revanced/revanced-patcher/commit/4ae9ad09d64a3f69512ccb037f816cb847d7350f))
|
||||
* remove unused extension `dependsOn` ([797286b](https://github.com/revanced/revanced-patcher/commit/797286b7588646272dea2fd35e8e78b0ffb18a0f))
|
||||
* remove unused patch extensions ([5583904](https://github.com/revanced/revanced-patcher/commit/55839049948033ad02414517fd3ba03619216aec))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* various changes in which packages classes previously where and their implementation
|
||||
* These extensions do not exist anymore and any use should be removed
|
||||
* The extension does not exist anymore and any use should be removed
|
||||
* The annotation does not exist anymore and any use should be removed
|
||||
|
||||
## [5.1.2](https://github.com/revanced/revanced-patcher/compare/v5.1.1...v5.1.2) (2022-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check dependencies for resource patches ([9c07ffc](https://github.com/revanced/revanced-patcher/commit/9c07ffcc7af9f088426528561f4321c5cc6b5b15))
|
||||
* use instruction index instead of strings list index for `StringMatch` ([843e62a](https://github.com/revanced/revanced-patcher/commit/843e62ad290ee0a707be9322ee943921da3ea420))
|
||||
|
||||
## [5.1.1](https://github.com/revanced/revanced-patcher/compare/v5.1.0...v5.1.1) (2022-09-26)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* decode resources only when necessary ([3ba4be2](https://github.com/revanced/revanced-patcher/commit/3ba4be240bf0a424e4bbfbaca9605644fda0984e))
|
||||
|
||||
# [5.1.0](https://github.com/revanced/revanced-patcher/compare/v5.0.1...v5.1.0) (2022-09-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* RwLock for opening files in `DomFileEditor` ([db4348c](https://github.com/revanced/revanced-patcher/commit/db4348c4faf51bfe29678baacfbe76ba645ec0b9))
|
||||
|
||||
## [5.0.1](https://github.com/revanced/revanced-patcher/compare/v5.0.0...v5.0.1) (2022-09-23)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* revert breaking changes ([#106](https://github.com/revanced/revanced-patcher/issues/106)) ([124332f](https://github.com/revanced/revanced-patcher/commit/124332f0e9bbdaf4f1aeeb6a31333093eeba1642))
|
||||
|
||||
# [5.0.0](https://github.com/revanced/revanced-patcher/compare/v4.5.0...v5.0.0) (2022-09-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tests:** access `patternScanResult` through `scanResult` ([76676fb](https://github.com/revanced/revanced-patcher/commit/76676fb5673a9e92517ee3a13943cdc98dd5102a))
|
||||
|
||||
|
||||
* refactor!: move utility methods from `MethodFingerprintUtils` `MethodFingerprint` ([d802ef8](https://github.com/revanced/revanced-patcher/commit/d802ef844edf65d4d26328d6ca72e3ddd5a52b15))
|
||||
* feat(fingerprint)!: `StringsScanResult` for `MethodFingerprint` ([3813e28](https://github.com/revanced/revanced-patcher/commit/3813e28ac2ad6710d8d935526ca679e7b1b5980e))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Imports will have to be updated from `MethodFingerprintUtils` to `MethodFingerprint.Companion`.
|
||||
|
||||
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
|
||||
* `MethodFingerprint` now has a field for `MethodFingerprintScanResult`. `MethodFingerprintScanResult` now holds the previous field `MethodFingerprint.patternScanResult`.
|
||||
|
||||
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
|
||||
|
||||
# [4.5.0](https://github.com/revanced/revanced-patcher/compare/v4.4.2...v4.5.0) (2022-09-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* section `acknowledgements` for issue templates ([a0cb449](https://github.com/revanced/revanced-patcher/commit/a0cb449c60310917141e2809abaa16b4174dc002))
|
||||
|
||||
## [4.4.2](https://github.com/revanced/revanced-patcher/compare/v4.4.1...v4.4.2) (2022-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **fingerprint:** do not throw on `MethodFingerprint.result` getter ([2f7e62e](https://github.com/revanced/revanced-patcher/commit/2f7e62ef65422f2c75ef8b09b9cd27076e172b30))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **fingerprint:** do not resolve already resolved fingerprints ([4bfd7eb](https://github.com/revanced/revanced-patcher/commit/4bfd7ebff8b6623b0da4a46d6048bed08c5070d4))
|
||||
|
||||
## [4.4.1](https://github.com/revanced/revanced-patcher/compare/v4.4.0...v4.4.1) (2022-09-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* compare any methods parameters ([#101](https://github.com/revanced/revanced-patcher/issues/101)) ([085a3a4](https://github.com/revanced/revanced-patcher/commit/085a3a479d7bd411dcb0492b283daca538c824a1))
|
||||
|
||||
# [4.4.0](https://github.com/revanced/revanced-patcher/compare/v4.3.0...v4.4.0) (2022-09-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add PathOption back ([172655b](https://github.com/revanced/revanced-patcher/commit/172655bde06efdb0955431b44d269e6a64fe317a))
|
||||
|
||||
# [4.3.0](https://github.com/revanced/revanced-patcher/compare/v4.2.3...v4.3.0) (2022-09-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improved Patch Options ([e722e3f](https://github.com/revanced/revanced-patcher/commit/e722e3f4f9dc64acf53595802a0a83cf46ee96b8))
|
||||
|
||||
## [4.2.3](https://github.com/revanced/revanced-patcher/compare/v4.2.2...v4.2.3) (2022-09-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* wrong value for iterator in PatchOptions ([e31ac1f](https://github.com/revanced/revanced-patcher/commit/e31ac1f132df56ba7d2f8446d289ae03ef28f67d))
|
||||
|
||||
## [4.2.2](https://github.com/revanced/revanced-patcher/compare/v4.2.1...v4.2.2) (2022-09-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* invalid type propagation in options ([b873228](https://github.com/revanced/revanced-patcher/commit/b873228ef0a9e6e431a4278c979caa5fcc508e0d)), closes [#98](https://github.com/revanced/revanced-patcher/issues/98)
|
||||
|
||||
## [4.2.1](https://github.com/revanced/revanced-patcher/compare/v4.2.0...v4.2.1) (2022-09-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make patcher version public ([76c45dd](https://github.com/revanced/revanced-patcher/commit/76c45dd7c1ffdca57e30ae7109c9fe0e5768f877))
|
||||
|
||||
# [4.2.0](https://github.com/revanced/revanced-patcher/compare/v4.1.5...v4.2.0) (2022-09-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove repeatable from PatchDeprecated ([6e73631](https://github.com/revanced/revanced-patcher/commit/6e73631d4d21e5e862f07ed7517244f36394e5ca))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* SincePatcher annotation ([25f74dc](https://github.com/revanced/revanced-patcher/commit/25f74dc5e9ed1a09258345b920d4f5a0dd7da527))
|
||||
|
||||
## [4.1.5](https://github.com/revanced/revanced-patcher/compare/v4.1.4...v4.1.5) (2022-09-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* broken deprecation message ([62aa295](https://github.com/revanced/revanced-patcher/commit/62aa295e7372014238415af36d902a4e88e2acbc))
|
||||
|
||||
## [4.1.4](https://github.com/revanced/revanced-patcher/compare/v4.1.3...v4.1.4) (2022-09-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle option types and nulls properly ([aff4968](https://github.com/revanced/revanced-patcher/commit/aff4968e6f67239afa3b5c02cc133a17d9c3cbeb))
|
||||
|
||||
## [4.1.3](https://github.com/revanced/revanced-patcher/compare/v4.1.2...v4.1.3) (2022-09-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* only run list option check if not null ([4055939](https://github.com/revanced/revanced-patcher/commit/4055939c089e3c396c308c980215d93a1dea5954))
|
||||
|
||||
## [4.1.2](https://github.com/revanced/revanced-patcher/compare/v4.1.1...v4.1.2) (2022-09-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* invalid types for example options ([79f91e0](https://github.com/revanced/revanced-patcher/commit/79f91e0e5a6d99828f30aae55339ce0d897394c7))
|
||||
|
||||
## [4.1.1](https://github.com/revanced/revanced-patcher/compare/v4.1.0...v4.1.1) (2022-09-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle private companion objects ([ad3d332](https://github.com/revanced/revanced-patcher/commit/ad3d332e27d07e9d074bbaaf51af7eb2f9bfc7d5))
|
||||
|
||||
# [4.1.0](https://github.com/revanced/revanced-patcher/compare/v4.0.0...v4.1.0) (2022-09-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* deprecation for patches ([80c2e80](https://github.com/revanced/revanced-patcher/commit/80c2e809251cdb04d2dd3b3bfdbb8844bdfa31fa))
|
||||
|
||||
# [4.0.0](https://github.com/revanced/revanced-patcher/compare/v3.5.1...v4.0.0) (2022-09-07)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* Improve Patch Options ([6b909c1](https://github.com/revanced/revanced-patcher/commit/6b909c1ee6b8c2ea08bbca059df755e2e5f31656))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Options has been moved from Patch to a new interface called OptionsContainer and are now handled entirely different. Make sure to check the examples to understand how it works.
|
||||
|
||||
## [3.5.1](https://github.com/revanced/revanced-patcher/compare/v3.5.0...v3.5.1) (2022-09-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add tests for PathOption ([d6308e1](https://github.com/revanced/revanced-patcher/commit/d6308e126c6217b098192c51b6e98bc85a8656bd))
|
||||
* PathOption should be open, not sealed ([a562e47](https://github.com/revanced/revanced-patcher/commit/a562e476c085841efbc7ee98b01d8e6bb18ed757))
|
||||
* typo in ListOption ([3921648](https://github.com/revanced/revanced-patcher/commit/392164862c83d6e76b2a2113d6f6d59fef0020d1))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* make exception an object ([75d2be8](https://github.com/revanced/revanced-patcher/commit/75d2be88037c9cf5436ab69d92abea575409a865))
|
||||
|
||||
# [3.5.0](https://github.com/revanced/revanced-patcher/compare/v3.4.1...v3.5.0) (2022-09-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* default value for `Package.versions` annotation parameter ([131dedd](https://github.com/revanced/revanced-patcher/commit/131dedd4b021fe1c3b0be49ccba4764b325770ea))
|
||||
|
||||
## [3.4.1](https://github.com/revanced/revanced-patcher/compare/v3.4.0...v3.4.1) (2022-09-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove default param from Package.versions ([4b81318](https://github.com/revanced/revanced-patcher/commit/4b813187107e85dc267dbc2d353884b2cc671cc4))
|
||||
|
||||
# [3.4.0](https://github.com/revanced/revanced-patcher/compare/v3.3.3...v3.4.0) (2022-08-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* nullable parameters ([7882a8d](https://github.com/revanced/revanced-patcher/commit/7882a8d928cad8de8cfea711947fc02659549d20))
|
||||
|
||||
## [3.3.3](https://github.com/revanced/revanced-patcher/compare/v3.3.2...v3.3.3) (2022-08-14)
|
||||
|
||||
|
||||
|
@@ -24,8 +24,9 @@ dependencies {
|
||||
implementation("xpp3:xpp3:1.1.4c")
|
||||
implementation("org.smali:smali:2.5.2")
|
||||
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
||||
implementation("org.apktool:apktool-lib:2.7.0-SNAPSHOT")
|
||||
implementation("org.apktool:apktool-lib:2.8.1-SNAPSHOT")
|
||||
|
||||
implementation(kotlin("reflect"))
|
||||
testImplementation(kotlin("test"))
|
||||
}
|
||||
|
||||
@@ -36,6 +37,9 @@ tasks {
|
||||
events("PASSED", "SKIPPED", "FAILED")
|
||||
}
|
||||
}
|
||||
processResources {
|
||||
expand("projectVersion" to project.version)
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
|
@@ -1,2 +1,2 @@
|
||||
kotlin.code.style = official
|
||||
version = 3.3.3
|
||||
version = 6.0.0
|
||||
|
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].
|
||||
* @param inputFile The input file (usually an apk file).
|
||||
* @param resourceCacheDirectory Directory to cache resources.
|
||||
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
|
||||
* @param aaptPath Optional path to a custom aapt binary.
|
||||
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
||||
* @param logger Custom logger implementation for the [Patcher].
|
||||
@@ -16,7 +15,6 @@ import java.io.File
|
||||
data class PatcherOptions(
|
||||
internal val inputFile: File,
|
||||
internal val resourceCacheDirectory: String,
|
||||
internal val patchResources: Boolean = false,
|
||||
internal val aaptPath: String = "",
|
||||
internal val frameworkFolderLocation: String? = null,
|
||||
internal val logger: Logger = NopLogger
|
||||
|
@@ -24,5 +24,5 @@ annotation class Compatibility(
|
||||
@MustBeDocumented
|
||||
annotation class Package(
|
||||
val name: String,
|
||||
val versions: Array<String>
|
||||
)
|
||||
val versions: Array<String> = [],
|
||||
)
|
||||
|
@@ -0,0 +1,19 @@
|
||||
package app.revanced.patcher.annotation
|
||||
|
||||
import app.revanced.patcher.data.Context
|
||||
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<Context>> = Patch::class
|
||||
// Values cannot be nullable in annotations, so this will have to do.
|
||||
)
|
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,20 +1,24 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.annotation.Compatibility
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.annotation.*
|
||||
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.patch.OptionsContainer
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchOptions
|
||||
import app.revanced.patcher.patch.annotations.DependsOn
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.companionObjectInstance
|
||||
|
||||
/**
|
||||
* Recursively find a given annotation on a class.
|
||||
* @param targetAnnotation The annotation to find.
|
||||
* @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())
|
||||
|
||||
|
||||
@@ -36,28 +40,53 @@ private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
||||
}
|
||||
|
||||
object PatchExtensions {
|
||||
val Class<out Patch<Data>>.patchName: String
|
||||
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.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<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
||||
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>>.patchName: String
|
||||
get() = findAnnotationRecursively(Name::class)?.name ?: this.javaClass.simpleName
|
||||
|
||||
@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
|
||||
}
|
||||
val Class<out Patch<Context>>.version
|
||||
get() = findAnnotationRecursively(Version::class)?.version
|
||||
|
||||
val Class<out Patch<Context>>.include
|
||||
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 ->
|
||||
if (cl.visibility != KVisibility.PUBLIC) return null
|
||||
kotlin.companionObjectInstance?.let {
|
||||
(it as? OptionsContainer)?.options
|
||||
}
|
||||
}
|
||||
|
||||
val Class<out Patch<Context>>.deprecated: Pair<String, KClass<out Patch<Context>>?>?
|
||||
get() = findAnnotationRecursively(PatchDeprecated::class)?.let {
|
||||
it.reason to it.replacement.let { cl ->
|
||||
if (cl == Patch::class) null else cl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MethodFingerprintExtensions {
|
||||
val MethodFingerprint.name: String
|
||||
get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||
val MethodFingerprint.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
||||
val MethodFingerprint.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
||||
val MethodFingerprint.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
val MethodFingerprint.matchingMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.MatchingMethod::class)
|
||||
val MethodFingerprint.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod::class)
|
||||
val MethodFingerprint.fuzzyScanThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||
get() = javaClass.findAnnotationRecursively(Name::class)?.name ?: this.javaClass.simpleName
|
||||
|
||||
val MethodFingerprint.version
|
||||
get() = javaClass.findAnnotationRecursively(Version::class)?.version ?: "0.0.1"
|
||||
|
||||
val MethodFingerprint.description
|
||||
get() = javaClass.findAnnotationRecursively(Description::class)?.description
|
||||
|
||||
val MethodFingerprint.fuzzyPatternScanMethod
|
||||
get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class)
|
||||
|
||||
val MethodFingerprint.fuzzyScanThreshold
|
||||
get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||
}
|
@@ -16,7 +16,6 @@ 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.ImmutableMethodImplementation
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
import java.io.OutputStream
|
||||
|
||||
infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
|
||||
@@ -47,16 +46,14 @@ fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare a method to another, considering constructors and parameters.
|
||||
* 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 {
|
||||
if (MethodUtil.isConstructor(this) && !parametersEqual(
|
||||
this.parameterTypes, otherMethod.parameterTypes
|
||||
)
|
||||
) return false
|
||||
return this.name == otherMethod.name
|
||||
return this.name == otherMethod.name && parametersEqual(
|
||||
this.parameterTypes, otherMethod.parameterTypes
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -2,18 +2,6 @@ package app.revanced.patcher.fingerprint.method.annotation
|
||||
|
||||
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.
|
||||
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||
@@ -22,11 +10,4 @@ annotation class MatchingMethod(
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class FuzzyPatternScanMethod(
|
||||
val threshold: Int = 1
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotations to scan a pattern [MethodFingerprint] directly.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class DirectPatternScanMethod
|
||||
)
|
File diff suppressed because it is too large
Load Diff
@@ -1,158 +0,0 @@
|
||||
package app.revanced.patcher.fingerprint.method.utils
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
||||
import app.revanced.patcher.extensions.parametersEqual
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult
|
||||
import app.revanced.patcher.fingerprint.method.impl.PatternScanResult
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import org.jf.dexlib2.iface.reference.StringReference
|
||||
|
||||
/**
|
||||
* Utility class for [MethodFingerprint]
|
||||
*/
|
||||
object MethodFingerprintUtils {
|
||||
/**
|
||||
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||
* @param context The classes on which to resolve the [MethodFingerprint].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun Iterable<MethodFingerprint>.resolve(forData: BytecodeData, context: Iterable<ClassDef>) {
|
||||
for (fingerprint in this) // For each fingerprint
|
||||
classes@ for (classDef in context) // search through all classes for the fingerprint
|
||||
if (fingerprint.resolve(forData, classDef))
|
||||
break@classes // if the resolution succeeded, continue with the next fingerprint
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] against a [ClassDef].
|
||||
* @param context The class on which to resolve the [MethodFingerprint].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean {
|
||||
for (method in context.methods)
|
||||
if (this.resolve(forData, method, context))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] against a [Method].
|
||||
* @param context The context on which to resolve the [MethodFingerprint].
|
||||
* @param classDef The class of the matching [Method].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean {
|
||||
val methodFingerprint = this
|
||||
|
||||
if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType))
|
||||
return false
|
||||
|
||||
if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags)
|
||||
return false
|
||||
|
||||
|
||||
if (methodFingerprint.parameters != null && !parametersEqual(
|
||||
methodFingerprint.parameters, // TODO: parseParameters()
|
||||
context.parameterTypes
|
||||
)
|
||||
) return false
|
||||
|
||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context))
|
||||
return false
|
||||
|
||||
if (methodFingerprint.strings != null) {
|
||||
val implementation = context.implementation ?: return false
|
||||
|
||||
val stringsList = methodFingerprint.strings.toMutableList()
|
||||
|
||||
implementation.instructions.forEach { instruction ->
|
||||
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEach
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
val index = stringsList.indexOfFirst { it == string }
|
||||
if (index != -1) stringsList.removeAt(index)
|
||||
}
|
||||
|
||||
if (stringsList.isNotEmpty()) return false
|
||||
}
|
||||
|
||||
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||
context.implementation?.instructions ?: return false
|
||||
|
||||
context.patternScan(methodFingerprint) ?: return false
|
||||
} else null
|
||||
|
||||
methodFingerprint.result = MethodFingerprintResult(context, classDef, patternScanResult, forData)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Method.patternScan(
|
||||
fingerprint: MethodFingerprint
|
||||
): PatternScanResult? {
|
||||
val instructions = this.implementation!!.instructions
|
||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold
|
||||
|
||||
val pattern = fingerprint.opcodes!!
|
||||
val instructionLength = instructions.count()
|
||||
val patternLength = pattern.count()
|
||||
|
||||
for (index in 0 until instructionLength) {
|
||||
var patternIndex = 0
|
||||
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||
|
||||
while (index + patternIndex < instructionLength) {
|
||||
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||
// reaching maximum threshold (0) means,
|
||||
// the pattern does not match to the current instructions
|
||||
if (threshold-- == 0) break
|
||||
}
|
||||
|
||||
if (patternIndex < patternLength - 1) {
|
||||
// if the entire pattern has not been scanned yet
|
||||
// continue the scan
|
||||
patternIndex++
|
||||
continue
|
||||
}
|
||||
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
||||
val result = PatternScanResult(index, index + patternIndex)
|
||||
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
||||
result.warnings = result.createWarnings(pattern, instructions)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun PatternScanResult.createWarnings(
|
||||
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
||||
) = buildList {
|
||||
for ((patternIndex, instructionIndex) in (this@createWarnings.startIndex until this@createWarnings.endIndex).withIndex()) {
|
||||
val originalOpcode = instructions.elementAt(instructionIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue
|
||||
|
||||
this.add(PatternScanResult.Warning(originalOpcode, patternOpcode, instructionIndex, patternIndex))
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun ClassDef.component1() = this
|
||||
private operator fun ClassDef.component2() = this.methods
|
@@ -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,25 +1,44 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* A ReVanced patch.
|
||||
*
|
||||
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
||||
* If it implements [Closeable], it will be closed after all patches have been executed.
|
||||
* Closing will be done in reverse execution order.
|
||||
*/
|
||||
abstract class Patch<out T : Data> {
|
||||
sealed interface Patch<out T : Context> : Closeable {
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* A list of [PatchOption]s.
|
||||
* The closing function for this patch.
|
||||
*
|
||||
* This can be treated like popping the patch from the current patch stack.
|
||||
*/
|
||||
open val options = PatchOptions()
|
||||
}
|
||||
override fun close() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
@@ -2,6 +2,8 @@
|
||||
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class NoSuchOptionException(val option: String) : Exception("No such option: $option")
|
||||
@@ -9,28 +11,46 @@ class IllegalValueException(val value: Any?) : Exception("Illegal value: $value"
|
||||
class InvalidTypeException(val got: String, val expected: String) :
|
||||
Exception("Invalid option value type: $got, expected $expected")
|
||||
|
||||
class RequirementNotMetException : Exception("null was passed into an option that requires a value")
|
||||
object RequirementNotMetException : Exception("null was passed into an option that requires a value")
|
||||
|
||||
/**
|
||||
* A registry for an array of [PatchOption]s.
|
||||
* @param options An array of [PatchOption]s.
|
||||
*/
|
||||
class PatchOptions(vararg val options: PatchOption<*>) : Iterable<PatchOption<*>> {
|
||||
private val register = buildMap {
|
||||
for (option in options) {
|
||||
if (containsKey(option.key)) {
|
||||
throw IllegalStateException("Multiple options found with the same key")
|
||||
}
|
||||
put(option.key, option)
|
||||
class PatchOptions(vararg options: PatchOption<*>) : Iterable<PatchOption<*>> {
|
||||
private val register = mutableMapOf<String, PatchOption<*>>()
|
||||
|
||||
init {
|
||||
options.forEach { register(it) }
|
||||
}
|
||||
|
||||
internal fun register(option: PatchOption<*>) {
|
||||
if (register.containsKey(option.key)) {
|
||||
throw IllegalStateException("Multiple options found with the same key")
|
||||
}
|
||||
register[option.key] = option
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a [PatchOption] by its key.
|
||||
* @param key The key of the [PatchOption].
|
||||
*/
|
||||
@JvmName("getUntyped")
|
||||
operator fun get(key: String) = register[key] ?: throw NoSuchOptionException(key)
|
||||
|
||||
/**
|
||||
* Get a [PatchOption] by its key.
|
||||
* @param key The key of the [PatchOption].
|
||||
*/
|
||||
inline operator fun <reified T> get(key: String): PatchOption<T> {
|
||||
val opt = get(key)
|
||||
if (opt.value !is T) throw InvalidTypeException(
|
||||
opt.value?.let { it::class.java.canonicalName } ?: "null",
|
||||
T::class.java.canonicalName
|
||||
)
|
||||
return opt as PatchOption<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a [PatchOption].
|
||||
* @param key The key of the [PatchOption].
|
||||
@@ -38,7 +58,7 @@ class PatchOptions(vararg val options: PatchOption<*>) : Iterable<PatchOption<*>
|
||||
* Please note that using the wrong value type results in a runtime error.
|
||||
*/
|
||||
inline operator fun <reified T> set(key: String, value: T) {
|
||||
@Suppress("UNCHECKED_CAST") val opt = get(key) as PatchOption<T>
|
||||
val opt = get<T>(key)
|
||||
if (opt.value !is T) throw InvalidTypeException(
|
||||
T::class.java.canonicalName,
|
||||
opt.value?.let { it::class.java.canonicalName } ?: "null"
|
||||
@@ -54,7 +74,7 @@ class PatchOptions(vararg val options: PatchOption<*>) : Iterable<PatchOption<*>
|
||||
get(key).value = null
|
||||
}
|
||||
|
||||
override fun iterator() = options.iterator()
|
||||
override fun iterator() = register.values.iterator()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,9 +95,15 @@ sealed class PatchOption<T>(
|
||||
val validator: (T?) -> Boolean
|
||||
) {
|
||||
var value: T? = default
|
||||
get() {
|
||||
if (field == null && required) {
|
||||
throw RequirementNotMetException
|
||||
}
|
||||
return field
|
||||
}
|
||||
set(value) {
|
||||
if (value == null && required) {
|
||||
throw RequirementNotMetException()
|
||||
throw RequirementNotMetException
|
||||
}
|
||||
if (!validator(value)) {
|
||||
throw IllegalValueException(value)
|
||||
@@ -89,13 +115,23 @@ sealed class PatchOption<T>(
|
||||
* Gets the value of the option.
|
||||
* Please note that using the wrong value type results in a runtime error.
|
||||
*/
|
||||
operator fun <T> getValue(thisRef: Nothing?, property: KProperty<*>) = value as T
|
||||
@JvmName("getValueTyped")
|
||||
inline operator fun <reified V> getValue(thisRef: Nothing?, property: KProperty<*>): V? {
|
||||
if (value !is V?) throw InvalidTypeException(
|
||||
V::class.java.canonicalName,
|
||||
value?.let { it::class.java.canonicalName } ?: "null"
|
||||
)
|
||||
return value as? V?
|
||||
}
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
|
||||
|
||||
/**
|
||||
* Gets the value of the option.
|
||||
* Please note that using the wrong value type results in a runtime error.
|
||||
*/
|
||||
inline operator fun <reified V> setValue(thisRef: Any?, property: KProperty<*>, new: V) {
|
||||
@JvmName("setValueTyped")
|
||||
inline operator fun <reified V> setValue(thisRef: Nothing?, property: KProperty<*>, new: V) {
|
||||
if (value !is V) throw InvalidTypeException(
|
||||
V::class.java.canonicalName,
|
||||
value?.let { it::class.java.canonicalName } ?: "null"
|
||||
@@ -103,6 +139,10 @@ sealed class PatchOption<T>(
|
||||
value = new as T
|
||||
}
|
||||
|
||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, new: T?) {
|
||||
value = new
|
||||
}
|
||||
|
||||
/**
|
||||
* A [PatchOption] representing a [String].
|
||||
* @see PatchOption
|
||||
@@ -152,8 +192,8 @@ sealed class PatchOption<T>(
|
||||
}
|
||||
) {
|
||||
init {
|
||||
if (default !in options) {
|
||||
throw IllegalStateException("Default option must be an allowed options")
|
||||
if (default != null && default !in options) {
|
||||
throw IllegalStateException("Default option must be an allowed option")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,4 +229,20 @@ sealed class PatchOption<T>(
|
||||
) : ListOption<Int>(
|
||||
key, default, options, title, description, required, validator
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A [PatchOption] representing a [Path], backed by a [String].
|
||||
* The validator passes a [String], if you need a [Path] you will have to convert it yourself.
|
||||
* @see PatchOption
|
||||
*/
|
||||
class PathOption(
|
||||
key: String,
|
||||
default: Path?,
|
||||
title: String,
|
||||
description: String,
|
||||
required: Boolean = false,
|
||||
validator: (String?) -> Boolean = { true }
|
||||
) : PatchOption<String>(
|
||||
key, default?.pathString, title, description, required, validator
|
||||
)
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
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 kotlin.reflect.KClass
|
||||
|
||||
@@ -20,5 +20,5 @@ annotation class Patch(val include: Boolean = true)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
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 org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> {
|
||||
private val internalProxies = mutableListOf<ClassProxy>()
|
||||
internal val proxies: List<ClassProxy> = internalProxies
|
||||
|
||||
fun add(classDef: ClassDef) = internalClasses.add(classDef)
|
||||
fun add(classProxy: ClassProxy) = internalProxies.add(classProxy)
|
||||
/**
|
||||
* A class that represents a set of classes and proxies.
|
||||
*
|
||||
* @param classes The classes to be backed by proxies.
|
||||
*/
|
||||
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() {
|
||||
// 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
|
||||
fun add(classDef: ClassDef) = classes.add(classDef)
|
||||
|
||||
// if it has been used, replace the internal class which it proxied
|
||||
val index = internalClasses.indexOfFirst { it.type == proxy.immutableClass.type }
|
||||
internalClasses[index] = proxy.mutatedClass
|
||||
/**
|
||||
* Add a [ClassProxy].
|
||||
*/
|
||||
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@removeIf true
|
||||
}
|
||||
}
|
||||
|
||||
override val size get() = internalClasses.size
|
||||
override fun contains(element: ClassDef) = internalClasses.contains(element)
|
||||
override fun containsAll(elements: Collection<ClassDef>) = internalClasses.containsAll(elements)
|
||||
override fun get(index: Int) = internalClasses[index]
|
||||
override fun indexOf(element: ClassDef) = internalClasses.indexOf(element)
|
||||
override fun isEmpty() = internalClasses.isEmpty()
|
||||
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)
|
||||
|
||||
override val size get() = classes.size
|
||||
override fun contains(element: ClassDef) = classes.contains(element)
|
||||
override fun containsAll(elements: Collection<ClassDef>) = classes.containsAll(elements)
|
||||
override fun isEmpty() = classes.isEmpty()
|
||||
override fun iterator() = classes.iterator()
|
||||
}
|
18
src/main/kotlin/app/revanced/patcher/util/VersionReader.kt
Normal file
18
src/main/kotlin/app/revanced/patcher/util/VersionReader.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import java.util.*
|
||||
|
||||
internal object VersionReader {
|
||||
@JvmStatic
|
||||
private val props = Properties().apply {
|
||||
load(
|
||||
VersionReader::class.java.getResourceAsStream("/revanced-patcher/version.properties")
|
||||
?: throw IllegalStateException("Could not load version.properties")
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun read(): String {
|
||||
return props.getProperty("version") ?: throw IllegalStateException("Version not found")
|
||||
}
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
package app.revanced.patcher.util.method
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.MethodNotFoundException
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.softCompareTo
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import org.jf.dexlib2.iface.Method
|
||||
@@ -10,16 +9,19 @@ import org.jf.dexlib2.iface.reference.MethodReference
|
||||
|
||||
/**
|
||||
* Find a method from another method via instruction offsets.
|
||||
* @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.
|
||||
*/
|
||||
class MethodWalker internal constructor(
|
||||
private val bytecodeData: BytecodeData,
|
||||
private val bytecodeContext: BytecodeContext,
|
||||
private var currentMethod: Method
|
||||
) {
|
||||
/**
|
||||
* Get the method which was walked last.
|
||||
*
|
||||
* 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 {
|
||||
return currentMethod
|
||||
@@ -27,18 +29,21 @@ class MethodWalker internal constructor(
|
||||
|
||||
/**
|
||||
* Walk to a method defined at the offset in the instruction list of the current method.
|
||||
*
|
||||
* The current method will be mutable.
|
||||
*
|
||||
* @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.
|
||||
* The current method will be mutable.
|
||||
* @return The same [MethodWalker] instance with the method at [offset].
|
||||
*/
|
||||
fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
||||
currentMethod.implementation?.instructions?.let { instructions ->
|
||||
val instruction = instructions.elementAt(offset)
|
||||
|
||||
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 ->
|
||||
return@first it.softCompareTo(newMethod)
|
||||
}
|
||||
@@ -47,5 +52,5 @@ class MethodWalker internal constructor(
|
||||
throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
|
||||
}
|
||||
|
||||
|
||||
internal class MethodNotFoundException(exception: String) : Exception(exception)
|
||||
}
|
@@ -1,18 +1,74 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
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 org.jf.dexlib2.DexFileFactory
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
|
||||
/**
|
||||
* A 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 {
|
||||
for (className in classNames) {
|
||||
val clazz = classLoader.loadClass(className)
|
||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) continue
|
||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
|
||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A patch bundle of type [Jar].
|
||||
*
|
||||
* @param patchBundlePath The path to the patch bundle.
|
||||
*/
|
||||
class Jar(patchBundlePath: String) : PatchBundle(patchBundlePath) {
|
||||
|
||||
/**
|
||||
* Load patches from the patch bundle.
|
||||
*
|
||||
* Patches will be loaded with a new [URLClassLoader].
|
||||
*/
|
||||
fun loadPatches() = loadPatches(
|
||||
URLClassLoader(
|
||||
arrayOf(this.toURI().toURL()),
|
||||
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
||||
),
|
||||
StringIterator(
|
||||
JarFile(this)
|
||||
.entries()
|
||||
.toList() // TODO: find a cleaner solution than that to filter non class files
|
||||
.filter {
|
||||
it.name.endsWith(".class") && !it.name.contains("$")
|
||||
}
|
||||
.iterator()
|
||||
) {
|
||||
it.realName.replace('/', '.').replace(".class", "")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A patch bundle of type [Dex] format.
|
||||
*
|
||||
* @param patchBundlePath The path to a patch bundle of dex format.
|
||||
* @param dexClassLoader The dex class loader.
|
||||
*/
|
||||
class Dex(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
||||
/**
|
||||
* Load patches from the patch bundle.
|
||||
*
|
||||
* Patches will be loaded to the provided [dexClassLoader].
|
||||
*/
|
||||
fun loadPatches() = loadPatches(dexClassLoader,
|
||||
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
package app.revanced.patcher.util.patch.impl
|
||||
|
||||
import app.revanced.patcher.util.patch.PatchBundle
|
||||
import app.revanced.patcher.util.patch.StringIterator
|
||||
import org.jf.dexlib2.DexFileFactory
|
||||
|
||||
/**
|
||||
* A patch bundle of the ReVanced [DexPatchBundle] format.
|
||||
* @param patchBundlePath The path to a patch bundle of dex format.
|
||||
* @param dexClassLoader The dex class loader.
|
||||
*/
|
||||
class DexPatchBundle(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
||||
fun loadPatches() = loadPatches(dexClassLoader,
|
||||
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||
})
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user