1
mirror of https://github.com/revanced/revanced-patcher synced 2025-09-03 03:43:05 +02:00

Compare commits

...

97 Commits

Author SHA1 Message Date
semantic-release-bot
4016bdc37f chore(release): 6.0.0 [skip ci]
# [6.0.0](https://github.com/revanced/revanced-patcher/compare/v5.1.2...v6.0.0) (2022-10-05)

### Code Refactoring

* improve structuring of classes and their implementations ([4aa14bb](4aa14bbb85))

### Features

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

### BREAKING CHANGES

* various changes in which packages classes previously where and their implementation
* These extensions do not exist anymore and any use should be removed
* The extension does not exist anymore and any use should be removed
* The annotation does not exist anymore and any use should be removed
2022-10-05 02:11:19 +00:00
oSumAtrIX
538b2a8599 feat: remove unused annotation DirectPatternScanMethod 2022-10-05 03:57:40 +02:00
oSumAtrIX
4aa14bbb85 refactor: improve structuring of classes and their implementations
BREAKING CHANGE: various changes in which packages classes previously where and their implementation
2022-10-05 03:57:40 +02:00
oSumAtrIX
d37452997b refactor: move OptionsContainer to own class file
BREAKING-CHANGE: this changes the package
2022-10-04 08:32:50 +02:00
oSumAtrIX
db21d5e953 refactor: remove unused import 2022-10-04 08:30:17 +02:00
oSumAtrIX
4d581811db feat!: seal abstract class Patch as an interface 2022-10-04 08:30:17 +02:00
oSumAtrIX
8c502448be feat!: remove unused annotation MatchingMethod 2022-10-04 08:30:16 +02:00
oSumAtrIX
fec16d9442 refactor: remove line break 2022-10-04 08:18:52 +02:00
oSumAtrIX
5583904994 feat: remove unused patch extensions
BREAKING CHANGE: These extensions do not exist anymore and any use should be removed
2022-10-04 08:18:43 +02:00
oSumAtrIX
797286b758 feat: remove unused extension dependsOn
BREAKING CHANGE: The extension does not exist anymore and any use should be removed
2022-10-04 08:18:09 +02:00
oSumAtrIX
4ae9ad09d6 feat: remove unused annotation SincePatcher
BREAKING CHANGE: The annotation does not exist anymore and any use should be removed
2022-10-04 08:16:58 +02:00
semantic-release-bot
447e1ad30e chore(release): 5.1.2 [skip ci]
## [5.1.2](https://github.com/revanced/revanced-patcher/compare/v5.1.1...v5.1.2) (2022-09-29)

### Bug Fixes

* check dependencies for resource patches ([9c07ffc](9c07ffcc7a))
* use instruction index instead of strings list index for `StringMatch` ([843e62a](843e62ad29))
2022-09-29 19:29:55 +00:00
oSumAtrIX
843e62ad29 fix: use instruction index instead of strings list index for StringMatch 2022-09-29 21:27:56 +02:00
oSumAtrIX
9c07ffcc7a fix: check dependencies for resource patches 2022-09-29 21:27:12 +02:00
semantic-release-bot
438321330e chore(release): 5.1.1 [skip ci]
## [5.1.1](https://github.com/revanced/revanced-patcher/compare/v5.1.0...v5.1.1) (2022-09-26)

### Performance Improvements

* decode resources only when necessary ([3ba4be2](3ba4be240b))
2022-09-26 06:59:37 +00:00
oSumAtrIX
3ba4be240b perf: decode resources only when necessary 2022-09-26 08:57:39 +02:00
semantic-release-bot
98ce0abfa9 chore(release): 5.1.0 [skip ci]
# [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](db4348c4fa))
2022-09-26 01:22:58 +00:00
oSumAtrIX
db4348c4fa feat: RwLock for opening files in DomFileEditor 2022-09-26 03:21:13 +02:00
semantic-release-bot
4839f87519 chore(release): 5.0.1 [skip ci]
## [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](124332f0e9))
2022-09-23 04:21:53 +00:00
oSumAtrIX
809862c997 build: update apktool-lib dependency 2022-09-23 06:20:00 +02:00
oSumAtrIX
fd5c878cee fix!: revert reverting changes
BREAKING-CHANGE: Imports will have to be updated from `MethodFingerprintUtils` to `MethodFingerprint.Companion`.
2022-09-21 16:45:16 +02:00
bogadana
124332f0e9 revert: revert breaking changes (#106) 2022-09-21 15:22:55 +02:00
semantic-release-bot
d4cf0cea52 chore(release): 5.0.0 [skip ci]
# [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](76676fb567))

* refactor!: move utility methods from `MethodFingerprintUtils` `MethodFingerprint` ([d802ef8](d802ef844e))
* feat(fingerprint)!: `StringsScanResult` for `MethodFingerprint` ([3813e28](3813e28ac2))

### 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>
2022-09-21 01:43:54 +00:00
oSumAtrIX
76676fb567 fix(tests): access patternScanResult through scanResult 2022-09-21 03:42:29 +02:00
oSumAtrIX
d802ef844e refactor!: move utility methods from MethodFingerprintUtils MethodFingerprint
BREAKING CHANGE: Imports will have to be updated from `MethodFingerprintUtils` to `MethodFingerprint.Companion`.

Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-09-21 01:38:49 +02:00
oSumAtrIX
90fc547673 refactor: suppress member visibility and unnecessary null assertion warnings
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-09-21 01:38:49 +02:00
oSumAtrIX
3813e28ac2 feat(fingerprint)!: StringsScanResult for MethodFingerprint
BREAKING CHANGE: `MethodFingerprint` now has a field for `MethodFingerprintScanResult`. `MethodFingerprintScanResult` now holds the previous field `MethodFingerprint.patternScanResult`.

Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-09-21 01:38:49 +02:00
semantic-release-bot
a2bb4004c7 chore(release): 4.5.0 [skip ci]
# [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](a0cb449c60))
2022-09-20 22:37:38 +00:00
oSumAtrIX
a0cb449c60 feat: section acknowledgements for issue templates
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-09-21 00:36:25 +02:00
semantic-release-bot
e0271790b8 chore(release): 4.4.2 [skip ci]
## [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](2f7e62ef65))

### Performance Improvements

* **fingerprint:** do not resolve already resolved fingerprints ([4bfd7eb](4bfd7ebff8))
2022-09-18 06:12:38 +00:00
oSumAtrIX
4bfd7ebff8 perf(fingerprint): do not resolve already resolved fingerprints
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-09-18 07:35:08 +02:00
oSumAtrIX
2f7e62ef65 fix(fingerprint): do not throw on MethodFingerprint.result getter
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-09-18 07:34:32 +02:00
semantic-release-bot
4485af8036 chore(release): 4.4.1 [skip ci]
## [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](085a3a479d))
2022-09-14 16:36:34 +00:00
d4rkk3y
085a3a479d fix: compare any methods parameters (#101) 2022-09-14 18:34:58 +02:00
semantic-release-bot
f75c9a78b8 chore(release): 4.4.0 [skip ci]
# [4.4.0](https://github.com/revanced/revanced-patcher/compare/v4.3.0...v4.4.0) (2022-09-09)

### Features

* add PathOption back ([172655b](172655bde0))
2022-09-09 14:24:48 +00:00
Sculas
172655bde0 feat: add PathOption back
Now backed by a String.
2022-09-09 16:23:21 +02:00
semantic-release-bot
456db7289a chore(release): 4.3.0 [skip ci]
# [4.3.0](https://github.com/revanced/revanced-patcher/compare/v4.2.3...v4.3.0) (2022-09-09)

### Features

* improved Patch Options ([e722e3f](e722e3f4f9))
2022-09-09 14:11:58 +00:00
Sculas
e722e3f4f9 feat: improved Patch Options
Removed a lot of the type mess. There's still some duplicated code PatchOption.kt, but I'm afraid there's nothing I can do about that. It's not a big deal anyway.
2022-09-09 16:10:30 +02:00
Sculas
c348c1f0a0 refactor: remove PathOption and FileOption 2022-09-09 15:04:17 +02:00
semantic-release-bot
ed1851013e chore(release): 4.2.3 [skip ci]
## [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](e31ac1f132))
2022-09-08 15:39:04 +00:00
Sculas
e31ac1f132 fix: wrong value for iterator in PatchOptions 2022-09-08 17:37:31 +02:00
semantic-release-bot
8f78f85e4a chore(release): 4.2.2 [skip ci]
## [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](b873228ef0)), closes [#98](https://github.com/revanced/revanced-patcher/issues/98)
2022-09-08 14:50:38 +00:00
Sculas
0be2677519 Merge remote-tracking branch 'origin/main' into main 2022-09-08 16:49:26 +02:00
Sculas
b873228ef0 fix: invalid type propagation in options
Fixes #98
2022-09-08 16:49:06 +02:00
semantic-release-bot
639ff1c0ba chore(release): 4.2.1 [skip ci]
## [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](76c45dd7c1))
2022-09-08 12:51:09 +00:00
Sculas
f30671ddd1 Merge remote-tracking branch 'origin/main' into main 2022-09-08 14:49:35 +02:00
Sculas
76c45dd7c1 fix: make patcher version public 2022-09-08 14:49:26 +02:00
semantic-release-bot
1bafb77355 chore(release): 4.2.0 [skip ci]
# [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](6e73631d4d))

### Features

* SincePatcher annotation ([25f74dc](25f74dc5e9))
2022-09-08 12:43:11 +00:00
Sculas
25f74dc5e9 feat: SincePatcher annotation 2022-09-08 14:41:42 +02:00
Sculas
6e73631d4d fix: remove repeatable from PatchDeprecated 2022-09-08 14:00:15 +02:00
semantic-release-bot
7761d5b85e chore(release): 4.1.5 [skip ci]
## [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](62aa295e73))
2022-09-08 11:43:39 +00:00
Sculas
62aa295e73 fix: broken deprecation message 2022-09-08 13:42:26 +02:00
Sculas
596ede1b12 refactor: make patchName work on any class 2022-09-08 13:42:15 +02:00
semantic-release-bot
7debe62738 chore(release): 4.1.4 [skip ci]
## [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](aff4968e6f))
2022-09-08 09:30:49 +00:00
Sculas
002f84da1a Merge remote-tracking branch 'origin/main' into main 2022-09-08 11:29:14 +02:00
Sculas
aff4968e6f fix: handle option types and nulls properly 2022-09-08 11:29:06 +02:00
Sculas
1d989abd55 chore: ignore kotlinc 2022-09-08 11:07:34 +02:00
semantic-release-bot
f1775f83d0 chore(release): 4.1.3 [skip ci]
## [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](4055939c08))
2022-09-07 21:48:24 +00:00
Sculas
4055939c08 fix: only run list option check if not null 2022-09-07 23:46:46 +02:00
semantic-release-bot
85120374d6 chore(release): 4.1.2 [skip ci]
## [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](79f91e0e5a))
2022-09-07 21:24:50 +00:00
Sculas
8b4819faa1 Merge remote-tracking branch 'origin/main' into main 2022-09-07 23:23:29 +02:00
semantic-release-bot
d219276298 chore(release): 4.1.1 [skip ci]
## [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](ad3d332e27))
2022-09-07 21:23:12 +00:00
Sculas
79f91e0e5a fix: invalid types for example options 2022-09-07 23:22:34 +02:00
Sculas
fadf62f594 Merge remote-tracking branch 'origin/main' into main 2022-09-07 23:21:41 +02:00
Sculas
ad3d332e27 fix: handle private companion objects 2022-09-07 23:21:32 +02:00
semantic-release-bot
8f66df7666 chore(release): 4.1.0 [skip ci]
# [4.1.0](https://github.com/revanced/revanced-patcher/compare/v4.0.0...v4.1.0) (2022-09-07)

### Features

* deprecation for patches ([80c2e80](80c2e80925))
2022-09-07 20:32:51 +00:00
Sculas
80c2e80925 feat: deprecation for patches 2022-09-07 22:31:15 +02:00
semantic-release-bot
c3db23d3c7 chore(release): 4.0.0 [skip ci]
# [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](6b909c1ee6))

### 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.
2022-09-07 18:57:04 +00:00
Sculas
c28584736e Merge remote-tracking branch 'origin/main' into main 2022-09-07 20:55:45 +02:00
Sculas
6b909c1ee6 refactor: Improve Patch Options
It's so much better now. Really happy with the current system.

BREAKING CHANGE: 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.
2022-09-07 20:55:35 +02:00
Sculas
0e8446516e build: add Kotlin Reflect 2022-09-07 20:52:05 +02:00
semantic-release-bot
aa46b953db chore(release): 3.5.1 [skip ci]
## [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](d6308e126c))
* PathOption should be open, not sealed ([a562e47](a562e476c0))
* typo in ListOption ([3921648](392164862c))

### Performance Improvements

* make exception an object ([75d2be8](75d2be8803))
2022-09-06 20:38:24 +00:00
Sculas
a562e476c0 fix: PathOption should be open, not sealed 2022-09-06 22:36:20 +02:00
Sculas
75d2be8803 perf: make exception an object 2022-09-06 22:35:33 +02:00
Sculas
d6308e126c fix: add tests for PathOption 2022-09-06 22:35:00 +02:00
Sculas
bb97af4d86 refactor: add FileOption alias for PathOption 2022-09-06 22:34:46 +02:00
Sculas
392164862c fix: typo in ListOption 2022-09-06 21:44:05 +02:00
Sculas
53e807dec1 refactor: add PatchOption.PathOption 2022-09-06 21:43:45 +02:00
semantic-release-bot
288d50a8b4 chore(release): 3.5.0 [skip ci]
# [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](131dedd4b0))
2022-09-05 14:45:56 +00:00
oSumAtrIX
131dedd4b0 feat: default value for Package.versions annotation parameter
Reverts 4b81318710
2022-09-05 16:44:35 +02:00
semantic-release-bot
5a92d5c29d chore(release): 3.4.1 [skip ci]
## [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](4b81318710))
2022-09-03 20:54:06 +00:00
Sculas
4b81318710 fix: remove default param from Package.versions
Kotlin compiler bug produces invalid bytecode, resulting in an IncompleteAnnotationException at runtime.
2022-09-03 22:52:49 +02:00
semantic-release-bot
44f6a3ebc5 chore(release): 3.4.0 [skip ci]
# [3.4.0](https://github.com/revanced/revanced-patcher/compare/v3.3.3...v3.4.0) (2022-08-31)

### Features

* nullable parameters ([7882a8d](7882a8d928))
2022-08-31 18:32:43 +00:00
oSumAtrIX
7882a8d928 feat: nullable parameters
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2022-08-31 20:30:31 +02:00
semantic-release-bot
cc3d32748b chore(release): 3.3.3 [skip ci]
## [3.3.3](https://github.com/revanced/revanced-patcher/compare/v3.3.2...v3.3.3) (2022-08-14)

### Bug Fixes

* show error message if cause is null ([f9da2ad](f9da2ad531))
2022-08-14 15:25:16 +00:00
oSumAtrIX
f9da2ad531 fix: show error message if cause is null 2022-08-14 17:22:43 +02:00
semantic-release-bot
b19e1131e8 chore(release): 3.3.2 [skip ci]
## [3.3.2](https://github.com/revanced/revanced-patcher/compare/v3.3.1...v3.3.2) (2022-08-06)

### Bug Fixes

* close open files ([#75](https://github.com/revanced/revanced-patcher/issues/75)) ([123ad54](123ad54c15))
2022-08-06 22:17:39 +00:00
dan1st
123ad54c15 fix: close open files (#75) 2022-08-07 00:16:23 +02:00
Sculas
09f6ab4155 Merge remote-tracking branch 'origin/main' into main 2022-08-03 18:32:34 +02:00
Sculas
01cf3fb50f refactor: util package structure 2022-08-03 18:31:31 +02:00
semantic-release-bot
6c5f9d4198 chore(release): 3.3.1 [skip ci]
## [3.3.1](https://github.com/revanced/revanced-patcher/compare/v3.3.0...v3.3.1) (2022-08-03)

### Bug Fixes

* revert soft dependencies ([7b2d058](7b2d058144))
2022-08-03 01:48:56 +00:00
oSumAtrIX
7b2d058144 fix: revert soft dependencies 2022-08-03 03:45:34 +02:00
semantic-release-bot
db2804270e chore(release): 3.3.0 [skip ci]
# [3.3.0](https://github.com/revanced/revanced-patcher/compare/v3.2.1...v3.3.0) (2022-08-02)

### Features

* add getValue & setValue for PatchOption ([2572cd0](2572cd04b5))
2022-08-02 21:31:59 +00:00
Sculas
2572cd04b5 feat: add getValue & setValue for PatchOption 2022-08-02 23:30:38 +02:00
semantic-release-bot
5eb8b428b9 chore(release): 3.2.1 [skip ci]
## [3.2.1](https://github.com/revanced/revanced-patcher/compare/v3.2.0...v3.2.1) (2022-08-02)

### Bug Fixes

* check if patch option requirement is met ([14a73bf](14a73bfcaf))
2022-08-02 20:43:26 +00:00
Sculas
3a118d9b9d Merge remote-tracking branch 'origin/main' into main 2022-08-02 22:41:41 +02:00
Sculas
14a73bfcaf fix: check if patch option requirement is met 2022-08-02 22:41:34 +02:00
44 changed files with 1518 additions and 889 deletions

View File

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

View File

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

@@ -6,3 +6,4 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
/kotlinc.xml

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,2 +1,2 @@
kotlin.code.style = official
version = 3.2.0
version = 6.0.0

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
package app.revanced.patcher
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.data.Context
import app.revanced.patcher.data.PackageMetadata
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.Patch
import org.jf.dexlib2.iface.ClassDef
import java.io.File
data class PatcherContext(
val classes: MutableList<ClassDef>,
val resourceCacheDirectory: File,
) {
val packageMetadata = PackageMetadata()
internal val patches = mutableListOf<Class<out Patch<Context>>>()
internal val bytecodeContext = BytecodeContext(classes)
internal val resourceContext = ResourceContext(resourceCacheDirectory)
}

View File

@@ -1,19 +0,0 @@
package app.revanced.patcher
import app.revanced.patcher.data.Data
import app.revanced.patcher.data.PackageMetadata
import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.data.impl.ResourceData
import app.revanced.patcher.patch.Patch
import org.jf.dexlib2.iface.ClassDef
import java.io.File
data class PatcherData(
internal val internalClasses: MutableList<ClassDef>,
internal val resourceCacheDirectory: String,
val packageMetadata: PackageMetadata
) {
internal val patches = mutableListOf<Class<out Patch<Data>>>()
internal val bytecodeData = BytecodeData(internalClasses)
internal val resourceData = ResourceData(File(resourceCacheDirectory))
}

View File

@@ -8,7 +8,6 @@ import java.io.File
* Options for the [Patcher].
* @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

View File

@@ -24,5 +24,5 @@ annotation class Compatibility(
@MustBeDocumented
annotation class Package(
val name: String,
val versions: Array<String>
)
val versions: Array<String> = [],
)

View File

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

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

View File

@@ -1,9 +0,0 @@
package app.revanced.patcher.data
import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.data.impl.ResourceData
/**
* Constraint interface for [BytecodeData] and [ResourceData]
*/
interface Data

View File

@@ -1,69 +0,0 @@
package app.revanced.patcher.data.impl
import app.revanced.patcher.data.Data
import app.revanced.patcher.util.ProxyBackedClassList
import app.revanced.patcher.util.method.MethodWalker
import org.jf.dexlib2.iface.ClassDef
import org.jf.dexlib2.iface.Method
class BytecodeData(
internalClasses: MutableList<ClassDef>
) : Data {
val classes = ProxyBackedClassList(internalClasses)
/**
* Find a class by a given class name.
* @param className The name of the class.
* @return A proxy for the first class that matches the class name.
*/
fun findClass(className: String) = findClass { it.type.contains(className) }
/**
* Find a class by a given predicate.
* @param predicate A predicate to match the class.
* @return A proxy for the first class that matches the predicate.
*/
fun findClass(predicate: (ClassDef) -> Boolean) =
// if we already proxied the class matching the predicate...
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
// else resolve the class to a proxy and return it, if the predicate is matching a class
classes.find(predicate)?.let { proxy(it) }
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
if (proxy == null) {
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
this.classes.add(proxy)
}
return proxy
}
}
internal class MethodNotFoundException(s: String) : Exception(s)
internal inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
for (element in this) {
if (predicate(element)) {
return element
}
}
return null
}
/**
* Create a [MethodWalker] instance for the current [BytecodeData].
* @param startMethod The method to start at.
* @return A [MethodWalker] instance.
*/
fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
return MethodWalker(this, startMethod)
}
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
for ((index, element) in this.withIndex()) {
if (predicate(element)) {
return element to index
}
}
return null
}

View File

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

View File

@@ -1,23 +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.annotations.Dependencies
import app.revanced.patcher.patch.annotations.DependencyType
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())
@@ -38,35 +39,54 @@ private fun <T : Annotation> Class<*>.findAnnotationRecursively(
return null
}
private typealias PatchClass = Class<out Patch<Data>>
object PatchExtensions {
val PatchClass.patchName: String get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
val PatchClass.version get() = recursiveAnnotation(Version::class)?.version
val PatchClass.include get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)!!.include
val PatchClass.description get() = recursiveAnnotation(Description::class)?.description
val PatchClass.dependencies get() = buildList {
recursiveAnnotation(DependsOn::class)?.let { add(PatchDependency(it.value, it.type)) }
recursiveAnnotation(Dependencies::class)?.dependencies?.forEach { add(PatchDependency(it, DependencyType.HARD)) }
}.toTypedArray()
val PatchClass.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
val Class<out Patch<Context>>.patchName: String
get() = findAnnotationRecursively(Name::class)?.name ?: this.javaClass.simpleName
@JvmStatic
fun PatchClass.dependsOn(patch: PatchClass): Boolean {
if (this.patchName == patch.patchName) throw IllegalArgumentException("thisval and patch may not be the same")
return this.dependencies.any { it.patch.java.patchName == this@dependsOn.patchName }
}
val Class<out Patch<Context>>.version
get() = findAnnotationRecursively(Version::class)?.version
class PatchDependency internal constructor(val patch: KClass<out Patch<Data>>, val type: DependencyType = DependencyType.HARD)
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
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
package app.revanced.patcher.patch
/**
* A container for patch options.
*/
abstract class OptionsContainer {
/**
* A list of [PatchOption]s.
* @see PatchOptions
*/
@Suppress("MemberVisibilityCanBePrivate")
val options = PatchOptions()
protected fun <T> option(opt: PatchOption<T>): PatchOption<T> {
options.register(opt)
return opt
}
}

View File

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

View File

@@ -1,32 +1,56 @@
@file:Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
@file:Suppress("CanBeParameter", "MemberVisibilityCanBePrivate", "UNCHECKED_CAST")
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")
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")
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].
@@ -34,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"
@@ -50,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()
}
/**
@@ -71,13 +95,54 @@ 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
}
if (!validator(value)) {
throw IllegalValueException(value)
}
field = value
}
/**
* Gets the value of the option.
* Please note that using the wrong value type results in a runtime error.
*/
@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.
*/
@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"
)
value = new as T
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, new: T?) {
value = new
}
/**
* A [PatchOption] representing a [String].
* @see PatchOption
@@ -127,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")
}
}
}
@@ -164,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
)
}

View File

@@ -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
@@ -19,29 +19,6 @@ annotation class Patch(val include: Boolean = true)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Deprecated(
"Does not support new parameter 'type'",
ReplaceWith("DependsOn")
)
annotation class Dependencies(
val dependencies: Array<KClass<out Patch<Data>>> = []
)
/**
* Annotation for dependencies of [Patch]es .
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Repeatable
annotation class DependsOn(
val value: KClass<out Patch<Data>>,
val type: DependencyType = DependencyType.HARD
)
enum class DependencyType {
/**
* Enforces that the dependency is applied, even if it was not selected.
*/
HARD
}
val dependencies: Array<KClass<out Patch<Context>>> = []
)

View File

@@ -1,13 +0,0 @@
package app.revanced.patcher.patch.impl
import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.patch.Patch
/**
* Bytecode patch for the Patcher.
* @param fingerprints A list of [MethodFingerprint] this patch relies on.
*/
abstract class BytecodePatch(
internal val fingerprints: Iterable<MethodFingerprint>? = null
) : Patch<BytecodeData>()

View File

@@ -1,9 +0,0 @@
package app.revanced.patcher.patch.impl
import app.revanced.patcher.data.impl.ResourceData
import app.revanced.patcher.patch.Patch
/**
* Resource patch for the Patcher.
*/
abstract class ResourcePatch : Patch<ResourceData>()

View File

@@ -3,40 +3,44 @@ package app.revanced.patcher.util
import app.revanced.patcher.util.proxy.ClassProxy
import 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()
}

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

View File

@@ -5,6 +5,6 @@ import java.io.InputStream
/**
* Wrapper for dex files.
* @param name The original name of the dex file.
* @param dexFileInputStream The dex file as [InputStream].
* @param stream The dex file as [InputStream].
*/
data class DexFile(val name: String, val dexFileInputStream: InputStream)
data class DexFile(val name: String, val stream: InputStream)

View File

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

View File

@@ -0,0 +1,74 @@
@file:Suppress("unused")
package app.revanced.patcher.util.patch
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.
*/
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<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('/', '.')
})
}
}

Some files were not shown because too many files have changed in this diff Show More