mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-18 00:43:56 +00:00
Compare commits
17 Commits
dev
...
v20.0.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ce772a597 | ||
|
|
3f9cbd2408 | ||
|
|
1ff0830249 | ||
|
|
7be131d348 | ||
|
|
1d78d690bb | ||
|
|
042f554d75 | ||
|
|
f63302feab | ||
|
|
1614d7f9f4 | ||
|
|
d64776c933 | ||
|
|
03cd9f7f54 | ||
|
|
b69226dd26 | ||
|
|
8e1117ed3f | ||
|
|
29adcd5aad | ||
|
|
6b2bc5ef4d | ||
|
|
d862d61386 | ||
|
|
a4e18334bc | ||
|
|
5bef74adb5 |
4
.github/workflows/build_pull_request.yml
vendored
4
.github/workflows/build_pull_request.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: burrunan/gradle-cache-action@v3
|
uses: burrunan/gradle-cache-action@v1
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.github/workflows/open_pull_request.yml
vendored
2
.github/workflows/open_pull_request.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Open pull request
|
- name: Open pull request
|
||||||
uses: repo-sync/pull-request@v2
|
uses: repo-sync/pull-request@v2
|
||||||
|
|||||||
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
@@ -10,13 +10,10 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
packages: write
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Make sure the release step uses its own credentials:
|
# Make sure the release step uses its own credentials:
|
||||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||||
@@ -24,7 +21,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: burrunan/gradle-cache-action@v3
|
uses: burrunan/gradle-cache-action@v1
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
@@ -32,7 +29,7 @@ jobs:
|
|||||||
run: ./gradlew build clean
|
run: ./gradlew build clean
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
@@ -49,5 +46,5 @@ jobs:
|
|||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||||
run: npm exec semantic-release
|
run: npm exec semantic-release
|
||||||
|
|||||||
@@ -23,8 +23,7 @@
|
|||||||
"assets": [
|
"assets": [
|
||||||
"CHANGELOG.md",
|
"CHANGELOG.md",
|
||||||
"gradle.properties"
|
"gradle.properties"
|
||||||
],
|
]
|
||||||
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|||||||
215
CHANGELOG.md
215
CHANGELOG.md
@@ -1,218 +1,3 @@
|
|||||||
# [21.1.0-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.4...v21.1.0-dev.5) (2025-10-16)
|
|
||||||
|
|
||||||
# [21.1.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.3...v21.1.0-dev.4) (2025-07-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Correctly save XML files in UTF-8 by using a bufferedWriter ([#356](https://github.com/ReVanced/revanced-patcher/issues/356)) ([33fadcb](https://github.com/ReVanced/revanced-patcher/commit/33fadcbd0c7076b848bdca4d62a9c684d5781232))
|
|
||||||
|
|
||||||
# [21.1.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.2...v21.1.0-dev.3) (2025-06-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Encode XML files as UTF-8 to fix compilation of resources ([#339](https://github.com/ReVanced/revanced-patcher/issues/339)) ([4f2ef3c](https://github.com/ReVanced/revanced-patcher/commit/4f2ef3c47cea76a26c464cfb45d4bb57fe7198b5))
|
|
||||||
|
|
||||||
# [21.1.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.1.0-dev.1...v21.1.0-dev.2) (2025-06-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Add back missing log by naming logger correctly ([#332](https://github.com/ReVanced/revanced-patcher/issues/332)) ([e4e66b0](https://github.com/ReVanced/revanced-patcher/commit/e4e66b0d8bb0986b79fb150b9c15da35b8e11561))
|
|
||||||
* Support UTF-8 chars when compiling instructions in Smali in non UTF-8 environments ([#331](https://github.com/ReVanced/revanced-patcher/issues/331)) ([bb8771b](https://github.com/ReVanced/revanced-patcher/commit/bb8771bb8b8ab1724d957e56f4de88c02684d87b))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Use option name as key for simplicity and consistency ([754b02e](https://github.com/ReVanced/revanced-patcher/commit/754b02e4ca66ec10764d5205c6643f2d86d0c6a2))
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* Use a buffered writer to reduce IO overhead ([#347](https://github.com/ReVanced/revanced-patcher/issues/347)) ([99f4318](https://github.com/ReVanced/revanced-patcher/commit/99f431897eb9e607987fd5d09b879d7eda442f3e))
|
|
||||||
|
|
||||||
# [21.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0...v21.1.0-dev.1) (2024-12-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Add identity hash code to unnamed patches ([88a3252](https://github.com/ReVanced/revanced-patcher/commit/88a325257494939a79fb30dd51d60c5c52546755))
|
|
||||||
|
|
||||||
# [21.0.0](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0) (2024-11-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Match fingerprint before delegating the match property ([5d996de](https://github.com/ReVanced/revanced-patcher/commit/5d996def4d3de4e2bfc34562e5a6c7d89a8cddf0))
|
|
||||||
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](https://github.com/ReVanced/revanced-patcher/commit/aa472eb9857145b53b49f843406a9764fbb7e5ce))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](https://github.com/ReVanced/revanced-patcher/commit/0abf1c6c0279708fdef5cb66b141d07d17682693))
|
|
||||||
* Improve various APIs ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](https://github.com/ReVanced/revanced-patcher/commit/b8249789df8b90129f7b7ad0e523a8d0ceaab848))
|
|
||||||
* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](https://github.com/ReVanced/revanced-patcher/commit/0746c22743a9561bae2284d234b151f2f8511ca5))
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* Use smallest lookup map for strings ([1358d3f](https://github.com/ReVanced/revanced-patcher/commit/1358d3fa10cb8ba011b6b89cfe3684ecf9849d2f))
|
|
||||||
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* Various APIs have been changed.
|
|
||||||
* Many APIs have been changed.
|
|
||||||
|
|
||||||
# [21.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.3...v21.0.0-dev.4) (2024-11-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* Use smallest lookup map for strings ([1358d3f](https://github.com/ReVanced/revanced-patcher/commit/1358d3fa10cb8ba011b6b89cfe3684ecf9849d2f))
|
|
||||||
|
|
||||||
# [21.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.2...v21.0.0-dev.3) (2024-11-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Move fingerprint match members to fingerprint for ease of access by using context receivers ([0746c22](https://github.com/ReVanced/revanced-patcher/commit/0746c22743a9561bae2284d234b151f2f8511ca5))
|
|
||||||
|
|
||||||
# [21.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v21.0.0-dev.1...v21.0.0-dev.2) (2024-11-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Match fingerprint before delegating the match property ([5d996de](https://github.com/ReVanced/revanced-patcher/commit/5d996def4d3de4e2bfc34562e5a6c7d89a8cddf0))
|
|
||||||
|
|
||||||
# [21.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.2...v21.0.0-dev.1) (2024-10-27)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Merge extension only when patch executes ([#315](https://github.com/ReVanced/revanced-patcher/issues/315)) ([aa472eb](https://github.com/ReVanced/revanced-patcher/commit/aa472eb9857145b53b49f843406a9764fbb7e5ce))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Improve Fingerprint API ([#316](https://github.com/ReVanced/revanced-patcher/issues/316)) ([0abf1c6](https://github.com/ReVanced/revanced-patcher/commit/0abf1c6c0279708fdef5cb66b141d07d17682693))
|
|
||||||
* Improve various APIs ([#317](https://github.com/ReVanced/revanced-patcher/issues/317)) ([b824978](https://github.com/ReVanced/revanced-patcher/commit/b8249789df8b90129f7b7ad0e523a8d0ceaab848))
|
|
||||||
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* Various APIs have been changed.
|
|
||||||
* Many APIs have been changed.
|
|
||||||
|
|
||||||
## [20.0.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2) (2024-10-17)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Make it work on Android 12 and lower by using existing APIs ([#312](https://github.com/ReVanced/revanced-patcher/issues/312)) ([a44802e](https://github.com/ReVanced/revanced-patcher/commit/a44802ef4ebf59ae47213854ba761c81dadc51f3))
|
|
||||||
|
|
||||||
## [20.0.2-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1...v20.0.2-dev.1) (2024-10-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Make it work on Android 12 and lower by using existing APIs ([#312](https://github.com/ReVanced/revanced-patcher/issues/312)) ([a44802e](https://github.com/ReVanced/revanced-patcher/commit/a44802ef4ebf59ae47213854ba761c81dadc51f3))
|
|
||||||
|
|
||||||
## [20.0.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0...v20.0.1) (2024-10-13)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Check for class type exactly instead of with contains ([#310](https://github.com/ReVanced/revanced-patcher/issues/310)) ([69f2f20](https://github.com/ReVanced/revanced-patcher/commit/69f2f20fd99162f91cd9c531dfe47d00d3152ead))
|
|
||||||
* Make it work on Android by not using APIs from JVM unavailable to Android. ([2be6e97](https://github.com/ReVanced/revanced-patcher/commit/2be6e97817437f40e17893dfff3bea2cd4c3ff9e))
|
|
||||||
* Use non-nullable type for options ([ea6fc70](https://github.com/ReVanced/revanced-patcher/commit/ea6fc70caab055251ad4d0d3f1b5cf53865abb85))
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* Free memory earlier and remove negligible lookup maps ([d53aacd](https://github.com/ReVanced/revanced-patcher/commit/d53aacdad4ed3750ddae526fb307577ea36e6171))
|
|
||||||
|
|
||||||
## [20.0.1-dev.5](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.4...v20.0.1-dev.5) (2024-10-11)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Use non-nullable type for options ([ea6fc70](https://github.com/ReVanced/revanced-patcher/commit/ea6fc70caab055251ad4d0d3f1b5cf53865abb85))
|
|
||||||
|
|
||||||
## [20.0.1-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.3...v20.0.1-dev.4) (2024-10-07)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Make it work on Android by not using APIs from JVM unavailable to Android. ([2be6e97](https://github.com/ReVanced/revanced-patcher/commit/2be6e97817437f40e17893dfff3bea2cd4c3ff9e))
|
|
||||||
|
|
||||||
## [20.0.1-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.2...v20.0.1-dev.3) (2024-10-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* Free memory earlier and remove negligible lookup maps ([d53aacd](https://github.com/ReVanced/revanced-patcher/commit/d53aacdad4ed3750ddae526fb307577ea36e6171))
|
|
||||||
|
|
||||||
## [20.0.1-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.1-dev.1...v20.0.1-dev.2) (2024-10-01)
|
|
||||||
|
|
||||||
## [20.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0...v20.0.1-dev.1) (2024-09-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Check for class type exactly instead of with contains ([#310](https://github.com/ReVanced/revanced-patcher/issues/310)) ([69f2f20](https://github.com/ReVanced/revanced-patcher/commit/69f2f20fd99162f91cd9c531dfe47d00d3152ead))
|
|
||||||
|
|
||||||
# [20.0.0](https://github.com/ReVanced/revanced-patcher/compare/v19.3.1...v20.0.0) (2024-08-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Downgrade smali to fix dex compilation issue ([5227e98](https://github.com/ReVanced/revanced-patcher/commit/5227e98abfaa2ff1204eb20a0f2671f58c489930))
|
|
||||||
* Improve exception message wording ([5481d0c](https://github.com/ReVanced/revanced-patcher/commit/5481d0c54ccecc91cd8d15af1ba2d3285a33e5ab))
|
|
||||||
* Make constructor internal as supposed ([7f44174](https://github.com/ReVanced/revanced-patcher/commit/7f44174d91f0af0d50a83d80a7103c779241e094))
|
|
||||||
* Merge all extensions before initializing lookup maps ([8c4dd5b](https://github.com/ReVanced/revanced-patcher/commit/8c4dd5b3a309077fa9a3827b4931fc28b0517809))
|
|
||||||
* Use null for compatible package version when adding packages only ([736b3ee](https://github.com/ReVanced/revanced-patcher/commit/736b3eebbfdd7279b8d5fcfc5c46c9e3aadbee12))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Add ability to create options outside of a patch ([d310246](https://github.com/ReVanced/revanced-patcher/commit/d310246852504b08a15f6376bbf25ac7c6fae76f))
|
|
||||||
* Convert APIs to Kotlin DSL ([#298](https://github.com/ReVanced/revanced-patcher/issues/298)) ([11a911d](https://github.com/ReVanced/revanced-patcher/commit/11a911dc674eb0801649949dd3f28dfeb00efe97))
|
|
||||||
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* Various old APIs are removed, and DSL APIs are added instead.
|
|
||||||
|
|
||||||
# [20.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0-dev.3...v20.0.0-dev.4) (2024-08-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Improve exception message wording ([bd434ce](https://github.com/ReVanced/revanced-patcher/commit/bd434ceb3394d1d5292e8b94e5bfd6da0e4e9c72))
|
|
||||||
|
|
||||||
# [20.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0-dev.2...v20.0.0-dev.3) (2024-08-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Make constructor internal as supposed ([e95fcd1](https://github.com/ReVanced/revanced-patcher/commit/e95fcd1c0b641164bbf0840ec7e562aeb3bacc3e))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Add ability to create options outside of a patch ([b8d763a](https://github.com/ReVanced/revanced-patcher/commit/b8d763a66e0601627dd71c8c24247726aa300146))
|
|
||||||
|
|
||||||
# [20.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v20.0.0-dev.1...v20.0.0-dev.2) (2024-07-31)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Downgrade smali to fix dex compilation issue ([714447d](https://github.com/ReVanced/revanced-patcher/commit/714447de70096bf736e8e1d31c14bb5f24195070))
|
|
||||||
* Merge all extensions before initializing lookup maps ([328aa87](https://github.com/ReVanced/revanced-patcher/commit/328aa876d8ed7826be3713754b6404195e9fe84b))
|
|
||||||
* Use null for compatible package version when adding packages only ([a8e8fa4](https://github.com/ReVanced/revanced-patcher/commit/a8e8fa4093deb8cffbd7a582409f41867f6b568b))
|
|
||||||
|
|
||||||
# [20.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v19.3.1...v20.0.0-dev.1) (2024-07-22)
|
# [20.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v19.3.1...v20.0.0-dev.1) (2024-07-22)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -117,9 +117,9 @@ you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced
|
|||||||
The documentation contains the fundamentals of ReVanced Patcher and how to use ReVanced Patcher to create patches.
|
The documentation contains the fundamentals of ReVanced Patcher and how to use ReVanced Patcher to create patches.
|
||||||
You can find it [here](https://github.com/ReVanced/revanced-patcher/tree/main/docs).
|
You can find it [here](https://github.com/ReVanced/revanced-patcher/tree/main/docs).
|
||||||
|
|
||||||
## 📜 License
|
## 📜 Licence
|
||||||
|
|
||||||
ReVanced Patcher is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
|
ReVanced Patcher is licensed under the GPLv3 license. Please see the [licence file](LICENSE) for more information.
|
||||||
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patcher as long as you track changes/dates in source files.
|
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patcher as long as you track changes/dates in source files.
|
||||||
Any modifications to ReVanced Patcher must also be made available under the GPL,
|
Any modifications to ReVanced Patcher must also be made available under the GPL,
|
||||||
along with build & install instructions.
|
along with build & install instructions.
|
||||||
|
|||||||
@@ -1,22 +1,7 @@
|
|||||||
public final class app/revanced/patcher/Fingerprint {
|
public final class app/revanced/patcher/Fingerprint {
|
||||||
public final fun getClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
public final fun getMatch ()Lapp/revanced/patcher/Match;
|
||||||
public final fun getClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
|
||||||
public final fun getMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Z
|
||||||
public final fun getMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
|
||||||
public final fun getOriginalClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
|
||||||
public final fun getOriginalClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
|
||||||
public final fun getOriginalMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
|
|
||||||
public final fun getOriginalMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
|
|
||||||
public final fun getPatternMatch (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
|
|
||||||
public final fun getPatternMatchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
|
|
||||||
public final fun getStringMatches (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
|
|
||||||
public final fun getStringMatchesOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
|
|
||||||
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
|
|
||||||
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
|
|
||||||
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
|
|
||||||
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
|
|
||||||
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
|
|
||||||
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/FingerprintBuilder {
|
public final class app/revanced/patcher/FingerprintBuilder {
|
||||||
@@ -33,27 +18,32 @@ public final class app/revanced/patcher/FingerprintBuilder {
|
|||||||
|
|
||||||
public final class app/revanced/patcher/FingerprintKt {
|
public final class app/revanced/patcher/FingerprintKt {
|
||||||
public static final fun fingerprint (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/Fingerprint;
|
public static final fun fingerprint (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/Fingerprint;
|
||||||
|
public static final fun fingerprint (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint;
|
||||||
public static synthetic fun fingerprint$default (ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/Fingerprint;
|
public static synthetic fun fingerprint$default (ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/Fingerprint;
|
||||||
|
public static synthetic fun fingerprint$default (Lapp/revanced/patcher/patch/BytecodePatchBuilder;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract interface annotation class app/revanced/patcher/InternalApi : java/lang/annotation/Annotation {
|
public abstract interface annotation class app/revanced/patcher/InternalApi : java/lang/annotation/Annotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/Match {
|
public final class app/revanced/patcher/Match {
|
||||||
public final fun getClassDef ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lapp/revanced/patcher/Match$PatternMatch;Ljava/util/List;Lapp/revanced/patcher/patch/BytecodePatchContext;)V
|
||||||
public final fun getMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
public final fun getClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||||
public final fun getOriginalClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
|
||||||
public final fun getOriginalMethod ()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 getPatternMatch ()Lapp/revanced/patcher/Match$PatternMatch;
|
public final fun getPatternMatch ()Lapp/revanced/patcher/Match$PatternMatch;
|
||||||
public final fun getStringMatches ()Ljava/util/List;
|
public final fun getStringMatches ()Ljava/util/List;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/Match$PatternMatch {
|
public final class app/revanced/patcher/Match$PatternMatch {
|
||||||
|
public fun <init> (II)V
|
||||||
public final fun getEndIndex ()I
|
public final fun getEndIndex ()I
|
||||||
public final fun getStartIndex ()I
|
public final fun getStartIndex ()I
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/Match$StringMatch {
|
public final class app/revanced/patcher/Match$StringMatch {
|
||||||
|
public fun <init> (Ljava/lang/String;I)V
|
||||||
public final fun getIndex ()I
|
public final fun getIndex ()I
|
||||||
public final fun getString ()Ljava/lang/String;
|
public final fun getString ()Ljava/lang/String;
|
||||||
}
|
}
|
||||||
@@ -73,14 +63,11 @@ public final class app/revanced/patcher/Patcher : java/io/Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/PatcherConfig {
|
public final class app/revanced/patcher/PatcherConfig {
|
||||||
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;)V
|
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/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
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 final class app/revanced/patcher/PatcherContext : java/io/Closeable {
|
public final class app/revanced/patcher/PatcherContext {
|
||||||
public fun close ()V
|
|
||||||
public final fun getPackageMetadata ()Lapp/revanced/patcher/PackageMetadata;
|
public final fun getPackageMetadata ()Lapp/revanced/patcher/PackageMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,36 +134,40 @@ public final class app/revanced/patcher/extensions/InstructionExtensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch {
|
public final class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch {
|
||||||
public final fun getExtensionInputStream ()Ljava/util/function/Supplier;
|
public final fun getExtension ()Ljava/io/InputStream;
|
||||||
|
public final fun getFingerprints ()Ljava/util/Set;
|
||||||
public fun toString ()Ljava/lang/String;
|
public fun toString ()Ljava/lang/String;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/patch/BytecodePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
|
public final class app/revanced/patcher/patch/BytecodePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
|
||||||
public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch;
|
public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch;
|
||||||
public final fun extendWith (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatchBuilder;
|
public final fun extendWith (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatchBuilder;
|
||||||
public final fun getExtensionInputStream ()Ljava/util/function/Supplier;
|
public final fun getExtension ()Ljava/io/InputStream;
|
||||||
public final fun setExtensionInputStream (Ljava/util/function/Supplier;)V
|
public final fun invoke (Lapp/revanced/patcher/Fingerprint;)Lapp/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint;
|
||||||
|
public final fun setExtension (Ljava/io/InputStream;)V
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/patch/BytecodePatchContext : app/revanced/patcher/patch/PatchContext, java/io/Closeable {
|
public final class app/revanced/patcher/patch/BytecodePatchBuilder$InvokedFingerprint {
|
||||||
|
public fun <init> (Lapp/revanced/patcher/Fingerprint;)V
|
||||||
|
public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/Match;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/BytecodePatchContext : app/revanced/patcher/patch/PatchContext {
|
||||||
public final fun classBy (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
public final fun classBy (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
||||||
public fun close ()V
|
public final fun classByType (Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
||||||
public synthetic fun get ()Ljava/lang/Object;
|
public synthetic fun get ()Ljava/lang/Object;
|
||||||
public fun get ()Ljava/util/Set;
|
public fun get ()Ljava/util/Set;
|
||||||
public final fun getClasses ()Lapp/revanced/patcher/util/ProxyClassList;
|
public final fun getClasses ()Lapp/revanced/patcher/util/ProxyClassList;
|
||||||
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)Lapp/revanced/patcher/util/MethodNavigator;
|
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/MethodNavigator;
|
||||||
public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/patch/Option {
|
public final class app/revanced/patcher/patch/Option {
|
||||||
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
|
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
|
||||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
|
|
||||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
|
||||||
public final fun getDefault ()Ljava/lang/Object;
|
public final fun getDefault ()Ljava/lang/Object;
|
||||||
public final fun getDescription ()Ljava/lang/String;
|
public final fun getDescription ()Ljava/lang/String;
|
||||||
public final fun getKey ()Ljava/lang/String;
|
public final fun getKey ()Ljava/lang/String;
|
||||||
public final fun getName ()Ljava/lang/String;
|
|
||||||
public final fun getRequired ()Z
|
public final fun getRequired ()Z
|
||||||
public final fun getTitle ()Ljava/lang/String;
|
public final fun getTitle ()Ljava/lang/String;
|
||||||
public final fun getType ()Lkotlin/reflect/KType;
|
public final fun getType ()Lkotlin/reflect/KType;
|
||||||
@@ -212,43 +203,25 @@ public final class app/revanced/patcher/patch/OptionException$ValueValidationExc
|
|||||||
|
|
||||||
public final class app/revanced/patcher/patch/OptionKt {
|
public final class app/revanced/patcher/patch/OptionKt {
|
||||||
public static final fun booleanOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun booleanOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun booleanOption (Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static synthetic fun booleanOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
public static synthetic fun booleanOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
||||||
public static synthetic fun booleanOption$default (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/Option;
|
|
||||||
public static final fun booleansOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun booleansOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun booleansOption (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static synthetic fun booleansOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
public static synthetic fun booleansOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static synthetic fun booleansOption$default (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static final fun floatOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun floatOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun floatOption (Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static synthetic fun floatOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
public static synthetic fun floatOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
||||||
public static synthetic fun floatOption$default (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/Option;
|
|
||||||
public static final fun floatsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun floatsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static synthetic fun floatsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
public static synthetic fun floatsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun intOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun intOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun intOption (Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static synthetic fun intOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
public static synthetic fun intOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
||||||
public static synthetic fun intOption$default (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/Option;
|
|
||||||
public static final fun intsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun intsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun intsOption (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static synthetic fun intsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
public static synthetic fun intsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static synthetic fun intsOption$default (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static final fun longOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun longOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun longOption (Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static synthetic fun longOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
public static synthetic fun longOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
||||||
public static synthetic fun longOption$default (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/Option;
|
|
||||||
public static final fun longsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun longsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun longsOption (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static synthetic fun longsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
public static synthetic fun longsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static synthetic fun longsOption$default (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static final fun stringOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun stringOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun stringOption (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static synthetic fun stringOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
public static synthetic fun stringOption$default (Lapp/revanced/patcher/patch/PatchBuilder;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/Option;
|
||||||
public static synthetic fun stringOption$default (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/Option;
|
|
||||||
public static final fun stringsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
public static final fun stringsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static final fun stringsOption (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
public static synthetic fun stringsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
public static synthetic fun stringsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||||
public static synthetic fun stringsOption$default (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/patch/Options : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker {
|
public final class app/revanced/patcher/patch/Options : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker {
|
||||||
@@ -294,7 +267,7 @@ public final class app/revanced/patcher/patch/Options : java/util/Map, kotlin/jv
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract class app/revanced/patcher/patch/Patch {
|
public abstract class app/revanced/patcher/patch/Patch {
|
||||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Set;Ljava/util/Set;Ljava/util/Set;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Set;Ljava/util/Set;Ljava/util/Set;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
public final fun execute (Lapp/revanced/patcher/patch/PatchContext;)V
|
public final fun execute (Lapp/revanced/patcher/patch/PatchContext;)V
|
||||||
public final fun finalize (Lapp/revanced/patcher/patch/PatchContext;)V
|
public final fun finalize (Lapp/revanced/patcher/patch/PatchContext;)V
|
||||||
public final fun getCompatiblePackages ()Ljava/util/Set;
|
public final fun getCompatiblePackages ()Ljava/util/Set;
|
||||||
@@ -311,13 +284,13 @@ public abstract class app/revanced/patcher/patch/PatchBuilder {
|
|||||||
public final fun compatibleWith ([Ljava/lang/String;)V
|
public final fun compatibleWith ([Ljava/lang/String;)V
|
||||||
public final fun compatibleWith ([Lkotlin/Pair;)V
|
public final fun compatibleWith ([Lkotlin/Pair;)V
|
||||||
public final fun dependsOn ([Lapp/revanced/patcher/patch/Patch;)V
|
public final fun dependsOn ([Lapp/revanced/patcher/patch/Patch;)V
|
||||||
public final fun execute (Lkotlin/jvm/functions/Function1;)V
|
public final fun execute (Lkotlin/jvm/functions/Function2;)V
|
||||||
public final fun finalize (Lkotlin/jvm/functions/Function1;)V
|
public final fun finalize (Lkotlin/jvm/functions/Function2;)V
|
||||||
protected final fun getCompatiblePackages ()Ljava/util/Set;
|
protected final fun getCompatiblePackages ()Ljava/util/Set;
|
||||||
protected final fun getDependencies ()Ljava/util/Set;
|
protected final fun getDependencies ()Ljava/util/Set;
|
||||||
protected final fun getDescription ()Ljava/lang/String;
|
protected final fun getDescription ()Ljava/lang/String;
|
||||||
protected final fun getExecutionBlock ()Lkotlin/jvm/functions/Function1;
|
protected final fun getExecutionBlock ()Lkotlin/jvm/functions/Function2;
|
||||||
protected final fun getFinalizeBlock ()Lkotlin/jvm/functions/Function1;
|
protected final fun getFinalizeBlock ()Lkotlin/jvm/functions/Function2;
|
||||||
protected final fun getName ()Ljava/lang/String;
|
protected final fun getName ()Ljava/lang/String;
|
||||||
protected final fun getOptions ()Ljava/util/Set;
|
protected final fun getOptions ()Ljava/util/Set;
|
||||||
protected final fun getUse ()Z
|
protected final fun getUse ()Z
|
||||||
@@ -325,8 +298,8 @@ public abstract class app/revanced/patcher/patch/PatchBuilder {
|
|||||||
public final fun invoke (Ljava/lang/String;[Ljava/lang/String;)Lkotlin/Pair;
|
public final fun invoke (Ljava/lang/String;[Ljava/lang/String;)Lkotlin/Pair;
|
||||||
protected final fun setCompatiblePackages (Ljava/util/Set;)V
|
protected final fun setCompatiblePackages (Ljava/util/Set;)V
|
||||||
protected final fun setDependencies (Ljava/util/Set;)V
|
protected final fun setDependencies (Ljava/util/Set;)V
|
||||||
protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function1;)V
|
protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function2;)V
|
||||||
protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function1;)V
|
protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function2;)V
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract interface class app/revanced/patcher/patch/PatchContext : java/util/function/Supplier {
|
public abstract interface class app/revanced/patcher/patch/PatchContext : java/util/function/Supplier {
|
||||||
@@ -403,13 +376,18 @@ public final class app/revanced/patcher/patch/ResourcePatchBuilder : app/revance
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/patch/ResourcePatchContext : app/revanced/patcher/patch/PatchContext {
|
public final class app/revanced/patcher/patch/ResourcePatchContext : app/revanced/patcher/patch/PatchContext {
|
||||||
public final fun delete (Ljava/lang/String;)Z
|
|
||||||
public final fun document (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
|
|
||||||
public final fun document (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
|
|
||||||
public fun get ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
|
public fun get ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
|
||||||
public synthetic fun get ()Ljava/lang/Object;
|
public synthetic fun get ()Ljava/lang/Object;
|
||||||
public final fun get (Ljava/lang/String;Z)Ljava/io/File;
|
public final fun get (Ljava/lang/String;Z)Ljava/io/File;
|
||||||
public static synthetic fun get$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
|
public static synthetic fun get$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
|
||||||
|
public final fun getDocument ()Lapp/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable;
|
||||||
|
public final fun stageDelete (Lkotlin/jvm/functions/Function1;)Z
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/ResourcePatchContext$DocumentOperatable {
|
||||||
|
public fun <init> (Lapp/revanced/patcher/patch/ResourcePatchContext;)V
|
||||||
|
public final fun get (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
|
||||||
|
public final fun get (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w3c/dom/Document {
|
public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w3c/dom/Document {
|
||||||
@@ -485,12 +463,11 @@ public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/util/MethodNavigator {
|
public final class app/revanced/patcher/util/MethodNavigator {
|
||||||
public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
public final fun at (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
|
||||||
public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
|
public final fun at ([I)Lapp/revanced/patcher/util/MethodNavigator;
|
||||||
public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
public static synthetic fun at$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
|
||||||
public final fun to (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
|
public final fun immutable ()Lcom/android/tools/smali/dexlib2/iface/Method;
|
||||||
public final fun to ([I)Lapp/revanced/patcher/util/MethodNavigator;
|
public final fun mutable ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||||
public static synthetic fun to$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {
|
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {
|
||||||
|
|||||||
@@ -36,28 +36,25 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
implementation(libs.xpp3)
|
||||||
|
implementation(libs.smali)
|
||||||
|
implementation(libs.multidexlib2)
|
||||||
|
implementation(libs.apktool.lib)
|
||||||
|
implementation(libs.kotlin.reflect)
|
||||||
|
|
||||||
// TODO: Convert project to KMP.
|
// TODO: Convert project to KMP.
|
||||||
compileOnly(libs.android) {
|
compileOnly(libs.android) {
|
||||||
// Exclude, otherwise the org.w3c.dom API breaks.
|
// Exclude, otherwise the org.w3c.dom API breaks.
|
||||||
exclude(group = "xerces", module = "xmlParserAPIs")
|
exclude(group = "xerces", module = "xmlParserAPIs")
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation(libs.apktool.lib)
|
|
||||||
implementation(libs.kotlin.reflect)
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
|
||||||
implementation(libs.multidexlib2)
|
|
||||||
implementation(libs.smali)
|
|
||||||
implementation(libs.xpp3)
|
|
||||||
|
|
||||||
testImplementation(libs.mockk)
|
|
||||||
testImplementation(libs.kotlin.test)
|
testImplementation(libs.kotlin.test)
|
||||||
|
testImplementation(libs.mockk)
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget.set(JvmTarget.JVM_11)
|
jvmTarget.set(JvmTarget.JVM_11)
|
||||||
|
|
||||||
freeCompilerArgs = listOf("-Xcontext-receivers")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ val patcherResult = Patcher(PatcherConfig(apkFile = File("some.apk"))).use { pat
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
patcher().collect { patchResult ->
|
patcher().collect { patchResult ->
|
||||||
if (patchResult.exception != null)
|
if (patchResult.exception != null)
|
||||||
logger.info { "\"${patchResult.patch}\" failed:\n${patchResult.exception}" }
|
logger.info("\"${patchResult.patch}\" failed:\n${patchResult.exception}")
|
||||||
else
|
else
|
||||||
logger.info { "\"${patchResult.patch}\" succeeded" }
|
logger.info("\"${patchResult.patch}\" succeeded")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,10 +72,6 @@ To start developing patches with ReVanced Patcher, you must prepare a developmen
|
|||||||
|
|
||||||
Throughout the documentation, [ReVanced Patches](https://github.com/revanced/revanced-patches) will be used as an example project.
|
Throughout the documentation, [ReVanced Patches](https://github.com/revanced/revanced-patches) will be used as an example project.
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> To start a fresh project,
|
|
||||||
> you can use the [ReVanced Patches template](https://github.com/revanced/revanced-patches-template).
|
|
||||||
|
|
||||||
1. Clone the repository
|
1. Clone the repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -60,16 +60,14 @@
|
|||||||
|
|
||||||
# 🔎 Fingerprinting
|
# 🔎 Fingerprinting
|
||||||
|
|
||||||
In the context of ReVanced, a fingerprint is a partial description of a method.
|
In the context of ReVanced, fingerprinting is primarily used to match methods with a limited amount of known information.
|
||||||
It is used to uniquely match a method by its characteristics.
|
|
||||||
Fingerprinting is used to match methods with a limited amount of known information.
|
|
||||||
Methods with obfuscated names that change with each update are primary candidates for fingerprinting.
|
Methods with obfuscated names that change with each update are primary candidates for fingerprinting.
|
||||||
The goal of fingerprinting is to uniquely identify a method by capturing various attributes, such as the return type,
|
The goal of fingerprinting is to uniquely identify a method by capturing various attributes, such as the return type,
|
||||||
access flags, an opcode pattern, strings, and more.
|
access flags, an opcode pattern, strings, and more.
|
||||||
|
|
||||||
## ⛳️ Example fingerprint
|
## ⛳️ Example fingerprint
|
||||||
|
|
||||||
An example fingerprint is shown below:
|
Throughout the documentation, the following example will be used to demonstrate the concepts of fingerprints:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
|
|
||||||
@@ -81,11 +79,11 @@ fingerprint {
|
|||||||
parameters("Z")
|
parameters("Z")
|
||||||
opcodes(Opcode.RETURN)
|
opcodes(Opcode.RETURN)
|
||||||
strings("pro")
|
strings("pro")
|
||||||
custom { (method, classDef) -> classDef == "Lcom/some/app/ads/AdsLoader;" }
|
custom { (method, classDef) -> method.definingClass == "Lcom/some/app/ads/AdsLoader;" }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔎 Reconstructing the original code from the example fingerprint from above
|
## 🔎 Reconstructing the original code from a fingerprint
|
||||||
|
|
||||||
The following code is reconstructed from the fingerprint to understand how a fingerprint is created.
|
The following code is reconstructed from the fingerprint to understand how a fingerprint is created.
|
||||||
|
|
||||||
@@ -109,43 +107,36 @@ The fingerprint contains the following information:
|
|||||||
- Package and class name:
|
- Package and class name:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
custom { (method, classDef) -> classDef == "Lcom/some/app/ads/AdsLoader;" }
|
custom = { (method, classDef) -> method.definingClass == "Lcom/some/app/ads/AdsLoader;"}
|
||||||
```
|
```
|
||||||
|
|
||||||
With this information, the original code can be reconstructed:
|
With this information, the original code can be reconstructed:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
package com.some.app.ads;
|
package com.some.app.ads;
|
||||||
|
|
||||||
<accessFlags>
|
<accessFlags> class AdsLoader {
|
||||||
|
public final boolean <methodName>(boolean <parameter>) {
|
||||||
|
// ...
|
||||||
|
|
||||||
class AdsLoader {
|
var userStatus = "pro";
|
||||||
public final boolean <methodName>(boolean <parameter>)
|
|
||||||
|
|
||||||
{
|
// ...
|
||||||
// ...
|
|
||||||
|
|
||||||
var userStatus = "pro";
|
return <returnValue>;
|
||||||
|
}
|
||||||
// ...
|
|
||||||
|
|
||||||
return <returnValue >;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Using that fingerprint, this method can be matched uniquely from all other methods.
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> A fingerprint should contain information about a method likely to remain the same across updates.
|
> A fingerprint should contain information about a method likely to remain the same across updates.
|
||||||
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated
|
> A method's name is not included in the fingerprint because it will likely change with each update in an obfuscated app.
|
||||||
> app.
|
> In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the same.
|
||||||
> In contrast, the return type, access flags, parameters, patterns of opcodes, and strings are likely to remain the
|
|
||||||
> same.
|
|
||||||
|
|
||||||
## 🔨 How to use fingerprints
|
## 🔨 How to use fingerprints
|
||||||
|
|
||||||
After declaring a fingerprint, it can be used in a patch to find the method it matches to:
|
Fingerprints can be added to a patch by directly creating and adding them or by invoking them manually.
|
||||||
|
Fingerprints added to a patch are matched by ReVanced Patcher before the patch is executed.
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
val fingerprint = fingerprint {
|
val fingerprint = fingerprint {
|
||||||
@@ -153,35 +144,18 @@ val fingerprint = fingerprint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val patch = bytecodePatch {
|
val patch = bytecodePatch {
|
||||||
execute {
|
// Directly create and add a fingerprint.
|
||||||
fingerprint.method
|
fingerprint {
|
||||||
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a fingerprint manually by invoking it.
|
||||||
|
fingerprint()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The fingerprint won't be matched again, if it has already been matched once, for performance reasons.
|
> [!TIP]
|
||||||
This makes it useful, to share fingerprints between multiple patches,
|
> Multiple patches can share fingerprints. If a fingerprint is matched once, it will not be matched again.
|
||||||
and let the first executing patch match the fingerprint:
|
|
||||||
|
|
||||||
```kt
|
|
||||||
// Either of these two patches will match the fingerprint first and the other patch can reuse the match:
|
|
||||||
val mainActivityPatch1 = bytecodePatch {
|
|
||||||
execute {
|
|
||||||
mainActivityOnCreateFingerprint.method
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val mainActivityPatch2 = bytecodePatch {
|
|
||||||
execute {
|
|
||||||
mainActivityOnCreateFingerprint.method
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> If the fingerprint can not be matched to any method,
|
|
||||||
> accessing certain properties of the fingerprint will raise an exception.
|
|
||||||
> Instead, the `orNull` properties can be used to return `null` if no match is found.
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode`
|
> If a fingerprint has an opcode pattern, you can use the `fuzzyPatternScanThreshhold` parameter of the `opcode`
|
||||||
@@ -190,52 +164,72 @@ val mainActivityPatch2 = bytecodePatch {
|
|||||||
>
|
>
|
||||||
> ```kt
|
> ```kt
|
||||||
> fingerprint(fuzzyPatternScanThreshhold = 2) {
|
> fingerprint(fuzzyPatternScanThreshhold = 2) {
|
||||||
> opcodes(
|
> opcodes(
|
||||||
> Opcode.ICONST_0,
|
> Opcode.ICONST_0,
|
||||||
> null,
|
> null,
|
||||||
> Opcode.ICONST_1,
|
> Opcode.ICONST_1,
|
||||||
> Opcode.IRETURN,
|
> Opcode.IRETURN,
|
||||||
> )
|
> )
|
||||||
>}
|
>}
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
The following properties can be accessed in a fingerprint:
|
Once the fingerprint is matched, the match can be used in the patch:
|
||||||
|
|
||||||
- `originalClassDef`: The original class definition the fingerprint matches to.
|
```kt
|
||||||
- `originalClassDefOrNull`: The original class definition the fingerprint matches to.
|
val patch = bytecodePatch {
|
||||||
- `originalMethod`: The original method the fingerprint matches to.
|
// Add a fingerprint and delegate its match to a variable.
|
||||||
- `originalMethodOrNull`: The original method the fingerprint matches to.
|
val match by showAdsFingerprint()
|
||||||
- `classDef`: The class the fingerprint matches to.
|
val match2 by fingerprint {
|
||||||
- `classDefOrNull`: The class the fingerprint matches to.
|
// ...
|
||||||
- `method`: The method the fingerprint matches to. If no match is found, an exception is raised.
|
}
|
||||||
- `methodOrNull`: The method the fingerprint matches to.
|
|
||||||
|
|
||||||
The difference between the `original` and non-`original` properties is that the `original` properties return the
|
execute {
|
||||||
original class or method definition, while the non-`original` properties return a mutable copy of the class or method.
|
val method = match.method
|
||||||
The mutable copies can be modified. They are lazy properties, so they are only computed
|
val method2 = match2.method
|
||||||
and only then will effectively replace the `original` method or class definition when accessed.
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
> [!TIP]
|
> [!WARNING]
|
||||||
> If only read-only access to the class or method is needed,
|
> If the fingerprint can not be matched to any method, the match of a fingerprint is `null`. If such a match is delegated
|
||||||
> the `originalClassDef` and `originalMethod` properties should be used,
|
> to a variable, accessing it will raise an exception.
|
||||||
> to avoid making a mutable copy of the class or method.
|
|
||||||
|
|
||||||
## 🏹 Manually matching fingerprints
|
The match of a fingerprint contains mutable and immutable references to the method and the class it matches to.
|
||||||
|
|
||||||
By default, a fingerprint is matched automatically against all classes
|
```kt
|
||||||
when one of the fingerprint's properties is accessed.
|
class Match(
|
||||||
|
val method: Method,
|
||||||
|
val classDef: ClassDef,
|
||||||
|
val patternMatch: Match.PatternMatch?,
|
||||||
|
val stringMatches: List<Match.StringMatch>?,
|
||||||
|
// ...
|
||||||
|
) {
|
||||||
|
val mutableClass by lazy { /* ... */ }
|
||||||
|
val mutableMethod by lazy { /* ... */ }
|
||||||
|
|
||||||
Instead, the fingerprint can be matched manually using various overloads of a fingerprint's `match` function:
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏹 Manual matching of fingerprints
|
||||||
|
|
||||||
|
Unless a fingerprint is added to a patch, the fingerprint will not be matched automatically by ReVanced Patcher
|
||||||
|
before the patch is executed.
|
||||||
|
Instead, the fingerprint can be matched manually using various overloads of a fingerprint's `match` function.
|
||||||
|
|
||||||
|
You can match a fingerprint the following ways:
|
||||||
|
|
||||||
- In a **list of classes**, if the fingerprint can match in a known subset of classes
|
- In a **list of classes**, if the fingerprint can match in a known subset of classes
|
||||||
|
|
||||||
If you have a known list of classes you know the fingerprint can match in,
|
If you have a known list of classes you know the fingerprint can match in,
|
||||||
you can match the fingerprint on the list of classes:
|
you can match the fingerprint on the list of classes:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
execute {
|
execute { context ->
|
||||||
val match = showAdsFingerprint(classes)
|
val match = showAdsFingerprint.apply {
|
||||||
}
|
match(context, context.classes)
|
||||||
|
}.match ?: throw PatchException("No match found")
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- In a **single class**, if the fingerprint can match in a single known class
|
- In a **single class**, if the fingerprint can match in a single known class
|
||||||
@@ -243,48 +237,37 @@ Instead, the fingerprint can be matched manually using various overloads of a fi
|
|||||||
If you know the fingerprint can match a method in a specific class, you can match the fingerprint in the class:
|
If you know the fingerprint can match a method in a specific class, you can match the fingerprint in the class:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
execute {
|
execute { context ->
|
||||||
val adsLoaderClass = classes.single { it.name == "Lcom/some/app/ads/Loader;" }
|
val adsLoaderClass = context.classes.single { it.name == "Lcom/some/app/ads/Loader;" }
|
||||||
|
|
||||||
val match = showAdsFingerprint.match(adsLoaderClass)
|
val match = showAdsFingerprint.apply {
|
||||||
}
|
match(context, adsLoaderClass)
|
||||||
```
|
}.match ?: throw PatchException("No match found")
|
||||||
|
|
||||||
Another common usecase is to use a fingerprint to reduce the search space of a method to a single class.
|
|
||||||
|
|
||||||
```kt
|
|
||||||
execute {
|
|
||||||
// Match showAdsFingerprint in the class of the ads loader found by adsLoaderClassFingerprint.
|
|
||||||
val match = showAdsFingerprint.match(adsLoaderClassFingerprint.classDef)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Match a **single method**, to extract certain information about it
|
- Match a **single method**, to extract certain information about it
|
||||||
|
|
||||||
The match of a fingerprint contains useful information about the method,
|
The match of a fingerprint contains useful information about the method, such as the start and end index of an opcode pattern
|
||||||
such as the start and end index of an opcode pattern or the indices of the instructions with certain string
|
or the indices of the instructions with certain string references.
|
||||||
references.
|
|
||||||
A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:
|
A fingerprint can be leveraged to extract such information from a method instead of manually figuring it out:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
execute {
|
execute { context ->
|
||||||
val currentPlanFingerprint = fingerprint {
|
val proStringsFingerprint = fingerprint {
|
||||||
strings("free", "trial")
|
strings("free", "trial")
|
||||||
}
|
|
||||||
|
|
||||||
currentPlanFingerprint.match(adsFingerprint.method).let { match ->
|
|
||||||
match.stringMatches.forEach { match ->
|
|
||||||
println("The index of the string '${match.string}' is ${match.index}")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
proStringsFingerprint.apply {
|
||||||
|
match(context, adsFingerprintMatch.method)
|
||||||
|
}.match?.let { match ->
|
||||||
|
match.stringMatches.forEach { match ->
|
||||||
|
println("The index of the string '${match.string}' is ${match.index}")
|
||||||
|
}
|
||||||
|
} ?: throw PatchException("No match found")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> If the fingerprint can not be matched to any method, calling `match` will raise an
|
|
||||||
> exception.
|
|
||||||
> Instead, the `orNull` overloads can be used to return `null` if no match is found.
|
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> To see real-world examples of fingerprints,
|
> To see real-world examples of fingerprints,
|
||||||
> check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
|
> check out the repository for [ReVanced Patches](https://github.com/revanced/revanced-patches).
|
||||||
|
|||||||
@@ -76,23 +76,23 @@ val disableAdsPatch = bytecodePatch(
|
|||||||
) {
|
) {
|
||||||
compatibleWith("com.some.app"("1.0.0"))
|
compatibleWith("com.some.app"("1.0.0"))
|
||||||
|
|
||||||
// Patches can depend on other patches, executing them first.
|
// Resource patch disables ads by patching resource files.
|
||||||
dependsOn(disableAdsResourcePatch)
|
dependsOn(disableAdsResourcePatch)
|
||||||
|
|
||||||
// Merge precompiled DEX files into the patched app, before the patch is executed.
|
// Precompiled DEX file to be merged into the patched app.
|
||||||
extendWith("disable-ads.rve")
|
extendWith("disable-ads.rve")
|
||||||
|
|
||||||
|
// Fingerprint to find the method to patch.
|
||||||
|
val showAdsMatch by showAdsFingerprint {
|
||||||
|
// More about fingerprints on the next page of the documentation.
|
||||||
|
}
|
||||||
|
|
||||||
// Business logic of the patch to disable ads in the app.
|
// Business logic of the patch to disable ads in the app.
|
||||||
execute {
|
execute {
|
||||||
// Fingerprint to find the method to patch.
|
|
||||||
val showAdsFingerprint = fingerprint {
|
|
||||||
// More about fingerprints on the next page of the documentation.
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the method that shows ads,
|
// In the method that shows ads,
|
||||||
// call DisableAdsPatch.shouldDisableAds() from the extension (precompiled DEX file)
|
// call DisableAdsPatch.shouldDisableAds() from the extension (precompiled DEX file)
|
||||||
// to enable or disable ads.
|
// to enable or disable ads.
|
||||||
showAdsFingerprint.method.addInstructions(
|
showAdsMatch.mutableMethod.addInstructions(
|
||||||
0,
|
0,
|
||||||
"""
|
"""
|
||||||
invoke-static {}, LDisableAdsPatch;->shouldDisableAds()Z
|
invoke-static {}, LDisableAdsPatch;->shouldDisableAds()Z
|
||||||
@@ -115,17 +115,17 @@ val disableAdsPatch = bytecodePatch(
|
|||||||
Patches can have options to get and set before a patch is executed.
|
Patches can have options to get and set before a patch is executed.
|
||||||
Options are useful for making patches configurable.
|
Options are useful for making patches configurable.
|
||||||
After loading the patches using `PatchLoader`, options can be set for a patch.
|
After loading the patches using `PatchLoader`, options can be set for a patch.
|
||||||
Multiple types are already built into ReVanced Patcher and are supported by any application that uses ReVanced Patcher.
|
Multiple types are already inbuilt in ReVanced Patcher and are supported by any application that uses ReVanced Patcher.
|
||||||
|
|
||||||
To define an option, use the available `option` functions:
|
To define an option, use available `option` functions:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
val patch = bytecodePatch(name = "Patch") {
|
val patch = bytecodePatch(name = "Patch") {
|
||||||
// Add an inbuilt option and delegate it to a property.
|
// Add an inbuilt option and delegate it to a property.
|
||||||
val value by stringOption(name = "Inbuilt option")
|
val value by stringOption(key = "option")
|
||||||
|
|
||||||
// Add an option with a custom type and delegate it to a property.
|
// Add an option with a custom type and delegate it to a property.
|
||||||
val string by option<String>(name = "String option")
|
val string by option<String>(key = "string")
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
println(value)
|
println(value)
|
||||||
@@ -139,31 +139,19 @@ Options of a patch can be set after loading the patches with `PatchLoader` by ob
|
|||||||
```kt
|
```kt
|
||||||
loadPatchesJar(patches).apply {
|
loadPatchesJar(patches).apply {
|
||||||
// Type is checked at runtime.
|
// Type is checked at runtime.
|
||||||
first { it.name == "Patch" }.options["Option"] = "Value"
|
first { it.name == "Patch" }.options["option"] = "Value"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The type of an option can be obtained from the `type` property of the option:
|
The type of an option can be obtained from the `type` property of the option:
|
||||||
|
|
||||||
```kt
|
```kt
|
||||||
option.type // The KType of the option. Captures the full type information of the option.
|
option.type // The KType of the option.
|
||||||
```
|
```
|
||||||
|
|
||||||
Options can be declared outside a patch and added to a patch manually:
|
|
||||||
|
|
||||||
```kt
|
|
||||||
val option = stringOption(name = "Option")
|
|
||||||
|
|
||||||
bytecodePatch(name = "Patch") {
|
|
||||||
val value by option()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful when the same option is referenced in multiple patches.
|
|
||||||
|
|
||||||
### 🧩 Extensions
|
### 🧩 Extensions
|
||||||
|
|
||||||
An extension is a precompiled DEX file merged into the patched app before a patch is executed.
|
An extension is a precompiled DEX file that is merged into the patched app before a patch is executed.
|
||||||
While patches are compile-time constructs, extensions are runtime constructs
|
While patches are compile-time constructs, extensions are runtime constructs
|
||||||
that extend the patched app with additional classes.
|
that extend the patched app with additional classes.
|
||||||
|
|
||||||
@@ -184,8 +172,10 @@ and use it in a patch:
|
|||||||
val patch = bytecodePatch(name = "Complex patch") {
|
val patch = bytecodePatch(name = "Complex patch") {
|
||||||
extendWith("complex-patch.rve")
|
extendWith("complex-patch.rve")
|
||||||
|
|
||||||
|
val match by methodFingerprint()
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
fingerprint.method.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
|
match.mutableMethod.addInstructions(0, "invoke-static { }, LComplexPatch;->doSomething()V")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -242,17 +232,17 @@ The same order is followed for multiple patches depending on the patch.
|
|||||||
|
|
||||||
## 💡 Additional tips
|
## 💡 Additional tips
|
||||||
|
|
||||||
- When using `PatchLoader` to load patches, only patches with a name are loaded.
|
- When using ´PatchLoader` to load patches, only patches with a name are loaded.
|
||||||
Refer to the inline documentation of `PatchLoader` for detailed information.
|
Refer to the inline documentation of `PatchLoader` for detailed information.
|
||||||
- Patches can depend on others. Dependencies are executed first.
|
- Patches can depend on others. Dependencies are executed first.
|
||||||
The dependent patch will not be executed if a dependency raises an exception while executing.
|
The dependent patch will not be executed if a dependency raises an exception while executing.
|
||||||
- A patch can declare compatibility with specific packages and versions,
|
- A patch can declare compatibility with specific packages and versions,
|
||||||
but patches can still be executed on any package or version.
|
but patches can still be executed on any package or version.
|
||||||
It is recommended that compatibility is specified to present known compatible packages and versions.
|
It is recommended to declare compatibility to present known compatible packages and versions.
|
||||||
- If `compatibleWith` is not used, the patch is treated as compatible with any package
|
- If `compatibleWith` is not used, the patch is treated as compatible with any package
|
||||||
- If a package is specified with no versions, the patch is compatible with any version of the package
|
- If a package is specified with no versions, the patch is compatible with any version of the package
|
||||||
- If an empty array of versions is specified, the patch is not compatible with any version of the package.
|
- If an empty array of versions is specified, the patch is not compatible with any version of the package.
|
||||||
This is useful for declaring incompatibility with a specific package.
|
This is useful for declaring incompatibility with a specific package.
|
||||||
- A patch can raise a `PatchException` at any time of execution to indicate that the patch failed to execute.
|
- A patch can raise a `PatchException` at any time of execution to indicate that the patch failed to execute.
|
||||||
|
|
||||||
## ⏭️ What's next
|
## ⏭️ What's next
|
||||||
|
|||||||
@@ -96,21 +96,21 @@ Example of patches:
|
|||||||
@Surpress("unused")
|
@Surpress("unused")
|
||||||
val bytecodePatch = bytecodePatch {
|
val bytecodePatch = bytecodePatch {
|
||||||
execute {
|
execute {
|
||||||
// More about this on the next page of the documentation.
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Surpress("unused")
|
@Surpress("unused")
|
||||||
val rawResourcePatch = rawResourcePatch {
|
val rawResourcePatch = rawResourcePatch {
|
||||||
execute {
|
execute {
|
||||||
// More about this on the next page of the documentation.
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Surpress("unused")
|
@Surpress("unused")
|
||||||
val resourcePatch = resourcePatch {
|
val resourcePatch = rawResourcePatch {
|
||||||
execute {
|
execute {
|
||||||
// More about this on the next page of the documentation.
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
110
docs/4_apis.md
110
docs/4_apis.md
@@ -4,107 +4,18 @@ A handful of APIs are available to make patch development easier and more effici
|
|||||||
|
|
||||||
## 📙 Overview
|
## 📙 Overview
|
||||||
|
|
||||||
1. 👹 Create mutable replacements of classes with `proxy(ClassDef)`
|
1. 👹 Mutate classes with `context.proxy(ClassDef)`
|
||||||
2. 🔍 Find and create mutable replaces with `classBy(Predicate)`
|
2. 🔍 Find and proxy existing classes with `classBy(Predicate)` and `classByType(String)`
|
||||||
3. 🏃 Navigate method calls recursively by index with `navigate(Method)`
|
3. 🏃 Easily access referenced methods recursively by index with `MethodNavigator`
|
||||||
4. 💾 Read and write resource files with `get(String, Boolean)` and `delete(String)`
|
4. 🔨 Make use of extension functions from `BytecodeUtils` and `ResourceUtils` with certain applications
|
||||||
5. 📃 Read and write DOM files using `document(String)` and `document(InputStream)`
|
(Available in ReVanced Patches)
|
||||||
|
5. 💾 Read and write (decoded) resources with `ResourcePatchContext.get(Path, Boolean)`
|
||||||
|
6. 📃 Read and write DOM files using `ResourcePatchContext.document`
|
||||||
|
|
||||||
### 🧰 APIs
|
### 🧰 APIs
|
||||||
|
|
||||||
#### 👹 `proxy(ClassDef)`
|
> [!WARNING]
|
||||||
|
> This section is still under construction and may be incomplete.
|
||||||
By default, the classes are immutable, meaning they cannot be modified.
|
|
||||||
To make a class mutable, use the `proxy(ClassDef)` function.
|
|
||||||
This function creates a lazy mutable copy of the class definition.
|
|
||||||
Accessing the property will replace the original class definition with the mutable copy,
|
|
||||||
thus allowing you to make changes to the class. Subsequent accesses will return the same mutable copy.
|
|
||||||
|
|
||||||
```kt
|
|
||||||
execute {
|
|
||||||
val mutableClass = proxy(classDef)
|
|
||||||
mutableClass.methods.add(Method())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 🔍 `classBy(Predicate)`
|
|
||||||
|
|
||||||
The `classBy(Predicate)` function is an alternative to finding and creating mutable classes by a predicate.
|
|
||||||
It automatically proxies the class definition, making it mutable.
|
|
||||||
|
|
||||||
```kt
|
|
||||||
execute {
|
|
||||||
// Alternative to proxy(classes.find { it.name == "Lcom/example/MyClass;" })?.classDef
|
|
||||||
val classDef = classBy { it.name == "Lcom/example/MyClass;" }?.classDef
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 🏃 `navigate(Method).at(index)`
|
|
||||||
|
|
||||||
The `navigate(Method)` function allows you to navigate method calls recursively by index.
|
|
||||||
|
|
||||||
```kt
|
|
||||||
execute {
|
|
||||||
// Sequentially navigate to the instructions at index 1 within 'someMethod'.
|
|
||||||
val method = navigate(someMethod).to(1).original() // original() returns the original immutable method.
|
|
||||||
|
|
||||||
// Further navigate to the second occurrence where the instruction's opcode is 'INVOKEVIRTUAL'.
|
|
||||||
// stop() returns the mutable copy of the method.
|
|
||||||
val method = navigate(someMethod).to(2) { instruction -> instruction.opcode == Opcode.INVOKEVIRTUAL }.stop()
|
|
||||||
|
|
||||||
// Alternatively, to stop(), you can delegate the method to a variable.
|
|
||||||
val method by navigate(someMethod).to(1)
|
|
||||||
|
|
||||||
// You can chain multiple calls to at() to navigate deeper into the method.
|
|
||||||
val method by navigate(someMethod).to(1).to(2, 3, 4).to(5)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 💾 `get(String, Boolean)` and `delete(String)`
|
|
||||||
|
|
||||||
The `get(String, Boolean)` function returns a `File` object that can be used to read and write resource files.
|
|
||||||
|
|
||||||
```kt
|
|
||||||
execute {
|
|
||||||
val file = get("res/values/strings.xml")
|
|
||||||
val content = file.readText()
|
|
||||||
file.writeText(content)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `delete` function can mark files for deletion when the APK is rebuilt.
|
|
||||||
|
|
||||||
```kt
|
|
||||||
execute {
|
|
||||||
delete("res/values/strings.xml")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 📃 `document(String)` and `document(InputStream)`
|
|
||||||
|
|
||||||
The `document` function is used to read and write DOM files.
|
|
||||||
|
|
||||||
```kt
|
|
||||||
execute {
|
|
||||||
document("res/values/strings.xml").use { document ->
|
|
||||||
val element = doc.createElement("string").apply {
|
|
||||||
textContent = "Hello, World!"
|
|
||||||
}
|
|
||||||
document.documentElement.appendChild(element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also read documents from an `InputStream`:
|
|
||||||
|
|
||||||
```kt
|
|
||||||
execute {
|
|
||||||
val inputStream = classLoader.getResourceAsStream("some.xml")
|
|
||||||
document(inputStream).use { document ->
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎉 Afterword
|
## 🎉 Afterword
|
||||||
|
|
||||||
@@ -112,6 +23,5 @@ ReVanced Patcher is a powerful library to patch Android applications, offering a
|
|||||||
that outlive app updates. Patches make up ReVanced; without you, the community of patch developers,
|
that outlive app updates. Patches make up ReVanced; without you, the community of patch developers,
|
||||||
ReVanced would not be what it is today. We hope that this documentation has been helpful to you
|
ReVanced would not be what it is today. We hope that this documentation has been helpful to you
|
||||||
and are excited to see what you will create with ReVanced Patcher. If you have any questions or need help,
|
and are excited to see what you will create with ReVanced Patcher. If you have any questions or need help,
|
||||||
talk to us on one of our platforms linked on [revanced.app](https://revanced.app) or open an issue in case of a bug or
|
talk to us on one of our platforms linked on [revanced.app](https://revanced.app) or open an issue in case of a bug or feature request,
|
||||||
feature request,
|
|
||||||
ReVanced
|
ReVanced
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
version = 21.1.0-dev.5
|
version = 20.0.0-dev.1
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
[versions]
|
[versions]
|
||||||
android = "4.1.1.4"
|
android = "4.1.1.4"
|
||||||
apktool-lib = "2.10.1.1"
|
apktool-lib = "2.9.3"
|
||||||
binary-compatibility-validator = "0.18.1"
|
kotlin = "1.9.22"
|
||||||
kotlin = "2.0.20"
|
kotlinx-coroutines-core = "1.7.3"
|
||||||
kotlinx-coroutines-core = "1.10.2"
|
mockk = "1.13.10"
|
||||||
mockk = "1.14.5"
|
|
||||||
multidexlib2 = "3.0.3.r3"
|
multidexlib2 = "3.0.3.r3"
|
||||||
# Tracking https://github.com/google/smali/issues/64.
|
smali = "3.0.5"
|
||||||
#noinspection GradleDependency
|
binary-compatibility-validator = "0.14.0"
|
||||||
smali = "3.0.9"
|
|
||||||
xpp3 = "1.1.4c"
|
xpp3 = "1.1.4c"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android = { module = "com.google.android:android", version.ref = "android" }
|
android = { module = "com.google.android:android", version.ref = "android" }
|
||||||
apktool-lib = { module = "app.revanced:apktool-lib", version.ref = "apktool-lib" }
|
|
||||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
apktool-lib = { module = "app.revanced:apktool-lib", version.ref = "apktool-lib" }
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||||
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||||
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
||||||
multidexlib2 = { module = "app.revanced:multidexlib2", version.ref = "multidexlib2" }
|
multidexlib2 = { module = "app.revanced:multidexlib2", version.ref = "multidexlib2" }
|
||||||
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
|
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
8
gradle/wrapper/gradle-wrapper.properties
vendored
8
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,8 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dist
|
||||||
22
gradlew
vendored
22
gradlew
vendored
@@ -15,8 +15,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -57,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -85,9 +83,7 @@ done
|
|||||||
# This is normally unused
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
|
||||||
' "$PWD" ) || exit
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -148,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
# shellcheck disable=SC2039,SC3045
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
@@ -156,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
# shellcheck disable=SC2039,SC3045
|
# shellcheck disable=SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -205,11 +201,11 @@ fi
|
|||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
# Collect all arguments for the java command;
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# and any embedded shellness will be escaped.
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
# double quotes to make sure that they get re-expanded; and
|
||||||
# treated as '${Hostname}' itself on the command line.
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
|||||||
22
gradlew.bat
vendored
22
gradlew.bat
vendored
@@ -13,8 +13,6 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
@rem SPDX-License-Identifier: Apache-2.0
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@@ -45,11 +43,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -59,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|||||||
4043
package-lock.json
generated
4043
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"gradle-semantic-release-plugin": "^1.10.1",
|
"gradle-semantic-release-plugin": "^1.9.1",
|
||||||
"semantic-release": "^24.2.9"
|
"semantic-release": "^23.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
|
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||||
import app.revanced.patcher.patch.BytecodePatchContext
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
import app.revanced.patcher.patch.PatchException
|
import app.revanced.patcher.patch.BytecodePatchContext.LookupMaps.Companion.appendParameters
|
||||||
|
import app.revanced.patcher.patch.MethodClassPairs
|
||||||
import app.revanced.patcher.util.proxy.ClassProxy
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
@@ -15,17 +17,7 @@ import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
|||||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fingerprint for a method. A fingerprint is a partial description of a method.
|
* A fingerprint.
|
||||||
* It is used to uniquely match a method by its characteristics.
|
|
||||||
*
|
|
||||||
* An example fingerprint for a public method that takes a single string parameter and returns void:
|
|
||||||
* ```
|
|
||||||
* fingerprint {
|
|
||||||
* accessFlags(AccessFlags.PUBLIC)
|
|
||||||
* returns("V")
|
|
||||||
* parameters("Ljava/lang/String;")
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
*
|
||||||
* @param accessFlags The exact access flags using values of [AccessFlags].
|
* @param accessFlags The exact access flags using values of [AccessFlags].
|
||||||
* @param returnType The return type. Compared using [String.startsWith].
|
* @param returnType The return type. Compared using [String.startsWith].
|
||||||
@@ -44,21 +36,16 @@ class Fingerprint internal constructor(
|
|||||||
internal val custom: ((method: Method, classDef: ClassDef) -> Boolean)?,
|
internal val custom: ((method: Method, classDef: ClassDef) -> Boolean)?,
|
||||||
private val fuzzyPatternScanThreshold: Int,
|
private val fuzzyPatternScanThreshold: Int,
|
||||||
) {
|
) {
|
||||||
@Suppress("ktlint:standard:backing-property-naming")
|
|
||||||
// Backing field needed for lazy initialization.
|
|
||||||
private var _matchOrNull: Match? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The match for this [Fingerprint]. Null if unmatched.
|
* The match for this [Fingerprint]. Null if unmatched.
|
||||||
*/
|
*/
|
||||||
context(BytecodePatchContext)
|
var match: Match? = null
|
||||||
private val matchOrNull: Match?
|
private set
|
||||||
get() = matchOrNull()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Match using [BytecodePatchContext.lookupMaps].
|
* Match using [BytecodePatchContext.LookupMaps].
|
||||||
*
|
*
|
||||||
* Generally faster than the other [matchOrNull] overloads when there are many methods to check for a match.
|
* Generally faster than the other [match] overloads when there are many methods to check for a match.
|
||||||
*
|
*
|
||||||
* Fingerprints can be optimized for performance:
|
* Fingerprints can be optimized for performance:
|
||||||
* - Slowest: Specify [custom] or [opcodes] and nothing else.
|
* - Slowest: Specify [custom] or [opcodes] and nothing else.
|
||||||
@@ -66,50 +53,69 @@ class Fingerprint internal constructor(
|
|||||||
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||||
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||||
*
|
*
|
||||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
* @param context The context to create mutable proxies for the matched method and its class.
|
||||||
|
* @return True if a match was found or if the fingerprint is already matched to a method, false otherwise.
|
||||||
*/
|
*/
|
||||||
context(BytecodePatchContext)
|
internal fun match(context: BytecodePatchContext): Boolean {
|
||||||
internal fun matchOrNull(): Match? {
|
val lookupMaps = context.lookupMaps
|
||||||
if (_matchOrNull != null) return _matchOrNull
|
|
||||||
|
|
||||||
var match = strings?.mapNotNull {
|
fun Fingerprint.match(methodClasses: MethodClassPairs): Boolean {
|
||||||
lookupMaps.methodsByStrings[it]
|
|
||||||
}?.minByOrNull { it.size }?.let { methodClasses ->
|
|
||||||
methodClasses.forEach { (classDef, method) ->
|
methodClasses.forEach { (classDef, method) ->
|
||||||
val match = matchOrNull(classDef, method)
|
if (match(context, classDef, method)) return true
|
||||||
if (match != null) return@let match
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: If only one string is necessary, why not use a single string for every fingerprint?
|
||||||
|
fun Fingerprint.lookupByStrings() = strings?.firstNotNullOfOrNull { lookupMaps.methodsByStrings[it] }
|
||||||
|
if (lookupByStrings()?.let(::match) == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// No strings declared or none matched (partial matches are allowed).
|
||||||
|
// Use signature matching.
|
||||||
|
fun Fingerprint.lookupBySignature(): MethodClassPairs {
|
||||||
|
if (accessFlags == null) return lookupMaps.allMethods
|
||||||
|
|
||||||
|
var returnTypeValue = returnType
|
||||||
|
if (returnTypeValue == null) {
|
||||||
|
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
||||||
|
// Constructors always have void return type.
|
||||||
|
returnTypeValue = "V"
|
||||||
|
} else {
|
||||||
|
return lookupMaps.allMethods
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
null
|
val signature =
|
||||||
}
|
buildString {
|
||||||
if (match != null) return match
|
append(accessFlags)
|
||||||
|
append(returnTypeValue.first())
|
||||||
|
appendParameters(parameters ?: return@buildString)
|
||||||
|
}
|
||||||
|
|
||||||
classes.forEach { classDef ->
|
return lookupMaps.methodsBySignature[signature] ?: return MethodClassPairs()
|
||||||
match = matchOrNull(classDef)
|
|
||||||
if (match != null) return match
|
|
||||||
}
|
}
|
||||||
|
return match(lookupBySignature())
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Match using a [ClassDef].
|
* Match using a [ClassDef].
|
||||||
*
|
*
|
||||||
* @param classDef The class to match against.
|
* @param classDef The class to match against.
|
||||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
* @param context The context to create mutable proxies for the matched method and its class.
|
||||||
|
* @return True if a match was found or if the fingerprint is already matched to a method, false otherwise.
|
||||||
*/
|
*/
|
||||||
context(BytecodePatchContext)
|
fun match(
|
||||||
fun matchOrNull(
|
context: BytecodePatchContext,
|
||||||
classDef: ClassDef,
|
classDef: ClassDef,
|
||||||
): Match? {
|
): Boolean {
|
||||||
if (_matchOrNull != null) return _matchOrNull
|
|
||||||
|
|
||||||
for (method in classDef.methods) {
|
for (method in classDef.methods) {
|
||||||
val match = matchOrNull(method, classDef)
|
if (match(context, method, classDef)) {
|
||||||
if (match != null) return match
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,33 +123,35 @@ class Fingerprint internal constructor(
|
|||||||
* The class is retrieved from the method.
|
* The class is retrieved from the method.
|
||||||
*
|
*
|
||||||
* @param method The method to match against.
|
* @param method The method to match against.
|
||||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
* @param context The context to create mutable proxies for the matched method and its class.
|
||||||
|
* @return True if a match was found or if the fingerprint is already matched to a method, false otherwise.
|
||||||
*/
|
*/
|
||||||
context(BytecodePatchContext)
|
fun match(
|
||||||
fun matchOrNull(
|
context: BytecodePatchContext,
|
||||||
method: Method,
|
method: Method,
|
||||||
) = matchOrNull(method, classBy { method.definingClass == it.type }!!.immutableClass)
|
) = match(context, method, context.classByType(method.definingClass)!!.immutableClass)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Match using a [Method].
|
* Match using a [Method].
|
||||||
*
|
*
|
||||||
* @param method The method to match against.
|
* @param method The method to match against.
|
||||||
* @param classDef The class the method is a member of.
|
* @param classDef The class the method is a member of.
|
||||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
* @param context The context to create mutable proxies for the matched method and its class.
|
||||||
|
* @return True if a match was found or if the fingerprint is already matched to a method, false otherwise.
|
||||||
*/
|
*/
|
||||||
context(BytecodePatchContext)
|
internal fun match(
|
||||||
fun matchOrNull(
|
context: BytecodePatchContext,
|
||||||
method: Method,
|
method: Method,
|
||||||
classDef: ClassDef,
|
classDef: ClassDef,
|
||||||
): Match? {
|
): Boolean {
|
||||||
if (_matchOrNull != null) return _matchOrNull
|
if (match != null) return true
|
||||||
|
|
||||||
if (returnType != null && !method.returnType.startsWith(returnType)) {
|
if (returnType != null && !method.returnType.startsWith(returnType)) {
|
||||||
return null
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessFlags != null && accessFlags != method.accessFlags) {
|
if (accessFlags != null && accessFlags != method.accessFlags) {
|
||||||
return null
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parametersEqual(
|
fun parametersEqual(
|
||||||
@@ -160,17 +168,17 @@ class Fingerprint internal constructor(
|
|||||||
|
|
||||||
// TODO: parseParameters()
|
// TODO: parseParameters()
|
||||||
if (parameters != null && !parametersEqual(parameters, method.parameterTypes)) {
|
if (parameters != null && !parametersEqual(parameters, method.parameterTypes)) {
|
||||||
return null
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (custom != null && !custom.invoke(method, classDef)) {
|
if (custom != null && !custom.invoke(method, classDef)) {
|
||||||
return null
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val stringMatches: List<Match.StringMatch>? =
|
val stringMatches: List<Match.StringMatch>? =
|
||||||
if (strings != null) {
|
if (strings != null) {
|
||||||
buildList {
|
buildList {
|
||||||
val instructions = method.instructionsOrNull ?: return null
|
val instructions = method.instructionsOrNull ?: return false
|
||||||
|
|
||||||
val stringsList = strings.toMutableList()
|
val stringsList = strings.toMutableList()
|
||||||
|
|
||||||
@@ -190,14 +198,14 @@ class Fingerprint internal constructor(
|
|||||||
stringsList.removeAt(index)
|
stringsList.removeAt(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stringsList.isNotEmpty()) return null
|
if (stringsList.isNotEmpty()) return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val patternMatch = if (opcodes != null) {
|
val patternMatch = if (opcodes != null) {
|
||||||
val instructions = method.instructionsOrNull ?: return null
|
val instructions = method.instructionsOrNull ?: return false
|
||||||
|
|
||||||
fun patternScan(): Match.PatternMatch? {
|
fun patternScan(): Match.PatternMatch? {
|
||||||
val fingerprintFuzzyPatternScanThreshold = fuzzyPatternScanThreshold
|
val fingerprintFuzzyPatternScanThreshold = fuzzyPatternScanThreshold
|
||||||
@@ -236,217 +244,61 @@ class Fingerprint internal constructor(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
patternScan() ?: return null
|
patternScan() ?: return false
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
_matchOrNull = Match(
|
match = Match(
|
||||||
method,
|
method,
|
||||||
|
classDef,
|
||||||
patternMatch,
|
patternMatch,
|
||||||
stringMatches,
|
stringMatches,
|
||||||
classDef,
|
context,
|
||||||
)
|
)
|
||||||
|
|
||||||
return _matchOrNull
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private val exception get() = PatchException("Failed to match the fingerprint: $this")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The match for this [Fingerprint].
|
|
||||||
*
|
|
||||||
* @throws PatchException If the [Fingerprint] has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
private val match
|
|
||||||
get() = matchOrNull ?: throw exception
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Match using a [ClassDef].
|
|
||||||
*
|
|
||||||
* @param classDef The class to match against.
|
|
||||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
|
||||||
* @throws PatchException If the fingerprint has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
fun match(
|
|
||||||
classDef: ClassDef,
|
|
||||||
) = matchOrNull(classDef) ?: throw exception
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Match using a [Method].
|
|
||||||
* The class is retrieved from the method.
|
|
||||||
*
|
|
||||||
* @param method The method to match against.
|
|
||||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
|
||||||
* @throws PatchException If the fingerprint has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
fun match(
|
|
||||||
method: Method,
|
|
||||||
) = matchOrNull(method) ?: throw exception
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Match using a [Method].
|
|
||||||
*
|
|
||||||
* @param method The method to match against.
|
|
||||||
* @param classDef The class the method is a member of.
|
|
||||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
|
||||||
* @throws PatchException If the fingerprint has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
fun match(
|
|
||||||
method: Method,
|
|
||||||
classDef: ClassDef,
|
|
||||||
) = matchOrNull(method, classDef) ?: throw exception
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The class the matching method is a member of.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val originalClassDefOrNull
|
|
||||||
get() = matchOrNull?.originalClassDef
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The matching method.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val originalMethodOrNull
|
|
||||||
get() = matchOrNull?.originalMethod
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The mutable version of [originalClassDefOrNull].
|
|
||||||
*
|
|
||||||
* Accessing this property allocates a [ClassProxy].
|
|
||||||
* Use [originalClassDefOrNull] if mutable access is not required.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val classDefOrNull
|
|
||||||
get() = matchOrNull?.classDef
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The mutable version of [originalMethodOrNull].
|
|
||||||
*
|
|
||||||
* Accessing this property allocates a [ClassProxy].
|
|
||||||
* Use [originalMethodOrNull] if mutable access is not required.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val methodOrNull
|
|
||||||
get() = matchOrNull?.method
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The match for the opcode pattern.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val patternMatchOrNull
|
|
||||||
get() = matchOrNull?.patternMatch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The matches for the strings.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val stringMatchesOrNull
|
|
||||||
get() = matchOrNull?.stringMatches
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The class the matching method is a member of.
|
|
||||||
*
|
|
||||||
* @throws PatchException If the fingerprint has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val originalClassDef
|
|
||||||
get() = match.originalClassDef
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The matching method.
|
|
||||||
*
|
|
||||||
* @throws PatchException If the fingerprint has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val originalMethod
|
|
||||||
get() = match.originalMethod
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The mutable version of [originalClassDef].
|
|
||||||
*
|
|
||||||
* Accessing this property allocates a [ClassProxy].
|
|
||||||
* Use [originalClassDef] if mutable access is not required.
|
|
||||||
*
|
|
||||||
* @throws PatchException If the fingerprint has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val classDef
|
|
||||||
get() = match.classDef
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The mutable version of [originalMethod].
|
|
||||||
*
|
|
||||||
* Accessing this property allocates a [ClassProxy].
|
|
||||||
* Use [originalMethod] if mutable access is not required.
|
|
||||||
*
|
|
||||||
* @throws PatchException If the fingerprint has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val method
|
|
||||||
get() = match.method
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The match for the opcode pattern.
|
|
||||||
*
|
|
||||||
* @throws PatchException If the fingerprint has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val patternMatch
|
|
||||||
get() = match.patternMatch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The matches for the strings.
|
|
||||||
*
|
|
||||||
* @throws PatchException If the fingerprint has not been matched.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
val stringMatches
|
|
||||||
get() = match.stringMatches
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A match of a [Fingerprint].
|
* A match for a [Fingerprint].
|
||||||
*
|
*
|
||||||
* @param originalClassDef The class the matching method is a member of.
|
* @param method The matching method.
|
||||||
* @param originalMethod The matching method.
|
* @param classDef The class the matching method is a member of.
|
||||||
* @param patternMatch The match for the opcode pattern.
|
* @param patternMatch The match for the opcode pattern.
|
||||||
* @param stringMatches The matches for the strings.
|
* @param stringMatches The matches for the strings.
|
||||||
|
* @param context The context to create mutable proxies in.
|
||||||
*/
|
*/
|
||||||
context(BytecodePatchContext)
|
class Match(
|
||||||
class Match internal constructor(
|
val method: Method,
|
||||||
val originalMethod: Method,
|
val classDef: ClassDef,
|
||||||
val patternMatch: PatternMatch?,
|
val patternMatch: PatternMatch?,
|
||||||
val stringMatches: List<StringMatch>?,
|
val stringMatches: List<StringMatch>?,
|
||||||
val originalClassDef: ClassDef,
|
internal val context: BytecodePatchContext,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* The mutable version of [originalClassDef].
|
* The mutable version of [classDef].
|
||||||
*
|
*
|
||||||
* Accessing this property allocates a [ClassProxy].
|
* Accessing this property allocates a [ClassProxy].
|
||||||
* Use [originalClassDef] if mutable access is not required.
|
* Use [classDef] if mutable access is not required.
|
||||||
*/
|
*/
|
||||||
val classDef by lazy { proxy(originalClassDef).mutableClass }
|
val mutableClass by lazy { context.proxy(classDef).mutableClass }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The mutable version of [originalMethod].
|
* The mutable version of [method].
|
||||||
*
|
*
|
||||||
* Accessing this property allocates a [ClassProxy].
|
* Accessing this property allocates a [ClassProxy].
|
||||||
* Use [originalMethod] if mutable access is not required.
|
* Use [method] if mutable access is not required.
|
||||||
*/
|
*/
|
||||||
val method by lazy { classDef.methods.first { MethodUtil.methodSignaturesMatch(it, originalMethod) } }
|
val mutableMethod by lazy { mutableClass.methods.first { MethodUtil.methodSignaturesMatch(it, method) } }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A match for an opcode pattern.
|
* A match for an opcode pattern.
|
||||||
* @param startIndex The index of the first opcode of the pattern in the method.
|
* @param startIndex The index of the first opcode of the pattern in the method.
|
||||||
* @param endIndex The index of the last opcode of the pattern in the method.
|
* @param endIndex The index of the last opcode of the pattern in the method.
|
||||||
*/
|
*/
|
||||||
class PatternMatch internal constructor(
|
class PatternMatch(
|
||||||
val startIndex: Int,
|
val startIndex: Int,
|
||||||
val endIndex: Int,
|
val endIndex: Int,
|
||||||
)
|
)
|
||||||
@@ -457,7 +309,7 @@ class Match internal constructor(
|
|||||||
* @param string The string that matched.
|
* @param string The string that matched.
|
||||||
* @param index The index of the instruction in the method.
|
* @param index The index of the instruction in the method.
|
||||||
*/
|
*/
|
||||||
class StringMatch internal constructor(val string: String, val index: Int)
|
class StringMatch(val string: String, val index: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -506,7 +358,7 @@ class FingerprintBuilder internal constructor(
|
|||||||
*
|
*
|
||||||
* @param returnType The return type compared using [String.startsWith].
|
* @param returnType The return type compared using [String.startsWith].
|
||||||
*/
|
*/
|
||||||
fun returns(returnType: String) {
|
infix fun returns(returnType: String) {
|
||||||
this.returnType = returnType
|
this.returnType = returnType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,3 +449,19 @@ fun fingerprint(
|
|||||||
fuzzyPatternScanThreshold: Int = 0,
|
fuzzyPatternScanThreshold: Int = 0,
|
||||||
block: FingerprintBuilder.() -> Unit,
|
block: FingerprintBuilder.() -> Unit,
|
||||||
) = FingerprintBuilder(fuzzyPatternScanThreshold).apply(block).build()
|
) = FingerprintBuilder(fuzzyPatternScanThreshold).apply(block).build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Fingerprint] and add it to the set of fingerprints.
|
||||||
|
*
|
||||||
|
* @param fuzzyPatternScanThreshold The threshold for fuzzy pattern scanning. Default is 0.
|
||||||
|
* @param block The block to build the [Fingerprint].
|
||||||
|
*
|
||||||
|
* @return The created [Fingerprint].
|
||||||
|
*/
|
||||||
|
fun BytecodePatchBuilder.fingerprint(
|
||||||
|
fuzzyPatternScanThreshold: Int = 0,
|
||||||
|
block: FingerprintBuilder.() -> Unit,
|
||||||
|
) = app.revanced.patcher.fingerprint(
|
||||||
|
fuzzyPatternScanThreshold,
|
||||||
|
block,
|
||||||
|
)() // Invoke to add it.
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ class Patcher(private val config: PatcherConfig) : Closeable {
|
|||||||
patch.addRecursively()
|
patch.addRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Patch<*>.anyRecursively(predicate: (Patch<*>) -> Boolean): Boolean =
|
||||||
|
predicate(this) || dependencies.any { dependency -> dependency.anyRecursively(predicate) }
|
||||||
|
|
||||||
context.allPatches.let { allPatches ->
|
context.allPatches.let { allPatches ->
|
||||||
// Check, if what kind of resource mode is required.
|
// Check, if what kind of resource mode is required.
|
||||||
config.resourceMode = if (allPatches.any { patch -> patch.anyRecursively { it is ResourcePatch } }) {
|
config.resourceMode = if (allPatches.any { patch -> patch.anyRecursively { it is ResourcePatch } }) {
|
||||||
@@ -91,16 +94,11 @@ class Patcher(private val config: PatcherConfig) : Closeable {
|
|||||||
}.also { executedPatches[this] = it }
|
}.also { executedPatches[this] = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent decoding the app manifest twice if it is not needed.
|
// Prevent from decoding the app manifest twice if it is not needed.
|
||||||
if (config.resourceMode != ResourcePatchContext.ResourceMode.NONE) {
|
if (config.resourceMode != ResourcePatchContext.ResourceMode.NONE) {
|
||||||
context.resourceContext.decodeResources(config.resourceMode)
|
context.resourceContext.decodeResources(config.resourceMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Initializing lookup maps")
|
|
||||||
|
|
||||||
// Accessing the lazy lookup maps to initialize them.
|
|
||||||
context.bytecodeContext.lookupMaps
|
|
||||||
|
|
||||||
logger.info("Executing patches")
|
logger.info("Executing patches")
|
||||||
|
|
||||||
val executedPatches = LinkedHashMap<Patch<*>, PatchResult>()
|
val executedPatches = LinkedHashMap<Patch<*>, PatchResult>()
|
||||||
@@ -148,7 +146,7 @@ class Patcher(private val config: PatcherConfig) : Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() = context.close()
|
override fun close() = context.bytecodeContext.lookupMaps.close()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile and save patched APK files.
|
* Compile and save patched APK files.
|
||||||
|
|||||||
@@ -12,32 +12,16 @@ import java.util.logging.Logger
|
|||||||
* @param temporaryFilesPath A path to a folder to store temporary files in.
|
* @param temporaryFilesPath A path to a folder to store temporary files in.
|
||||||
* @param aaptBinaryPath A path to a custom aapt binary.
|
* @param aaptBinaryPath A path to a custom aapt binary.
|
||||||
* @param frameworkFileDirectory A path to the directory to cache the framework file in.
|
* @param frameworkFileDirectory A path to the directory to cache the framework file in.
|
||||||
|
* @param multithreadingDexFileWriter Whether to use multiple threads for writing dex files.
|
||||||
|
* This has impact on memory usage and performance.
|
||||||
*/
|
*/
|
||||||
class PatcherConfig(
|
class PatcherConfig(
|
||||||
internal val apkFile: File,
|
internal val apkFile: File,
|
||||||
private val temporaryFilesPath: File = File("revanced-temporary-files"),
|
private val temporaryFilesPath: File = File("revanced-temporary-files"),
|
||||||
aaptBinaryPath: File? = null,
|
aaptBinaryPath: String? = null,
|
||||||
frameworkFileDirectory: String? = null,
|
frameworkFileDirectory: String? = null,
|
||||||
|
internal val multithreadingDexFileWriter: Boolean = false,
|
||||||
) {
|
) {
|
||||||
/**
|
|
||||||
* The configuration for the patcher.
|
|
||||||
*
|
|
||||||
* @param apkFile The apk file to patch.
|
|
||||||
* @param temporaryFilesPath A path to a folder to store temporary files in.
|
|
||||||
* @param aaptBinaryPath A path to a custom aapt binary.
|
|
||||||
* @param frameworkFileDirectory A path to the directory to cache the framework file in.
|
|
||||||
*/
|
|
||||||
@Deprecated(
|
|
||||||
"Use the constructor with a File for aaptBinaryPath instead.",
|
|
||||||
ReplaceWith("PatcherConfig(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory)"),
|
|
||||||
)
|
|
||||||
constructor(
|
|
||||||
apkFile: File,
|
|
||||||
temporaryFilesPath: File = File("revanced-temporary-files"),
|
|
||||||
aaptBinaryPath: String? = null,
|
|
||||||
frameworkFileDirectory: String? = null,
|
|
||||||
) : this(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory)
|
|
||||||
|
|
||||||
private val logger = Logger.getLogger(PatcherConfig::class.java.name)
|
private val logger = Logger.getLogger(PatcherConfig::class.java.name)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +36,8 @@ class PatcherConfig(
|
|||||||
*/
|
*/
|
||||||
internal val resourceConfig =
|
internal val resourceConfig =
|
||||||
Config.getDefaultConfig().apply {
|
Config.getDefaultConfig().apply {
|
||||||
aaptBinary = aaptBinaryPath
|
useAapt2 = true
|
||||||
|
aaptPath = aaptBinaryPath ?: ""
|
||||||
frameworkDirectory = frameworkFileDirectory
|
frameworkDirectory = frameworkFileDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import app.revanced.patcher.patch.Patch
|
|||||||
import app.revanced.patcher.patch.ResourcePatchContext
|
import app.revanced.patcher.patch.ResourcePatchContext
|
||||||
import brut.androlib.apk.ApkInfo
|
import brut.androlib.apk.ApkInfo
|
||||||
import brut.directory.ExtFile
|
import brut.directory.ExtFile
|
||||||
import java.io.Closeable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A context for the patcher containing the current state of the patcher.
|
* A context for the patcher containing the current state of the patcher.
|
||||||
@@ -13,7 +12,7 @@ import java.io.Closeable
|
|||||||
* @param config The configuration for the patcher.
|
* @param config The configuration for the patcher.
|
||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
class PatcherContext internal constructor(config: PatcherConfig): Closeable {
|
class PatcherContext internal constructor(config: PatcherConfig) {
|
||||||
/**
|
/**
|
||||||
* [PackageMetadata] of the supplied [PatcherConfig.apkFile].
|
* [PackageMetadata] of the supplied [PatcherConfig.apkFile].
|
||||||
*/
|
*/
|
||||||
@@ -38,6 +37,4 @@ class PatcherContext internal constructor(config: PatcherConfig): Closeable {
|
|||||||
* The context for patches containing the current state of the bytecode.
|
* The context for patches containing the current state of the bytecode.
|
||||||
*/
|
*/
|
||||||
internal val bytecodeContext = BytecodePatchContext(config)
|
internal val bytecodeContext = BytecodePatchContext(config)
|
||||||
|
|
||||||
override fun close() = bytecodeContext.close()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ class PatcherResult internal constructor(
|
|||||||
* @param resourcesApk The compiled resources.apk file.
|
* @param resourcesApk The compiled resources.apk file.
|
||||||
* @param otherResources The directory containing other resources files.
|
* @param otherResources The directory containing other resources files.
|
||||||
* @param doNotCompress List of files that should not be compressed.
|
* @param doNotCompress List of files that should not be compressed.
|
||||||
* @param deleteResources List of resources that should be deleted.
|
* @param deleteResources List of predicates about resources that should be deleted.
|
||||||
*/
|
*/
|
||||||
class PatchedResources internal constructor(
|
class PatchedResources internal constructor(
|
||||||
val resourcesApk: File?,
|
val resourcesApk: File?,
|
||||||
val otherResources: File?,
|
val otherResources: File?,
|
||||||
val doNotCompress: Set<String>,
|
val doNotCompress: Set<String>,
|
||||||
val deleteResources: Set<String>,
|
val deleteResources: Set<(String) -> Boolean>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
|
|||||||
import com.android.tools.smali.dexlib2.iface.DexFile
|
import com.android.tools.smali.dexlib2.iface.DexFile
|
||||||
import com.android.tools.smali.dexlib2.iface.Method
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
import lanchon.multidexlib2.BasicDexFileNamer
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
import lanchon.multidexlib2.DexIO
|
import lanchon.multidexlib2.DexIO
|
||||||
@@ -22,6 +21,7 @@ import lanchon.multidexlib2.MultiDexIO
|
|||||||
import lanchon.multidexlib2.RawDexIO
|
import lanchon.multidexlib2.RawDexIO
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.FileFilter
|
import java.io.FileFilter
|
||||||
|
import java.io.InputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
@@ -31,10 +31,8 @@ import java.util.logging.Logger
|
|||||||
* @param config The [PatcherConfig] used to create this context.
|
* @param config The [PatcherConfig] used to create this context.
|
||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
class BytecodePatchContext internal constructor(private val config: PatcherConfig) :
|
class BytecodePatchContext internal constructor(private val config: PatcherConfig) : PatchContext<Set<PatcherResult.PatchedDexFile>> {
|
||||||
PatchContext<Set<PatcherResult.PatchedDexFile>>,
|
private val logger = Logger.getLogger(BytecodePatchContext::class.java.name)
|
||||||
Closeable {
|
|
||||||
private val logger = Logger.getLogger(this::class.java.name)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Opcodes] of the supplied [PatcherConfig.apkFile].
|
* [Opcodes] of the supplied [PatcherConfig.apkFile].
|
||||||
@@ -60,38 +58,45 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
|||||||
internal val lookupMaps by lazy { LookupMaps(classes) }
|
internal val lookupMaps by lazy { LookupMaps(classes) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge the extension of [bytecodePatch] into the [BytecodePatchContext].
|
* Merge an extension to [classes].
|
||||||
* If no extension is present, the function will return early.
|
|
||||||
*
|
*
|
||||||
* @param bytecodePatch The [BytecodePatch] to merge the extension of.
|
* @param extensionInputStream The input stream of the extension to merge.
|
||||||
*/
|
*/
|
||||||
internal fun mergeExtension(bytecodePatch: BytecodePatch) {
|
internal fun merge(extensionInputStream: InputStream) {
|
||||||
bytecodePatch.extensionInputStream?.get()?.use { extensionStream ->
|
val extension = extensionInputStream.readAllBytes()
|
||||||
RawDexIO.readRawDexFile(extensionStream, 0, null).classes.forEach { classDef ->
|
|
||||||
val existingClass = lookupMaps.classesByType[classDef.type] ?: run {
|
|
||||||
logger.fine { "Adding class \"$classDef\"" }
|
|
||||||
|
|
||||||
classes += classDef
|
RawDexIO.readRawDexFile(extension, 0, null).classes.forEach { classDef ->
|
||||||
lookupMaps.classesByType[classDef.type] = classDef
|
val existingClass = lookupMaps.classesByType[classDef.type] ?: run {
|
||||||
|
logger.fine("Adding class \"$classDef\"")
|
||||||
|
|
||||||
return@forEach
|
lookupMaps.classesByType[classDef.type] = classDef
|
||||||
}
|
classes += classDef
|
||||||
|
|
||||||
logger.fine { "Class \"$classDef\" exists already. Adding missing methods and fields." }
|
return@forEach
|
||||||
|
|
||||||
existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
|
|
||||||
// If the class was merged, replace the original class with the merged class.
|
|
||||||
if (mergedClass === existingClass) {
|
|
||||||
return@let
|
|
||||||
}
|
|
||||||
|
|
||||||
classes -= existingClass
|
|
||||||
classes += mergedClass
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} ?: logger.fine("No extension to merge")
|
|
||||||
|
logger.fine("Class \"$classDef\" exists already. Adding missing methods and fields.")
|
||||||
|
|
||||||
|
existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
|
||||||
|
// If the class was merged, replace the original class with the merged class.
|
||||||
|
if (mergedClass === existingClass) {
|
||||||
|
return@let
|
||||||
|
}
|
||||||
|
|
||||||
|
classes -= existingClass
|
||||||
|
classes += mergedClass
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a class by its type using a contains check.
|
||||||
|
*
|
||||||
|
* @param type The type of the class.
|
||||||
|
* @return A proxy for the first class that matches the type.
|
||||||
|
*/
|
||||||
|
fun classByType(type: String) = classBy { type in it.type }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a class with a predicate.
|
* Find a class with a predicate.
|
||||||
*
|
*
|
||||||
@@ -108,9 +113,9 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
|||||||
*
|
*
|
||||||
* @return A proxy for the class.
|
* @return A proxy for the class.
|
||||||
*/
|
*/
|
||||||
fun proxy(classDef: ClassDef) = classes.proxyPool.find {
|
fun proxy(classDef: ClassDef) = this@BytecodePatchContext.classes.proxyPool.find {
|
||||||
it.immutableClass.type == classDef.type
|
it.immutableClass.type == classDef.type
|
||||||
} ?: ClassProxy(classDef).also { classes.proxyPool.add(it) }
|
} ?: ClassProxy(classDef).also { this@BytecodePatchContext.classes.proxyPool.add(it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate a method.
|
* Navigate a method.
|
||||||
@@ -119,7 +124,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
|||||||
*
|
*
|
||||||
* @return A [MethodNavigator] for the method.
|
* @return A [MethodNavigator] for the method.
|
||||||
*/
|
*/
|
||||||
fun navigate(method: MethodReference) = MethodNavigator(method)
|
fun navigate(method: Method) = MethodNavigator(this@BytecodePatchContext, method)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile bytecode from the [BytecodePatchContext].
|
* Compile bytecode from the [BytecodePatchContext].
|
||||||
@@ -130,9 +135,6 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
|||||||
override fun get(): Set<PatcherResult.PatchedDexFile> {
|
override fun get(): Set<PatcherResult.PatchedDexFile> {
|
||||||
logger.info("Compiling patched dex files")
|
logger.info("Compiling patched dex files")
|
||||||
|
|
||||||
// Free up memory before compiling the dex files.
|
|
||||||
lookupMaps.close()
|
|
||||||
|
|
||||||
val patchedDexFileResults =
|
val patchedDexFileResults =
|
||||||
config.patchedFiles.resolve("dex").also {
|
config.patchedFiles.resolve("dex").also {
|
||||||
it.deleteRecursively() // Make sure the directory is empty.
|
it.deleteRecursively() // Make sure the directory is empty.
|
||||||
@@ -140,7 +142,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
|||||||
}.apply {
|
}.apply {
|
||||||
MultiDexIO.writeDexFile(
|
MultiDexIO.writeDexFile(
|
||||||
true,
|
true,
|
||||||
-1,
|
if (config.multithreadingDexFileWriter) -1 else 1,
|
||||||
this,
|
this,
|
||||||
BasicDexFileNamer(),
|
BasicDexFileNamer(),
|
||||||
object : DexFile {
|
object : DexFile {
|
||||||
@@ -150,7 +152,7 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
|||||||
override fun getOpcodes() = this@BytecodePatchContext.opcodes
|
override fun getOpcodes() = this@BytecodePatchContext.opcodes
|
||||||
},
|
},
|
||||||
DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
|
DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
|
||||||
) { _, entryName, _ -> logger.info { "Compiled $entryName" } }
|
) { _, entryName, _ -> logger.info("Compiled $entryName") }
|
||||||
}.listFiles(FileFilter { it.isFile })!!.map {
|
}.listFiles(FileFilter { it.isFile })!!.map {
|
||||||
PatcherResult.PatchedDexFile(it.name, it.inputStream())
|
PatcherResult.PatchedDexFile(it.name, it.inputStream())
|
||||||
}.toSet()
|
}.toSet()
|
||||||
@@ -166,21 +168,47 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
|||||||
* @param classes The list of classes to create the lookup maps from.
|
* @param classes The list of classes to create the lookup maps from.
|
||||||
*/
|
*/
|
||||||
internal class LookupMaps internal constructor(classes: List<ClassDef>) : Closeable {
|
internal class LookupMaps internal constructor(classes: List<ClassDef>) : Closeable {
|
||||||
|
/**
|
||||||
|
* Classes associated by their type.
|
||||||
|
*/
|
||||||
|
internal val classesByType = classes.associateBy { it.type }.toMutableMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All methods and the class they are a member of.
|
||||||
|
*/
|
||||||
|
internal val allMethods = MethodClassPairs()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods associated by its access flags, return type and parameter.
|
||||||
|
*/
|
||||||
|
internal val methodsBySignature = MethodClassPairsLookupMap()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Methods associated by strings referenced in it.
|
* Methods associated by strings referenced in it.
|
||||||
*/
|
*/
|
||||||
internal val methodsByStrings = MethodClassPairsLookupMap()
|
internal val methodsByStrings = MethodClassPairsLookupMap()
|
||||||
|
|
||||||
// Lookup map for fast checking if a class exists by its type.
|
|
||||||
val classesByType = mutableMapOf<String, ClassDef>().apply {
|
|
||||||
classes.forEach { classDef -> put(classDef.type, classDef) }
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
classes.forEach { classDef ->
|
classes.forEach { classDef ->
|
||||||
classDef.methods.forEach { method ->
|
classDef.methods.forEach { method ->
|
||||||
val methodClassPair: MethodClassPair = method to classDef
|
val methodClassPair: MethodClassPair = method to classDef
|
||||||
|
|
||||||
|
// For fingerprints with no access or return type specified.
|
||||||
|
allMethods += methodClassPair
|
||||||
|
|
||||||
|
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
|
||||||
|
|
||||||
|
// Add <access><returnType> as the key.
|
||||||
|
methodsBySignature[accessFlagsReturnKey] = methodClassPair
|
||||||
|
|
||||||
|
// Add <access><returnType>[parameters] as the key.
|
||||||
|
methodsBySignature[
|
||||||
|
buildString {
|
||||||
|
append(accessFlagsReturnKey)
|
||||||
|
appendParameters(method.parameterTypes)
|
||||||
|
},
|
||||||
|
] = methodClassPair
|
||||||
|
|
||||||
// Add strings contained in the method as the key.
|
// Add strings contained in the method as the key.
|
||||||
method.instructionsOrNull?.forEach instructions@{ instruction ->
|
method.instructionsOrNull?.forEach instructions@{ instruction ->
|
||||||
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) {
|
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) {
|
||||||
@@ -198,15 +226,33 @@ class BytecodePatchContext internal constructor(private val config: PatcherConfi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
internal companion object {
|
||||||
methodsByStrings.clear()
|
/**
|
||||||
classesByType.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())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
lookupMaps.close()
|
allMethods.clear()
|
||||||
classes.clear()
|
methodsBySignature.clear()
|
||||||
|
methodsByStrings.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,51 +20,16 @@ import kotlin.reflect.typeOf
|
|||||||
* @constructor Create a new [Option].
|
* @constructor Create a new [Option].
|
||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||||
class Option<T>
|
class Option<T> @PublishedApi internal constructor(
|
||||||
@PublishedApi
|
|
||||||
@Deprecated("Use the constructor with the name instead of a key instead.")
|
|
||||||
internal constructor(
|
|
||||||
@Deprecated("Use the name property instead.")
|
|
||||||
val key: String,
|
val key: String,
|
||||||
val default: T? = null,
|
val default: T? = null,
|
||||||
val values: Map<String, T?>? = null,
|
val values: Map<String, T?>? = null,
|
||||||
@Deprecated("Use the name property instead.")
|
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
val required: Boolean = false,
|
val required: Boolean = false,
|
||||||
val type: KType,
|
val type: KType,
|
||||||
val validator: Option<T>.(T?) -> Boolean = { true },
|
val validator: Option<T>.(T?) -> Boolean = { true },
|
||||||
) {
|
) {
|
||||||
/**
|
|
||||||
* The name.
|
|
||||||
*/
|
|
||||||
val name = key
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An option.
|
|
||||||
*
|
|
||||||
* @param T The value type of the option.
|
|
||||||
* @param name The name.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @param description A description.
|
|
||||||
* @param required Whether the option is required.
|
|
||||||
* @param type The type of the option value (to handle type erasure).
|
|
||||||
* @param validator The function to validate the option value.
|
|
||||||
*
|
|
||||||
* @constructor Create a new [Option].
|
|
||||||
*/
|
|
||||||
@PublishedApi
|
|
||||||
internal constructor(
|
|
||||||
name: String,
|
|
||||||
default: T? = null,
|
|
||||||
values: Map<String, T?>? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
type: KType,
|
|
||||||
validator: Option<T>.(T?) -> Boolean = { true },
|
|
||||||
) : this(name, default, values, name, description, required, type, validator)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value of the [Option].
|
* The value of the [Option].
|
||||||
*/
|
*/
|
||||||
@@ -135,7 +100,7 @@ internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of [Option]s where options can be set and retrieved by their key.
|
* A collection of [Option]s where options can be set and retrieved by key.
|
||||||
*
|
*
|
||||||
* @param options The options.
|
* @param options The options.
|
||||||
*
|
*
|
||||||
@@ -144,7 +109,7 @@ internal constructor(
|
|||||||
class Options internal constructor(
|
class Options internal constructor(
|
||||||
private val options: Map<String, Option<*>>,
|
private val options: Map<String, Option<*>>,
|
||||||
) : Map<String, Option<*>> by options {
|
) : Map<String, Option<*>> by options {
|
||||||
internal constructor(options: Set<Option<*>>) : this(options.associateBy { it.name })
|
internal constructor(options: Set<Option<*>>) : this(options.associateBy { it.key })
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set an option's value.
|
* Set an option's value.
|
||||||
@@ -178,39 +143,6 @@ class Options internal constructor(
|
|||||||
override fun get(key: String) = options[key] ?: throw OptionException.OptionNotFoundException(key)
|
override fun get(key: String) = options[key] ?: throw OptionException.OptionNotFoundException(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option] with a string value.
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
fun stringOption(
|
|
||||||
key: String,
|
|
||||||
default: String? = null,
|
|
||||||
values: Map<String, String?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: Option<String>.(String?) -> Boolean = { true },
|
|
||||||
) = option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new [Option] with a string value and add it to the current [PatchBuilder].
|
* Create a new [Option] with a string value and add it to the current [PatchBuilder].
|
||||||
*
|
*
|
||||||
@@ -244,39 +176,6 @@ fun PatchBuilder<*>.stringOption(
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option] with an integer value.
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
fun intOption(
|
|
||||||
key: String,
|
|
||||||
default: Int? = null,
|
|
||||||
values: Map<String, Int?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: Option<Int>.(Int?) -> Boolean = { true },
|
|
||||||
) = option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new [Option] with an integer value and add it to the current [PatchBuilder].
|
* Create a new [Option] with an integer value and add it to the current [PatchBuilder].
|
||||||
*
|
*
|
||||||
@@ -299,40 +198,7 @@ fun PatchBuilder<*>.intOption(
|
|||||||
title: String? = null,
|
title: String? = null,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
required: Boolean = false,
|
required: Boolean = false,
|
||||||
validator: Option<Int>.(Int?) -> Boolean = { true },
|
validator: Option<Int?>.(Int?) -> Boolean = { true },
|
||||||
) = option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option] with a boolean value.
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
fun booleanOption(
|
|
||||||
key: String,
|
|
||||||
default: Boolean? = null,
|
|
||||||
values: Map<String, Boolean?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
|
|
||||||
) = option(
|
) = option(
|
||||||
key,
|
key,
|
||||||
default,
|
default,
|
||||||
@@ -365,40 +231,7 @@ fun PatchBuilder<*>.booleanOption(
|
|||||||
title: String? = null,
|
title: String? = null,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
required: Boolean = false,
|
required: Boolean = false,
|
||||||
validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
|
validator: Option<Boolean?>.(Boolean?) -> Boolean = { true },
|
||||||
) = option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option] with a float value.
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
fun floatOption(
|
|
||||||
key: String,
|
|
||||||
default: Float? = null,
|
|
||||||
values: Map<String, Float?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: Option<Float>.(Float?) -> Boolean = { true },
|
|
||||||
) = option(
|
) = option(
|
||||||
key,
|
key,
|
||||||
default,
|
default,
|
||||||
@@ -431,40 +264,7 @@ fun PatchBuilder<*>.floatOption(
|
|||||||
title: String? = null,
|
title: String? = null,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
required: Boolean = false,
|
required: Boolean = false,
|
||||||
validator: Option<Float>.(Float?) -> Boolean = { true },
|
validator: Option<Float?>.(Float?) -> Boolean = { true },
|
||||||
) = option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option] with a long value.
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
fun longOption(
|
|
||||||
key: String,
|
|
||||||
default: Long? = null,
|
|
||||||
values: Map<String, Long?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: Option<Long>.(Long?) -> Boolean = { true },
|
|
||||||
) = option(
|
) = option(
|
||||||
key,
|
key,
|
||||||
default,
|
default,
|
||||||
@@ -497,40 +297,7 @@ fun PatchBuilder<*>.longOption(
|
|||||||
title: String? = null,
|
title: String? = null,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
required: Boolean = false,
|
required: Boolean = false,
|
||||||
validator: Option<Long>.(Long?) -> Boolean = { true },
|
validator: Option<Long?>.(Long?) -> Boolean = { true },
|
||||||
) = option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option] with a string list value.
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
fun stringsOption(
|
|
||||||
key: String,
|
|
||||||
default: List<String>? = null,
|
|
||||||
values: Map<String, List<String>?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: Option<List<String>>.(List<String>?) -> Boolean = { true },
|
|
||||||
) = option(
|
) = option(
|
||||||
key,
|
key,
|
||||||
default,
|
default,
|
||||||
@@ -574,39 +341,6 @@ fun PatchBuilder<*>.stringsOption(
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option] with an integer list value.
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
fun intsOption(
|
|
||||||
key: String,
|
|
||||||
default: List<Int>? = null,
|
|
||||||
values: Map<String, List<Int>?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: Option<List<Int>>.(List<Int>?) -> Boolean = { true },
|
|
||||||
) = option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new [Option] with an integer list value and add it to the current [PatchBuilder].
|
* Create a new [Option] with an integer list value and add it to the current [PatchBuilder].
|
||||||
*
|
*
|
||||||
@@ -640,39 +374,6 @@ fun PatchBuilder<*>.intsOption(
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option] with a boolean list value.
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
fun booleansOption(
|
|
||||||
key: String,
|
|
||||||
default: List<Boolean>? = null,
|
|
||||||
values: Map<String, List<Boolean>?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: Option<List<Boolean>>.(List<Boolean>?) -> Boolean = { true },
|
|
||||||
) = option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new [Option] with a boolean list value and add it to the current [PatchBuilder].
|
* Create a new [Option] with a boolean list value and add it to the current [PatchBuilder].
|
||||||
*
|
*
|
||||||
@@ -739,39 +440,6 @@ fun PatchBuilder<*>.floatsOption(
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option] with a long list value.
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
fun longsOption(
|
|
||||||
key: String,
|
|
||||||
default: List<Long>? = null,
|
|
||||||
values: Map<String, List<Long>?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
validator: Option<List<Long>>.(List<Long>?) -> Boolean = { true },
|
|
||||||
) = option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new [Option] with a long list value and add it to the current [PatchBuilder].
|
* Create a new [Option] with a long list value and add it to the current [PatchBuilder].
|
||||||
*
|
*
|
||||||
@@ -805,40 +473,6 @@ fun PatchBuilder<*>.longsOption(
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [Option].
|
|
||||||
*
|
|
||||||
* @param key The key.
|
|
||||||
* @param default The default value.
|
|
||||||
* @param values Eligible option values mapped to a human-readable name.
|
|
||||||
* @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 [Option].
|
|
||||||
*
|
|
||||||
* @see Option
|
|
||||||
*/
|
|
||||||
inline fun <reified T> option(
|
|
||||||
key: String,
|
|
||||||
default: T? = null,
|
|
||||||
values: Map<String, T?>? = null,
|
|
||||||
title: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
required: Boolean = false,
|
|
||||||
noinline validator: Option<T>.(T?) -> Boolean = { true },
|
|
||||||
) = Option(
|
|
||||||
key,
|
|
||||||
default,
|
|
||||||
values,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
typeOf<T>(),
|
|
||||||
validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new [Option] and add it to the current [PatchBuilder].
|
* Create a new [Option] and add it to the current [PatchBuilder].
|
||||||
*
|
*
|
||||||
@@ -862,13 +496,14 @@ inline fun <reified T> PatchBuilder<*>.option(
|
|||||||
description: String? = null,
|
description: String? = null,
|
||||||
required: Boolean = false,
|
required: Boolean = false,
|
||||||
noinline validator: Option<T>.(T?) -> Boolean = { true },
|
noinline validator: Option<T>.(T?) -> Boolean = { true },
|
||||||
) = app.revanced.patcher.patch.option(
|
) = Option(
|
||||||
key,
|
key,
|
||||||
default,
|
default,
|
||||||
values,
|
values,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
required,
|
required,
|
||||||
|
typeOf<T>(),
|
||||||
validator,
|
validator,
|
||||||
).also { it() }
|
).also { it() }
|
||||||
|
|
||||||
@@ -884,26 +519,30 @@ sealed class OptionException(errorMessage: String) : Exception(errorMessage, nul
|
|||||||
* @param invalidType The type of the value that was passed.
|
* @param invalidType The type of the value that was passed.
|
||||||
* @param expectedType The type of the value that was expected.
|
* @param expectedType The type of the value that was expected.
|
||||||
*/
|
*/
|
||||||
class InvalidValueTypeException(invalidType: String, expectedType: String) : OptionException("Type $expectedType was expected but received type $invalidType")
|
class InvalidValueTypeException(invalidType: String, expectedType: String) :
|
||||||
|
OptionException("Type $expectedType was expected but received type $invalidType")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception thrown when a value did not satisfy the value conditions specified by the [Option].
|
* An exception thrown when a value did not satisfy the value conditions specified by the [Option].
|
||||||
*
|
*
|
||||||
* @param value The value that failed validation.
|
* @param value The value that failed validation.
|
||||||
*/
|
*/
|
||||||
class ValueValidationException(value: Any?, option: Option<*>) : OptionException("The option value \"$value\" failed validation for ${option.name}")
|
class ValueValidationException(value: Any?, option: Option<*>) :
|
||||||
|
OptionException("The option value \"$value\" failed validation for ${option.key}")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception thrown when a value is required but null was passed.
|
* An exception thrown when a value is required but null was passed.
|
||||||
*
|
*
|
||||||
* @param option The [Option] that requires a value.
|
* @param option The [Option] that requires a value.
|
||||||
*/
|
*/
|
||||||
class ValueRequiredException(option: Option<*>) : OptionException("The option ${option.name} requires a value, but the value was null")
|
class ValueRequiredException(option: Option<*>) :
|
||||||
|
OptionException("The option ${option.key} requires a value, but null was passed")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception thrown when a [Option] is not found.
|
* An exception thrown when a [Option] is not found.
|
||||||
*
|
*
|
||||||
* @param key The key of the [Option].
|
* @param key The key of the [Option].
|
||||||
*/
|
*/
|
||||||
class OptionNotFoundException(key: String) : OptionException("No option with key $key")
|
class OptionNotFoundException(key: String) :
|
||||||
|
OptionException("No option with key $key")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package app.revanced.patcher.patch
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
|
import app.revanced.patcher.Fingerprint
|
||||||
import app.revanced.patcher.Patcher
|
import app.revanced.patcher.Patcher
|
||||||
import app.revanced.patcher.PatcherContext
|
import app.revanced.patcher.PatcherContext
|
||||||
import dalvik.system.DexClassLoader
|
import dalvik.system.DexClassLoader
|
||||||
@@ -9,12 +10,9 @@ import lanchon.multidexlib2.BasicDexFileNamer
|
|||||||
import lanchon.multidexlib2.MultiDexIO
|
import lanchon.multidexlib2.MultiDexIO
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.lang.reflect.Member
|
|
||||||
import java.lang.reflect.Method
|
|
||||||
import java.lang.reflect.Modifier
|
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.util.function.Supplier
|
|
||||||
import java.util.jar.JarFile
|
import java.util.jar.JarFile
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
typealias PackageName = String
|
typealias PackageName = String
|
||||||
typealias VersionName = String
|
typealias VersionName = String
|
||||||
@@ -27,7 +25,7 @@ typealias Package = Pair<PackageName, Set<VersionName>?>
|
|||||||
* @param name The name of the patch.
|
* @param name The name of the patch.
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
* @param description The description of the patch.
|
* @param description The description of the patch.
|
||||||
* @param use Whether or not the patch should be used.
|
* @param use Weather or not the patch should be used.
|
||||||
* @param dependencies Other patches this patch depends on.
|
* @param dependencies Other patches this patch depends on.
|
||||||
* @param compatiblePackages The packages the patch is compatible with.
|
* @param compatiblePackages The packages the patch is compatible with.
|
||||||
* If null, the patch is compatible with all packages.
|
* If null, the patch is compatible with all packages.
|
||||||
@@ -45,75 +43,46 @@ sealed class Patch<C : PatchContext<*>>(
|
|||||||
val dependencies: Set<Patch<*>>,
|
val dependencies: Set<Patch<*>>,
|
||||||
val compatiblePackages: Set<Package>?,
|
val compatiblePackages: Set<Package>?,
|
||||||
options: Set<Option<*>>,
|
options: Set<Option<*>>,
|
||||||
private val executeBlock: (C) -> Unit,
|
private val executeBlock: Patch<C>.(C) -> Unit,
|
||||||
// Must be internal and nullable, so that Patcher.invoke can check,
|
// Must be internal and nullable, so that Patcher.invoke can check,
|
||||||
// if a patch has a finalizing block in order to not emit it twice.
|
// if a patch has a finalizing block in order to not emit it twice.
|
||||||
internal var finalizeBlock: ((C) -> Unit)?,
|
internal var finalizeBlock: (Patch<C>.(C) -> Unit)?,
|
||||||
) {
|
) {
|
||||||
/**
|
|
||||||
* The options of the patch.
|
|
||||||
*/
|
|
||||||
val options = Options(options)
|
val options = Options(options)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the execution block of the patch.
|
* Runs the execution block of the patch.
|
||||||
* This function is called by [Patcher.invoke].
|
* Called by [Patcher].
|
||||||
*
|
*
|
||||||
* @param context The [PatcherContext] to get the [PatchContext] from to execute the patch with.
|
* @param context The [PatcherContext] to get the [PatchContext] from to execute the patch with.
|
||||||
*/
|
*/
|
||||||
internal abstract fun execute(context: PatcherContext)
|
internal abstract fun execute(context: PatcherContext)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the execution block of the patch.
|
* Runs the execution block of the patch.
|
||||||
*
|
*
|
||||||
* @param context The [PatchContext] to execute the patch with.
|
* @param context The [PatchContext] to execute the patch with.
|
||||||
*/
|
*/
|
||||||
fun execute(context: C) = executeBlock(context)
|
fun execute(context: C) = executeBlock(context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the finalizing block of the patch.
|
* Runs the finalizing block of the patch.
|
||||||
* This function is called by [Patcher.invoke].
|
* Called by [Patcher].
|
||||||
*
|
*
|
||||||
* @param context The [PatcherContext] to get the [PatchContext] from to finalize the patch with.
|
* @param context The [PatcherContext] to get the [PatchContext] from to finalize the patch with.
|
||||||
*/
|
*/
|
||||||
internal abstract fun finalize(context: PatcherContext)
|
internal abstract fun finalize(context: PatcherContext)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the finalizing block of the patch.
|
* Runs the finalizing block of the patch.
|
||||||
*
|
*
|
||||||
* @param context The [PatchContext] to finalize the patch with.
|
* @param context The [PatchContext] to finalize the patch with.
|
||||||
*/
|
*/
|
||||||
fun finalize(context: C) {
|
fun finalize(context: C) {
|
||||||
finalizeBlock?.invoke(context)
|
finalizeBlock?.invoke(this, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = name ?:
|
override fun toString() = name ?: "Patch"
|
||||||
"Patch@${System.identityHashCode(this)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun Patch<*>.anyRecursively(
|
|
||||||
visited: MutableSet<Patch<*>> = mutableSetOf(),
|
|
||||||
predicate: (Patch<*>) -> Boolean,
|
|
||||||
): Boolean {
|
|
||||||
if (this in visited) return false
|
|
||||||
|
|
||||||
if (predicate(this)) return true
|
|
||||||
|
|
||||||
visited += this
|
|
||||||
|
|
||||||
return dependencies.any { it.anyRecursively(visited, predicate) }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun Iterable<Patch<*>>.forEachRecursively(
|
|
||||||
visited: MutableSet<Patch<*>> = mutableSetOf(),
|
|
||||||
action: (Patch<*>) -> Unit,
|
|
||||||
): Unit = forEach {
|
|
||||||
if (it in visited) return@forEach
|
|
||||||
|
|
||||||
visited += it
|
|
||||||
action(it)
|
|
||||||
|
|
||||||
it.dependencies.forEachRecursively(visited, action)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,12 +91,13 @@ internal fun Iterable<Patch<*>>.forEachRecursively(
|
|||||||
* @param name The name of the patch.
|
* @param name The name of the patch.
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
* @param description The description of the patch.
|
* @param description The description of the patch.
|
||||||
* @param use Whether or not the patch should be used.
|
* @param use Weather or not the patch should be used.
|
||||||
* @param compatiblePackages The packages the patch is compatible with.
|
* @param compatiblePackages The packages the patch is compatible with.
|
||||||
* If null, the patch is compatible with all packages.
|
* If null, the patch is compatible with all packages.
|
||||||
* @param dependencies Other patches this patch depends on.
|
* @param dependencies Other patches this patch depends on.
|
||||||
* @param options The options of the patch.
|
* @param options The options of the patch.
|
||||||
* @property extensionInputStream Getter for the extension input stream of the patch.
|
* @param fingerprints The fingerprints that are resolved before the patch is executed.
|
||||||
|
* @property extension An input stream of the extension resource this patch uses.
|
||||||
* An extension is a precompiled DEX file that is merged into the patched app before this patch is executed.
|
* An extension is a precompiled DEX file that is merged into the patched app before this patch is executed.
|
||||||
* @param executeBlock The execution block of the patch.
|
* @param executeBlock The execution block of the patch.
|
||||||
* @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
|
* @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
|
||||||
@@ -142,9 +112,10 @@ class BytecodePatch internal constructor(
|
|||||||
compatiblePackages: Set<Package>?,
|
compatiblePackages: Set<Package>?,
|
||||||
dependencies: Set<Patch<*>>,
|
dependencies: Set<Patch<*>>,
|
||||||
options: Set<Option<*>>,
|
options: Set<Option<*>>,
|
||||||
val extensionInputStream: Supplier<InputStream>?,
|
val fingerprints: Set<Fingerprint>,
|
||||||
executeBlock: (BytecodePatchContext) -> Unit,
|
val extension: InputStream?,
|
||||||
finalizeBlock: ((BytecodePatchContext) -> Unit)?,
|
executeBlock: Patch<BytecodePatchContext>.(BytecodePatchContext) -> Unit,
|
||||||
|
finalizeBlock: (Patch<BytecodePatchContext>.(BytecodePatchContext) -> Unit)?,
|
||||||
) : Patch<BytecodePatchContext>(
|
) : Patch<BytecodePatchContext>(
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
@@ -156,13 +127,15 @@ class BytecodePatch internal constructor(
|
|||||||
finalizeBlock,
|
finalizeBlock,
|
||||||
) {
|
) {
|
||||||
override fun execute(context: PatcherContext) = with(context.bytecodeContext) {
|
override fun execute(context: PatcherContext) = with(context.bytecodeContext) {
|
||||||
mergeExtension(this@BytecodePatch)
|
extension?.let(::merge)
|
||||||
|
fingerprints.forEach { it.match(this) }
|
||||||
|
|
||||||
execute(this)
|
execute(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun finalize(context: PatcherContext) = finalize(context.bytecodeContext)
|
override fun finalize(context: PatcherContext) = finalize(context.bytecodeContext)
|
||||||
|
|
||||||
override fun toString() = name ?: "Bytecode${super.toString()}"
|
override fun toString() = name ?: "BytecodePatch"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,7 +144,7 @@ class BytecodePatch internal constructor(
|
|||||||
* @param name The name of the patch.
|
* @param name The name of the patch.
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
* @param description The description of the patch.
|
* @param description The description of the patch.
|
||||||
* @param use Whether or not the patch should be used.
|
* @param use Weather or not the patch should be used.
|
||||||
* @param compatiblePackages The packages the patch is compatible with.
|
* @param compatiblePackages The packages the patch is compatible with.
|
||||||
* If null, the patch is compatible with all packages.
|
* If null, the patch is compatible with all packages.
|
||||||
* @param dependencies Other patches this patch depends on.
|
* @param dependencies Other patches this patch depends on.
|
||||||
@@ -189,8 +162,8 @@ class RawResourcePatch internal constructor(
|
|||||||
compatiblePackages: Set<Package>?,
|
compatiblePackages: Set<Package>?,
|
||||||
dependencies: Set<Patch<*>>,
|
dependencies: Set<Patch<*>>,
|
||||||
options: Set<Option<*>>,
|
options: Set<Option<*>>,
|
||||||
executeBlock: (ResourcePatchContext) -> Unit,
|
executeBlock: Patch<ResourcePatchContext>.(ResourcePatchContext) -> Unit,
|
||||||
finalizeBlock: ((ResourcePatchContext) -> Unit)?,
|
finalizeBlock: (Patch<ResourcePatchContext>.(ResourcePatchContext) -> Unit)?,
|
||||||
) : Patch<ResourcePatchContext>(
|
) : Patch<ResourcePatchContext>(
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
@@ -205,7 +178,7 @@ class RawResourcePatch internal constructor(
|
|||||||
|
|
||||||
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
||||||
|
|
||||||
override fun toString() = name ?: "RawResource${super.toString()}"
|
override fun toString() = name ?: "RawResourcePatch"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,7 +187,7 @@ class RawResourcePatch internal constructor(
|
|||||||
* @param name The name of the patch.
|
* @param name The name of the patch.
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
* @param description The description of the patch.
|
* @param description The description of the patch.
|
||||||
* @param use Whether or not the patch should be used.
|
* @param use Weather or not the patch should be used.
|
||||||
* @param compatiblePackages The packages the patch is compatible with.
|
* @param compatiblePackages The packages the patch is compatible with.
|
||||||
* If null, the patch is compatible with all packages.
|
* If null, the patch is compatible with all packages.
|
||||||
* @param dependencies Other patches this patch depends on.
|
* @param dependencies Other patches this patch depends on.
|
||||||
@@ -232,8 +205,8 @@ class ResourcePatch internal constructor(
|
|||||||
compatiblePackages: Set<Package>?,
|
compatiblePackages: Set<Package>?,
|
||||||
dependencies: Set<Patch<*>>,
|
dependencies: Set<Patch<*>>,
|
||||||
options: Set<Option<*>>,
|
options: Set<Option<*>>,
|
||||||
executeBlock: (ResourcePatchContext) -> Unit,
|
executeBlock: Patch<ResourcePatchContext>.(ResourcePatchContext) -> Unit,
|
||||||
finalizeBlock: ((ResourcePatchContext) -> Unit)?,
|
finalizeBlock: (Patch<ResourcePatchContext>.(ResourcePatchContext) -> Unit)?,
|
||||||
) : Patch<ResourcePatchContext>(
|
) : Patch<ResourcePatchContext>(
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
@@ -248,7 +221,7 @@ class ResourcePatch internal constructor(
|
|||||||
|
|
||||||
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
||||||
|
|
||||||
override fun toString() = name ?: "Resource${super.toString()}"
|
override fun toString() = name ?: "ResourcePatch"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,7 +231,7 @@ class ResourcePatch internal constructor(
|
|||||||
* @param name The name of the patch.
|
* @param name The name of the patch.
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
* @param description The description of the patch.
|
* @param description The description of the patch.
|
||||||
* @param use Whether or not the patch should be used.
|
* @param use Weather or not the patch should be used.
|
||||||
* @property compatiblePackages The packages the patch is compatible with.
|
* @property compatiblePackages The packages the patch is compatible with.
|
||||||
* If null, the patch is compatible with all packages.
|
* If null, the patch is compatible with all packages.
|
||||||
* @property dependencies Other patches this patch depends on.
|
* @property dependencies Other patches this patch depends on.
|
||||||
@@ -278,8 +251,8 @@ sealed class PatchBuilder<C : PatchContext<*>>(
|
|||||||
protected var dependencies = mutableSetOf<Patch<*>>()
|
protected var dependencies = mutableSetOf<Patch<*>>()
|
||||||
protected val options = mutableSetOf<Option<*>>()
|
protected val options = mutableSetOf<Option<*>>()
|
||||||
|
|
||||||
protected var executionBlock: ((C) -> Unit) = { }
|
protected var executionBlock: (Patch<C>.(C) -> Unit) = { }
|
||||||
protected var finalizeBlock: ((C) -> Unit)? = null
|
protected var finalizeBlock: (Patch<C>.(C) -> Unit)? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an option to the patch.
|
* Add an option to the patch.
|
||||||
@@ -295,14 +268,7 @@ sealed class PatchBuilder<C : PatchContext<*>>(
|
|||||||
*
|
*
|
||||||
* @param versions The versions of the package.
|
* @param versions The versions of the package.
|
||||||
*/
|
*/
|
||||||
operator fun String.invoke(vararg versions: String) = invoke(versions.toSet())
|
operator fun String.invoke(vararg versions: String) = this to versions.toSet()
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a package a patch is compatible with.
|
|
||||||
*
|
|
||||||
* @param versions The versions of the package.
|
|
||||||
*/
|
|
||||||
private operator fun String.invoke(versions: Set<String>? = null) = this to versions
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add packages the patch is compatible with.
|
* Add packages the patch is compatible with.
|
||||||
@@ -338,7 +304,7 @@ sealed class PatchBuilder<C : PatchContext<*>>(
|
|||||||
*
|
*
|
||||||
* @param block The execution block of the patch.
|
* @param block The execution block of the patch.
|
||||||
*/
|
*/
|
||||||
fun execute(block: C.() -> Unit) {
|
fun execute(block: Patch<C>.(C) -> Unit) {
|
||||||
executionBlock = block
|
executionBlock = block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +313,7 @@ sealed class PatchBuilder<C : PatchContext<*>>(
|
|||||||
*
|
*
|
||||||
* @param block The finalizing block of the patch.
|
* @param block The finalizing block of the patch.
|
||||||
*/
|
*/
|
||||||
fun finalize(block: C.() -> Unit) {
|
fun finalize(block: Patch<C>.(C) -> Unit) {
|
||||||
finalizeBlock = block
|
finalizeBlock = block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,24 +325,15 @@ sealed class PatchBuilder<C : PatchContext<*>>(
|
|||||||
internal abstract fun build(): Patch<C>
|
internal abstract fun build(): Patch<C>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a [Patch].
|
|
||||||
*
|
|
||||||
* @param B The [PatchBuilder] to build the patch with.
|
|
||||||
* @param block The block to build the patch.
|
|
||||||
*
|
|
||||||
* @return The built [Patch].
|
|
||||||
*/
|
|
||||||
private fun <B : PatchBuilder<*>> B.buildPatch(block: B.() -> Unit = {}) = apply(block).build()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [BytecodePatchBuilder] builder.
|
* A [BytecodePatchBuilder] builder.
|
||||||
*
|
*
|
||||||
* @param name The name of the patch.
|
* @param name The name of the patch.
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
* @param description The description of the patch.
|
* @param description The description of the patch.
|
||||||
* @param use Whether or not the patch should be used.
|
* @param use Weather or not the patch should be used.
|
||||||
* @property extensionInputStream Getter for the extension input stream of the patch.
|
* @property fingerprints The fingerprints that are resolved before the patch is executed.
|
||||||
|
* @property extension An input stream of the extension resource this patch uses.
|
||||||
* An extension is a precompiled DEX file that is merged into the patched app before this patch is executed.
|
* An extension is a precompiled DEX file that is merged into the patched app before this patch is executed.
|
||||||
*
|
*
|
||||||
* @constructor Create a new [BytecodePatchBuilder] builder.
|
* @constructor Create a new [BytecodePatchBuilder] builder.
|
||||||
@@ -386,9 +343,27 @@ class BytecodePatchBuilder internal constructor(
|
|||||||
description: String?,
|
description: String?,
|
||||||
use: Boolean,
|
use: Boolean,
|
||||||
) : PatchBuilder<BytecodePatchContext>(name, description, use) {
|
) : PatchBuilder<BytecodePatchContext>(name, description, use) {
|
||||||
|
private val fingerprints = mutableSetOf<Fingerprint>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the fingerprint to the patch.
|
||||||
|
*
|
||||||
|
* @return A wrapper for the fingerprint with the ability to delegate the match to the fingerprint.
|
||||||
|
*/
|
||||||
|
operator fun Fingerprint.invoke() = InvokedFingerprint(also { fingerprints.add(it) })
|
||||||
|
|
||||||
|
class InvokedFingerprint(private val fingerprint: Fingerprint) {
|
||||||
|
// The reason getValue isn't extending the Fingerprint class is
|
||||||
|
// because delegating makes only sense if the fingerprint was previously added to the patch by invoking it.
|
||||||
|
// It may be likely to forget invoking it. By wrapping the fingerprint into this class,
|
||||||
|
// the compiler will throw an error if the fingerprint was not invoked if attempting to delegate the match.
|
||||||
|
operator fun getValue(nothing: Nothing?, property: KProperty<*>) = fingerprint.match
|
||||||
|
?: throw PatchException("No fingerprint match to delegate to ${property.name}.")
|
||||||
|
}
|
||||||
|
|
||||||
// Must be internal for the inlined function "extendWith".
|
// Must be internal for the inlined function "extendWith".
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal var extensionInputStream: Supplier<InputStream>? = null
|
internal var extension: InputStream? = null
|
||||||
|
|
||||||
// Inlining is necessary to get the class loader that loaded the patch
|
// Inlining is necessary to get the class loader that loaded the patch
|
||||||
// to load the extension from the resources.
|
// to load the extension from the resources.
|
||||||
@@ -397,13 +372,9 @@ class BytecodePatchBuilder internal constructor(
|
|||||||
*
|
*
|
||||||
* @param extension The name of the extension resource.
|
* @param extension The name of the extension resource.
|
||||||
*/
|
*/
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
inline fun extendWith(extension: String) = apply {
|
inline fun extendWith(extension: String) = apply {
|
||||||
val classLoader = object {}.javaClass.classLoader
|
this.extension = object {}.javaClass.classLoader.getResourceAsStream(extension)
|
||||||
|
?: throw PatchException("Extension resource \"$extension\" not found")
|
||||||
extensionInputStream = Supplier {
|
|
||||||
classLoader.getResourceAsStream(extension) ?: throw PatchException("Extension \"$extension\" not found")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun build() = BytecodePatch(
|
override fun build() = BytecodePatch(
|
||||||
@@ -413,37 +384,20 @@ class BytecodePatchBuilder internal constructor(
|
|||||||
compatiblePackages,
|
compatiblePackages,
|
||||||
dependencies,
|
dependencies,
|
||||||
options,
|
options,
|
||||||
extensionInputStream,
|
fingerprints,
|
||||||
|
extension,
|
||||||
executionBlock,
|
executionBlock,
|
||||||
finalizeBlock,
|
finalizeBlock,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [BytecodePatch].
|
|
||||||
*
|
|
||||||
* @param name The name of the patch.
|
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
|
||||||
* @param description The description of the patch.
|
|
||||||
* @param use Whether or not the patch should be used.
|
|
||||||
* @param block The block to build the patch.
|
|
||||||
*
|
|
||||||
* @return The created [BytecodePatch].
|
|
||||||
*/
|
|
||||||
fun bytecodePatch(
|
|
||||||
name: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
use: Boolean = true,
|
|
||||||
block: BytecodePatchBuilder.() -> Unit = {},
|
|
||||||
) = BytecodePatchBuilder(name, description, use).buildPatch(block) as BytecodePatch
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [RawResourcePatch] builder.
|
* A [RawResourcePatch] builder.
|
||||||
*
|
*
|
||||||
* @param name The name of the patch.
|
* @param name The name of the patch.
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
* @param description The description of the patch.
|
* @param description The description of the patch.
|
||||||
* @param use Whether or not the patch should be used.
|
* @param use Weather or not the patch should be used.
|
||||||
*
|
*
|
||||||
* @constructor Create a new [RawResourcePatch] builder.
|
* @constructor Create a new [RawResourcePatch] builder.
|
||||||
*/
|
*/
|
||||||
@@ -464,30 +418,13 @@ class RawResourcePatchBuilder internal constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [RawResourcePatch].
|
|
||||||
*
|
|
||||||
* @param name The name of the patch.
|
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
|
||||||
* @param description The description of the patch.
|
|
||||||
* @param use Whether or not the patch should be used.
|
|
||||||
* @param block The block to build the patch.
|
|
||||||
* @return The created [RawResourcePatch].
|
|
||||||
*/
|
|
||||||
fun rawResourcePatch(
|
|
||||||
name: String? = null,
|
|
||||||
description: String? = null,
|
|
||||||
use: Boolean = true,
|
|
||||||
block: RawResourcePatchBuilder.() -> Unit = {},
|
|
||||||
) = RawResourcePatchBuilder(name, description, use).buildPatch(block) as RawResourcePatch
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [ResourcePatch] builder.
|
* A [ResourcePatch] builder.
|
||||||
*
|
*
|
||||||
* @param name The name of the patch.
|
* @param name The name of the patch.
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
* @param description The description of the patch.
|
* @param description The description of the patch.
|
||||||
* @param use Whether or not the patch should be used.
|
* @param use Weather or not the patch should be used.
|
||||||
*
|
*
|
||||||
* @constructor Create a new [ResourcePatch] builder.
|
* @constructor Create a new [ResourcePatch] builder.
|
||||||
*/
|
*/
|
||||||
@@ -508,13 +445,58 @@ class ResourcePatchBuilder internal constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a [Patch].
|
||||||
|
*
|
||||||
|
* @param B The [PatchBuilder] to build the patch with.
|
||||||
|
* @param block The block to build the patch.
|
||||||
|
*
|
||||||
|
* @return The built [Patch].
|
||||||
|
*/
|
||||||
|
private fun <B : PatchBuilder<*>> B.buildPatch(block: B.() -> Unit = {}) = apply(block).build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [BytecodePatch].
|
||||||
|
*
|
||||||
|
* @param name The name of the patch.
|
||||||
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
|
* @param description The description of the patch.
|
||||||
|
* @param use Weather or not the patch should be used.
|
||||||
|
* @param block The block to build the patch.
|
||||||
|
*
|
||||||
|
* @return The created [BytecodePatch].
|
||||||
|
*/
|
||||||
|
fun bytecodePatch(
|
||||||
|
name: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
use: Boolean = true,
|
||||||
|
block: BytecodePatchBuilder.() -> Unit = {},
|
||||||
|
) = BytecodePatchBuilder(name, description, use).buildPatch(block) as BytecodePatch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [RawResourcePatch].
|
||||||
|
*
|
||||||
|
* @param name The name of the patch.
|
||||||
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
|
* @param description The description of the patch.
|
||||||
|
* @param use Weather or not the patch should be used.
|
||||||
|
* @param block The block to build the patch.
|
||||||
|
* @return The created [RawResourcePatch].
|
||||||
|
*/
|
||||||
|
fun rawResourcePatch(
|
||||||
|
name: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
use: Boolean = true,
|
||||||
|
block: RawResourcePatchBuilder.() -> Unit = {},
|
||||||
|
) = RawResourcePatchBuilder(name, description, use).buildPatch(block) as RawResourcePatch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new [ResourcePatch].
|
* Create a new [ResourcePatch].
|
||||||
*
|
*
|
||||||
* @param name The name of the patch.
|
* @param name The name of the patch.
|
||||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||||
* @param description The description of the patch.
|
* @param description The description of the patch.
|
||||||
* @param use Whether or not the patch should be used.
|
* @param use Weather or not the patch should be used.
|
||||||
* @param block The block to build the patch.
|
* @param block The block to build the patch.
|
||||||
*
|
*
|
||||||
* @return The created [ResourcePatch].
|
* @return The created [ResourcePatch].
|
||||||
@@ -619,7 +601,7 @@ sealed class PatchLoader private constructor(
|
|||||||
*/
|
*/
|
||||||
private val Class<*>.patchFields
|
private val Class<*>.patchFields
|
||||||
get() = fields.filter { field ->
|
get() = fields.filter { field ->
|
||||||
field.type.isPatch && field.canAccess()
|
field.type.isPatch && field.canAccess(null)
|
||||||
}.map { field ->
|
}.map { field ->
|
||||||
field.get(null) as Patch<*>
|
field.get(null) as Patch<*>
|
||||||
}
|
}
|
||||||
@@ -629,7 +611,7 @@ sealed class PatchLoader private constructor(
|
|||||||
*/
|
*/
|
||||||
private val Class<*>.patchMethods
|
private val Class<*>.patchMethods
|
||||||
get() = methods.filter { method ->
|
get() = methods.filter { method ->
|
||||||
method.returnType.isPatch && method.parameterCount == 0 && method.canAccess()
|
method.returnType.isPatch && method.parameterCount == 0 && method.canAccess(null)
|
||||||
}.map { method ->
|
}.map { method ->
|
||||||
method.invoke(null) as Patch<*>
|
method.invoke(null) as Patch<*>
|
||||||
}
|
}
|
||||||
@@ -653,12 +635,6 @@ sealed class PatchLoader private constructor(
|
|||||||
it.name != null
|
it.name != null
|
||||||
}.toSet()
|
}.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Member.canAccess(): Boolean {
|
|
||||||
if (this is Method && parameterCount != 0) return false
|
|
||||||
|
|
||||||
return Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import brut.androlib.ApkDecoder
|
|||||||
import brut.androlib.apk.UsesFramework
|
import brut.androlib.apk.UsesFramework
|
||||||
import brut.androlib.res.Framework
|
import brut.androlib.res.Framework
|
||||||
import brut.androlib.res.ResourcesDecoder
|
import brut.androlib.res.ResourcesDecoder
|
||||||
import brut.androlib.res.decoder.AndroidManifestPullStreamDecoder
|
|
||||||
import brut.androlib.res.decoder.AndroidManifestResourceParser
|
import brut.androlib.res.decoder.AndroidManifestResourceParser
|
||||||
import brut.androlib.res.xml.ResXmlUtils
|
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
||||||
|
import brut.androlib.res.xml.ResXmlPatcher
|
||||||
import brut.directory.ExtFile
|
import brut.directory.ExtFile
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@@ -31,82 +31,79 @@ class ResourcePatchContext internal constructor(
|
|||||||
) : PatchContext<PatcherResult.PatchedResources?> {
|
) : PatchContext<PatcherResult.PatchedResources?> {
|
||||||
private val logger = Logger.getLogger(ResourcePatchContext::class.java.name)
|
private val logger = Logger.getLogger(ResourcePatchContext::class.java.name)
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a document from an [InputStream].
|
|
||||||
*/
|
|
||||||
fun document(inputStream: InputStream) = Document(inputStream)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read and write documents in the [PatcherConfig.apkFiles].
|
* Read and write documents in the [PatcherConfig.apkFiles].
|
||||||
*/
|
*/
|
||||||
fun document(path: String) = Document(get(path))
|
val document = DocumentOperatable()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of resources from [PatcherConfig.apkFiles] to delete.
|
* Predicate to delete resources from [PatcherConfig.apkFiles].
|
||||||
*/
|
*/
|
||||||
private val deleteResources = mutableSetOf<String>()
|
private val deleteResources = mutableSetOf<(String) -> Boolean>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode resources of [PatcherConfig.apkFile].
|
* Decode resources of [PatcherConfig.apkFile].
|
||||||
*
|
*
|
||||||
* @param mode The [ResourceMode] to use.
|
* @param mode The [ResourceMode] to use.
|
||||||
*/
|
*/
|
||||||
internal fun decodeResources(mode: ResourceMode) = with(packageMetadata.apkInfo) {
|
internal fun decodeResources(mode: ResourceMode) =
|
||||||
config.initializeTemporaryFilesDirectories()
|
with(packageMetadata.apkInfo) {
|
||||||
|
config.initializeTemporaryFilesDirectories()
|
||||||
|
|
||||||
// Needed to decode resources.
|
// Needed to decode resources.
|
||||||
val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this)
|
val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this)
|
||||||
|
|
||||||
if (mode == ResourceMode.FULL) {
|
if (mode == ResourceMode.FULL) {
|
||||||
logger.info("Decoding resources")
|
logger.info("Decoding resources")
|
||||||
|
|
||||||
resourcesDecoder.decodeResources(config.apkFiles)
|
resourcesDecoder.decodeResources(config.apkFiles)
|
||||||
resourcesDecoder.decodeManifest(config.apkFiles)
|
resourcesDecoder.decodeManifest(config.apkFiles)
|
||||||
|
|
||||||
// Needed to record uncompressed files.
|
// Needed to record uncompressed files.
|
||||||
ApkDecoder(this, config.resourceConfig).recordUncompressedFiles(resourcesDecoder.resFileMapping)
|
val apkDecoder = ApkDecoder(config.resourceConfig, this)
|
||||||
|
apkDecoder.recordUncompressedFiles(resourcesDecoder.resFileMapping)
|
||||||
|
|
||||||
usesFramework =
|
usesFramework =
|
||||||
UsesFramework().apply {
|
UsesFramework().apply {
|
||||||
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
|
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.info("Decoding app manifest")
|
|
||||||
|
|
||||||
// Decode manually instead of using resourceDecoder.decodeManifest
|
|
||||||
// because it does not support decoding to an OutputStream.
|
|
||||||
AndroidManifestPullStreamDecoder(
|
|
||||||
AndroidManifestResourceParser(resourcesDecoder.resTable),
|
|
||||||
resourcesDecoder.newXmlSerializer(),
|
|
||||||
).decode(
|
|
||||||
apkFile.directory.getFileInput("AndroidManifest.xml"),
|
|
||||||
// Older Android versions do not support OutputStream.nullOutputStream()
|
|
||||||
object : OutputStream() {
|
|
||||||
override fun write(b: Int) { // Do nothing.
|
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
)
|
logger.info("Decoding app manifest")
|
||||||
|
|
||||||
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
|
// Decode manually instead of using resourceDecoder.decodeManifest
|
||||||
// AndroidManifestPullStreamDecoder.decode() sets metadata.apkInfo.
|
// because it does not support decoding to an OutputStream.
|
||||||
packageMetadata.let { metadata ->
|
XmlPullStreamDecoder(
|
||||||
metadata.packageName = resourcesDecoder.resTable.packageRenamed
|
AndroidManifestResourceParser(resourcesDecoder.resTable),
|
||||||
versionInfo.let {
|
resourcesDecoder.resXmlSerializer,
|
||||||
metadata.packageVersion = it.versionName ?: it.versionCode
|
).decodeManifest(
|
||||||
|
apkFile.directory.getFileInput("AndroidManifest.xml"),
|
||||||
|
// Older Android versions do not support OutputStream.nullOutputStream()
|
||||||
|
object : OutputStream() {
|
||||||
|
override fun write(b: Int) { // Do nothing.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
|
||||||
|
// XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo.
|
||||||
|
packageMetadata.let { metadata ->
|
||||||
|
metadata.packageName = resourcesDecoder.resTable.packageRenamed
|
||||||
|
versionInfo.let {
|
||||||
|
metadata.packageVersion = it.versionName ?: it.versionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The ResTable if flagged as sparse if the main package is not loaded, which is the case here,
|
||||||
|
because ResourcesDecoder.decodeResources loads the main package
|
||||||
|
and not XmlPullStreamDecoder.decodeManifest.
|
||||||
|
See ARSCDecoder.readTableType for more info.
|
||||||
|
|
||||||
|
Set this to false again to prevent the ResTable from being flagged as sparse falsely.
|
||||||
|
*/
|
||||||
|
metadata.apkInfo.sparseResources = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
The ResTable if flagged as sparse if the main package is not loaded, which is the case here,
|
|
||||||
because ResourcesDecoder.decodeResources loads the main package
|
|
||||||
and not AndroidManifestPullStreamDecoder.decode.
|
|
||||||
See ARSCDecoder.readTableType for more info.
|
|
||||||
|
|
||||||
Set this to false again to prevent the ResTable from being flagged as sparse falsely.
|
|
||||||
*/
|
|
||||||
metadata.apkInfo.sparseResources = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile resources in [PatcherConfig.apkFiles].
|
* Compile resources in [PatcherConfig.apkFiles].
|
||||||
@@ -128,10 +125,10 @@ class ResourcePatchContext internal constructor(
|
|||||||
AaptInvoker(
|
AaptInvoker(
|
||||||
config.resourceConfig,
|
config.resourceConfig,
|
||||||
packageMetadata.apkInfo,
|
packageMetadata.apkInfo,
|
||||||
).invoke(
|
).invokeAapt(
|
||||||
resources.resolve("resources.apk"),
|
resources.resolve("resources.apk"),
|
||||||
config.apkFiles.resolve("AndroidManifest.xml").also {
|
config.apkFiles.resolve("AndroidManifest.xml").also {
|
||||||
ResXmlUtils.fixingPublicAttrsInProviderAttributes(it)
|
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it)
|
||||||
},
|
},
|
||||||
config.apkFiles.resolve("res"),
|
config.apkFiles.resolve("res"),
|
||||||
null,
|
null,
|
||||||
@@ -204,11 +201,11 @@ class ResourcePatchContext internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a file for deletion when the APK is rebuilt.
|
* Stage a file to be deleted from [PatcherConfig.apkFile].
|
||||||
*
|
*
|
||||||
* @param name The name of the file to delete.
|
* @param shouldDelete The predicate to stage the file for deletion given its name.
|
||||||
*/
|
*/
|
||||||
fun delete(name: String) = deleteResources.add(name)
|
fun stageDelete(shouldDelete: (String) -> Boolean) = deleteResources.add(shouldDelete)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How to handle resources decoding and compiling.
|
* How to handle resources decoding and compiling.
|
||||||
@@ -230,4 +227,10 @@ class ResourcePatchContext internal constructor(
|
|||||||
*/
|
*/
|
||||||
NONE,
|
NONE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner class DocumentOperatable {
|
||||||
|
operator fun get(inputStream: InputStream) = Document(inputStream)
|
||||||
|
|
||||||
|
operator fun get(path: String) = Document(this@ResourcePatchContext[path])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ internal object ClassMerger {
|
|||||||
|
|
||||||
if (missingMethods.isEmpty()) return this
|
if (missingMethods.isEmpty()) return this
|
||||||
|
|
||||||
logger.fine { "Found ${missingMethods.size} missing methods" }
|
logger.fine("Found ${missingMethods.size} missing methods")
|
||||||
|
|
||||||
return asMutableClass().apply {
|
return asMutableClass().apply {
|
||||||
methods.addAll(missingMethods.map { it.toMutable() })
|
methods.addAll(missingMethods.map { it.toMutable() })
|
||||||
@@ -80,7 +80,7 @@ internal object ClassMerger {
|
|||||||
|
|
||||||
if (missingFields.isEmpty()) return this
|
if (missingFields.isEmpty()) return this
|
||||||
|
|
||||||
logger.fine { "Found ${missingFields.size} missing fields" }
|
logger.fine("Found ${missingFields.size} missing fields")
|
||||||
|
|
||||||
return asMutableClass().apply {
|
return asMutableClass().apply {
|
||||||
fields.addAll(missingFields.map { it.toMutable() })
|
fields.addAll(missingFields.map { it.toMutable() })
|
||||||
@@ -100,7 +100,7 @@ internal object ClassMerger {
|
|||||||
context.traverseClassHierarchy(this) {
|
context.traverseClassHierarchy(this) {
|
||||||
if (accessFlags.isPublic()) return@traverseClassHierarchy
|
if (accessFlags.isPublic()) return@traverseClassHierarchy
|
||||||
|
|
||||||
logger.fine { "Publicizing ${this.type}" }
|
logger.fine("Publicizing ${this.type}")
|
||||||
|
|
||||||
accessFlags = accessFlags.toPublic()
|
accessFlags = accessFlags.toPublic()
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ internal object ClassMerger {
|
|||||||
|
|
||||||
if (brokenFields.isEmpty()) return this
|
if (brokenFields.isEmpty()) return this
|
||||||
|
|
||||||
logger.fine { "Found ${brokenFields.size} broken fields" }
|
logger.fine("Found ${brokenFields.size} broken fields")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a field public.
|
* Make a field public.
|
||||||
@@ -153,7 +153,7 @@ internal object ClassMerger {
|
|||||||
|
|
||||||
if (brokenMethods.isEmpty()) return this
|
if (brokenMethods.isEmpty()) return this
|
||||||
|
|
||||||
logger.fine { "Found ${brokenMethods.size} methods" }
|
logger.fine("Found ${brokenMethods.size} methods")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a method public.
|
* Make a method public.
|
||||||
@@ -179,9 +179,7 @@ internal object ClassMerger {
|
|||||||
callback: MutableClass.() -> Unit,
|
callback: MutableClass.() -> Unit,
|
||||||
) {
|
) {
|
||||||
callback(targetClass)
|
callback(targetClass)
|
||||||
|
this.classByType(targetClass.superclass ?: return)?.mutableClass?.let {
|
||||||
targetClass.superclass ?: return
|
|
||||||
this.classBy { targetClass.superclass == it.type }?.mutableClass?.let {
|
|
||||||
traverseClassHierarchy(it, callback)
|
traverseClassHierarchy(it, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.io.Closeable
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
import javax.xml.transform.OutputKeys
|
|
||||||
import javax.xml.transform.TransformerFactory
|
import javax.xml.transform.TransformerFactory
|
||||||
import javax.xml.transform.dom.DOMSource
|
import javax.xml.transform.dom.DOMSource
|
||||||
import javax.xml.transform.stream.StreamResult
|
import javax.xml.transform.stream.StreamResult
|
||||||
@@ -35,22 +34,15 @@ class Document internal constructor(
|
|||||||
readerCount.remove(it)
|
readerCount.remove(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val transformer = TransformerFactory.newInstance().newTransformer()
|
it.outputStream().use { stream ->
|
||||||
// Set to UTF-16 to prevent surrogate pairs from being escaped to invalid numeric character references, but save as UTF-8.
|
TransformerFactory.newInstance()
|
||||||
if (isAndroid) {
|
.newTransformer()
|
||||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-16")
|
.transform(DOMSource(this), StreamResult(stream))
|
||||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
|
|
||||||
it.bufferedWriter(charset = Charsets.UTF_8).use { writer ->
|
|
||||||
transformer.transform(DOMSource(this), StreamResult(writer))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
transformer.transform(DOMSource(this), StreamResult(it))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private val readerCount = mutableMapOf<File, Int>()
|
private val readerCount = mutableMapOf<File, Int>()
|
||||||
private val isAndroid = System.getProperty("java.runtime.name") == "Android Runtime"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ 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.instruction.ReferenceInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A navigator for methods.
|
* A navigator for methods.
|
||||||
*
|
*
|
||||||
|
* @param context The [BytecodePatchContext] to use.
|
||||||
* @param startMethod The [Method] to start navigating from.
|
* @param startMethod The [Method] to start navigating from.
|
||||||
*
|
*
|
||||||
* @constructor Creates a new [MethodNavigator].
|
* @constructor Creates a new [MethodNavigator].
|
||||||
@@ -24,16 +24,12 @@ import kotlin.reflect.KProperty
|
|||||||
* @throws NavigateException If the method does not have an implementation.
|
* @throws NavigateException If the method does not have an implementation.
|
||||||
* @throws NavigateException If the instruction at the specified index is not a method reference.
|
* @throws NavigateException If the instruction at the specified index is not a method reference.
|
||||||
*/
|
*/
|
||||||
context(BytecodePatchContext)
|
class MethodNavigator internal constructor(private val context: BytecodePatchContext, private var startMethod: MethodReference) {
|
||||||
class MethodNavigator internal constructor(
|
|
||||||
private var startMethod: MethodReference,
|
|
||||||
) {
|
|
||||||
private var lastNavigatedMethodReference = startMethod
|
private var lastNavigatedMethodReference = startMethod
|
||||||
|
|
||||||
private val lastNavigatedMethodInstructions
|
private val lastNavigatedMethodInstructions get() = with(immutable()) {
|
||||||
get() = with(original()) {
|
instructionsOrNull ?: throw NavigateException("Method $definingClass.$name does not have an implementation.")
|
||||||
instructionsOrNull ?: throw NavigateException("Method $this does not have an implementation.")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to the method at the specified index.
|
* Navigate to the method at the specified index.
|
||||||
@@ -42,7 +38,7 @@ class MethodNavigator internal constructor(
|
|||||||
*
|
*
|
||||||
* @return This [MethodNavigator].
|
* @return This [MethodNavigator].
|
||||||
*/
|
*/
|
||||||
fun to(vararg index: Int): MethodNavigator {
|
fun at(vararg index: Int): MethodNavigator {
|
||||||
index.forEach {
|
index.forEach {
|
||||||
lastNavigatedMethodReference = lastNavigatedMethodInstructions.getMethodReferenceAt(it)
|
lastNavigatedMethodReference = lastNavigatedMethodInstructions.getMethodReferenceAt(it)
|
||||||
}
|
}
|
||||||
@@ -56,7 +52,7 @@ class MethodNavigator internal constructor(
|
|||||||
* @param index The index of the method to navigate to.
|
* @param index The index of the method to navigate to.
|
||||||
* @param predicate The predicate to match.
|
* @param predicate The predicate to match.
|
||||||
*/
|
*/
|
||||||
fun to(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator {
|
fun at(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator {
|
||||||
lastNavigatedMethodReference = lastNavigatedMethodInstructions.asSequence()
|
lastNavigatedMethodReference = lastNavigatedMethodInstructions.asSequence()
|
||||||
.filter(predicate).asIterable().getMethodReferenceAt(index)
|
.filter(predicate).asIterable().getMethodReferenceAt(index)
|
||||||
|
|
||||||
@@ -80,22 +76,15 @@ class MethodNavigator internal constructor(
|
|||||||
*
|
*
|
||||||
* @return The last navigated method mutably.
|
* @return The last navigated method mutably.
|
||||||
*/
|
*/
|
||||||
fun stop() = classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
|
fun mutable() = context.classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
|
||||||
as MutableMethod
|
as MutableMethod
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the last navigated method mutably.
|
|
||||||
*
|
|
||||||
* @return The last navigated method mutably.
|
|
||||||
*/
|
|
||||||
operator fun getValue(nothing: Nothing?, property: KProperty<*>) = stop()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the last navigated method immutably.
|
* Get the last navigated method immutably.
|
||||||
*
|
*
|
||||||
* @return The last navigated method immutably.
|
* @return The last navigated method immutably.
|
||||||
*/
|
*/
|
||||||
fun original(): Method = classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
|
fun immutable() = context.classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Predicate to match the class defining the current method reference.
|
* Predicate to match the class defining the current method reference.
|
||||||
@@ -107,10 +96,9 @@ class MethodNavigator internal constructor(
|
|||||||
/**
|
/**
|
||||||
* Find the first [lastNavigatedMethodReference] in the class.
|
* Find the first [lastNavigatedMethodReference] in the class.
|
||||||
*/
|
*/
|
||||||
private val ClassDef.firstMethodBySignature
|
private val ClassDef.firstMethodBySignature get() = methods.first {
|
||||||
get() = methods.first {
|
MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
|
||||||
MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception thrown when navigating fails.
|
* An exception thrown when navigating fails.
|
||||||
|
|||||||
@@ -9,13 +9,21 @@ class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
|
|||||||
private val type = annotation.type
|
private val type = annotation.type
|
||||||
private val _elements by lazy { annotation.elements.map { element -> element.toMutable() }.toMutableSet() }
|
private val _elements by lazy { annotation.elements.map { element -> element.toMutable() }.toMutableSet() }
|
||||||
|
|
||||||
override fun getType(): String = type
|
override fun getType(): String {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
override fun getElements(): MutableSet<MutableAnnotationElement> = _elements
|
override fun getElements(): MutableSet<MutableAnnotationElement> {
|
||||||
|
return _elements
|
||||||
|
}
|
||||||
|
|
||||||
override fun getVisibility(): Int = visibility
|
override fun getVisibility(): Int {
|
||||||
|
return visibility
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun Annotation.toMutable(): MutableAnnotation = MutableAnnotation(this)
|
fun Annotation.toMutable(): MutableAnnotation {
|
||||||
|
return MutableAnnotation(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,17 @@ class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnot
|
|||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getName(): String = name
|
override fun getName(): String {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
override fun getValue(): EncodedValue = value
|
override fun getValue(): EncodedValue {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun AnnotationElement.toMutable(): MutableAnnotationElement = MutableAnnotationElement(this)
|
fun AnnotationElement.toMutable(): MutableAnnotationElement {
|
||||||
|
return MutableAnnotationElement(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ import com.android.tools.smali.dexlib2.base.reference.BaseTypeReference
|
|||||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
import com.android.tools.smali.dexlib2.util.FieldUtil
|
import com.android.tools.smali.dexlib2.util.FieldUtil
|
||||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
|
import com.google.common.collect.Iterables
|
||||||
|
|
||||||
class MutableClass(classDef: ClassDef) :
|
class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
|
||||||
BaseTypeReference(),
|
|
||||||
ClassDef {
|
|
||||||
// Class
|
// Class
|
||||||
private var type = classDef.type
|
private var type = classDef.type
|
||||||
private var sourceFile = classDef.sourceFile
|
private var sourceFile = classDef.sourceFile
|
||||||
@@ -24,13 +23,13 @@ class MutableClass(classDef: ClassDef) :
|
|||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
private val _methods by lazy { classDef.methods.map { method -> method.toMutable() }.toMutableSet() }
|
private val _methods by lazy { classDef.methods.map { method -> method.toMutable() }.toMutableSet() }
|
||||||
private val _directMethods by lazy { _methods.filter { method -> MethodUtil.isDirect(method) }.toMutableSet() }
|
private val _directMethods by lazy { Iterables.filter(_methods, MethodUtil.METHOD_IS_DIRECT).toMutableSet() }
|
||||||
private val _virtualMethods by lazy { _methods.filter { method -> !MethodUtil.isDirect(method) }.toMutableSet() }
|
private val _virtualMethods by lazy { Iterables.filter(_methods, MethodUtil.METHOD_IS_VIRTUAL).toMutableSet() }
|
||||||
|
|
||||||
// Fields
|
// Fields
|
||||||
private val _fields by lazy { classDef.fields.map { field -> field.toMutable() }.toMutableSet() }
|
private val _fields by lazy { classDef.fields.map { field -> field.toMutable() }.toMutableSet() }
|
||||||
private val _staticFields by lazy { _fields.filter { field -> FieldUtil.isStatic(field) }.toMutableSet() }
|
private val _staticFields by lazy { Iterables.filter(_fields, FieldUtil.FIELD_IS_STATIC).toMutableSet() }
|
||||||
private val _instanceFields by lazy { _fields.filter { field -> !FieldUtil.isStatic(field) }.toMutableSet() }
|
private val _instanceFields by lazy { Iterables.filter(_fields, FieldUtil.FIELD_IS_INSTANCE).toMutableSet() }
|
||||||
|
|
||||||
fun setType(type: String) {
|
fun setType(type: String) {
|
||||||
this.type = type
|
this.type = type
|
||||||
@@ -48,31 +47,57 @@ class MutableClass(classDef: ClassDef) :
|
|||||||
this.superclass = superclass
|
this.superclass = superclass
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getType(): String = type
|
override fun getType(): String {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAccessFlags(): Int = accessFlags
|
override fun getAccessFlags(): Int {
|
||||||
|
return accessFlags
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSourceFile(): String? = sourceFile
|
override fun getSourceFile(): String? {
|
||||||
|
return sourceFile
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSuperclass(): String? = superclass
|
override fun getSuperclass(): String? {
|
||||||
|
return superclass
|
||||||
|
}
|
||||||
|
|
||||||
override fun getInterfaces(): MutableList<String> = _interfaces
|
override fun getInterfaces(): MutableList<String> {
|
||||||
|
return _interfaces
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAnnotations(): MutableSet<MutableAnnotation> = _annotations
|
override fun getAnnotations(): MutableSet<MutableAnnotation> {
|
||||||
|
return _annotations
|
||||||
|
}
|
||||||
|
|
||||||
override fun getStaticFields(): MutableSet<MutableField> = _staticFields
|
override fun getStaticFields(): MutableSet<MutableField> {
|
||||||
|
return _staticFields
|
||||||
|
}
|
||||||
|
|
||||||
override fun getInstanceFields(): MutableSet<MutableField> = _instanceFields
|
override fun getInstanceFields(): MutableSet<MutableField> {
|
||||||
|
return _instanceFields
|
||||||
|
}
|
||||||
|
|
||||||
override fun getFields(): MutableSet<MutableField> = _fields
|
override fun getFields(): MutableSet<MutableField> {
|
||||||
|
return _fields
|
||||||
|
}
|
||||||
|
|
||||||
override fun getDirectMethods(): MutableSet<MutableMethod> = _directMethods
|
override fun getDirectMethods(): MutableSet<MutableMethod> {
|
||||||
|
return _directMethods
|
||||||
|
}
|
||||||
|
|
||||||
override fun getVirtualMethods(): MutableSet<MutableMethod> = _virtualMethods
|
override fun getVirtualMethods(): MutableSet<MutableMethod> {
|
||||||
|
return _virtualMethods
|
||||||
|
}
|
||||||
|
|
||||||
override fun getMethods(): MutableSet<MutableMethod> = _methods
|
override fun getMethods(): MutableSet<MutableMethod> {
|
||||||
|
return _methods
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun ClassDef.toMutable(): MutableClass = MutableClass(this)
|
fun ClassDef.toMutable(): MutableClass {
|
||||||
|
return MutableClass(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import com.android.tools.smali.dexlib2.HiddenApiRestriction
|
|||||||
import com.android.tools.smali.dexlib2.base.reference.BaseFieldReference
|
import com.android.tools.smali.dexlib2.base.reference.BaseFieldReference
|
||||||
import com.android.tools.smali.dexlib2.iface.Field
|
import com.android.tools.smali.dexlib2.iface.Field
|
||||||
|
|
||||||
class MutableField(field: Field) :
|
class MutableField(field: Field) : Field, BaseFieldReference() {
|
||||||
BaseFieldReference(),
|
|
||||||
Field {
|
|
||||||
private var definingClass = field.definingClass
|
private var definingClass = field.definingClass
|
||||||
private var name = field.name
|
private var name = field.name
|
||||||
private var type = field.type
|
private var type = field.type
|
||||||
@@ -39,21 +37,37 @@ class MutableField(field: Field) :
|
|||||||
this.initialValue = initialValue
|
this.initialValue = initialValue
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDefiningClass(): String = this.definingClass
|
override fun getDefiningClass(): String {
|
||||||
|
return this.definingClass
|
||||||
|
}
|
||||||
|
|
||||||
override fun getName(): String = this.name
|
override fun getName(): String {
|
||||||
|
return this.name
|
||||||
|
}
|
||||||
|
|
||||||
override fun getType(): String = this.type
|
override fun getType(): String {
|
||||||
|
return this.type
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAnnotations(): MutableSet<MutableAnnotation> = this._annotations
|
override fun getAnnotations(): MutableSet<MutableAnnotation> {
|
||||||
|
return this._annotations
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAccessFlags(): Int = this.accessFlags
|
override fun getAccessFlags(): Int {
|
||||||
|
return this.accessFlags
|
||||||
|
}
|
||||||
|
|
||||||
override fun getHiddenApiRestrictions(): MutableSet<HiddenApiRestriction> = this._hiddenApiRestrictions
|
override fun getHiddenApiRestrictions(): MutableSet<HiddenApiRestriction> {
|
||||||
|
return this._hiddenApiRestrictions
|
||||||
|
}
|
||||||
|
|
||||||
override fun getInitialValue(): MutableEncodedValue? = this.initialValue
|
override fun getInitialValue(): MutableEncodedValue? {
|
||||||
|
return this.initialValue
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun Field.toMutable(): MutableField = MutableField(this)
|
fun Field.toMutable(): MutableField {
|
||||||
|
return MutableField(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import com.android.tools.smali.dexlib2.base.reference.BaseMethodReference
|
|||||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||||
import com.android.tools.smali.dexlib2.iface.Method
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
|
|
||||||
class MutableMethod(method: Method) :
|
class MutableMethod(method: Method) : Method, BaseMethodReference() {
|
||||||
BaseMethodReference(),
|
|
||||||
Method {
|
|
||||||
private var definingClass = method.definingClass
|
private var definingClass = method.definingClass
|
||||||
private var name = method.name
|
private var name = method.name
|
||||||
private var accessFlags = method.accessFlags
|
private var accessFlags = method.accessFlags
|
||||||
@@ -38,25 +36,45 @@ class MutableMethod(method: Method) :
|
|||||||
this.returnType = returnType
|
this.returnType = returnType
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDefiningClass(): String = definingClass
|
override fun getDefiningClass(): String {
|
||||||
|
return definingClass
|
||||||
|
}
|
||||||
|
|
||||||
override fun getName(): String = name
|
override fun getName(): String {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
override fun getParameterTypes(): MutableList<CharSequence> = _parameterTypes
|
override fun getParameterTypes(): MutableList<CharSequence> {
|
||||||
|
return _parameterTypes
|
||||||
|
}
|
||||||
|
|
||||||
override fun getReturnType(): String = returnType
|
override fun getReturnType(): String {
|
||||||
|
return returnType
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAnnotations(): MutableSet<MutableAnnotation> = _annotations
|
override fun getAnnotations(): MutableSet<MutableAnnotation> {
|
||||||
|
return _annotations
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAccessFlags(): Int = accessFlags
|
override fun getAccessFlags(): Int {
|
||||||
|
return accessFlags
|
||||||
|
}
|
||||||
|
|
||||||
override fun getHiddenApiRestrictions(): MutableSet<HiddenApiRestriction> = _hiddenApiRestrictions
|
override fun getHiddenApiRestrictions(): MutableSet<HiddenApiRestriction> {
|
||||||
|
return _hiddenApiRestrictions
|
||||||
|
}
|
||||||
|
|
||||||
override fun getParameters(): MutableList<MutableMethodParameter> = _parameters
|
override fun getParameters(): MutableList<MutableMethodParameter> {
|
||||||
|
return _parameters
|
||||||
|
}
|
||||||
|
|
||||||
override fun getImplementation(): MutableMethodImplementation? = _implementation
|
override fun getImplementation(): MutableMethodImplementation? {
|
||||||
|
return _implementation
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun Method.toMutable(): MutableMethod = MutableMethod(this)
|
fun Method.toMutable(): MutableMethod {
|
||||||
|
return MutableMethod(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import com.android.tools.smali.dexlib2.base.BaseMethodParameter
|
|||||||
import com.android.tools.smali.dexlib2.iface.MethodParameter
|
import com.android.tools.smali.dexlib2.iface.MethodParameter
|
||||||
|
|
||||||
// TODO: finish overriding all members if necessary
|
// TODO: finish overriding all members if necessary
|
||||||
class MutableMethodParameter(parameter: MethodParameter) :
|
class MutableMethodParameter(parameter: MethodParameter) : MethodParameter, BaseMethodParameter() {
|
||||||
BaseMethodParameter(),
|
|
||||||
MethodParameter {
|
|
||||||
private var type = parameter.type
|
private var type = parameter.type
|
||||||
private var name = parameter.name
|
private var name = parameter.name
|
||||||
private var signature = parameter.signature
|
private var signature = parameter.signature
|
||||||
@@ -15,15 +13,25 @@ class MutableMethodParameter(parameter: MethodParameter) :
|
|||||||
parameter.annotations.map { annotation -> annotation.toMutable() }.toMutableSet()
|
parameter.annotations.map { annotation -> annotation.toMutable() }.toMutableSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getType(): String = type
|
override fun getType(): String {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
override fun getName(): String? = name
|
override fun getName(): String? {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSignature(): String? = signature
|
override fun getSignature(): String? {
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAnnotations(): MutableSet<MutableAnnotation> = _annotations
|
override fun getAnnotations(): MutableSet<MutableAnnotation> {
|
||||||
|
return _annotations
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun MethodParameter.toMutable(): MutableMethodParameter = MutableMethodParameter(this)
|
fun MethodParameter.toMutable(): MutableMethodParameter {
|
||||||
|
return MutableMethodParameter(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,21 @@ class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedVal
|
|||||||
annotationEncodedValue.elements.map { annotationElement -> annotationElement.toMutable() }.toMutableSet()
|
annotationEncodedValue.elements.map { annotationElement -> annotationElement.toMutable() }.toMutableSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getType(): String = this.type
|
override fun getType(): String {
|
||||||
|
return this.type
|
||||||
|
}
|
||||||
|
|
||||||
fun setType(type: String) {
|
fun setType(type: String) {
|
||||||
this.type = type
|
this.type = type
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getElements(): MutableSet<out AnnotationElement> = _elements
|
override fun getElements(): MutableSet<out AnnotationElement> {
|
||||||
|
return _elements
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun AnnotationEncodedValue.toMutable(): MutableAnnotationEncodedValue = MutableAnnotationEncodedValue(this)
|
fun AnnotationEncodedValue.toMutable(): MutableAnnotationEncodedValue {
|
||||||
|
return MutableAnnotationEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,18 @@ import com.android.tools.smali.dexlib2.base.value.BaseArrayEncodedValue
|
|||||||
import com.android.tools.smali.dexlib2.iface.value.ArrayEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.ArrayEncodedValue
|
||||||
import com.android.tools.smali.dexlib2.iface.value.EncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.EncodedValue
|
||||||
|
|
||||||
class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) :
|
class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) : BaseArrayEncodedValue(), MutableEncodedValue {
|
||||||
BaseArrayEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private val _value by lazy {
|
private val _value by lazy {
|
||||||
arrayEncodedValue.value.map { encodedValue -> encodedValue.toMutable() }.toMutableList()
|
arrayEncodedValue.value.map { encodedValue -> encodedValue.toMutable() }.toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getValue(): MutableList<out EncodedValue> = _value
|
override fun getValue(): MutableList<out EncodedValue> {
|
||||||
|
return _value
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun ArrayEncodedValue.toMutable(): MutableArrayEncodedValue = MutableArrayEncodedValue(this)
|
fun ArrayEncodedValue.toMutable(): MutableArrayEncodedValue {
|
||||||
|
return MutableArrayEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,17 @@ class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) :
|
|||||||
MutableEncodedValue {
|
MutableEncodedValue {
|
||||||
private var value = booleanEncodedValue.value
|
private var value = booleanEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): Boolean = this.value
|
override fun getValue(): Boolean {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: Boolean) {
|
fun setValue(value: Boolean) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun BooleanEncodedValue.toMutable(): MutableBooleanEncodedValue = MutableBooleanEncodedValue(this)
|
fun BooleanEncodedValue.toMutable(): MutableBooleanEncodedValue {
|
||||||
|
return MutableBooleanEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
|||||||
import com.android.tools.smali.dexlib2.base.value.BaseByteEncodedValue
|
import com.android.tools.smali.dexlib2.base.value.BaseByteEncodedValue
|
||||||
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
||||||
|
|
||||||
class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) :
|
class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) : BaseByteEncodedValue(), MutableEncodedValue {
|
||||||
BaseByteEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private var value = byteEncodedValue.value
|
private var value = byteEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): Byte = this.value
|
override fun getValue(): Byte {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: Byte) {
|
fun setValue(value: Byte) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue = MutableByteEncodedValue(this)
|
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue {
|
||||||
|
return MutableByteEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
|||||||
import com.android.tools.smali.dexlib2.base.value.BaseCharEncodedValue
|
import com.android.tools.smali.dexlib2.base.value.BaseCharEncodedValue
|
||||||
import com.android.tools.smali.dexlib2.iface.value.CharEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.CharEncodedValue
|
||||||
|
|
||||||
class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) :
|
class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) : BaseCharEncodedValue(), MutableEncodedValue {
|
||||||
BaseCharEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private var value = charEncodedValue.value
|
private var value = charEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): Char = this.value
|
override fun getValue(): Char {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: Char) {
|
fun setValue(value: Char) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun CharEncodedValue.toMutable(): MutableCharEncodedValue = MutableCharEncodedValue(this)
|
fun CharEncodedValue.toMutable(): MutableCharEncodedValue {
|
||||||
|
return MutableCharEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,17 @@ class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) :
|
|||||||
MutableEncodedValue {
|
MutableEncodedValue {
|
||||||
private var value = doubleEncodedValue.value
|
private var value = doubleEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): Double = this.value
|
override fun getValue(): Double {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: Double) {
|
fun setValue(value: Double) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun DoubleEncodedValue.toMutable(): MutableDoubleEncodedValue = MutableDoubleEncodedValue(this)
|
fun DoubleEncodedValue.toMutable(): MutableDoubleEncodedValue {
|
||||||
|
return MutableDoubleEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,20 @@ import com.android.tools.smali.dexlib2.base.value.BaseEnumEncodedValue
|
|||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
import com.android.tools.smali.dexlib2.iface.value.EnumEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.EnumEncodedValue
|
||||||
|
|
||||||
class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) :
|
class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) : BaseEnumEncodedValue(), MutableEncodedValue {
|
||||||
BaseEnumEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private var value = enumEncodedValue.value
|
private var value = enumEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): FieldReference = this.value
|
override fun getValue(): FieldReference {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: FieldReference) {
|
fun setValue(value: FieldReference) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun EnumEncodedValue.toMutable(): MutableEnumEncodedValue = MutableEnumEncodedValue(this)
|
fun EnumEncodedValue.toMutable(): MutableEnumEncodedValue {
|
||||||
|
return MutableEnumEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,20 +5,24 @@ import com.android.tools.smali.dexlib2.base.value.BaseFieldEncodedValue
|
|||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
import com.android.tools.smali.dexlib2.iface.value.FieldEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.FieldEncodedValue
|
||||||
|
|
||||||
class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) :
|
class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) : BaseFieldEncodedValue(), MutableEncodedValue {
|
||||||
BaseFieldEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private var value = fieldEncodedValue.value
|
private var value = fieldEncodedValue.value
|
||||||
|
|
||||||
override fun getValueType(): Int = ValueType.FIELD
|
override fun getValueType(): Int {
|
||||||
|
return ValueType.FIELD
|
||||||
|
}
|
||||||
|
|
||||||
override fun getValue(): FieldReference = this.value
|
override fun getValue(): FieldReference {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: FieldReference) {
|
fun setValue(value: FieldReference) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun FieldEncodedValue.toMutable(): MutableFieldEncodedValue = MutableFieldEncodedValue(this)
|
fun FieldEncodedValue.toMutable(): MutableFieldEncodedValue {
|
||||||
|
return MutableFieldEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
|||||||
import com.android.tools.smali.dexlib2.base.value.BaseFloatEncodedValue
|
import com.android.tools.smali.dexlib2.base.value.BaseFloatEncodedValue
|
||||||
import com.android.tools.smali.dexlib2.iface.value.FloatEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.FloatEncodedValue
|
||||||
|
|
||||||
class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) :
|
class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) : BaseFloatEncodedValue(), MutableEncodedValue {
|
||||||
BaseFloatEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private var value = floatEncodedValue.value
|
private var value = floatEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): Float = this.value
|
override fun getValue(): Float {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: Float) {
|
fun setValue(value: Float) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun FloatEncodedValue.toMutable(): MutableFloatEncodedValue = MutableFloatEncodedValue(this)
|
fun FloatEncodedValue.toMutable(): MutableFloatEncodedValue {
|
||||||
|
return MutableFloatEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
|||||||
import com.android.tools.smali.dexlib2.base.value.BaseIntEncodedValue
|
import com.android.tools.smali.dexlib2.base.value.BaseIntEncodedValue
|
||||||
import com.android.tools.smali.dexlib2.iface.value.IntEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.IntEncodedValue
|
||||||
|
|
||||||
class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) :
|
class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) : BaseIntEncodedValue(), MutableEncodedValue {
|
||||||
BaseIntEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private var value = intEncodedValue.value
|
private var value = intEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): Int = this.value
|
override fun getValue(): Int {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: Int) {
|
fun setValue(value: Int) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun IntEncodedValue.toMutable(): MutableIntEncodedValue = MutableIntEncodedValue(this)
|
fun IntEncodedValue.toMutable(): MutableIntEncodedValue {
|
||||||
|
return MutableIntEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
|||||||
import com.android.tools.smali.dexlib2.base.value.BaseLongEncodedValue
|
import com.android.tools.smali.dexlib2.base.value.BaseLongEncodedValue
|
||||||
import com.android.tools.smali.dexlib2.iface.value.LongEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.LongEncodedValue
|
||||||
|
|
||||||
class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) :
|
class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) : BaseLongEncodedValue(), MutableEncodedValue {
|
||||||
BaseLongEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private var value = longEncodedValue.value
|
private var value = longEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): Long = this.value
|
override fun getValue(): Long {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: Long) {
|
fun setValue(value: Long) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun LongEncodedValue.toMutable(): MutableLongEncodedValue = MutableLongEncodedValue(this)
|
fun LongEncodedValue.toMutable(): MutableLongEncodedValue {
|
||||||
|
return MutableLongEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) :
|
|||||||
MutableEncodedValue {
|
MutableEncodedValue {
|
||||||
private var value = methodEncodedValue.value
|
private var value = methodEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): MethodReference = this.value
|
override fun getValue(): MethodReference {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: MethodReference) {
|
fun setValue(value: MethodReference) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun MethodEncodedValue.toMutable(): MutableMethodEncodedValue = MutableMethodEncodedValue(this)
|
fun MethodEncodedValue.toMutable(): MutableMethodEncodedValue {
|
||||||
|
return MutableMethodEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ class MutableMethodHandleEncodedValue(methodHandleEncodedValue: MethodHandleEnco
|
|||||||
MutableEncodedValue {
|
MutableEncodedValue {
|
||||||
private var value = methodHandleEncodedValue.value
|
private var value = methodHandleEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): MethodHandleReference = this.value
|
override fun getValue(): MethodHandleReference {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: MethodHandleReference) {
|
fun setValue(value: MethodHandleReference) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun MethodHandleEncodedValue.toMutable(): MutableMethodHandleEncodedValue = MutableMethodHandleEncodedValue(this)
|
fun MethodHandleEncodedValue.toMutable(): MutableMethodHandleEncodedValue {
|
||||||
|
return MutableMethodHandleEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedVal
|
|||||||
MutableEncodedValue {
|
MutableEncodedValue {
|
||||||
private var value = methodTypeEncodedValue.value
|
private var value = methodTypeEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): MethodProtoReference = this.value
|
override fun getValue(): MethodProtoReference {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: MethodProtoReference) {
|
fun setValue(value: MethodProtoReference) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun MethodTypeEncodedValue.toMutable(): MutableMethodTypeEncodedValue = MutableMethodTypeEncodedValue(this)
|
fun MethodTypeEncodedValue.toMutable(): MutableMethodTypeEncodedValue {
|
||||||
|
return MutableMethodTypeEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
|||||||
import com.android.tools.smali.dexlib2.base.value.BaseNullEncodedValue
|
import com.android.tools.smali.dexlib2.base.value.BaseNullEncodedValue
|
||||||
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
||||||
|
|
||||||
class MutableNullEncodedValue :
|
class MutableNullEncodedValue : BaseNullEncodedValue(), MutableEncodedValue {
|
||||||
BaseNullEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
companion object {
|
companion object {
|
||||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue = MutableByteEncodedValue(this)
|
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue {
|
||||||
|
return MutableByteEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
|||||||
import com.android.tools.smali.dexlib2.base.value.BaseShortEncodedValue
|
import com.android.tools.smali.dexlib2.base.value.BaseShortEncodedValue
|
||||||
import com.android.tools.smali.dexlib2.iface.value.ShortEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.ShortEncodedValue
|
||||||
|
|
||||||
class MutableShortEncodedValue(shortEncodedValue: ShortEncodedValue) :
|
class MutableShortEncodedValue(shortEncodedValue: ShortEncodedValue) : BaseShortEncodedValue(), MutableEncodedValue {
|
||||||
BaseShortEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private var value = shortEncodedValue.value
|
private var value = shortEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): Short = this.value
|
override fun getValue(): Short {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: Short) {
|
fun setValue(value: Short) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun ShortEncodedValue.toMutable(): MutableShortEncodedValue = MutableShortEncodedValue(this)
|
fun ShortEncodedValue.toMutable(): MutableShortEncodedValue {
|
||||||
|
return MutableShortEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) :
|
|||||||
MutableEncodedValue {
|
MutableEncodedValue {
|
||||||
private var value = stringEncodedValue.value
|
private var value = stringEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): String = this.value
|
override fun getValue(): String {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: String) {
|
fun setValue(value: String) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue = MutableByteEncodedValue(this)
|
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue {
|
||||||
|
return MutableByteEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
|||||||
import com.android.tools.smali.dexlib2.base.value.BaseTypeEncodedValue
|
import com.android.tools.smali.dexlib2.base.value.BaseTypeEncodedValue
|
||||||
import com.android.tools.smali.dexlib2.iface.value.TypeEncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.TypeEncodedValue
|
||||||
|
|
||||||
class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) :
|
class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) : BaseTypeEncodedValue(), MutableEncodedValue {
|
||||||
BaseTypeEncodedValue(),
|
|
||||||
MutableEncodedValue {
|
|
||||||
private var value = typeEncodedValue.value
|
private var value = typeEncodedValue.value
|
||||||
|
|
||||||
override fun getValue(): String = this.value
|
override fun getValue(): String {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
fun setValue(value: String) {
|
fun setValue(value: String) {
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun TypeEncodedValue.toMutable(): MutableTypeEncodedValue = MutableTypeEncodedValue(this)
|
fun TypeEncodedValue.toMutable(): MutableTypeEncodedValue {
|
||||||
|
return MutableTypeEncodedValue(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class InlineSmaliCompiler {
|
|||||||
registers,
|
registers,
|
||||||
instructions,
|
instructions,
|
||||||
)
|
)
|
||||||
val reader = InputStreamReader(input.byteInputStream(), Charsets.UTF_8)
|
val reader = InputStreamReader(input.byteInputStream())
|
||||||
val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
|
val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
|
||||||
val tokens = CommonTokenStream(lexer as TokenSource)
|
val tokens = CommonTokenStream(lexer as TokenSource)
|
||||||
val parser = smaliParser(tokens)
|
val parser = smaliParser(tokens)
|
||||||
|
|||||||
@@ -5,15 +5,16 @@ import app.revanced.patcher.patch.BytecodePatchContext.LookupMaps
|
|||||||
import app.revanced.patcher.util.ProxyClassList
|
import app.revanced.patcher.util.ProxyClassList
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
|
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||||
import io.mockk.*
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.assertAll
|
import org.junit.jupiter.api.assertDoesNotThrow
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
internal object PatcherTest {
|
internal object PatcherTest {
|
||||||
@@ -148,15 +149,19 @@ internal object PatcherTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `throws if unmatched fingerprint match is delegated`() {
|
fun `throws if unmatched fingerprint match is delegated`() {
|
||||||
val patch = bytecodePatch {
|
val patch = bytecodePatch {
|
||||||
execute {
|
// Fingerprint can never match.
|
||||||
// Fingerprint can never match.
|
val match by fingerprint { }
|
||||||
val fingerprint = fingerprint { }
|
// Manually add the fingerprint.
|
||||||
|
app.revanced.patcher.fingerprint { }()
|
||||||
|
|
||||||
|
execute {
|
||||||
// Throws, because the fingerprint can't be matched.
|
// Throws, because the fingerprint can't be matched.
|
||||||
fingerprint.patternMatch
|
match.patternMatch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertEquals(2, patch.fingerprints.size)
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
patch().exception != null,
|
patch().exception != null,
|
||||||
"Expected an exception because the fingerprint can't match.",
|
"Expected an exception because the fingerprint can't match.",
|
||||||
@@ -165,6 +170,42 @@ internal object PatcherTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `matches fingerprint`() {
|
fun `matches fingerprint`() {
|
||||||
|
mockClassWithMethod()
|
||||||
|
|
||||||
|
val patches = setOf(bytecodePatch { fingerprint { this returns "V" } })
|
||||||
|
|
||||||
|
assertNull(
|
||||||
|
patches.first().fingerprints.first().match,
|
||||||
|
"Expected fingerprint to be matched before execution.",
|
||||||
|
)
|
||||||
|
|
||||||
|
patches()
|
||||||
|
|
||||||
|
assertDoesNotThrow("Expected fingerprint to be matched.") {
|
||||||
|
assertEquals(
|
||||||
|
"V",
|
||||||
|
patches.first().fingerprints.first().match!!.method.returnType,
|
||||||
|
"Expected fingerprint to be matched.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
|
||||||
|
every { patcher.context.executablePatches } returns toMutableSet()
|
||||||
|
|
||||||
|
return runBlocking { patcher().toList() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private operator fun Patch<*>.invoke() = setOf(this)().first()
|
||||||
|
|
||||||
|
private fun Any.setPrivateField(field: String, value: Any) {
|
||||||
|
this::class.java.getDeclaredField(field).apply {
|
||||||
|
this.isAccessible = true
|
||||||
|
set(this@setPrivateField, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mockClassWithMethod() {
|
||||||
every { patcher.context.bytecodeContext.classes } returns ProxyClassList(
|
every { patcher.context.bytecodeContext.classes } returns ProxyClassList(
|
||||||
mutableListOf(
|
mutableListOf(
|
||||||
ImmutableClassDef(
|
ImmutableClassDef(
|
||||||
@@ -190,47 +231,6 @@ internal object PatcherTest {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val fingerprint = fingerprint { returns("V") }
|
|
||||||
val fingerprint2 = fingerprint { returns("V") }
|
|
||||||
val fingerprint3 = fingerprint { returns("V") }
|
|
||||||
|
|
||||||
val patches = setOf(
|
|
||||||
bytecodePatch {
|
|
||||||
execute {
|
|
||||||
fingerprint.match(classes.first().methods.first())
|
|
||||||
fingerprint2.match(classes.first())
|
|
||||||
fingerprint3.originalClassDef
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
patches()
|
|
||||||
|
|
||||||
with(patcher.context.bytecodeContext) {
|
|
||||||
assertAll(
|
|
||||||
"Expected fingerprints to match.",
|
|
||||||
{ assertNotNull(fingerprint.originalClassDefOrNull) },
|
|
||||||
{ assertNotNull(fingerprint2.originalClassDefOrNull) },
|
|
||||||
{ assertNotNull(fingerprint3.originalClassDefOrNull) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
|
|
||||||
every { patcher.context.executablePatches } returns toMutableSet()
|
|
||||||
every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classes)
|
every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classes)
|
||||||
every { with(patcher.context.bytecodeContext) { mergeExtension(any<BytecodePatch>()) } } just runs
|
|
||||||
|
|
||||||
return runBlocking { patcher().toList() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private operator fun Patch<*>.invoke() = setOf(this)().first()
|
|
||||||
|
|
||||||
private fun Any.setPrivateField(field: String, value: Any) {
|
|
||||||
this::class.java.getDeclaredField(field).apply {
|
|
||||||
this.isAccessible = true
|
|
||||||
set(this@setPrivateField, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.revanced.patcher.patch
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@@ -23,6 +24,23 @@ internal object PatchTest {
|
|||||||
assertEquals("compatible.package", patch.compatiblePackages!!.first().first)
|
assertEquals("compatible.package", patch.compatiblePackages!!.first().first)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can create patch with fingerprints`() {
|
||||||
|
val externalFingerprint = fingerprint {}
|
||||||
|
|
||||||
|
val patch = bytecodePatch(name = "Test") {
|
||||||
|
val externalFingerprintMatch by externalFingerprint()
|
||||||
|
val internalFingerprintMatch by fingerprint {}
|
||||||
|
|
||||||
|
execute {
|
||||||
|
externalFingerprintMatch.method
|
||||||
|
internalFingerprintMatch.method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(2, patch.fingerprints.size)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can create patch with dependencies`() {
|
fun `can create patch with dependencies`() {
|
||||||
val patch = bytecodePatch(name = "Test") {
|
val patch = bytecodePatch(name = "Test") {
|
||||||
|
|||||||
@@ -7,11 +7,7 @@ import kotlin.reflect.typeOf
|
|||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
internal object OptionsTest {
|
internal object OptionsTest {
|
||||||
private val externalOption = stringOption("external", "default")
|
|
||||||
|
|
||||||
private val optionsTestPatch = bytecodePatch {
|
private val optionsTestPatch = bytecodePatch {
|
||||||
externalOption()
|
|
||||||
|
|
||||||
booleanOption("bool", true)
|
booleanOption("bool", true)
|
||||||
|
|
||||||
stringOption("required", "default", required = true)
|
stringOption("required", "default", required = true)
|
||||||
@@ -128,17 +124,5 @@ internal object OptionsTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `external option should be accessible`() {
|
|
||||||
assertDoesNotThrow {
|
|
||||||
externalOption.value = "test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `should allow getting the external option from the patch`() {
|
|
||||||
assertEquals(optionsTestPatch.options["external"].value, externalOption.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun options(block: Options.() -> Unit) = optionsTestPatch.options.let(block)
|
private fun options(block: Options.() -> Unit) = optionsTestPatch.options.let(block)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user