Compare commits

...

108 Commits

Author SHA1 Message Date
semantic-release-bot
f05a404e48 chore(release): 19.0.0-dev.1 [skip ci]
# [19.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0...v19.0.0-dev.1) (2023-10-24)

### Features

* Add `PatchOption#valueType` to handle type erasure ([a46e948](a46e948b5a))

### BREAKING CHANGES

* This changes the signature of the `PatchOption` constructor.
2023-10-24 20:35:38 +00:00
oSumAtrIX
a46e948b5a feat: Add PatchOption#valueType to handle type erasure
Without this new property, it is not possible to infer the type without abusing `ClassCastException` at runtime to infer the type of the option value.

BREAKING CHANGE: This changes the signature of the `PatchOption` constructor.
2023-10-24 22:33:10 +02:00
semantic-release-bot
dc09ea639f chore(release): 18.0.0 [skip ci]
# [18.0.0](https://github.com/ReVanced/revanced-patcher/compare/v17.0.0...v18.0.0) (2023-10-22)

### Bug Fixes

* Do not set patch fields if they are empty ([a76ac04](a76ac04214))
* Only allow setting `MethodFingerprint#result` privately ([aed1eac](aed1eac315))

### Code Refactoring

* Change `PatchOption` from abstract to open class ([09cd6aa](09cd6aa568))
* Change data classes to actual classes ([6192089](6192089b71))
* Convert extension functions to member functions ([e2ca507](e2ca50729d))
* Move files to simplify package structure ([124a2e9](124a2e9d3e))
* Remove deprecated classes and members ([a4212f6](a4212f6bf9))

* refactor!: Remove `Fingerprint` interface ([54a2f8f](54a2f8f16f))

### Features

* Add function to reset options to their default value ([ebbaafb](ebbaafb78e))
* Add function to reset options to their default value ([e6de90d](e6de90d300))
* Add getter for default option value ([c7922e9](c7922e90d0))
* Make `PatchOption#values` nullable ([56ce9ec](56ce9ec2f9))
* Name patch option value validator property correctly ([caa634f](caa634fac6))
* Remove patch annotation processor ([4456031](4456031459))
* Use a map for `PatchOption#values` ([54ac139](54ac1394a9))

### Performance Improvements

* Run the garbage collector after writing dex files ([d9fb241](d9fb241d57))

### BREAKING CHANGES

* The `MethodFingerprint#result` member can now only be set inside `MethodFingerprint`.
* The `Fingerprint` interface is no longer present.
* Some extension functions are now member functions.
* This gets rid of data class members.
* Some deprecated classes and members are not present anymore.
* Classes and members have changed packages.
* This gets rid of the existing basic implementations of the `PatchOptions` type and moves extension functions.
* This changes the getter name of the property.
* Various patch constructor signatures have changed.
2023-10-22 23:59:31 +00:00
oSumAtrIX
49ed096e85 chore: Merge branch dev to main (#257) 2023-10-23 01:56:40 +02:00
semantic-release-bot
167bd83f4e chore(release): 18.0.0-dev.6 [skip ci]
# [18.0.0-dev.6](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.5...v18.0.0-dev.6) (2023-10-22)

### Bug Fixes

* Only allow setting `MethodFingerprint#result` privately ([aed1eac](aed1eac315))

### Code Refactoring

* Change data classes to actual classes ([6192089](6192089b71))
* Convert extension functions to member functions ([e2ca507](e2ca50729d))
* Move files to simplify package structure ([124a2e9](124a2e9d3e))
* Remove deprecated classes and members ([a4212f6](a4212f6bf9))

* refactor!: Remove `Fingerprint` interface ([54a2f8f](54a2f8f16f))

### BREAKING CHANGES

* The `MethodFingerprint#result` member can now only be set inside `MethodFingerprint`.
* The `Fingerprint` interface is no longer present.
* Some extension functions are now member functions.
* This gets rid of data class members.
* Some deprecated classes and members are not present anymore.
* Classes and members have changed packages.
2023-10-22 23:54:59 +00:00
oSumAtrIX
aed1eac315 fix: Only allow setting MethodFingerprint#result privately
BREAKING CHANGE: The `MethodFingerprint#result` member can now only be set inside `MethodFingerprint`.
2023-10-23 01:52:43 +02:00
oSumAtrIX
54a2f8f16f refactor!: Remove Fingerprint interface
It served no purpose so far.

BREAKING CHANGE: The `Fingerprint` interface is no longer present.
2023-10-23 01:52:04 +02:00
oSumAtrIX
2ca543ffb9 refactor: Move lookup related classes and members to own file 2023-10-23 01:51:48 +02:00
oSumAtrIX
58e7f815a5 refactor: Move MethodFingerprintResult to own file 2023-10-23 01:51:34 +02:00
oSumAtrIX
15b38fc841 refactor: Simplify method implementation 2023-10-23 01:50:54 +02:00
oSumAtrIX
e2ca50729d refactor: Convert extension functions to member functions
BREAKING CHANGE: Some extension functions are now member functions.
2023-10-23 01:50:53 +02:00
oSumAtrIX
6192089b71 refactor: Change data classes to actual classes
The data class members did not serve any actual purpose.

BREAKING CHANGE: This gets rid of data class members.
2023-10-23 01:49:57 +02:00
oSumAtrIX
a4212f6bf9 refactor: Remove deprecated classes and members
BREAKING CHANGE: Some deprecated classes and members are not present anymore.
2023-10-23 01:49:23 +02:00
oSumAtrIX
124a2e9d3e refactor: Move files to simplify package structure
BREAKING CHANGE: Classes and members have changed packages.
2023-10-23 01:48:52 +02:00
semantic-release-bot
f77624b3b9 chore(release): 18.0.0-dev.5 [skip ci]
# [18.0.0-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.4...v18.0.0-dev.5) (2023-10-22)

### Bug Fixes

* Do not set patch fields if they are empty ([a76ac04](a76ac04214))
2023-10-22 16:10:54 +00:00
oSumAtrIX
a76ac04214 fix: Do not set patch fields if they are empty 2023-10-22 18:08:28 +02:00
semantic-release-bot
0447fa9c28 chore(release): 18.0.0-dev.4 [skip ci]
# [18.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.3...v18.0.0-dev.4) (2023-10-22)

### Features

* Use a map for `PatchOption#values` ([54ac139](54ac1394a9))
2023-10-22 15:39:57 +00:00
oSumAtrIX
54ac1394a9 feat: Use a map for PatchOption#values
This allows to display a string representation associated to the option value.
2023-10-22 17:07:22 +02:00
oSumAtrIX
0b04c73ac5 build: Bump Kotlin Gradle plugin version 2023-10-22 16:14:46 +02:00
semantic-release-bot
079de45238 chore(release): 18.0.0-dev.3 [skip ci]
# [18.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.2...v18.0.0-dev.3) (2023-10-22)

### Features

* Make `PatchOption#values` nullable ([56ce9ec](56ce9ec2f9))
2023-10-22 14:09:28 +00:00
oSumAtrIX
56ce9ec2f9 feat: Make PatchOption#values nullable
There is no difference semantically, but this change allows passing null as a parameter which is simpler than having to use `emptySet()`.
2023-10-22 16:07:01 +02:00
semantic-release-bot
1b52e4b0f9 chore(release): 18.0.0-dev.2 [skip ci]
# [18.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.1...v18.0.0-dev.2) (2023-10-22)

### Code Refactoring

* Change `PatchOption` from abstract to open class ([09cd6aa](09cd6aa568))

### Features

* Add function to reset options to their default value ([ebbaafb](ebbaafb78e))
* Add function to reset options to their default value ([e6de90d](e6de90d300))
* Add getter for default option value ([c7922e9](c7922e90d0))
* Name patch option value validator property correctly ([caa634f](caa634fac6))

### BREAKING CHANGES

* This gets rid of the existing basic implementations of the `PatchOptions` type and moves extension functions.
* This changes the getter name of the property.
2023-10-22 00:56:40 +00:00
oSumAtrIX
098c2c1efa chore: Test default option value 2023-10-22 02:50:26 +02:00
oSumAtrIX
64343e5a7c chore: Test resetting options 2023-10-22 02:49:44 +02:00
oSumAtrIX
0caf6caeb9 refactor: Simplify patch option tests 2023-10-22 02:46:51 +02:00
oSumAtrIX
55f6c2a9fc feat!: Add property PatchOption#values 2023-10-22 02:38:12 +02:00
oSumAtrIX
c6095bc38a feat!: Add PatchOption#toString 2023-10-22 02:04:49 +02:00
oSumAtrIX
09cd6aa568 refactor: Change PatchOption from abstract to open class
BREAKING CHANGE: This gets rid of the existing basic implementations of the `PatchOptions` type and moves extension functions.
2023-10-22 01:55:05 +02:00
oSumAtrIX
ebbaafb78e feat: Add function to reset options to their default value 2023-10-22 01:14:48 +02:00
oSumAtrIX
e6de90d300 feat: Add function to reset options to their default value 2023-10-22 01:10:15 +02:00
oSumAtrIX
c7922e90d0 feat: Add getter for default option value 2023-10-22 01:02:43 +02:00
oSumAtrIX
c1f4c0445a chore: Fix inline docs wording 2023-10-21 23:52:47 +02:00
oSumAtrIX
caa634fac6 feat: Name patch option value validator property correctly
BREAKING CHANGE: This changes the getter name of the property.
2023-10-21 23:51:44 +02:00
oSumAtrIX
f28bfe0dbd chore: Add missing inline docs 2023-10-21 23:49:40 +02:00
semantic-release-bot
155e787ff4 chore(release): 18.0.0-dev.1 [skip ci]
# [18.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v17.0.1-dev.1...v18.0.0-dev.1) (2023-10-14)

### Features

* Remove patch annotation processor ([4456031](4456031459))

### BREAKING CHANGES

* Various patch constructor signatures have changed.
2023-10-14 18:43:40 +00:00
oSumAtrIX
c38f0ef42a build: Move subproject to root project 2023-10-14 19:30:10 +02:00
oSumAtrIX
4456031459 feat: Remove patch annotation processor
Unfortunately processing annotations required generating new classes which turned out to be quite cumbersome to work with, especially when trying to publish an API. Therefor, the patch annotation retention is now `RUNTIME`.

BREAKING CHANGE: Various patch constructor signatures have changed.
2023-10-14 19:29:23 +02:00
semantic-release-bot
5fb59a227f chore(release): 17.0.1-dev.1 [skip ci]
## [17.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v17.0.0...v17.0.1-dev.1) (2023-10-10)

### Performance Improvements

* Run the garbage collector after writing dex files ([d9fb241](d9fb241d57))
2023-10-10 18:23:51 +00:00
oSumAtrIX
d9fb241d57 perf: Run the garbage collector after writing dex files
Writing dex files consumes a lot of memory.
2023-10-10 20:06:27 +02:00
oSumAtrIX
642c4ea97e refactor: Use correct class structure 2023-10-10 20:05:46 +02:00
semantic-release-bot
8c8a251626 chore(release): 17.0.0 [skip ci]
# [17.0.0](https://github.com/ReVanced/revanced-patcher/compare/v16.0.2...v17.0.0) (2023-10-09)

### Features

* Add option to use single threaded writer for dex files ([77dbee3](77dbee3d6a))

### BREAKING CHANGES

* This commit gets rid of deprecated constructors.
2023-10-09 13:00:18 +00:00
oSumAtrIX
5953d6cfb5 chore: Merge branch dev to main (#252) 2023-10-09 14:56:10 +02:00
semantic-release-bot
a1962fe600 chore(release): 17.0.0-dev.1 [skip ci]
# [17.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v16.0.2...v17.0.0-dev.1) (2023-10-09)

### Features

* Add option to use single threaded writer for dex files ([77dbee3](77dbee3d6a))

### BREAKING CHANGES

* This commit gets rid of deprecated constructors.
2023-10-09 12:53:08 +00:00
oSumAtrIX
77dbee3d6a feat: Add option to use single threaded writer for dex files
BREAKING CHANGE: This commit gets rid of deprecated constructors.
2023-10-09 14:42:20 +02:00
oSumAtrIX
cb5e39d73e build: Publish artifacts on Jitpack 2023-10-09 14:42:20 +02:00
semantic-release-bot
38ef2f470a chore(release): 16.0.2 [skip ci]
## [16.0.2](https://github.com/ReVanced/revanced-patcher/compare/v16.0.1...v16.0.2) (2023-10-06)

### Performance Improvements

* Use a map to merge integrations classes ([6059d3c](6059d3ca26))
2023-10-06 23:59:10 +00:00
oSumAtrIX
129d84e108 chore: Merge branch dev to main (#250) 2023-10-07 01:55:48 +02:00
semantic-release-bot
affeba76b8 chore(release): 16.0.2-dev.1 [skip ci]
## [16.0.2-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v16.0.1...v16.0.2-dev.1) (2023-10-06)

### Performance Improvements

* Use a map to merge integrations classes ([6059d3c](6059d3ca26))
2023-10-06 23:55:11 +00:00
oSumAtrIX
6059d3ca26 perf: Use a map to merge integrations classes 2023-10-07 01:52:21 +02:00
semantic-release-bot
444dee5a16 chore(release): 16.0.1 [skip ci]
## [16.0.1](https://github.com/ReVanced/revanced-patcher/compare/v16.0.0...v16.0.1) (2023-10-05)

### Bug Fixes

* Merge integrations when required ([06c2b76](06c2b76f11))
2023-10-05 15:31:28 +00:00
oSumAtrIX
d314466ce2 chore: Merge branch dev to main (#249) 2023-10-05 17:28:45 +02:00
semantic-release-bot
fdaf9c21c8 chore(release): 16.0.1-dev.1 [skip ci]
## [16.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v16.0.0...v16.0.1-dev.1) (2023-10-05)

### Bug Fixes

* Merge integrations when required ([06c2b76](06c2b76f11))
2023-10-05 15:27:34 +00:00
oSumAtrIX
06c2b76f11 fix: Merge integrations when required
Previous to this commit integrations were merged when not required.
2023-10-05 17:24:28 +02:00
semantic-release-bot
3896b30738 chore(release): 16.0.0 [skip ci]
# [16.0.0](https://github.com/ReVanced/revanced-patcher/compare/v15.0.3...v16.0.0) (2023-10-04)

### Bug Fixes

* Use correct super class type ([f590436](f590436399))

### BREAKING CHANGES

* This changes the super classes of some `PatchOptionException` classes
2023-10-04 02:18:27 +00:00
oSumAtrIX
2c4b88e1a0 chore: Merge branch dev to main (#248) 2023-10-04 04:15:04 +02:00
semantic-release-bot
dfc7e1596b chore(release): 16.0.0-dev.1 [skip ci]
# [16.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.3...v16.0.0-dev.1) (2023-10-04)

### Bug Fixes

* Use correct super class type ([f590436](f590436399))

### BREAKING CHANGES

* This changes the super classes of some `PatchOptionException` classes
2023-10-04 02:06:03 +00:00
oSumAtrIX
f590436399 fix: Use correct super class type
BREAKING CHANGE: This changes the super classes of some `PatchOptionException` classes
2023-10-04 04:02:10 +02:00
semantic-release-bot
cbfb9ba02f chore(release): 15.0.3 [skip ci]
## [15.0.3](https://github.com/ReVanced/revanced-patcher/compare/v15.0.2...v15.0.3) (2023-10-01)

### Bug Fixes

* Fix SMALI compilation on devices with RTL language ([#242](https://github.com/ReVanced/revanced-patcher/issues/242)) ([356f1f1](356f1f1553))
2023-10-01 16:58:40 +00:00
oSumAtrIX
b4cfe80ad5 chore: Merge branch dev to main (#243) 2023-10-01 18:56:02 +02:00
semantic-release-bot
b37906fa35 chore(release): 15.0.3-dev.1 [skip ci]
## [15.0.3-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.2...v15.0.3-dev.1) (2023-09-29)

### Bug Fixes

* Fix SMALI compilation on devices with RTL language ([#242](https://github.com/ReVanced/revanced-patcher/issues/242)) ([356f1f1](356f1f1553))
2023-09-29 13:18:47 +00:00
LisoUseInAIKyrios
356f1f1553 fix: Fix SMALI compilation on devices with RTL language (#242) 2023-09-29 15:15:14 +02:00
semantic-release-bot
e882af74ee chore(release): 15.0.2 [skip ci]
## [15.0.2](https://github.com/ReVanced/revanced-patcher/compare/v15.0.1...v15.0.2) (2023-09-27)

### Performance Improvements

* Do not unnecessary resolve fingeprints twice ([#241](https://github.com/ReVanced/revanced-patcher/issues/241)) ([4d6e08a](4d6e08a650))
2023-09-27 21:27:32 +00:00
oSumAtrIX
46875fb28e chore: Merge branch dev to main (#238) 2023-09-27 23:23:46 +02:00
oSumAtrIX
417c3e4234 ci: Bump checkout action 2023-09-27 18:02:50 +02:00
semantic-release-bot
6d2c28807b chore(release): 15.0.2-dev.1 [skip ci]
## [15.0.2-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.1...v15.0.2-dev.1) (2023-09-26)

### Performance Improvements

* Do not unnecessary resolve fingeprints twice ([#241](https://github.com/ReVanced/revanced-patcher/issues/241)) ([4d6e08a](4d6e08a650))
2023-09-26 12:25:00 +00:00
LisoUseInAIKyrios
4d6e08a650 perf: Do not unnecessary resolve fingeprints twice (#241) 2023-09-26 14:21:43 +02:00
oSumAtrIX
5cebc1fd30 ci: Use better workflow name and PR message 2023-09-23 18:17:47 +02:00
semantic-release-bot
ac61731dc6 chore(release): 15.0.1 [skip ci]
## [15.0.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0...v15.0.1) (2023-09-20)

### Bug Fixes

* Remove log management ([d51bc32](d51bc32e37))
2023-09-20 03:19:14 +00:00
oSumAtrIX
9e4ffabd5c chore: merge branch dev to main (#235) 2023-09-20 05:16:25 +02:00
semantic-release-bot
3f410bd39f chore(release): 15.0.1-dev.1 [skip ci]
## [15.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0...v15.0.1-dev.1) (2023-09-20)

### Bug Fixes

* Remove log management ([d51bc32](d51bc32e37))
2023-09-20 03:16:04 +00:00
oSumAtrIX
d51bc32e37 fix: Remove log management
Changing the logging behaviour caused unintended behaviour outside of the library.
2023-09-19 05:09:19 +02:00
oSumAtrIX
b7f6aa94cc chore: Fix inline docs reference 2023-09-19 05:08:37 +02:00
semantic-release-bot
ff965e6953 chore(release): 15.0.0 [skip ci]
# [15.0.0](https://github.com/ReVanced/revanced-patcher/compare/v14.2.2...v15.0.0) (2023-09-18)

### Bug Fixes

* Account for source patch dependency for tests ([6918418](69184187d9))
* Always make the generated patch depend on the source patch ([8de3063](8de30633ae))
* Catch correct exception ([637d487](637d48746f))
* Delegate `PatchBundleLoader` by mutable set of patches ([9a109c1](9a109c129b))
* Do not resolve the proxied patch to the proxy in the dependency list ([e112837](e11283744a))
* Do not set `CompatiblePackage.versions` if `@CompatiblePackage.versions` is empty ([6b1e0a1](6b1e0a1656))
* Filter for patches correctly ([4bc4b0d](4bc4b0dc01))
* Find dependency in `context.allPatches` ([670f015](670f0153de))
* Log the correct patch names ([9fdb8f0](9fdb8f087f))
* Make `CompatiblePackage.versions` a property ([67b7dff](67b7dff67a))
* Print patch name instead of class name ([4e7811e](4e7811ea07))
* Print stack trace of exception ([aa71146](aa71146b1b))
* Run code-block if `executablePatches` does not yet contain `patch` ([1d7aeca](1d7aeca696))
* Suppress logger when loading patches in `PatchBundleLoader` ([72c9eb2](72c9eb2129))
* Use correct module name ([080fbe9](080fbe9feb))

### Code Refactoring

* Internalize processor constructor ([a802d0d](a802d0df46))

* feat Use `Set` as super type for `PatchBundleLoader` ([4b76d19](4b76d19596))
* feat!: Add patch annotation processor ([3fc6a13](3fc6a139ee))
* feat!: Remove patch annotations ([3b4db3d](3b4db3ddb7))

### Features

* Add patch annotation processor ([#231](https://github.com/ReVanced/revanced-patcher/issues/231)) ([a29931f](a29931f2ec))

### BREAKING CHANGES

* This gets rid of the public constructor.
* `PatchBundleLoader` is not a map anymore
* This renames packages and the Maven package.
* The manifest for patches has been removed, and the properties have been added to patches. Patches are now `OptionsContainer`. The `@Patch` annotation has been removed in favour of the `@Patch` annotation from the annotation processor.
* Patch annotations have been removed. PatcherException is now thrown in various places. PatchBundleLoader is now a map of patches associated by their name. Patches are now instances.
2023-09-18 15:44:01 +00:00
oSumAtrIX
468d5d7421 chore: merge branch dev to main (#232) 2023-09-18 17:40:24 +02:00
semantic-release-bot
fc95b28c49 chore(release): 15.0.0-dev.4 [skip ci]
# [15.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0-dev.3...v15.0.0-dev.4) (2023-09-13)

### Bug Fixes

* Account for source patch dependency for tests ([6918418](69184187d9))
* Always make the generated patch depend on the source patch ([8de3063](8de30633ae))
* Catch correct exception ([637d487](637d48746f))
* Delegate `PatchBundleLoader` by mutable set of patches ([9a109c1](9a109c129b))
* Do not resolve the proxied patch to the proxy in the dependency list ([e112837](e11283744a))
* Do not set `CompatiblePackage.versions` if `@CompatiblePackage.versions` is empty ([6b1e0a1](6b1e0a1656))
* Filter for patches correctly ([4bc4b0d](4bc4b0dc01))
* Find dependency in `context.allPatches` ([670f015](670f0153de))
* Log the correct patch names ([9fdb8f0](9fdb8f087f))
* Print patch name instead of class name ([4e7811e](4e7811ea07))
* Print stack trace of exception ([aa71146](aa71146b1b))
* Run code-block if `executablePatches` does not yet contain `patch` ([1d7aeca](1d7aeca696))
* Suppress logger when loading patches in `PatchBundleLoader` ([72c9eb2](72c9eb2129))

### Code Refactoring

* Internalize processor constructor ([a802d0d](a802d0df46))

### BREAKING CHANGES

* This gets rid of the public constructor.
2023-09-13 03:44:27 +00:00
oSumAtrIX
69184187d9 fix: Account for source patch dependency for tests 2023-09-13 05:40:59 +02:00
oSumAtrIX
a802d0df46 refactor: Internalize processor constructor
BREAKING CHANGE: This gets rid of the public constructor.
2023-09-13 05:32:40 +02:00
oSumAtrIX
8de30633ae fix: Always make the generated patch depend on the source patch 2023-09-13 05:18:28 +02:00
oSumAtrIX
a1fbb7990f refactor: Simplify fingerprint resolution 2023-09-13 04:13:38 +02:00
oSumAtrIX
aa71146b1b fix: Print stack trace of exception
Otherwise the log will not include what originally raised the exception.
2023-09-13 03:58:46 +02:00
oSumAtrIX
9fdb8f087f fix: Log the correct patch names 2023-09-13 03:35:39 +02:00
oSumAtrIX
670f0153de fix: Find dependency in context.allPatches 2023-09-13 03:06:40 +02:00
oSumAtrIX
1d7aeca696 fix: Run code-block if executablePatches does not yet contain patch 2023-09-13 03:06:19 +02:00
oSumAtrIX
4e7811ea07 fix: Print patch name instead of class name 2023-09-13 03:05:37 +02:00
oSumAtrIX
e11283744a fix: Do not resolve the proxied patch to the proxy in the dependency list
If a patch is used as a dependency, it would be present in `dependencyResolutionMap`. If that patch would also be annotated, then the generated patch would depend on itself.
2023-09-13 03:05:06 +02:00
oSumAtrIX
91cdfd53ef refactor: Simplify accessing the patches from PatcherContext.executablePatches 2023-09-13 02:22:31 +02:00
oSumAtrIX
bc7d6b9941 refactor: Rename unused destructed parameter to _ 2023-09-13 02:17:54 +02:00
oSumAtrIX
6b1e0a1656 fix: Do not set CompatiblePackage.versions if @CompatiblePackage.versions is empty 2023-09-13 02:17:24 +02:00
oSumAtrIX
72c9eb2129 fix: Suppress logger when loading patches in PatchBundleLoader 2023-09-13 02:11:51 +02:00
oSumAtrIX
4bc4b0dc01 fix: Filter for patches correctly 2023-09-13 01:52:15 +02:00
oSumAtrIX
637d48746f fix: Catch correct exception 2023-09-13 01:51:46 +02:00
oSumAtrIX
9a109c129b fix: Delegate PatchBundleLoader by mutable set of patches 2023-09-13 01:36:53 +02:00
oSumAtrIX
d49e4ee5ea refactor: Remove unused logger 2023-09-13 00:08:52 +02:00
semantic-release-bot
30f0ea29a3 chore(release): 15.0.0-dev.3 [skip ci]
# [15.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0-dev.2...v15.0.0-dev.3) (2023-09-06)

### Bug Fixes

* Make `CompatiblePackage.versions` a property ([67b7dff](67b7dff67a))
* Use correct module name ([080fbe9](080fbe9feb))

* feat Use `Set` as super type for `PatchBundleLoader` ([4b76d19](4b76d19596))

### BREAKING CHANGES

* `PatchBundleLoader` is not a map anymore
* This renames packages and the Maven package.
2023-09-06 12:32:05 +00:00
oSumAtrIX
49930f6565 chore: Use correct resource names for tests 2023-09-06 14:27:51 +02:00
oSumAtrIX
909d89fa8d chore: Print correct error message 2023-09-06 14:23:26 +02:00
oSumAtrIX
81d1d7f544 refactor: Do not reify generic type 2023-09-06 06:34:38 +02:00
oSumAtrIX
67b7dff67a fix: Make CompatiblePackage.versions a property 2023-09-06 06:12:44 +02:00
oSumAtrIX
4b76d19596 feat Use Set as super type for PatchBundleLoader
BREAKING CHANGE: `PatchBundleLoader` is not a map anymore
2023-09-06 06:12:22 +02:00
oSumAtrIX
080fbe9feb fix: Use correct module name
BREAKING CHANGE: This renames packages and the Maven package.
2023-09-06 06:11:15 +02:00
semantic-release-bot
d3721229bf chore(release): 15.0.0-dev.2 [skip ci]
# [15.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0-dev.1...v15.0.0-dev.2) (2023-09-06)
2023-09-06 03:36:03 +00:00
oSumAtrIX
86c1c9c772 build(Needs bump): Use correct Maven repository URL 2023-09-06 05:32:58 +02:00
oSumAtrIX
c299817193 feat!: Use Kotlin DSL for patch options (#234) 2023-09-06 02:53:31 +02:00
semantic-release-bot
fcc1de45ed chore(release): 15.0.0-dev.1 [skip ci]
# [15.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v14.2.2...v15.0.0-dev.1) (2023-09-04)

* feat!: Add patch annotation processor ([3fc6a13](3fc6a139ee))
* feat!: Remove patch annotations ([3b4db3d](3b4db3ddb7))

### Features

* Add patch annotation processor ([#231](https://github.com/ReVanced/revanced-patcher/issues/231)) ([a29931f](a29931f2ec))

### BREAKING CHANGES

* The manifest for patches has been removed, and the properties have been added to patches. Patches are now `OptionsContainer`. The `@Patch` annotation has been removed in favour of the `@Patch` annotation from the annotation processor.
* Patch annotations have been removed. PatcherException is now thrown in various places. PatchBundleLoader is now a map of patches associated by their name. Patches are now instances.
2023-09-04 03:51:04 +00:00
oSumAtrIX
a29931f2ec feat: Add patch annotation processor (#231) 2023-09-04 05:48:20 +02:00
oSumAtrIX
3fc6a139ee feat!: Add patch annotation processor
This commit introduces an annotation processor for patches. Patches can use the `@Patch` instead of super constructor parameters.

BREAKING CHANGE: The manifest for patches has been removed, and the properties have been added to patches. Patches are now `OptionsContainer`. The `@Patch` annotation has been removed in favour of the `@Patch` annotation from the annotation processor.
2023-09-04 05:43:41 +02:00
oSumAtrIX
4dd04975d9 refactor: Move ReVanced Patcher to sub-project
This allows other sub-projects to exist.
2023-09-04 05:37:13 +02:00
oSumAtrIX
3b4db3ddb7 feat!: Remove patch annotations
Annotations required reflection and working with them turned out to be rather cumbersome. The annotations have been replaced with properties for the most part.

BREAKING CHANGE: Patch annotations have been removed. PatcherException is now thrown in various places. PatchBundleLoader is now a map of patches associated by their name. Patches are now instances.
2023-09-04 05:24:29 +02:00
49 changed files with 2300 additions and 1795 deletions

View File

@@ -1,4 +1,4 @@
name: PR to main
name: Open a PR to main
on:
push:
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
env:
MESSAGE: merge branch `${{ github.head_ref || github.ref_name }}` to `main`
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
jobs:
pull-request:
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Open pull request
uses: repo-sync/pull-request@v2
with:

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages

View File

@@ -1,3 +1,381 @@
# [19.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0...v19.0.0-dev.1) (2023-10-24)
### Features
* Add `PatchOption#valueType` to handle type erasure ([a46e948](https://github.com/ReVanced/revanced-patcher/commit/a46e948b5a0cf9bc8d31f557e371cd7d7c2f5b1c))
### BREAKING CHANGES
* This changes the signature of the `PatchOption` constructor.
# [18.0.0](https://github.com/ReVanced/revanced-patcher/compare/v17.0.0...v18.0.0) (2023-10-22)
### Bug Fixes
* Do not set patch fields if they are empty ([a76ac04](https://github.com/ReVanced/revanced-patcher/commit/a76ac04214a2ab91e3b2f9dddb13ed52816fe723))
* Only allow setting `MethodFingerprint#result` privately ([aed1eac](https://github.com/ReVanced/revanced-patcher/commit/aed1eac3157317acf87f522750cf2f41509606c3))
### Code Refactoring
* Change `PatchOption` from abstract to open class ([09cd6aa](https://github.com/ReVanced/revanced-patcher/commit/09cd6aa568988dd5241bfa6a2e12b7926a7b0683))
* Change data classes to actual classes ([6192089](https://github.com/ReVanced/revanced-patcher/commit/6192089b71bdca15765369f3e607ddd1f8266205))
* Convert extension functions to member functions ([e2ca507](https://github.com/ReVanced/revanced-patcher/commit/e2ca50729da7085799c0ff6fc4f7afaf82579738))
* Move files to simplify package structure ([124a2e9](https://github.com/ReVanced/revanced-patcher/commit/124a2e9d3efb88f0f038ae306d941e918ad3ad3c))
* Remove deprecated classes and members ([a4212f6](https://github.com/ReVanced/revanced-patcher/commit/a4212f6bf952971541c4550e20f6bf57a382e19a))
* refactor!: Remove `Fingerprint` interface ([54a2f8f](https://github.com/ReVanced/revanced-patcher/commit/54a2f8f16fddf2b2ed47eb23717ba3734c4a6c5d))
### Features
* Add function to reset options to their default value ([ebbaafb](https://github.com/ReVanced/revanced-patcher/commit/ebbaafb78e88f34faeafe9ff8532afe29231bd79))
* Add function to reset options to their default value ([e6de90d](https://github.com/ReVanced/revanced-patcher/commit/e6de90d300bc9c82ca1696cb898db04c65a1cd5b))
* Add getter for default option value ([c7922e9](https://github.com/ReVanced/revanced-patcher/commit/c7922e90d0c6ae83f513611c706ebea33c1a2b63))
* Make `PatchOption#values` nullable ([56ce9ec](https://github.com/ReVanced/revanced-patcher/commit/56ce9ec2f98ff351c3d42df71b49e5c88f07e665))
* Name patch option value validator property correctly ([caa634f](https://github.com/ReVanced/revanced-patcher/commit/caa634fac6d7a717f54e3b015827c8858fd637b9))
* Remove patch annotation processor ([4456031](https://github.com/ReVanced/revanced-patcher/commit/445603145979a6f67823a79f9d6cd140299cff37))
* Use a map for `PatchOption#values` ([54ac139](https://github.com/ReVanced/revanced-patcher/commit/54ac1394a914d3eed7865ec697e8016834134911))
### Performance Improvements
* Run the garbage collector after writing dex files ([d9fb241](https://github.com/ReVanced/revanced-patcher/commit/d9fb241d57b0c4340130c0e5900250e66730ea56))
### BREAKING CHANGES
* The `MethodFingerprint#result` member can now only be set inside `MethodFingerprint`.
* The `Fingerprint` interface is no longer present.
* Some extension functions are now member functions.
* This gets rid of data class members.
* Some deprecated classes and members are not present anymore.
* Classes and members have changed packages.
* This gets rid of the existing basic implementations of the `PatchOptions` type and moves extension functions.
* This changes the getter name of the property.
* Various patch constructor signatures have changed.
# [18.0.0-dev.6](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.5...v18.0.0-dev.6) (2023-10-22)
### Bug Fixes
* Only allow setting `MethodFingerprint#result` privately ([aed1eac](https://github.com/ReVanced/revanced-patcher/commit/aed1eac3157317acf87f522750cf2f41509606c3))
### Code Refactoring
* Change data classes to actual classes ([6192089](https://github.com/ReVanced/revanced-patcher/commit/6192089b71bdca15765369f3e607ddd1f8266205))
* Convert extension functions to member functions ([e2ca507](https://github.com/ReVanced/revanced-patcher/commit/e2ca50729da7085799c0ff6fc4f7afaf82579738))
* Move files to simplify package structure ([124a2e9](https://github.com/ReVanced/revanced-patcher/commit/124a2e9d3efb88f0f038ae306d941e918ad3ad3c))
* Remove deprecated classes and members ([a4212f6](https://github.com/ReVanced/revanced-patcher/commit/a4212f6bf952971541c4550e20f6bf57a382e19a))
* refactor!: Remove `Fingerprint` interface ([54a2f8f](https://github.com/ReVanced/revanced-patcher/commit/54a2f8f16fddf2b2ed47eb23717ba3734c4a6c5d))
### BREAKING CHANGES
* The `MethodFingerprint#result` member can now only be set inside `MethodFingerprint`.
* The `Fingerprint` interface is no longer present.
* Some extension functions are now member functions.
* This gets rid of data class members.
* Some deprecated classes and members are not present anymore.
* Classes and members have changed packages.
# [18.0.0-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.4...v18.0.0-dev.5) (2023-10-22)
### Bug Fixes
* Do not set patch fields if they are empty ([a76ac04](https://github.com/ReVanced/revanced-patcher/commit/a76ac04214a2ab91e3b2f9dddb13ed52816fe723))
# [18.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.3...v18.0.0-dev.4) (2023-10-22)
### Features
* Use a map for `PatchOption#values` ([54ac139](https://github.com/ReVanced/revanced-patcher/commit/54ac1394a914d3eed7865ec697e8016834134911))
# [18.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.2...v18.0.0-dev.3) (2023-10-22)
### Features
* Make `PatchOption#values` nullable ([56ce9ec](https://github.com/ReVanced/revanced-patcher/commit/56ce9ec2f98ff351c3d42df71b49e5c88f07e665))
# [18.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v18.0.0-dev.1...v18.0.0-dev.2) (2023-10-22)
### Code Refactoring
* Change `PatchOption` from abstract to open class ([09cd6aa](https://github.com/ReVanced/revanced-patcher/commit/09cd6aa568988dd5241bfa6a2e12b7926a7b0683))
### Features
* Add function to reset options to their default value ([ebbaafb](https://github.com/ReVanced/revanced-patcher/commit/ebbaafb78e88f34faeafe9ff8532afe29231bd79))
* Add function to reset options to their default value ([e6de90d](https://github.com/ReVanced/revanced-patcher/commit/e6de90d300bc9c82ca1696cb898db04c65a1cd5b))
* Add getter for default option value ([c7922e9](https://github.com/ReVanced/revanced-patcher/commit/c7922e90d0c6ae83f513611c706ebea33c1a2b63))
* Name patch option value validator property correctly ([caa634f](https://github.com/ReVanced/revanced-patcher/commit/caa634fac6d7a717f54e3b015827c8858fd637b9))
### BREAKING CHANGES
* This gets rid of the existing basic implementations of the `PatchOptions` type and moves extension functions.
* This changes the getter name of the property.
# [18.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v17.0.1-dev.1...v18.0.0-dev.1) (2023-10-14)
### Features
* Remove patch annotation processor ([4456031](https://github.com/ReVanced/revanced-patcher/commit/445603145979a6f67823a79f9d6cd140299cff37))
### BREAKING CHANGES
* Various patch constructor signatures have changed.
## [17.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v17.0.0...v17.0.1-dev.1) (2023-10-10)
### Performance Improvements
* Run the garbage collector after writing dex files ([d9fb241](https://github.com/ReVanced/revanced-patcher/commit/d9fb241d57b0c4340130c0e5900250e66730ea56))
# [17.0.0](https://github.com/ReVanced/revanced-patcher/compare/v16.0.2...v17.0.0) (2023-10-09)
### Features
* Add option to use single threaded writer for dex files ([77dbee3](https://github.com/ReVanced/revanced-patcher/commit/77dbee3d6ae7b8dc77543e036624daa68ae63504))
### BREAKING CHANGES
* This commit gets rid of deprecated constructors.
# [17.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v16.0.2...v17.0.0-dev.1) (2023-10-09)
### Features
* Add option to use single threaded writer for dex files ([77dbee3](https://github.com/ReVanced/revanced-patcher/commit/77dbee3d6ae7b8dc77543e036624daa68ae63504))
### BREAKING CHANGES
* This commit gets rid of deprecated constructors.
## [16.0.2](https://github.com/ReVanced/revanced-patcher/compare/v16.0.1...v16.0.2) (2023-10-06)
### Performance Improvements
* Use a map to merge integrations classes ([6059d3c](https://github.com/ReVanced/revanced-patcher/commit/6059d3ca2685cb659023b171b95d4b9d279c6e53))
## [16.0.2-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v16.0.1...v16.0.2-dev.1) (2023-10-06)
### Performance Improvements
* Use a map to merge integrations classes ([6059d3c](https://github.com/ReVanced/revanced-patcher/commit/6059d3ca2685cb659023b171b95d4b9d279c6e53))
## [16.0.1](https://github.com/ReVanced/revanced-patcher/compare/v16.0.0...v16.0.1) (2023-10-05)
### Bug Fixes
* Merge integrations when required ([06c2b76](https://github.com/ReVanced/revanced-patcher/commit/06c2b76f11ac1bfe43d51d54d425e7577ecefdf6))
## [16.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v16.0.0...v16.0.1-dev.1) (2023-10-05)
### Bug Fixes
* Merge integrations when required ([06c2b76](https://github.com/ReVanced/revanced-patcher/commit/06c2b76f11ac1bfe43d51d54d425e7577ecefdf6))
# [16.0.0](https://github.com/ReVanced/revanced-patcher/compare/v15.0.3...v16.0.0) (2023-10-04)
### Bug Fixes
* Use correct super class type ([f590436](https://github.com/ReVanced/revanced-patcher/commit/f590436399f6385c51cea54618251b5d823c31f9))
### BREAKING CHANGES
* This changes the super classes of some `PatchOptionException` classes
# [16.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.3...v16.0.0-dev.1) (2023-10-04)
### Bug Fixes
* Use correct super class type ([f590436](https://github.com/ReVanced/revanced-patcher/commit/f590436399f6385c51cea54618251b5d823c31f9))
### BREAKING CHANGES
* This changes the super classes of some `PatchOptionException` classes
## [15.0.3](https://github.com/ReVanced/revanced-patcher/compare/v15.0.2...v15.0.3) (2023-10-01)
### Bug Fixes
* Fix SMALI compilation on devices with RTL language ([#242](https://github.com/ReVanced/revanced-patcher/issues/242)) ([356f1f1](https://github.com/ReVanced/revanced-patcher/commit/356f1f155348347a8f318a2e024716ebf4fec99b))
## [15.0.3-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.2...v15.0.3-dev.1) (2023-09-29)
### Bug Fixes
* Fix SMALI compilation on devices with RTL language ([#242](https://github.com/ReVanced/revanced-patcher/issues/242)) ([356f1f1](https://github.com/ReVanced/revanced-patcher/commit/356f1f155348347a8f318a2e024716ebf4fec99b))
## [15.0.2](https://github.com/ReVanced/revanced-patcher/compare/v15.0.1...v15.0.2) (2023-09-27)
### Performance Improvements
* Do not unnecessary resolve fingeprints twice ([#241](https://github.com/ReVanced/revanced-patcher/issues/241)) ([4d6e08a](https://github.com/ReVanced/revanced-patcher/commit/4d6e08a650dde6ec2e18611c5db1ab92b9a61dd1))
## [15.0.2-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.1...v15.0.2-dev.1) (2023-09-26)
### Performance Improvements
* Do not unnecessary resolve fingeprints twice ([#241](https://github.com/ReVanced/revanced-patcher/issues/241)) ([4d6e08a](https://github.com/ReVanced/revanced-patcher/commit/4d6e08a650dde6ec2e18611c5db1ab92b9a61dd1))
## [15.0.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0...v15.0.1) (2023-09-20)
### Bug Fixes
* Remove log management ([d51bc32](https://github.com/ReVanced/revanced-patcher/commit/d51bc32e37a865194c3825471085b3ccf8c78421))
## [15.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0...v15.0.1-dev.1) (2023-09-20)
### Bug Fixes
* Remove log management ([d51bc32](https://github.com/ReVanced/revanced-patcher/commit/d51bc32e37a865194c3825471085b3ccf8c78421))
# [15.0.0](https://github.com/ReVanced/revanced-patcher/compare/v14.2.2...v15.0.0) (2023-09-18)
### Bug Fixes
* Account for source patch dependency for tests ([6918418](https://github.com/ReVanced/revanced-patcher/commit/69184187d90f126478d2f49415c1e3381217557f))
* Always make the generated patch depend on the source patch ([8de3063](https://github.com/ReVanced/revanced-patcher/commit/8de30633ae6eb7acf7f0a351e26d4a6c2fdbdfec))
* Catch correct exception ([637d487](https://github.com/ReVanced/revanced-patcher/commit/637d48746ff8694e01c5aead1c75a9a1efeb5ac8))
* Delegate `PatchBundleLoader` by mutable set of patches ([9a109c1](https://github.com/ReVanced/revanced-patcher/commit/9a109c129b135a634be1aad4130a06d9e8e96ecd))
* Do not resolve the proxied patch to the proxy in the dependency list ([e112837](https://github.com/ReVanced/revanced-patcher/commit/e11283744a21fe2d09435e99d6924462b6aac3b8))
* Do not set `CompatiblePackage.versions` if `@CompatiblePackage.versions` is empty ([6b1e0a1](https://github.com/ReVanced/revanced-patcher/commit/6b1e0a16568124e9f82fb5740353360fa8ec614a))
* Filter for patches correctly ([4bc4b0d](https://github.com/ReVanced/revanced-patcher/commit/4bc4b0dc0104073b62528d02a88383cecd7a50e7))
* Find dependency in `context.allPatches` ([670f015](https://github.com/ReVanced/revanced-patcher/commit/670f0153de10c6f0db25b08df1c01a2905037f84))
* Log the correct patch names ([9fdb8f0](https://github.com/ReVanced/revanced-patcher/commit/9fdb8f087f62babf6081879db65c80db639aa0a7))
* Make `CompatiblePackage.versions` a property ([67b7dff](https://github.com/ReVanced/revanced-patcher/commit/67b7dff67a212b4fc30eb4f0cbe58f0ba09fb09a))
* Print patch name instead of class name ([4e7811e](https://github.com/ReVanced/revanced-patcher/commit/4e7811ea07762667a1f22526dc176022038f60eb))
* Print stack trace of exception ([aa71146](https://github.com/ReVanced/revanced-patcher/commit/aa71146b1bf4ffebcc81a1663e15abae89e97ff0))
* Run code-block if `executablePatches` does not yet contain `patch` ([1d7aeca](https://github.com/ReVanced/revanced-patcher/commit/1d7aeca696be873dfaf88eaa6d312949a3b8572b))
* Suppress logger when loading patches in `PatchBundleLoader` ([72c9eb2](https://github.com/ReVanced/revanced-patcher/commit/72c9eb212985f99f3390cf1faa10ab547d2dbe7e))
* Use correct module name ([080fbe9](https://github.com/ReVanced/revanced-patcher/commit/080fbe9feb9d4ea9ec4e599ecef296eacd803b05))
### Code Refactoring
* Internalize processor constructor ([a802d0d](https://github.com/ReVanced/revanced-patcher/commit/a802d0df463695976e85d8391762942eb977920b))
* feat Use `Set` as super type for `PatchBundleLoader` ([4b76d19](https://github.com/ReVanced/revanced-patcher/commit/4b76d1959691babf8c99d3d5235df4a4388956f0))
* feat!: Add patch annotation processor ([3fc6a13](https://github.com/ReVanced/revanced-patcher/commit/3fc6a139eef67237c116fb4e3e29bf9542d3a981))
* feat!: Remove patch annotations ([3b4db3d](https://github.com/ReVanced/revanced-patcher/commit/3b4db3ddb72cdcee8af2f787eadf58eeb37543de))
### Features
* Add patch annotation processor ([#231](https://github.com/ReVanced/revanced-patcher/issues/231)) ([a29931f](https://github.com/ReVanced/revanced-patcher/commit/a29931f2ec0a666dba209b54855425d9dc2f4462))
### BREAKING CHANGES
* This gets rid of the public constructor.
* `PatchBundleLoader` is not a map anymore
* This renames packages and the Maven package.
* The manifest for patches has been removed, and the properties have been added to patches. Patches are now `OptionsContainer`. The `@Patch` annotation has been removed in favour of the `@Patch` annotation from the annotation processor.
* Patch annotations have been removed. PatcherException is now thrown in various places. PatchBundleLoader is now a map of patches associated by their name. Patches are now instances.
# [15.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0-dev.3...v15.0.0-dev.4) (2023-09-13)
### Bug Fixes
* Account for source patch dependency for tests ([6918418](https://github.com/ReVanced/revanced-patcher/commit/69184187d90f126478d2f49415c1e3381217557f))
* Always make the generated patch depend on the source patch ([8de3063](https://github.com/ReVanced/revanced-patcher/commit/8de30633ae6eb7acf7f0a351e26d4a6c2fdbdfec))
* Catch correct exception ([637d487](https://github.com/ReVanced/revanced-patcher/commit/637d48746ff8694e01c5aead1c75a9a1efeb5ac8))
* Delegate `PatchBundleLoader` by mutable set of patches ([9a109c1](https://github.com/ReVanced/revanced-patcher/commit/9a109c129b135a634be1aad4130a06d9e8e96ecd))
* Do not resolve the proxied patch to the proxy in the dependency list ([e112837](https://github.com/ReVanced/revanced-patcher/commit/e11283744a21fe2d09435e99d6924462b6aac3b8))
* Do not set `CompatiblePackage.versions` if `@CompatiblePackage.versions` is empty ([6b1e0a1](https://github.com/ReVanced/revanced-patcher/commit/6b1e0a16568124e9f82fb5740353360fa8ec614a))
* Filter for patches correctly ([4bc4b0d](https://github.com/ReVanced/revanced-patcher/commit/4bc4b0dc0104073b62528d02a88383cecd7a50e7))
* Find dependency in `context.allPatches` ([670f015](https://github.com/ReVanced/revanced-patcher/commit/670f0153de10c6f0db25b08df1c01a2905037f84))
* Log the correct patch names ([9fdb8f0](https://github.com/ReVanced/revanced-patcher/commit/9fdb8f087f62babf6081879db65c80db639aa0a7))
* Print patch name instead of class name ([4e7811e](https://github.com/ReVanced/revanced-patcher/commit/4e7811ea07762667a1f22526dc176022038f60eb))
* Print stack trace of exception ([aa71146](https://github.com/ReVanced/revanced-patcher/commit/aa71146b1bf4ffebcc81a1663e15abae89e97ff0))
* Run code-block if `executablePatches` does not yet contain `patch` ([1d7aeca](https://github.com/ReVanced/revanced-patcher/commit/1d7aeca696be873dfaf88eaa6d312949a3b8572b))
* Suppress logger when loading patches in `PatchBundleLoader` ([72c9eb2](https://github.com/ReVanced/revanced-patcher/commit/72c9eb212985f99f3390cf1faa10ab547d2dbe7e))
### Code Refactoring
* Internalize processor constructor ([a802d0d](https://github.com/ReVanced/revanced-patcher/commit/a802d0df463695976e85d8391762942eb977920b))
### BREAKING CHANGES
* This gets rid of the public constructor.
# [15.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0-dev.2...v15.0.0-dev.3) (2023-09-06)
### Bug Fixes
* Make `CompatiblePackage.versions` a property ([67b7dff](https://github.com/ReVanced/revanced-patcher/commit/67b7dff67a212b4fc30eb4f0cbe58f0ba09fb09a))
* Use correct module name ([080fbe9](https://github.com/ReVanced/revanced-patcher/commit/080fbe9feb9d4ea9ec4e599ecef296eacd803b05))
* feat Use `Set` as super type for `PatchBundleLoader` ([4b76d19](https://github.com/ReVanced/revanced-patcher/commit/4b76d1959691babf8c99d3d5235df4a4388956f0))
### BREAKING CHANGES
* `PatchBundleLoader` is not a map anymore
* This renames packages and the Maven package.
# [15.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v15.0.0-dev.1...v15.0.0-dev.2) (2023-09-06)
# [15.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v14.2.2...v15.0.0-dev.1) (2023-09-04)
* feat!: Add patch annotation processor ([3fc6a13](https://github.com/ReVanced/revanced-patcher/commit/3fc6a139eef67237c116fb4e3e29bf9542d3a981))
* feat!: Remove patch annotations ([3b4db3d](https://github.com/ReVanced/revanced-patcher/commit/3b4db3ddb72cdcee8af2f787eadf58eeb37543de))
### Features
* Add patch annotation processor ([#231](https://github.com/ReVanced/revanced-patcher/issues/231)) ([a29931f](https://github.com/ReVanced/revanced-patcher/commit/a29931f2ec0a666dba209b54855425d9dc2f4462))
### BREAKING CHANGES
* The manifest for patches has been removed, and the properties have been added to patches. Patches are now `OptionsContainer`. The `@Patch` annotation has been removed in favour of the `@Patch` annotation from the annotation processor.
* Patch annotations have been removed. PatcherException is now thrown in various places. PatchBundleLoader is now a map of patches associated by their name. Patches are now instances.
## [14.2.2](https://github.com/ReVanced/revanced-patcher/compare/v14.2.1...v14.2.2) (2023-08-30)

View File

@@ -7,40 +7,22 @@ public final class app/revanced/patcher/PackageMetadata {
public final fun getPackageVersion ()Ljava/lang/String;
}
public abstract class app/revanced/patcher/PatchBundleLoader : java/util/List, kotlin/jvm/internal/markers/KMutableList {
public synthetic fun <init> (Ljava/lang/Iterable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun add (ILjava/lang/Class;)V
public synthetic fun add (ILjava/lang/Object;)V
public fun add (Ljava/lang/Class;)Z
public abstract class app/revanced/patcher/PatchBundleLoader : java/util/Set, kotlin/jvm/internal/markers/KMappedMarker {
public synthetic fun <init> (Ljava/lang/ClassLoader;[Ljava/io/File;Lkotlin/jvm/functions/Function1;Ljava/util/Set;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun add (Lapp/revanced/patcher/patch/Patch;)Z
public synthetic fun add (Ljava/lang/Object;)Z
public fun addAll (ILjava/util/Collection;)Z
public fun addAll (Ljava/util/Collection;)Z
public fun clear ()V
public fun contains (Ljava/lang/Class;)Z
public fun contains (Lapp/revanced/patcher/patch/Patch;)Z
public final fun contains (Ljava/lang/Object;)Z
public fun containsAll (Ljava/util/Collection;)Z
public fun get (I)Ljava/lang/Class;
public synthetic fun get (I)Ljava/lang/Object;
public fun getSize ()I
public fun indexOf (Ljava/lang/Class;)I
public final fun indexOf (Ljava/lang/Object;)I
public fun isEmpty ()Z
public fun iterator ()Ljava/util/Iterator;
public fun lastIndexOf (Ljava/lang/Class;)I
public final fun lastIndexOf (Ljava/lang/Object;)I
public fun listIterator ()Ljava/util/ListIterator;
public fun listIterator (I)Ljava/util/ListIterator;
public final fun remove (I)Ljava/lang/Class;
public synthetic fun remove (I)Ljava/lang/Object;
public fun remove (Ljava/lang/Class;)Z
public final fun remove (Ljava/lang/Object;)Z
public fun remove (Ljava/lang/Object;)Z
public fun removeAll (Ljava/util/Collection;)Z
public fun removeAt (I)Ljava/lang/Class;
public fun retainAll (Ljava/util/Collection;)Z
public fun set (ILjava/lang/Class;)Ljava/lang/Class;
public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object;
public final fun size ()I
public fun subList (II)Ljava/util/List;
public fun toArray ()[Ljava/lang/Object;
public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
}
@@ -49,12 +31,10 @@ public final class app/revanced/patcher/PatchBundleLoader$Dex : app/revanced/pat
public fun <init> ([Ljava/io/File;)V
public fun <init> ([Ljava/io/File;Ljava/io/File;)V
public synthetic fun <init> ([Ljava/io/File;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun remove (I)Ljava/lang/Object;
}
public final class app/revanced/patcher/PatchBundleLoader$Jar : app/revanced/patcher/PatchBundleLoader {
public fun <init> ([Ljava/io/File;)V
public synthetic fun remove (I)Ljava/lang/Object;
}
public abstract interface class app/revanced/patcher/PatchExecutorFunction : java/util/function/Function {
@@ -76,13 +56,19 @@ public final class app/revanced/patcher/PatcherContext {
public final fun getPackageMetadata ()Lapp/revanced/patcher/PackageMetadata;
}
public abstract class app/revanced/patcher/PatcherException : java/lang/Exception {
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/patcher/PatcherException$CircularDependencyException : app/revanced/patcher/PatcherException {
}
public final class app/revanced/patcher/PatcherOptions {
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patcher/logging/Logger;)V
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patcher/logging/Logger;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun copy (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patcher/logging/Logger;)Lapp/revanced/patcher/PatcherOptions;
public static synthetic fun copy$default (Lapp/revanced/patcher/PatcherOptions;Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Lapp/revanced/patcher/logging/Logger;ILjava/lang/Object;)Lapp/revanced/patcher/PatcherOptions;
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Z)V
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun copy (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Z)Lapp/revanced/patcher/PatcherOptions;
public static synthetic fun copy$default (Lapp/revanced/patcher/PatcherOptions;Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/patcher/PatcherOptions;
public fun equals (Ljava/lang/Object;)Z
public fun hashCode ()I
public final fun recreateResourceCacheDirectory ()Ljava/io/File;
@@ -115,23 +101,6 @@ public abstract interface class app/revanced/patcher/PatchesConsumer {
public abstract fun acceptPatches (Ljava/util/List;)V
}
public abstract interface annotation class app/revanced/patcher/annotation/Compatibility : java/lang/annotation/Annotation {
public abstract fun compatiblePackages ()[Lapp/revanced/patcher/annotation/Package;
}
public abstract interface annotation class app/revanced/patcher/annotation/Description : java/lang/annotation/Annotation {
public abstract fun description ()Ljava/lang/String;
}
public abstract interface annotation class app/revanced/patcher/annotation/Name : java/lang/annotation/Annotation {
public abstract fun name ()Ljava/lang/String;
}
public abstract interface annotation class app/revanced/patcher/annotation/Package : java/lang/annotation/Annotation {
public abstract fun name ()Ljava/lang/String;
public abstract fun versions ()[Ljava/lang/String;
}
public final class app/revanced/patcher/data/BytecodeContext : app/revanced/patcher/data/Context {
public final fun findClass (Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/ClassProxy;
public final fun findClass (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy;
@@ -198,182 +167,95 @@ public final class app/revanced/patcher/extensions/InstructionExtensions {
public final class app/revanced/patcher/extensions/MethodFingerprintExtensions {
public static final field INSTANCE Lapp/revanced/patcher/extensions/MethodFingerprintExtensions;
public final fun getFuzzyPatternScanMethod (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprint;)Lapp/revanced/patcher/fingerprint/method/annotation/FuzzyPatternScanMethod;
public final fun getName (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprint;)Ljava/lang/String;
public final fun getFuzzyPatternScanMethod (Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Lapp/revanced/patcher/fingerprint/annotation/FuzzyPatternScanMethod;
}
public final class app/revanced/patcher/extensions/PatchExtensions {
public static final field INSTANCE Lapp/revanced/patcher/extensions/PatchExtensions;
public final fun getCompatiblePackages (Ljava/lang/Class;)[Lapp/revanced/patcher/annotation/Package;
public final fun getDependencies (Ljava/lang/Class;)[Lkotlin/reflect/KClass;
public final fun getDescription (Ljava/lang/Class;)Ljava/lang/String;
public final fun getInclude (Ljava/lang/Class;)Z
public final fun getOptions (Ljava/lang/Class;)Lapp/revanced/patcher/patch/PatchOptions;
public final fun getPatchName (Ljava/lang/Class;)Ljava/lang/String;
}
public abstract interface class app/revanced/patcher/fingerprint/Fingerprint {
}
public abstract interface annotation class app/revanced/patcher/fingerprint/method/annotation/FuzzyPatternScanMethod : java/lang/annotation/Annotation {
public abstract fun threshold ()I
}
public abstract class app/revanced/patcher/fingerprint/method/impl/MethodFingerprint : app/revanced/patcher/fingerprint/Fingerprint {
public static final field Companion Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprint$Companion;
public abstract class app/revanced/patcher/fingerprint/MethodFingerprint {
public static final field Companion Lapp/revanced/patcher/fingerprint/MethodFingerprint$Companion;
public fun <init> ()V
public fun <init> (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getResult ()Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult;
public final fun setResult (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult;)V
public final fun getResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
public final fun resolve (Lapp/revanced/patcher/data/BytecodeContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
public final fun resolve (Lapp/revanced/patcher/data/BytecodeContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
}
public final class app/revanced/patcher/fingerprint/method/impl/MethodFingerprint$Companion {
public final fun resolve (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprint;Lapp/revanced/patcher/data/BytecodeContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
public final fun resolve (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprint;Lapp/revanced/patcher/data/BytecodeContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
public final class app/revanced/patcher/fingerprint/MethodFingerprint$Companion {
public final fun resolve (Ljava/lang/Iterable;Lapp/revanced/patcher/data/BytecodeContext;Ljava/lang/Iterable;)V
}
public final class app/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult {
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult;Lapp/revanced/patcher/data/BytecodeContext;)V
public final fun component1 ()Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun component2 ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public final fun component3 ()Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult;
public final fun copy (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult;Lapp/revanced/patcher/data/BytecodeContext;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult;
public static synthetic fun copy$default (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult;Lapp/revanced/patcher/data/BytecodeContext;ILjava/lang/Object;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult;
public fun equals (Ljava/lang/Object;)Z
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult {
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult;Lapp/revanced/patcher/data/BytecodeContext;)V
public final fun getClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
public final fun getMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
public final fun getMutableMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
public final fun getScanResult ()Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public final fun getScanResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult;
}
public final class app/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult {
public fun <init> (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;)V
public final fun component1 ()Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;
public final fun component2 ()Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;
public final fun copy (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult;
public static synthetic fun copy$default (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult;Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;ILjava/lang/Object;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult;
public fun equals (Ljava/lang/Object;)Z
public final fun getPatternScanResult ()Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;
public final fun getStringsScanResult ()Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult {
public fun <init> (Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;)V
public final fun getPatternScanResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;
public final fun getStringsScanResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;
}
public final class app/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult {
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult {
public fun <init> (IILjava/util/List;)V
public synthetic fun <init> (IILjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()I
public final fun component2 ()I
public final fun component3 ()Ljava/util/List;
public final fun copy (IILjava/util/List;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;
public static synthetic fun copy$default (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;IILjava/util/List;ILjava/lang/Object;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;
public fun equals (Ljava/lang/Object;)Z
public final fun getEndIndex ()I
public final fun getStartIndex ()I
public final fun getWarnings ()Ljava/util/List;
public fun hashCode ()I
public final fun setWarnings (Ljava/util/List;)V
public fun toString ()Ljava/lang/String;
}
public final class app/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult$Warning {
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult$Warning {
public fun <init> (Lcom/android/tools/smali/dexlib2/Opcode;Lcom/android/tools/smali/dexlib2/Opcode;II)V
public final fun component1 ()Lcom/android/tools/smali/dexlib2/Opcode;
public final fun component2 ()Lcom/android/tools/smali/dexlib2/Opcode;
public final fun component3 ()I
public final fun component4 ()I
public final fun copy (Lcom/android/tools/smali/dexlib2/Opcode;Lcom/android/tools/smali/dexlib2/Opcode;II)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult$Warning;
public static synthetic fun copy$default (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult$Warning;Lcom/android/tools/smali/dexlib2/Opcode;Lcom/android/tools/smali/dexlib2/Opcode;IIILjava/lang/Object;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult$Warning;
public fun equals (Ljava/lang/Object;)Z
public final fun getCorrectOpcode ()Lcom/android/tools/smali/dexlib2/Opcode;
public final fun getInstructionIndex ()I
public final fun getPatternIndex ()I
public final fun getWrongOpcode ()Lcom/android/tools/smali/dexlib2/Opcode;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class app/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult {
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult {
public fun <init> (Ljava/util/List;)V
public final fun component1 ()Ljava/util/List;
public final fun copy (Ljava/util/List;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;
public static synthetic fun copy$default (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;Ljava/util/List;ILjava/lang/Object;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;
public fun equals (Ljava/lang/Object;)Z
public final fun getMatches ()Ljava/util/List;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class app/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult$StringMatch {
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult$StringMatch {
public fun <init> (Ljava/lang/String;I)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()I
public final fun copy (Ljava/lang/String;I)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult$StringMatch;
public static synthetic fun copy$default (Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult$StringMatch;Ljava/lang/String;IILjava/lang/Object;)Lapp/revanced/patcher/fingerprint/method/impl/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult$StringMatch;
public fun equals (Ljava/lang/Object;)Z
public final fun getIndex ()I
public final fun getString ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public abstract interface class app/revanced/patcher/logging/Logger {
public abstract fun error (Ljava/lang/String;)V
public abstract fun info (Ljava/lang/String;)V
public abstract fun trace (Ljava/lang/String;)V
public abstract fun warn (Ljava/lang/String;)V
}
public final class app/revanced/patcher/logging/Logger$DefaultImpls {
public static fun error (Lapp/revanced/patcher/logging/Logger;Ljava/lang/String;)V
public static fun info (Lapp/revanced/patcher/logging/Logger;Ljava/lang/String;)V
public static fun trace (Lapp/revanced/patcher/logging/Logger;Ljava/lang/String;)V
public static fun warn (Lapp/revanced/patcher/logging/Logger;Ljava/lang/String;)V
}
public final class app/revanced/patcher/logging/impl/NopLogger : app/revanced/patcher/logging/Logger {
public static final field INSTANCE Lapp/revanced/patcher/logging/impl/NopLogger;
public fun error (Ljava/lang/String;)V
public fun info (Ljava/lang/String;)V
public fun trace (Ljava/lang/String;)V
public fun warn (Ljava/lang/String;)V
public abstract interface annotation class app/revanced/patcher/fingerprint/annotation/FuzzyPatternScanMethod : java/lang/annotation/Annotation {
public abstract fun threshold ()I
}
public abstract class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch {
public fun <init> ()V
public fun <init> (Ljava/lang/Iterable;)V
public synthetic fun <init> (Ljava/lang/Iterable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/util/Set;)V
public synthetic fun <init> (Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/patcher/patch/IllegalValueException : java/lang/Exception {
public fun <init> (Ljava/lang/Object;)V
public final fun getValue ()Ljava/lang/Object;
}
public final class app/revanced/patcher/patch/InvalidTypeException : java/lang/Exception {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public final fun getExpected ()Ljava/lang/String;
public final fun getGot ()Ljava/lang/String;
}
public final class app/revanced/patcher/patch/NoSuchOptionException : java/lang/Exception {
public fun <init> (Ljava/lang/String;)V
public final fun getOption ()Ljava/lang/String;
}
public abstract class app/revanced/patcher/patch/OptionsContainer {
public fun <init> ()V
public final fun getOptions ()Lapp/revanced/patcher/patch/PatchOptions;
protected final fun option (Lapp/revanced/patcher/patch/PatchOption;)Lapp/revanced/patcher/patch/PatchOption;
}
public abstract interface class app/revanced/patcher/patch/Patch {
public abstract class app/revanced/patcher/patch/Patch {
public fun equals (Ljava/lang/Object;)Z
public abstract fun execute (Lapp/revanced/patcher/data/Context;)V
public final fun getCompatiblePackages ()Ljava/util/Set;
public final fun getDependencies ()Ljava/util/Set;
public final fun getDescription ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
public final fun getOptions ()Lapp/revanced/patcher/patch/options/PatchOptions;
public final fun getRequiresIntegrations ()Z
public final fun getUse ()Z
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class app/revanced/patcher/patch/Patch$CompatiblePackage {
public fun <init> (Ljava/lang/String;Ljava/util/Set;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getName ()Ljava/lang/String;
public final fun getVersions ()Ljava/util/Set;
}
public final class app/revanced/patcher/patch/PatchException : java/lang/Exception {
@@ -382,73 +264,118 @@ public final class app/revanced/patcher/patch/PatchException : java/lang/Excepti
public fun <init> (Ljava/lang/Throwable;)V
}
public abstract class app/revanced/patcher/patch/PatchOption {
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final class app/revanced/patcher/patch/PatchResult {
public final fun getException ()Lapp/revanced/patcher/patch/PatchException;
public final fun getPatch ()Lapp/revanced/patcher/patch/Patch;
}
public abstract class app/revanced/patcher/patch/ResourcePatch : app/revanced/patcher/patch/Patch {
public fun <init> ()V
}
public abstract interface annotation class app/revanced/patcher/patch/annotation/CompatiblePackage : java/lang/annotation/Annotation {
public abstract fun name ()Ljava/lang/String;
public abstract fun versions ()[Ljava/lang/String;
}
public abstract interface annotation class app/revanced/patcher/patch/annotation/Patch : java/lang/annotation/Annotation {
public abstract fun compatiblePackages ()[Lapp/revanced/patcher/patch/annotation/CompatiblePackage;
public abstract fun dependencies ()[Ljava/lang/Class;
public abstract fun description ()Ljava/lang/String;
public abstract fun name ()Ljava/lang/String;
public abstract fun requiresIntegrations ()Z
public abstract fun use ()Z
}
public class app/revanced/patcher/patch/options/PatchOption {
public static final field PatchExtensions Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Lkotlin/jvm/functions/Function2;)V
public final fun getDefault ()Ljava/lang/Object;
public final fun getDescription ()Ljava/lang/String;
public final fun getKey ()Ljava/lang/String;
public final fun getRequired ()Z
public final fun getTitle ()Ljava/lang/String;
public final fun getValidator ()Lkotlin/jvm/functions/Function1;
public final fun getValidator ()Lkotlin/jvm/functions/Function2;
public final fun getValue ()Ljava/lang/Object;
public final fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object;
public final fun getValueType ()Ljava/lang/String;
public final fun getValues ()Ljava/util/Map;
public fun reset ()V
public final fun setValue (Ljava/lang/Object;)V
public final fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V
public fun toString ()Ljava/lang/String;
}
public final class app/revanced/patcher/patch/PatchOption$BooleanOption : app/revanced/patcher/patch/PatchOption {
public fun <init> (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final class app/revanced/patcher/patch/options/PatchOption$PatchExtensions {
public final fun booleanArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun booleanArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun booleanPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun booleanPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun floatArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun floatArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun floatPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun floatPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun intArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun intArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun intPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun intPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun longArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun longArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun longPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun longPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun stringArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun stringArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun stringPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
public static synthetic fun stringPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
}
public final class app/revanced/patcher/patch/PatchOption$IntListOption : app/revanced/patcher/patch/PatchOption$ListOption {
public fun <init> (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public abstract class app/revanced/patcher/patch/options/PatchOptionException : java/lang/Exception {
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public abstract class app/revanced/patcher/patch/PatchOption$ListOption : app/revanced/patcher/patch/PatchOption {
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getOptions ()Ljava/lang/Iterable;
public final class app/revanced/patcher/patch/options/PatchOptionException$InvalidValueTypeException : app/revanced/patcher/patch/options/PatchOptionException {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/patcher/patch/PatchOption$StringListOption : app/revanced/patcher/patch/PatchOption$ListOption {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Iterable;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final class app/revanced/patcher/patch/options/PatchOptionException$PatchOptionNotFoundException : app/revanced/patcher/patch/options/PatchOptionException {
public fun <init> (Ljava/lang/String;)V
}
public final class app/revanced/patcher/patch/PatchOption$StringOption : app/revanced/patcher/patch/PatchOption {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final class app/revanced/patcher/patch/options/PatchOptionException$ValueRequiredException : app/revanced/patcher/patch/options/PatchOptionException {
public fun <init> (Lapp/revanced/patcher/patch/options/PatchOption;)V
}
public final class app/revanced/patcher/patch/PatchOptions : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker {
public fun <init> ([Lapp/revanced/patcher/patch/PatchOption;)V
public final fun getUntyped (Ljava/lang/String;)Lapp/revanced/patcher/patch/PatchOption;
public fun iterator ()Ljava/util/Iterator;
public final fun nullify (Ljava/lang/String;)V
public final class app/revanced/patcher/patch/options/PatchOptionException$ValueValidationException : app/revanced/patcher/patch/options/PatchOptionException {
public fun <init> (Ljava/lang/Object;Lapp/revanced/patcher/patch/options/PatchOption;)V
}
public final class app/revanced/patcher/patch/PatchResult {
public final fun getException ()Lapp/revanced/patcher/patch/PatchException;
public final fun getPatchName ()Ljava/lang/String;
}
public final class app/revanced/patcher/patch/RequirementNotMetException : java/lang/Exception {
public static final field INSTANCE Lapp/revanced/patcher/patch/RequirementNotMetException;
}
public abstract interface class app/revanced/patcher/patch/ResourcePatch : app/revanced/patcher/patch/Patch {
}
public abstract interface annotation class app/revanced/patcher/patch/annotations/DependsOn : java/lang/annotation/Annotation {
public abstract fun dependencies ()[Ljava/lang/Class;
}
public abstract interface annotation class app/revanced/patcher/patch/annotations/Patch : java/lang/annotation/Annotation {
public abstract fun include ()Z
}
public abstract interface annotation class app/revanced/patcher/patch/annotations/RequiresIntegrations : java/lang/annotation/Annotation {
public final class app/revanced/patcher/patch/options/PatchOptions : java/util/Map, kotlin/jvm/internal/markers/KMutableMap {
public fun <init> ()V
public fun clear ()V
public final fun containsKey (Ljava/lang/Object;)Z
public fun containsKey (Ljava/lang/String;)Z
public fun containsValue (Lapp/revanced/patcher/patch/options/PatchOption;)Z
public final fun containsValue (Ljava/lang/Object;)Z
public final fun entrySet ()Ljava/util/Set;
public final fun get (Ljava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object;
public fun get (Ljava/lang/String;)Lapp/revanced/patcher/patch/options/PatchOption;
public fun getEntries ()Ljava/util/Set;
public fun getKeys ()Ljava/util/Set;
public fun getSize ()I
public fun getValues ()Ljava/util/Collection;
public fun isEmpty ()Z
public final fun keySet ()Ljava/util/Set;
public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public fun put (Ljava/lang/String;Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/patcher/patch/options/PatchOption;
public fun putAll (Ljava/util/Map;)V
public final fun register (Lapp/revanced/patcher/patch/options/PatchOption;)V
public final fun remove (Ljava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
public final synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object;
public fun remove (Ljava/lang/String;)Lapp/revanced/patcher/patch/options/PatchOption;
public final fun set (Ljava/lang/String;Ljava/lang/Object;)V
public final fun size ()I
public final fun values ()Ljava/util/Collection;
}
public final class app/revanced/patcher/util/DomFileEditor : java/io/Closeable {

View File

@@ -1,11 +1,33 @@
plugins {
kotlin("jvm") version "1.8.20"
`maven-publish`
kotlin("jvm") version "1.9.10"
alias(libs.plugins.binary.compatibility.validator)
`maven-publish`
signing
java
}
group = "app.revanced"
tasks {
processResources {
expand("projectVersion" to project.version)
}
test {
useJUnitPlatform()
testLogging {
events("PASSED", "SKIPPED", "FAILED")
}
}
}
repositories {
mavenCentral()
mavenLocal()
maven { url = uri("https://jitpack.io") }
google()
}
dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.xpp3)
@@ -19,40 +41,46 @@ dependencies {
testImplementation(libs.kotlin.test)
}
tasks {
test {
useJUnitPlatform()
testLogging {
events("PASSED", "SKIPPED", "FAILED")
}
}
processResources {
expand("projectVersion" to project.version)
}
}
kotlin { jvmToolchain(11) }
java {
withJavadocJar()
withSourcesJar()
}
kotlin {
jvmToolchain(11)
}
publishing {
repositories {
mavenLocal()
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
publications {
create<MavenPublication>("revanced-patcher-publication") {
from(components["java"])
version = project.version.toString()
pom {
name = "ReVanced Patcher"
description = "Patcher used by ReVanced."
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-patcher.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-patcher.git"
url = "https://github.com/revanced/revanced-patcher"
}
}
}
}
publications {
create<MavenPublication>("gpr") {
from(components["java"])
}
}
}
}

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 14.2.2
version = 19.0.0-dev.1

View File

@@ -1,10 +1,10 @@
[versions]
android = "4.1.1.4"
kotlin-reflect = "1.9.0"
apktool-lib = "2.8.2-6"
apktool-lib = "2.9.1"
kotlin-test = "1.8.20-RC"
kotlinx-coroutines-core = "1.7.1"
multidexlib2 = "3.0.3.r2"
kotlinx-coroutines-core = "1.7.3"
multidexlib2 = "3.0.3.r3"
smali = "3.0.3"
xpp3 = "1.1.4c"
binary-compatibility-validator = "0.13.2"
@@ -12,7 +12,7 @@ binary-compatibility-validator = "0.13.2"
[libraries]
android = { module = "com.google.android:android", version.ref = "android" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
apktool-lib = { module = "app.revanced:apktool-lib", version.ref = "apktool-lib" }
apktool-lib = { module = "app.revanced:apktool", version.ref = "apktool-lib" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
multidexlib2 = { module = "app.revanced:multidexlib2", version.ref = "multidexlib2" }

View File

@@ -1,22 +1 @@
val githubUsername: String = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
val githubPassword: String = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
google()
mavenLocal()
listOf("multidexlib2", "apktool").forEach { repo ->
maven {
url = uri("https://maven.pkg.github.com/revanced/$repo")
credentials {
username = githubUsername
password = githubPassword
}
}
}
}
}
rootProject.name = "revanced-patcher"
rootProject.name = "revanced-patcher"

View File

@@ -2,37 +2,88 @@
package app.revanced.patcher
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchClass
import dalvik.system.DexClassLoader
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.MultiDexIO
import java.io.File
import java.net.URLClassLoader
import java.util.jar.JarFile
import java.util.logging.Logger
import kotlin.reflect.KClass
/**
* A patch bundle.
* A set of [Patch]es.
*/
typealias PatchSet = Set<Patch<*>>
/**
* A [Patch] class.
*/
typealias PatchClass = KClass<out Patch<*>>
/**
* A loader of [Patch]es from patch bundles.
* This will load all [Patch]es from the given patch bundles that have a name.
*
*
* @param fromClasses The classes to get [Patch]es from.
* @param getBinaryClassNames A function that returns the binary names of all classes in a patch bundle.
* @param classLoader The [ClassLoader] to use for loading the classes.
* @param patchBundles A set of patches to initialize this instance with.
*/
sealed class PatchBundleLoader private constructor(
fromClasses: Iterable<Class<*>>
) : MutableList<PatchClass> by mutableListOf() {
init {
fromClasses.filter {
if (it.isAnnotation) return@filter false
classLoader: ClassLoader,
patchBundles: Array<out File>,
getBinaryClassNames: (patchBundle: File) -> List<String>,
// This constructor parameter is unfortunately necessary,
// so that a reference to the mutable set is present in the constructor to be able to add patches to it.
// because the instance itself is a PatchSet, which is immutable, that is delegated by the parameter.
private val patchSet: MutableSet<Patch<*>> = mutableSetOf()
) : PatchSet by patchSet {
private val logger = Logger.getLogger(PatchBundleLoader::class.java.name)
it.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class) != null
}.map {
@Suppress("UNCHECKED_CAST")
it as PatchClass
}.sortedBy {
it.patchName
}.let { addAll(it) }
init {
patchBundles.flatMap(getBinaryClassNames).asSequence().map {
classLoader.loadClass(it)
}.filter {
Patch::class.java.isAssignableFrom(it)
}.mapNotNull { patchClass ->
patchClass.getInstance(logger, silent = true)
}.filter {
it.name != null
}.let { patches ->
patchSet.addAll(patches)
}
}
internal companion object Utils {
/**
* Instantiates a [Patch]. If the class is a singleton, the INSTANCE field will be used.
*
* @param logger The [Logger] to use for logging.
* @param silent Whether to suppress logging.
* @return The instantiated [Patch] or `null` if the [Patch] could not be instantiated.
*/
internal fun Class<*>.getInstance(logger: Logger, silent: Boolean = false): Patch<*>? {
return try {
getField("INSTANCE").get(null)
} catch (exception: NoSuchFieldException) {
if (!silent) logger.fine(
"Patch class '${name}' has no INSTANCE field, therefor not a singleton. " +
"Will try to instantiate it."
)
try {
getDeclaredConstructor().newInstance()
} catch (exception: Exception) {
if (!silent) logger.severe(
"Patch class '${name}' is not singleton and has no suitable constructor, " +
"therefor cannot be instantiated and will be ignored."
)
return null
}
} as Patch<*>
}
}
/**
@@ -41,18 +92,13 @@ sealed class PatchBundleLoader private constructor(
* @param patchBundles The path to patch bundles of JAR format.
*/
class Jar(vararg patchBundles: File) : PatchBundleLoader(
with(
URLClassLoader(patchBundles.map { it.toURI().toURL() }.toTypedArray())
) {
patchBundles.flatMap { patchBundle ->
// Get the names of all classes in the DEX file.
JarFile(patchBundle).entries().asSequence()
.filter { it.name.endsWith(".class") }
.map { it.name.replace('/', '.').replace(".class", "") }
.map { loadClass(it) }
}
})
URLClassLoader(patchBundles.map { it.toURI().toURL() }.toTypedArray()),
patchBundles,
{ patchBundle ->
JarFile(patchBundle).entries().toList().filter { it.name.endsWith(".class") }
.map { it.name.replace('/', '.').replace(".class", "") }
}
)
/**
* A [PatchBundleLoader] for [Dex] files.
@@ -62,20 +108,19 @@ sealed class PatchBundleLoader private constructor(
* This parameter is deprecated and has no effect since API level 26.
*/
class Dex(vararg patchBundles: File, optimizedDexDirectory: File? = null) : PatchBundleLoader(
with(
DexClassLoader(
DexClassLoader(
patchBundles.joinToString(File.pathSeparator) { it.absolutePath }, optimizedDexDirectory?.absolutePath,
null,
PatchBundleLoader::class.java.classLoader
)
) {
patchBundles
.flatMap {
MultiDexIO.readDexFile(true, it, BasicDexFileNamer(), null, null).classes
}
.map { classDef -> classDef.type.substring(1, classDef.length - 1) }
.map { loadClass(it) }
}) {
),
patchBundles,
{ patchBundle ->
MultiDexIO.readDexFile(true, patchBundle, BasicDexFileNamer(), null, null).classes
.map { classDef ->
classDef.type.substring(1, classDef.length - 1)
}
}
) {
@Deprecated("This constructor is deprecated. Use the constructor with the second parameter instead.")
constructor(vararg patchBundles: File) : this(*patchBundles, optimizedDexDirectory = null)
}

View File

@@ -1,20 +1,15 @@
package app.revanced.patcher
import app.revanced.patcher.data.Context
import app.revanced.patcher.PatchBundleLoader.Utils.getInstance
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
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.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap
import app.revanced.patcher.fingerprint.LookupMap.Maps.clearLookupMaps
import app.revanced.patcher.fingerprint.LookupMap.Maps.initializeLookupMaps
import app.revanced.patcher.fingerprint.MethodFingerprint.Companion.resolveUsingLookupMap
import app.revanced.patcher.patch.*
import kotlinx.coroutines.flow.flow
import java.io.Closeable
import java.io.File
import java.util.function.Supplier
import java.util.logging.Level
import java.util.logging.LogManager
import java.util.logging.Logger
/**
@@ -34,47 +29,87 @@ class Patcher(
val context = PatcherContext(options)
init {
LogManager.getLogManager().let { manager ->
// Disable root logger.
manager.getLogger("").level = Level.OFF
// Enable ReVanced logging only.
manager.loggerNames
.toList()
.filter { it.startsWith("app.revanced") }
.map { manager.getLogger(it) }
.forEach { it.level = Level.INFO }
}
context.resourceContext.decodeResources(ResourceContext.ResourceDecodingMode.MANIFEST_ONLY)
}
override fun acceptPatches(patches: List<PatchClass>) {
// TODO: Fix circular dependency detection.
// /**
// * Add [Patch]es to ReVanced [Patcher].
// * It is not guaranteed that all supplied [Patch]es will be accepted, if an exception is thrown.
// *
// * @param patches The [Patch]es to add.
// * @throws PatcherException.CircularDependencyException If a circular dependency is detected.
// */
/**
* Add [Patch]es to ReVanced [Patcher].
*
* @param patches The [Patch]es to add.
*/
@Suppress("NAME_SHADOWING")
override fun acceptPatches(patches: List<Patch<*>>) {
/**
* Returns true if at least one patches or its dependencies matches the given predicate.
* Add dependencies of a [Patch] recursively to [PatcherContext.allPatches].
* If a [Patch] is already in [PatcherContext.allPatches], it will not be added again.
*/
fun PatchClass.anyRecursively(predicate: (PatchClass) -> Boolean): Boolean =
fun PatchClass.putDependenciesRecursively() {
if (context.allPatches.contains(this)) return
val dependency = this.java.getInstance(logger)!!
context.allPatches[this] = dependency
dependency.dependencies?.forEach { it.putDependenciesRecursively() }
}
// Add all patches and their dependencies to the context.
for (patch in patches) context.executablePatches.putIfAbsent(patch::class, patch) ?: run {
context.allPatches[patch::class] = patch
patch.dependencies?.forEach { it.putDependenciesRecursively() }
}
/* TODO: Fix circular dependency detection.
val graph = mutableMapOf<PatchClass, MutableList<PatchClass>>()
fun PatchClass.visit() {
if (this in graph) return
val group = graph.getOrPut(this) { mutableListOf(this) }
val dependencies = context.allPatches[this]!!.manifest.dependencies ?: return
dependencies.forEach { dependency ->
if (group == graph[dependency])
throw PatcherException.CircularDependencyException(context.allPatches[this]!!.manifest.name)
graph[dependency] = group.apply { add(dependency) }
dependency.visit()
}
}
*/
/**
* Returns true if at least one patch or its dependencies matches the given predicate.
*
* @param predicate The predicate to match.
*/
fun Patch<*>.anyRecursively(predicate: (Patch<*>) -> Boolean): Boolean =
predicate(this) || dependencies?.any { dependency ->
dependency.java.anyRecursively(predicate)
context.allPatches[dependency]!!.anyRecursively(predicate)
} ?: false
// Determine if resource patching is required.
for (patch in patches) {
if (patch.anyRecursively { ResourcePatch::class.java.isAssignableFrom(it) }) {
options.resourceDecodingMode = ResourceContext.ResourceDecodingMode.FULL
break
}
}
context.allPatches.values.let { patches ->
// Determine, if resource patching is required.
for (patch in patches)
if (patch.anyRecursively { patch is ResourcePatch }) {
options.resourceDecodingMode = ResourceContext.ResourceDecodingMode.FULL
break
}
// Determine if merging integrations is required.
for (patch in patches) {
if (patch.anyRecursively { it.requiresIntegrations }) {
context.bytecodeContext.integrations.merge = true
break
}
// Determine, if merging integrations is required.
for (patch in patches)
if (patch.anyRecursively { it.requiresIntegrations }) {
context.bytecodeContext.integrations.merge = true
break
}
}
context.patches.addAll(patches)
}
/**
@@ -93,70 +128,67 @@ class Patcher(
* @return A pair of the name of the [Patch] and its [PatchResult].
*/
override fun apply(returnOnError: Boolean) = flow {
class ExecutedPatch(val patchInstance: Patch<Context<*>>, val patchResult: PatchResult)
/**
* Execute a [Patch] and its dependencies recursively.
*
* @param patchClass The [Patch] to execute.
* @param patch The [Patch] to execute.
* @param executedPatches A map to prevent [Patch]es from being executed twice due to dependencies.
* @return The result of executing the [Patch].
*/
fun executePatch(
patchClass: PatchClass,
executedPatches: LinkedHashMap<String, ExecutedPatch>
patch: Patch<*>,
executedPatches: LinkedHashMap<Patch<*>, PatchResult>
): PatchResult {
val patchName = patchClass.patchName
val patchName = patch.name ?: patch.toString()
executedPatches[patchName]?.let { executedPatch ->
executedPatch.patchResult.exception ?: return executedPatch.patchResult
executedPatches[patch]?.let { patchResult ->
patchResult.exception ?: return patchResult
// Return a new result with an exception indicating that the patch was not executed previously,
// because it is a dependency of another patch that failed.
return PatchResult(patchName, PatchException("'$patchName' did not succeed previously"))
return PatchResult(patch, PatchException("'$patchName' did not succeed previously"))
}
// Recursively execute all dependency patches.
patchClass.dependencies?.forEach { dependencyClass ->
val dependency = dependencyClass.java
patch.dependencies?.forEach { dependencyClass ->
val dependency = context.allPatches[dependencyClass]!!
val result = executePatch(dependency, executedPatches)
result.exception?.let {
return PatchResult(
patchName,
patch,
PatchException(
"'$patchName' depends on '${dependency.patchName}' that raised an exception: $it"
"'$patchName' depends on '${dependency.name ?: dependency}' " +
"that raised an exception:\n${it.stackTraceToString()}"
)
)
}
}
// TODO: Implement this in a more polymorphic way.
val patchInstance = patchClass.getDeclaredConstructor().newInstance()
val patchContext = if (patchInstance is BytecodePatch) {
patchInstance.fingerprints?.resolveUsingLookupMap(context.bytecodeContext)
context.bytecodeContext
} else {
context.resourceContext
}
return try {
patchInstance.execute(patchContext)
// TODO: Implement this in a more polymorphic way.
when (patch) {
is BytecodePatch -> {
patch.fingerprints.resolveUsingLookupMap(context.bytecodeContext)
patch.execute(context.bytecodeContext)
}
is ResourcePatch -> {
patch.execute(context.resourceContext)
}
}
PatchResult(patchName)
PatchResult(patch)
} catch (exception: PatchException) {
PatchResult(patchName, exception)
PatchResult(patch, exception)
} catch (exception: Exception) {
PatchResult(patchName, PatchException(exception))
}.also { executedPatches[patchName] = ExecutedPatch(patchInstance, it) }
PatchResult(patch, PatchException(exception))
}.also { executedPatches[patch] = it }
}
if (context.bytecodeContext.integrations.merge) context.bytecodeContext.integrations.flush()
MethodFingerprint.initializeFingerprintResolutionLookupMaps(context.bytecodeContext)
initializeLookupMaps(context.bytecodeContext)
// Prevent from decoding the app manifest twice if it is not needed.
if (options.resourceDecodingMode == ResourceContext.ResourceDecodingMode.FULL)
@@ -164,63 +196,61 @@ class Patcher(
logger.info("Executing patches")
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // Key is name.
val executedPatches = LinkedHashMap<Patch<*>, PatchResult>() // Key is name.
context.patches.forEach { patch ->
val result = executePatch(patch, executedPatches)
context.executablePatches.values.sortedBy { it.name }.forEach { patch ->
val patchResult = executePatch(patch, executedPatches)
// If the patch failed, or if the patch is not closeable, emit the result.
// Results of patches that are closeable will be emitted later.
result.exception?.let {
emit(result)
// If the patch failed, emit the result, even if it is closeable.
// Results of executed patches that are closeable will be emitted later.
patchResult.exception?.let {
// Propagate exception to caller instead of wrapping it in a new exception.
emit(patchResult)
if (returnOnError) return@flow
} ?: run {
if (executedPatches[result.patchName]!!.patchInstance is Closeable) return@run
if (patch is Closeable) return@run
emit(result)
emit(patchResult)
}
}
executedPatches.values
.filter { it.patchResult.exception == null }
.filter { it.patchInstance is Closeable }.asReversed().forEach { executedPatch ->
val patchName = executedPatch.patchResult.patchName
.filter { it.exception == null }
.filter { it.patch is Closeable }.asReversed().forEach { executedPatch ->
val patch = executedPatch.patch
val result = try {
(executedPatch.patchInstance as Closeable).close()
(patch as Closeable).close()
executedPatch.patchResult
executedPatch
} catch (exception: PatchException) {
PatchResult(patchName, exception)
PatchResult(patch, exception)
} catch (exception: Exception) {
PatchResult(patchName, PatchException(exception))
PatchResult(patch, PatchException(exception))
}
result.exception?.let {
emit(
PatchResult(
patchName,
PatchException("'$patchName' raised an exception while being closed: $it")
patch,
PatchException(
"'${patch.name}' raised an exception while being closed: ${it.stackTraceToString()}",
result.exception
)
)
)
if (returnOnError) return@flow
} ?: run {
executedPatch
.patchInstance::class
.java
.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)
?: return@run
patch.name ?: return@run
emit(result)
}
}
}
override fun close() {
MethodFingerprint.clearFingerprintResolutionLookupMaps()
}
override fun close() = clearLookupMaps()
/**
* Compile and save the patched APK file.

View File

@@ -3,7 +3,6 @@ package app.revanced.patcher
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchClass
import brut.androlib.apk.ApkInfo
import brut.directory.ExtFile
@@ -19,9 +18,14 @@ class PatcherContext internal constructor(options: PatcherOptions) {
val packageMetadata = PackageMetadata(ApkInfo(ExtFile(options.inputFile)))
/**
* The list of [Patch]es to execute.
* The map of [Patch]es associated by their [PatchClass].
*/
internal val patches = mutableListOf<PatchClass>()
internal val executablePatches = mutableMapOf<PatchClass, Patch<*>>()
/**
* The map of all [Patch]es and their dependencies associated by their [PatchClass].
*/
internal val allPatches = mutableMapOf<PatchClass, Patch<*>>()
/**
* The [ResourceContext] of this [PatcherContext].

View File

@@ -0,0 +1,16 @@
package app.revanced.patcher
/**
* An exception thrown by ReVanced [Patcher].
*
* @param errorMessage The exception message.
* @param cause The corresponding [Throwable].
*/
sealed class PatcherException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
constructor(errorMessage: String) : this(errorMessage, null)
class CircularDependencyException internal constructor(dependant: String) : PatcherException(
"Patch '$dependant' causes a circular dependency"
)
}

View File

@@ -1,7 +1,6 @@
package app.revanced.patcher
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.logging.impl.NopLogger
import brut.androlib.Config
import java.io.File
import java.util.logging.Logger
@@ -12,16 +11,15 @@ import java.util.logging.Logger
* @param resourceCachePath The path to the directory to use for caching resources.
* @param aaptBinaryPath The path to a custom aapt binary.
* @param frameworkFileDirectory The path to the directory to cache the framework file in.
* @param unusedLogger The logger to use for logging.
* @param multithreadingDexFileWriter Whether to use multiple threads for writing dex files.
* This can impact memory usage.
*/
data class PatcherOptions
@Deprecated("Use the constructor without the logger parameter instead")
constructor(
data class PatcherOptions(
internal val inputFile: File,
internal val resourceCachePath: File = File("revanced-resource-cache"),
internal val aaptBinaryPath: String? = null,
internal val frameworkFileDirectory: String? = null,
internal val unusedLogger: app.revanced.patcher.logging.Logger = NopLogger
internal val multithreadingDexFileWriter: Boolean = false,
) {
private val logger = Logger.getLogger(PatcherOptions::class.java.name)
@@ -40,26 +38,6 @@ constructor(
frameworkDirectory = frameworkFileDirectory
}
/**
* Options for ReVanced [Patcher].
* @param inputFile The input file to patch.
* @param resourceCachePath The path to the directory to use for caching resources.
* @param aaptBinaryPath The path to a custom aapt binary.
* @param frameworkFileDirectory The path to the directory to cache the framework file in.
*/
constructor(
inputFile: File,
resourceCachePath: File = File("revanced-resource-cache"),
aaptBinaryPath: String? = null,
frameworkFileDirectory: String? = null,
) : this(
inputFile,
resourceCachePath,
aaptBinaryPath,
frameworkFileDirectory,
NopLogger
)
fun recreateResourceCacheDirectory() = resourceCachePath.also {
if (it.exists()) {
logger.info("Deleting existing resource cache directory")

View File

@@ -1,8 +1,8 @@
package app.revanced.patcher
import app.revanced.patcher.patch.PatchClass
import app.revanced.patcher.patch.Patch
@FunctionalInterface
interface PatchesConsumer {
fun acceptPatches(patches: List<PatchClass>)
fun acceptPatches(patches: List<Patch<*>>)
}

View File

@@ -1,23 +0,0 @@
package app.revanced.patcher.annotation
import app.revanced.patcher.patch.Patch
/**
* Annotation to constrain a [Patch] to compatible packages.
* @param compatiblePackages A list of packages a [Patch] is compatible with.
*/
@Target(AnnotationTarget.CLASS)
annotation class Compatibility(
val compatiblePackages: Array<Package>,
)
/**
* 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] is compatible with.
*/
@Target()
annotation class Package(
val name: String,
val versions: Array<String> = [],
)

View File

@@ -1,21 +0,0 @@
package app.revanced.patcher.annotation
import app.revanced.patcher.patch.Patch
/**
* Annotation to name a [Patch].
* @param name A suggestive name for the [Patch].
*/
@Target(AnnotationTarget.CLASS)
annotation class Name(
val name: String,
)
/**
* Annotation to describe a [Patch].
* @param description A description for the [Patch].
*/
@Target(AnnotationTarget.CLASS)
annotation class Description(
val description: String,
)

View File

@@ -4,7 +4,6 @@ import app.revanced.patcher.PatcherContext
import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.PatcherResult
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.annotations.RequiresIntegrations
import app.revanced.patcher.util.ClassMerger.merge
import app.revanced.patcher.util.ProxyClassList
import app.revanced.patcher.util.method.MethodWalker
@@ -13,11 +12,11 @@ import com.android.tools.smali.dexlib2.Opcodes
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.DexFile
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.writer.io.MemoryDataStore
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.DexIO
import lanchon.multidexlib2.MultiDexIO
import java.io.File
import java.io.FileFilter
import java.io.Flushable
import java.util.logging.Logger
@@ -91,13 +90,43 @@ class BytecodeContext internal constructor(private val options: PatcherOptions)
*/
fun toMethodWalker(startMethod: Method) = MethodWalker(this, startMethod)
/**
* Compile bytecode from the [BytecodeContext].
*
* @return The compiled bytecode.
*/
override fun get(): List<PatcherResult.PatchedDexFile> {
logger.info("Compiling patched dex files")
val patchedDexFileResults = options.resourceCachePath.resolve("dex").also {
it.deleteRecursively() // Make sure the directory is empty.
it.mkdirs()
}.apply {
MultiDexIO.writeDexFile(
true,
if (options.multithreadingDexFileWriter) -1 else 1,
this,
BasicDexFileNamer(),
object : DexFile {
override fun getClasses() = this@BytecodeContext.classes.also(ProxyClassList::replaceClasses)
override fun getOpcodes() = this@BytecodeContext.opcodes
},
DexIO.DEFAULT_MAX_DEX_POOL_SIZE
) { _, entryName, _ -> logger.info("Compiled $entryName") }
}.listFiles(FileFilter { it.isFile })!!.map { PatcherResult.PatchedDexFile(it.name, it.inputStream()) }
System.gc()
return patchedDexFileResults
}
/**
* The integrations of a [PatcherContext].
*/
internal inner class Integrations : MutableList<File> by mutableListOf(), Flushable {
/**
* Whether to merge integrations.
* True when any supplied [Patch] is annotated with [RequiresIntegrations].
* Set to true, if the field requiresIntegrations of any supplied [Patch] is true.
*/
var merge = false
@@ -109,7 +138,8 @@ class BytecodeContext internal constructor(private val options: PatcherOptions)
logger.info("Merging integrations")
// TODO: Multi-thread this.
val classMap = classes.associateBy { it.type }
this@Integrations.forEach { integrations ->
MultiDexIO.readDexFile(
true,
@@ -117,8 +147,8 @@ class BytecodeContext internal constructor(private val options: PatcherOptions)
null,
null
).classes.forEach classDef@{ classDef ->
val existingClass = classes.find { it.type == classDef.type } ?: run {
logger.fine("Merging $classDef")
val existingClass = classMap[classDef.type] ?: run {
logger.fine("Adding $classDef")
classes.add(classDef)
return@classDef
}
@@ -132,27 +162,7 @@ class BytecodeContext internal constructor(private val options: PatcherOptions)
}
}
}
clear()
}
}
/**
* Compile bytecode from the [BytecodeContext].
*
* @return The compiled bytecode.
*/
override fun get(): List<PatcherResult.PatchedDexFile> {
logger.info("Compiling modified dex files")
return mutableMapOf<String, MemoryDataStore>().apply {
MultiDexIO.writeDexFile(
true, -1, // Defaults to amount of available cores.
this, BasicDexFileNamer(), object : DexFile {
override fun getClasses() = this@BytecodeContext.classes.also(ProxyClassList::replaceClasses)
override fun getOpcodes() = this@BytecodeContext.opcodes
}, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
)
}.map { PatcherResult.PatchedDexFile(it.key, it.value.readAt(0)) }
}
}

View File

@@ -1,17 +1,11 @@
package app.revanced.patcher.extensions
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint
object MethodFingerprintExtensions {
/**
* The name of a [MethodFingerprint].
*/
val MethodFingerprint.name: String
get() = this.javaClass.simpleName
// TODO: Make this a property.
/**
* The [FuzzyPatternScanMethod] annotation of a [MethodFingerprint].
*/

View File

@@ -1,64 +0,0 @@
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.extensions.AnnotationExtensions.findAnnotationRecursively
import app.revanced.patcher.patch.OptionsContainer
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchClass
import app.revanced.patcher.patch.PatchOptions
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.RequiresIntegrations
import kotlin.reflect.KVisibility
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.companionObjectInstance
object PatchExtensions {
/**
* The name of a [Patch].
*/
val PatchClass.patchName: String
get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName
/**
* Weather or not a [Patch] should be included.
*/
val PatchClass.include
get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include
/**
* The description of a [Patch].
*/
val PatchClass.description
get() = findAnnotationRecursively(Description::class)?.description
/**
* The dependencies of a [Patch].
*/
val PatchClass.dependencies
get() = findAnnotationRecursively(DependsOn::class)?.dependencies
/**
* The packages a [Patch] is compatible with.
*/
val PatchClass.compatiblePackages
get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages
/**
* Weather or not a [Patch] requires integrations.
*/
internal val PatchClass.requiresIntegrations
get() = findAnnotationRecursively(RequiresIntegrations::class) != null
/**
* The options of a [Patch].
*/
val PatchClass.options: PatchOptions?
get() = kotlin.companionObject?.let { cl ->
if (cl.visibility != KVisibility.PUBLIC) return null
kotlin.companionObjectInstance?.let {
(it as? OptionsContainer)?.options
}
}
}

View File

@@ -1,9 +0,0 @@
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,125 @@
package app.revanced.patcher.fingerprint
import app.revanced.patcher.data.BytecodeContext
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference
import java.util.*
internal typealias MethodClassPair = Pair<Method, ClassDef>
/**
* Lookup map for methods.
*/
internal class LookupMap : MutableMap<String, LookupMap.MethodClassList> by mutableMapOf() {
/**
* Adds a [MethodClassPair] to the list associated with the given key.
* If the key does not exist, a new list is created and the [MethodClassPair] is added to it.
*/
fun add(
key: String,
methodClassPair: MethodClassPair
) {
getOrPut(key) { MethodClassList() }.add(methodClassPair)
}
/**
* List of methods and the class they are a member of.
*/
internal class MethodClassList : LinkedList<MethodClassPair>()
companion object Maps {
/**
* A list of methods and the class they are a member of.
*/
internal val methods = MethodClassList()
/**
* Lookup map for methods keyed to the methods access flags, return type and parameter.
*/
internal val methodSignatureLookupMap = LookupMap()
/**
* Lookup map for methods associated by strings referenced in the method.
*/
internal val methodStringsLookupMap = LookupMap()
/**
* Initializes lookup maps for [MethodFingerprint] resolution
* using attributes of methods such as the method signature or strings.
*
* @param context The [BytecodeContext] containing the classes to initialize the lookup maps with.
*/
internal fun initializeLookupMaps(context: BytecodeContext) {
if (methods.isNotEmpty()) clearLookupMaps()
context.classes.forEach { classDef ->
classDef.methods.forEach { method ->
val methodClassPair = method to classDef
// For fingerprints with no access or return type specified.
methods += methodClassPair
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
// Add <access><returnType> as the key.
methodSignatureLookupMap.add(accessFlagsReturnKey, methodClassPair)
// Add <access><returnType>[parameters] as the key.
methodSignatureLookupMap.add(
buildString {
append(accessFlagsReturnKey)
appendParameters(method.parameterTypes)
},
methodClassPair
)
// Add strings contained in the method as the key.
method.implementation?.instructions?.forEach instructions@{ instruction ->
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO)
return@instructions
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
methodStringsLookupMap.add(string, methodClassPair)
}
// In the future, the class type could be added to the lookup map.
// This would require MethodFingerprint to be changed to include the class type.
}
}
}
/**
* Clears the internal lookup maps created in [initializeLookupMaps]
*/
internal fun clearLookupMaps() {
methods.clear()
methodSignatureLookupMap.clear()
methodStringsLookupMap.clear()
}
/**
* Appends a string based on the parameter reference types of this method.
*/
internal fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
// Maximum parameters to use in the signature key.
// Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
// To keep the signature map from becoming needlessly bloated,
// group together in the same map entry all methods with the same access/return and 5 or more parameters.
// The value of 5 was chosen based on local performance testing and is not set in stone.
val maxSignatureParameters = 5
// Must append a unique value before the parameters to distinguish this key includes the parameters.
// If this is not appended, then methods with no parameters
// will collide with different keys that specify access/return but omit the parameters.
append("p:")
parameters.forEachIndexed { index, parameter ->
if (index >= maxSignatureParameters) return
append(parameter.first())
}
}
}
}

View File

@@ -0,0 +1,320 @@
package app.revanced.patcher.fingerprint
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.LookupMap.Maps.appendParameters
import app.revanced.patcher.fingerprint.LookupMap.Maps.initializeLookupMaps
import app.revanced.patcher.fingerprint.LookupMap.Maps.methodSignatureLookupMap
import app.revanced.patcher.fingerprint.LookupMap.Maps.methodStringsLookupMap
import app.revanced.patcher.fingerprint.LookupMap.Maps.methods
import app.revanced.patcher.fingerprint.MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.patch.PatchException
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference
/**
* A fingerprint to resolve methods.
*
* @param returnType The method's return type compared using [String.startsWith].
* @param accessFlags The method's exact access flags using values of [AccessFlags].
* @param parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
* @param opcodes An opcode pattern of the method's instructions. Wildcard or unknown opcodes can be specified by `null`.
* @param strings A list of the method's strings compared each using [String.contains].
* @param customFingerprint A custom condition for this fingerprint.
*/
abstract class MethodFingerprint(
internal val returnType: String? = null,
internal val accessFlags: 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, classDef: ClassDef) -> Boolean)? = null
) {
/**
* The result of the [MethodFingerprint].
*/
var result: MethodFingerprintResult? = null
private set
/**
* Resolve a [MethodFingerprint] using the lookup map built by [initializeLookupMaps].
*
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
* amount of time because they are resolved in sequence.
*
* For apps with many fingerprints, resolving performance can be improved by:
* - Slowest: Specify [opcodes] and nothing else.
* - Fast: Specify [accessFlags], [returnType].
* - Faster: Specify [accessFlags], [returnType] and [parameters].
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
*/
internal fun resolveUsingLookupMap(context: BytecodeContext): Boolean {
/**
* Lookup [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
*
* @return A list of [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
*/
fun MethodFingerprint.methodStringsLookup(): LookupMap.MethodClassList? {
strings?.forEach {
val methods = methodStringsLookupMap[it]
if (methods != null) return methods
}
return null
}
/**
* Lookup [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
*
* @return A list of [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
*/
fun MethodFingerprint.methodSignatureLookup(): LookupMap.MethodClassList {
if (accessFlags == null) return methods
var returnTypeValue = returnType
if (returnTypeValue == null) {
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
// Constructors always have void return type
returnTypeValue = "V"
} else {
return methods
}
}
val key = buildString {
append(accessFlags)
append(returnTypeValue.first())
if (parameters != null) appendParameters(parameters)
}
return methodSignatureLookupMap[key] ?: return LookupMap.MethodClassList()
}
/**
* Resolve a [MethodFingerprint] using a list of [MethodClassPair].
*
* @return True if the resolution was successful, false otherwise.
*/
fun MethodFingerprint.resolveUsingMethodClassPair(methodClasses: LookupMap.MethodClassList): Boolean {
methodClasses.forEach { classAndMethod ->
if (resolve(context, classAndMethod.first, classAndMethod.second)) return true
}
return false
}
val methodsWithSameStrings = methodStringsLookup()
if (methodsWithSameStrings != null) if (resolveUsingMethodClassPair(methodsWithSameStrings)) return true
// No strings declared or none matched (partial matches are allowed).
// Use signature matching.
return resolveUsingMethodClassPair(methodSignatureLookup())
}
/**
* 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 resolve(context: BytecodeContext, forClass: ClassDef): Boolean {
for (method in forClass.methods)
if (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 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.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags)
return false
fun parametersEqual(
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
): Boolean {
if (parameters1.count() != parameters2.count()) return false
val iterator1 = parameters1.iterator()
parameters2.forEach {
if (!it.startsWith(iterator1.next())) return false
}
return true
}
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, forClass))
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(StringsScanResult.StringMatch(string, instructionIndex))
stringsList.removeAt(index)
}
if (stringsList.isNotEmpty()) return false
}
)
} else null
val patternScanResult = if (methodFingerprint.opcodes != null) {
method.implementation?.instructions ?: return false
fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.newWarnings(
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
) = buildList {
for ((patternIndex, instructionIndex) in (this@newWarnings.startIndex until this@newWarnings.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
)
)
}
}
fun Method.patternScan(
fingerprint: MethodFingerprint
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
val instructions = this.implementation!!.instructions
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
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.newWarnings(pattern, instructions)
return result
}
}
return null
}
method.patternScan(methodFingerprint) ?: return false
} else null
methodFingerprint.result = MethodFingerprintResult(
method,
forClass,
MethodFingerprintResult.MethodFingerprintScanResult(
patternScanResult,
stringsScanResult
),
context
)
return true
}
companion object {
/**
* Resolve a list of [MethodFingerprint] using the lookup map built by [initializeLookupMaps].
*
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
* amount of time because they are resolved in sequence.
*
* For apps with many fingerprints, resolving performance can be improved by:
* - Slowest: Specify [opcodes] and nothing else.
* - Fast: Specify [accessFlags], [returnType].
* - Faster: Specify [accessFlags], [returnType] and [parameters].
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
*/
internal fun Set<MethodFingerprint>.resolveUsingLookupMap(context: BytecodeContext) {
if (methods.isEmpty()) throw PatchException("lookup map not initialized")
forEach { fingerprint ->
fingerprint.resolveUsingLookupMap(context)
}
}
/**
* 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>) =
forEach { fingerprint ->
for (classDef in classes) {
if (fingerprint.resolve(context, classDef)) break
}
}
}
}

View File

@@ -0,0 +1,94 @@
package app.revanced.patcher.fingerprint
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.util.MethodUtil
/**
* 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.
*/
@Suppress("MemberVisibilityCanBePrivate")
class MethodFingerprintResult(
val method: Method,
val classDef: ClassDef,
val scanResult: MethodFingerprintScanResult,
internal val context: BytecodeContext
) {
/**
* 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)
}
}
/**
* The result of scanning on the [MethodFingerprint].
* @param patternScanResult The result of the pattern scan.
* @param stringsScanResult The result of the string scan.
*/
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.
*/
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.
*/
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].
*/
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.
*/
class Warning(
val correctOpcode: Opcode,
val wrongOpcode: Opcode,
val instructionIndex: Int,
val patternIndex: Int,
)
}
}
}

View File

@@ -1,6 +1,6 @@
package app.revanced.patcher.fingerprint.method.annotation
package app.revanced.patcher.fingerprint.annotation
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.MethodFingerprint
/**
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.

View File

@@ -1,513 +0,0 @@
package app.revanced.patcher.fingerprint.method.impl
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.Fingerprint
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.proxy.ClassProxy
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference
import com.android.tools.smali.dexlib2.util.MethodUtil
import java.util.*
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
private typealias MethodClassPair = Pair<Method, ClassDef>
/**
* A fingerprint to resolve methods.
*
* @param returnType The method's return type compared using [String.startsWith].
* @param accessFlags The method's exact access flags using values of [AccessFlags].
* @param parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
* @param opcodes An opcode pattern of the method's instructions. Wildcard or unknown opcodes can be specified by `null`.
* @param strings A list of the method's strings compared each using [String.contains].
* @param customFingerprint A custom condition for this fingerprint.
*/
abstract class MethodFingerprint(
internal val returnType: String? = null,
internal val accessFlags: 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, classDef: ClassDef) -> Boolean)? = null
) : Fingerprint {
/**
* The result of the [MethodFingerprint].
*/
var result: MethodFingerprintResult? = null
companion object {
/**
* A list of methods and the class they were found in.
*/
private val methods = mutableListOf<MethodClassPair>()
/**
* Lookup map for methods keyed to the methods access flags, return type and parameter.
*/
private val methodSignatureLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
/**
* Lookup map for methods keyed to the strings contained in the method.
*/
private val methodStringsLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
/**
* Appends a string based on the parameter reference types of this method.
*/
private fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
// Maximum parameters to use in the signature key.
// Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
// To keep the signature map from becoming needlessly bloated,
// group together in the same map entry all methods with the same access/return and 5 or more parameters.
// The value of 5 was chosen based on local performance testing and is not set in stone.
val maxSignatureParameters = 5
// Must append a unique value before the parameters to distinguish this key includes the parameters.
// If this is not appended, then methods with no parameters
// will collide with different keys that specify access/return but omit the parameters.
append("p:")
parameters.forEachIndexed { index, parameter ->
if (index >= maxSignatureParameters) return
append(parameter.first())
}
}
/**
* Initializes lookup maps for [MethodFingerprint] resolution
* using attributes of methods such as the method signature or strings.
*
* @param context The [BytecodeContext] containing the classes to initialize the lookup maps with.
*/
internal fun initializeFingerprintResolutionLookupMaps(context: BytecodeContext) {
fun MutableMap<String, MutableList<MethodClassPair>>.add(
key: String,
methodClassPair: MethodClassPair
) {
var methodClassPairs = this[key]
methodClassPairs ?: run {
methodClassPairs = LinkedList<MethodClassPair>().also { this[key] = it }
}
methodClassPairs!!.add(methodClassPair)
}
if (methods.isNotEmpty()) clearFingerprintResolutionLookupMaps()
context.classes.forEach { classDef ->
classDef.methods.forEach { method ->
val methodClassPair = method to classDef
// For fingerprints with no access or return type specified.
methods += methodClassPair
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
// Add <access><returnType> as the key.
methodSignatureLookupMap.add(accessFlagsReturnKey, methodClassPair)
// Add <access><returnType>[parameters] as the key.
methodSignatureLookupMap.add(
buildString {
append(accessFlagsReturnKey)
appendParameters(method.parameterTypes)
},
methodClassPair
)
// Add strings contained in the method as the key.
method.implementation?.instructions?.forEach instructions@{ instruction ->
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO)
return@instructions
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
methodStringsLookupMap.add(string, methodClassPair)
}
// In the future, the class type could be added to the lookup map.
// This would require MethodFingerprint to be changed to include the class type.
}
}
}
/**
* Clears the internal lookup maps created in [initializeFingerprintResolutionLookupMaps]
*/
internal fun clearFingerprintResolutionLookupMaps() {
methods.clear()
methodSignatureLookupMap.clear()
methodStringsLookupMap.clear()
}
/**
* Resolve a list of [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
*
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
* amount of time because they are resolved in sequence.
*
* For apps with many fingerprints, resolving performance can be improved by:
* - Slowest: Specify [opcodes] and nothing else.
* - Fast: Specify [accessFlags], [returnType].
* - Faster: Specify [accessFlags], [returnType] and [parameters].
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
*/
internal fun Iterable<MethodFingerprint>.resolveUsingLookupMap(context: BytecodeContext) {
if (methods.isEmpty()) throw PatchException("lookup map not initialized")
for (fingerprint in this) {
fingerprint.resolveUsingLookupMap(context)
}
}
/**
* Resolve a [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
*
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
* amount of time because they are resolved in sequence.
*
* For apps with many fingerprints, resolving performance can be improved by:
* - Slowest: Specify [opcodes] and nothing else.
* - Fast: Specify [accessFlags], [returnType].
* - Faster: Specify [accessFlags], [returnType] and [parameters].
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
*/
internal fun MethodFingerprint.resolveUsingLookupMap(context: BytecodeContext): Boolean {
/**
* Lookup [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
*
* @return A list of [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
*/
fun MethodFingerprint.methodStringsLookup(): List<MethodClassPair>? {
strings?.forEach {
val methods = methodStringsLookupMap[it]
if (methods != null) return methods
}
return null
}
/**
* Lookup [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
*
* @return A list of [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
*/
fun MethodFingerprint.methodSignatureLookup(): List<MethodClassPair> {
if (accessFlags == null) return methods
var returnTypeValue = returnType
if (returnTypeValue == null) {
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
// Constructors always have void return type
returnTypeValue = "V"
} else {
return methods
}
}
val key = buildString {
append(accessFlags)
append(returnTypeValue.first())
if (parameters != null) appendParameters(parameters)
}
return methodSignatureLookupMap[key] ?: return emptyList()
}
/**
* Resolve a [MethodFingerprint] using a list of [MethodClassPair].
*
* @return True if the resolution was successful, false otherwise.
*/
fun MethodFingerprint.resolveUsingMethodClassPair(classMethods: Iterable<MethodClassPair>): Boolean {
classMethods.forEach { classAndMethod ->
if (resolve(context, classAndMethod.first, classAndMethod.second)) return true
}
return false
}
val methodsWithSameStrings = methodStringsLookup()
if (methodsWithSameStrings != null) if (resolveUsingMethodClassPair(methodsWithSameStrings)) return true
// No strings declared or none matched (partial matches are allowed).
// Use signature matching.
return resolveUsingMethodClassPair(methodSignatureLookup())
}
/**
* 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 MethodFingerprint
if (fingerprint.resolve(context, classDef))
break@classes // ...if the resolution succeeded, continue with the next MethodFingerprint.
}
/**
* 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.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags)
return false
fun parametersEqual(
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
): Boolean {
if (parameters1.count() != parameters2.count()) return false
val iterator1 = parameters1.iterator()
parameters2.forEach {
if (!it.startsWith(iterator1.next())) return false
}
return true
}
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, forClass))
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
fun Method.patternScan(
fingerprint: MethodFingerprint
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
val instructions = this.implementation!!.instructions
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
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
}
method.patternScan(methodFingerprint) ?: return false
} else null
methodFingerprint.result = MethodFingerprintResult(
method,
forClass,
MethodFingerprintResult.MethodFingerprintScanResult(
patternScanResult,
stringsScanResult
),
context
)
return true
}
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
)
)
}
}
}
}
/**
* 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
) {
/**
* 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)
}
}
/**
* 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,
)
}
}
}

View File

@@ -1,9 +0,0 @@
package app.revanced.patcher.logging
@Deprecated("This will be removed in a future release")
interface Logger {
fun error(msg: String) {}
fun warn(msg: String) {}
fun info(msg: String) {}
fun trace(msg: String) {}
}

View File

@@ -1,6 +0,0 @@
package app.revanced.patcher.logging.impl
import app.revanced.patcher.logging.Logger
@Deprecated("This will be removed in a future release")
object NopLogger : Logger

View File

@@ -0,0 +1,13 @@
package app.revanced.patcher.patch
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.fingerprint.MethodFingerprint
/**
* A ReVanced [Patch] that works on [BytecodeContext].
*
* @param fingerprints A list of [MethodFingerprint]s which will be resolved before the patch is executed.
*/
abstract class BytecodePatch(
internal val fingerprints : Set<MethodFingerprint> = emptySet(),
) : Patch<BytecodeContext>()

View File

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

View File

@@ -1,39 +1,107 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package app.revanced.patcher.patch
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.PatchClass
import app.revanced.patcher.Patcher
import app.revanced.patcher.data.Context
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.patch.options.PatchOptions
import java.io.Closeable
typealias PatchClass = Class<out Patch<Context<*>>>
import kotlin.reflect.full.findAnnotation
/**
* A ReVanced patch.
* If an implementation of [Patch] also implements [Closeable]
* it will be closed in reverse execution order of patches executed by ReVanced [Patcher].
*
* If it implements [Closeable], it will be closed after all patches have been executed.
* Closing will be done in reverse execution order.
* @param T The [Context] type this patch will work on.
*/
sealed interface Patch<out T : Context<*>> {
sealed class Patch<out T : Context<*>> {
/**
* The main function of the [Patch] which the patcher will call.
* The name of the patch.
*/
var name: String? = null
private set
/**
* The description of the patch.
*/
var description: String? = null
private set
/**
* The packages the patch is compatible with.
*/
var compatiblePackages: Set<CompatiblePackage>? = null
private set
/**
* Other patches this patch depends on.
*/
var dependencies: Set<PatchClass>? = null
private set
/**
* Weather or not the patch should be used.
*/
var use = true
private set
// TODO: Remove this property, once integrations are coupled with patches.
/**
* Weather or not the patch requires integrations.
*/
var requiresIntegrations = false
private set
/**
* The options of the patch associated by the options key.
*/
val options = PatchOptions()
init {
this::class.findAnnotation<app.revanced.patcher.patch.annotation.Patch>()?.let { annotation ->
name = annotation.name.ifEmpty { null }
description = annotation.description.ifEmpty { null }
compatiblePackages = annotation.compatiblePackages
.map { CompatiblePackage(it.name, it.versions.toSet().ifEmpty { null }) }
.toSet().ifEmpty { null }
dependencies = annotation.dependencies.toSet().ifEmpty { null }
use = annotation.use
requiresIntegrations = annotation.requiresIntegrations
}
}
/**
* The execution function of the patch.
*
* @param context The [Context] the patch will work on.
* @return The result of executing the patch.
*/
fun execute(context: @UnsafeVariance T)
}
abstract fun execute(context: @UnsafeVariance T)
/**
* Resource patch for the Patcher.
*/
interface ResourcePatch : Patch<ResourceContext>
override fun hashCode() = name.hashCode()
/**
* 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>
override fun toString() = name ?: this::class.simpleName ?: "Unnamed patch"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Patch<*>
return name == other.name
}
/**
* A package a [Patch] is compatible with.
*
* @param name The name of the package.
* @param versions The versions of the package.
*/
class CompatiblePackage(
val name: String,
val versions: Set<String>? = null,
)
}

View File

@@ -1,232 +0,0 @@
@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`_
* @param default The default value of the option.
* @param title A human-readable title of the option. Example: _Patch Settings_
* @param description A human-readable description of the option. Example: _Settings for the patches._
* @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
)
}

View File

@@ -3,8 +3,7 @@ package app.revanced.patcher.patch
/**
* A result of executing a [Patch].
*
* @param patchName The name of the [Patch].
* @param patch The [Patch] that was executed.
* @param exception The [PatchException] thrown, if any.
*/
@Suppress("MemberVisibilityCanBePrivate")
class PatchResult internal constructor(val patchName: String, val exception: PatchException? = null)
class PatchResult internal constructor(val patch: Patch<*>, val exception: PatchException? = null)

View File

@@ -0,0 +1,8 @@
package app.revanced.patcher.patch
import app.revanced.patcher.data.ResourceContext
/**
* A ReVanced [Patch] that works on [ResourceContext].
*/
abstract class ResourcePatch : Patch<ResourceContext>()

View File

@@ -0,0 +1,37 @@
package app.revanced.patcher.patch.annotation
import java.lang.annotation.Inherited
import kotlin.reflect.KClass
/**
* Annotation for [app.revanced.patcher.patch.Patch] classes.
*
* @param name The name of the patch. If empty, the patch will be unnamed.
* @param description The description of the patch. If empty, no description will be used.
* @param dependencies The patches this patch depends on.
* @param compatiblePackages The packages this patch is compatible with.
* @param use Whether this patch should be used.
* @param requiresIntegrations Whether this patch requires integrations.
*/
@Target(AnnotationTarget.CLASS)
@Inherited
annotation class Patch(
val name: String = "",
val description: String = "",
val dependencies: Array<KClass<out app.revanced.patcher.patch.Patch<*>>> = [],
val compatiblePackages: Array<CompatiblePackage> = [],
val use: Boolean = true,
// TODO: Remove this property, once integrations are coupled with patches.
val requiresIntegrations: Boolean = false,
)
/**
* A package that a [app.revanced.patcher.patch.Patch] is compatible with.
*
* @param name The name of the package.
* @param versions The versions of the package.
*/
annotation class CompatiblePackage(
val name: String,
val versions: Array<String> = [],
)

View File

@@ -1,27 +0,0 @@
package app.revanced.patcher.patch.annotations
import app.revanced.patcher.data.Context
import app.revanced.patcher.patch.Patch
import kotlin.reflect.KClass
/**
* Annotation to mark a class as a patch.
* @param include If false, the patch should be treated as optional by default.
*/
@Target(AnnotationTarget.CLASS)
annotation class Patch(val include: Boolean = true)
/**
* Annotation for dependencies of [Patch]es.
*/
@Target(AnnotationTarget.CLASS)
annotation class DependsOn(
val dependencies: Array<KClass<out Patch<Context<*>>>> = []
)
// TODO: Remove this annotation, once integrations are coupled with patches.
/**
* Annotation to mark [Patch]es which depend on integrations.
*/
@Target(AnnotationTarget.CLASS)
annotation class RequiresIntegrations

View File

@@ -0,0 +1,364 @@
package app.revanced.patcher.patch.options
import app.revanced.patcher.patch.Patch
import kotlin.reflect.KProperty
/**
* A [Patch] option.
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values identified by their string representation.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param valueType The type of the option value (to handle type erasure).
* @param validator The function to validate the option value.
* @param T The value type of the option.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
open class PatchOption<T>(
val key: String,
val default: T?,
val values: Map<String, T?>?,
val title: String?,
val description: String?,
val required: Boolean,
val valueType: String,
val validator: PatchOption<T>.(T?) -> Boolean
) {
/**
* The value of the [PatchOption].
*/
var value: T?
/**
* Set the value of the [PatchOption].
*
* @param value The value to set.
*
* @throws PatchOptionException.ValueRequiredException If the value is required but null.
* @throws PatchOptionException.ValueValidationException If the value is invalid.
*/
set(value) {
assertRequiredButNotNull(value)
assertValid(value)
uncheckedValue = value
}
/**
* Get the value of the [PatchOption].
*
* @return The value.
*
* @throws PatchOptionException.ValueRequiredException If the value is required but null.
* @throws PatchOptionException.ValueValidationException If the value is invalid.
*/
get() {
assertRequiredButNotNull(uncheckedValue)
assertValid(uncheckedValue)
return uncheckedValue
}
// The unchecked value is used to allow setting the value without validation.
private var uncheckedValue = default
/**
* Reset the [PatchOption] to its default value.
* Override this method if you need to mutate the value instead of replacing it.
*/
open fun reset() {
uncheckedValue = default
}
private fun assertRequiredButNotNull(value: T?) {
if (required && value == null) throw PatchOptionException.ValueRequiredException(this)
}
private fun assertValid(value: T?) {
if (!validator(value)) throw PatchOptionException.ValueValidationException(value, this)
}
override fun toString() = value.toString()
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
this.value = value
}
@Suppress("unused")
companion object PatchExtensions {
/**
* Create a new [PatchOption] with a string value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.stringPatchOption(
key: String,
default: String? = null,
values: Map<String, String?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<String>.(String?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "String", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with an integer value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.intPatchOption(
key: String,
default: Int? = null,
values: Map<String, Int?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Int?>.(Int?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "Int", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a boolean value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.booleanPatchOption(
key: String,
default: Boolean? = null,
values: Map<String, Boolean?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Boolean?>.(Boolean?) -> Boolean = { true }
) = PatchOption(key, default, values, title, description, required, "Boolean", validator).also {
registerOption(it)
}
/**
* Create a new [PatchOption] with a float value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.floatPatchOption(
key: String,
default: Float? = null,
values: Map<String, Float?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Float?>.(Float?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "Float", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a long value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.longPatchOption(
key: String,
default: Long? = null,
values: Map<String, Long?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Long?>.(Long?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "Long", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a string array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.stringArrayPatchOption(
key: String,
default: Array<String>? = null,
values: Map<String, Array<String>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<String>?>.(Array<String>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "StringArray", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with an integer array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.intArrayPatchOption(
key: String,
default: Array<Int>? = null,
values: Map<String, Array<Int>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Int>?>.(Array<Int>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "IntArray", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a boolean array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.booleanArrayPatchOption(
key: String,
default: Array<Boolean>? = null,
values: Map<String, Array<Boolean>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Boolean>?>.(Array<Boolean>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "BooleanArray", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a float array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.floatArrayPatchOption(
key: String,
default: Array<Float>? = null,
values: Map<String, Array<Float>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Float>?>.(Array<Float>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "FloatArray", validator
).also { registerOption(it) }
/**
* Create a new [PatchOption] with a long array value and add it to the current [Patch].
*
* @param key The identifier.
* @param default The default value.
* @param values The set of guaranteed valid values.
* @param title The title.
* @param description A description.
* @param required Whether the option is required.
* @param validator The function to validate the option value.
*
* @return The created [PatchOption].
*
* @see PatchOption
*/
fun <P : Patch<*>> P.longArrayPatchOption(
key: String,
default: Array<Long>? = null,
values: Map<String, Array<Long>?>? = null,
title: String? = null,
description: String? = null,
required: Boolean = false,
validator: PatchOption<Array<Long>?>.(Array<Long>?) -> Boolean = { true }
) = PatchOption(
key, default, values, title, description, required, "LongArray", validator
).also { registerOption(it) }
private fun <P : Patch<*>> P.registerOption(option: PatchOption<*>) = option.also { options.register(it) }
}
}

View File

@@ -0,0 +1,41 @@
package app.revanced.patcher.patch.options
/**
* An exception thrown when using [PatchOption]s.
*
* @param errorMessage The exception message.
*/
sealed class PatchOptionException(errorMessage: String) : Exception(errorMessage, null) {
/**
* An exception thrown when a [PatchOption] is set to an invalid value.
*
* @param invalidType The type of the value that was passed.
* @param expectedType The type of the value that was expected.
*/
class InvalidValueTypeException(invalidType: String, expectedType: String) :
PatchOptionException("Type $expectedType was expected but received type $invalidType")
/**
* An exception thrown when a value did not satisfy the value conditions specified by the [PatchOption].
*
* @param value The value that failed validation.
*/
class ValueValidationException(value: Any?, option: PatchOption<*>) :
PatchOptionException("The option value \"$value\" failed validation for ${option.key}")
/**
* An exception thrown when a value is required but null was passed.
*
* @param option The [PatchOption] that requires a value.
*/
class ValueRequiredException(option: PatchOption<*>) :
PatchOptionException("The option ${option.key} requires a value, but null was passed")
/**
* An exception thrown when a [PatchOption] is not found.
*
* @param key The key of the [PatchOption].
*/
class PatchOptionNotFoundException(key: String)
: PatchOptionException("No option with key $key")
}

View File

@@ -0,0 +1,45 @@
package app.revanced.patcher.patch.options
/**
* A map of [PatchOption]s associated by their keys.
*
* @param options The [PatchOption]s to initialize with.
*/
class PatchOptions internal constructor(
private val options: MutableMap<String, PatchOption<*>> = mutableMapOf()
) : MutableMap<String, PatchOption<*>> by options {
/**
* Register a [PatchOption]. Acts like [MutableMap.put].
* @param value The [PatchOption] to register.
*/
fun register(value: PatchOption<*>) {
options[value.key] = value
}
/**
* Set an option's value.
* @param key The identifier.
* @param value The value.
* @throws PatchOptionException.PatchOptionNotFoundException If the option does not exist.
*/
operator fun <T : Any> set(key: String, value: T?) {
val option = this[key]
try {
@Suppress("UNCHECKED_CAST")
(option as PatchOption<T>).value = value
} catch (e: ClassCastException) {
throw PatchOptionException.InvalidValueTypeException(
value?.let { it::class.java.name } ?: "null",
option.value?.let { it::class.java.name } ?: "null",
)
}
}
/**
* Get an option.
*/
override operator fun get(key: String) =
options[key] ?: throw PatchOptionException.PatchOptionNotFoundException(key)
}

View File

@@ -13,6 +13,7 @@ import com.android.tools.smali.smali.smaliFlexLexer
import com.android.tools.smali.smali.smaliParser
import com.android.tools.smali.smali.smaliTreeWalker
import java.io.InputStreamReader
import java.util.Locale
private const val METHOD_TEMPLATE = """
.class LInlineCompiler;
@@ -33,7 +34,7 @@ class InlineSmaliCompiler {
fun compile(
instructions: String, parameters: String, registers: Int, forStaticMethod: Boolean
): List<BuilderInstruction> {
val input = METHOD_TEMPLATE.format(
val input = METHOD_TEMPLATE.format(Locale.ENGLISH,
if (forStaticMethod) {
"static"
} else {

View File

@@ -19,7 +19,7 @@ import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21s
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import kotlin.test.Test
import kotlin.test.assertEquals
private object InstructionExtensionsTest {

View File

@@ -1,18 +0,0 @@
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

@@ -1,109 +0,0 @@
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.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
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) {
assertNotNull(choice)
}
}
is PatchOption.IntListOption -> {
option.value = option.options.first()
for (choice in option.options) {
assertNotNull(choice)
}
}
}
}
val option = options.get<String>("key1")
// or: val option: String? by options["key1"]
// then you won't need `.value` every time
assertEquals("Hello World", option.value)
options["key1"] = "Hello, world!"
assertEquals("Hello, world!", 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> {
options["key5"].value
}
}
}

View File

@@ -0,0 +1,127 @@
package app.revanced.patcher.patch.options
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringArrayPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import kotlin.test.Test
import kotlin.test.assertNull
import kotlin.test.assertTrue
internal class PatchOptionsTest {
@Test
fun `should not fail because default value is unvalidated`() {
assertDoesNotThrow { OptionsTestPatch.requiredStringOption }
}
@Test
fun `should not allow setting custom value with validation`() {
// Getter validation on incorrect value.
assertThrows<PatchOptionException.ValueValidationException> { OptionsTestPatch.validatedOption }
// Setter validation on incorrect value.
assertThrows<PatchOptionException.ValueValidationException> { OptionsTestPatch.validatedOption = "invalid" }
// Setter validation on correct value.
assertDoesNotThrow { OptionsTestPatch.validatedOption = "valid" }
}
@Test
fun `should throw due to incorrect type`() {
assertThrows<PatchOptionException.InvalidValueTypeException> {
OptionsTestPatch.options["bool"] = "not a boolean"
}
}
@Test
fun `should be nullable`() {
OptionsTestPatch.booleanOption = null
}
@Test
fun `option should not be found`() {
assertThrows<PatchOptionException.PatchOptionNotFoundException> {
OptionsTestPatch.options["this option does not exist"] = 1
}
}
@Test
fun `should be able to add options manually`() {
assertThrows<PatchOptionException.InvalidValueTypeException> {
OptionsTestPatch.options["array"] = OptionsTestPatch.stringArrayOption
}
assertDoesNotThrow {
OptionsTestPatch.options.register(OptionsTestPatch.stringArrayOption)
}
}
@Suppress("UNCHECKED_CAST")
@Test
fun `should allow setting value from values`() =
with(OptionsTestPatch.options["choices"] as PatchOption<String>) {
value = values!!.values.last()
assertTrue(value == "valid")
}
@Test
fun `should allow setting custom value`() =
assertDoesNotThrow { OptionsTestPatch.stringOptionWithChoices = "unknown" }
@Test
fun `should allow resetting value`() = assertDoesNotThrow { OptionsTestPatch.stringOptionWithChoices = null }
@Test
fun `reset should not fail`() {
assertDoesNotThrow {
OptionsTestPatch.resettableOption.value = "test"
OptionsTestPatch.resettableOption.reset()
}
assertThrows<PatchOptionException.ValueRequiredException> {
OptionsTestPatch.resettableOption.value
}
}
@Test
fun `option types should be known`() =
assertTrue(OptionsTestPatch.options["array"].valueType == "StringArray")
@Test
fun `getting default value should work`() =
assertDoesNotThrow { assertNull(OptionsTestPatch.resettableOption.default) }
private object OptionsTestPatch : BytecodePatch() {
var booleanOption by booleanPatchOption(
"bool",
true
)
var requiredStringOption by stringPatchOption(
"required",
"default",
required = true
)
var stringArrayOption = stringArrayPatchOption(
"array",
arrayOf("1", "2")
)
var stringOptionWithChoices by stringPatchOption(
"choices",
"value",
values = mapOf("Valid option value" to "valid")
)
var validatedOption by stringPatchOption(
"validated",
"default"
) { it == "valid" }
var resettableOption = stringPatchOption(
"resettable", null,
required = true
)
override fun execute(context: BytecodeContext) {}
}
}

View File

@@ -0,0 +1,146 @@
package app.revanced.patcher.patch.usage
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
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 com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Format
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction11x
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableFieldReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
import com.android.tools.smali.dexlib2.immutable.value.ImmutableFieldEncodedValue
import com.android.tools.smali.dexlib2.util.Preconditions
import com.google.common.collect.ImmutableList
@Suppress("unused")
@Patch(
name = "Example bytecode patch",
description = "Example demonstration of a bytecode patch.",
dependencies = [ExampleResourcePatch::class],
compatiblePackages = [CompatiblePackage("com.example.examplePackage", arrayOf("0.0.1", "0.0.2"))]
)
object ExampleBytecodePatch : BytecodePatch(setOf(ExampleFingerprint)) {
// Entry point of a patch. Supplied fingerprints are resolved at this point.
override fun execute(context: BytecodeContext) {
ExampleFingerprint.result?.let { result ->
// 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.patternScanResult!!.startIndex
result.mutableMethod.apply {
replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
// Store the fields initial value into the first virtual register.
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.
addInstructionsWithLabels(
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
"""
)
}
// Find the class in which the method matching our fingerprint is defined in.
context.findClass(result.classDef.type)!!.mutableClass.apply {
// Add a new method that returns a string.
methods.add(
ImmutableMethod(
result.classDef.type,
"returnHello",
null,
"Ljava/lang/String;",
AccessFlags.PRIVATE or AccessFlags.STATIC,
null,
null,
ImmutableMethodImplementation(
1,
ImmutableList.of(
BuilderInstruction21c(
Opcode.CONST_STRING,
0,
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
),
BuilderInstruction11x(Opcode.RETURN_OBJECT, 0)
),
null,
null
)
).toMutable()
)
// Add a field in the main class.
// We will use this field in our method below to call println on.
// The field holds the Ljava/io/PrintStream->out; field.
fields.add(
ImmutableField(
type,
"dummyField",
"Ljava/io/PrintStream;",
AccessFlags.PRIVATE or AccessFlags.STATIC,
ImmutableFieldEncodedValue(
ImmutableFieldReference(
"Ljava/lang/System;",
"out",
"Ljava/io/PrintStream;"
)
),
null,
null
).toMutable()
)
}
} ?: throw PatchException("Fingerprint failed to resolve.")
}
/**
* Replace an existing instruction with a new one containing a reference to a new string.
* @param index The index of the instruction to replace.
* @param string The replacement string.
*/
private fun MutableMethod.replaceStringAt(index: Int, string: String) {
val instruction = getInstruction(index)
// Utility method of dexlib2.
Preconditions.checkFormat(instruction.opcode, Format.Format21c)
// Cast this to an instruction of the format 21c.
// The instruction format can be found in the docs at
// https://source.android.com/devices/tech/dalvik/dalvik-bytecode
val strInstruction = instruction as Instruction21c
// In our case we want an instruction with the opcode CONST_STRING
// The format is 21c, so we create a new BuilderInstruction21c
// This instruction will hold the string reference constant in the virtual register of the original instruction
// For that a reference to the string is needed. It can be created with an ImmutableStringReference.
// At last, use the method replaceInstruction to replace it at the given index startIndex.
replaceInstruction(
index,
"const-string ${strInstruction.registerA}, ${ImmutableStringReference(string)}"
)
}
}

View File

@@ -1,7 +1,7 @@
package app.revanced.patcher.usage.bytecode
package app.revanced.patcher.patch.usage
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@@ -12,8 +12,8 @@ object ExampleFingerprint : MethodFingerprint(
listOf("[L"),
listOf(
Opcode.SGET_OBJECT,
null, // Testing unknown opcodes.
Opcode.INVOKE_STATIC, // This is intentionally wrong to test the Fuzzy resolver.
null, // Matching unknown opcodes.
Opcode.INVOKE_STATIC, // This is intentionally wrong to test fuzzy matching.
Opcode.RETURN_VOID
),
null

View File

@@ -1,18 +1,11 @@
package app.revanced.patcher.usage.resource.patch
package app.revanced.patcher.patch.usage
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
import org.w3c.dom.Element
@Patch
@Name("example-resource-patch")
@Description("Example demonstration of a resource patch.")
@ExampleResourceCompatibility
class ExampleResourcePatch : ResourcePatch {
class ExampleResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val element = editor // regular DomFileEditor

View File

@@ -1,13 +0,0 @@
package app.revanced.patcher.usage.bytecode
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility(
[Package(
"com.example.examplePackage", arrayOf("0.0.1", "0.0.2")
)]
)
@Target(AnnotationTarget.CLASS)
internal annotation class ExampleBytecodeCompatibility

View File

@@ -1,190 +0,0 @@
package app.revanced.patcher.usage.bytecode
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.OptionsContainer
import app.revanced.patcher.patch.PatchOption
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch
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 com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Format
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction11x
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableFieldReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
import com.android.tools.smali.dexlib2.immutable.value.ImmutableFieldEncodedValue
import com.android.tools.smali.dexlib2.util.Preconditions
import com.google.common.collect.ImmutableList
@Patch
@Name("example-bytecode-patch")
@Description("Example demonstration of a bytecode patch.")
@ExampleResourceCompatibility
@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(context: BytecodeContext) {
// 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 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.patternScanResult!!.startIndex
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
// 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.classDef.type,
"returnHello",
null,
"Ljava/lang/String;",
AccessFlags.PRIVATE or AccessFlags.STATIC,
null,
null,
ImmutableMethodImplementation(
1,
ImmutableList.of(
BuilderInstruction21c(
Opcode.CONST_STRING,
0,
ImmutableStringReference("Hello, ReVanced! Adding bytecode.")
),
BuilderInstruction11x(Opcode.RETURN_OBJECT, 0)
),
null,
null
)
).toMutable()
)
// Add a field in the main class
// We will use this field in our method below to call println on
// The field holds the Ljava/io/PrintStream->out; field
mainClass.fields.add(
ImmutableField(
mainClass.type,
"dummyField",
"Ljava/io/PrintStream;",
AccessFlags.PRIVATE or AccessFlags.STATIC,
ImmutableFieldEncodedValue(
ImmutableFieldReference(
"Ljava/lang/System;",
"out",
"Ljava/io/PrintStream;"
)
),
null,
null
).toMutable()
)
// store the fields initial value into the first virtual register
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.
method.addInstructionsWithLabels(
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
"""
)
}
/**
* Replace the string for an instruction at the given index with a new one.
* @param index The index of the instruction to replace the string for
* @param string The replacing string
*/
private fun MutableMethodImplementation.replaceStringAt(index: Int, string: String) {
val instruction = this.instructions[index]
// Utility method of dexlib2
Preconditions.checkFormat(instruction.opcode, Format.Format21c)
// Cast this to an instruction of the format 21c
// The instruction format can be found in the docs at
// https://source.android.com/devices/tech/dalvik/dalvik-bytecode
val strInstruction = instruction as Instruction21c
// In our case we want an instruction with the opcode CONST_STRING
// The format is 21c, so we create a new BuilderInstruction21c
// This instruction will hold the string reference constant in the virtual register of the original instruction
// For that a reference to the string is needed. It can be created with an ImmutableStringReference.
// At last, use the method replaceInstruction to replace it at the given index startIndex.
this.replaceInstruction(
index,
BuilderInstruction21c(
Opcode.CONST_STRING,
strInstruction.registerA,
ImmutableStringReference(string)
)
)
}
@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
)
)
}
}

View File

@@ -1,13 +0,0 @@
package app.revanced.patcher.usage.resource.annotation
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility(
[Package(
"com.example.examplePackage", arrayOf("0.0.1", "0.0.2")
)]
)
@Target(AnnotationTarget.CLASS)
internal annotation class ExampleResourceCompatibility