Compare commits

...

352 Commits

Author SHA1 Message Date
semantic-release-bot
2432fde6bf chore(release): 7.1.0-dev.2 [skip ci]
# [7.1.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v7.1.0-dev.1...v7.1.0-dev.2) (2023-05-05)

### Features

* add overload to get instruction as type ([49c173d](49c173dc14))
2023-05-05 21:38:29 +00:00
oSumAtrIX
49c173dc14 feat: add overload to get instruction as type 2023-05-05 23:36:30 +02:00
oSumAtrIX
d83e9372bb chore: update gradle and dependencies 2023-04-30 05:27:22 +02:00
semantic-release-bot
7e8cd3bede chore(release): 7.1.0-dev.1 [skip ci]
# [7.1.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.0.0...v7.1.0-dev.1) (2023-04-30)

### Features

* add appreciation message for new contributors ([d674362](d67436271d))
2023-04-30 01:16:45 +00:00
oSumAtrIX
d67436271d feat: add appreciation message for new contributors 2023-04-30 03:15:12 +02:00
oSumAtrIX
aa07f35f06 chore: bump dependencies 2023-04-03 03:04:05 +02:00
oSumAtrIX
77e0536838 build: update Kotlin to 1.8.10 2023-03-31 17:19:40 +02:00
oSumAtrIX
a49e78234b chore: remove project files
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-03-02 19:23:41 +01:00
semantic-release-bot
a3ae825e48 chore(release): 7.0.0 [skip ci]
# [7.0.0](https://github.com/revanced/revanced-patcher/compare/v6.4.3...v7.0.0) (2023-02-26)

* feat!: merge integrations only when necessary ([6e24a85](6e24a85eab))

### BREAKING CHANGES

* `Patcher.addFiles` is now renamed to `Patcher.addIntegrations`

Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-26 23:42:48 +00:00
oSumAtrIX
146c8504ed chore: merge branch dev to main (#159) 2023-02-27 00:41:27 +01:00
semantic-release-bot
2eb125ad69 chore(release): 7.0.0-dev.1 [skip ci]
# [7.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.3...v7.0.0-dev.1) (2023-02-26)

* feat!: merge integrations only when necessary ([6e24a85](6e24a85eab))

### BREAKING CHANGES

* `Patcher.addFiles` is now renamed to `Patcher.addIntegrations`

Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-26 23:24:38 +00:00
oSumAtrIX
6e24a85eab feat!: merge integrations only when necessary
BREAKING CHANGE: `Patcher.addFiles` is now renamed to `Patcher.addIntegrations`

Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-27 00:23:27 +01:00
oSumAtrIX
e4c3e9ffc5 refactor: remove unnecessary annotations
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-26 23:18:35 +01:00
Javier Flores
4c1778a62f chore: update gradle wrapper to 8.0.1 (#160) 2023-02-26 03:46:33 +01:00
oSumAtrIX
d99261cdbb ci: fix backmerge direction
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-22 16:39:50 +01:00
oSumAtrIX
ac1c0f2773 ci: add backmerge target branch
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-22 06:45:16 +01:00
oSumAtrIX
eddd4ec7ac ci: use new semantic-release-backmerge option
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-18 21:55:40 +01:00
Palm
07a2829c65 ci: update actions (#158) 2023-02-13 22:29:20 +01:00
semantic-release-bot
3d77e299d9 chore(release): 6.4.3 [skip ci]
## [6.4.3](https://github.com/revanced/revanced-patcher/compare/v6.4.2...v6.4.3) (2023-02-10)

### Bug Fixes

* check `CONST_STRING_JUMP` instructions for matching string ([058d292](058d292ad5))
2023-02-10 04:28:15 +00:00
oSumAtrIX
f1336f89e4 chore: merge branch dev to main (#156) 2023-02-10 05:26:43 +01:00
semantic-release-bot
0502f84c20 chore(release): 6.4.3-dev.1 [skip ci]
## [6.4.3-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.2...v6.4.3-dev.1) (2023-02-10)

### Bug Fixes

* check `CONST_STRING_JUMP` instructions for matching string ([058d292](058d292ad5))
2023-02-10 04:20:46 +00:00
oSumAtrIX
058d292ad5 fix: check CONST_STRING_JUMP instructions for matching string
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-10 05:18:38 +01:00
oSumAtrIX
1029d56a52 refactor: remove dead code
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-10 05:17:36 +01:00
oSumAtrIX
709b5a0fec chore: fix typo
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-02-10 05:16:00 +01:00
semantic-release-bot
e1accc5041 chore(release): 6.4.2 [skip ci]
## [6.4.2](https://github.com/revanced/revanced-patcher/compare/v6.4.1...v6.4.2) (2023-01-17)

### Bug Fixes

* resolve failing builds ([a263fdf](a263fdfd41))
2023-01-17 23:29:34 +00:00
oSumAtrIX
6dbbf2e03e chore: merge branch dev to main (#153) 2023-01-18 00:14:38 +01:00
semantic-release-bot
16557eeab0 chore(release): 6.4.2-dev.1 [skip ci]
## [6.4.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.1...v6.4.2-dev.1) (2023-01-17)

### Bug Fixes

* resolve failing builds ([a263fdf](a263fdfd41))
2023-01-17 23:09:26 +00:00
oSumAtrIX
6bca3e2bb5 build: update dependencies 2023-01-17 23:57:19 +01:00
oSumAtrIX
a263fdfd41 fix: resolve failing builds
This updates dependencies, because the previous ones were missing or outdated.
2023-01-17 01:32:11 +01:00
semantic-release-bot
e4b4bacae8 chore(release): 6.4.1 [skip ci]
## [6.4.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1) (2023-01-15)

### Bug Fixes

* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](dd7dd38357))
2023-01-15 05:13:48 +00:00
semantic-release-bot
cbc97af155 chore(release): 6.4.1 [skip ci]
## [6.4.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1) (2023-01-15)

### Bug Fixes

* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](dd7dd38357))
2023-01-15 04:32:33 +00:00
oSumAtrIX
d5533788e2 chore: merge branch dev to main (#152) 2023-01-15 05:31:12 +01:00
oSumAtrIX
5a4ea5cd7d ci: add missing PAT in step Release 2023-01-15 05:29:50 +01:00
oSumAtrIX
70f3c8b38c chore: merge branch dev to main (#151) 2023-01-15 05:24:25 +01:00
semantic-release-bot
6b410a0eea chore(release): 6.4.1-dev.1 [skip ci]
## [6.4.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1-dev.1) (2023-01-15)

### Bug Fixes

* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](dd7dd38357))
2023-01-15 03:07:43 +00:00
oSumAtrIX
73a013d75b ci: use PAT in step Release 2023-01-15 04:06:45 +01:00
oSumAtrIX
7159f3db4c ci: downgrade semantic release and plugins to v19 2023-01-15 04:06:45 +01:00
oSumAtrIX
7d5ecf095c ci: give jobs names 2023-01-15 04:06:44 +01:00
oSumAtrIX
fa015a424d refactor: fix formatting 2023-01-15 04:06:44 +01:00
oSumAtrIX
dd7dd38357 fix: update dependency app.revanced:multidexlib2 (#150)
This commit addresses https://github.com/revanced/multidexlib2/issues/2.
2023-01-15 04:06:43 +01:00
semantic-release-bot
22356f2d26 chore(release): 6.4.0 [skip ci]
# [6.4.0](https://github.com/revanced/revanced-patcher/compare/v6.3.2...v6.4.0) (2023-01-02)

### Features

* add missing setter to `MutableMethod` ([8f3ecc3](8f3ecc318c))
* do not fix methods or methods in class merger ([4102f43](4102f43b8a))
* fix method and field access when merging classes ([5c09ef7](5c09ef7837))
* make `aaptPath` nullable ([#146](https://github.com/revanced/revanced-patcher/issues/146)) ([9f0a09a](9f0a09a756))
2023-01-02 08:07:08 +00:00
oSumAtrIX
66701f6076 chore: merge branch dev to main (#147) 2023-01-02 09:05:19 +01:00
semantic-release-bot
6a6ded084e chore(release): 6.4.0-dev.2 [skip ci]
# [6.4.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v6.4.0-dev.1...v6.4.0-dev.2) (2023-01-02)

### Features

* add missing setter to `MutableMethod` ([8f3ecc3](8f3ecc318c))
* do not fix methods or methods in class merger ([4102f43](4102f43b8a))
* fix method and field access when merging classes ([5c09ef7](5c09ef7837))
2023-01-02 07:58:55 +00:00
oSumAtrIX
5887c69bde refactor: move merging classes code to own class (#149) 2023-01-02 08:58:05 +01:00
oSumAtrIX
4102f43b8a feat: do not fix methods or methods in class merger 2023-01-02 08:50:08 +01:00
oSumAtrIX
5c09ef7837 feat: fix method and field access when merging classes 2023-01-02 07:57:50 +01:00
oSumAtrIX
3e0bf8c863 refactor: move merging classes code to own class 2023-01-02 07:57:48 +01:00
oSumAtrIX
8f3ecc318c feat: add missing setter to MutableMethod 2023-01-02 07:09:58 +01:00
oSumAtrIX
365da96e2b build: do not comment on successful releases 2022-12-31 21:12:03 +01:00
oSumAtrIX
cd68ec4803 ci: do not escape in environment variable MESSAGE 2022-12-29 19:07:31 +01:00
semantic-release-bot
35265e029c chore(release): 6.4.0-dev.1 [skip ci]
# [6.4.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.2...v6.4.0-dev.1) (2022-12-20)

### Features

* make `aaptPath` nullable ([#146](https://github.com/revanced/revanced-patcher/issues/146)) ([9f0a09a](9f0a09a756))
2022-12-20 19:05:47 +00:00
Canny
9f0a09a756 feat: make aaptPath nullable (#146) 2022-12-20 20:04:15 +01:00
semantic-release-bot
e802141df5 chore(release): 6.3.2 [skip ci]
## [6.3.2](https://github.com/revanced/revanced-patcher/compare/v6.3.1...v6.3.2) (2022-12-18)

### Bug Fixes

* check if fingerprint string is substring of any string references ([c5de9e2](c5de9e2988))
* print full exception when patch fails ([7cf79e6](7cf79e68e0))
2022-12-18 21:11:48 +00:00
oSumAtrIX
abebc0862c chore: merge branch dev to main (#144) 2022-12-18 22:10:56 +01:00
semantic-release-bot
96ef150e89 chore(release): 6.3.2-dev.1 [skip ci]
## [6.3.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.1...v6.3.2-dev.1) (2022-12-18)

### Bug Fixes

* check if fingerprint string is substring of any string references ([c5de9e2](c5de9e2988))
* print full exception when patch fails ([7cf79e6](7cf79e68e0))
2022-12-18 21:08:40 +00:00
oSumAtrIX
c5de9e2988 fix: check if fingerprint string is substring of any string references 2022-12-18 22:05:05 +01:00
semantic-release-bot
c391ca648b chore(release): 6.3.2-dev.1 [skip ci]
## [6.3.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.1...v6.3.2-dev.1) (2022-12-17)

### Bug Fixes

* print full exception when patch fails ([27a8401](27a8401d81))
2022-12-18 09:02:48 +01:00
oSumAtrIX
7cf79e68e0 fix: print full exception when patch fails 2022-12-18 09:02:48 +01:00
oSumAtrIX
f07db3c214 chore: merge branch dev to main (#143) 2022-12-15 21:44:59 +01:00
oSumAtrIX
88bb3a8845 ci: do not release on build commit type 2022-12-15 19:47:31 +01:00
oSumAtrIX
b9e6bd6775 chore: merge branch dev to main (#141) 2022-12-15 19:36:43 +01:00
oSumAtrIX
cd1b72e078 ci: remove unnecessary step 2022-12-15 01:08:00 +01:00
oSumAtrIX
6b889557ab ci: stash before rebasing 2022-12-15 01:07:48 +01:00
oSumAtrIX
4b1be8c647 ci: only back-merge from main branch to dev 2022-12-15 01:04:16 +01:00
oSumAtrIX
73c893c6e7 chore: merge branch dev to main (#140)
chore: merge branch `dev` to `main`
2022-12-14 00:13:25 +01:00
oSumAtrIX
75b36823b8 ci: back-merge releases back into dev branch 2022-12-14 00:02:52 +01:00
semantic-release-bot
d2d93cd075 chore(release): 6.3.1 [skip ci]
## [6.3.1](https://github.com/revanced/revanced-patcher/compare/v6.3.0...v6.3.1) (2022-12-13)

### Bug Fixes

* publicize types when merging files if necessary ([#137](https://github.com/revanced/revanced-patcher/issues/137)) ([9ec720e](9ec720e983))
2022-12-13 22:52:11 +00:00
oSumAtrIX
26b8621ac8 chore: merge branch dev to main (#139) 2022-12-13 23:50:28 +01:00
semantic-release-bot
f365a41741 chore(release): 6.3.1-dev.1 [skip ci]
## [6.3.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.0...v6.3.1-dev.1) (2022-12-13)

### Bug Fixes

* publicize types when merging files if necessary ([#137](https://github.com/revanced/revanced-patcher/issues/137)) ([9ec720e](9ec720e983))
2022-12-13 22:39:34 +00:00
oSumAtrIX
9ec720e983 fix: publicize types when merging files if necessary (#137) 2022-12-13 23:36:47 +01:00
oSumAtrIX
0f432b3fdd ci: escape backticks in message environment variable 2022-12-13 23:36:47 +01:00
oSumAtrIX
96cd5618dd ci: open pull requests to merge dev to main (#131) 2022-12-13 23:36:47 +01:00
oSumAtrIX
c2a5a55e67 refactor: remove unnecessary test 2022-12-11 03:01:41 +01:00
oSumAtrIX
6c5de8b414 ci: refactor release workflow 2022-12-11 03:01:40 +01:00
semantic-release-bot
ea773cfa56 chore(release): 6.3.0 [skip ci]
# [6.3.0](https://github.com/revanced/revanced-patcher/compare/v6.2.0...v6.3.0) (2022-12-02)

### Features

* sort patches in lexicographical order ([a306561](a306561b55)), closes [#125](https://github.com/revanced/revanced-patcher/issues/125)
2022-12-02 02:32:49 +00:00
oSumAtrIX
a306561b55 feat: sort patches in lexicographical order
Closes #125
2022-12-02 03:31:25 +01:00
semantic-release-bot
b6dcd88495 chore(release): 6.2.0 [skip ci]
# [6.2.0](https://github.com/revanced/revanced-patcher/compare/v6.1.1...v6.2.0) (2022-12-02)

### Features

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

### Bug Fixes

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

### Features

* apply changes from ReVanced Patcher ([ba9d998](ba9d998681))
2022-11-22 23:30:33 +00:00
oSumAtrIX
ba9d998681 feat: apply changes from ReVanced Patcher
BREAKING-CHANGE: Some annotations have been removed regarding fingerprints and patches.
2022-11-23 00:29:12 +01:00
semantic-release-bot
75df245ec3 chore(release): 6.0.2 [skip ci]
## [6.0.2](https://github.com/revanced/revanced-patcher/compare/v6.0.1...v6.0.2) (2022-11-18)

### Bug Fixes

* fallback to patch class name instead of `java.lang.Class` class name ([4164cb0](4164cb0dea))
2022-11-18 01:20:19 +00:00
oSumAtrIX
4164cb0dea fix: fallback to patch class name instead of java.lang.Class class name 2022-11-18 02:14:20 +01:00
semantic-release-bot
18fe35ae73 chore(release): 6.0.1 [skip ci]
## [6.0.1](https://github.com/revanced/revanced-patcher/compare/v6.0.0...v6.0.1) (2022-11-14)

### Bug Fixes

* remove unnecessary dummy nop instructions ([#111](https://github.com/revanced/revanced-patcher/issues/111)) ([f9bc95f](f9bc95f220))
2022-11-14 15:59:22 +00:00
MewtR
f9bc95f220 fix: remove unnecessary dummy nop instructions (#111) 2022-11-14 16:57:13 +01:00
Nico Mexis
d2f91a8545 build: update workflow actions (#121) [skip ci] 2022-11-05 15:27:56 +01:00
semantic-release-bot
4016bdc37f chore(release): 6.0.0 [skip ci]
# [6.0.0](https://github.com/revanced/revanced-patcher/compare/v5.1.2...v6.0.0) (2022-10-05)

### Code Refactoring

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

### Features

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

### BREAKING CHANGES

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

### Bug Fixes

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

### Performance Improvements

* decode resources only when necessary ([3ba4be2](3ba4be240b))
2022-09-26 06:59:37 +00:00
oSumAtrIX
3ba4be240b perf: decode resources only when necessary 2022-09-26 08:57:39 +02:00
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
semantic-release-bot
567bf52e16 chore(release): 3.2.0 [skip ci]
# [3.2.0](https://github.com/revanced/revanced-patcher/compare/v3.1.0...v3.2.0) (2022-08-02)

### Features

* PatchOptions#nullify to nullify an option ([371f0c4](371f0c4d0b))
2022-08-02 20:36:21 +00:00
Sculas
35c6489dba Merge remote-tracking branch 'origin/main' into main 2022-08-02 22:34:44 +02:00
Sculas
371f0c4d0b feat: PatchOptions#nullify to nullify an option 2022-08-02 22:32:55 +02:00
Sculas
1b42f65d95 refactor: migrate to custom exceptions for patch options 2022-08-02 22:16:37 +02:00
semantic-release-bot
2aee0cbd0f chore(release): 3.1.0 [skip ci]
# [3.1.0](https://github.com/revanced/revanced-patcher/compare/v3.0.0...v3.1.0) (2022-08-02)

### Features

* validator for patch options ([4e2e772](4e2e772389))
2022-08-02 20:02:18 +00:00
Sculas
19256b5437 Merge remote-tracking branch 'origin/main' into main 2022-08-02 22:00:40 +02:00
Sculas
67a5237541 test: refactor & add more tests 2022-08-02 22:00:32 +02:00
Sculas
4e2e772389 feat: validator for patch options 2022-08-02 22:00:10 +02:00
semantic-release-bot
799bc9e163 chore(release): 3.0.0 [skip ci]
# [3.0.0](https://github.com/revanced/revanced-patcher/compare/v2.9.0...v3.0.0) (2022-08-02)

### Features

* registry for patch options ([2431785](2431785d0e))

### BREAKING CHANGES

* Patch options now use the PatchOptions registry class instead of an Iterable. This change requires modifications to existing patches using this API.
2022-08-02 19:11:43 +00:00
Sculas
2431785d0e feat: registry for patch options
BREAKING CHANGE: Patch options now use the PatchOptions registry class instead of an Iterable. This change requires modifications to existing patches using this API.
2022-08-02 21:10:14 +02:00
semantic-release-bot
fb3c0e87d4 chore(release): 2.9.0 [skip ci]
# [2.9.0](https://github.com/revanced/revanced-patcher/compare/v2.8.0...v2.9.0) (2022-08-02)

### Bug Fixes

* show error message instead of `null` ([8d95b14](8d95b14f35))

### Features

* exclusive mutable access to files ([814ce0b](814ce0b9ae))
2022-08-02 01:11:01 +00:00
oSumAtrIX
5f7ef2dbff Merge remote-tracking branch 'origin/main' 2022-08-02 03:09:02 +02:00
Sculas
ec1d8a8fba refactor: remove DependencyType.SOFT (#82)
BREAKING-CHANGE: DependencyType.SOFT has now been removed after it was deprecated. There is no direct replacement for this. Please look into Patch Options instead, which supersedes this.
2022-08-02 00:47:32 +02:00
oSumAtrIX
814ce0b9ae feat: exclusive mutable access to files 2022-08-02 00:42:38 +02:00
oSumAtrIX
8d95b14f35 fix: show error message instead of null 2022-08-02 00:42:38 +02:00
semantic-release-bot
711b8a25a7 chore(release): 2.8.0 [skip ci]
# [2.8.0](https://github.com/revanced/revanced-patcher/compare/v2.7.0...v2.8.0) (2022-08-01)

### Bug Fixes

* remove requirement for solution [skip ci] ([#80](https://github.com/revanced/revanced-patcher/issues/80)) ([9a4d30e](9a4d30e152))

### Features

* patch options ([#81](https://github.com/revanced/revanced-patcher/issues/81)) ([fbb09f3](fbb09f38dc))
2022-08-01 22:32:32 +00:00
Sculas
fbb09f38dc feat: patch options (#81)
* feat: Patch Options

* refactor: remove delegate property

* feat: List patch options

* refactor: add setter example to PatchOptionsUsage.kt

* docs: add docs for PatchOptions

* docs: tidy docs
2022-08-02 00:31:00 +02:00
Robert
9a4d30e152 fix: remove requirement for solution [skip ci] (#80) 2022-08-01 13:07:12 +02:00
semantic-release-bot
368c61c1bf chore(release): 2.7.0 [skip ci]
# [2.7.0](https://github.com/revanced/revanced-patcher/compare/v2.6.0...v2.7.0) (2022-08-01)

### Features

* `Closeable` patches ([bbd40bf](bbd40bf2f6))
2022-08-01 02:15:18 +00:00
oSumAtrIX
bbd40bf2f6 feat: Closeable patches 2022-08-01 04:12:33 +02:00
Sculas
3de999a2d3 refactor: remove ReplaceWith from DependencyType.SOFT 2022-07-31 18:35:02 +02:00
Sculas
83cbb2f110 Merge remote-tracking branch 'origin/main' into main 2022-07-31 18:31:50 +02:00
Sculas
fcc7fa75d0 refactor: deprecate DependencyType.SOFT
Soft dependencies will be removed when Patch Options is implemented.
2022-07-31 18:31:40 +02:00
semantic-release-bot
495ebface8 chore(release): 2.6.0 [skip ci]
# [2.6.0](https://github.com/revanced/revanced-patcher/compare/v2.5.2...v2.6.0) (2022-07-31)

### Features

* add Patch#dependsOn extension ([523f67b](523f67b238))
* Soft Dependencies for Patches ([8c12f8d](8c12f8d488))
2022-07-31 14:52:23 +00:00
Sculas
8c12f8d488 feat: Soft Dependencies for Patches 2022-07-31 16:50:59 +02:00
Sculas
523f67b238 feat: add Patch#dependsOn extension 2022-07-31 15:07:23 +02:00
Sculas
4813a8b48e chore: remove import 2022-07-31 15:01:38 +02:00
Sculas
d5c66022c9 refactor: get rid of package names in example 2022-07-31 15:01:13 +02:00
Lucaskyy
1d4034b36c chore: add description to readme 2022-07-31 14:41:41 +02:00
oSumAtrIX
dd2d696d00 docs: add missing . to feature-issue.yml description 2022-07-26 19:38:00 +02:00
oSumAtrIX
6326321b65 docs: add missing . to issue description 2022-07-26 19:34:39 +02:00
oSumAtrIX
f291a4ae3e docs: GitHub issue forms 2022-07-26 19:28:32 +02:00
semantic-release-bot
817b8db019 chore(release): 2.5.2 [skip ci]
## [2.5.2](https://github.com/revanced/revanced-patcher/compare/v2.5.1...v2.5.2) (2022-07-24)
2022-07-24 16:30:57 +00:00
Sculas
a321b8971b build: update Apktool to 2.7.0 2022-07-24 18:29:25 +02:00
semantic-release-bot
783b2de9db chore(release): 2.5.1 [skip ci]
## [2.5.1](https://github.com/revanced/revanced-patcher/compare/v2.5.0...v2.5.1) (2022-07-17)

### Bug Fixes

* close stream when closing `DomFileEditor` ([77604d4](77604d4078))
2022-07-17 23:30:04 +00:00
oSumAtrIX
77604d4078 fix: close stream when closing DomFileEditor 2022-07-18 01:28:16 +02:00
oSumAtrIX
21b5404180 docs: change size of headings [skip ci] 2022-07-12 01:12:45 +02:00
semantic-release-bot
9ac6d5c7da chore(release): 2.5.0 [skip ci]
# [2.5.0](https://github.com/revanced/revanced-patcher/compare/v2.4.0...v2.5.0) (2022-07-11)

### Bug Fixes

* missing additional items [skip ci] ([0ebab8b](0ebab8bf59))

### Features

* feature request issue template ([1b39278](1b39278b24))
* issue templates [skip ci] ([112bc99](112bc998f4))
2022-07-11 18:21:14 +00:00
oSumAtrIX
1b39278b24 feat: feature request issue template 2022-07-11 20:19:53 +02:00
oSumAtrIX
0ebab8bf59 fix: missing additional items [skip ci] 2022-07-10 00:34:55 +02:00
oSumAtrIX
112bc998f4 feat: issue templates [skip ci] 2022-07-10 00:32:05 +02:00
semantic-release-bot
12c96bf818 chore(release): 2.4.0 [skip ci]
# [2.4.0](https://github.com/revanced/revanced-patcher/compare/v2.3.1...v2.4.0) (2022-07-09)

### Features

* Improve Smali Compiler ([6bfe571](6bfe5716c3))
2022-07-09 13:04:21 +00:00
oSumAtrIX
91298a8790 Merge pull request #52 from revanced/feat/smali-branching
feat: improve Smali compiler
2022-07-09 15:03:01 +02:00
oSumAtrIX
f2a7cff41c style: fix casing of the first letter in comments 2022-07-09 06:27:49 +02:00
oSumAtrIX
dd941233ca refactor: improve the addInstructions extension method further more 2022-07-09 06:26:05 +02:00
semantic-release-bot
fc06dd1c29 chore(release): 2.3.1 [skip ci]
## [2.3.1](https://github.com/revanced/revanced-patcher/compare/v2.3.0...v2.3.1) (2022-07-07)

### Bug Fixes

* handle null properly ([#64](https://github.com/revanced/revanced-patcher/issues/64)) ([482af78](482af78f2b))
2022-07-07 06:35:28 +00:00
bogadana
482af78f2b fix: handle null properly (#64) 2022-07-07 08:33:40 +02:00
semantic-release-bot
89a27dfbe6 chore(release): 2.3.0 [skip ci]
# [2.3.0](https://github.com/revanced/revanced-patcher/compare/v2.2.2...v2.3.0) (2022-07-05)

### Features

* nullability for `BytecodePatch` constructor ([#59](https://github.com/revanced/revanced-patcher/issues/59)) ([4ea030d](4ea030d0a0))
2022-07-05 14:48:22 +00:00
bogadana
4ea030d0a0 feat: nullability for BytecodePatch constructor (#59) 2022-07-05 16:46:54 +02:00
semantic-release-bot
4cc2fa17f5 chore(release): 2.2.2 [skip ci]
## [2.2.2](https://github.com/revanced/revanced-patcher/compare/v2.2.1...v2.2.2) (2022-07-04)

### Bug Fixes

* `MethodWalker` not accounting for all reference instructions ([48068cb](48068cb3d7))
2022-07-04 19:36:34 +00:00
oSumAtrIX
48068cb3d7 fix: MethodWalker not accounting for all reference instructions 2022-07-03 22:51:04 +02:00
semantic-release-bot
d107c7245c chore(release): 2.2.1 [skip ci]
## [2.2.1](https://github.com/revanced/revanced-patcher/compare/v2.2.0...v2.2.1) (2022-07-03)

### Bug Fixes

* more useful error message ([4b2e323](4b2e3230ec))
2022-07-03 14:46:19 +00:00
oSumAtrIX
4b2e3230ec fix: more useful error message 2022-07-03 15:37:48 +02:00
semantic-release-bot
fb5b82da4e chore(release): 2.2.0 [skip ci]
# [2.2.0](https://github.com/revanced/revanced-patcher/compare/v2.1.2...v2.2.0) (2022-07-02)

### Bug Fixes

* DomFileEditor opening in- and output streams on the same file ([83187c9](83187c9edd))

### Features

* remove deprecated functions ([ada5a03](ada5a033de))
* streams overload for `XmlFileHolder` ([6f72c4c](6f72c4c4c0))
2022-07-02 22:59:27 +00:00
oSumAtrIX
5970e32aa5 Merge pull request #57 from revanced/dev
Merge `dev` to `main`
2022-07-03 00:30:37 +02:00
semantic-release-bot
0f00d33f4e chore(release): 2.2.0-dev.3 [skip ci]
# [2.2.0-dev.3](https://github.com/revanced/revanced-patcher/compare/v2.2.0-dev.2...v2.2.0-dev.3) (2022-07-02)

### Bug Fixes

* DomFileEditor opening in- and output streams on the same file ([83187c9](83187c9edd))
2022-07-02 22:21:19 +00:00
oSumAtrIX
83187c9edd fix: DomFileEditor opening in- and output streams on the same file 2022-07-03 00:19:39 +02:00
semantic-release-bot
79d70cff4b chore(release): 2.2.0-dev.2 [skip ci]
# [2.2.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v2.2.0-dev.1...v2.2.0-dev.2) (2022-07-02)

### Features

* streams overload for `XmlFileHolder` ([6f72c4c](6f72c4c4c0))
2022-07-02 15:46:16 +00:00
oSumAtrIX
6f72c4c4c0 feat: streams overload for XmlFileHolder 2022-07-02 17:44:08 +02:00
oSumAtrIX
c8eedac4d9 refactor: members of ResourceData 2022-07-02 16:22:19 +02:00
semantic-release-bot
60a8278ae8 chore(release): 2.2.0-dev.1 [skip ci]
# [2.2.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v2.1.2...v2.2.0-dev.1) (2022-07-02)

### Features

* remove deprecated functions ([ada5a03](ada5a033de))
2022-07-02 02:23:32 +00:00
oSumAtrIX
ada5a033de feat: remove deprecated functions 2022-07-02 04:21:20 +02:00
semantic-release-bot
109b8a296d chore(release): 2.1.2 [skip ci]
## [2.1.2](https://github.com/revanced/revanced-patcher/compare/v2.1.1...v2.1.2) (2022-06-29)

### Bug Fixes

* invert fingerprint resolution condition of `customFingerprint` ([e2faf4c](e2faf4ca9b))
2022-06-29 23:37:37 +00:00
oSumAtrIX
e2faf4ca9b fix: invert fingerprint resolution condition of customFingerprint 2022-06-30 01:36:25 +02:00
oSumAtrIX
2134182a0e refactor: declare data parameter internal 2022-06-29 19:55:23 +02:00
semantic-release-bot
d8b5b8bb7c chore(release): 2.1.1 [skip ci]
## [2.1.1](https://github.com/revanced/revanced-patcher/compare/v2.1.0...v2.1.1) (2022-06-28)
2022-06-28 17:47:39 +00:00
Sculas
49970b5926 build: update Apktool to re-enable 9patch decoder 2022-06-28 19:45:45 +02:00
semantic-release-bot
c1fbd8cf8c chore(release): 2.1.0 [skip ci]
# [2.1.0](https://github.com/revanced/revanced-patcher/compare/v2.0.4...v2.1.0) (2022-06-28)

### Features

* log failed patches due to failed dependencies ([a467fbb](a467fbb704))
2022-06-28 01:27:05 +00:00
oSumAtrIX
a467fbb704 feat: log failed patches due to failed dependencies 2022-06-28 03:25:49 +02:00
semantic-release-bot
5a4bd7a76e chore(release): 2.0.4 [skip ci]
## [2.0.4](https://github.com/revanced/revanced-patcher/compare/v2.0.3...v2.0.4) (2022-06-27)
2022-06-27 22:38:36 +00:00
Sculas
e5ca86fac6 build: update Apktool to disable 9patch decoder
for real for real, this time
2022-06-28 00:36:50 +02:00
semantic-release-bot
68d9e9f02c chore(release): 2.0.3 [skip ci]
## [2.0.3](https://github.com/revanced/revanced-patcher/compare/v2.0.2...v2.0.3) (2022-06-27)
2022-06-27 22:22:19 +00:00
Sculas
494a9a09ac build: update Apktool to disable 9patch decoder
for real, this time
2022-06-28 00:20:54 +02:00
semantic-release-bot
06a88839de chore(release): 2.0.2 [skip ci]
## [2.0.2](https://github.com/revanced/revanced-patcher/compare/v2.0.1...v2.0.2) (2022-06-27)
2022-06-27 22:05:56 +00:00
Sculas
4c41b955df ci: trigger release on build commits 2022-06-28 00:04:39 +02:00
Sculas
614e555f4c build: update Apktool to disable 9patch decoder 2022-06-28 00:01:38 +02:00
Lucaskyy
6bfe5716c3 feat: Improve Smali Compiler
- Branching support has been added. See InlineSmaliCompilerTest.kt for an example.
- Some other improvements have been made too.
2022-06-27 21:11:59 +02:00
semantic-release-bot
d6ed06a327 chore(release): 2.0.1 [skip ci]
## [2.0.1](https://github.com/revanced/revanced-patcher/compare/v2.0.0...v2.0.1) (2022-06-26)

### Bug Fixes

* use `Exception` instead of `MethodNotFoundException` ([2fc4ec4](2fc4ec4021))
2022-06-26 16:03:10 +00:00
oSumAtrIX
2fc4ec4021 fix: use Exception instead of MethodNotFoundException 2022-06-26 18:01:54 +02:00
Lucaskyy
f565c4f6a7 refactor: improve ResourceData.kt
Old methods have been marked as deprecated, and will be removed in the future.

- ResourceData.kt was made an Iterable<File>, and the forEach method was removed in favor of Kotlin's forEach function. (no modifications required)
- The resolve method was deprecated in favor of a new operator getter function, which can be either called using get(path) or data[path]. This keeps backwards compatibility with the old get method.
- The getXmlEditor method was deprecated in favor of the new xmlEditor variable, which is a XmlFileHolder which has an operator getter which acts like an array. This is syntactically better.
2022-06-26 17:14:38 +02:00
Lucaskyy
35749454ab refactor: remove deprecated methods in ResourceData.kt 2022-06-26 16:53:25 +02:00
Lucaskyy
2b492e7a5e refactor: cleanup code 2022-06-26 16:32:22 +02:00
Lucaskyy
081a5a6849 refactor: fmt ExampleBytecodePatch.kt 2022-06-26 16:21:55 +02:00
Lucaskyy
852ae7d8d1 refactor: move BytecodeData#proxy into class 2022-06-26 16:20:45 +02:00
semantic-release-bot
52f8a6a2eb chore(release): 2.0.0 [skip ci]
# [2.0.0](https://github.com/revanced/revanced-patcher/compare/v1.11.0...v2.0.0) (2022-06-26)

### Code Refactoring

* migrate from `Signature` to `Fingerprint` ([efa8ea1](efa8ea1445))

### BREAKING CHANGES

* Not backwards compatible, since a lot of classes where renamed.
2022-06-26 13:12:24 +00:00
oSumAtrIX
efa8ea1445 refactor: migrate from Signature to Fingerprint
BREAKING CHANGE: Not backwards compatible, since a lot of classes where renamed.
2022-06-26 15:10:54 +02:00
semantic-release-bot
c828fa2a27 chore(release): 1.11.0 [skip ci]
# [1.11.0](https://github.com/revanced/revanced-patcher/compare/v1.10.2...v1.11.0) (2022-06-24)

### Features

* add replace and remove extensions ([#50](https://github.com/revanced/revanced-patcher/issues/50)) ([92ac5e4](92ac5e4dc2))
2022-06-24 16:49:12 +00:00
bogadana
92ac5e4dc2 feat: add replace and remove extensions (#50) 2022-06-24 18:47:37 +02:00
semantic-release-bot
070c09cf71 chore(release): 1.10.2 [skip ci]
## [1.10.2](https://github.com/revanced/revanced-patcher/compare/v1.10.1...v1.10.2) (2022-06-23)

### Bug Fixes

* dexlib must be propagated ([b738dcd](b738dcd7ea))
2022-06-23 10:21:25 +00:00
Sculas
b738dcd7ea fix: dexlib must be propagated 2022-06-23 12:20:04 +02:00
semantic-release-bot
8efcf329bb chore(release): 1.10.1 [skip ci]
## [1.10.1](https://github.com/revanced/revanced-patcher/compare/v1.10.0...v1.10.1) (2022-06-23)

### Bug Fixes

* callback only when inteded ([e3bf367](e3bf367ad6))
* mutability of local variable `modified` ([0e87ef5](0e87ef56c4))
2022-06-23 06:59:43 +00:00
oSumAtrIX
0e87ef56c4 fix: mutability of local variable modified 2022-06-23 08:57:41 +02:00
oSumAtrIX
e3bf367ad6 fix: callback only when inteded 2022-06-23 08:54:47 +02:00
semantic-release-bot
3d61dacbda chore(release): 1.10.0 [skip ci]
# [1.10.0](https://github.com/revanced/revanced-patcher/compare/v1.9.0...v1.10.0) (2022-06-23)

### Features

* improve logging ([c20dfe1](c20dfe12d5))
2022-06-23 00:12:04 +00:00
oSumAtrIX
c20dfe12d5 feat: improve logging 2022-06-23 02:10:43 +02:00
semantic-release-bot
78663cde88 chore(release): 1.9.0 [skip ci]
# [1.9.0](https://github.com/revanced/revanced-patcher/compare/v1.8.0...v1.9.0) (2022-06-22)

### Bug Fixes

* callback for each file instead of class ([930768d](930768dfb3))

### Features

* yield the patch result ([dde5385](dde5385232))
2022-06-22 23:43:17 +00:00
oSumAtrIX
dde5385232 feat: yield the patch result 2022-06-23 01:41:52 +02:00
oSumAtrIX
930768dfb3 fix: callback for each file instead of class 2022-06-23 01:41:52 +02:00
semantic-release-bot
1f4bc5079f chore(release): 1.8.0 [skip ci]
# [1.8.0](https://github.com/revanced/revanced-patcher/compare/v1.7.2...v1.8.0) (2022-06-22)

### Features

* logging class ([caf2745](caf2745805))
2022-06-22 23:25:29 +00:00
oSumAtrIX
caf2745805 feat: logging class 2022-06-23 01:23:35 +02:00
oSumAtrIX
a4529c3fee refactor: logging and exception strings 2022-06-22 16:47:58 +02:00
semantic-release-bot
835c0f9f7a chore(release): 1.7.2 [skip ci]
## [1.7.2](https://github.com/revanced/revanced-patcher/compare/v1.7.1...v1.7.2) (2022-06-22)

### Bug Fixes

* add execute permission to `./gradlew` file ([#46](https://github.com/revanced/revanced-patcher/issues/46)) ([34f607a](34f607aa24))
2022-06-22 14:17:16 +00:00
Oskar
34f607aa24 fix: add execute permission to ./gradlew file (#46) 2022-06-22 16:16:00 +02:00
Lucaskyy
0f38b94701 Merge remote-tracking branch 'origin/main' into main 2022-06-22 16:13:34 +02:00
Lucaskyy
39bb1b25dc refactor: add logging 2022-06-22 16:13:23 +02:00
Lucaskyy
4fc63a4d8a refactor: add callbacks for applyPatches & addFiles 2022-06-22 15:37:33 +02:00
semantic-release-bot
6037397bc2 chore(release): 1.7.1 [skip ci]
## [1.7.1](https://github.com/revanced/revanced-patcher/compare/v1.7.0...v1.7.1) (2022-06-22)

### Reverts

* revert "feat: use of `java.util.logging.Logger`" ([e8488b3](e8488b3e86))
2022-06-22 13:07:22 +00:00
Lucaskyy
273dd86b65 chore: remove ExtFile import 2022-06-22 15:04:05 +02:00
Lucaskyy
e8488b3e86 revert "feat: use of java.util.logging.Logger"
This reverts commit 9c39c9efdb.
This reverts commit 8f66f9f606.
2022-06-22 15:03:30 +02:00
semantic-release-bot
c13361823d chore(release): 1.7.0 [skip ci]
# [1.7.0](https://github.com/revanced/revanced-patcher/compare/v1.6.0...v1.7.0) (2022-06-22)

### Features

* migrate logger to `slf4j` ([8f66f9f](8f66f9f606))
2022-06-22 12:18:57 +00:00
oSumAtrIX
8f66f9f606 feat: migrate logger to slf4j 2022-06-22 14:17:09 +02:00
semantic-release-bot
a123026f46 chore(release): 1.6.0 [skip ci]
# [1.6.0](https://github.com/revanced/revanced-patcher/compare/v1.5.0...v1.6.0) (2022-06-22)

### Features

* use of `java.util.logging.Logger` ([9c39c9e](9c39c9efdb))
2022-06-22 11:48:43 +00:00
oSumAtrIX
9c39c9efdb feat: use of java.util.logging.Logger 2022-06-22 13:45:13 +02:00
semantic-release-bot
3ee1c01430 chore(release): 1.5.0 [skip ci]
# [1.5.0](https://github.com/revanced/revanced-patcher/compare/v1.4.0...v1.5.0) (2022-06-22)

### Features

* use streams to write the dex files ([64bae88](64bae884dc))
2022-06-22 01:21:33 +00:00
oSumAtrIX
64bae884dc feat: use streams to write the dex files 2022-06-22 03:19:39 +02:00
semantic-release-bot
e94a706949 chore(release): 1.4.0 [skip ci]
# [1.4.0](https://github.com/revanced/revanced-patcher/compare/v1.3.4...v1.4.0) (2022-06-22)

### Features

* return a `File` instance instead of `ExtFile` ([68174bb](68174bbd6b))
2022-06-22 00:55:56 +00:00
oSumAtrIX
89bb43066b build: use dependencies as implementations instead of apis 2022-06-22 02:54:23 +02:00
oSumAtrIX
68174bbd6b feat: return a File instance instead of ExtFile 2022-06-22 02:53:37 +02:00
semantic-release-bot
d05c9416d6 chore(release): 1.3.4 [skip ci]
## [1.3.4](https://github.com/revanced/revanced-patcher/compare/v1.3.3...v1.3.4) (2022-06-21)

### Bug Fixes

* `String.toInstructions` defaulting `forStaticMethod` to `false` ([5a2f02b](5a2f02b97d)), closes [revanced/revanced-patches#46](https://github.com/revanced/revanced-patches/issues/46)
2022-06-21 21:53:33 +00:00
oSumAtrIX
5a2f02b97d fix: String.toInstructions defaulting forStaticMethod to false
Fixes revanced/revanced-patches#46
2022-06-21 23:52:09 +02:00
semantic-release-bot
a3005fa08e chore(release): 1.3.3 [skip ci]
## [1.3.3](https://github.com/revanced/revanced-patcher/compare/v1.3.2...v1.3.3) (2022-06-21)

### Bug Fixes

* add docs (trigger release) ([6628b78](6628b7870f))

### Reverts

* propagate dependencies ([365e1d7](365e1d7a45))
2022-06-21 19:04:06 +00:00
Lucaskyy
6628b7870f fix: add docs (trigger release) 2022-06-21 21:02:50 +02:00
Lucaskyy
a6411245aa Merge remote-tracking branch 'origin/main' into main 2022-06-21 20:50:11 +02:00
Lucaskyy
365e1d7a45 revert: propagate dependencies
guess it doesn't work when not propagating them
2022-06-21 20:50:00 +02:00
semantic-release-bot
4507cd2353 chore(release): 1.3.2 [skip ci]
## [1.3.2](https://github.com/revanced/revanced-patcher/compare/v1.3.1...v1.3.2) (2022-06-21)

### Bug Fixes

* return resourceFile to caller ([1f75777](1f75777cf9))
2022-06-21 18:45:16 +00:00
Lucaskyy
1f75777cf9 fix: return resourceFile to caller 2022-06-21 20:43:47 +02:00
Lucaskyy
28d5468b07 build: do not propagate all dependencies 2022-06-21 20:29:37 +02:00
semantic-release-bot
746496125d chore(release): 1.3.1 [skip ci]
## [1.3.1](https://github.com/revanced/revanced-patcher/compare/v1.3.0...v1.3.1) (2022-06-21)

### Bug Fixes

* `InlineSmaliCompiler.compile` using 0 registers instead of 1 by default ([835a421](835a421cc0))
2022-06-21 15:48:43 +00:00
oSumAtrIX
835a421cc0 fix: InlineSmaliCompiler.compile using 0 registers instead of 1 by default 2022-06-21 17:46:43 +02:00
semantic-release-bot
99342fe033 chore(release): 1.3.0 [skip ci]
# [1.3.0](https://github.com/revanced/revanced-patcher/compare/v1.2.9...v1.3.0) (2022-06-20)

### Features

* `parametersCount` for `InlineSmaliCompiler` instead of `parameters` ([ad6c5c8](ad6c5c8273))
* simplify adding instructions ([e47b67d](e47b67d7ec))
2022-06-20 19:22:22 +00:00
oSumAtrIX
e47b67d7ec feat: simplify adding instructions 2022-06-20 21:20:51 +02:00
oSumAtrIX
ad6c5c8273 feat: parametersCount for InlineSmaliCompiler instead of parameters 2022-06-20 20:10:11 +02:00
oSumAtrIX
fd690acd61 refactor: add internal attribute to signatures field 2022-06-20 20:10:06 +02:00
semantic-release-bot
e698b02bf6 chore(release): 1.2.9 [skip ci]
## [1.2.9](https://github.com/revanced/revanced-patcher/compare/v1.2.8...v1.2.9) (2022-06-20)

### Bug Fixes

* update apktool ([ab866bb](ab866bb8ef))
2022-06-20 15:08:57 +00:00
Lucaskyy
ab866bb8ef fix: update apktool 2022-06-20 17:07:04 +02:00
semantic-release-bot
714a98422d chore(release): 1.2.8 [skip ci]
## [1.2.8](https://github.com/revanced/revanced-patcher/compare/v1.2.7...v1.2.8) (2022-06-18)

### Bug Fixes

* update apktool ([051afd9](051afd98d0))
2022-06-18 21:51:07 +00:00
Sculas
051afd98d0 fix: update apktool 2022-06-18 23:49:41 +02:00
semantic-release-bot
d38cf6a229 chore(release): 1.2.7 [skip ci]
## [1.2.7](https://github.com/revanced/revanced-patcher/compare/v1.2.6...v1.2.7) (2022-06-18)

### Bug Fixes

* version not working with apktool due to cache ([03f5ee0](03f5ee088b))
2022-06-18 21:17:22 +00:00
Sculas
03f5ee088b fix: version not working with apktool due to cache 2022-06-18 23:15:33 +02:00
semantic-release-bot
5d0fd48b15 chore(release): 1.2.6 [skip ci]
## [1.2.6](https://github.com/revanced/revanced-patcher/compare/v1.2.5...v1.2.6) (2022-06-18)

### Bug Fixes

* remove javadoc jar (also trigger release) ([56f6ca3](56f6ca3891))
2022-06-18 20:54:29 +00:00
Sculas
56f6ca3891 fix: remove javadoc jar (also trigger release) 2022-06-18 22:52:49 +02:00
semantic-release-bot
9e0a74fcfb chore(release): 1.2.5 [skip ci]
## [1.2.5](https://github.com/revanced/revanced-patcher/compare/v1.2.4...v1.2.5) (2022-06-17)

### Bug Fixes

* goodbye security ([8f3ac77](8f3ac7702a))
2022-06-17 16:13:47 +00:00
Sculas
8f3ac7702a fix: goodbye security 2022-06-17 18:12:18 +02:00
semantic-release-bot
7b65f2d02c chore(release): 1.2.4 [skip ci]
## [1.2.4](https://github.com/revanced/revanced-patcher/compare/v1.2.3...v1.2.4) (2022-06-15)

### Reverts

* "fix: enforce aapt v1" ([dfd8a24](dfd8a24512))
2022-06-15 19:41:28 +00:00
Lucaskyy
2a1b2df56b Merge remote-tracking branch 'origin/main' into main 2022-06-15 21:39:42 +02:00
Lucaskyy
dfd8a24512 revert: "fix: enforce aapt v1"
This reverts commit cff87ff077.
2022-06-15 21:39:27 +02:00
semantic-release-bot
a3efd212fc chore(release): 1.2.3 [skip ci]
## [1.2.3](https://github.com/revanced/revanced-patcher/compare/v1.2.2...v1.2.3) (2022-06-14)

### Bug Fixes

* enforce aapt v1 ([cff87ff](cff87ff077))
2022-06-14 22:26:32 +00:00
Lucaskyy
3e610f7ba9 Merge remote-tracking branch 'origin/main' into main 2022-06-15 00:25:21 +02:00
Lucaskyy
cff87ff077 fix: enforce aapt v1 2022-06-15 00:25:06 +02:00
semantic-release-bot
54aa04ca34 chore(release): 1.2.2 [skip ci]
## [1.2.2](https://github.com/revanced/revanced-patcher/compare/v1.2.1...v1.2.2) (2022-06-14)

### Bug Fixes

* enforce aapt v2 ([b68b0bf](b68b0bf3d7))
2022-06-14 20:35:38 +00:00
Lucaskyy
0eda84eaef Merge remote-tracking branch 'origin/main' into main 2022-06-14 22:34:21 +02:00
Lucaskyy
b68b0bf3d7 fix: enforce aapt v2 2022-06-14 22:34:10 +02:00
semantic-release-bot
c692202f67 chore(release): 1.2.1 [skip ci]
## [1.2.1](https://github.com/revanced/revanced-patcher/compare/v1.2.0...v1.2.1) (2022-06-14)

### Bug Fixes

* Patcher setting BuildOptions too late ([6a5c873](6a5c8735fb))
2022-06-14 16:25:08 +00:00
Lucaskyy
8ce3535427 Merge remote-tracking branch 'origin/main' into main 2022-06-14 18:23:49 +02:00
Lucaskyy
6a5c8735fb fix: Patcher setting BuildOptions too late
This causes the Manager to crash, due to a stupid bug in Apktool which is prevented by setting a valid frameworkFolderLocation.
2022-06-14 18:23:40 +02:00
semantic-release-bot
8f32bc9c08 chore(release): 1.2.0 [skip ci]
# [1.2.0](https://github.com/revanced/revanced-patcher/compare/v1.1.0...v1.2.0) (2022-06-14)

### Features

* allow custom framework path to be specified ([d3a580e](d3a580ea19))
2022-06-14 15:50:24 +00:00
Lucaskyy
d3a580ea19 feat: allow custom framework path to be specified 2022-06-14 17:49:07 +02:00
oSumAtrIX
4c8eb0e5c7 refactor: gradle build script 2022-06-14 01:34:48 +02:00
semantic-release-bot
bc92eb7fd8 chore(release): 1.1.0 [skip ci]
# [1.1.0](https://github.com/revanced/revanced-patcher/compare/v1.0.0...v1.1.0) (2022-06-11)

### Bug Fixes

* resource patcher ([31815ca](31815ca9ea))
* update apktool to fork ([566ecef](566ecefa2b))

### Features

* allow custom aapt path to be specified ([8eb4a8f](8eb4a8f87a))
2022-06-11 23:25:19 +00:00
oSumAtrIX
cf89bd4171 Merge pull request #39 from revanced/non-root
fix: resource patcher
2022-06-12 01:24:04 +02:00
Lucaskyy
566ecefa2b fix: update apktool to fork 2022-06-11 20:39:29 +02:00
Lucaskyy
8eb4a8f87a feat: allow custom aapt path to be specified 2022-06-11 20:08:00 +02:00
oSumAtrIX
afcba5c212 refactor: simplify code by removing the with block 2022-06-11 19:45:11 +02:00
oSumAtrIX
2dcbd8d079 refactor: use include annotation parameter instead of excludeByDefault 2022-06-11 19:14:19 +02:00
oSumAtrIX
81895c7d5c Merge remote-tracking branch 'origin/non-root' into non-root
# Conflicts:
#	src/main/kotlin/app/revanced/patcher/Patcher.kt
2022-06-11 18:56:38 +02:00
Lucaskyy
22267883b1 refactor: get rid of all useless let blocks 2022-06-11 16:56:05 +02:00
oSumAtrIX
26fca60b53 refactor: use also instead of let where possible 2022-06-11 16:24:49 +02:00
oSumAtrIX
31815ca9ea fix: resource patcher 2022-06-11 06:36:13 +02:00
83 changed files with 9994 additions and 1436 deletions

72
.github/ISSUE_TEMPLATE/bug-issue.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: 🐞 Bug report
description: Report a very clearly broken issue.
title: 'bug: <title>'
labels: [bug]
body:
- type: markdown
attributes:
value: |
# ReVanced bug report
Important to note that your issue may have already been reported before. Please check for existing issues [here](https://github.com/revanced/revanced-patcher/labels/bug).
- type: dropdown
attributes:
label: Type
options:
- Crash
- Cosmetic
- Other
validations:
required: true
- type: textarea
attributes:
label: Bug description
description: How did you find the bug? Any additional details that might help?
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Add the steps to reproduce this bug including your environment.
placeholder: Step 1. Download some files. Step 2. ...
validations:
required: true
- type: textarea
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
validations:
required: true
- type: textarea
attributes:
label: Screenshots or videos
description: Add screenshots or videos that show the bug here.
placeholder: Drag and drop the screenshots/videos into this box.
validations:
required: false
- type: textarea
attributes:
label: Solution
description: If applicable, add a possible solution.
validations:
required: false
- type: textarea
attributes:
label: Additional context
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

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 📃 Documentation
url: https://github.com/revanced/revanced-documentation/
about: Don't know how or where to start? Check out our documentation!
- name: 🗨 Discussions
url: https://github.com/revanced/revanced-suggestions/discussions
about: Got something you think should change or be added? Search for or start a new discussion!

View File

@@ -0,0 +1,58 @@
name: ⭐ Feature request
description: Create a detailed feature request.
title: 'feat: <title>'
labels: [feature-request]
body:
- type: markdown
attributes:
value: |
# ReVanced feature request
Do not submit requests for patches here. Please submit them [here](https://github.com/orgs/revanced/discussions/categories/patches) instead.
Important to note that your feature request may have already been made before. Please check for existing feature requests [here](https://github.com/revanced/revanced-patcher/labels/feature-request).
- type: dropdown
attributes:
label: Type
options:
- Functionality
- Cosmetic
- Other
validations:
required: true
- type: textarea
attributes:
label: Issue
description: What is the current problem. Why does it require a feature request?
validations:
required: true
- type: textarea
attributes:
label: Feature
description: Describe your feature in detail. How does it solve the issue?
validations:
required: true
- type: textarea
attributes:
label: Motivation
description: Why should your feature should be considered?
validations:
required: true
- type: textarea
attributes:
label: Additional context
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

2
.github/config.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
firstPRMergeComment: >
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.

25
.github/workflows/pull_request.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: PR to main
on:
push:
branches:
- dev
workflow_dispatch:
env:
MESSAGE: merge branch `${{ github.head_ref || github.ref_name }}` to `main`
jobs:
pull-request:
name: Open pull request
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Open pull request
uses: repo-sync/pull-request@v2
with:
destination_branch: 'main'
pr_title: 'chore: ${{ env.MESSAGE }}'
pr_body: 'This pull request will ${{ env.MESSAGE }}.'
pr_draft: true

View File

@@ -1,4 +1,5 @@
name: Release
on:
workflow_dispatch:
push:
@@ -9,34 +10,33 @@ on:
branches:
- main
- dev
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages
persist-credentials: false
fetch-depth: 0
- name: Setup JDK
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
distribution: 'zulu'
cache: gradle
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: "lts/*"
- name: Make gradlew executable
run: chmod +x gradlew
- name: Build with Gradle
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build
node-version: "18"
cache: 'npm'
- name: Setup semantic-release
run: npm install -g semantic-release @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin -D
run: npm install
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
run: npm exec semantic-release

7
.gitignore vendored
View File

@@ -74,6 +74,7 @@ cmake-build-*/
# IntelliJ
out/
.idea/
# mpeltonen/sbt-idea plugin
.idea_modules/
@@ -115,3 +116,9 @@ gradle-app.setting
# Avoid ignoring test resources
!src/test/resources/*
# Dependency directories
node_modules/
# Gradle props, to avoid sharing the gpr key
gradle.properties

8
.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1,10 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

7
.idea/discord.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="UnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

10
.idea/misc.xml generated
View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

12
.idea/vcs.xml generated
View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CommitMessageInspectionProfile">
<profile version="1.0">
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -20,6 +20,18 @@
]
}
],
"@semantic-release/github"
[
"@saithodev/semantic-release-backmerge",
{
backmergeBranches: [{"from": "main", "to": "dev"}],
clearWorkspace: true
}
],
[
"@semantic-release/github",
{
successComment: false
}
]
]
}

View File

@@ -1,3 +1,876 @@
# [7.1.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v7.1.0-dev.1...v7.1.0-dev.2) (2023-05-05)
### Features
* add overload to get instruction as type ([49c173d](https://github.com/revanced/revanced-patcher/commit/49c173dc14137ddd198a611e9882dc71300831ee))
# [7.1.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.0.0...v7.1.0-dev.1) (2023-04-30)
### Features
* add appreciation message for new contributors ([d674362](https://github.com/revanced/revanced-patcher/commit/d67436271ddca9ccfe008272c1ca82c6123ae7ee))
# [7.0.0](https://github.com/revanced/revanced-patcher/compare/v6.4.3...v7.0.0) (2023-02-26)
* feat!: merge integrations only when necessary ([6e24a85](https://github.com/revanced/revanced-patcher/commit/6e24a85eabd1e7a1484fad229d5ba55c3ba1f1b4))
### BREAKING CHANGES
* `Patcher.addFiles` is now renamed to `Patcher.addIntegrations`
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
# [7.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.3...v7.0.0-dev.1) (2023-02-26)
* feat!: merge integrations only when necessary ([6e24a85](https://github.com/revanced/revanced-patcher/commit/6e24a85eabd1e7a1484fad229d5ba55c3ba1f1b4))
### BREAKING CHANGES
* `Patcher.addFiles` is now renamed to `Patcher.addIntegrations`
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
## [6.4.3](https://github.com/revanced/revanced-patcher/compare/v6.4.2...v6.4.3) (2023-02-10)
### Bug Fixes
* check `CONST_STRING_JUMP` instructions for matching string ([058d292](https://github.com/revanced/revanced-patcher/commit/058d292ad5e297f4c652ff543c13e77a39f7fb1b))
## [6.4.3-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.2...v6.4.3-dev.1) (2023-02-10)
### Bug Fixes
* check `CONST_STRING_JUMP` instructions for matching string ([058d292](https://github.com/revanced/revanced-patcher/commit/058d292ad5e297f4c652ff543c13e77a39f7fb1b))
## [6.4.2](https://github.com/revanced/revanced-patcher/compare/v6.4.1...v6.4.2) (2023-01-17)
### Bug Fixes
* resolve failing builds ([a263fdf](https://github.com/revanced/revanced-patcher/commit/a263fdfd413fc05098e28d4800e36ce7d313085b))
## [6.4.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.1...v6.4.2-dev.1) (2023-01-17)
### Bug Fixes
* resolve failing builds ([a263fdf](https://github.com/revanced/revanced-patcher/commit/a263fdfd413fc05098e28d4800e36ce7d313085b))
## [6.4.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1) (2023-01-15)
### Bug Fixes
* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](https://github.com/revanced/revanced-patcher/commit/dd7dd383577dcfc95e97f77b446a89b41b589dc0))
## [6.4.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1) (2023-01-15)
### Bug Fixes
* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](https://github.com/revanced/revanced-patcher/commit/dd7dd383577dcfc95e97f77b446a89b41b589dc0))
## [6.4.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1-dev.1) (2023-01-15)
### Bug Fixes
* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](https://github.com/revanced/revanced-patcher/commit/dd7dd383577dcfc95e97f77b446a89b41b589dc0))
# [6.4.0](https://github.com/revanced/revanced-patcher/compare/v6.3.2...v6.4.0) (2023-01-02)
### Features
* add missing setter to `MutableMethod` ([8f3ecc3](https://github.com/revanced/revanced-patcher/commit/8f3ecc318c39f0270aff53efdee7a1c8d82af421))
* do not fix methods or methods in class merger ([4102f43](https://github.com/revanced/revanced-patcher/commit/4102f43b8a9473fd0ee96c5d4fb8f6e9b4e30e70))
* fix method and field access when merging classes ([5c09ef7](https://github.com/revanced/revanced-patcher/commit/5c09ef7837f9b731e137b66c19da77f63c007595))
* make `aaptPath` nullable ([#146](https://github.com/revanced/revanced-patcher/issues/146)) ([9f0a09a](https://github.com/revanced/revanced-patcher/commit/9f0a09a7569fd5dd78afa27cb66a73d1662edc69))
# [6.4.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v6.4.0-dev.1...v6.4.0-dev.2) (2023-01-02)
### Features
* add missing setter to `MutableMethod` ([8f3ecc3](https://github.com/revanced/revanced-patcher/commit/8f3ecc318c39f0270aff53efdee7a1c8d82af421))
* do not fix methods or methods in class merger ([4102f43](https://github.com/revanced/revanced-patcher/commit/4102f43b8a9473fd0ee96c5d4fb8f6e9b4e30e70))
* fix method and field access when merging classes ([5c09ef7](https://github.com/revanced/revanced-patcher/commit/5c09ef7837f9b731e137b66c19da77f63c007595))
# [6.4.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.2...v6.4.0-dev.1) (2022-12-20)
### Features
* make `aaptPath` nullable ([#146](https://github.com/revanced/revanced-patcher/issues/146)) ([9f0a09a](https://github.com/revanced/revanced-patcher/commit/9f0a09a7569fd5dd78afa27cb66a73d1662edc69))
## [6.3.2](https://github.com/revanced/revanced-patcher/compare/v6.3.1...v6.3.2) (2022-12-18)
### Bug Fixes
* check if fingerprint string is substring of any string references ([c5de9e2](https://github.com/revanced/revanced-patcher/commit/c5de9e29889dffd18b31e62a892881cc48e8b607))
* print full exception when patch fails ([7cf79e6](https://github.com/revanced/revanced-patcher/commit/7cf79e68e0e9dfd9faddee33139b127b71882d3e))
## [6.3.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.1...v6.3.2-dev.1) (2022-12-18)
### Bug Fixes
* check if fingerprint string is substring of any string references ([c5de9e2](https://github.com/revanced/revanced-patcher/commit/c5de9e29889dffd18b31e62a892881cc48e8b607))
* print full exception when patch fails ([7cf79e6](https://github.com/revanced/revanced-patcher/commit/7cf79e68e0e9dfd9faddee33139b127b71882d3e))
## [6.3.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.1...v6.3.2-dev.1) (2022-12-17)
### Bug Fixes
* print full exception when patch fails ([27a8401](https://github.com/revanced/revanced-patcher/commit/27a8401d81e078e0303f7ddcb0ac6f342f8e4def))
## [6.3.1](https://github.com/revanced/revanced-patcher/compare/v6.3.0...v6.3.1) (2022-12-13)
### Bug Fixes
* publicize types when merging files if necessary ([#137](https://github.com/revanced/revanced-patcher/issues/137)) ([9ec720e](https://github.com/revanced/revanced-patcher/commit/9ec720e983785d8b1dde330cc0e0e0f914c1803c))
## [6.3.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.0...v6.3.1-dev.1) (2022-12-13)
### Bug Fixes
* publicize types when merging files if necessary ([#137](https://github.com/revanced/revanced-patcher/issues/137)) ([9ec720e](https://github.com/revanced/revanced-patcher/commit/9ec720e983785d8b1dde330cc0e0e0f914c1803c))
# [6.3.0](https://github.com/revanced/revanced-patcher/compare/v6.2.0...v6.3.0) (2022-12-02)
### Features
* sort patches in lexicographical order ([a306561](https://github.com/revanced/revanced-patcher/commit/a306561b55ac848792046378f582a036f7ffab03)), closes [#125](https://github.com/revanced/revanced-patcher/issues/125)
# [6.2.0](https://github.com/revanced/revanced-patcher/compare/v6.1.1...v6.2.0) (2022-12-02)
### Features
* merge classes on addition ([#127](https://github.com/revanced/revanced-patcher/issues/127)) ([a925650](https://github.com/revanced/revanced-patcher/commit/a9256500440f9b4117f1b8813ba0097dafee4ebb))
## [6.1.1](https://github.com/revanced/revanced-patcher/compare/v6.1.0...v6.1.1) (2022-11-25)
### Bug Fixes
* use `MethodUtil.methodSignaturesMatch` instead of `Method.softCompareTo` ([bd053b7](https://github.com/revanced/revanced-patcher/commit/bd053b7e9974c0282d56e6762459db7070452e4a))
# [6.1.0](https://github.com/revanced/revanced-patcher/compare/v6.0.2...v6.1.0) (2022-11-22)
### Features
* apply changes from ReVanced Patcher ([ba9d998](https://github.com/revanced/revanced-patcher/commit/ba9d99868103406fe36b9aa0cfaa0ed5023edfab))
## [6.0.2](https://github.com/revanced/revanced-patcher/compare/v6.0.1...v6.0.2) (2022-11-18)
### Bug Fixes
* fallback to patch class name instead of `java.lang.Class` class name ([4164cb0](https://github.com/revanced/revanced-patcher/commit/4164cb0deacc7e1eed9fce63dab030180f28b762))
## [6.0.1](https://github.com/revanced/revanced-patcher/compare/v6.0.0...v6.0.1) (2022-11-14)
### Bug Fixes
* remove unnecessary dummy nop instructions ([#111](https://github.com/revanced/revanced-patcher/issues/111)) ([f9bc95f](https://github.com/revanced/revanced-patcher/commit/f9bc95f220aa434308ce6950ba6ad2e7efac9c8a))
# [6.0.0](https://github.com/revanced/revanced-patcher/compare/v5.1.2...v6.0.0) (2022-10-05)
### Code Refactoring
* improve structuring of classes and their implementations ([4aa14bb](https://github.com/revanced/revanced-patcher/commit/4aa14bbb858af9253eae9328b759f3298b65a215))
### Features
* remove unused annotation `DirectPatternScanMethod` ([538b2a8](https://github.com/revanced/revanced-patcher/commit/538b2a859962570c700362afc88704ed3611aa87))
* remove unused annotation `SincePatcher` ([4ae9ad0](https://github.com/revanced/revanced-patcher/commit/4ae9ad09d64a3f69512ccb037f816cb847d7350f))
* remove unused extension `dependsOn` ([797286b](https://github.com/revanced/revanced-patcher/commit/797286b7588646272dea2fd35e8e78b0ffb18a0f))
* remove unused patch extensions ([5583904](https://github.com/revanced/revanced-patcher/commit/55839049948033ad02414517fd3ba03619216aec))
### BREAKING CHANGES
* various changes in which packages classes previously where and their implementation
* These extensions do not exist anymore and any use should be removed
* The extension does not exist anymore and any use should be removed
* The annotation does not exist anymore and any use should be removed
## [5.1.2](https://github.com/revanced/revanced-patcher/compare/v5.1.1...v5.1.2) (2022-09-29)
### Bug Fixes
* check dependencies for resource patches ([9c07ffc](https://github.com/revanced/revanced-patcher/commit/9c07ffcc7af9f088426528561f4321c5cc6b5b15))
* use instruction index instead of strings list index for `StringMatch` ([843e62a](https://github.com/revanced/revanced-patcher/commit/843e62ad290ee0a707be9322ee943921da3ea420))
## [5.1.1](https://github.com/revanced/revanced-patcher/compare/v5.1.0...v5.1.1) (2022-09-26)
### Performance Improvements
* decode resources only when necessary ([3ba4be2](https://github.com/revanced/revanced-patcher/commit/3ba4be240bf0a424e4bbfbaca9605644fda0984e))
# [5.1.0](https://github.com/revanced/revanced-patcher/compare/v5.0.1...v5.1.0) (2022-09-26)
### Features
* RwLock for opening files in `DomFileEditor` ([db4348c](https://github.com/revanced/revanced-patcher/commit/db4348c4faf51bfe29678baacfbe76ba645ec0b9))
## [5.0.1](https://github.com/revanced/revanced-patcher/compare/v5.0.0...v5.0.1) (2022-09-23)
### 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)
### Bug Fixes
* show error message if cause is null ([f9da2ad](https://github.com/revanced/revanced-patcher/commit/f9da2ad531644617ad5a2cc6a1819d530e18ba22))
## [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](https://github.com/revanced/revanced-patcher/commit/123ad54c150bd04f4b8ef5c65334ea468ceb99cc))
## [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](https://github.com/revanced/revanced-patcher/commit/7b2d058144b0718992d329731e2af7cc704e4370))
# [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](https://github.com/revanced/revanced-patcher/commit/2572cd04b5da4eeae738c8dde31493177edf0bf8))
## [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](https://github.com/revanced/revanced-patcher/commit/14a73bfcafac36bce2b8466788d460edde7a14fd))
# [3.2.0](https://github.com/revanced/revanced-patcher/compare/v3.1.0...v3.2.0) (2022-08-02)
### Features
* PatchOptions#nullify to nullify an option ([371f0c4](https://github.com/revanced/revanced-patcher/commit/371f0c4d0bf96e7f6db35085efccaed3000a096c))
# [3.1.0](https://github.com/revanced/revanced-patcher/compare/v3.0.0...v3.1.0) (2022-08-02)
### Features
* validator for patch options ([4e2e772](https://github.com/revanced/revanced-patcher/commit/4e2e77238957d7732326cfe5e05145bf7dab5bfb))
# [3.0.0](https://github.com/revanced/revanced-patcher/compare/v2.9.0...v3.0.0) (2022-08-02)
### Features
* registry for patch options ([2431785](https://github.com/revanced/revanced-patcher/commit/2431785d0e494d6271c6951eec9adfff9db95c17))
### BREAKING CHANGES
* Patch options now use the PatchOptions registry class instead of an Iterable. This change requires modifications to existing patches using this API.
# [2.9.0](https://github.com/revanced/revanced-patcher/compare/v2.8.0...v2.9.0) (2022-08-02)
### Bug Fixes
* show error message instead of `null` ([8d95b14](https://github.com/revanced/revanced-patcher/commit/8d95b14f350b47ec029f35e776f6e627aaf5f607))
### Features
* exclusive mutable access to files ([814ce0b](https://github.com/revanced/revanced-patcher/commit/814ce0b9ae29725417c86b7d11b40d025724a426))
# [2.8.0](https://github.com/revanced/revanced-patcher/compare/v2.7.0...v2.8.0) (2022-08-01)
### Bug Fixes
* remove requirement for solution [skip ci] ([#80](https://github.com/revanced/revanced-patcher/issues/80)) ([9a4d30e](https://github.com/revanced/revanced-patcher/commit/9a4d30e15234ef62844f035c58a1143674d4c12e))
### Features
* patch options ([#81](https://github.com/revanced/revanced-patcher/issues/81)) ([fbb09f3](https://github.com/revanced/revanced-patcher/commit/fbb09f38dce49adc7f63b71bdf2df2ef0b84db04))
# [2.7.0](https://github.com/revanced/revanced-patcher/compare/v2.6.0...v2.7.0) (2022-08-01)
### Features
* `Closeable` patches ([bbd40bf](https://github.com/revanced/revanced-patcher/commit/bbd40bf2f6ff200705f2bcb272dd1680bb244e3f))
# [2.6.0](https://github.com/revanced/revanced-patcher/compare/v2.5.2...v2.6.0) (2022-07-31)
### Features
* add Patch#dependsOn extension ([523f67b](https://github.com/revanced/revanced-patcher/commit/523f67b238646caaa9b7676a0e238ce82adbdda4))
* Soft Dependencies for Patches ([8c12f8d](https://github.com/revanced/revanced-patcher/commit/8c12f8d488f939cc932e826aad0b20876ae165b7))
## [2.5.2](https://github.com/revanced/revanced-patcher/compare/v2.5.1...v2.5.2) (2022-07-24)
## [2.5.1](https://github.com/revanced/revanced-patcher/compare/v2.5.0...v2.5.1) (2022-07-17)
### Bug Fixes
* close stream when closing `DomFileEditor` ([77604d4](https://github.com/revanced/revanced-patcher/commit/77604d40785847b775155c0e75b663a3c7336aa3))
# [2.5.0](https://github.com/revanced/revanced-patcher/compare/v2.4.0...v2.5.0) (2022-07-11)
### Bug Fixes
* missing additional items [skip ci] ([0ebab8b](https://github.com/revanced/revanced-patcher/commit/0ebab8bf598d993df6e340651205cba48f1ef725))
### Features
* feature request issue template ([1b39278](https://github.com/revanced/revanced-patcher/commit/1b39278b24ba2f964d93bd8ad2e28472ee036d90))
* issue templates [skip ci] ([112bc99](https://github.com/revanced/revanced-patcher/commit/112bc998f4761a647cb9eab7454e35264fa96fd9))
# [2.4.0](https://github.com/revanced/revanced-patcher/compare/v2.3.1...v2.4.0) (2022-07-09)
### Features
* Improve Smali Compiler ([6bfe571](https://github.com/revanced/revanced-patcher/commit/6bfe5716c38181bbe9476b5c6ad29526edb4e022))
## [2.3.1](https://github.com/revanced/revanced-patcher/compare/v2.3.0...v2.3.1) (2022-07-07)
### Bug Fixes
* handle null properly ([#64](https://github.com/revanced/revanced-patcher/issues/64)) ([482af78](https://github.com/revanced/revanced-patcher/commit/482af78f2ba23b8003fc9961df5fde54d7295d5c))
# [2.3.0](https://github.com/revanced/revanced-patcher/compare/v2.2.2...v2.3.0) (2022-07-05)
### Features
* nullability for `BytecodePatch` constructor ([#59](https://github.com/revanced/revanced-patcher/issues/59)) ([4ea030d](https://github.com/revanced/revanced-patcher/commit/4ea030d0a03f736bbecbd491317ba2167b18fe94))
## [2.2.2](https://github.com/revanced/revanced-patcher/compare/v2.2.1...v2.2.2) (2022-07-04)
### Bug Fixes
* `MethodWalker` not accounting for all reference instructions ([48068cb](https://github.com/revanced/revanced-patcher/commit/48068cb3d79e283ff1cad9f3f78dc1d0fcd14f83))
## [2.2.1](https://github.com/revanced/revanced-patcher/compare/v2.2.0...v2.2.1) (2022-07-03)
### Bug Fixes
* more useful error message ([4b2e323](https://github.com/revanced/revanced-patcher/commit/4b2e3230ec74fa3a57ae86067e5cb7cecbe45013))
# [2.2.0](https://github.com/revanced/revanced-patcher/compare/v2.1.2...v2.2.0) (2022-07-02)
### Bug Fixes
* DomFileEditor opening in- and output streams on the same file ([83187c9](https://github.com/revanced/revanced-patcher/commit/83187c9edd7b088bc18960c5eb9a2042ca536b5f))
### Features
* remove deprecated functions ([ada5a03](https://github.com/revanced/revanced-patcher/commit/ada5a033de3cf94e7255ec2d522520f86431f001))
* streams overload for `XmlFileHolder` ([6f72c4c](https://github.com/revanced/revanced-patcher/commit/6f72c4c4c051e48c8d03d2a7b2cfc1c53028ed86))
# [2.2.0-dev.3](https://github.com/revanced/revanced-patcher/compare/v2.2.0-dev.2...v2.2.0-dev.3) (2022-07-02)
### Bug Fixes
* DomFileEditor opening in- and output streams on the same file ([83187c9](https://github.com/revanced/revanced-patcher/commit/83187c9edd7b088bc18960c5eb9a2042ca536b5f))
# [2.2.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v2.2.0-dev.1...v2.2.0-dev.2) (2022-07-02)
### Features
* streams overload for `XmlFileHolder` ([6f72c4c](https://github.com/revanced/revanced-patcher/commit/6f72c4c4c051e48c8d03d2a7b2cfc1c53028ed86))
# [2.2.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v2.1.2...v2.2.0-dev.1) (2022-07-02)
### Features
* remove deprecated functions ([ada5a03](https://github.com/revanced/revanced-patcher/commit/ada5a033de3cf94e7255ec2d522520f86431f001))
## [2.1.2](https://github.com/revanced/revanced-patcher/compare/v2.1.1...v2.1.2) (2022-06-29)
### Bug Fixes
* invert fingerprint resolution condition of `customFingerprint` ([e2faf4c](https://github.com/revanced/revanced-patcher/commit/e2faf4ca9b6de23300b20ab471ee9dc365b04339))
## [2.1.1](https://github.com/revanced/revanced-patcher/compare/v2.1.0...v2.1.1) (2022-06-28)
# [2.1.0](https://github.com/revanced/revanced-patcher/compare/v2.0.4...v2.1.0) (2022-06-28)
### Features
* log failed patches due to failed dependencies ([a467fbb](https://github.com/revanced/revanced-patcher/commit/a467fbb704eebe812cdec14025398dab2af43959))
## [2.0.4](https://github.com/revanced/revanced-patcher/compare/v2.0.3...v2.0.4) (2022-06-27)
## [2.0.3](https://github.com/revanced/revanced-patcher/compare/v2.0.2...v2.0.3) (2022-06-27)
## [2.0.2](https://github.com/revanced/revanced-patcher/compare/v2.0.1...v2.0.2) (2022-06-27)
## [2.0.1](https://github.com/revanced/revanced-patcher/compare/v2.0.0...v2.0.1) (2022-06-26)
### Bug Fixes
* use `Exception` instead of `MethodNotFoundException` ([2fc4ec4](https://github.com/revanced/revanced-patcher/commit/2fc4ec40217a917ea6106ddc87be332f725aa13c))
# [2.0.0](https://github.com/revanced/revanced-patcher/compare/v1.11.0...v2.0.0) (2022-06-26)
### Code Refactoring
* migrate from `Signature` to `Fingerprint` ([efa8ea1](https://github.com/revanced/revanced-patcher/commit/efa8ea144528fcff588e782468845c315a7d6abd))
### BREAKING CHANGES
* Not backwards compatible, since a lot of classes where renamed.
# [1.11.0](https://github.com/revanced/revanced-patcher/compare/v1.10.2...v1.11.0) (2022-06-24)
### Features
* add replace and remove extensions ([#50](https://github.com/revanced/revanced-patcher/issues/50)) ([92ac5e4](https://github.com/revanced/revanced-patcher/commit/92ac5e4dc25f612856e2b5e528cf5fd48a5f20af))
## [1.10.2](https://github.com/revanced/revanced-patcher/compare/v1.10.1...v1.10.2) (2022-06-23)
### Bug Fixes
* dexlib must be propagated ([b738dcd](https://github.com/revanced/revanced-patcher/commit/b738dcd7ea04f5fe56e66af46fb11541fe54f6af))
## [1.10.1](https://github.com/revanced/revanced-patcher/compare/v1.10.0...v1.10.1) (2022-06-23)
### Bug Fixes
* callback only when inteded ([e3bf367](https://github.com/revanced/revanced-patcher/commit/e3bf367ad6615b30b06027d65f906b2588567a7f))
* mutability of local variable `modified` ([0e87ef5](https://github.com/revanced/revanced-patcher/commit/0e87ef56c418d5c37d58abb9b27f85e25fd44f81))
# [1.10.0](https://github.com/revanced/revanced-patcher/compare/v1.9.0...v1.10.0) (2022-06-23)
### Features
* improve logging ([c20dfe1](https://github.com/revanced/revanced-patcher/commit/c20dfe12d5c737264b844e6634de11bf1e1629f0))
# [1.9.0](https://github.com/revanced/revanced-patcher/compare/v1.8.0...v1.9.0) (2022-06-22)
### Bug Fixes
* callback for each file instead of class ([930768d](https://github.com/revanced/revanced-patcher/commit/930768dfb31dc5fa6c248050b08ac117c40ee0a3))
### Features
* yield the patch result ([dde5385](https://github.com/revanced/revanced-patcher/commit/dde5385232abddc8a85d6e9a939549b71dd9130e))
# [1.8.0](https://github.com/revanced/revanced-patcher/compare/v1.7.2...v1.8.0) (2022-06-22)
### Features
* logging class ([caf2745](https://github.com/revanced/revanced-patcher/commit/caf2745805ffd4b59fa81e79cc489b1a1a5c5d89))
## [1.7.2](https://github.com/revanced/revanced-patcher/compare/v1.7.1...v1.7.2) (2022-06-22)
### Bug Fixes
* add execute permission to `./gradlew` file ([#46](https://github.com/revanced/revanced-patcher/issues/46)) ([34f607a](https://github.com/revanced/revanced-patcher/commit/34f607aa24d89a777d906cc887203f343ce3fd07))
## [1.7.1](https://github.com/revanced/revanced-patcher/compare/v1.7.0...v1.7.1) (2022-06-22)
### Reverts
* revert "feat: use of `java.util.logging.Logger`" ([e8488b3](https://github.com/revanced/revanced-patcher/commit/e8488b3e86e0132011824f8ecba29e64f8db0573))
# [1.7.0](https://github.com/revanced/revanced-patcher/compare/v1.6.0...v1.7.0) (2022-06-22)
### Features
* migrate logger to `slf4j` ([8f66f9f](https://github.com/revanced/revanced-patcher/commit/8f66f9f606a785ac947b0e553822877f211d82df))
# [1.6.0](https://github.com/revanced/revanced-patcher/compare/v1.5.0...v1.6.0) (2022-06-22)
### Features
* use of `java.util.logging.Logger` ([9c39c9e](https://github.com/revanced/revanced-patcher/commit/9c39c9efdb5d48ddaffce7f711c275e732b0b2d9))
# [1.5.0](https://github.com/revanced/revanced-patcher/compare/v1.4.0...v1.5.0) (2022-06-22)
### Features
* use streams to write the dex files ([64bae88](https://github.com/revanced/revanced-patcher/commit/64bae884dcb72550a3218e149f3ca0fd0ca03aaf))
# [1.4.0](https://github.com/revanced/revanced-patcher/compare/v1.3.4...v1.4.0) (2022-06-22)
### Features
* return a `File` instance instead of `ExtFile` ([68174bb](https://github.com/revanced/revanced-patcher/commit/68174bbd6b4df47a91b610c2b97dbae55b594163))
## [1.3.4](https://github.com/revanced/revanced-patcher/compare/v1.3.3...v1.3.4) (2022-06-21)
### Bug Fixes
* `String.toInstructions` defaulting `forStaticMethod` to `false` ([5a2f02b](https://github.com/revanced/revanced-patcher/commit/5a2f02b97dcde95dbe901fa68cca6c6c0219cb82)), closes [revanced/revanced-patches#46](https://github.com/revanced/revanced-patches/issues/46)
## [1.3.3](https://github.com/revanced/revanced-patcher/compare/v1.3.2...v1.3.3) (2022-06-21)
### Bug Fixes
* add docs (trigger release) ([6628b78](https://github.com/revanced/revanced-patcher/commit/6628b7870fc052da40be0d50a7e2b0b6c57743cc))
### Reverts
* propagate dependencies ([365e1d7](https://github.com/revanced/revanced-patcher/commit/365e1d7a4507b918a4c8170ce2c88f6c8ff1d474))
## [1.3.2](https://github.com/revanced/revanced-patcher/compare/v1.3.1...v1.3.2) (2022-06-21)
### Bug Fixes
* return resourceFile to caller ([1f75777](https://github.com/revanced/revanced-patcher/commit/1f75777cf985bf08483033ec541937d3e733347b))
## [1.3.1](https://github.com/revanced/revanced-patcher/compare/v1.3.0...v1.3.1) (2022-06-21)
### Bug Fixes
* `InlineSmaliCompiler.compile` using 0 registers instead of 1 by default ([835a421](https://github.com/revanced/revanced-patcher/commit/835a421cc0588b92c2995e9d74727069d14b1750))
# [1.3.0](https://github.com/revanced/revanced-patcher/compare/v1.2.9...v1.3.0) (2022-06-20)
### Features
* `parametersCount` for `InlineSmaliCompiler` instead of `parameters` ([ad6c5c8](https://github.com/revanced/revanced-patcher/commit/ad6c5c827389d10eae473dc66557a699df8c3280))
* simplify adding instructions ([e47b67d](https://github.com/revanced/revanced-patcher/commit/e47b67d7ec521f288644afb89baf4146dc9bc87d))
## [1.2.9](https://github.com/revanced/revanced-patcher/compare/v1.2.8...v1.2.9) (2022-06-20)
### Bug Fixes
* update apktool ([ab866bb](https://github.com/revanced/revanced-patcher/commit/ab866bb8ef4792d8f2a51edc79e687b5b636c621))
## [1.2.8](https://github.com/revanced/revanced-patcher/compare/v1.2.7...v1.2.8) (2022-06-18)
### Bug Fixes
* update apktool ([051afd9](https://github.com/revanced/revanced-patcher/commit/051afd98d065f71556392139d77c20b4c2dc7dd1))
## [1.2.7](https://github.com/revanced/revanced-patcher/compare/v1.2.6...v1.2.7) (2022-06-18)
### Bug Fixes
* version not working with apktool due to cache ([03f5ee0](https://github.com/revanced/revanced-patcher/commit/03f5ee088b1b96b88cb7aeb323443b6209a13950))
## [1.2.6](https://github.com/revanced/revanced-patcher/compare/v1.2.5...v1.2.6) (2022-06-18)
### Bug Fixes
* remove javadoc jar (also trigger release) ([56f6ca3](https://github.com/revanced/revanced-patcher/commit/56f6ca38919b522c0d5558eabffa4aee41cc0b0b))
## [1.2.5](https://github.com/revanced/revanced-patcher/compare/v1.2.4...v1.2.5) (2022-06-17)
### Bug Fixes
* goodbye security ([8f3ac77](https://github.com/revanced/revanced-patcher/commit/8f3ac7702a2b3ee98c55aeac6a1b9972f99664cc))
## [1.2.4](https://github.com/revanced/revanced-patcher/compare/v1.2.3...v1.2.4) (2022-06-15)
### Reverts
* "fix: enforce aapt v1" ([dfd8a24](https://github.com/revanced/revanced-patcher/commit/dfd8a245124f85b1b028bbba197c70c8dca689b6))
## [1.2.3](https://github.com/revanced/revanced-patcher/compare/v1.2.2...v1.2.3) (2022-06-14)
### Bug Fixes
* enforce aapt v1 ([cff87ff](https://github.com/revanced/revanced-patcher/commit/cff87ff0770d774d7ef79eec5a22462eadbcb9c5))
## [1.2.2](https://github.com/revanced/revanced-patcher/compare/v1.2.1...v1.2.2) (2022-06-14)
### Bug Fixes
* enforce aapt v2 ([b68b0bf](https://github.com/revanced/revanced-patcher/commit/b68b0bf3d735f54b92ad7dad8132f77e9007063f))
## [1.2.1](https://github.com/revanced/revanced-patcher/compare/v1.2.0...v1.2.1) (2022-06-14)
### Bug Fixes
* Patcher setting BuildOptions too late ([6a5c873](https://github.com/revanced/revanced-patcher/commit/6a5c8735fb8a5d6f7e9c606734b6684c7fa99e7f))
# [1.2.0](https://github.com/revanced/revanced-patcher/compare/v1.1.0...v1.2.0) (2022-06-14)
### Features
* allow custom framework path to be specified ([d3a580e](https://github.com/revanced/revanced-patcher/commit/d3a580ea19d7c2d5d8c97650b1e6396ea0a7fc25))
# [1.1.0](https://github.com/revanced/revanced-patcher/compare/v1.0.0...v1.1.0) (2022-06-11)
### Bug Fixes
* resource patcher ([31815ca](https://github.com/revanced/revanced-patcher/commit/31815ca9ea990f16b3600d61fd570c1805be1c82))
* update apktool to fork ([566ecef](https://github.com/revanced/revanced-patcher/commit/566ecefa2bd4cde5ebfb2b22dc56cd8bf9f396bd))
### Features
* allow custom aapt path to be specified ([8eb4a8f](https://github.com/revanced/revanced-patcher/commit/8eb4a8f87ae7679a272f3224273a37a31d4bb121))
# 1.0.0 (2022-06-05)

View File

@@ -1 +1,2 @@
# Patcher
Patcher framework used in the ReVanced project.

View File

@@ -1,38 +1,32 @@
plugins {
kotlin("jvm") version "1.6.21"
java
kotlin("jvm") version "1.8.10"
`maven-publish`
}
group = "app.revanced"
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
repositories {
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/revanced/multidexlib2")
credentials {
// DO NOT set these variables in the project's gradle.properties.
// Instead, you should set them in:
// Windows: %homepath%\.gradle\gradle.properties
// Linux: ~/.gradle/gradle.properties
username =
project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") // DO NOT CHANGE!
password =
project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") // DO NOT CHANGE!
username = githubUsername
password = githubPassword
}
}
}
dependencies {
implementation(kotlin("stdlib"))
implementation("xpp3:xpp3:1.1.4c")
implementation("app.revanced:smali:2.5.3-a3836654")
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
implementation("app.revanced:apktool-lib:2.7.0")
api("xpp3:xpp3:1.1.4c")
api("org.apktool:apktool-lib:2.6.1")
api("app.revanced:multidexlib2:2.5.2.r2")
api("org.smali:smali:2.5.2")
testImplementation(kotlin("test"))
implementation(kotlin("reflect"))
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
}
tasks {
@@ -42,18 +36,18 @@ tasks {
events("PASSED", "SKIPPED", "FAILED")
}
}
processResources {
expand("projectVersion" to project.version)
}
}
java {
withSourcesJar()
withJavadocJar()
}
val isGitHubCI = System.getenv("GITHUB_ACTOR") != null
publishing {
repositories {
if (isGitHubCI) {
if (System.getenv("GITHUB_ACTOR") != null)
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
@@ -62,13 +56,12 @@ publishing {
password = System.getenv("GITHUB_TOKEN")
}
}
} else {
else
mavenLocal()
}
}
publications {
register<MavenPublication>("gpr") {
from(components["java"])
}
}
}
}

View File

@@ -1,2 +1,2 @@
kotlin.code.style = official
version = 1.0.0
version = 7.1.0-dev.2

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

0
gradlew vendored Normal file → Executable file
View File

6580
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

9
package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"devDependencies": {
"@saithodev/semantic-release-backmerge": "^3.1.0",
"@semantic-release/changelog": "^6.0.2",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.7.6",
"semantic-release": "^20.1.0"
}
}

View File

@@ -1,244 +1,391 @@
package app.revanced.patcher
import app.revanced.patcher.data.PatcherData
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.data.implementation.findIndexed
import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.PatchExtensions.dependencies
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
import app.revanced.patcher.extensions.nullOutputStream
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.patch.implementation.BytecodePatch
import app.revanced.patcher.patch.implementation.ResourcePatch
import app.revanced.patcher.patch.implementation.misc.PatchResult
import app.revanced.patcher.patch.implementation.misc.PatchResultError
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
import app.revanced.patcher.signature.implementation.method.resolver.MethodSignatureResolver
import app.revanced.patcher.util.ListBackedSet
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
import app.revanced.patcher.patch.*
import app.revanced.patcher.util.VersionReader
import brut.androlib.Androlib
import brut.androlib.meta.UsesFramework
import brut.androlib.options.BuildOptions
import brut.androlib.res.AndrolibResources
import brut.androlib.res.data.ResPackage
import brut.androlib.res.decoder.AXmlResourceParser
import brut.androlib.res.decoder.ResAttrDecoder
import brut.androlib.res.decoder.XmlPullStreamDecoder
import brut.androlib.res.xml.ResXmlPatcher
import brut.directory.ExtFile
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.DexIO
import lanchon.multidexlib2.MultiDexIO
import org.jf.dexlib2.Opcodes
import org.jf.dexlib2.iface.ClassDef
import org.jf.dexlib2.iface.DexFile
import org.jf.dexlib2.writer.io.MemoryDataStore
import java.io.File
import java.nio.file.Files
val NAMER = BasicDexFileNamer()
internal val NAMER = BasicDexFileNamer()
/**
* The ReVanced Patcher.
* @param options The options for the patcher.
*/
class Patcher(
private val options: PatcherOptions
) {
val packageVersion: String
val packageName: String
private lateinit var usesFramework: UsesFramework
private val patcherData: PatcherData
class Patcher(private val options: PatcherOptions) {
private val logger = options.logger
private val opcodes: Opcodes
private var resourceDecodingMode = ResourceDecodingMode.MANIFEST_ONLY
private var mergeIntegrations = false
val context: PatcherContext
companion object {
@JvmStatic
val version = VersionReader.read()
private fun BuildOptions.setBuildOptions(options: PatcherOptions) {
this.aaptPath = options.aaptPath
this.useAapt2 = true
this.frameworkFolderLocation = options.frameworkFolderLocation
}
}
init {
val extFileInput = ExtFile(options.inputFile)
val outDir = File(options.resourceCacheDirectory)
if (outDir.exists()) outDir.deleteRecursively()
outDir.mkdir()
// load the resource table from the input file
val androlib = Androlib()
val resourceTable = androlib.getResTable(extFileInput, true)
if (options.patchResources) {
// 1. decode resources to cache directory
androlib.decodeManifestWithResources(extFileInput, outDir, resourceTable)
androlib.decodeResourcesFull(extFileInput, outDir, resourceTable)
// 2. read framework ids from the resource table
usesFramework = UsesFramework()
usesFramework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
} else {
// create decoder for the resource table
val decoder = ResAttrDecoder()
decoder.currentPackage = ResPackage(resourceTable, 0, null)
// create xml parser with the decoder
val axmlParser = AXmlResourceParser()
axmlParser.attrDecoder = decoder
// parse package information with the decoder and parser which will set required values in the resource table
// instead of decodeManifest another more low level solution can be created to make it faster/better
XmlPullStreamDecoder(
axmlParser, AndrolibResources().resXmlSerializer
).decodeManifest(
extFileInput.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
)
}
// set package information
packageVersion = resourceTable.versionInfo.versionName
packageName = resourceTable.currentResPackage.name
logger.info("Reading dex files")
// read dex files
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
// get the opcodes
opcodes = dexFile.opcodes
// finally create patcher context
context = PatcherContext(dexFile.classes.toMutableList(), File(options.resourceCacheDirectory))
// save to patcher data
patcherData = PatcherData(dexFile.classes.toMutableList(), options.resourceCacheDirectory)
// decode manifest file
decodeResources(ResourceDecodingMode.MANIFEST_ONLY)
}
/**
* Add additional dex file container to the patcher.
* @param files The dex file containers to add to the patcher.
* @param allowedOverwrites A list of class types that are allowed to be overwritten.
* @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found.
* Add integrations to be merged by the patcher.
* The integrations will only be merged, if necessary.
*
* @param integrations The integrations, must be dex files or dex file container such as ZIP, APK or DEX files.
* @param callback The callback for [integrations] which are being added.
*/
fun addFiles(
files: Iterable<File>, allowedOverwrites: Iterable<String> = emptyList(), throwOnDuplicates: Boolean = false
fun addIntegrations(
integrations: List<File>,
callback: (File) -> Unit
) {
for (file in files) {
val dexFile = MultiDexIO.readDexFile(true, file, NAMER, null, null)
for (classDef in dexFile.classes) {
val e = patcherData.bytecodeData.classes.internalClasses.findIndexed { it.type == classDef.type }
if (e != null) {
if (throwOnDuplicates) {
throw Exception("Class ${classDef.type} has already been added to the patcher.")
}
val (_, idx) = e
if (allowedOverwrites.contains(classDef.type)) {
patcherData.bytecodeData.classes.internalClasses[idx] = classDef
}
continue
}
patcherData.bytecodeData.classes.internalClasses.add(classDef)
}
context.integrations.apply integrations@{
add(integrations)
this@integrations.callback = callback
}
}
/**
* Save the patched dex file.
*/
fun save(): Map<String, MemoryDataStore> {
fun save(): PatcherResult {
val packageMetadata = context.packageMetadata
val metaInfo = packageMetadata.metaInfo
var resourceFile: File? = null
when (resourceDecodingMode) {
ResourceDecodingMode.FULL -> {
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
try {
val androlibResources = AndrolibResources().also { resources ->
resources.buildOptions = BuildOptions().also { buildOptions ->
buildOptions.setBuildOptions(options)
buildOptions.isFramework = metaInfo.isFrameworkApk
buildOptions.resourcesAreCompressed = metaInfo.compressionType
buildOptions.doNotCompress = metaInfo.doNotCompress
}
resources.setSdkInfo(metaInfo.sdkInfo)
resources.setVersionInfo(metaInfo.versionInfo)
resources.setSharedLibrary(metaInfo.sharedLibrary)
resources.setSparseResources(metaInfo.sparseResources)
}
val manifestFile = cacheDirectory.resolve("AndroidManifest.xml")
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifestFile)
val aaptFile = cacheDirectory.resolve("aapt_temp_file")
// delete if it exists
Files.deleteIfExists(aaptFile.toPath())
val resDirectory = cacheDirectory.resolve("res")
val includedFiles = metaInfo.usesFramework.ids.map { id ->
androlibResources.getFrameworkApk(
id, metaInfo.usesFramework.tag
)
}.toTypedArray()
logger.info("Compiling resources")
androlibResources.aaptPackage(
aaptFile, manifestFile, resDirectory, null, null, includedFiles
)
resourceFile = aaptFile
} finally {
cacheDirectory.close()
}
}
else -> logger.info("Not compiling resources because resource patching is not required")
}
logger.trace("Creating new dex file")
val newDexFile = object : DexFile {
override fun getClasses(): Set<ClassDef> {
patcherData.bytecodeData.classes.applyProxies()
return ListBackedSet(patcherData.bytecodeData.classes.internalClasses)
}
override fun getOpcodes(): Opcodes {
return this@Patcher.opcodes
}
override fun getClasses() = context.bytecodeContext.classes.also { it.replaceClasses() }
override fun getOpcodes() = this@Patcher.opcodes
}
// build modified resources
if (options.patchResources) {
val extDir = ExtFile(options.resourceCacheDirectory)
// TODO: figure out why a new instance of Androlib is necessary here
Androlib().buildResources(extDir, usesFramework)
}
// write dex modified files
val output = mutableMapOf<String, MemoryDataStore>()
// write modified dex files
logger.info("Writing modified dex files")
val dexFiles = mutableMapOf<String, MemoryDataStore>()
MultiDexIO.writeDexFile(
true, -1, // core count
output, NAMER, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
dexFiles, NAMER, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
)
return PatcherResult(
dexFiles.map {
app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0))
},
metaInfo.doNotCompress?.toList(),
resourceFile
)
return output
}
/**
* Add [Patch]es to the patcher.
* @param patches [Patch]es The patches to add.
*/
fun addPatches(patches: Iterable<Class<out Patch<Data>>>) {
patcherData.patches.addAll(patches)
fun addPatches(patches: Iterable<Class<out Patch<Context>>>) {
/**
* Returns true if at least one patches or its dependencies matches the given predicate.
*/
fun Class<out Patch<Context>>.anyRecursively(predicate: (Class<out Patch<Context>>) -> Boolean): Boolean =
predicate(this) || dependencies?.any { it.java.anyRecursively(predicate) } == true
// Determine if resource patching is required.
for (patch in patches) {
if (patch.anyRecursively { ResourcePatch::class.java.isAssignableFrom(it) }) {
resourceDecodingMode = ResourceDecodingMode.FULL
break
}
}
// Determine if merging integrations is required.
for (patch in patches) {
if (patch.anyRecursively { it.requiresIntegrations }) {
mergeIntegrations = true
break
}
}
context.patches.addAll(patches)
}
/**
* Apply a [patch] and its dependencies recursively.
* @param patch The [patch] to apply.
* @param appliedPatches A list of [patch] names, to prevent applying [patch]es twice.
* @return The result of executing the [patch].
* Decode resources for the patcher.
*
* @param mode The [ResourceDecodingMode] to use when decoding.
*/
private fun applyPatch(
patch: Class<out Patch<Data>>, appliedPatches: MutableList<String>
): PatchResult {
val patchName = patch.patchName
private fun decodeResources(mode: ResourceDecodingMode) {
val extInputFile = ExtFile(options.inputFile)
try {
val androlib = Androlib(BuildOptions().also { it.setBuildOptions(options) })
val resourceTable = androlib.getResTable(extInputFile, true)
when (mode) {
ResourceDecodingMode.FULL -> {
val outDir = File(options.resourceCacheDirectory)
if (outDir.exists()) {
logger.info("Deleting existing resource cache directory")
if (!outDir.deleteRecursively()) {
logger.error("Failed to delete existing resource cache directory")
}
}
outDir.mkdirs()
// if the patch has already applied silently skip it
if (appliedPatches.contains(patchName)) return PatchResultSuccess()
appliedPatches.add(patchName)
logger.info("Decoding resources")
// recursively apply all dependency patches
patch.dependencies?.forEach {
val patchDependency = it.java
// decode resources to cache directory
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
androlib.decodeResourcesFull(extInputFile, outDir, resourceTable)
val result = applyPatch(patchDependency, appliedPatches)
if (result.isSuccess()) return@forEach
// read additional metadata from the resource table
context.packageMetadata.let { metadata ->
metadata.metaInfo.usesFramework = UsesFramework().also { framework ->
framework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
}
val errorMessage = result.error()!!.message
return PatchResultError("$patchName depends on ${patchDependency.patchName} but the following error was raised: $errorMessage")
}
// read files to not compress
metadata.metaInfo.doNotCompress = buildList {
androlib.recordUncompressedFiles(extInputFile, this)
}
}
val patchInstance = patch.getDeclaredConstructor().newInstance()
// if the current patch is a resource patch but resource patching is disabled, return an error
val isResourcePatch = patchInstance is ResourcePatch
if (!options.patchResources && isResourcePatch) return PatchResultError("$patchName is a resource patch, but resource patching is disabled.")
// TODO: find a solution for this
val data = if (isResourcePatch) {
patcherData.resourceData
} else {
MethodSignatureResolver(
patcherData.bytecodeData.classes.internalClasses, (patchInstance as BytecodePatch).signatures
).resolve(patcherData)
patcherData.bytecodeData
}
return try {
patchInstance.execute(data)
} catch (e: Exception) {
PatchResultError(e)
}
}
/**
* Apply patches loaded into the patcher.
* @param stopOnError If true, the patches will stop on the first error.
* @return A map of [PatchResultSuccess]. If the [Patch] was successfully applied,
* [PatchResultSuccess] will always be returned to the wrapping Result object.
* If the [Patch] failed to apply, an Exception will always be returned to the wrapping Result object.
*/
fun applyPatches(
stopOnError: Boolean = false, callback: (String) -> Unit = {}
): Map<String, Result<PatchResultSuccess>> {
val appliedPatches = mutableListOf<String>()
return buildMap {
for (patch in patcherData.patches) {
val result = applyPatch(patch, appliedPatches)
val name = patch.patchName
callback(name)
this[name] = if (result.isSuccess()) {
Result.success(result.success()!!)
} else {
Result.failure(result.error()!!)
}
if (stopOnError && result.isError()) break
ResourceDecodingMode.MANIFEST_ONLY -> {
logger.info("Decoding AndroidManifest.xml only, because resources are not needed")
// create decoder for the resource table
val decoder = ResAttrDecoder()
decoder.currentPackage = ResPackage(resourceTable, 0, null)
// create xml parser with the decoder
val axmlParser = AXmlResourceParser()
axmlParser.attrDecoder = decoder
// parse package information with the decoder and parser which will set required values in the resource table
// instead of decodeManifest another more low level solution can be created to make it faster/better
XmlPullStreamDecoder(
axmlParser, AndrolibResources().resXmlSerializer
).decodeManifest(
extInputFile.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
)
}
}
// read of the resourceTable which is created by reading the manifest file
context.packageMetadata.let { metadata ->
metadata.packageName = resourceTable.currentResPackage.name
metadata.packageVersion = resourceTable.versionInfo.versionName
metadata.metaInfo.versionInfo = resourceTable.versionInfo
metadata.metaInfo.sdkInfo = resourceTable.sdkInfo
}
} finally {
extInputFile.close()
}
}
/**
* Execute patches added the patcher.
*
* @param stopOnError If true, the patches will stop on the first error.
* @return A pair of the name of the [Patch] and its [PatchResult].
*/
fun executePatches(stopOnError: Boolean = false): Sequence<Pair<String, Result<PatchResultSuccess>>> {
/**
* Execute a [Patch] and its dependencies recursively.
*
* @param patchClass The [Patch] to execute.
* @param executedPatches A map of [Patch]es paired to a boolean indicating their success, to prevent infinite recursion.
* @return The result of executing the [Patch].
*/
fun executePatch(
patchClass: Class<out Patch<Context>>,
executedPatches: LinkedHashMap<String, ExecutedPatch>
): PatchResult {
val patchName = patchClass.patchName
// if the patch has already applied silently skip it
if (executedPatches.contains(patchName)) {
if (!executedPatches[patchName]!!.success)
return PatchResultError("'$patchName' did not succeed previously")
logger.trace("Skipping '$patchName' because it has already been applied")
return PatchResultSuccess()
}
// recursively execute all dependency patches
patchClass.dependencies?.forEach { dependencyClass ->
val dependency = dependencyClass.java
val result = executePatch(dependency, executedPatches)
if (result.isSuccess()) return@forEach
return PatchResultError(
"'$patchName' depends on '${dependency.patchName}' but the following error was raised: " +
result.error()!!.let { it.cause?.stackTraceToString() ?: it.message }
)
}
val isResourcePatch = ResourcePatch::class.java.isAssignableFrom(patchClass)
val patchInstance = patchClass.getDeclaredConstructor().newInstance()
// TODO: implement this in a more polymorphic way
val patchContext = if (isResourcePatch) {
context.resourceContext
} else {
context.bytecodeContext.also { context ->
(patchInstance as BytecodePatch).fingerprints?.resolve(
context,
context.classes.classes
)
}
}
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
return try {
patchInstance.execute(patchContext).also {
executedPatches[patchName] = ExecutedPatch(patchInstance, it.isSuccess())
}
} catch (e: Exception) {
PatchResultError(e).also {
executedPatches[patchName] = ExecutedPatch(patchInstance, false)
}
}
}
return sequence {
if (mergeIntegrations) context.integrations.merge(logger)
// prevent from decoding the manifest twice if it is not needed
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
logger.trace("Executing all patches")
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
try {
context.patches.forEach { patch ->
val patchResult = executePatch(patch, executedPatches)
val result = if (patchResult.isSuccess()) {
Result.success(patchResult.success()!!)
} else {
Result.failure(patchResult.error()!!)
}
yield(patch.patchName to result)
if (stopOnError && patchResult.isError()) return@sequence
}
} finally {
executedPatches.values.reversed().forEach { (patch, _) ->
patch.close()
}
}
}
}
/**
* The type of decoding the resources.
*/
private enum class ResourceDecodingMode {
/**
* Decode all resources.
*/
FULL,
/**
* Decode the manifest file only.
*/
MANIFEST_ONLY,
}
}
/**
* A result of executing a [Patch].
*
* @param patchInstance The instance of the [Patch] that was applied.
* @param success The result of the [Patch].
*/
internal data class ExecutedPatch(val patchInstance: Patch<Context>, val success: Boolean)

View File

@@ -0,0 +1,64 @@
package app.revanced.patcher
import app.revanced.patcher.data.*
import app.revanced.patcher.logging.Logger
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.util.ClassMerger.merge
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 integrations = Integrations(this)
internal val bytecodeContext = BytecodeContext(classes)
internal val resourceContext = ResourceContext(resourceCacheDirectory)
internal class Integrations(val context: PatcherContext) {
var callback: ((File) -> Unit)? = null
private val integrations: MutableList<File> = mutableListOf()
fun add(integrations: List<File>) = this@Integrations.integrations.addAll(integrations)
/**
* Merge integrations.
* @param logger A logger.
*/
fun merge(logger: Logger) {
with(context.bytecodeContext.classes) {
for (integrations in integrations) {
callback?.let { it(integrations) }
for (classDef in lanchon.multidexlib2.MultiDexIO.readDexFile(
true,
integrations,
NAMER,
null,
null
).classes) {
val type = classDef.type
val result = classes.findIndexed { it.type == type }
if (result == null) {
logger.trace("Merging type $type")
classes.add(classDef)
continue
}
val (existingClass, existingClassIndex) = result
logger.trace("Type $type exists. Adding missing methods and fields.")
existingClass.merge(classDef, context, logger).let { mergedClass ->
if (mergedClass !== existingClass) // referential equality check
classes[existingClassIndex] = mergedClass
}
}
}
}
}
}
}

View File

@@ -1,15 +1,21 @@
package app.revanced.patcher
import app.revanced.patcher.logging.Logger
import app.revanced.patcher.logging.impl.NopLogger
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].
*/
data class PatcherOptions(
internal val inputFile: File,
// TODO: maybe a file system in memory is better. Could cause high memory usage.
internal val resourceCacheDirectory: String,
internal val patchResources: Boolean = false
internal val aaptPath: String? = null,
internal val frameworkFolderLocation: String? = null,
internal val logger: Logger = NopLogger
)

View File

@@ -0,0 +1,16 @@
package app.revanced.patcher
import app.revanced.patcher.util.dex.DexFile
import java.io.File
/**
* The result of a patcher.
* @param dexFiles The patched dex files.
* @param doNotCompress List of relative paths to files to exclude from compressing.
* @param resourceFile File containing resources that need to be extracted into the APK.
*/
data class PatcherResult(
val dexFiles: List<DexFile>,
val doNotCompress: List<String>? = null,
val resourceFile: File?
)

View File

@@ -1,15 +1,12 @@
package app.revanced.patcher.annotation
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.patch.Patch
/**
* Annotation to constrain a [Patch] or [MethodSignature] to compatible packages.
* @param compatiblePackages A list of packages a [Patch] or [MethodSignature] is compatible with.
* Annotation to constrain a [Patch] to compatible packages.
* @param compatiblePackages A list of packages a [Patch] is compatible with.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Compatibility(
val compatiblePackages: Array<Package>,
)
@@ -17,12 +14,10 @@ annotation class Compatibility(
/**
* Annotation to represent packages a patch can be compatible with.
* @param name The package identifier name.
* @param versions The versions of the package the [Patch] or [MethodSignature]is compatible with.
* @param versions The versions of the package the [Patch] is compatible with.
*/
@Target()
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Package(
val name: String,
val versions: Array<String>
)
val versions: Array<String> = [],
)

View File

@@ -1,38 +1,31 @@
package app.revanced.patcher.annotation
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.patch.Patch
/**
* Annotation to name a [Patch] or [MethodSignature].
* @param name A suggestive name for the [Patch] or [MethodSignature].
* Annotation to name a [Patch].
* @param name A suggestive name for the [Patch].
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Name(
val name: String,
)
/**
* Annotation to describe a [Patch] or [MethodSignature].
* @param description A description for the [Patch] or [MethodSignature].
* Annotation to describe a [Patch].
* @param description A description for the [Patch].
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Description(
val description: String,
)
/**
* Annotation to version a [Patch] or [MethodSignature].
* @param version The version of a [Patch] or [MethodSignature].
* Annotation to version a [Patch].
* @param version The version of a [Patch].
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Version(
val version: String,
)

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

@@ -0,0 +1,13 @@
package app.revanced.patcher.data
import brut.androlib.meta.MetaInfo
/**
* Metadata about a package.
*/
class PackageMetadata {
lateinit var packageName: String
lateinit var packageVersion: String
internal val metaInfo: MetaInfo = MetaInfo()
}

View File

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

View File

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

View File

@@ -1,69 +0,0 @@
package app.revanced.patcher.data.implementation
import app.revanced.patcher.data.base.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) }
}
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
}
fun BytecodeData.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
}

View File

@@ -1,48 +0,0 @@
package app.revanced.patcher.data.implementation
import app.revanced.patcher.data.base.Data
import org.w3c.dom.Document
import java.io.Closeable
import java.io.File
import javax.xml.XMLConstants
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 {
private fun resolve(path: String) = resourceCacheDirectory.resolve(path)
fun forEach(action: (File) -> Unit) = resourceCacheDirectory.walkTopDown().forEach(action)
fun get(path: String) = resolve(path)
fun replace(path: String, oldValue: String, newValue: String, oldValueIsRegex: Boolean = false) {
// TODO: buffer this somehow
val content = resolve(path).readText()
if (oldValueIsRegex) {
content.replace(Regex(oldValue), newValue)
return
}
}
fun getXmlEditor(path: String) = DomFileEditor(resolve(path))
}
class DomFileEditor internal constructor(private val domFile: File) : Closeable {
val file: Document
init {
val factory = DocumentBuilderFactory.newInstance()
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
val builder = factory.newDocumentBuilder()
// this will expectedly throw
file = builder.parse(domFile)
file.normalize()
}
override fun close() = TransformerFactory.newInstance().newTransformer()
.transform(DOMSource(file), StreamResult(domFile.outputStream()))
}

View File

@@ -1,22 +1,25 @@
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.base.Data
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
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 app.revanced.patcher.patch.annotations.RequiresIntegrations
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,20 +41,43 @@ 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>>.description get() = recursiveAnnotation(Description::class)?.description
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Dependencies::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.simpleName
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
internal val Class<out Patch<Context>>.requiresIntegrations
get() = findAnnotationRecursively(RequiresIntegrations::class) != null
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
}
}
}
object MethodSignatureExtensions {
val MethodSignature.name: String get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
val MethodSignature.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
val MethodSignature.description get() = javaClass.recursiveAnnotation(Description::class)?.description
val MethodSignature.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
val MethodSignature.matchingMethod get() = javaClass.recursiveAnnotation(MatchingMethod::class)
val MethodSignature.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(FuzzyPatternScanMethod::class)
val MethodSignature.fuzzyThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
object MethodFingerprintExtensions {
val MethodFingerprint.name: String
get() = this.javaClass.simpleName
val MethodFingerprint.fuzzyPatternScanMethod
get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class)
val MethodFingerprint.fuzzyScanThreshold
get() = fuzzyPatternScanMethod?.threshold ?: 0
}

View File

@@ -1,14 +1,20 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patcher.util.smali.toInstruction
import app.revanced.patcher.util.smali.toInstructions
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.builder.BuilderInstruction
import org.jf.dexlib2.builder.BuilderOffsetInstruction
import org.jf.dexlib2.builder.Label
import org.jf.dexlib2.builder.MutableMethodImplementation
import org.jf.dexlib2.builder.instruction.*
import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.reference.MethodReference
import org.jf.dexlib2.iface.instruction.Instruction
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
@@ -20,17 +26,22 @@ fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<B
}
}
/**
* Compare a method to another, considering constructors 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
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) {
for (instruction in instructions) {
this.addInstruction(instruction)
}
}
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
for (i in instructions.lastIndex downTo 0) {
this.replaceInstruction(index + i, instructions[i])
}
}
fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) {
for (i in count downTo 0) {
this.removeInstruction(index + i)
}
}
/**
@@ -39,9 +50,7 @@ fun Method.softCompareTo(
* This may be a positive or negative number.
* @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy.
*/
internal fun Method.clone(
registerCount: Int = 0,
): ImmutableMethod {
internal fun Method.clone(registerCount: Int = 0): ImmutableMethod {
val clonedImplementation = implementation?.let {
ImmutableMethodImplementation(
it.registerCount + registerCount,
@@ -51,31 +60,169 @@ internal fun Method.clone(
)
}
return ImmutableMethod(
returnType,
name,
parameters,
returnType,
accessFlags,
annotations,
hiddenApiRestrictions,
clonedImplementation
returnType, name, parameters, returnType, accessFlags, annotations, hiddenApiRestrictions, clonedImplementation
)
}
/**
* Add a smali instruction to the method.
* @param instruction The smali instruction to add.
*/
fun MutableMethod.addInstruction(instruction: String) =
this.implementation!!.addInstruction(instruction.toInstruction(this))
/**
* Add a smali instruction to the method.
* @param index The index to insert the instruction at.
* @param instruction The smali instruction to add.
*/
fun MutableMethod.addInstruction(index: Int, instruction: String) =
this.implementation!!.addInstruction(index, instruction.toInstruction(this))
/**
* Replace a smali instruction within the method.
* @param index The index to replace the instruction at.
* @param instruction The smali instruction to place.
*/
fun MutableMethod.replaceInstruction(index: Int, instruction: String) =
this.implementation!!.replaceInstruction(index, instruction.toInstruction(this))
/**
* Remove a smali instruction within the method.
* @param index The index to delete the instruction at.
*/
fun MutableMethod.removeInstruction(index: Int) = this.implementation!!.removeInstruction(index)
/**
* Create a label for the instruction at given index in the method's implementation.
* @param index The index to create the label for the instruction at.
* @return The label.
*/
fun MutableMethod.label(index: Int) = this.implementation!!.newLabelForIndex(index)
/**
* Get an instruction at the given index in the method's implementation.
* @param index The index to get the instruction at.
* @return The instruction.
*/
fun MutableMethod.instruction(index: Int): BuilderInstruction = this.implementation!!.instructions[index]
/**
* Get an instruction at the given index in the method's implementation.
* @param index The index to get the instruction at.
* @param T The type of instruction to return.
* @return The instruction.
*/
fun <T> MutableMethod.instruction(index: Int): T = instruction(index) as T
/**
* Add smali instructions to the method.
* @param index The index to insert the instructions at.
* @param smali The smali instructions to add.
* @param externalLabels A list of [ExternalLabel] representing a list of labels for instructions which are not in the method to compile.
*/
fun MutableMethod.addInstructions(index: Int, smali: String, externalLabels: List<ExternalLabel> = emptyList()) {
// Create reference dummy instructions for the instructions.
val nopedSmali = StringBuilder(smali).also { builder ->
externalLabels.forEach { (name, _) ->
builder.append("\n:$name\nnop")
}
}.toString()
// Compile the instructions with the dummy labels
val compiledInstructions = nopedSmali.toInstructions(this)
// Add the compiled list of instructions to the method.
val methodImplementation = this.implementation!!
methodImplementation.addInstructions(index, compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size))
val methodInstructions = methodImplementation.instructions
methodInstructions.subList(index, index + compiledInstructions.size - externalLabels.size)
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
// If the compiled instruction is not an offset instruction, skip it.
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
/**
* Creates a new label for the instruction and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex].
*/
fun Instruction.makeNewLabel() {
// Create the final label.
val label = methodImplementation.newLabelForIndex(methodInstructions.indexOf(this))
// Create the final instruction with the new label.
val newInstruction = replaceOffset(
compiledInstruction, label
)
// Replace the instruction pointing to the dummy label with the new instruction pointing to the real instruction.
methodImplementation.replaceInstruction(index + compiledInstructionIndex, newInstruction)
}
// If the compiled instruction targets its own instruction,
// which means it points to some of its own, simply an offset has to be applied.
val labelIndex = compiledInstruction.target.location.index
if (labelIndex < compiledInstructions.size - externalLabels.size) {
// Get the targets index (insertion index + the index of the dummy instruction).
methodInstructions[index + labelIndex].makeNewLabel()
return@forEachIndexed
}
// Since the compiled instruction points to a dummy instruction,
// we can find the real instruction which it was created for by calculation.
// Get the index of the instruction in the externalLabels list which the dummy instruction was created for.
// this line works because we created the dummy instructions in the same order as the externalLabels list.
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
instruction.makeNewLabel()
}
}
/**
* Add smali instructions to the end of the method.
* @param instructions The smali instructions to add.
*/
fun MutableMethod.addInstructions(instructions: String, labels: List<ExternalLabel> = emptyList()) =
this.addInstructions(this.implementation!!.instructions.size, instructions, labels)
/**
* Replace smali instructions within the method.
* @param index The index to replace the instructions at.
* @param instructions The smali instructions to place.
*/
fun MutableMethod.replaceInstructions(index: Int, instructions: String) =
this.implementation!!.replaceInstructions(index, instructions.toInstructions(this))
/**
* Remove smali instructions from the method.
* @param index The index to remove the instructions at.
* @param count The amount of instructions to remove.
*/
fun MutableMethod.removeInstructions(index: Int, count: Int) = this.implementation!!.removeInstructions(index, count)
private fun replaceOffset(
i: BuilderOffsetInstruction, label: Label
): BuilderOffsetInstruction {
return when (i) {
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
is BuilderInstruction22t -> BuilderInstruction22t(i.opcode, i.registerA, i.registerB, label)
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
else -> throw IllegalStateException("A non-offset instruction was given, this should never happen!")
}
}
/**
* Clones the method.
* @param registerCount This parameter allows you to change the register count of the method.
* This may be a positive or negative number.
* @return The **mutable** cloned method. Call [clone] to get an **immutable** copy.
*/
internal fun Method.cloneMutable(
registerCount: Int = 0,
) = clone(registerCount).toMutable()
internal fun Method.cloneMutable(registerCount: Int = 0) = clone(registerCount).toMutable()
// FIXME: also check the order of parameters as different order equals different method overload
internal fun parametersEqual(
parameters1: Iterable<CharSequence>,
parameters2: Iterable<CharSequence>
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
): Boolean {
return parameters1.count() == parameters2.count() && parameters1.all { parameter ->
parameters2.any {
@@ -86,7 +233,6 @@ internal fun parametersEqual(
}
}
internal val nullOutputStream: OutputStream =
object : OutputStream() {
override fun write(b: Int) {}
}
internal val nullOutputStream = object : OutputStream() {
override fun write(b: Int) {}
}

View File

@@ -0,0 +1,9 @@
package app.revanced.patcher.fingerprint
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
/**
* A ReVanced fingerprint.
* Can be a [MethodFingerprint].
*/
interface Fingerprint

View File

@@ -0,0 +1,12 @@
package app.revanced.patcher.fingerprint.method.annotation
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
/**
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.
* @param threshold if [threshold] or more of the opcodes do not match, skip.
*/
@Target(AnnotationTarget.CLASS)
annotation class FuzzyPatternScanMethod(
val threshold: Int = 1
)

View File

@@ -0,0 +1,306 @@
package app.revanced.patcher.fingerprint.method.impl
import app.revanced.patcher.data.BytecodeContext
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.Fingerprint
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.util.proxy.ClassProxy
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
import org.jf.dexlib2.util.MethodUtil
/**
* Represents the [MethodFingerprint] for a method.
* @param returnType The return type of the method.
* @param access The access flags of the method.
* @param parameters The parameters of the method.
* @param opcodes The list of opcodes of the method.
* @param strings A list of strings which a method contains.
* @param customFingerprint A custom condition for this fingerprint.
* A `null` opcode is equals to an unknown opcode.
*/
abstract class MethodFingerprint(
internal val returnType: String? = null,
internal val access: Int? = null,
internal val parameters: Iterable<String>? = null,
internal val opcodes: Iterable<Opcode?>? = null,
internal val strings: Iterable<String>? = null,
internal val customFingerprint: ((methodDef: Method) -> Boolean)? = null
) : Fingerprint {
/**
* The result of the [MethodFingerprint] the [Method].
*/
var result: MethodFingerprintResult? = null
companion object {
/**
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
*
* @param classes The classes on which to resolve the [MethodFingerprint] in.
* @param context The [BytecodeContext] to host proxies.
* @return True if the resolution was successful, false otherwise.
*/
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) {
for (fingerprint in this) // For each fingerprint
classes@ for (classDef in classes) // search through all classes for the fingerprint
if (fingerprint.resolve(context, classDef))
break@classes // if the resolution succeeded, continue with the next fingerprint
}
/**
* Resolve a [MethodFingerprint] against a [ClassDef].
*
* @param forClass The class on which to resolve the [MethodFingerprint] in.
* @param context The [BytecodeContext] to host proxies.
* @return True if the resolution was successful, false otherwise.
*/
fun MethodFingerprint.resolve(context: BytecodeContext, forClass: ClassDef): Boolean {
for (method in forClass.methods)
if (this.resolve(context, method, forClass))
return true
return false
}
/**
* Resolve a [MethodFingerprint] against a [Method].
*
* @param method The class on which to resolve the [MethodFingerprint] in.
* @param forClass The class on which to resolve the [MethodFingerprint].
* @param context The [BytecodeContext] to host proxies.
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
*/
fun MethodFingerprint.resolve(context: BytecodeContext, method: Method, forClass: ClassDef): Boolean {
val methodFingerprint = this
if (methodFingerprint.result != null) return true
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType))
return false
if (methodFingerprint.access != null && methodFingerprint.access != method.accessFlags)
return false
if (methodFingerprint.parameters != null && !parametersEqual(
methodFingerprint.parameters, // TODO: parseParameters()
method.parameterTypes
)
) return false
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method))
return false
val stringsScanResult: StringsScanResult? =
if (methodFingerprint.strings != null) {
StringsScanResult(
buildList {
val implementation = method.implementation ?: return false
val stringsList = methodFingerprint.strings.toMutableList()
implementation.instructions.forEachIndexed { instructionIndex, instruction ->
if (
instruction.opcode != Opcode.CONST_STRING &&
instruction.opcode != Opcode.CONST_STRING_JUMBO
) return@forEachIndexed
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
val index = stringsList.indexOfFirst(string::contains)
if (index == -1) return@forEachIndexed
add(
StringMatch(
string,
instructionIndex
)
)
stringsList.removeAt(index)
}
if (stringsList.isNotEmpty()) return false
}
)
} else null
val patternScanResult = if (methodFingerprint.opcodes != null) {
method.implementation?.instructions ?: return false
method.patternScan(methodFingerprint) ?: return false
} else null
methodFingerprint.result = MethodFingerprintResult(
method,
forClass,
MethodFingerprintResult.MethodFingerprintScanResult(
patternScanResult,
stringsScanResult
),
context
)
return true
}
private fun Method.patternScan(
fingerprint: MethodFingerprint
): MethodFingerprintResult.MethodFingerprintScanResult.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 =
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
index,
index + patternIndex
)
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
result.warnings = result.createWarnings(pattern, instructions)
return result
}
}
return null
}
private fun MethodFingerprintResult.MethodFingerprintScanResult.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(
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.Warning(
originalOpcode,
patternOpcode,
instructionIndex,
patternIndex
)
)
}
}
}
}
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
/**
* Represents the result of a [MethodFingerprintResult].
*
* @param method The matching method.
* @param classDef The [ClassDef] that contains the matching [method].
* @param scanResult The result of scanning for the [MethodFingerprint].
* @param context The [BytecodeContext] this [MethodFingerprintResult] is attached to, to create proxies.
*/
data class MethodFingerprintResult(
val method: Method,
val classDef: ClassDef,
val scanResult: MethodFingerprintScanResult,
internal val context: BytecodeContext
) {
/**
* The result of scanning on the [MethodFingerprint].
* @param patternScanResult The result of the pattern scan.
* @param stringsScanResult The result of the string scan.
*/
data class MethodFingerprintScanResult(
val patternScanResult: PatternScanResult?,
val stringsScanResult: StringsScanResult?
) {
/**
* The result of scanning strings on the [MethodFingerprint].
* @param matches The list of strings that were matched.
*/
data class StringsScanResult(val matches: List<StringMatch>) {
/**
* Represents a match for a string at an index.
* @param string The string that was matched.
* @param index The index of the string.
*/
data class StringMatch(val string: String, val index: Int)
}
/**
* The result of a pattern scan.
* @param startIndex The start index of the instructions where to which this pattern matches.
* @param endIndex The end index of the instructions where to which this pattern matches.
* @param warnings A list of warnings considering this [PatternScanResult].
*/
data class PatternScanResult(
val startIndex: Int,
val endIndex: Int,
var warnings: List<Warning>? = null
) {
/**
* Represents warnings of the pattern scan.
* @param correctOpcode The opcode the instruction list has.
* @param wrongOpcode The opcode the pattern list of the signature currently has.
* @param instructionIndex The index of the opcode relative to the instruction list.
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
*/
data class Warning(
val correctOpcode: Opcode,
val wrongOpcode: Opcode,
val instructionIndex: Int,
val patternIndex: Int,
)
}
}
/**
* Returns a mutable clone of [classDef]
*
* Please note, this method allocates a [ClassProxy].
* Use [classDef] where possible.
*/
@Suppress("MemberVisibilityCanBePrivate")
val mutableClass by lazy { context.proxy(classDef).mutableClass }
/**
* Returns a mutable clone of [method]
*
* Please note, this method allocates a [ClassProxy].
* Use [method] where possible.
*/
val mutableMethod by lazy {
mutableClass.methods.first {
MethodUtil.methodSignaturesMatch(it, this.method)
}
}
}

View File

@@ -0,0 +1,8 @@
package app.revanced.patcher.logging
interface Logger {
fun error(msg: String) {}
fun warn(msg: String) {}
fun info(msg: String) {}
fun trace(msg: String) {}
}

View File

@@ -0,0 +1,5 @@
package app.revanced.patcher.logging.impl
import app.revanced.patcher.logging.Logger
object NopLogger : Logger

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

@@ -0,0 +1,44 @@
package app.revanced.patcher.patch
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.
*
* If it implements [Closeable], it will be closed after all patches have been executed.
* Closing will be done in reverse execution order.
*/
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.
*/
fun execute(context: @UnsafeVariance T): PatchResult
/**
* The closing function for this patch.
*
* This can be treated like popping the patch from the current patch stack.
*/
override fun close() {}
}
/**
* 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

@@ -0,0 +1,248 @@
@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 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].
* @param value The value you want it to be.
* Please note that using the wrong value type results in a runtime error.
*/
inline operator fun <reified T> set(key: String, value: 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"
)
opt.value = value
}
/**
* Sets the value of a [PatchOption] to `null`.
* @param key The key of the [PatchOption].
*/
fun nullify(key: String) {
get(key).value = null
}
override fun iterator() = register.values.iterator()
}
/**
* A [Patch] option.
* @param key Unique identifier of the option. Example: _`settings.microg.enabled`_
* @param default The default value of the option.
* @param title A human-readable title of the option. Example: _MicroG Settings_
* @param description A human-readable description of the option. Example: _Settings integration for MicroG._
* @param required Whether the option is required.
*/
@Suppress("MemberVisibilityCanBePrivate")
sealed class PatchOption<T>(
val key: String,
default: T?,
val title: String,
val description: String,
val required: Boolean,
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
*/
class StringOption(
key: String,
default: String?,
title: String,
description: String,
required: Boolean = false,
validator: (String?) -> Boolean = { true }
) : PatchOption<String>(
key, default, title, description, required, validator
)
/**
* A [PatchOption] representing a [Boolean].
* @see PatchOption
*/
class BooleanOption(
key: String,
default: Boolean?,
title: String,
description: String,
required: Boolean = false,
validator: (Boolean?) -> Boolean = { true }
) : PatchOption<Boolean>(
key, default, title, description, required, validator
)
/**
* A [PatchOption] with a list of allowed options.
* @param options A list of allowed options for the [ListOption].
* @see PatchOption
*/
sealed class ListOption<E>(
key: String,
default: E?,
val options: Iterable<E>,
title: String,
description: String,
required: Boolean = false,
validator: (E?) -> Boolean = { true }
) : PatchOption<E>(
key, default, title, description, required, {
(it?.let { it in options } ?: true) && validator(it)
}
) {
init {
if (default != null && default !in options) {
throw IllegalStateException("Default option must be an allowed option")
}
}
}
/**
* A [ListOption] of type [String].
* @see ListOption
*/
class StringListOption(
key: String,
default: String?,
options: Iterable<String>,
title: String,
description: String,
required: Boolean = false,
validator: (String?) -> Boolean = { true }
) : ListOption<String>(
key, default, options, title, description, required, validator
)
/**
* A [ListOption] of type [Int].
* @see ListOption
*/
class IntListOption(
key: String,
default: Int?,
options: Iterable<Int>,
title: String,
description: String,
required: Boolean = false,
validator: (Int?) -> Boolean = { true }
) : 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,4 +1,4 @@
package app.revanced.patcher.patch.implementation.misc
package app.revanced.patcher.patch
interface PatchResult {
fun error(): PatchResultError? {

View File

@@ -1,23 +1,27 @@
package app.revanced.patcher.patch.annotations
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.data.Context
import app.revanced.patcher.patch.Patch
import kotlin.reflect.KClass
/**
* Annotation to mark a Class as a patch.
* Annotation to mark a class as a patch.
* @param include If false, the patch should be treated as optional by default.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Patch
annotation class Patch(val include: Boolean = true)
/**
* Annotation for dependencies of [Patch]es .
* Annotation for dependencies of [Patch]es.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Dependencies(
val dependencies: Array<KClass<out Patch<Data>>> = []
)
annotation class DependsOn(
val dependencies: Array<KClass<out Patch<Context>>> = []
)
/**
* Annotation to mark [Patch]es which depend on integrations.
*/
@Target(AnnotationTarget.CLASS)
annotation class RequiresIntegrations // required because integrations are decoupled from patches

View File

@@ -1,19 +0,0 @@
package app.revanced.patcher.patch.base
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.patch.implementation.BytecodePatch
import app.revanced.patcher.patch.implementation.ResourcePatch
import app.revanced.patcher.patch.implementation.misc.PatchResult
/**
* A ReVanced patch.
* Can either be a [ResourcePatch] or a [BytecodePatch].
*/
abstract class Patch<out T : Data> {
/**
* The main function of the [Patch] which the patcher will call.
*/
abstract fun execute(data: @UnsafeVariance T): PatchResult
}

View File

@@ -1,13 +0,0 @@
package app.revanced.patcher.patch.implementation
import app.revanced.patcher.data.implementation.BytecodeData
import app.revanced.patcher.patch.base.Patch
import app.revanced.patcher.signature.implementation.method.MethodSignature
/**
* Bytecode patch for the Patcher.
* @param signatures A list of [MethodSignature] this patch relies on.
*/
abstract class BytecodePatch(
val signatures: Iterable<MethodSignature>
) : Patch<BytecodeData>()

View File

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

View File

@@ -1,9 +0,0 @@
package app.revanced.patcher.signature.base
import app.revanced.patcher.signature.implementation.method.MethodSignature
/**
* A ReVanced signature.
* Can be a [MethodSignature].
*/
interface Signature

View File

@@ -1,33 +0,0 @@
package app.revanced.patcher.signature.implementation.method
import app.revanced.patcher.data.implementation.MethodNotFoundException
import app.revanced.patcher.extensions.MethodSignatureExtensions.name
import app.revanced.patcher.signature.base.Signature
import app.revanced.patcher.signature.implementation.method.resolver.SignatureResolverResult
import org.jf.dexlib2.Opcode
/**
* Represents the [MethodSignature] for a method.
* @param returnType The return type of the method.
* @param accessFlags The access flags of the method.
* @param methodParameters The parameters of the method.
* @param opcodes The list of opcodes of the method.
* @param strings A list of strings which a method contains.
* A `null` opcode is equals to an unknown opcode.
*/
abstract class MethodSignature(
internal val returnType: String?,
internal val accessFlags: Int?,
internal val methodParameters: Iterable<String>?,
internal val opcodes: Iterable<Opcode?>?,
internal val strings: Iterable<String>? = null
) : Signature {
/**
* The result of the signature
*/
var result: SignatureResolverResult? = null
@Throws(MethodNotFoundException::class)
get() {
return field ?: throw MethodNotFoundException("Could not resolve required signature ${this.name}")
}
}

View File

@@ -1,32 +0,0 @@
package app.revanced.patcher.signature.implementation.method.annotation
import app.revanced.patcher.signature.implementation.method.MethodSignature
/**
* Annotations for a method which matches to a [MethodSignature].
* @param definingClass The defining class name of the method.
* @param name A suggestive name for the method which the [MethodSignature] 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 [MethodSignature] with fuzzy algorithm.
* @param threshold if [threshold] or more of the opcodes do not match, skip.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class FuzzyPatternScanMethod(
val threshold: Int = 1
)
/**
* Annotations to scan a pattern [MethodSignature] directly.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class DirectPatternScanMethod

View File

@@ -1,164 +0,0 @@
package app.revanced.patcher.signature.implementation.method.resolver
import app.revanced.patcher.data.PatcherData
import app.revanced.patcher.data.implementation.proxy
import app.revanced.patcher.extensions.MethodSignatureExtensions.fuzzyThreshold
import app.revanced.patcher.extensions.parametersEqual
import app.revanced.patcher.signature.implementation.method.MethodSignature
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.formats.Instruction21c
import org.jf.dexlib2.iface.reference.StringReference
internal class MethodSignatureResolver(
private val classes: List<ClassDef>,
private val methodSignatures: Iterable<MethodSignature>
) {
fun resolve(patcherData: PatcherData) {
for (signature in methodSignatures) {
for (classDef in classes) {
for (method in classDef.methods) {
val patternScanData = compareSignatureToMethod(signature, method) ?: continue
// create class proxy, in case a patch needs mutability
val classProxy = patcherData.bytecodeData.proxy(classDef)
signature.result = SignatureResolverResult(
classProxy,
patternScanData,
method,
)
}
}
}
}
// These functions do not require the constructor values, so they can be static.
companion object {
fun resolveFromProxy(
classProxy: app.revanced.patcher.util.proxy.ClassProxy,
signature: MethodSignature
): SignatureResolverResult? {
for (method in classProxy.immutableClass.methods) {
val result = compareSignatureToMethod(signature, method) ?: continue
return SignatureResolverResult(
classProxy,
result,
method,
)
}
return null
}
private fun compareSignatureToMethod(
signature: MethodSignature,
method: Method
): PatternScanResult? {
signature.returnType?.let {
if (!method.returnType.startsWith(signature.returnType)) {
return null
}
}
signature.accessFlags?.let {
if (signature.accessFlags != method.accessFlags) {
return null
}
}
signature.methodParameters?.let {
if (!parametersEqual(signature.methodParameters, method.parameterTypes)) {
return null
}
}
signature.strings?.let { strings ->
method.implementation ?: return null
method.implementation!!.instructions.let { instructions ->
val stringsList = strings.toMutableList()
for (instruction in instructions) {
if (instruction.opcode != Opcode.CONST_STRING) continue
val string = ((instruction as Instruction21c).reference as StringReference).string
val i = stringsList.indexOfFirst { it == string }
if (i != -1) stringsList.removeAt(i)
}
if (stringsList.isNotEmpty()) return null
}
}
return if (signature.opcodes == null) {
PatternScanResult(0, 0)
} else {
method.implementation?.instructions?.let {
compareOpcodes(signature, it)
}
}
}
private fun compareOpcodes(
signature: MethodSignature,
instructions: Iterable<Instruction>
): PatternScanResult? {
val count = instructions.count()
val pattern = signature.opcodes!!
val size = pattern.count()
val threshold = signature.fuzzyThreshold
for (instructionIndex in 0 until count) {
var patternIndex = 0
var currentThreshold = threshold
while (instructionIndex + patternIndex < count) {
val originalOpcode = instructions.elementAt(instructionIndex + patternIndex).opcode
val patternOpcode = pattern.elementAt(patternIndex)
if (
patternOpcode != null && // unknown opcode
originalOpcode != patternOpcode &&
currentThreshold-- == 0
) break
if (++patternIndex < size) continue
patternIndex-- // fix pattern offset
val result = PatternScanResult(instructionIndex, instructionIndex + patternIndex)
result.warnings = generateWarnings(signature, instructions, result)
return result
}
}
return null
}
private fun generateWarnings(
signature: MethodSignature,
instructions: Iterable<Instruction>,
scanResult: PatternScanResult,
) = buildList {
val pattern = signature.opcodes!!
for ((patternIndex, instructionIndex) in (scanResult.startIndex until scanResult.endIndex).withIndex()) {
val correctOpcode = instructions.elementAt(instructionIndex).opcode
val patternOpcode = pattern.elementAt(patternIndex)
if (
patternOpcode != null && // unknown opcode
correctOpcode != patternOpcode
) {
this.add(
PatternScanResult.Warning(
correctOpcode, patternOpcode,
instructionIndex, patternIndex,
)
)
}
}
}
}
}
private operator fun ClassDef.component1() = this
private operator fun ClassDef.component2() = this.methods

View File

@@ -1,75 +0,0 @@
package app.revanced.patcher.signature.implementation.method.resolver
import app.revanced.patcher.extensions.softCompareTo
import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.util.proxy.ClassProxy
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.iface.Method
/**
* Represents the result of a [MethodSignatureResolver].
* @param definingClassProxy The [ClassProxy] that the matching method was found in.
* @param resolvedMethod The actual matching method.
* @param scanResult Opcodes pattern scan result.
*/
data class SignatureResolverResult(
val definingClassProxy: ClassProxy,
val scanResult: PatternScanResult,
private val resolvedMethod: Method,
) {
/**
* Returns the **mutable** method by the [resolvedMethod] from the [definingClassProxy].
*
* Please note, this method allocates a [ClassProxy].
* Use [immutableMethod] where possible.
*/
val method
get() = definingClassProxy.resolve().methods.first {
it.softCompareTo(resolvedMethod)
}
/**
* Returns the **immutable** method by the [resolvedMethod] from the [definingClassProxy].
*
* If you need to modify the method, use [method] instead.
*/
@Suppress("MemberVisibilityCanBePrivate")
val immutableMethod: Method
get() = definingClassProxy.immutableClass.methods.first {
it.softCompareTo(resolvedMethod)
}
fun findParentMethod(signature: MethodSignature): SignatureResolverResult? {
return MethodSignatureResolver.resolveFromProxy(definingClassProxy, signature)
}
}
data class PatternScanResult(
val startIndex: Int,
val endIndex: Int
) {
/**
* A list of warnings the resolver found.
*
* This list will be allocated when the signature has been found.
* Meaning, if the signature was not found,
* or the signature was not yet resolved,
* the list will be null.
*/
var warnings: List<Warning>? = null
/**
* Represents a resolver warning.
* @param correctOpcode The opcode the instruction list has.
* @param wrongOpcode The opcode the pattern list of the signature currently has.
* @param instructionIndex The index of the opcode relative to the instruction list.
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
*/
data class Warning(
val correctOpcode: Opcode,
val wrongOpcode: Opcode,
val instructionIndex: Int,
val patternIndex: Int,
)
}

View File

@@ -0,0 +1,214 @@
package app.revanced.patcher.util
import app.revanced.patcher.PatcherContext
import app.revanced.patcher.extensions.or
import app.revanced.patcher.logging.Logger
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
import app.revanced.patcher.util.ClassMerger.Utils.filterAny
import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny
import app.revanced.patcher.util.ClassMerger.Utils.isPublic
import app.revanced.patcher.util.ClassMerger.Utils.toPublic
import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableField
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.iface.ClassDef
import org.jf.dexlib2.util.MethodUtil
import kotlin.reflect.KFunction2
/**
* Experimental class to merge a [ClassDef] with another.
* Note: This will not consider method implementations or if the class is missing a superclass or interfaces.
*/
internal object ClassMerger {
/**
* Merge a class with [otherClass].
*
* @param otherClass The class to merge with
* @param context The context to traverse the class hierarchy in.
* @param logger A logger.
*/
fun ClassDef.merge(otherClass: ClassDef, context: PatcherContext, logger: Logger? = null) = this
//.fixFieldAccess(otherClass, logger)
//.fixMethodAccess(otherClass, logger)
.addMissingFields(otherClass, logger)
.addMissingMethods(otherClass, logger)
.publicize(otherClass, context, logger)
/**
* Add methods which are missing but existing in [fromClass].
*
* @param fromClass The class to add missing methods from.
* @param logger A logger.
*/
private fun ClassDef.addMissingMethods(fromClass: ClassDef, logger: Logger? = null): ClassDef {
val missingMethods = fromClass.methods.let { fromMethods ->
methods.filterNot { method ->
fromMethods.any { fromMethod ->
MethodUtil.methodSignaturesMatch(fromMethod, method)
}
}
}
if (missingMethods.isEmpty()) return this
logger?.trace("Found ${missingMethods.size} missing methods")
return asMutableClass().apply {
methods.addAll(missingMethods.map { it.toMutable() })
}
}
/**
* Add fields which are missing but existing in [fromClass].
*
* @param fromClass The class to add missing fields from.
* @param logger A logger.
*/
private fun ClassDef.addMissingFields(fromClass: ClassDef, logger: Logger? = null): ClassDef {
val missingFields = fields.filterNotAny(fromClass.fields) { field, fromField ->
fromField.name == field.name
}
if (missingFields.isEmpty()) return this
logger?.trace("Found ${missingFields.size} missing fields")
return asMutableClass().apply {
fields.addAll(missingFields.map { it.toMutable() })
}
}
/**
* Make a class and its super class public recursively.
* @param reference The class to check the [AccessFlags] of.
* @param context The context to traverse the class hierarchy in.
* @param logger A logger.
*/
private fun ClassDef.publicize(reference: ClassDef, context: PatcherContext, logger: Logger? = null) =
if (reference.accessFlags.isPublic() && !accessFlags.isPublic())
this.asMutableClass().apply {
context.bytecodeContext.traverseClassHierarchy(this) {
if (accessFlags.isPublic()) return@traverseClassHierarchy
logger?.trace("Publicizing ${this.type}")
accessFlags = accessFlags.toPublic()
}
}
else this
/**
* Publicize fields if they are public in [reference].
*
* @param reference The class to check the [AccessFlags] of the fields in.
* @param logger A logger.
*/
private fun ClassDef.fixFieldAccess(reference: ClassDef, logger: Logger? = null): ClassDef {
val brokenFields = fields.filterAny(reference.fields) { field, referenceField ->
if (field.name != referenceField.name) return@filterAny false
referenceField.accessFlags.isPublic() && !field.accessFlags.isPublic()
}
if (brokenFields.isEmpty()) return this
logger?.trace("Found ${brokenFields.size} broken fields")
/**
* Make a field public.
*/
fun MutableField.publicize() {
accessFlags = accessFlags.toPublic()
}
return asMutableClass().apply {
fields.filter { brokenFields.contains(it) }.forEach(MutableField::publicize)
}
}
/**
* Publicize methods if they are public in [reference].
*
* @param reference The class to check the [AccessFlags] of the methods in.
* @param logger A logger.
*/
private fun ClassDef.fixMethodAccess(reference: ClassDef, logger: Logger? = null): ClassDef {
val brokenMethods = methods.filterAny(reference.methods) { method, referenceMethod ->
if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false
referenceMethod.accessFlags.isPublic() && !method.accessFlags.isPublic()
}
if (brokenMethods.isEmpty()) return this
logger?.trace("Found ${brokenMethods.size} methods")
/**
* Make a method public.
*/
fun MutableMethod.publicize() {
accessFlags = accessFlags.toPublic()
}
return asMutableClass().apply {
methods.filter { brokenMethods.contains(it) }.forEach(MutableMethod::publicize)
}
}
private object Utils {
fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable()
/**
* Check if the [AccessFlags.PUBLIC] flag is set.
*
* @return True, if the flag is set.
*/
fun Int.isPublic() = AccessFlags.PUBLIC.isSet(this)
/**
* Make [AccessFlags] public.
*
* @return The new [AccessFlags].
*/
fun Int.toPublic() = this.or(AccessFlags.PUBLIC).and(AccessFlags.PRIVATE.value.inv())
/**
* Filter [this] on [needles] matching the given [predicate].
*
* @param this The hay to filter for [needles].
* @param needles The needles to filter [this] with.
* @param predicate The filter.
* @return The [this] filtered on [needles] matching the given [predicate].
*/
fun <HayType, NeedleType> Iterable<HayType>.filterAny(
needles: Iterable<NeedleType>, predicate: (HayType, NeedleType) -> Boolean
) = Iterable<HayType>::filter.any(this, needles, predicate)
/**
* Filter [this] on [needles] not matching the given [predicate].
*
* @param this The hay to filter for [needles].
* @param needles The needles to filter [this] with.
* @param predicate The filter.
* @return The [this] filtered on [needles] not matching the given [predicate].
*/
fun <HayType, NeedleType> Iterable<HayType>.filterNotAny(
needles: Iterable<NeedleType>, predicate: (HayType, NeedleType) -> Boolean
) = Iterable<HayType>::filterNot.any(this, needles, predicate)
fun <HayType, NeedleType> KFunction2<Iterable<HayType>, (HayType) -> Boolean, List<HayType>>.any(
haystack: Iterable<HayType>,
needles: Iterable<NeedleType>,
predicate: (HayType, NeedleType) -> Boolean
) = this(haystack) { hay ->
needles.any { needle ->
predicate(hay, needle)
}
}
}
}

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,19 @@
package app.revanced.patcher.util
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
object TypeUtil {
/**
* traverse the class hierarchy starting from the given root class
*
* @param targetClass the class to start traversing the class hierarchy from
* @param callback function that is called for every class in the hierarchy
*/
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
callback(targetClass)
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
traverseClassHierarchy(it, callback)
}
}
}

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

@@ -0,0 +1,10 @@
package app.revanced.patcher.util.dex
import java.io.InputStream
/**
* Wrapper for dex files.
* @param name The original name of the dex file.
* @param stream The dex file as [InputStream].
*/
data class DexFile(val name: String, val stream: InputStream)

View File

@@ -1,27 +1,27 @@
package app.revanced.patcher.util.method
import app.revanced.patcher.data.implementation.BytecodeData
import app.revanced.patcher.data.implementation.MethodNotFoundException
import app.revanced.patcher.extensions.softCompareTo
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import org.jf.dexlib2.Format
import org.jf.dexlib2.iface.Method
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.iface.reference.MethodReference
import org.jf.dexlib2.util.Preconditions
import org.jf.dexlib2.util.MethodUtil
/**
* 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
@@ -29,27 +29,28 @@ 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)
Preconditions.checkFormat(instruction.opcode, Format.Format35c)
val newMethod = (instruction as ReferenceInstruction).reference as MethodReference
val proxy = bytecodeContext.findClass(newMethod.definingClass)!!
val newMethod = (instruction as Instruction35c).reference as MethodReference
val proxy = bytecodeData.findClass(newMethod.definingClass)!!
val methods = if (walkMutable) proxy.resolve().methods else proxy.immutableClass.methods
currentMethod = methods.first { it ->
return@first it.softCompareTo(newMethod)
val methods = if (walkMutable) proxy.mutableClass.methods else proxy.immutableClass.methods
currentMethod = methods.first {
return@first MethodUtil.methodSignaturesMatch(it, newMethod)
}
return this
}
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,75 @@
@file:Suppress("unused")
package app.revanced.patcher.util.patch
import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.PatchExtensions.patchName
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 {
classNames.forEach { className ->
val clazz = classLoader.loadClass(className)
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
}
}.sortedBy { it.patchName }
/**
* A patch bundle of type [Jar].
*
* @param patchBundlePath The path to the patch bundle.
*/
class Jar(patchBundlePath: String) : PatchBundle(patchBundlePath) {
/**
* Load patches from the patch bundle.
*
* Patches will be loaded with a new [URLClassLoader].
*/
fun loadPatches() = loadPatches(
URLClassLoader(
arrayOf(this.toURI().toURL()),
Thread.currentThread().contextClassLoader // TODO: find out why this is required
),
StringIterator(
JarFile(this)
.entries()
.toList() // TODO: find a cleaner solution than that to filter non class files
.filter {
it.name.endsWith(".class") && !it.name.contains("$")
}
.iterator()
) {
it.realName.replace('/', '.').replace(".class", "")
}
)
}
/**
* A patch bundle of type [Dex] format.
*
* @param patchBundlePath The path to a patch bundle of dex format.
* @param dexClassLoader The dex class loader.
*/
class Dex(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
/**
* Load patches from the patch bundle.
*
* Patches will be loaded to the provided [dexClassLoader].
*/
fun loadPatches() = loadPatches(dexClassLoader,
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
})
}
}

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.util.patch.util
package app.revanced.patcher.util.patch
internal class StringIterator<T, I : Iterator<T>>(
private val iterator: I,

View File

@@ -1,18 +0,0 @@
package app.revanced.patcher.util.patch.base
import app.revanced.patcher.data.base.Data
import app.revanced.patcher.patch.base.Patch
import java.io.File
/**
* @param patchBundlePath The path to the patch bundle.
*/
abstract class PatchBundle(patchBundlePath: String) : File(patchBundlePath) {
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
classNames.forEach { className ->
val clazz = classLoader.loadClass(className)
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
}
}
}

View File

@@ -1,17 +0,0 @@
package app.revanced.patcher.util.patch.implementation
import app.revanced.patcher.util.patch.base.PatchBundle
import app.revanced.patcher.util.patch.util.StringIterator
import org.jf.dexlib2.DexFileFactory
/**
* A patch bundle of the ReVanced [DexPatchBundle] format.
* @param patchBundlePath The path to a patch bundle of dex format.
* @param dexClassLoader The dex class loader.
*/
class DexPatchBundle(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
fun loadPatches() = loadPatches(dexClassLoader,
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
})
}

View File

@@ -1,30 +0,0 @@
package app.revanced.patcher.util.patch.implementation
import app.revanced.patcher.util.patch.base.PatchBundle
import app.revanced.patcher.util.patch.util.StringIterator
import java.net.URLClassLoader
import java.util.jar.JarFile
/**
* A patch bundle of the ReVanced [JarPatchBundle] format.
* @param patchBundlePath The path to the patch bundle.
*/
class JarPatchBundle(patchBundlePath: String) : PatchBundle(patchBundlePath) {
fun loadPatches() = loadPatches(
URLClassLoader(
arrayOf(this.toURI().toURL()),
Thread.currentThread().contextClassLoader // TODO: find out why this is required
),
StringIterator(
JarFile(this)
.entries()
.toList() // TODO: find a cleaner solution than that to filter non class files
.filter {
it.name.endsWith(".class") && !it.name.contains("$")
}
.iterator()
) {
it.realName.replace('/', '.').replace(".class", "")
}
)
}

View File

@@ -8,34 +8,26 @@ import org.jf.dexlib2.iface.ClassDef
*
* A class proxy simply holds a reference to the original class
* and allocates a mutable clone for the original class if needed.
* @param immutableClass The class to proxy
* @param immutableClass The class to proxy.
*/
class ClassProxy(
class ClassProxy internal constructor(
val immutableClass: ClassDef,
) {
internal var proxyUsed = false
internal lateinit var mutatedClass: MutableClass
init {
// in the instance, that a [MutableClass] is being proxied,
// do not create an additional clone and reuse the [MutableClass] instance
if (immutableClass is MutableClass) {
mutatedClass = immutableClass
proxyUsed = true
}
}
/**
* Weather the proxy was actually used.
*/
internal var resolved = false
/**
* Allocates and returns a mutable clone of the original class.
* A patch should always use the original immutable class reference
* to avoid unnecessary allocations for the mutable class.
* @return A mutable clone of the original class.
* The mutable clone of the original class.
*
* Note: This is only allocated if the proxy is actually used.
*/
fun resolve(): MutableClass {
if (!proxyUsed) {
proxyUsed = true
mutatedClass = MutableClass(immutableClass)
}
return mutatedClass
val mutableClass by lazy {
resolved = true
if (immutableClass is MutableClass) {
immutableClass
} else
MutableClass(immutableClass)
}
}

View File

@@ -20,6 +20,22 @@ class MutableMethod(method: Method) : Method, BaseMethodReference() {
private val _parameterTypes by lazy { method.parameterTypes.toMutableList() }
private val _hiddenApiRestrictions by lazy { method.hiddenApiRestrictions }
fun setDefiningClass(definingClass: String) {
this.definingClass = definingClass
}
fun setName(name: String) {
this.name = name
}
fun setAccessFlags(accessFlags: Int) {
this.accessFlags = accessFlags
}
fun setReturnType(returnType: String) {
this.returnType = returnType
}
override fun getDefiningClass(): String {
return definingClass
}

View File

@@ -0,0 +1,10 @@
package app.revanced.patcher.util.smali
import org.jf.dexlib2.iface.instruction.Instruction
/**
* A class that represents a label for an instruction.
* @param name The label name.
* @param instruction The instruction that this label is for.
*/
data class ExternalLabel(internal val name: String, internal val instruction: Instruction)

View File

@@ -1,8 +1,10 @@
package app.revanced.patcher.util.smali
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import org.antlr.runtime.CommonTokenStream
import org.antlr.runtime.TokenSource
import org.antlr.runtime.tree.CommonTreeNodeStream
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcodes
import org.jf.dexlib2.builder.BuilderInstruction
import org.jf.dexlib2.writer.builder.DexBuilder
@@ -13,32 +15,31 @@ import org.jf.smali.smaliTreeWalker
import java.io.InputStreamReader
private const val METHOD_TEMPLATE = """
.class LInlineCompiler;
.super Ljava/lang/Object;
.method %s dummyMethod(%s)V
.registers %d
%s
.end method
.class LInlineCompiler;
.super Ljava/lang/Object;
.method %s dummyMethod(%s)V
.registers %d
%s
.end method
"""
class InlineSmaliCompiler {
companion object {
/**
* Compiles a string of Smali code to a list of instructions.
* p0, p1 etc. will only work correctly if the parameters and registers are passed.
* Do not cross the boundaries of the control flow (if-nez insn, etc),
* as that will result in exceptions since the labels cannot be calculated.
* Do not create dummy labels to fix the issue, since the code addresses will
* be messed up and results in broken Dalvik bytecode.
* FIXME: Fix the above issue. When this is fixed, add the proper conversions in [InstructionConverter].
* Special registers (such as p0, p1) will only work correctly
* if the parameters and registers of the method are passed.
*/
fun compileMethodInstructions(
instructions: String,
parameters: String,
registers: Int,
forStaticMethod: Boolean
fun compile(
instructions: String, parameters: String, registers: Int, forStaticMethod: Boolean
): List<BuilderInstruction> {
val input = METHOD_TEMPLATE.format(if (forStaticMethod) "static" else "", parameters, registers, instructions)
val input = METHOD_TEMPLATE.format(
if (forStaticMethod) {
"static"
} else {
""
}, parameters, registers, instructions
)
val reader = InputStreamReader(input.byteInputStream())
val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
val tokens = CommonTokenStream(lexer as TokenSource)
@@ -54,13 +55,30 @@ class InlineSmaliCompiler {
val dexGen = smaliTreeWalker(treeStream)
dexGen.setDexBuilder(DexBuilder(Opcodes.getDefault()))
val classDef = dexGen.smali_file()
return classDef.methods.first().implementation!!.instructions.map { it.toBuilderInstruction() }
return classDef.methods.first().implementation!!.instructions.map { it as BuilderInstruction }
}
}
}
fun String.toInstructions(parameters: String = "", registers: Int = 1, forStaticMethod: Boolean = true) =
InlineSmaliCompiler.compileMethodInstructions(this, parameters, registers, forStaticMethod)
/**
* Compile lines of Smali code to a list of instructions.
*
* Note: Adding compiled instructions to an existing method with
* offset instructions WITHOUT specifying a parent method will not work.
* @param method The method to compile the instructions against.
* @returns A list of instructions.
*/
fun String.toInstructions(method: MutableMethod? = null): List<BuilderInstruction> {
return InlineSmaliCompiler.compile(this,
method?.parameters?.joinToString("") { it } ?: "",
method?.implementation?.registerCount ?: 1,
method?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true
)
}
fun String.toInstruction(parameters: String = "", registers: Int = 1, forStaticMethod: Boolean = true) =
this.toInstructions(parameters, registers, forStaticMethod).first()
/**
* Compile a line of Smali code to an instruction.
* @param templateMethod The method to compile the instructions against.
* @return The instruction.
*/
fun String.toInstruction(templateMethod: MutableMethod? = null) = this.toInstructions(templateMethod).first()

View File

@@ -1,261 +0,0 @@
package app.revanced.patcher.util.smali
import org.jf.dexlib2.Format
import org.jf.dexlib2.builder.instruction.*
import org.jf.dexlib2.iface.instruction.Instruction
import org.jf.dexlib2.iface.instruction.formats.*
import org.jf.util.ExceptionWithContext
fun Instruction.toBuilderInstruction() =
when (this.opcode.format) {
Format.Format10x -> InstructionConverter.newBuilderInstruction10x(this as Instruction10x)
Format.Format11n -> InstructionConverter.newBuilderInstruction11n(this as Instruction11n)
Format.Format11x -> InstructionConverter.newBuilderInstruction11x(this as Instruction11x)
Format.Format12x -> InstructionConverter.newBuilderInstruction12x(this as Instruction12x)
Format.Format20bc -> InstructionConverter.newBuilderInstruction20bc(this as Instruction20bc)
Format.Format21c -> InstructionConverter.newBuilderInstruction21c(this as Instruction21c)
Format.Format21ih -> InstructionConverter.newBuilderInstruction21ih(this as Instruction21ih)
Format.Format21lh -> InstructionConverter.newBuilderInstruction21lh(this as Instruction21lh)
Format.Format21s -> InstructionConverter.newBuilderInstruction21s(this as Instruction21s)
Format.Format22b -> InstructionConverter.newBuilderInstruction22b(this as Instruction22b)
Format.Format22c -> InstructionConverter.newBuilderInstruction22c(this as Instruction22c)
Format.Format22cs -> InstructionConverter.newBuilderInstruction22cs(this as Instruction22cs)
Format.Format22s -> InstructionConverter.newBuilderInstruction22s(this as Instruction22s)
Format.Format22x -> InstructionConverter.newBuilderInstruction22x(this as Instruction22x)
Format.Format23x -> InstructionConverter.newBuilderInstruction23x(this as Instruction23x)
Format.Format31c -> InstructionConverter.newBuilderInstruction31c(this as Instruction31c)
Format.Format31i -> InstructionConverter.newBuilderInstruction31i(this as Instruction31i)
Format.Format32x -> InstructionConverter.newBuilderInstruction32x(this as Instruction32x)
Format.Format35c -> InstructionConverter.newBuilderInstruction35c(this as Instruction35c)
Format.Format35mi -> InstructionConverter.newBuilderInstruction35mi(this as Instruction35mi)
Format.Format35ms -> InstructionConverter.newBuilderInstruction35ms(this as Instruction35ms)
Format.Format3rc -> InstructionConverter.newBuilderInstruction3rc(this as Instruction3rc)
Format.Format3rmi -> InstructionConverter.newBuilderInstruction3rmi(this as Instruction3rmi)
Format.Format3rms -> InstructionConverter.newBuilderInstruction3rms(this as Instruction3rms)
Format.Format51l -> InstructionConverter.newBuilderInstruction51l(this as Instruction51l)
else -> throw ExceptionWithContext("Instruction format %s not supported", this.opcode.format)
}
internal class InstructionConverter {
companion object {
internal fun newBuilderInstruction10x(instruction: Instruction10x): BuilderInstruction10x {
return BuilderInstruction10x(
instruction.opcode
)
}
internal fun newBuilderInstruction11n(instruction: Instruction11n): BuilderInstruction11n {
return BuilderInstruction11n(
instruction.opcode,
instruction.registerA,
instruction.narrowLiteral
)
}
internal fun newBuilderInstruction11x(instruction: Instruction11x): BuilderInstruction11x {
return BuilderInstruction11x(
instruction.opcode,
instruction.registerA
)
}
internal fun newBuilderInstruction12x(instruction: Instruction12x): BuilderInstruction12x {
return BuilderInstruction12x(
instruction.opcode,
instruction.registerA,
instruction.registerB
)
}
internal fun newBuilderInstruction20bc(instruction: Instruction20bc): BuilderInstruction20bc {
return BuilderInstruction20bc(
instruction.opcode,
instruction.verificationError,
instruction.reference
)
}
internal fun newBuilderInstruction21c(instruction: Instruction21c): BuilderInstruction21c {
return BuilderInstruction21c(
instruction.opcode,
instruction.registerA,
instruction.reference
)
}
internal fun newBuilderInstruction21ih(instruction: Instruction21ih): BuilderInstruction21ih {
return BuilderInstruction21ih(
instruction.opcode,
instruction.registerA,
instruction.narrowLiteral
)
}
internal fun newBuilderInstruction21lh(instruction: Instruction21lh): BuilderInstruction21lh {
return BuilderInstruction21lh(
instruction.opcode,
instruction.registerA,
instruction.wideLiteral
)
}
internal fun newBuilderInstruction21s(instruction: Instruction21s): BuilderInstruction21s {
return BuilderInstruction21s(
instruction.opcode,
instruction.registerA,
instruction.narrowLiteral
)
}
internal fun newBuilderInstruction22b(instruction: Instruction22b): BuilderInstruction22b {
return BuilderInstruction22b(
instruction.opcode,
instruction.registerA,
instruction.registerB,
instruction.narrowLiteral
)
}
internal fun newBuilderInstruction22c(instruction: Instruction22c): BuilderInstruction22c {
return BuilderInstruction22c(
instruction.opcode,
instruction.registerA,
instruction.registerB,
instruction.reference
)
}
internal fun newBuilderInstruction22cs(instruction: Instruction22cs): BuilderInstruction22cs {
return BuilderInstruction22cs(
instruction.opcode,
instruction.registerA,
instruction.registerB,
instruction.fieldOffset
)
}
internal fun newBuilderInstruction22s(instruction: Instruction22s): BuilderInstruction22s {
return BuilderInstruction22s(
instruction.opcode,
instruction.registerA,
instruction.registerB,
instruction.narrowLiteral
)
}
internal fun newBuilderInstruction22x(instruction: Instruction22x): BuilderInstruction22x {
return BuilderInstruction22x(
instruction.opcode,
instruction.registerA,
instruction.registerB
)
}
internal fun newBuilderInstruction23x(instruction: Instruction23x): BuilderInstruction23x {
return BuilderInstruction23x(
instruction.opcode,
instruction.registerA,
instruction.registerB,
instruction.registerC
)
}
internal fun newBuilderInstruction31c(instruction: Instruction31c): BuilderInstruction31c {
return BuilderInstruction31c(
instruction.opcode,
instruction.registerA,
instruction.reference
)
}
internal fun newBuilderInstruction31i(instruction: Instruction31i): BuilderInstruction31i {
return BuilderInstruction31i(
instruction.opcode,
instruction.registerA,
instruction.narrowLiteral
)
}
internal fun newBuilderInstruction32x(instruction: Instruction32x): BuilderInstruction32x {
return BuilderInstruction32x(
instruction.opcode,
instruction.registerA,
instruction.registerB
)
}
internal fun newBuilderInstruction35c(instruction: Instruction35c): BuilderInstruction35c {
return BuilderInstruction35c(
instruction.opcode,
instruction.registerCount,
instruction.registerC,
instruction.registerD,
instruction.registerE,
instruction.registerF,
instruction.registerG,
instruction.reference
)
}
internal fun newBuilderInstruction35mi(instruction: Instruction35mi): BuilderInstruction35mi {
return BuilderInstruction35mi(
instruction.opcode,
instruction.registerCount,
instruction.registerC,
instruction.registerD,
instruction.registerE,
instruction.registerF,
instruction.registerG,
instruction.inlineIndex
)
}
internal fun newBuilderInstruction35ms(instruction: Instruction35ms): BuilderInstruction35ms {
return BuilderInstruction35ms(
instruction.opcode,
instruction.registerCount,
instruction.registerC,
instruction.registerD,
instruction.registerE,
instruction.registerF,
instruction.registerG,
instruction.vtableIndex
)
}
internal fun newBuilderInstruction3rc(instruction: Instruction3rc): BuilderInstruction3rc {
return BuilderInstruction3rc(
instruction.opcode,
instruction.startRegister,
instruction.registerCount,
instruction.reference
)
}
internal fun newBuilderInstruction3rmi(instruction: Instruction3rmi): BuilderInstruction3rmi {
return BuilderInstruction3rmi(
instruction.opcode,
instruction.startRegister,
instruction.registerCount,
instruction.inlineIndex
)
}
internal fun newBuilderInstruction3rms(instruction: Instruction3rms): BuilderInstruction3rms {
return BuilderInstruction3rms(
instruction.opcode,
instruction.startRegister,
instruction.registerCount,
instruction.vtableIndex
)
}
internal fun newBuilderInstruction51l(instruction: Instruction51l): BuilderInstruction51l {
return BuilderInstruction51l(
instruction.opcode,
instruction.registerA,
instruction.wideLiteral
)
}
}
}

View File

@@ -0,0 +1 @@
version=${projectVersion}

View File

@@ -0,0 +1,18 @@
package app.revanced.patcher.issues
import app.revanced.patcher.patch.PatchOption
import org.junit.jupiter.api.Test
import kotlin.test.assertNull
internal class Issue98 {
companion object {
var key1: String? by PatchOption.StringOption(
"key1", null, "title", "description"
)
}
@Test
fun `should infer nullable type correctly`() {
assertNull(key1)
}
}

View File

@@ -0,0 +1,113 @@
package app.revanced.patcher.patch
import app.revanced.patcher.usage.bytecode.ExampleBytecodePatch
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.io.path.Path
import kotlin.io.path.pathString
import kotlin.test.assertNotEquals
internal class PatchOptionsTest {
private val options = ExampleBytecodePatch.options
@Test
fun `should not throw an exception`() {
for (option in options) {
when (option) {
is PatchOption.StringOption -> {
option.value = "Hello World"
}
is PatchOption.BooleanOption -> {
option.value = false
}
is PatchOption.StringListOption -> {
option.value = option.options.first()
for (choice in option.options) {
println(choice)
}
}
is PatchOption.IntListOption -> {
option.value = option.options.first()
for (choice in option.options) {
println(choice)
}
}
is PatchOption.PathOption -> {
option.value = Path("test.txt").pathString
}
}
}
val option = options.get<String>("key1")
// or: val option: String? by options["key1"]
// then you won't need `.value` every time
println(option.value)
options["key1"] = "Hello, world!"
println(option.value)
}
@Test
fun `should return a different value when changed`() {
var value: String? by options["key1"]
val current = value + "" // force a copy
value = "Hello, world!"
assertNotEquals(current, value)
}
@Test
fun `should be able to set value to null`() {
// Sadly, doing:
// > options["key2"] = null
// is not possible because Kotlin
// cannot reify the type "Nothing?".
// So we have to do this instead:
options["key2"] = null as Any?
// This is a cleaner replacement for the above:
options.nullify("key2")
}
@Test
fun `should fail because the option does not exist`() {
assertThrows<NoSuchOptionException> {
options["this option does not exist"] = 123
}
}
@Test
fun `should fail because of invalid value type when setting an option`() {
assertThrows<InvalidTypeException> {
options["key1"] = 123
}
}
@Test
fun `should fail because of invalid value type when getting an option`() {
assertThrows<InvalidTypeException> {
options.get<Int>("key1")
}
}
@Test
fun `should fail because of an illegal value`() {
assertThrows<IllegalValueException> {
options["key3"] = "this value is not an allowed option"
}
}
@Test
fun `should fail because the requirement is not met`() {
assertThrows<RequirementNotMetException> {
options.nullify("key1")
}
}
@Test
fun `should fail because getting a non-initialized option is illegal`() {
assertThrows<RequirementNotMetException> {
println(options["key5"].value)
}
}
}

View File

@@ -1,46 +0,0 @@
package app.revanced.patcher.usage
import org.junit.jupiter.api.Test
internal class PatcherTest {
@Test
fun testPatcher() {
return // FIXME: create a proper resource to pass this test
/**
val patcher = Patcher(
File(PatcherTest::class.java.getResource("/example.apk")!!.toURI()),
"exampleCacheDirectory",
patchResources = true
)
patcher.addPatches(listOf(ExampleBytecodePatch(), ExampleResourcePatch()))
for (signature in patcher.resolveSignatures()) {
if (!signature.resolved) {
throw Exception("Signature ${signature.metadata.name} was not resolved!")
}
val patternScanMethod = signature.metadata.patternScanMethod
if (patternScanMethod is PatternScanMethod.Fuzzy) {
val warnings = patternScanMethod.warnings
if (warnings != null) {
println("Signature ${signature.metadata.name} had ${warnings.size} warnings!")
for (warning in warnings) {
println(warning.toString())
}
} else {
println("Signature ${signature.metadata.name} used the fuzzy resolver, but the warnings list is null!")
}
}
}
for ((metadata, result) in patcher.applyPatches()) {
if (result.isFailure) {
throw Exception("Patch ${metadata.shortName} failed", result.exceptionOrNull()!!)
} else {
println("Patch ${metadata.shortName} applied successfully!")
}
}
val out = patcher.save()
assertTrue(out.isNotEmpty(), "Expected the output of Patcher#save() to not be empty.")
*/
}
}

View File

@@ -1,4 +1,4 @@
package app.revanced.patcher.usage.bytecode.annotation
package app.revanced.patcher.usage.bytecode
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@@ -9,6 +9,5 @@ import app.revanced.patcher.annotation.Package
)]
)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
internal annotation class ExampleBytecodeCompatibility

View File

@@ -1,21 +1,19 @@
package app.revanced.patcher.usage.bytecode.patch
package app.revanced.patcher.usage.bytecode
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.implementation.BytecodeData
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.or
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.*
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.implementation.BytecodePatch
import app.revanced.patcher.patch.implementation.misc.PatchResult
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
import app.revanced.patcher.usage.bytecode.signatures.ExampleSignature
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
import app.revanced.patcher.usage.resource.patch.ExampleResourcePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.toInstruction
import app.revanced.patcher.util.smali.toInstructions
import com.google.common.collect.ImmutableList
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Format
@@ -31,43 +29,45 @@ import org.jf.dexlib2.immutable.reference.ImmutableFieldReference
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
import org.jf.dexlib2.util.Preconditions
import kotlin.io.path.Path
@Patch
@Name("example-bytecode-patch")
@Description("Example demonstration of a bytecode patch.")
@ExampleResourceCompatibility
@Version("0.0.1")
class ExampleBytecodePatch : BytecodePatch(
listOf(
ExampleSignature
)
) {
@DependsOn([ExampleResourcePatch::class])
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
// This function will be executed by the patcher.
// You can treat it as a constructor
override fun execute(data: BytecodeData): PatchResult {
// Get the resolved method for the signature from the resolver cache
val result = signatures.first().result!!
override fun execute(context: BytecodeContext): PatchResult {
// Get the resolved method by its fingerprint from the resolver cache
val result = ExampleFingerprint.result!!
// Patch options
println(key1)
key2 = false
// Get the implementation for the resolved method
val implementation = result.method.implementation!!
val method = result.mutableMethod
val implementation = method.implementation!!
// Let's modify it, so it prints "Hello, ReVanced! Editing bytecode."
// Get the start index of our opcode pattern.
// This will be the index of the instruction with the opcode CONST_STRING.
val startIndex = result.scanResult.startIndex
val startIndex = result.scanResult.patternScanResult!!.startIndex
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
// Get the class in which the method matching our signature is defined in.
val mainClass = data.findClass {
it.type == result.definingClassProxy.immutableClass.type
}!!.resolve()
// Get the class in which the method matching our fingerprint is defined in.
val mainClass = context.findClass {
it.type == result.classDef.type
}!!.mutableClass
// Add a new method returning a string
mainClass.methods.add(
ImmutableMethod(
result.definingClassProxy.immutableClass.type,
result.classDef.type,
"returnHello",
null,
"Ljava/lang/String;",
@@ -112,22 +112,21 @@ class ExampleBytecodePatch : BytecodePatch(
)
// store the fields initial value into the first virtual register
implementation.replaceInstruction(
0,
"sget-object v0, LTestClass;->dummyField:Ljava/io/PrintStream;".toInstruction()
)
method.replaceInstruction(0, "sget-object v0, LTestClass;->dummyField:Ljava/io/PrintStream;")
// Now let's create a new call to our method and print the return value!
// You can also use the smali compiler to create instructions.
// For this sake of example I reuse the TestClass field dummyField inside the virtual register 0.
//
// Control flow instructions are not supported as of now.
val instructions = """
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
move-result-object v1
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
""".trimIndent().toInstructions()
implementation.addInstructions(startIndex + 2, instructions)
method.addInstructions(
startIndex + 2,
"""
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
move-result-object v1
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
"""
)
// Finally, tell the patcher that this patch was a success.
// You can also return PatchResultError with a message.
@@ -166,4 +165,38 @@ class ExampleBytecodePatch : BytecodePatch(
)
)
}
@Suppress("unused")
companion object : OptionsContainer() {
private var key1 by option(
PatchOption.StringOption(
"key1", "default", "title", "description", true
)
)
private var key2 by option(
PatchOption.BooleanOption(
"key2", true, "title", "description" // required defaults to false
)
)
private var key3 by option(
PatchOption.StringListOption(
"key3", "TEST", listOf("TEST", "TEST1", "TEST2"), "title", "description"
)
)
private var key4 by option(
PatchOption.IntListOption(
"key4", 1, listOf(1, 2, 3), "title", "description"
)
)
private var key5 by option(
PatchOption.StringOption(
"key5", null, "title", "description", true
)
)
private var key6 by option(
PatchOption.PathOption(
"key6", Path("test.txt"), "title", "description", true
)
)
}
}

View File

@@ -0,0 +1,20 @@
package app.revanced.patcher.usage.bytecode
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
@FuzzyPatternScanMethod(2)
object ExampleFingerprint : MethodFingerprint(
"V",
AccessFlags.PUBLIC or AccessFlags.STATIC,
listOf("[L"),
listOf(
Opcode.SGET_OBJECT,
null, // Testing unknown opcodes.
Opcode.INVOKE_STATIC, // This is intentionally wrong to test the Fuzzy resolver.
Opcode.RETURN_VOID
),
null
)

View File

@@ -1,32 +0,0 @@
package app.revanced.patcher.usage.bytecode.signatures
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.extensions.or
import app.revanced.patcher.signature.implementation.method.MethodSignature
import app.revanced.patcher.signature.implementation.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.signature.implementation.method.annotation.MatchingMethod
import app.revanced.patcher.usage.bytecode.annotation.ExampleBytecodeCompatibility
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
@Name("example-signature")
@MatchingMethod(
"LexampleClass;",
"exampleMehod"
)
@FuzzyPatternScanMethod(2)
@ExampleBytecodeCompatibility
@Version("0.0.1")
object ExampleSignature : MethodSignature(
"V",
AccessFlags.PUBLIC or AccessFlags.STATIC,
listOf("[L"),
listOf(
Opcode.SGET_OBJECT,
null, // Testing unknown opcodes.
Opcode.INVOKE_STATIC, // This is intentionally wrong to test the Fuzzy resolver.
Opcode.RETURN_VOID
),
null
)

View File

@@ -9,6 +9,5 @@ import app.revanced.patcher.annotation.Package
)]
)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
internal annotation class ExampleResourceCompatibility

View File

@@ -3,11 +3,11 @@ package app.revanced.patcher.usage.resource.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.implementation.ResourceData
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.implementation.ResourcePatch
import app.revanced.patcher.patch.implementation.misc.PatchResult
import app.revanced.patcher.patch.implementation.misc.PatchResultSuccess
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
import org.w3c.dom.Element
@@ -16,10 +16,10 @@ import org.w3c.dom.Element
@Description("Example demonstration of a resource patch.")
@ExampleResourceCompatibility
@Version("0.0.1")
class ExampleResourcePatch : ResourcePatch() {
override fun execute(data: ResourceData): PatchResult {
data.getXmlEditor("AndroidManifest.xml").use { domFileEditor ->
val element = domFileEditor // regular DomFileEditor
class ExampleResourcePatch : ResourcePatch {
override fun execute(context: ResourceContext): PatchResult {
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val element = editor // regular DomFileEditor
.file
.getElementsByTagName("application")
.item(0) as Element
@@ -30,18 +30,6 @@ class ExampleResourcePatch : ResourcePatch() {
)
}
// iterate through all available resources
data.forEach {
if (it.extension.lowercase() != "xml") return@forEach
data.replace(
it.path,
"\\ddip", // regex supported
"0dip",
true
)
}
return PatchResultSuccess()
}
}

View File

@@ -0,0 +1,104 @@
package app.revanced.patcher.util.smali
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.instruction
import app.revanced.patcher.extensions.label
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.BuilderInstruction
import org.jf.dexlib2.builder.MutableMethodImplementation
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
import org.jf.dexlib2.builder.instruction.BuilderInstruction21t
import org.jf.dexlib2.immutable.ImmutableMethod
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
import java.util.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
internal class InlineSmaliCompilerTest {
@Test
fun `compiler should output valid instruction`() {
val want = BuilderInstruction21c(Opcode.CONST_STRING, 0, ImmutableStringReference("Test")) as BuilderInstruction
val have = "const-string v0, \"Test\"".toInstruction()
instructionEquals(want, have)
}
@Test
fun `compiler should support branching with own branches`() {
val method = createMethod()
val insnAmount = 8
val insnIndex = insnAmount - 2
val targetIndex = insnIndex - 1
method.addInstructions(arrayOfNulls<String>(insnAmount).also {
Arrays.fill(it, "const/4 v0, 0x0")
}.joinToString("\n"))
method.addInstructions(
targetIndex,
"""
:test
const/4 v0, 0x1
if-eqz v0, :test
"""
)
val insn = method.instruction<BuilderInstruction21t>(insnIndex)
assertEquals(targetIndex, insn.target.location.index)
}
@Test
fun `compiler should support branching to outside branches`() {
val method = createMethod()
val insnIndex = 3
val labelIndex = 1
method.addInstructions(
"""
const/4 v0, 0x1
const/4 v0, 0x0
"""
)
assertEquals(labelIndex, method.label(labelIndex).location.index)
method.addInstructions(
"""
const/4 v0, 0x1
if-eqz v0, :test
return-void
""", listOf(
ExternalLabel("test",method.instruction(1))
)
)
val insn = method.instruction<BuilderInstruction21t>(insnIndex)
assertTrue(insn.target.isPlaced, "Label was not placed")
assertEquals(labelIndex, insn.target.location.index)
}
companion object {
private fun createMethod(
name: String = "dummy",
returnType: String = "V",
accessFlags: Int = AccessFlags.STATIC.value,
registerCount: Int = 1,
) = ImmutableMethod(
"Ldummy;",
name,
emptyList(), // parameters
returnType,
accessFlags,
emptySet(),
emptySet(),
MutableMethodImplementation(registerCount)
).toMutable()
private fun instructionEquals(want: BuilderInstruction, have: BuilderInstruction) {
assertEquals(want.opcode, have.opcode)
assertEquals(want.format, have.format)
assertEquals(want.codeUnits, have.codeUnits)
}
}
}