mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-11 22:06:18 +00:00
Compare commits
157 Commits
v6.4.1-dev
...
v14.2.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ba0b47e60 | ||
|
|
e8f2087a6f | ||
|
|
6ce99f5cdf | ||
|
|
13c0c9cdd3 | ||
|
|
58ffdb60d7 | ||
|
|
ba56a6a2ee | ||
|
|
ccccf5b1d2 | ||
|
|
b507ac0a54 | ||
|
|
e985676c2d | ||
|
|
f7f4ba6c55 | ||
|
|
4292f43814 | ||
|
|
30bd4fd9fe | ||
|
|
76de39369d | ||
|
|
88a703ce36 | ||
|
|
5938f6b7ea | ||
|
|
5c0c0d6c37 | ||
|
|
0f15077225 | ||
|
|
273dd8d388 | ||
|
|
1795f376ef | ||
|
|
e7360a7692 | ||
|
|
e1fc86934f | ||
|
|
6b8977f178 | ||
|
|
12c6c73de0 | ||
|
|
db62a1607b | ||
|
|
58bb879ef5 | ||
|
|
254912438a | ||
|
|
0e48918bcc | ||
|
|
783ccf8529 | ||
|
|
8fb2f2dc1d | ||
|
|
2a8cc283c7 | ||
|
|
433fe3af9f | ||
|
|
c2d89c622e | ||
|
|
02d6ff15fe | ||
|
|
f2cb7ee7df | ||
|
|
a2ac44dcc1 | ||
|
|
3cf9d74efa | ||
|
|
d5f89a903f | ||
|
|
496c2242bc | ||
|
|
98fbff87df | ||
|
|
ddb51a1c45 | ||
|
|
8df1155215 | ||
|
|
53f2a61409 | ||
|
|
746544f9d5 | ||
|
|
c65c3df11c | ||
|
|
b29b8f12b3 | ||
|
|
d6945677c4 | ||
|
|
aedf4aea08 | ||
|
|
dc28d414dc | ||
|
|
9755bab298 | ||
|
|
fae4029cfc | ||
|
|
1790f0d706 | ||
|
|
0ba2c51676 | ||
|
|
03cd97b49c | ||
|
|
16a162c1dd | ||
|
|
c9bbcf2bf2 | ||
|
|
86e1bf6078 | ||
|
|
1bca84ef0b | ||
|
|
e0f8e1b71a | ||
|
|
416d69142f | ||
|
|
426807aeaa | ||
|
|
90cb075a97 | ||
|
|
ac2ca8fbd3 | ||
|
|
69e4a49065 | ||
|
|
a4a030f2b2 | ||
|
|
dcc4ecd237 | ||
|
|
57f3036a96 | ||
|
|
753e55dfc3 | ||
|
|
9d81baf4b4 | ||
|
|
7cb4d4c596 | ||
|
|
2c8565508e | ||
|
|
c7f156e4c9 | ||
|
|
fcef4342e8 | ||
|
|
72783a5e74 | ||
|
|
a379b69eeb | ||
|
|
0a8ccba33e | ||
|
|
519359a9eb | ||
|
|
b615ed6aab | ||
|
|
d718134ab2 | ||
|
|
5e681ed381 | ||
|
|
6e1b6479b6 | ||
|
|
f3c9e28a62 | ||
|
|
d5d6f85084 | ||
|
|
b8151ebccb | ||
|
|
5650e34432 | ||
|
|
c893d16d52 | ||
|
|
34f08bf206 | ||
|
|
f02a42610b | ||
|
|
c95e6fa92f | ||
|
|
fd738e723b | ||
|
|
b1d1956323 | ||
|
|
725a8012ac | ||
|
|
bb9a73e53b | ||
|
|
ef2de35a74 | ||
|
|
2a453d51a8 | ||
|
|
43d6868d1f | ||
|
|
cea9379b32 | ||
|
|
a12fe7dd9e | ||
|
|
efdd01a988 | ||
|
|
eafe1c631f | ||
|
|
aacf900764 | ||
|
|
f82494e9bb | ||
|
|
1e0ffa176e | ||
|
|
b7eb2d2249 | ||
|
|
b6d6a7591b | ||
|
|
8f1c835299 | ||
|
|
a188c16a99 | ||
|
|
3e6804f06c | ||
|
|
526a3d7c35 | ||
|
|
28fc6a2ddd | ||
|
|
d4f08d7bff | ||
|
|
ca9fe322eb | ||
|
|
239ea0bcaa | ||
|
|
7f02b8df48 | ||
|
|
a2052202b2 | ||
|
|
223cea7021 | ||
|
|
ac9337f694 | ||
|
|
549651d04a | ||
|
|
966bbd902e | ||
|
|
81e6f8784e | ||
|
|
9c53877888 | ||
|
|
98f8eedecd | ||
|
|
4ed429d25c | ||
|
|
119d05f469 | ||
|
|
2432fde6bf | ||
|
|
49c173dc14 | ||
|
|
d83e9372bb | ||
|
|
7e8cd3bede | ||
|
|
d67436271d | ||
|
|
aa07f35f06 | ||
|
|
77e0536838 | ||
|
|
a49e78234b | ||
|
|
a3ae825e48 | ||
|
|
146c8504ed | ||
|
|
2eb125ad69 | ||
|
|
6e24a85eab | ||
|
|
e4c3e9ffc5 | ||
|
|
4c1778a62f | ||
|
|
d99261cdbb | ||
|
|
ac1c0f2773 | ||
|
|
eddd4ec7ac | ||
|
|
07a2829c65 | ||
|
|
3d77e299d9 | ||
|
|
f1336f89e4 | ||
|
|
0502f84c20 | ||
|
|
058d292ad5 | ||
|
|
1029d56a52 | ||
|
|
709b5a0fec | ||
|
|
e1accc5041 | ||
|
|
6dbbf2e03e | ||
|
|
16557eeab0 | ||
|
|
6bca3e2bb5 | ||
|
|
a263fdfd41 | ||
|
|
e4b4bacae8 | ||
|
|
cbc97af155 | ||
|
|
d5533788e2 | ||
|
|
5a4ea5cd7d | ||
|
|
70f3c8b38c |
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# Linux start script should use lf
|
||||
/gradlew text eol=lf
|
||||
|
||||
# These are Windows script files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
|
||||
2
.github/config.yml
vendored
Normal file
2
.github/config.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
firstPRMergeComment: >
|
||||
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
|
||||
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
@@ -23,20 +23,23 @@ jobs:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'zulu'
|
||||
cache: gradle
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "latest"
|
||||
cache: 'npm'
|
||||
- name: Setup semantic-release
|
||||
run: npm install semantic-release@19.0.5 @saithodev/semantic-release-backmerge @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin@1.7.4 -D
|
||||
- name: Release
|
||||
path: |
|
||||
${{ runner.home }}/.gradle/caches
|
||||
${{ runner.home }}/.gradle/wrapper
|
||||
.gradle
|
||||
build
|
||||
node_modules
|
||||
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npx semantic-release
|
||||
run: ./gradlew clean --no-daemon
|
||||
- name: Setup semantic-release
|
||||
run: npm install
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
run: npm exec semantic-release
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -74,6 +74,7 @@ cmake-build-*/
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
.idea/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
@@ -117,4 +118,7 @@ gradle-app.setting
|
||||
!src/test/resources/*
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
node_modules/
|
||||
|
||||
# Gradle props, to avoid sharing the gpr key
|
||||
gradle.properties
|
||||
|
||||
9
.idea/.gitignore
generated
vendored
9
.idea/.gitignore
generated
vendored
@@ -1,9 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
/kotlinc.xml
|
||||
10
.idea/codeStyles/Project.xml
generated
10
.idea/codeStyles/Project.xml
generated
@@ -1,10 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
7
.idea/discord.xml
generated
7
.idea/discord.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
15
.idea/git_toolbox_prj.xml
generated
15
.idea/git_toolbox_prj.xml
generated
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxProjectSettings">
|
||||
<option name="commitMessageIssueKeyValidationOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
<option name="commitMessageValidationEnabledOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="UnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
10
.idea/misc.xml
generated
10
.idea/misc.xml
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/vcs.xml
generated
12
.idea/vcs.xml
generated
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CommitMessageInspectionProfile">
|
||||
<profile version="1.0">
|
||||
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
10
.releaserc
10
.releaserc
@@ -7,7 +7,13 @@
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
[
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{ "type": "build", "scope": "Needs bump", "release": "patch" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"gradle-semantic-release-plugin",
|
||||
@@ -23,7 +29,7 @@
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
branches: [{from: "main", to: "dev"}],
|
||||
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||
clearWorkspace: true
|
||||
}
|
||||
],
|
||||
|
||||
505
CHANGELOG.md
505
CHANGELOG.md
@@ -1,3 +1,508 @@
|
||||
# [14.2.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v14.1.0...v14.2.0-dev.1) (2023-08-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* load patches in lexicographical order ([e8f2087](https://github.com/ReVanced/revanced-patcher/commit/e8f2087a6ffa6077fb3a6a69e29f3aec72e2fc1b))
|
||||
|
||||
# [14.1.0](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0...v14.1.0) (2023-08-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* move version properties file to correct package ([e985676](https://github.com/ReVanced/revanced-patcher/commit/e985676c2d8e5d6cb907d371de30428caaa6da43))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* properly make use of logging facade ([ba56a6a](https://github.com/ReVanced/revanced-patcher/commit/ba56a6a2eef503c0d6cdd846ddce2e1474d8ed1a))
|
||||
|
||||
# [14.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v14.0.1-dev.1...v14.1.0-dev.1) (2023-08-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* properly make use of logging facade ([ba56a6a](https://github.com/ReVanced/revanced-patcher/commit/ba56a6a2eef503c0d6cdd846ddce2e1474d8ed1a))
|
||||
|
||||
## [14.0.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0...v14.0.1-dev.1) (2023-08-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* move version properties file to correct package ([e985676](https://github.com/ReVanced/revanced-patcher/commit/e985676c2d8e5d6cb907d371de30428caaa6da43))
|
||||
|
||||
# [14.0.0](https://github.com/ReVanced/revanced-patcher/compare/v13.0.0...v14.0.0) (2023-08-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* log decoding resources after logging deleting resource cache directory ([db62a16](https://github.com/ReVanced/revanced-patcher/commit/db62a1607b4a9d6256b5f5153decb088d9680553))
|
||||
* only emit closed patches that did not throw an exception with the `@Patch` annotation ([5938f6b](https://github.com/ReVanced/revanced-patcher/commit/5938f6b7ea25103a0a1b56ceebe49139bc80c6f5))
|
||||
* supply the parent classloader to `DexClassLoader` ([0f15077](https://github.com/ReVanced/revanced-patcher/commit/0f15077225600b65200022c1a318e504deb472b9))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* improve structure and public API ([6b8977f](https://github.com/ReVanced/revanced-patcher/commit/6b8977f17854ef0344d868e6391cb18134eceadc))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* do not log instantiation of ReVanced Patcher ([273dd8d](https://github.com/ReVanced/revanced-patcher/commit/273dd8d388f8e9b7436c6d6145a94c12c1fabe55))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Various public APIs have been changed. The `Version` annotation has been removed. Patches do not return anything anymore and instead throw `PatchException`. Multiple patch bundles can now be loaded in a single ClassLoader to bypass class loader isolation.
|
||||
|
||||
# [14.0.0-dev.4](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0-dev.3...v14.0.0-dev.4) (2023-08-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* only emit closed patches that did not throw an exception with the `@Patch` annotation ([5938f6b](https://github.com/ReVanced/revanced-patcher/commit/5938f6b7ea25103a0a1b56ceebe49139bc80c6f5))
|
||||
|
||||
# [14.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0-dev.2...v14.0.0-dev.3) (2023-08-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* supply the parent classloader to `DexClassLoader` ([0f15077](https://github.com/ReVanced/revanced-patcher/commit/0f15077225600b65200022c1a318e504deb472b9))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* do not log instantiation of ReVanced Patcher ([273dd8d](https://github.com/ReVanced/revanced-patcher/commit/273dd8d388f8e9b7436c6d6145a94c12c1fabe55))
|
||||
|
||||
# [14.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v14.0.0-dev.1...v14.0.0-dev.2) (2023-08-19)
|
||||
|
||||
# [14.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v13.0.0...v14.0.0-dev.1) (2023-08-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* log decoding resources after logging deleting resource cache directory ([db62a16](https://github.com/ReVanced/revanced-patcher/commit/db62a1607b4a9d6256b5f5153decb088d9680553))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* improve structure and public API ([6b8977f](https://github.com/ReVanced/revanced-patcher/commit/6b8977f17854ef0344d868e6391cb18134eceadc))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Various public APIs have been changed. The `Version` annotation has been removed. Patches do not return anything anymore and instead throw `PatchException`. Multiple patch bundles can now be loaded in a single ClassLoader to bypass class loader isolation.
|
||||
|
||||
# [13.0.0](https://github.com/ReVanced/revanced-patcher/compare/v12.1.1...v13.0.0) (2023-08-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* decode in correct order ([8fb2f2d](https://github.com/ReVanced/revanced-patcher/commit/8fb2f2dc1d3b9b1e9fd13b39485985d2886d52ae))
|
||||
* disable correct loggers ([c2d89c6](https://github.com/ReVanced/revanced-patcher/commit/c2d89c622e06e58e5042e1a00ef67cee8a246e53))
|
||||
* get framework ids to compile resources ([f2cb7ee](https://github.com/ReVanced/revanced-patcher/commit/f2cb7ee7dffa573c31df497cf235a3f5d120f91f))
|
||||
* only enable logging for ReVanced ([783ccf8](https://github.com/ReVanced/revanced-patcher/commit/783ccf8529f5d16aa463982da6977328306232bb))
|
||||
* set package metadata correctly ([02d6ff1](https://github.com/ReVanced/revanced-patcher/commit/02d6ff15fe87c2352de29749610e9d72db8ba418))
|
||||
|
||||
|
||||
* build(Needs bump)!: Bump dependencies ([d5f89a9](https://github.com/ReVanced/revanced-patcher/commit/d5f89a903f019c199bdb27a50287124fc4b4978e))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This bump updates smali, a crucial dependency
|
||||
|
||||
# [13.0.0-dev.3](https://github.com/ReVanced/revanced-patcher/compare/v13.0.0-dev.2...v13.0.0-dev.3) (2023-08-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* decode in correct order ([8fb2f2d](https://github.com/ReVanced/revanced-patcher/commit/8fb2f2dc1d3b9b1e9fd13b39485985d2886d52ae))
|
||||
* only enable logging for ReVanced ([783ccf8](https://github.com/ReVanced/revanced-patcher/commit/783ccf8529f5d16aa463982da6977328306232bb))
|
||||
|
||||
# [13.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v13.0.0-dev.1...v13.0.0-dev.2) (2023-08-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* disable correct loggers ([c2d89c6](https://github.com/ReVanced/revanced-patcher/commit/c2d89c622e06e58e5042e1a00ef67cee8a246e53))
|
||||
* get framework ids to compile resources ([f2cb7ee](https://github.com/ReVanced/revanced-patcher/commit/f2cb7ee7dffa573c31df497cf235a3f5d120f91f))
|
||||
* set package metadata correctly ([02d6ff1](https://github.com/ReVanced/revanced-patcher/commit/02d6ff15fe87c2352de29749610e9d72db8ba418))
|
||||
|
||||
# [13.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v12.1.1...v13.0.0-dev.1) (2023-08-11)
|
||||
|
||||
|
||||
* build(Needs bump)!: Bump dependencies ([d5f89a9](https://github.com/ReVanced/revanced-patcher/commit/d5f89a903f019c199bdb27a50287124fc4b4978e))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This bump updates smali, a crucial dependency
|
||||
|
||||
## [12.1.1](https://github.com/ReVanced/revanced-patcher/compare/v12.1.0...v12.1.1) (2023-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clear method lookup maps before initializing them ([#210](https://github.com/ReVanced/revanced-patcher/issues/210)) ([746544f](https://github.com/ReVanced/revanced-patcher/commit/746544f9d51d1013bb160075709cd26bffd425b3))
|
||||
|
||||
## [12.1.1-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.1.1-dev.1...v12.1.1-dev.2) (2023-08-03)
|
||||
|
||||
## [12.1.1-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v12.1.0...v12.1.1-dev.1) (2023-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clear method lookup maps before initializing them ([#210](https://github.com/ReVanced/revanced-patcher/issues/210)) ([746544f](https://github.com/ReVanced/revanced-patcher/commit/746544f9d51d1013bb160075709cd26bffd425b3))
|
||||
|
||||
# [12.1.0](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0...v12.1.0) (2023-08-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `MutableMethod.getInstructions` extension function ([fae4029](https://github.com/ReVanced/revanced-patcher/commit/fae4029cfccfad7aa3dd8f7fbef1c63ee26b85b3))
|
||||
|
||||
# [12.1.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.1.0-dev.1...v12.1.0-dev.2) (2023-08-03)
|
||||
|
||||
# [12.1.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0...v12.1.0-dev.1) (2023-08-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `MutableMethod.getInstructions` extension function ([fae4029](https://github.com/ReVanced/revanced-patcher/commit/fae4029cfccfad7aa3dd8f7fbef1c63ee26b85b3))
|
||||
|
||||
# [12.0.0](https://github.com/ReVanced/revanced-patcher/compare/v11.0.4...v12.0.0) (2023-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct access flags of `PackageMetadata` ([416d691](https://github.com/ReVanced/revanced-patcher/commit/416d69142f50dab49c9ea3f027e9d53e4777f257))
|
||||
* set resource table via resource decoder ([e0f8e1b](https://github.com/ReVanced/revanced-patcher/commit/e0f8e1b71a295948b610029c89a48f52762396b6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Deprecate `Version` annotation ([c9bbcf2](https://github.com/ReVanced/revanced-patcher/commit/c9bbcf2bf2b0f50ab9100380a3a66c6346ad42ac))
|
||||
* remove `Path` option ([#202](https://github.com/ReVanced/revanced-patcher/issues/202)) ([69e4a49](https://github.com/ReVanced/revanced-patcher/commit/69e4a490659ebc4fb4bf46148634f4b064ef1713))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This removes the previously available `Path` option
|
||||
|
||||
# [12.0.0-dev.2](https://github.com/ReVanced/revanced-patcher/compare/v12.0.0-dev.1...v12.0.0-dev.2) (2023-07-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Deprecate `Version` annotation ([400442f](https://github.com/ReVanced/revanced-patcher/commit/400442f70ee56cafd4493b2ce64a294db9836509))
|
||||
|
||||
# [12.0.0-dev.1](https://github.com/ReVanced/revanced-patcher/compare/v11.0.4...v12.0.0-dev.1) (2023-07-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct access flags of `PackageMetadata` ([416d691](https://github.com/ReVanced/revanced-patcher/commit/416d69142f50dab49c9ea3f027e9d53e4777f257))
|
||||
* set resource table via resource decoder ([e0f8e1b](https://github.com/ReVanced/revanced-patcher/commit/e0f8e1b71a295948b610029c89a48f52762396b6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* remove `Path` option ([#202](https://github.com/ReVanced/revanced-patcher/issues/202)) ([69e4a49](https://github.com/ReVanced/revanced-patcher/commit/69e4a490659ebc4fb4bf46148634f4b064ef1713))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This removes the previously available `Path` option
|
||||
|
||||
## [11.0.4](https://github.com/revanced/revanced-patcher/compare/v11.0.3...v11.0.4) (2023-07-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clear method lookup maps ([#198](https://github.com/revanced/revanced-patcher/issues/198)) ([9d81baf](https://github.com/revanced/revanced-patcher/commit/9d81baf4b4ca7514f8a1009e72218638609a7c7f))
|
||||
|
||||
## [11.0.4-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.3...v11.0.4-dev.1) (2023-07-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clear method lookup maps ([#198](https://github.com/revanced/revanced-patcher/issues/198)) ([9d81baf](https://github.com/revanced/revanced-patcher/commit/9d81baf4b4ca7514f8a1009e72218638609a7c7f))
|
||||
|
||||
## [11.0.3](https://github.com/revanced/revanced-patcher/compare/v11.0.2...v11.0.3) (2023-06-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* NPE on method lookup ([#195](https://github.com/revanced/revanced-patcher/issues/195)) ([fcef434](https://github.com/revanced/revanced-patcher/commit/fcef4342e8bde73945e8315aef6337cc8a8d8572))
|
||||
|
||||
## [11.0.3-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.2...v11.0.3-dev.1) (2023-06-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* NPE on method lookup ([#195](https://github.com/revanced/revanced-patcher/issues/195)) ([fcef434](https://github.com/revanced/revanced-patcher/commit/fcef4342e8bde73945e8315aef6337cc8a8d8572))
|
||||
|
||||
## [11.0.2](https://github.com/revanced/revanced-patcher/compare/v11.0.1...v11.0.2) (2023-06-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* catch exceptions from closing patches ([d5d6f85](https://github.com/revanced/revanced-patcher/commit/d5d6f85084c03ed9c776632823ca12394a716167))
|
||||
* do not load annotations as patches ([519359a](https://github.com/revanced/revanced-patcher/commit/519359a9eb0e9dfa390c5016e9fe4a7490b8ab18))
|
||||
* only close succeeded patches ([b8151eb](https://github.com/revanced/revanced-patcher/commit/b8151ebccb5b27dd9e06fa63235cf9baeef1c0ee))
|
||||
* use `versionCode` if `versionName` is unavailable ([6e1b647](https://github.com/revanced/revanced-patcher/commit/6e1b6479b677657c226693e9cc6b63f4ef2ee060))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* resolve fingerprints using method maps ([#185](https://github.com/revanced/revanced-patcher/issues/185)) ([d718134](https://github.com/revanced/revanced-patcher/commit/d718134ab26423e02708e01eba711737f9260ba0))
|
||||
|
||||
## [11.0.2-dev.4](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.3...v11.0.2-dev.4) (2023-06-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not load annotations as patches ([519359a](https://github.com/revanced/revanced-patcher/commit/519359a9eb0e9dfa390c5016e9fe4a7490b8ab18))
|
||||
|
||||
## [11.0.2-dev.3](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.2...v11.0.2-dev.3) (2023-06-27)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* resolve fingerprints using method maps ([#185](https://github.com/revanced/revanced-patcher/issues/185)) ([d718134](https://github.com/revanced/revanced-patcher/commit/d718134ab26423e02708e01eba711737f9260ba0))
|
||||
|
||||
## [11.0.2-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.1...v11.0.2-dev.2) (2023-06-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use `versionCode` if `versionName` is unavailable ([6e1b647](https://github.com/revanced/revanced-patcher/commit/6e1b6479b677657c226693e9cc6b63f4ef2ee060))
|
||||
|
||||
## [11.0.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.1...v11.0.2-dev.1) (2023-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* catch exceptions from closing patches ([d5d6f85](https://github.com/revanced/revanced-patcher/commit/d5d6f85084c03ed9c776632823ca12394a716167))
|
||||
* only close succeeded patches ([b8151eb](https://github.com/revanced/revanced-patcher/commit/b8151ebccb5b27dd9e06fa63235cf9baeef1c0ee))
|
||||
|
||||
## [11.0.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1) (2023-06-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* revert using `OutputStream.nullOutputStream` ([f02a426](https://github.com/revanced/revanced-patcher/commit/f02a42610b7698fc8cc6bc5183c9ccba2ed96cda))
|
||||
|
||||
## [11.0.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1-dev.1) (2023-06-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* revert using `OutputStream.nullOutputStream` ([f02a426](https://github.com/revanced/revanced-patcher/commit/f02a42610b7698fc8cc6bc5183c9ccba2ed96cda))
|
||||
|
||||
# [11.0.0](https://github.com/revanced/revanced-patcher/compare/v10.0.0...v11.0.0) (2023-06-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add imports to fix failing tests ([43d6868](https://github.com/revanced/revanced-patcher/commit/43d6868d1f59922f9812f3e4a2a890f3b331def6))
|
||||
|
||||
|
||||
* refactor!: move extension functions to their corresponding classes ([a12fe7d](https://github.com/revanced/revanced-patcher/commit/a12fe7dd9e976c38a0a82fe35e6650f58f815de4))
|
||||
* refactor!: use proper extension function names ([efdd01a](https://github.com/revanced/revanced-patcher/commit/efdd01a9886b6f06af213731824621964367b2a3))
|
||||
* fix!: implement extension functions consistently ([aacf900](https://github.com/revanced/revanced-patcher/commit/aacf9007647b1cc11bc40053625802573efda6ef))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This changes the import paths for extension functions.
|
||||
* This changes the names of extension functions
|
||||
* This changes the name of functions
|
||||
|
||||
# [11.0.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.0-dev.1...v11.0.0-dev.2) (2023-06-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add imports to fix failing tests ([43d6868](https://github.com/revanced/revanced-patcher/commit/43d6868d1f59922f9812f3e4a2a890f3b331def6))
|
||||
|
||||
# [11.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v10.0.0...v11.0.0-dev.1) (2023-06-07)
|
||||
|
||||
|
||||
* refactor!: move extension functions to their corresponding classes ([a12fe7d](https://github.com/revanced/revanced-patcher/commit/a12fe7dd9e976c38a0a82fe35e6650f58f815de4))
|
||||
* refactor!: use proper extension function names ([efdd01a](https://github.com/revanced/revanced-patcher/commit/efdd01a9886b6f06af213731824621964367b2a3))
|
||||
* fix!: implement extension functions consistently ([aacf900](https://github.com/revanced/revanced-patcher/commit/aacf9007647b1cc11bc40053625802573efda6ef))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This changes the import paths for extension functions.
|
||||
* This changes the names of extension functions
|
||||
* This changes the name of functions
|
||||
|
||||
# [10.0.0](https://github.com/revanced/revanced-patcher/compare/v9.0.0...v10.0.0) (2023-06-07)
|
||||
|
||||
|
||||
* fix!: check for two methods parameters orders (#183) ([b6d6a75](https://github.com/revanced/revanced-patcher/commit/b6d6a7591ba1c0b48ffdef52352709564da8d9be)), closes [#183](https://github.com/revanced/revanced-patcher/issues/183)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This requires changes to `MethodFingerprint`
|
||||
|
||||
# [10.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v9.0.0...v10.0.0-dev.1) (2023-06-07)
|
||||
|
||||
|
||||
* fix!: check for two methods parameters orders (#183) ([b6d6a75](https://github.com/revanced/revanced-patcher/commit/b6d6a7591ba1c0b48ffdef52352709564da8d9be)), closes [#183](https://github.com/revanced/revanced-patcher/issues/183)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This requires changes to `MethodFingerprint`
|
||||
|
||||
# [9.0.0](https://github.com/revanced/revanced-patcher/compare/v8.0.0...v9.0.0) (2023-05-23)
|
||||
|
||||
|
||||
* refactor!: rename parameter ([526a3d7](https://github.com/revanced/revanced-patcher/commit/526a3d7c359e2d95d26756da0f88d5ce975f5d9b))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This changes named parameters.
|
||||
|
||||
# [9.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v8.0.0...v9.0.0-dev.1) (2023-05-23)
|
||||
|
||||
|
||||
* refactor!: rename parameter ([526a3d7](https://github.com/revanced/revanced-patcher/commit/526a3d7c359e2d95d26756da0f88d5ce975f5d9b))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This changes named parameters.
|
||||
|
||||
# [8.0.0](https://github.com/revanced/revanced-patcher/compare/v7.1.1...v8.0.0) (2023-05-13)
|
||||
|
||||
|
||||
* feat!: add `classDef` parameter to `MethodFingerprint` (#175) ([a205220](https://github.com/revanced/revanced-patcher/commit/a2052202b23037150df6aadc47f6e91efcd481cf)), closes [#175](https://github.com/revanced/revanced-patcher/issues/175)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This changes the signature of the `customFingerprint` function.
|
||||
|
||||
# [8.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.1.1...v8.0.0-dev.1) (2023-05-10)
|
||||
|
||||
|
||||
* feat!: add `classDef` parameter to `MethodFingerprint` (#175) ([a205220](https://github.com/revanced/revanced-patcher/commit/a2052202b23037150df6aadc47f6e91efcd481cf)), closes [#175](https://github.com/revanced/revanced-patcher/issues/175)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This changes the signature of the `customFingerprint` function.
|
||||
|
||||
## [7.1.1](https://github.com/revanced/revanced-patcher/compare/v7.1.0...v7.1.1) (2023-05-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove `count` instead of `count + 1` instructions with `removeInstructions` ([#167](https://github.com/revanced/revanced-patcher/issues/167)) ([98f8eed](https://github.com/revanced/revanced-patcher/commit/98f8eedecd72b0afe6a0f099a3641a1cc6be2698))
|
||||
|
||||
## [7.1.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.1.0...v7.1.1-dev.1) (2023-05-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove `count` instead of `count + 1` instructions with `removeInstructions` ([#167](https://github.com/revanced/revanced-patcher/issues/167)) ([98f8eed](https://github.com/revanced/revanced-patcher/commit/98f8eedecd72b0afe6a0f099a3641a1cc6be2698))
|
||||
|
||||
# [7.1.0](https://github.com/revanced/revanced-patcher/compare/v7.0.0...v7.1.0) (2023-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add appreciation message for new contributors ([d674362](https://github.com/revanced/revanced-patcher/commit/d67436271ddca9ccfe008272c1ca82c6123ae7ee))
|
||||
* add overload to get instruction as type ([49c173d](https://github.com/revanced/revanced-patcher/commit/49c173dc14137ddd198a611e9882dc71300831ee))
|
||||
|
||||
# [7.1.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v7.1.0-dev.1...v7.1.0-dev.2) (2023-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add overload to get instruction as type ([49c173d](https://github.com/revanced/revanced-patcher/commit/49c173dc14137ddd198a611e9882dc71300831ee))
|
||||
|
||||
# [7.1.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.0.0...v7.1.0-dev.1) (2023-04-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add appreciation message for new contributors ([d674362](https://github.com/revanced/revanced-patcher/commit/d67436271ddca9ccfe008272c1ca82c6123ae7ee))
|
||||
|
||||
# [7.0.0](https://github.com/revanced/revanced-patcher/compare/v6.4.3...v7.0.0) (2023-02-26)
|
||||
|
||||
|
||||
* feat!: merge integrations only when necessary ([6e24a85](https://github.com/revanced/revanced-patcher/commit/6e24a85eabd1e7a1484fad229d5ba55c3ba1f1b4))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* `Patcher.addFiles` is now renamed to `Patcher.addIntegrations`
|
||||
|
||||
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
|
||||
|
||||
# [7.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.3...v7.0.0-dev.1) (2023-02-26)
|
||||
|
||||
|
||||
* feat!: merge integrations only when necessary ([6e24a85](https://github.com/revanced/revanced-patcher/commit/6e24a85eabd1e7a1484fad229d5ba55c3ba1f1b4))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* `Patcher.addFiles` is now renamed to `Patcher.addIntegrations`
|
||||
|
||||
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
|
||||
|
||||
## [6.4.3](https://github.com/revanced/revanced-patcher/compare/v6.4.2...v6.4.3) (2023-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check `CONST_STRING_JUMP` instructions for matching string ([058d292](https://github.com/revanced/revanced-patcher/commit/058d292ad5e297f4c652ff543c13e77a39f7fb1b))
|
||||
|
||||
## [6.4.3-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.2...v6.4.3-dev.1) (2023-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check `CONST_STRING_JUMP` instructions for matching string ([058d292](https://github.com/revanced/revanced-patcher/commit/058d292ad5e297f4c652ff543c13e77a39f7fb1b))
|
||||
|
||||
## [6.4.2](https://github.com/revanced/revanced-patcher/compare/v6.4.1...v6.4.2) (2023-01-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* resolve failing builds ([a263fdf](https://github.com/revanced/revanced-patcher/commit/a263fdfd413fc05098e28d4800e36ce7d313085b))
|
||||
|
||||
## [6.4.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.1...v6.4.2-dev.1) (2023-01-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* resolve failing builds ([a263fdf](https://github.com/revanced/revanced-patcher/commit/a263fdfd413fc05098e28d4800e36ce7d313085b))
|
||||
|
||||
## [6.4.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1) (2023-01-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](https://github.com/revanced/revanced-patcher/commit/dd7dd383577dcfc95e97f77b446a89b41b589dc0))
|
||||
|
||||
## [6.4.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1) (2023-01-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](https://github.com/revanced/revanced-patcher/commit/dd7dd383577dcfc95e97f77b446a89b41b589dc0))
|
||||
|
||||
## [6.4.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1-dev.1) (2023-01-15)
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# Patcher
|
||||
Patcher framework used in the ReVanced project.
|
||||
# 💉 ReVanced Patcher
|
||||
|
||||
ReVanced Patcher used to patch Android applications.
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.7.0"
|
||||
java
|
||||
kotlin("jvm") version "1.8.20"
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
|
||||
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = uri("https://maven.pkg.github.com/revanced/multidexlib2")
|
||||
credentials {
|
||||
username = githubUsername
|
||||
password = githubPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("xpp3:xpp3:1.1.4c")
|
||||
implementation("org.smali:smali:2.5.2")
|
||||
implementation("app.revanced:multidexlib2:2.5.3-8649b642")
|
||||
implementation("org.apktool:apktool-lib:2.9.0-SNAPSHOT")
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.xpp3)
|
||||
implementation(libs.smali)
|
||||
implementation(libs.multidexlib2)
|
||||
implementation(libs.apktool.lib)
|
||||
implementation(libs.kotlin.reflect)
|
||||
|
||||
implementation(kotlin("reflect"))
|
||||
testImplementation(kotlin("test"))
|
||||
compileOnly(libs.android)
|
||||
|
||||
testImplementation(libs.kotlin.test)
|
||||
}
|
||||
|
||||
tasks {
|
||||
@@ -37,31 +25,28 @@ tasks {
|
||||
events("PASSED", "SKIPPED", "FAILED")
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
expand("projectVersion" to project.version)
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
}
|
||||
kotlin { jvmToolchain(11) }
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
if (System.getenv("GITHUB_ACTOR") != null)
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
else
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
publications {
|
||||
register<MavenPublication>("gpr") {
|
||||
create<MavenPublication>("gpr") {
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 6.4.1-dev.1
|
||||
version = 14.2.0-dev.1
|
||||
|
||||
21
gradle/libs.versions.toml
Normal file
21
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[versions]
|
||||
android = "4.1.1.4"
|
||||
kotlin-reflect = "1.9.0"
|
||||
apktool-lib = "2.8.2-5"
|
||||
kotlin-test = "1.8.20-RC"
|
||||
kotlinx-coroutines-core = "1.7.1"
|
||||
multidexlib2 = "3.0.3.r2"
|
||||
shadow = "8.1.1"
|
||||
smali = "3.0.3"
|
||||
xpp3 = "1.1.4c"
|
||||
|
||||
|
||||
[libraries]
|
||||
android = { module = "com.google.android:android", version.ref = "android" }
|
||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
|
||||
apktool-lib = { module = "app.revanced:apktool-lib", version.ref = "apktool-lib" }
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||
multidexlib2 = { module = "app.revanced:multidexlib2", version.ref = "multidexlib2" }
|
||||
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
|
||||
xpp3 = { module = "xpp3:xpp3", version.ref = "xpp3" }
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
40
gradlew
vendored
40
gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright <EFBFBD> 2015-2021 the original authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -32,10 +32,10 @@
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>,
|
||||
# <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>;
|
||||
# * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>;
|
||||
# * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>.
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/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.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,13 +80,10 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# 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"'
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -133,22 +130,29 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -193,6 +197,10 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# 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"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
@@ -205,6 +213,12 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
15
gradlew.bat
vendored
15
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
727
package-lock.json
generated
727
package-lock.json
generated
@@ -5,10 +5,11 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
||||
"@semantic-release/changelog": "^6.0.2",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.7.4",
|
||||
"semantic-release": "^19.0.5"
|
||||
"gradle-semantic-release-plugin": "^1.7.6",
|
||||
"semantic-release": "^20.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -312,6 +313,20 @@
|
||||
"@octokit/openapi-types": "^14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@saithodev/semantic-release-backmerge": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@saithodev/semantic-release-backmerge/-/semantic-release-backmerge-3.1.0.tgz",
|
||||
"integrity": "sha512-92AN5eI8svpxeUD6cw2JjCrHHZVlWIxQ67SiSSwoI1UP4N5QohCOf9O/W3OUApxKg3C8Y0RpGt7TUpGEwGhXhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@semantic-release/error": "^3.0.0",
|
||||
"aggregate-error": "^3.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"execa": "^5.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"semantic-release": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@semantic-release/changelog": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.2.tgz",
|
||||
@@ -498,12 +513,6 @@
|
||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||
@@ -580,6 +589,12 @@
|
||||
"integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/argv-formatter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz",
|
||||
@@ -735,14 +750,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
@@ -887,19 +905,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz",
|
||||
"integrity": "sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/parse-json": "^4.0.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"parse-json": "^5.0.0",
|
||||
"path-type": "^4.0.0",
|
||||
"yaml": "^1.10.0"
|
||||
"path-type": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
@@ -1077,17 +1094,126 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/env-ci": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.5.0.tgz",
|
||||
"integrity": "sha512-o0JdWIbOLP+WJKIUt36hz1ImQQFuN92nhsfTkHHap+J8CiI8WgGpH/a9jEGHh4/TU5BUUGjlnKXNoDb57+ne+A==",
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/env-ci/-/env-ci-8.0.0.tgz",
|
||||
"integrity": "sha512-W+3BqGZozFua9MPeXpmTm5eYEBtGgL76jGu/pwMVp/L8PdECSCEWaIp7d4Mw7kuUrbUldK0oV0bNd6ZZjLiMiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"execa": "^5.0.0",
|
||||
"fromentries": "^1.3.2",
|
||||
"java-properties": "^1.0.0"
|
||||
"execa": "^6.1.0",
|
||||
"java-properties": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.17"
|
||||
"node": "^16.10 || >=18"
|
||||
}
|
||||
},
|
||||
"node_modules/env-ci/node_modules/execa": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz",
|
||||
"integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^6.0.1",
|
||||
"human-signals": "^3.0.1",
|
||||
"is-stream": "^3.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^5.1.0",
|
||||
"onetime": "^6.0.0",
|
||||
"signal-exit": "^3.0.7",
|
||||
"strip-final-newline": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/env-ci/node_modules/human-signals": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz",
|
||||
"integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/env-ci/node_modules/is-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
|
||||
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/env-ci/node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/env-ci/node_modules/npm-run-path": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
|
||||
"integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/env-ci/node_modules/onetime": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
|
||||
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-fn": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/env-ci/node_modules/path-key": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
|
||||
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/env-ci/node_modules/strip-final-newline": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
|
||||
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
@@ -1179,15 +1305,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz",
|
||||
"integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^1.0.5"
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"is-unicode-supported": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/figures/node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -1219,15 +1358,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/find-versions": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz",
|
||||
"integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz",
|
||||
"integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver-regex": "^3.1.2"
|
||||
"semver-regex": "^4.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -1243,26 +1382,6 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fromentries": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
|
||||
"integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
|
||||
@@ -1402,9 +1521,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/gradle-semantic-release-plugin": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/gradle-semantic-release-plugin/-/gradle-semantic-release-plugin-1.7.4.tgz",
|
||||
"integrity": "sha512-Mm2JnmodHxQlCmn0GtSi5j8z4PS2+2VBY993b9GuNGGM+JaIrr8T3tF9uTLR2nGH/oJba6a9jx1ZqVEJdOXDPw==",
|
||||
"version": "1.7.6",
|
||||
"resolved": "https://registry.npmjs.org/gradle-semantic-release-plugin/-/gradle-semantic-release-plugin-1.7.6.tgz",
|
||||
"integrity": "sha512-FNoZAm9jntxOXLee5uJLlCb9hsFsJ1d4jUnvz08NF6p72OwSmaSBeFN7Wnl6RjW8mPrAuOSwkuinuWWjVeO2aw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1413,14 +1532,14 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"promisified-properties": "^2.0.3",
|
||||
"promisified-properties": "^2.0.27",
|
||||
"split2": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || ^14.17"
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"semantic-release": "^19.0.2"
|
||||
"semantic-release": "^20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
@@ -1475,12 +1594,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hook-std": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz",
|
||||
"integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz",
|
||||
"integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
@@ -1750,6 +1872,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-unicode-supported": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
|
||||
"integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
@@ -1793,6 +1927,18 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-better-errors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||
@@ -1909,6 +2055,12 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.capitalize": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz",
|
||||
@@ -4817,12 +4969,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/p-each-series": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz",
|
||||
"integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz",
|
||||
"integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -5102,9 +5254,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/promisified-properties": {
|
||||
"version": "2.0.25",
|
||||
"resolved": "https://registry.npmjs.org/promisified-properties/-/promisified-properties-2.0.25.tgz",
|
||||
"integrity": "sha512-FtyMDSNf3IwVBjVq4uj5JD4lG+YmOMzt/Huuc+VwPHVqbzim3/U04iMBnVH3lYgOc9s5Jj9sC4G0OVGoo2KbWA==",
|
||||
"version": "2.0.27",
|
||||
"resolved": "https://registry.npmjs.org/promisified-properties/-/promisified-properties-2.0.27.tgz",
|
||||
"integrity": "sha512-fmx256M3b0QcHnqOj+Ok127LoYpmnYRf7g2OyLl7qD7Z0fzNbIZhHHIPKyvegbA29iAPP5BVWv7BJ/y2cMHHjA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"parsimmon": "^1.13.0"
|
||||
@@ -5393,9 +5545,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/semantic-release": {
|
||||
"version": "19.0.5",
|
||||
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz",
|
||||
"integrity": "sha512-NMPKdfpXTnPn49FDogMBi36SiBfXkSOJqCkk0E4iWOY1tusvvgBwqUmxTX1kmlT6kIYed9YwNKD1sfPpqa5yaA==",
|
||||
"version": "20.1.0",
|
||||
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-20.1.0.tgz",
|
||||
"integrity": "sha512-+9+n6RIr0Fz0F53cXrjpawxWlUg3O7/qr1jF9lrE+/v6WqwBrSWnavVHTPaf2WLerET2EngoqI0M4pahkKl6XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@semantic-release/commit-analyzer": "^9.0.2",
|
||||
@@ -5403,35 +5555,350 @@
|
||||
"@semantic-release/github": "^8.0.0",
|
||||
"@semantic-release/npm": "^9.0.0",
|
||||
"@semantic-release/release-notes-generator": "^10.0.0",
|
||||
"aggregate-error": "^3.0.0",
|
||||
"cosmiconfig": "^7.0.0",
|
||||
"aggregate-error": "^4.0.1",
|
||||
"cosmiconfig": "^8.0.0",
|
||||
"debug": "^4.0.0",
|
||||
"env-ci": "^5.0.0",
|
||||
"execa": "^5.0.0",
|
||||
"figures": "^3.0.0",
|
||||
"find-versions": "^4.0.0",
|
||||
"env-ci": "^8.0.0",
|
||||
"execa": "^6.1.0",
|
||||
"figures": "^5.0.0",
|
||||
"find-versions": "^5.1.0",
|
||||
"get-stream": "^6.0.0",
|
||||
"git-log-parser": "^1.2.0",
|
||||
"hook-std": "^2.0.0",
|
||||
"hosted-git-info": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^4.0.10",
|
||||
"marked-terminal": "^5.0.0",
|
||||
"hook-std": "^3.0.0",
|
||||
"hosted-git-info": "^6.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^4.1.0",
|
||||
"marked-terminal": "^5.1.1",
|
||||
"micromatch": "^4.0.2",
|
||||
"p-each-series": "^2.1.0",
|
||||
"p-reduce": "^2.0.0",
|
||||
"read-pkg-up": "^7.0.0",
|
||||
"p-each-series": "^3.0.0",
|
||||
"p-reduce": "^3.0.0",
|
||||
"read-pkg-up": "^9.1.0",
|
||||
"resolve-from": "^5.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"semver-diff": "^3.1.1",
|
||||
"semver-diff": "^4.0.0",
|
||||
"signale": "^1.2.1",
|
||||
"yargs": "^16.2.0"
|
||||
"yargs": "^17.5.1"
|
||||
},
|
||||
"bin": {
|
||||
"semantic-release": "bin/semantic-release.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || ^14.17"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/aggregate-error": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz",
|
||||
"integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"clean-stack": "^4.0.0",
|
||||
"indent-string": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/clean-stack": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz",
|
||||
"integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/execa": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz",
|
||||
"integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^6.0.1",
|
||||
"human-signals": "^3.0.1",
|
||||
"is-stream": "^3.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^5.1.0",
|
||||
"onetime": "^6.0.0",
|
||||
"signal-exit": "^3.0.7",
|
||||
"strip-final-newline": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/find-up": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
|
||||
"integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"locate-path": "^7.1.0",
|
||||
"path-exists": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/hosted-git-info": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
|
||||
"integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^7.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/human-signals": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz",
|
||||
"integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/indent-string": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
|
||||
"integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/is-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
|
||||
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/locate-path": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
|
||||
"integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-locate": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/lru-cache": {
|
||||
"version": "7.14.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz",
|
||||
"integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/npm-run-path": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
|
||||
"integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/onetime": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
|
||||
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-fn": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/p-limit": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
|
||||
"integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yocto-queue": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/p-locate": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
|
||||
"integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-limit": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/p-reduce": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz",
|
||||
"integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/path-exists": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
|
||||
"integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/path-key": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
|
||||
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/read-pkg": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz",
|
||||
"integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/normalize-package-data": "^2.4.1",
|
||||
"normalize-package-data": "^3.0.2",
|
||||
"parse-json": "^5.2.0",
|
||||
"type-fest": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/read-pkg-up": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz",
|
||||
"integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"find-up": "^6.3.0",
|
||||
"read-pkg": "^7.1.0",
|
||||
"type-fest": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/strip-final-newline": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
|
||||
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semantic-release/node_modules/type-fest": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
|
||||
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
@@ -5450,33 +5917,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver-diff": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
|
||||
"integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz",
|
||||
"integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver": "^6.3.0"
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/semver-diff/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semver-regex": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz",
|
||||
"integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz",
|
||||
"integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -6067,31 +6528,22 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"version": "17.6.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
|
||||
"integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cliui": "^7.0.2",
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.0",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
@@ -6102,6 +6554,27 @@
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
|
||||
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
||||
"@semantic-release/changelog": "^6.0.2",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.7.4",
|
||||
"semantic-release": "^19.0.5"
|
||||
"gradle-semantic-release-plugin": "^1.7.6",
|
||||
"semantic-release": "^20.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,22 @@
|
||||
val githubUsername: String = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
|
||||
val githubPassword: String = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
mavenLocal()
|
||||
listOf("multidexlib2", "apktool").forEach { repo ->
|
||||
maven {
|
||||
url = uri("https://maven.pkg.github.com/revanced/$repo")
|
||||
credentials {
|
||||
username = githubUsername
|
||||
password = githubPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "revanced-patcher"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import java.io.File
|
||||
|
||||
@FunctionalInterface
|
||||
interface IntegrationsConsumer {
|
||||
fun acceptIntegrations(integrations: List<File>)
|
||||
}
|
||||
14
src/main/kotlin/app/revanced/patcher/PackageMetadata.kt
Normal file
14
src/main/kotlin/app/revanced/patcher/PackageMetadata.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import brut.androlib.apk.ApkInfo
|
||||
|
||||
/**
|
||||
* Metadata about a package.
|
||||
*/
|
||||
class PackageMetadata internal constructor(internal val apkInfo: ApkInfo) {
|
||||
lateinit var packageName: String
|
||||
internal set
|
||||
|
||||
lateinit var packageVersion: String
|
||||
internal set
|
||||
}
|
||||
74
src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt
Normal file
74
src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt
Normal file
@@ -0,0 +1,74 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchClass
|
||||
import dalvik.system.DexClassLoader
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
|
||||
/**
|
||||
* A patch bundle.
|
||||
*
|
||||
* @param fromClasses The classes to get [Patch]es from.
|
||||
*/
|
||||
sealed class PatchBundleLoader private constructor(
|
||||
fromClasses: Iterable<Class<*>>
|
||||
) : MutableList<PatchClass> by mutableListOf() {
|
||||
init {
|
||||
fromClasses.filter {
|
||||
if (it.isAnnotation) return@filter false
|
||||
|
||||
it.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class) != null
|
||||
}.map {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
it as PatchClass
|
||||
}.sortedBy {
|
||||
it.patchName
|
||||
}.let { addAll(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A [PatchBundleLoader] for JAR files.
|
||||
*
|
||||
* @param patchBundles The path to patch bundles of JAR format.
|
||||
*/
|
||||
class Jar(vararg patchBundles: File) :
|
||||
PatchBundleLoader(with(URLClassLoader(patchBundles.map { it.toURI().toURL() }.toTypedArray())) {
|
||||
patchBundles.flatMap { patchBundle ->
|
||||
// Get the names of all classes in the DEX file.
|
||||
|
||||
JarFile(patchBundle).entries().asSequence()
|
||||
.filter { it.name.endsWith(".class") }
|
||||
.map { it.name.replace('/', '.').replace(".class", "") }
|
||||
.map { loadClass(it) }
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* A [PatchBundleLoader] for [Dex] files.
|
||||
*
|
||||
* @param patchBundles The path to patch bundles of DEX format.
|
||||
*/
|
||||
class Dex(vararg patchBundles: File) : PatchBundleLoader(with(
|
||||
DexClassLoader(
|
||||
patchBundles.joinToString(File.pathSeparator) { it.absolutePath },
|
||||
null,
|
||||
null,
|
||||
PatchBundleLoader::class.java.classLoader
|
||||
)
|
||||
) {
|
||||
patchBundles
|
||||
.flatMap {
|
||||
MultiDexIO.readDexFile(true, it, BasicDexFileNamer(), null, null).classes
|
||||
}
|
||||
.map { classDef -> classDef.type.substring(1, classDef.length - 1) }
|
||||
.map { loadClass(it) }
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.util.function.Function
|
||||
|
||||
@FunctionalInterface
|
||||
interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>
|
||||
@@ -1,396 +1,236 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.data.findIndexed
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.extensions.nullOutputStream
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
|
||||
import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap
|
||||
import app.revanced.patcher.patch.*
|
||||
import app.revanced.patcher.util.ClassMerger.merge
|
||||
import app.revanced.patcher.util.VersionReader
|
||||
import brut.androlib.Androlib
|
||||
import brut.androlib.meta.UsesFramework
|
||||
import brut.androlib.options.BuildOptions
|
||||
import brut.androlib.res.AndrolibResources
|
||||
import brut.androlib.res.data.ResPackage
|
||||
import brut.androlib.res.decoder.AXmlResourceParser
|
||||
import brut.androlib.res.decoder.ResAttrDecoder
|
||||
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
||||
import brut.androlib.res.xml.ResXmlPatcher
|
||||
import brut.directory.ExtFile
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.DexIO
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import org.jf.dexlib2.Opcodes
|
||||
import org.jf.dexlib2.iface.DexFile
|
||||
import org.jf.dexlib2.writer.io.MemoryDataStore
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
private val NAMER = BasicDexFileNamer()
|
||||
import java.util.function.Supplier
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.LogManager
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* The ReVanced Patcher.
|
||||
* ReVanced Patcher.
|
||||
*
|
||||
* @param options The options for the patcher.
|
||||
*/
|
||||
class Patcher(private val options: PatcherOptions) {
|
||||
private val logger = options.logger
|
||||
private val opcodes: Opcodes
|
||||
private var resourceDecodingMode = ResourceDecodingMode.MANIFEST_ONLY
|
||||
val context: PatcherContext
|
||||
class Patcher(
|
||||
private val options: PatcherOptions
|
||||
) : PatchExecutorFunction, PatchesConsumer, IntegrationsConsumer, Supplier<PatcherResult>, Closeable {
|
||||
private val logger = Logger.getLogger(Patcher::class.java.name)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val version = VersionReader.read()
|
||||
private fun BuildOptions.setBuildOptions(options: PatcherOptions) {
|
||||
this.aaptPath = options.aaptPath
|
||||
this.useAapt2 = true
|
||||
this.frameworkFolderLocation = options.frameworkFolderLocation
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The context of ReVanced [Patcher].
|
||||
* This holds the current state of the patcher.
|
||||
*/
|
||||
val context = PatcherContext(options)
|
||||
|
||||
init {
|
||||
logger.info("Reading dex files")
|
||||
// read dex files
|
||||
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
|
||||
// get the opcodes
|
||||
opcodes = dexFile.opcodes
|
||||
// finally create patcher context
|
||||
context = PatcherContext(dexFile.classes.toMutableList(), File(options.resourceCacheDirectory))
|
||||
LogManager.getLogManager().let { manager ->
|
||||
// Disable root logger.
|
||||
manager.getLogger("").level = Level.OFF
|
||||
|
||||
// decode manifest file
|
||||
decodeResources(ResourceDecodingMode.MANIFEST_ONLY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional dex file container to the patcher.
|
||||
* @param files The dex file containers to add to the patcher.
|
||||
* @param process The callback for [files] which are being added.
|
||||
*/
|
||||
fun addFiles(
|
||||
files: List<File>,
|
||||
process: (File) -> Unit
|
||||
) {
|
||||
with(context.bytecodeContext.classes) {
|
||||
for (file in files) {
|
||||
process(file)
|
||||
for (classDef in MultiDexIO.readDexFile(true, file, NAMER, null, null).classes) {
|
||||
val type = classDef.type
|
||||
|
||||
val result = classes.findIndexed { it.type == type }
|
||||
if (result == null) {
|
||||
logger.trace("Merging type $type")
|
||||
classes.add(classDef)
|
||||
continue
|
||||
}
|
||||
|
||||
val (existingClass, existingClassIndex) = result
|
||||
|
||||
logger.trace("Type $type exists. Adding missing methods and fields.")
|
||||
|
||||
existingClass.merge(classDef, context, logger).let { mergedClass ->
|
||||
if (mergedClass !== existingClass) // referential equality check
|
||||
classes[existingClassIndex] = mergedClass
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the patched dex file.
|
||||
*/
|
||||
fun save(): PatcherResult {
|
||||
val packageMetadata = context.packageMetadata
|
||||
val metaInfo = packageMetadata.metaInfo
|
||||
var resourceFile: File? = null
|
||||
|
||||
when (resourceDecodingMode) {
|
||||
ResourceDecodingMode.FULL -> {
|
||||
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
||||
try {
|
||||
val androlibResources = AndrolibResources().also { resources ->
|
||||
resources.buildOptions = BuildOptions().also { buildOptions ->
|
||||
buildOptions.setBuildOptions(options)
|
||||
buildOptions.isFramework = metaInfo.isFrameworkApk
|
||||
buildOptions.resourcesAreCompressed = metaInfo.compressionType
|
||||
buildOptions.doNotCompress = metaInfo.doNotCompress
|
||||
}
|
||||
|
||||
resources.setSdkInfo(metaInfo.sdkInfo)
|
||||
resources.setVersionInfo(metaInfo.versionInfo)
|
||||
resources.setSharedLibrary(metaInfo.sharedLibrary)
|
||||
resources.setSparseResources(metaInfo.sparseResources)
|
||||
}
|
||||
|
||||
val manifestFile = cacheDirectory.resolve("AndroidManifest.xml")
|
||||
|
||||
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifestFile)
|
||||
|
||||
val aaptFile = cacheDirectory.resolve("aapt_temp_file")
|
||||
|
||||
// delete if it exists
|
||||
Files.deleteIfExists(aaptFile.toPath())
|
||||
|
||||
val resDirectory = cacheDirectory.resolve("res")
|
||||
val includedFiles = metaInfo.usesFramework.ids.map { id ->
|
||||
androlibResources.getFrameworkApk(
|
||||
id, metaInfo.usesFramework.tag
|
||||
)
|
||||
}.toTypedArray()
|
||||
|
||||
logger.info("Compiling resources")
|
||||
androlibResources.aaptPackage(
|
||||
aaptFile, manifestFile, resDirectory, null, null, includedFiles
|
||||
)
|
||||
|
||||
resourceFile = aaptFile
|
||||
} finally {
|
||||
cacheDirectory.close()
|
||||
}
|
||||
}
|
||||
|
||||
else -> logger.info("Not compiling resources because resource patching is not required")
|
||||
// Enable ReVanced logging only.
|
||||
manager.loggerNames
|
||||
.toList()
|
||||
.filter { it.startsWith("app.revanced") }
|
||||
.map { manager.getLogger(it) }
|
||||
.forEach { it.level = Level.INFO }
|
||||
}
|
||||
|
||||
logger.trace("Creating new dex file")
|
||||
val newDexFile = object : DexFile {
|
||||
override fun getClasses() = context.bytecodeContext.classes.also { it.replaceClasses() }
|
||||
override fun getOpcodes() = this@Patcher.opcodes
|
||||
}
|
||||
|
||||
// write modified dex files
|
||||
logger.info("Writing modified dex files")
|
||||
val dexFiles = mutableMapOf<String, MemoryDataStore>()
|
||||
MultiDexIO.writeDexFile(
|
||||
true, -1, // core count
|
||||
dexFiles, NAMER, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
|
||||
)
|
||||
|
||||
return PatcherResult(
|
||||
dexFiles.map {
|
||||
app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0))
|
||||
},
|
||||
metaInfo.doNotCompress?.toList(),
|
||||
resourceFile
|
||||
)
|
||||
context.resourceContext.decodeResources(ResourceContext.ResourceDecodingMode.MANIFEST_ONLY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add [Patch]es to the patcher.
|
||||
* @param patches [Patch]es The patches to add.
|
||||
*/
|
||||
fun addPatches(patches: Iterable<Class<out Patch<Context>>>) {
|
||||
override fun acceptPatches(patches: List<PatchClass>) {
|
||||
/**
|
||||
* Fill the cache with the instances of the [Patch]es for later use.
|
||||
* Note: Dependencies of the [Patch] will be cached as well.
|
||||
* Returns true if at least one patches or its dependencies matches the given predicate.
|
||||
*/
|
||||
fun Class<out Patch<Context>>.isResource() {
|
||||
this.also {
|
||||
if (!ResourcePatch::class.java.isAssignableFrom(it)) return@also
|
||||
// set the mode to decode all resources before running the patches
|
||||
resourceDecodingMode = ResourceDecodingMode.FULL
|
||||
}.dependencies?.forEach { it.java.isResource() }
|
||||
fun PatchClass.anyRecursively(predicate: (PatchClass) -> Boolean): Boolean =
|
||||
predicate(this) || dependencies?.any { dependency ->
|
||||
dependency.java.anyRecursively(predicate)
|
||||
} ?: false
|
||||
|
||||
// Determine if resource patching is required.
|
||||
for (patch in patches) {
|
||||
if (patch.anyRecursively { ResourcePatch::class.java.isAssignableFrom(it) }) {
|
||||
options.resourceDecodingMode = ResourceContext.ResourceDecodingMode.FULL
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
context.patches.addAll(patches.onEach(Class<out Patch<Context>>::isResource))
|
||||
// Determine if merging integrations is required.
|
||||
for (patch in patches) {
|
||||
if (patch.anyRecursively { it.requiresIntegrations }) {
|
||||
context.bytecodeContext.integrations.merge = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
context.patches.addAll(patches)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode resources for the patcher.
|
||||
* Add integrations to the [Patcher].
|
||||
*
|
||||
* @param mode The [ResourceDecodingMode] to use when decoding.
|
||||
* @param integrations The integrations to add. Must be a DEX file or container of DEX files.
|
||||
*/
|
||||
private fun decodeResources(mode: ResourceDecodingMode) {
|
||||
val extInputFile = ExtFile(options.inputFile)
|
||||
try {
|
||||
val androlib = Androlib(BuildOptions().also { it.setBuildOptions(options) })
|
||||
val resourceTable = androlib.getResTable(extInputFile, true)
|
||||
when (mode) {
|
||||
ResourceDecodingMode.FULL -> {
|
||||
val outDir = File(options.resourceCacheDirectory)
|
||||
if (outDir.exists()) {
|
||||
logger.info("Deleting existing resource cache directory")
|
||||
if (!outDir.deleteRecursively()) {
|
||||
logger.error("Failed to delete existing resource cache directory")
|
||||
}
|
||||
}
|
||||
outDir.mkdirs()
|
||||
|
||||
logger.info("Decoding resources")
|
||||
|
||||
// decode resources to cache directory
|
||||
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
|
||||
androlib.decodeResourcesFull(extInputFile, outDir, resourceTable)
|
||||
|
||||
// read additional metadata from the resource table
|
||||
context.packageMetadata.let { metadata ->
|
||||
metadata.metaInfo.usesFramework = UsesFramework().also { framework ->
|
||||
framework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
|
||||
}
|
||||
|
||||
// read files to not compress
|
||||
metadata.metaInfo.doNotCompress = buildList {
|
||||
androlib.recordUncompressedFiles(extInputFile, this)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ResourceDecodingMode.MANIFEST_ONLY -> {
|
||||
logger.info("Decoding AndroidManifest.xml only, because resources are not needed")
|
||||
|
||||
// create decoder for the resource table
|
||||
val decoder = ResAttrDecoder()
|
||||
decoder.currentPackage = ResPackage(resourceTable, 0, null)
|
||||
|
||||
// create xml parser with the decoder
|
||||
val axmlParser = AXmlResourceParser()
|
||||
axmlParser.attrDecoder = decoder
|
||||
|
||||
// parse package information with the decoder and parser which will set required values in the resource table
|
||||
// instead of decodeManifest another more low level solution can be created to make it faster/better
|
||||
XmlPullStreamDecoder(
|
||||
axmlParser, AndrolibResources().resXmlSerializer
|
||||
).decodeManifest(
|
||||
extInputFile.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// read of the resourceTable which is created by reading the manifest file
|
||||
context.packageMetadata.let { metadata ->
|
||||
metadata.packageName = resourceTable.currentResPackage.name
|
||||
metadata.packageVersion = resourceTable.versionInfo.versionName
|
||||
metadata.metaInfo.versionInfo = resourceTable.versionInfo
|
||||
metadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
||||
}
|
||||
} finally {
|
||||
extInputFile.close()
|
||||
}
|
||||
override fun acceptIntegrations(integrations: List<File>) {
|
||||
context.bytecodeContext.integrations.addAll(integrations)
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute patches added the patcher.
|
||||
* Execute [Patch]es that were added to ReVanced [Patcher].
|
||||
*
|
||||
* @param stopOnError If true, the patches will stop on the first error.
|
||||
* @param returnOnError If true, ReVanced [Patcher] will return immediately if a [Patch] fails.
|
||||
* @return A pair of the name of the [Patch] and its [PatchResult].
|
||||
*/
|
||||
fun executePatches(stopOnError: Boolean = false): Sequence<Pair<String, Result<PatchResultSuccess>>> {
|
||||
override fun apply(returnOnError: Boolean) = flow {
|
||||
class ExecutedPatch(val patchInstance: Patch<Context<*>>, val patchResult: PatchResult)
|
||||
|
||||
/**
|
||||
* Execute a [Patch] and its dependencies recursively.
|
||||
*
|
||||
* @param patchClass The [Patch] to execute.
|
||||
* @param executedPatches A map of [Patch]es paired to a boolean indicating their success, to prevent infinite recursion.
|
||||
* @param executedPatches A map to prevent [Patch]es from being executed twice due to dependencies.
|
||||
* @return The result of executing the [Patch].
|
||||
*/
|
||||
fun executePatch(
|
||||
patchClass: Class<out Patch<Context>>,
|
||||
patchClass: PatchClass,
|
||||
executedPatches: LinkedHashMap<String, ExecutedPatch>
|
||||
): PatchResult {
|
||||
val patchName = patchClass.patchName
|
||||
|
||||
// if the patch has already applied silently skip it
|
||||
if (executedPatches.contains(patchName)) {
|
||||
if (!executedPatches[patchName]!!.success)
|
||||
return PatchResultError("'$patchName' did not succeed previously")
|
||||
executedPatches[patchName]?.let { executedPatch ->
|
||||
executedPatch.patchResult.exception ?: return executedPatch.patchResult
|
||||
|
||||
logger.trace("Skipping '$patchName' because it has already been applied")
|
||||
|
||||
return PatchResultSuccess()
|
||||
// Return a new result with an exception indicating that the patch was not executed previously,
|
||||
// because it is a dependency of another patch that failed.
|
||||
return PatchResult(patchName, PatchException("'$patchName' did not succeed previously"))
|
||||
}
|
||||
|
||||
// recursively execute all dependency patches
|
||||
// Recursively execute all dependency patches.
|
||||
patchClass.dependencies?.forEach { dependencyClass ->
|
||||
val dependency = dependencyClass.java
|
||||
|
||||
val result = executePatch(dependency, executedPatches)
|
||||
if (result.isSuccess()) return@forEach
|
||||
|
||||
return PatchResultError(
|
||||
"'$patchName' depends on '${dependency.patchName}' but the following error was raised: " +
|
||||
result.error()!!.let { it.cause?.stackTraceToString() ?: it.message }
|
||||
)
|
||||
}
|
||||
|
||||
val isResourcePatch = ResourcePatch::class.java.isAssignableFrom(patchClass)
|
||||
val patchInstance = patchClass.getDeclaredConstructor().newInstance()
|
||||
|
||||
// TODO: implement this in a more polymorphic way
|
||||
val patchContext = if (isResourcePatch) {
|
||||
context.resourceContext
|
||||
} else {
|
||||
context.bytecodeContext.also { context ->
|
||||
(patchInstance as BytecodePatch).fingerprints?.resolve(
|
||||
context,
|
||||
context.classes.classes
|
||||
result.exception?.let {
|
||||
return PatchResult(
|
||||
patchName,
|
||||
PatchException(
|
||||
"'$patchName' depends on '${dependency.patchName}' that raised an exception: $it"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
||||
// TODO: Implement this in a more polymorphic way.
|
||||
val patchInstance = patchClass.getDeclaredConstructor().newInstance()
|
||||
|
||||
val patchContext = if (patchInstance is BytecodePatch) {
|
||||
patchInstance.fingerprints?.resolveUsingLookupMap(context.bytecodeContext)
|
||||
|
||||
context.bytecodeContext
|
||||
} else {
|
||||
context.resourceContext
|
||||
}
|
||||
|
||||
return try {
|
||||
patchInstance.execute(patchContext).also {
|
||||
executedPatches[patchName] = ExecutedPatch(patchInstance, it.isSuccess())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
PatchResultError(e).also {
|
||||
executedPatches[patchName] = ExecutedPatch(patchInstance, false)
|
||||
}
|
||||
patchInstance.execute(patchContext)
|
||||
|
||||
PatchResult(patchName)
|
||||
} catch (exception: PatchException) {
|
||||
PatchResult(patchName, exception)
|
||||
} catch (exception: Exception) {
|
||||
PatchResult(patchName, PatchException(exception))
|
||||
}.also { executedPatches[patchName] = ExecutedPatch(patchInstance, it) }
|
||||
}
|
||||
|
||||
if (context.bytecodeContext.integrations.merge) context.bytecodeContext.integrations.flush()
|
||||
|
||||
MethodFingerprint.initializeFingerprintResolutionLookupMaps(context.bytecodeContext)
|
||||
|
||||
// Prevent from decoding the app manifest twice if it is not needed.
|
||||
if (options.resourceDecodingMode == ResourceContext.ResourceDecodingMode.FULL)
|
||||
context.resourceContext.decodeResources(ResourceContext.ResourceDecodingMode.FULL)
|
||||
|
||||
logger.info("Executing patches")
|
||||
|
||||
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // Key is name.
|
||||
|
||||
context.patches.forEach { patch ->
|
||||
val result = executePatch(patch, executedPatches)
|
||||
|
||||
// If the patch failed, or if the patch is not closeable, emit the result.
|
||||
// Results of patches that are closeable will be emitted later.
|
||||
result.exception?.let {
|
||||
emit(result)
|
||||
|
||||
if (returnOnError) return@flow
|
||||
} ?: run {
|
||||
if (executedPatches[result.patchName]!!.patchInstance is Closeable) return@run
|
||||
|
||||
emit(result)
|
||||
}
|
||||
}
|
||||
|
||||
return sequence {
|
||||
// prevent from decoding the manifest twice if it is not needed
|
||||
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
|
||||
executedPatches.values
|
||||
.filter { it.patchResult.exception == null }
|
||||
.filter { it.patchInstance is Closeable }.asReversed().forEach { executedPatch ->
|
||||
val patchName = executedPatch.patchResult.patchName
|
||||
|
||||
logger.trace("Executing all patches")
|
||||
val result = try {
|
||||
(executedPatch.patchInstance as Closeable).close()
|
||||
|
||||
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
|
||||
|
||||
try {
|
||||
context.patches.forEach { patch ->
|
||||
val patchResult = executePatch(patch, executedPatches)
|
||||
|
||||
val result = if (patchResult.isSuccess()) {
|
||||
Result.success(patchResult.success()!!)
|
||||
} else {
|
||||
Result.failure(patchResult.error()!!)
|
||||
}
|
||||
|
||||
yield(patch.patchName to result)
|
||||
if (stopOnError && patchResult.isError()) return@sequence
|
||||
executedPatch.patchResult
|
||||
} catch (exception: PatchException) {
|
||||
PatchResult(patchName, exception)
|
||||
} catch (exception: Exception) {
|
||||
PatchResult(patchName, PatchException(exception))
|
||||
}
|
||||
} finally {
|
||||
executedPatches.values.reversed().forEach { (patch, _) ->
|
||||
patch.close()
|
||||
|
||||
result.exception?.let {
|
||||
emit(
|
||||
PatchResult(
|
||||
patchName,
|
||||
PatchException("'$patchName' raised an exception while being closed: $it")
|
||||
)
|
||||
)
|
||||
|
||||
if (returnOnError) return@flow
|
||||
} ?: run {
|
||||
executedPatch
|
||||
.patchInstance::class
|
||||
.java
|
||||
.findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)
|
||||
?: return@run
|
||||
|
||||
emit(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
MethodFingerprint.clearFingerprintResolutionLookupMaps()
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of decoding the resources.
|
||||
* Compile and save the patched APK file.
|
||||
*
|
||||
* @return The [PatcherResult] containing the patched input files.
|
||||
*/
|
||||
private enum class ResourceDecodingMode {
|
||||
/**
|
||||
* Decode all resources.
|
||||
*/
|
||||
FULL,
|
||||
|
||||
/**
|
||||
* Decode the manifest file only.
|
||||
*/
|
||||
MANIFEST_ONLY,
|
||||
}
|
||||
override fun get() = PatcherResult(
|
||||
context.bytecodeContext.get(),
|
||||
context.resourceContext.get(),
|
||||
context.packageMetadata.apkInfo.doNotCompress?.toList()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A result of executing a [Patch].
|
||||
*
|
||||
* @param patchInstance The instance of the [Patch] that was applied.
|
||||
* @param success The result of the [Patch].
|
||||
*/
|
||||
internal data class ExecutedPatch(val patchInstance: Patch<Context>, val success: Boolean)
|
||||
@@ -1,19 +1,37 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.data.PackageMetadata
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import java.io.File
|
||||
import app.revanced.patcher.patch.PatchClass
|
||||
import brut.androlib.apk.ApkInfo
|
||||
import brut.directory.ExtFile
|
||||
|
||||
data class PatcherContext(
|
||||
val classes: MutableList<ClassDef>,
|
||||
val resourceCacheDirectory: File,
|
||||
) {
|
||||
val packageMetadata = PackageMetadata()
|
||||
internal val patches = mutableListOf<Class<out Patch<Context>>>()
|
||||
internal val bytecodeContext = BytecodeContext(classes)
|
||||
internal val resourceContext = ResourceContext(resourceCacheDirectory)
|
||||
/**
|
||||
* A context for ReVanced [Patcher].
|
||||
*
|
||||
* @param options The [PatcherOptions] used to create this context.
|
||||
*/
|
||||
class PatcherContext internal constructor(options: PatcherOptions) {
|
||||
/**
|
||||
* [PackageMetadata] of the supplied [PatcherOptions.inputFile].
|
||||
*/
|
||||
val packageMetadata = PackageMetadata(ApkInfo(ExtFile(options.inputFile)))
|
||||
|
||||
/**
|
||||
* The list of [Patch]es to execute.
|
||||
*/
|
||||
internal val patches = mutableListOf<PatchClass>()
|
||||
|
||||
/**
|
||||
* The [ResourceContext] of this [PatcherContext].
|
||||
* This holds the current state of the resources.
|
||||
*/
|
||||
internal val resourceContext = ResourceContext(this, options)
|
||||
|
||||
/**
|
||||
* The [BytecodeContext] of this [PatcherContext].
|
||||
* This holds the current state of the bytecode.
|
||||
*/
|
||||
internal val bytecodeContext = BytecodeContext(options)
|
||||
}
|
||||
@@ -1,21 +1,73 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.logging.Logger
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.logging.impl.NopLogger
|
||||
import brut.androlib.Config
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* Options for the [Patcher].
|
||||
* @param inputFile The input file (usually an apk file).
|
||||
* @param resourceCacheDirectory Directory to cache resources.
|
||||
* @param aaptPath Optional path to a custom aapt binary.
|
||||
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
||||
* @param logger Custom logger implementation for the [Patcher].
|
||||
* Options for ReVanced [Patcher].
|
||||
* @param inputFile The input file to patch.
|
||||
* @param resourceCachePath The path to the directory to use for caching resources.
|
||||
* @param aaptBinaryPath The path to a custom aapt binary.
|
||||
* @param frameworkFileDirectory The path to the directory to cache the framework file in.
|
||||
* @param unusedLogger The logger to use for logging.
|
||||
*/
|
||||
data class PatcherOptions(
|
||||
data class PatcherOptions
|
||||
@Deprecated("Use the constructor without the logger parameter instead")
|
||||
constructor(
|
||||
internal val inputFile: File,
|
||||
internal val resourceCacheDirectory: String,
|
||||
internal val aaptPath: String? = null,
|
||||
internal val frameworkFolderLocation: String? = null,
|
||||
internal val logger: Logger = NopLogger
|
||||
)
|
||||
internal val resourceCachePath: File = File("revanced-resource-cache"),
|
||||
internal val aaptBinaryPath: String? = null,
|
||||
internal val frameworkFileDirectory: String? = null,
|
||||
internal val unusedLogger: app.revanced.patcher.logging.Logger = NopLogger
|
||||
) {
|
||||
private val logger = Logger.getLogger(PatcherOptions::class.java.name)
|
||||
|
||||
/**
|
||||
* The mode to use for resource decoding.
|
||||
* @see ResourceContext.ResourceDecodingMode
|
||||
*/
|
||||
internal var resourceDecodingMode = ResourceContext.ResourceDecodingMode.MANIFEST_ONLY
|
||||
|
||||
/**
|
||||
* The configuration to use for resource decoding and compiling.
|
||||
*/
|
||||
internal val resourceConfig = Config.getDefaultConfig().apply {
|
||||
useAapt2 = true
|
||||
aaptPath = aaptBinaryPath ?: ""
|
||||
frameworkDirectory = frameworkFileDirectory
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for ReVanced [Patcher].
|
||||
* @param inputFile The input file to patch.
|
||||
* @param resourceCachePath The path to the directory to use for caching resources.
|
||||
* @param aaptBinaryPath The path to a custom aapt binary.
|
||||
* @param frameworkFileDirectory The path to the directory to cache the framework file in.
|
||||
*/
|
||||
constructor(
|
||||
inputFile: File,
|
||||
resourceCachePath: File = File("revanced-resource-cache"),
|
||||
aaptBinaryPath: String? = null,
|
||||
frameworkFileDirectory: String? = null,
|
||||
) : this(
|
||||
inputFile,
|
||||
resourceCachePath,
|
||||
aaptBinaryPath,
|
||||
frameworkFileDirectory,
|
||||
NopLogger
|
||||
)
|
||||
|
||||
fun recreateResourceCacheDirectory() = resourceCachePath.also {
|
||||
if (it.exists()) {
|
||||
logger.info("Deleting existing resource cache directory")
|
||||
|
||||
if (!it.deleteRecursively())
|
||||
logger.severe("Failed to delete existing resource cache directory")
|
||||
}
|
||||
|
||||
it.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.util.dex.DexFile
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* The result of a patcher.
|
||||
* @param dexFiles The patched dex files.
|
||||
* @param doNotCompress List of relative paths to files to exclude from compressing.
|
||||
* @param resourceFile File containing resources that need to be extracted into the APK.
|
||||
* @param doNotCompress List of relative paths of files to exclude from compressing.
|
||||
*/
|
||||
data class PatcherResult(
|
||||
val dexFiles: List<DexFile>,
|
||||
val doNotCompress: List<String>? = null,
|
||||
val resourceFile: File?
|
||||
)
|
||||
val dexFiles: List<PatchedDexFile>,
|
||||
val resourceFile: File?,
|
||||
val doNotCompress: List<String>? = null
|
||||
) {
|
||||
/**
|
||||
* Wrapper for dex files.
|
||||
* @param name The original name of the dex file.
|
||||
* @param stream The dex file as [InputStream].
|
||||
*/
|
||||
class PatchedDexFile(val name: String, val stream: InputStream)
|
||||
}
|
||||
8
src/main/kotlin/app/revanced/patcher/PatchesConsumer.kt
Normal file
8
src/main/kotlin/app/revanced/patcher/PatchesConsumer.kt
Normal file
@@ -0,0 +1,8 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.patch.PatchClass
|
||||
|
||||
@FunctionalInterface
|
||||
interface PatchesConsumer {
|
||||
fun acceptPatches(patches: List<PatchClass>)
|
||||
}
|
||||
@@ -7,8 +7,6 @@ import app.revanced.patcher.patch.Patch
|
||||
* @param compatiblePackages A list of packages a [Patch] is compatible with.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Compatibility(
|
||||
val compatiblePackages: Array<Package>,
|
||||
)
|
||||
@@ -19,8 +17,6 @@ annotation class Compatibility(
|
||||
* @param versions The versions of the package the [Patch] is compatible with.
|
||||
*/
|
||||
@Target()
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Package(
|
||||
val name: String,
|
||||
val versions: Array<String> = [],
|
||||
|
||||
@@ -7,8 +7,6 @@ import app.revanced.patcher.patch.Patch
|
||||
* @param name A suggestive name for the [Patch].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Name(
|
||||
val name: String,
|
||||
)
|
||||
@@ -18,20 +16,6 @@ annotation class Name(
|
||||
* @param description A description for the [Patch].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Description(
|
||||
val description: String,
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Annotation to version a [Patch].
|
||||
* @param version The version of a [Patch].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Version(
|
||||
val version: String,
|
||||
)
|
||||
)
|
||||
155
src/main/kotlin/app/revanced/patcher/data/BytecodeContext.kt
Normal file
155
src/main/kotlin/app/revanced/patcher/data/BytecodeContext.kt
Normal file
@@ -0,0 +1,155 @@
|
||||
package app.revanced.patcher.data
|
||||
|
||||
import app.revanced.patcher.PatcherContext
|
||||
import app.revanced.patcher.PatcherOptions
|
||||
import app.revanced.patcher.PatcherResult
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.annotations.RequiresIntegrations
|
||||
import app.revanced.patcher.util.ClassMerger.merge
|
||||
import app.revanced.patcher.util.ProxyClassList
|
||||
import app.revanced.patcher.util.method.MethodWalker
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import com.android.tools.smali.dexlib2.Opcodes
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.DexFile
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.writer.io.MemoryDataStore
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.DexIO
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import java.io.File
|
||||
import java.io.Flushable
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* A context for bytecode.
|
||||
* This holds the current state of the bytecode.
|
||||
*
|
||||
* @param options The [PatcherOptions] used to create this context.
|
||||
*/
|
||||
class BytecodeContext internal constructor(private val options: PatcherOptions) :
|
||||
Context<List<PatcherResult.PatchedDexFile>> {
|
||||
private val logger = Logger.getLogger(BytecodeContext::class.java.name)
|
||||
|
||||
/**
|
||||
* [Opcodes] of the supplied [PatcherOptions.inputFile].
|
||||
*/
|
||||
internal lateinit var opcodes: Opcodes
|
||||
|
||||
/**
|
||||
* The list of classes.
|
||||
*/
|
||||
val classes by lazy {
|
||||
ProxyClassList(
|
||||
MultiDexIO.readDexFile(
|
||||
true, options.inputFile, BasicDexFileNamer(), null, null
|
||||
).also { opcodes = it.opcodes }.classes.toMutableSet()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The [Integrations] of this [PatcherContext].
|
||||
*/
|
||||
internal val integrations = Integrations()
|
||||
|
||||
/**
|
||||
* Find a class by a given class name.
|
||||
*
|
||||
* @param className The name of the class.
|
||||
* @return A proxy for the first class that matches the class name.
|
||||
*/
|
||||
fun findClass(className: String) = findClass { it.type.contains(className) }
|
||||
|
||||
/**
|
||||
* Find a class by a given predicate.
|
||||
*
|
||||
* @param predicate A predicate to match the class.
|
||||
* @return A proxy for the first class that matches the predicate.
|
||||
*/
|
||||
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||
// if we already proxied the class matching the predicate...
|
||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||
classes.find(predicate)?.let { proxy(it) }
|
||||
|
||||
/**
|
||||
* Proxy a class.
|
||||
* This will allow the class to be modified.
|
||||
*
|
||||
* @param classDef The class to proxy.
|
||||
* @return A proxy for the class.
|
||||
*/
|
||||
fun proxy(classDef: ClassDef) = this.classes.proxies.find { it.immutableClass.type == classDef.type } ?: let {
|
||||
ClassProxy(classDef).also { this.classes.add(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [MethodWalker] instance for the current [BytecodeContext].
|
||||
*
|
||||
* @param startMethod The method to start at.
|
||||
* @return A [MethodWalker] instance.
|
||||
*/
|
||||
fun toMethodWalker(startMethod: Method) = MethodWalker(this, startMethod)
|
||||
|
||||
/**
|
||||
* The integrations of a [PatcherContext].
|
||||
*/
|
||||
internal inner class Integrations : MutableList<File> by mutableListOf(), Flushable {
|
||||
/**
|
||||
* Whether to merge integrations.
|
||||
* True when any supplied [Patch] is annotated with [RequiresIntegrations].
|
||||
*/
|
||||
var merge = false
|
||||
|
||||
/**
|
||||
* Merge integrations into the [BytecodeContext] and flush all [Integrations].
|
||||
*/
|
||||
override fun flush() {
|
||||
if (!merge) return
|
||||
|
||||
this@Integrations.forEach { integrations ->
|
||||
MultiDexIO.readDexFile(
|
||||
true,
|
||||
integrations, BasicDexFileNamer(),
|
||||
null,
|
||||
null
|
||||
).classes.forEach classDef@{ classDef ->
|
||||
val existingClass = classes.find { it == classDef } ?: run {
|
||||
logger.fine("Merging $classDef")
|
||||
classes.add(classDef)
|
||||
return@classDef
|
||||
}
|
||||
|
||||
logger.fine("$classDef exists. Adding missing methods and fields.")
|
||||
|
||||
existingClass.merge(classDef, this@BytecodeContext).let { mergedClass ->
|
||||
// If the class was merged, replace the original class with the merged class.
|
||||
if (mergedClass === existingClass) return@let
|
||||
classes.apply { remove(existingClass); add(mergedClass) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile bytecode from the [BytecodeContext].
|
||||
*
|
||||
* @return The compiled bytecode.
|
||||
*/
|
||||
override fun get(): List<PatcherResult.PatchedDexFile> {
|
||||
logger.info("Compiling modified dex files")
|
||||
|
||||
return mutableMapOf<String, MemoryDataStore>().apply {
|
||||
MultiDexIO.writeDexFile(
|
||||
true, -1, // Defaults to amount of available cores.
|
||||
this, BasicDexFileNamer(), object : DexFile {
|
||||
override fun getClasses() = this@BytecodeContext.classes.also(ProxyClassList::replaceClasses)
|
||||
override fun getOpcodes() = this@BytecodeContext.opcodes
|
||||
}, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
|
||||
)
|
||||
}.map { PatcherResult.PatchedDexFile(it.key, it.value.readAt(0)) }
|
||||
}
|
||||
}
|
||||
@@ -1,181 +1,9 @@
|
||||
package app.revanced.patcher.data
|
||||
|
||||
import app.revanced.patcher.util.ProxyBackedClassList
|
||||
import app.revanced.patcher.util.method.MethodWalker
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.w3c.dom.Document
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* A common interface to constrain [Context] to [BytecodeContext] and [ResourceContext].
|
||||
* A common interface for contexts such as [ResourceContext] and [BytecodeContext].
|
||||
*/
|
||||
|
||||
sealed interface Context
|
||||
|
||||
class BytecodeContext internal constructor(classes: MutableList<ClassDef>) : Context {
|
||||
/**
|
||||
* The list of classes.
|
||||
*/
|
||||
val classes = ProxyBackedClassList(classes)
|
||||
|
||||
/**
|
||||
* Find a class by a given class name.
|
||||
*
|
||||
* @param className The name of the class.
|
||||
* @return A proxy for the first class that matches the class name.
|
||||
*/
|
||||
fun findClass(className: String) = findClass { it.type.contains(className) }
|
||||
|
||||
/**
|
||||
* Find a class by a given predicate.
|
||||
*
|
||||
* @param predicate A predicate to match the class.
|
||||
* @return A proxy for the first class that matches the predicate.
|
||||
*/
|
||||
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||
// if we already proxied the class matching the predicate...
|
||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||
classes.find(predicate)?.let { proxy(it) }
|
||||
|
||||
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||
if (proxy == null) {
|
||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||
this.classes.add(proxy)
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
|
||||
private companion object {
|
||||
inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
|
||||
for (element in this) {
|
||||
if (predicate(element)) {
|
||||
return element
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [MethodWalker] instance for the current [BytecodeContext].
|
||||
*
|
||||
* @param startMethod The method to start at.
|
||||
* @return A [MethodWalker] instance.
|
||||
*/
|
||||
fun BytecodeContext.toMethodWalker(startMethod: Method): MethodWalker {
|
||||
return MethodWalker(this, startMethod)
|
||||
}
|
||||
|
||||
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
|
||||
for ((index, element) in this.withIndex()) {
|
||||
if (predicate(element)) {
|
||||
return element to index
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
class ResourceContext internal constructor(private val resourceCacheDirectory: File) : Context, Iterable<File> {
|
||||
val xmlEditor = XmlFileHolder()
|
||||
|
||||
operator fun get(path: String) = resourceCacheDirectory.resolve(path)
|
||||
|
||||
override fun iterator() = resourceCacheDirectory.walkTopDown().iterator()
|
||||
|
||||
inner class XmlFileHolder {
|
||||
operator fun get(inputStream: InputStream) =
|
||||
DomFileEditor(inputStream)
|
||||
|
||||
operator fun get(path: String): DomFileEditor {
|
||||
return DomFileEditor(this@ResourceContext[path])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for a file that can be edited as a dom document.
|
||||
*
|
||||
* This constructor does not check for locks to the file when writing.
|
||||
* Use the secondary constructor.
|
||||
*
|
||||
* @param inputStream the input stream to read the xml file from.
|
||||
* @param outputStream the output stream to write the xml file to. If null, the file will be read only.
|
||||
*
|
||||
*/
|
||||
class DomFileEditor internal constructor(
|
||||
private val inputStream: InputStream,
|
||||
private val outputStream: Lazy<OutputStream>? = null,
|
||||
) : Closeable {
|
||||
// path to the xml file to unlock the resource when closing the editor
|
||||
private var filePath: String? = null
|
||||
private var closed: Boolean = false
|
||||
|
||||
/**
|
||||
* The document of the xml file
|
||||
*/
|
||||
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
|
||||
.also(Document::normalize)
|
||||
|
||||
|
||||
// lazily open an output stream
|
||||
// this is required because when constructing a DomFileEditor the output stream is created along with the input stream, which is not allowed
|
||||
// the workaround is to lazily create the output stream. This way it would be used after the input stream is closed, which happens in the constructor
|
||||
constructor(file: File) : this(file.inputStream(), lazy { file.outputStream() }) {
|
||||
// increase the lock
|
||||
locks.merge(file.path, 1, Integer::sum)
|
||||
filePath = file.path
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the editor. Write backs and decreases the lock count.
|
||||
*
|
||||
* Will not write back to the file if the file is still locked.
|
||||
*/
|
||||
override fun close() {
|
||||
if (closed) return
|
||||
|
||||
inputStream.close()
|
||||
|
||||
// if the output stream is not null, do not close it
|
||||
outputStream?.let {
|
||||
// prevent writing to same file, if it is being locked
|
||||
// isLocked will be false if the editor was created through a stream
|
||||
val isLocked = filePath?.let { path ->
|
||||
val isLocked = locks[path]!! > 1
|
||||
// decrease the lock count if the editor was opened for a file
|
||||
locks.merge(path, -1, Integer::sum)
|
||||
isLocked
|
||||
} ?: false
|
||||
|
||||
// if unlocked, write back to the file
|
||||
if (!isLocked) {
|
||||
it.value.use { stream ->
|
||||
val result = StreamResult(stream)
|
||||
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)
|
||||
}
|
||||
|
||||
it.value.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
closed = true
|
||||
}
|
||||
|
||||
private companion object {
|
||||
// map of concurrent open files
|
||||
val locks = mutableMapOf<String, Int>()
|
||||
}
|
||||
}
|
||||
sealed interface Context<T> : Supplier<T>
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.patcher.data
|
||||
|
||||
import brut.androlib.meta.MetaInfo
|
||||
|
||||
/**
|
||||
* Metadata about a package.
|
||||
*/
|
||||
class PackageMetadata {
|
||||
lateinit var packageName: String
|
||||
lateinit var packageVersion: String
|
||||
|
||||
internal val metaInfo: MetaInfo = MetaInfo()
|
||||
}
|
||||
160
src/main/kotlin/app/revanced/patcher/data/ResourceContext.kt
Normal file
160
src/main/kotlin/app/revanced/patcher/data/ResourceContext.kt
Normal file
@@ -0,0 +1,160 @@
|
||||
package app.revanced.patcher.data
|
||||
|
||||
import app.revanced.patcher.PatcherContext
|
||||
import app.revanced.patcher.PatcherOptions
|
||||
import app.revanced.patcher.util.DomFileEditor
|
||||
import brut.androlib.AaptInvoker
|
||||
import brut.androlib.ApkDecoder
|
||||
import brut.androlib.apk.UsesFramework
|
||||
import brut.androlib.res.Framework
|
||||
import brut.androlib.res.ResourcesDecoder
|
||||
import brut.androlib.res.decoder.AndroidManifestResourceParser
|
||||
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
||||
import brut.androlib.res.xml.ResXmlPatcher
|
||||
import brut.directory.ExtFile
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* A context for resources.
|
||||
* This holds the current state of the resources.
|
||||
*
|
||||
* @param context The [PatcherContext] to create the context for.
|
||||
*/
|
||||
class ResourceContext internal constructor(
|
||||
private val context: PatcherContext,
|
||||
private val options: PatcherOptions
|
||||
) : Context<File?>, Iterable<File> {
|
||||
private val logger = Logger.getLogger(ResourceContext::class.java.name)
|
||||
|
||||
val xmlEditor = XmlFileHolder()
|
||||
|
||||
/**
|
||||
* Decode resources for the patcher.
|
||||
*
|
||||
* @param mode The [ResourceDecodingMode] to use when decoding.
|
||||
*/
|
||||
internal fun decodeResources(mode: ResourceDecodingMode) = with(context.packageMetadata.apkInfo) {
|
||||
// Needed to decode resources.
|
||||
val resourcesDecoder = ResourcesDecoder(options.resourceConfig, this)
|
||||
|
||||
when (mode) {
|
||||
ResourceDecodingMode.FULL -> {
|
||||
val outDir = options.recreateResourceCacheDirectory()
|
||||
|
||||
logger.info("Decoding resources")
|
||||
|
||||
resourcesDecoder.decodeResources(outDir)
|
||||
resourcesDecoder.decodeManifest(outDir)
|
||||
|
||||
// Needed to record uncompressed files.
|
||||
val apkDecoder = ApkDecoder(options.resourceConfig, this)
|
||||
apkDecoder.recordUncompressedFiles(resourcesDecoder.resFileMapping)
|
||||
|
||||
usesFramework = UsesFramework().apply {
|
||||
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
|
||||
}
|
||||
}
|
||||
|
||||
ResourceDecodingMode.MANIFEST_ONLY -> {
|
||||
logger.info("Decoding app manifest")
|
||||
|
||||
// Decode manually instead of using resourceDecoder.decodeManifest
|
||||
// because it does not support decoding to an OutputStream.
|
||||
XmlPullStreamDecoder(
|
||||
AndroidManifestResourceParser(resourcesDecoder.resTable),
|
||||
resourcesDecoder.resXmlSerializer
|
||||
).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.
|
||||
context.packageMetadata.let { metadata ->
|
||||
metadata.packageName = resourcesDecoder.resTable.packageRenamed
|
||||
versionInfo.let {
|
||||
metadata.packageVersion = it.versionName ?: it.versionCode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
operator fun get(path: String) = options.resourceCachePath.resolve(path)
|
||||
|
||||
override fun iterator() = options.resourceCachePath.walkTopDown().iterator()
|
||||
|
||||
|
||||
/**
|
||||
* Compile resources from the [ResourceContext].
|
||||
*
|
||||
* @return The compiled resources.
|
||||
*/
|
||||
override fun get(): File? {
|
||||
var resourceFile: File? = null
|
||||
|
||||
if (options.resourceDecodingMode == ResourceDecodingMode.FULL) {
|
||||
logger.info("Compiling modified resources")
|
||||
|
||||
val cacheDirectory = ExtFile(options.resourceCachePath)
|
||||
val aaptFile = cacheDirectory.resolve("aapt_temp_file").also {
|
||||
Files.deleteIfExists(it.toPath())
|
||||
}.also { resourceFile = it }
|
||||
|
||||
try {
|
||||
AaptInvoker(
|
||||
options.resourceConfig, context.packageMetadata.apkInfo
|
||||
).invokeAapt(aaptFile,
|
||||
cacheDirectory.resolve("AndroidManifest.xml").also {
|
||||
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it)
|
||||
},
|
||||
cacheDirectory.resolve("res"),
|
||||
null,
|
||||
null,
|
||||
context.packageMetadata.apkInfo.usesFramework.let { usesFramework ->
|
||||
usesFramework.ids.map { id ->
|
||||
Framework(options.resourceConfig).getFrameworkApk(id, usesFramework.tag)
|
||||
}.toTypedArray()
|
||||
})
|
||||
} finally {
|
||||
cacheDirectory.close()
|
||||
}
|
||||
}
|
||||
|
||||
return resourceFile
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of decoding the resources.
|
||||
*/
|
||||
internal enum class ResourceDecodingMode {
|
||||
/**
|
||||
* Decode all resources.
|
||||
*/
|
||||
FULL,
|
||||
|
||||
/**
|
||||
* Decode the manifest file only.
|
||||
*/
|
||||
MANIFEST_ONLY,
|
||||
}
|
||||
|
||||
inner class XmlFileHolder {
|
||||
operator fun get(inputStream: InputStream) =
|
||||
DomFileEditor(inputStream)
|
||||
|
||||
operator fun get(path: String): DomFileEditor {
|
||||
return DomFileEditor(this@ResourceContext[path])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,33 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.annotation.*
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.OptionsContainer
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchOptions
|
||||
import app.revanced.patcher.patch.annotations.DependsOn
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.companionObjectInstance
|
||||
|
||||
/**
|
||||
* Recursively find a given annotation on a class.
|
||||
* @param targetAnnotation The annotation to find.
|
||||
* @return The annotation.
|
||||
*/
|
||||
private fun <T : Annotation> Class<*>.findAnnotationRecursively(targetAnnotation: KClass<T>) =
|
||||
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||
internal object AnnotationExtensions {
|
||||
/**
|
||||
* Recursively find a given annotation on a class.
|
||||
*
|
||||
* @param targetAnnotation The annotation to find.
|
||||
* @return The annotation.
|
||||
*/
|
||||
fun <T : Annotation> Class<*>.findAnnotationRecursively(targetAnnotation: KClass<T>): T? {
|
||||
fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
||||
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
|
||||
): T? {
|
||||
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
||||
|
||||
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
|
||||
|
||||
private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
||||
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
|
||||
): T? {
|
||||
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
||||
for (annotation in this.annotations) {
|
||||
if (traversed.contains(annotation)) continue
|
||||
traversed.add(annotation)
|
||||
|
||||
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
|
||||
|
||||
for (annotation in this.annotations) {
|
||||
if (traversed.contains(annotation)) continue
|
||||
traversed.add(annotation)
|
||||
|
||||
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) ?: continue
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
object PatchExtensions {
|
||||
val Class<out Patch<Context>>.patchName: String
|
||||
get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName
|
||||
|
||||
val Class<out Patch<Context>>.version
|
||||
get() = findAnnotationRecursively(Version::class)?.version
|
||||
|
||||
val Class<out Patch<Context>>.include
|
||||
get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include
|
||||
|
||||
val Class<out Patch<Context>>.description
|
||||
get() = findAnnotationRecursively(Description::class)?.description
|
||||
|
||||
val Class<out Patch<Context>>.dependencies
|
||||
get() = findAnnotationRecursively(DependsOn::class)?.dependencies
|
||||
|
||||
val Class<out Patch<Context>>.compatiblePackages
|
||||
get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages
|
||||
|
||||
val Class<out Patch<Context>>.options: PatchOptions?
|
||||
get() = kotlin.companionObject?.let { cl ->
|
||||
if (cl.visibility != KVisibility.PUBLIC) return null
|
||||
kotlin.companionObjectInstance?.let {
|
||||
(it as? OptionsContainer)?.options
|
||||
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed))
|
||||
?: continue
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
object MethodFingerprintExtensions {
|
||||
val MethodFingerprint.name: String
|
||||
get() = this.javaClass.simpleName
|
||||
|
||||
val MethodFingerprint.fuzzyPatternScanMethod
|
||||
get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class)
|
||||
|
||||
val MethodFingerprint.fuzzyScanThreshold
|
||||
get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||
return this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||
}
|
||||
}
|
||||
@@ -1,247 +1,33 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patcher.util.smali.toInstruction
|
||||
import app.revanced.patcher.util.smali.toInstructions
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.builder.BuilderInstruction
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction
|
||||
import org.jf.dexlib2.builder.Label
|
||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
||||
import org.jf.dexlib2.builder.instruction.*
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
import org.jf.dexlib2.immutable.ImmutableMethod
|
||||
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
||||
import java.io.OutputStream
|
||||
|
||||
infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
|
||||
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||
|
||||
fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
||||
for (i in instructions.lastIndex downTo 0) {
|
||||
this.addInstruction(index, instructions[i])
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) {
|
||||
for (instruction in instructions) {
|
||||
this.addInstruction(instruction)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
||||
for (i in instructions.lastIndex downTo 0) {
|
||||
this.replaceInstruction(index + i, instructions[i])
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) {
|
||||
for (i in count downTo 0) {
|
||||
this.removeInstruction(index + i)
|
||||
}
|
||||
}
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
/**
|
||||
* Clones the method.
|
||||
* @param registerCount This parameter allows you to change the register count of the method.
|
||||
* This may be a positive or negative number.
|
||||
* @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy.
|
||||
*/
|
||||
internal fun Method.clone(registerCount: Int = 0): ImmutableMethod {
|
||||
val clonedImplementation = implementation?.let {
|
||||
ImmutableMethodImplementation(
|
||||
it.registerCount + registerCount,
|
||||
it.instructions,
|
||||
it.tryBlocks,
|
||||
it.debugItems,
|
||||
)
|
||||
}
|
||||
return ImmutableMethod(
|
||||
returnType, name, parameters, returnType, accessFlags, annotations, hiddenApiRestrictions, clonedImplementation
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a smali instruction to the method.
|
||||
* @param instruction The smali instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(instruction: String) =
|
||||
this.implementation!!.addInstruction(instruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Add a smali instruction to the method.
|
||||
* @param index The index to insert the instruction at.
|
||||
* @param instruction The smali instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(index: Int, instruction: String) =
|
||||
this.implementation!!.addInstruction(index, instruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Replace a smali instruction within the method.
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param instruction The smali instruction to place.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(index: Int, instruction: String) =
|
||||
this.implementation!!.replaceInstruction(index, instruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Remove a smali instruction within the method.
|
||||
* @param index The index to delete the instruction at.
|
||||
*/
|
||||
fun MutableMethod.removeInstruction(index: Int) = this.implementation!!.removeInstruction(index)
|
||||
|
||||
/**
|
||||
* Create a label for the instruction at given index in the method's implementation.
|
||||
* Create a label for the instruction at given index.
|
||||
*
|
||||
* @param index The index to create the label for the instruction at.
|
||||
* @return The label.
|
||||
*/
|
||||
fun MutableMethod.label(index: Int) = this.implementation!!.newLabelForIndex(index)
|
||||
fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index)
|
||||
|
||||
/**
|
||||
* Get the instruction at given index in the method's implementation.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
* Perform a bitwise OR operation between two [AccessFlags].
|
||||
*
|
||||
* @param other The other [AccessFlags] to perform the operation with.
|
||||
*/
|
||||
fun MutableMethod.instruction(index: Int): BuilderInstruction = this.implementation!!.instructions[index]
|
||||
infix fun AccessFlags.or(other: AccessFlags) = value or other.value
|
||||
|
||||
/**
|
||||
* Add smali instructions to the method.
|
||||
* @param index The index to insert the instructions at.
|
||||
* @param smali The smali instructions to add.
|
||||
* @param externalLabels A list of [ExternalLabel] representing a list of labels for instructions which are not in the method to compile.
|
||||
* Perform a bitwise OR operation between an [AccessFlags] and an [Int].
|
||||
*
|
||||
* @param other The [Int] to perform the operation with.
|
||||
*/
|
||||
|
||||
fun MutableMethod.addInstructions(index: Int, smali: String, externalLabels: List<ExternalLabel> = emptyList()) {
|
||||
// Create reference dummy instructions for the instructions.
|
||||
val nopedSmali = StringBuilder(smali).also { builder ->
|
||||
externalLabels.forEach { (name, _) ->
|
||||
builder.append("\n:$name\nnop")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
// Compile the instructions with the dummy labels
|
||||
val compiledInstructions = nopedSmali.toInstructions(this)
|
||||
|
||||
// Add the compiled list of instructions to the method.
|
||||
val methodImplementation = this.implementation!!
|
||||
methodImplementation.addInstructions(index, compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size))
|
||||
|
||||
val methodInstructions = methodImplementation.instructions
|
||||
methodInstructions.subList(index, index + compiledInstructions.size - externalLabels.size)
|
||||
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
|
||||
// If the compiled instruction is not an offset instruction, skip it.
|
||||
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
|
||||
|
||||
/**
|
||||
* Creates a new label for the instruction and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex].
|
||||
*/
|
||||
fun Instruction.makeNewLabel() {
|
||||
// Create the final label.
|
||||
val label = methodImplementation.newLabelForIndex(methodInstructions.indexOf(this))
|
||||
// Create the final instruction with the new label.
|
||||
val newInstruction = replaceOffset(
|
||||
compiledInstruction, label
|
||||
)
|
||||
// Replace the instruction pointing to the dummy label with the new instruction pointing to the real instruction.
|
||||
methodImplementation.replaceInstruction(index + compiledInstructionIndex, newInstruction)
|
||||
}
|
||||
|
||||
// If the compiled instruction targets its own instruction,
|
||||
// which means it points to some of its own, simply an offset has to be applied.
|
||||
val labelIndex = compiledInstruction.target.location.index
|
||||
if (labelIndex < compiledInstructions.size - externalLabels.size) {
|
||||
// Get the targets index (insertion index + the index of the dummy instruction).
|
||||
methodInstructions[index + labelIndex].makeNewLabel()
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
// Since the compiled instruction points to a dummy instruction,
|
||||
// we can find the real instruction which it was created for by calculation.
|
||||
|
||||
// Get the index of the instruction in the externalLabels list which the dummy instruction was created for.
|
||||
// this line works because we created the dummy instructions in the same order as the externalLabels list.
|
||||
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
|
||||
instruction.makeNewLabel()
|
||||
}
|
||||
}
|
||||
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||
|
||||
/**
|
||||
* Add smali instructions to the end of the method.
|
||||
* @param instructions The smali instructions to add.
|
||||
* Perform a bitwise OR operation between an [Int] and an [AccessFlags].
|
||||
*
|
||||
* @param other The [AccessFlags] to perform the operation with.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(instructions: String, labels: List<ExternalLabel> = emptyList()) =
|
||||
this.addInstructions(this.implementation!!.instructions.size, instructions, labels)
|
||||
|
||||
/**
|
||||
* Replace smali instructions within the method.
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The smali instructions to place.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(index: Int, instructions: String) =
|
||||
this.implementation!!.replaceInstructions(index, instructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Remove smali instructions from the method.
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(index: Int, count: Int) = this.implementation!!.removeInstructions(index, count)
|
||||
|
||||
private fun replaceOffset(
|
||||
i: BuilderOffsetInstruction, label: Label
|
||||
): BuilderOffsetInstruction {
|
||||
return when (i) {
|
||||
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
|
||||
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
|
||||
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
|
||||
is BuilderInstruction22t -> BuilderInstruction22t(i.opcode, i.registerA, i.registerB, label)
|
||||
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
|
||||
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
|
||||
else -> throw IllegalStateException("A non-offset instruction was given, this should never happen!")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the method.
|
||||
* @param registerCount This parameter allows you to change the register count of the method.
|
||||
* This may be a positive or negative number.
|
||||
* @return The **mutable** cloned method. Call [clone] to get an **immutable** copy.
|
||||
*/
|
||||
internal fun Method.cloneMutable(registerCount: Int = 0) = clone(registerCount).toMutable()
|
||||
|
||||
// FIXME: also check the order of parameters as different order equals different method overload
|
||||
internal fun parametersEqual(
|
||||
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
|
||||
): Boolean {
|
||||
return parameters1.count() == parameters2.count() && parameters1.all { parameter ->
|
||||
parameters2.any {
|
||||
it.startsWith(
|
||||
parameter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val nullOutputStream = object : OutputStream() {
|
||||
override fun write(b: Int) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be used to parse a list of parameters represented by their first letter,
|
||||
* or in the case of arrays prefixed with an unspecified amount of '[' character.
|
||||
*/
|
||||
internal fun String.parseParameters(): List<String> {
|
||||
val parameters = mutableListOf<String>()
|
||||
var parameter = ""
|
||||
for (char in this.toCharArray()) {
|
||||
parameter += char
|
||||
if (char == '[') continue
|
||||
|
||||
parameters.add(parameter)
|
||||
parameter = ""
|
||||
}
|
||||
return parameters
|
||||
}
|
||||
infix fun AccessFlags.or(other: Int) = value or other
|
||||
@@ -0,0 +1,330 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patcher.util.smali.toInstruction
|
||||
import app.revanced.patcher.util.smali.toInstructions
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
|
||||
import com.android.tools.smali.dexlib2.builder.Label
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.*
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
|
||||
object InstructionExtensions {
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethodImplementation.addInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>
|
||||
) = instructions.asReversed().forEach { addInstruction(index, it) }
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
* The instructions will be added at the end of the method.
|
||||
*
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) =
|
||||
instructions.forEach { this.addInstruction(it) }
|
||||
|
||||
/**
|
||||
* Remove instructions from a method at the given index.
|
||||
*
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) = repeat(count) {
|
||||
removeInstruction(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the first instructions from a method.
|
||||
*
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethodImplementation.removeInstructions(count: Int) = removeInstructions(0, count)
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index with the given instructions.
|
||||
* The amount of instructions to replace is the amount of instructions in the given list.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
||||
// Remove the instructions at the given index.
|
||||
removeInstructions(index, instructions.size)
|
||||
|
||||
// Add the instructions at the given index.
|
||||
addInstructions(index, instructions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an instruction to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instruction at.
|
||||
* @param instruction The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(index: Int, instruction: BuilderInstruction) =
|
||||
implementation!!.addInstruction(index, instruction)
|
||||
|
||||
/**
|
||||
* Add an instruction to a method.
|
||||
*
|
||||
* @param instruction The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(instruction: BuilderInstruction) =
|
||||
implementation!!.addInstruction(instruction)
|
||||
|
||||
/**
|
||||
* Add an instruction to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instruction at.
|
||||
* @param smaliInstructions The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(index: Int, smaliInstructions: String) =
|
||||
implementation!!.addInstruction(index, smaliInstructions.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Add an instruction to a method.
|
||||
*
|
||||
* @param smaliInstructions The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(smaliInstructions: String) =
|
||||
implementation!!.addInstruction(smaliInstructions.toInstruction(this))
|
||||
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(index: Int, instructions: List<BuilderInstruction>) =
|
||||
implementation!!.addInstructions(index, instructions)
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) =
|
||||
implementation!!.addInstructions(instructions)
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param smaliInstructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(index: Int, smaliInstructions: String) =
|
||||
implementation!!.addInstructions(index, smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param smaliInstructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(smaliInstructions: String) =
|
||||
implementation!!.addInstructions(smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param smaliInstructions The instructions to add.
|
||||
* @param externalLabels A list of [ExternalLabel] for instructions outside of [smaliInstructions].
|
||||
*/
|
||||
// Special function for adding instructions with external labels.
|
||||
fun MutableMethod.addInstructionsWithLabels(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
vararg externalLabels: ExternalLabel
|
||||
) {
|
||||
// Create reference dummy instructions for the instructions.
|
||||
val nopSmali = StringBuilder(smaliInstructions).also { builder ->
|
||||
externalLabels.forEach { (name, _) ->
|
||||
builder.append("\n:$name\nnop")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
// Compile the instructions with the dummy labels
|
||||
val compiledInstructions = nopSmali.toInstructions(this)
|
||||
|
||||
// Add the compiled list of instructions to the method.
|
||||
addInstructions(
|
||||
index,
|
||||
compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size)
|
||||
)
|
||||
|
||||
implementation!!.apply {
|
||||
this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size)
|
||||
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
|
||||
// If the compiled instruction is not an offset instruction, skip it.
|
||||
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
|
||||
|
||||
/**
|
||||
* Creates a new label for the instruction
|
||||
* and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex].
|
||||
*/
|
||||
fun Instruction.makeNewLabel() {
|
||||
fun replaceOffset(
|
||||
i: BuilderOffsetInstruction, label: Label
|
||||
): BuilderOffsetInstruction {
|
||||
return when (i) {
|
||||
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
|
||||
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
|
||||
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
|
||||
is BuilderInstruction22t -> BuilderInstruction22t(
|
||||
i.opcode,
|
||||
i.registerA,
|
||||
i.registerB,
|
||||
label
|
||||
)
|
||||
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
|
||||
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
|
||||
else -> throw IllegalStateException(
|
||||
"A non-offset instruction was given, this should never happen!"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the final label.
|
||||
val label = newLabelForIndex(this@apply.instructions.indexOf(this))
|
||||
|
||||
// Create the final instruction with the new label.
|
||||
val newInstruction = replaceOffset(
|
||||
compiledInstruction, label
|
||||
)
|
||||
|
||||
// Replace the instruction pointing to the dummy label
|
||||
// with the new instruction pointing to the real instruction.
|
||||
replaceInstruction(index + compiledInstructionIndex, newInstruction)
|
||||
}
|
||||
|
||||
// If the compiled instruction targets its own instruction,
|
||||
// which means it points to some of its own, simply an offset has to be applied.
|
||||
val labelIndex = compiledInstruction.target.location.index
|
||||
if (labelIndex < compiledInstructions.size - externalLabels.size) {
|
||||
// Get the targets index (insertion index + the index of the dummy instruction).
|
||||
this.instructions[index + labelIndex].makeNewLabel()
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
// Since the compiled instruction points to a dummy instruction,
|
||||
// we can find the real instruction which it was created for by calculation.
|
||||
|
||||
// Get the index of the instruction in the externalLabels list
|
||||
// which the dummy instruction was created for.
|
||||
// This works because we created the dummy instructions in the same order as the externalLabels list.
|
||||
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
|
||||
instruction.makeNewLabel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an instruction at the given index.
|
||||
*
|
||||
* @param index The index to remove the instruction at.
|
||||
*/
|
||||
fun MutableMethod.removeInstruction(index: Int) =
|
||||
implementation!!.removeInstruction(index)
|
||||
|
||||
/**
|
||||
* Remove instructions at the given index.
|
||||
*
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(index: Int, count: Int) =
|
||||
implementation!!.removeInstructions(index, count)
|
||||
|
||||
/**
|
||||
* Remove instructions at the given index.
|
||||
*
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(count: Int) =
|
||||
implementation!!.removeInstructions(count)
|
||||
|
||||
/**
|
||||
* Replace an instruction at the given index.
|
||||
*
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param instruction The instruction to replace the instruction with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(index: Int, instruction: BuilderInstruction) =
|
||||
implementation!!.replaceInstruction(index, instruction)
|
||||
|
||||
/**
|
||||
* Replace an instruction at the given index.
|
||||
*
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param smaliInstruction The smali instruction to replace the instruction with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(index: Int, smaliInstruction: String) =
|
||||
implementation!!.replaceInstruction(index, smaliInstruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) =
|
||||
implementation!!.replaceInstructions(index, instructions)
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param smaliInstructions The smali instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(index: Int, smaliInstructions: String) =
|
||||
implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MutableMethodImplementation.getInstruction(index: Int): BuilderInstruction = instructions[index]
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> MutableMethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MutableMethod.getInstruction(index: Int): BuilderInstruction = implementation!!.getInstruction(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun <T> MutableMethod.getInstruction(index: Int): T = implementation!!.getInstruction<T>(index)
|
||||
|
||||
/**
|
||||
* Get the instructions of a method.
|
||||
* @return The instructions.
|
||||
*/
|
||||
fun MutableMethod.getInstructions(): MutableList<BuilderInstruction> = implementation!!.instructions
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
|
||||
object MethodFingerprintExtensions {
|
||||
|
||||
/**
|
||||
* The name of a [MethodFingerprint].
|
||||
*/
|
||||
val MethodFingerprint.name: String
|
||||
get() = this.javaClass.simpleName
|
||||
|
||||
/**
|
||||
* The [FuzzyPatternScanMethod] annotation of a [MethodFingerprint].
|
||||
*/
|
||||
val MethodFingerprint.fuzzyPatternScanMethod
|
||||
get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.annotation.Compatibility
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||
import app.revanced.patcher.patch.OptionsContainer
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchClass
|
||||
import app.revanced.patcher.patch.PatchOptions
|
||||
import app.revanced.patcher.patch.annotations.DependsOn
|
||||
import app.revanced.patcher.patch.annotations.RequiresIntegrations
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.companionObjectInstance
|
||||
|
||||
object PatchExtensions {
|
||||
/**
|
||||
* The name of a [Patch].
|
||||
*/
|
||||
val PatchClass.patchName: String
|
||||
get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName
|
||||
|
||||
/**
|
||||
* Weather or not a [Patch] should be included.
|
||||
*/
|
||||
val PatchClass.include
|
||||
get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include
|
||||
|
||||
/**
|
||||
* The description of a [Patch].
|
||||
*/
|
||||
val PatchClass.description
|
||||
get() = findAnnotationRecursively(Description::class)?.description
|
||||
|
||||
/**
|
||||
* The dependencies of a [Patch].
|
||||
*/
|
||||
val PatchClass.dependencies
|
||||
get() = findAnnotationRecursively(DependsOn::class)?.dependencies
|
||||
|
||||
/**
|
||||
* The packages a [Patch] is compatible with.
|
||||
*/
|
||||
val PatchClass.compatiblePackages
|
||||
get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages
|
||||
|
||||
/**
|
||||
* Weather or not a [Patch] requires integrations.
|
||||
*/
|
||||
internal val PatchClass.requiresIntegrations
|
||||
get() = findAnnotationRecursively(RequiresIntegrations::class) != null
|
||||
|
||||
/**
|
||||
* The options of a [Patch].
|
||||
*/
|
||||
val PatchClass.options: PatchOptions?
|
||||
get() = kotlin.companionObject?.let { cl ->
|
||||
if (cl.visibility != KVisibility.PUBLIC) return null
|
||||
kotlin.companionObjectInstance?.let {
|
||||
(it as? OptionsContainer)?.options
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class FuzzyPatternScanMethod(
|
||||
val threshold: Int = 1
|
||||
)
|
||||
@@ -2,43 +2,243 @@ package app.revanced.patcher.fingerprint.method.impl
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
||||
import app.revanced.patcher.extensions.parametersEqual
|
||||
import app.revanced.patcher.fingerprint.Fingerprint
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import org.jf.dexlib2.iface.reference.StringReference
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.*
|
||||
|
||||
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
||||
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
||||
private typealias MethodClassPair = Pair<Method, ClassDef>
|
||||
|
||||
/**
|
||||
* Represents the [MethodFingerprint] for a method.
|
||||
* @param returnType The return type of the method.
|
||||
* @param access The access flags of the method.
|
||||
* @param parameters The parameters of the method.
|
||||
* @param opcodes The list of opcodes of the method.
|
||||
* @param strings A list of strings which a method contains.
|
||||
* A fingerprint to resolve methods.
|
||||
*
|
||||
* @param returnType The method's return type compared using [String.startsWith].
|
||||
* @param accessFlags The method's exact access flags using values of [AccessFlags].
|
||||
* @param parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
|
||||
* @param opcodes An opcode pattern of the method's instructions. Wildcard or unknown opcodes can be specified by `null`.
|
||||
* @param strings A list of the method's strings compared each using [String.contains].
|
||||
* @param customFingerprint A custom condition for this fingerprint.
|
||||
* A `null` opcode is equals to an unknown opcode.
|
||||
*/
|
||||
abstract class MethodFingerprint(
|
||||
internal val returnType: String? = null,
|
||||
internal val access: Int? = null,
|
||||
internal val accessFlags: Int? = null,
|
||||
internal val parameters: Iterable<String>? = null,
|
||||
internal val opcodes: Iterable<Opcode?>? = null,
|
||||
internal val strings: Iterable<String>? = null,
|
||||
internal val customFingerprint: ((methodDef: Method) -> Boolean)? = null
|
||||
internal val customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null
|
||||
) : Fingerprint {
|
||||
/**
|
||||
* The result of the [MethodFingerprint] the [Method].
|
||||
* The result of the [MethodFingerprint].
|
||||
*/
|
||||
var result: MethodFingerprintResult? = null
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* A list of methods and the class they were found in.
|
||||
*/
|
||||
private val methods = mutableListOf<MethodClassPair>()
|
||||
|
||||
/**
|
||||
* Lookup map for methods keyed to the methods access flags, return type and parameter.
|
||||
*/
|
||||
private val methodSignatureLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
|
||||
|
||||
/**
|
||||
* Lookup map for methods keyed to the strings contained in the method.
|
||||
*/
|
||||
private val methodStringsLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
|
||||
|
||||
/**
|
||||
* Appends a string based on the parameter reference types of this method.
|
||||
*/
|
||||
private fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
|
||||
// Maximum parameters to use in the signature key.
|
||||
// Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
|
||||
// To keep the signature map from becoming needlessly bloated,
|
||||
// group together in the same map entry all methods with the same access/return and 5 or more parameters.
|
||||
// The value of 5 was chosen based on local performance testing and is not set in stone.
|
||||
val maxSignatureParameters = 5
|
||||
// Must append a unique value before the parameters to distinguish this key includes the parameters.
|
||||
// If this is not appended, then methods with no parameters
|
||||
// will collide with different keys that specify access/return but omit the parameters.
|
||||
append("p:")
|
||||
parameters.forEachIndexed { index, parameter ->
|
||||
if (index >= maxSignatureParameters) return
|
||||
append(parameter.first())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes lookup maps for [MethodFingerprint] resolution
|
||||
* using attributes of methods such as the method signature or strings.
|
||||
*
|
||||
* @param context The [BytecodeContext] containing the classes to initialize the lookup maps with.
|
||||
*/
|
||||
internal fun initializeFingerprintResolutionLookupMaps(context: BytecodeContext) {
|
||||
fun MutableMap<String, MutableList<MethodClassPair>>.add(
|
||||
key: String,
|
||||
methodClassPair: MethodClassPair
|
||||
) {
|
||||
var methodClassPairs = this[key]
|
||||
|
||||
methodClassPairs ?: run {
|
||||
methodClassPairs = LinkedList<MethodClassPair>().also { this[key] = it }
|
||||
}
|
||||
|
||||
methodClassPairs!!.add(methodClassPair)
|
||||
}
|
||||
|
||||
if (methods.isNotEmpty()) clearFingerprintResolutionLookupMaps()
|
||||
|
||||
context.classes.forEach { classDef ->
|
||||
classDef.methods.forEach { method ->
|
||||
val methodClassPair = method to classDef
|
||||
|
||||
// For fingerprints with no access or return type specified.
|
||||
methods += methodClassPair
|
||||
|
||||
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
|
||||
|
||||
// Add <access><returnType> as the key.
|
||||
methodSignatureLookupMap.add(accessFlagsReturnKey, methodClassPair)
|
||||
|
||||
// Add <access><returnType>[parameters] as the key.
|
||||
methodSignatureLookupMap.add(
|
||||
buildString {
|
||||
append(accessFlagsReturnKey)
|
||||
appendParameters(method.parameterTypes)
|
||||
},
|
||||
methodClassPair
|
||||
)
|
||||
|
||||
// Add strings contained in the method as the key.
|
||||
method.implementation?.instructions?.forEach instructions@{ instruction ->
|
||||
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO)
|
||||
return@instructions
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
|
||||
methodStringsLookupMap.add(string, methodClassPair)
|
||||
}
|
||||
|
||||
// In the future, the class type could be added to the lookup map.
|
||||
// This would require MethodFingerprint to be changed to include the class type.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the internal lookup maps created in [initializeFingerprintResolutionLookupMaps]
|
||||
*/
|
||||
internal fun clearFingerprintResolutionLookupMaps() {
|
||||
methods.clear()
|
||||
methodSignatureLookupMap.clear()
|
||||
methodStringsLookupMap.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a list of [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
|
||||
*
|
||||
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||
* amount of time because they are resolved in sequence.
|
||||
*
|
||||
* For apps with many fingerprints, resolving performance can be improved by:
|
||||
* - Slowest: Specify [opcodes] and nothing else.
|
||||
* - Fast: Specify [accessFlags], [returnType].
|
||||
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||
*/
|
||||
internal fun Iterable<MethodFingerprint>.resolveUsingLookupMap(context: BytecodeContext) {
|
||||
if (methods.isEmpty()) throw PatchException("lookup map not initialized")
|
||||
|
||||
for (fingerprint in this) {
|
||||
fingerprint.resolveUsingLookupMap(context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
|
||||
*
|
||||
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||
* amount of time because they are resolved in sequence.
|
||||
*
|
||||
* For apps with many fingerprints, resolving performance can be improved by:
|
||||
* - Slowest: Specify [opcodes] and nothing else.
|
||||
* - Fast: Specify [accessFlags], [returnType].
|
||||
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||
*/
|
||||
internal fun MethodFingerprint.resolveUsingLookupMap(context: BytecodeContext): Boolean {
|
||||
/**
|
||||
* Lookup [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||
*
|
||||
* @return A list of [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||
*/
|
||||
fun MethodFingerprint.methodStringsLookup(): List<MethodClassPair>? {
|
||||
strings?.forEach {
|
||||
val methods = methodStringsLookupMap[it]
|
||||
if (methods != null) return methods
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||
*
|
||||
* @return A list of [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||
*/
|
||||
fun MethodFingerprint.methodSignatureLookup(): List<MethodClassPair> {
|
||||
if (accessFlags == null) return methods
|
||||
|
||||
var returnTypeValue = returnType
|
||||
if (returnTypeValue == null) {
|
||||
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
||||
// Constructors always have void return type
|
||||
returnTypeValue = "V"
|
||||
} else {
|
||||
return methods
|
||||
}
|
||||
}
|
||||
|
||||
val key = buildString {
|
||||
append(accessFlags)
|
||||
append(returnTypeValue.first())
|
||||
if (parameters != null) appendParameters(parameters)
|
||||
}
|
||||
return methodSignatureLookupMap[key] ?: return emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] using a list of [MethodClassPair].
|
||||
*
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun MethodFingerprint.resolveUsingMethodClassPair(classMethods: Iterable<MethodClassPair>): Boolean {
|
||||
classMethods.forEach { classAndMethod ->
|
||||
if (resolve(context, classAndMethod.first, classAndMethod.second)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
val methodsWithSameStrings = methodStringsLookup()
|
||||
if (methodsWithSameStrings != null) if (resolveUsingMethodClassPair(methodsWithSameStrings)) return true
|
||||
|
||||
// No strings declared or none matched (partial matches are allowed).
|
||||
// Use signature matching.
|
||||
return resolveUsingMethodClassPair(methodSignatureLookup())
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||
*
|
||||
@@ -47,10 +247,10 @@ abstract class MethodFingerprint(
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) {
|
||||
for (fingerprint in this) // For each fingerprint
|
||||
classes@ for (classDef in classes) // search through all classes for the fingerprint
|
||||
for (fingerprint in this) // For each fingerprint...
|
||||
classes@ for (classDef in classes) // ...search through all classes for the MethodFingerprint
|
||||
if (fingerprint.resolve(context, classDef))
|
||||
break@classes // if the resolution succeeded, continue with the next fingerprint
|
||||
break@classes // ...if the resolution succeeded, continue with the next MethodFingerprint.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,10 +283,21 @@ abstract class MethodFingerprint(
|
||||
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType))
|
||||
return false
|
||||
|
||||
if (methodFingerprint.access != null && methodFingerprint.access != method.accessFlags)
|
||||
if (methodFingerprint.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags)
|
||||
return false
|
||||
|
||||
|
||||
fun parametersEqual(
|
||||
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
|
||||
): Boolean {
|
||||
if (parameters1.count() != parameters2.count()) return false
|
||||
val iterator1 = parameters1.iterator()
|
||||
parameters2.forEach {
|
||||
if (!it.startsWith(iterator1.next())) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (methodFingerprint.parameters != null && !parametersEqual(
|
||||
methodFingerprint.parameters, // TODO: parseParameters()
|
||||
method.parameterTypes
|
||||
@@ -94,7 +305,7 @@ abstract class MethodFingerprint(
|
||||
) return false
|
||||
|
||||
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method))
|
||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method, forClass))
|
||||
return false
|
||||
|
||||
val stringsScanResult: StringsScanResult? =
|
||||
@@ -106,7 +317,10 @@ abstract class MethodFingerprint(
|
||||
val stringsList = methodFingerprint.strings.toMutableList()
|
||||
|
||||
implementation.instructions.forEachIndexed { instructionIndex, instruction ->
|
||||
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEachIndexed
|
||||
if (
|
||||
instruction.opcode != Opcode.CONST_STRING &&
|
||||
instruction.opcode != Opcode.CONST_STRING_JUMBO
|
||||
) return@forEachIndexed
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
val index = stringsList.indexOfFirst(string::contains)
|
||||
@@ -129,6 +343,52 @@ abstract class MethodFingerprint(
|
||||
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||
method.implementation?.instructions ?: return false
|
||||
|
||||
fun Method.patternScan(
|
||||
fingerprint: MethodFingerprint
|
||||
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
||||
val instructions = this.implementation!!.instructions
|
||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
|
||||
|
||||
val pattern = fingerprint.opcodes!!
|
||||
val instructionLength = instructions.count()
|
||||
val patternLength = pattern.count()
|
||||
|
||||
for (index in 0 until instructionLength) {
|
||||
var patternIndex = 0
|
||||
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||
|
||||
while (index + patternIndex < instructionLength) {
|
||||
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||
// reaching maximum threshold (0) means,
|
||||
// the pattern does not match to the current instructions
|
||||
if (threshold-- == 0) break
|
||||
}
|
||||
|
||||
if (patternIndex < patternLength - 1) {
|
||||
// if the entire pattern has not been scanned yet
|
||||
// continue the scan
|
||||
patternIndex++
|
||||
continue
|
||||
}
|
||||
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
||||
val result =
|
||||
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
|
||||
index,
|
||||
index + patternIndex
|
||||
)
|
||||
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
||||
result.warnings = result.createWarnings(pattern, instructions)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
method.patternScan(methodFingerprint) ?: return false
|
||||
} else null
|
||||
|
||||
@@ -145,52 +405,6 @@ abstract class MethodFingerprint(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Method.patternScan(
|
||||
fingerprint: MethodFingerprint
|
||||
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
||||
val instructions = this.implementation!!.instructions
|
||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold
|
||||
|
||||
val pattern = fingerprint.opcodes!!
|
||||
val instructionLength = instructions.count()
|
||||
val patternLength = pattern.count()
|
||||
|
||||
for (index in 0 until instructionLength) {
|
||||
var patternIndex = 0
|
||||
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||
|
||||
while (index + patternIndex < instructionLength) {
|
||||
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||
val patternOpcode = pattern.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||
// reaching maximum threshold (0) means,
|
||||
// the pattern does not match to the current instructions
|
||||
if (threshold-- == 0) break
|
||||
}
|
||||
|
||||
if (patternIndex < patternLength - 1) {
|
||||
// if the entire pattern has not been scanned yet
|
||||
// continue the scan
|
||||
patternIndex++
|
||||
continue
|
||||
}
|
||||
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
||||
val result =
|
||||
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
|
||||
index,
|
||||
index + patternIndex
|
||||
)
|
||||
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
||||
result.warnings = result.createWarnings(pattern, instructions)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.createWarnings(
|
||||
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
||||
) = buildList {
|
||||
@@ -213,9 +427,6 @@ abstract class MethodFingerprint(
|
||||
}
|
||||
}
|
||||
|
||||
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
||||
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
||||
|
||||
/**
|
||||
* Represents the result of a [MethodFingerprintResult].
|
||||
*
|
||||
@@ -230,6 +441,26 @@ data class MethodFingerprintResult(
|
||||
val scanResult: MethodFingerprintScanResult,
|
||||
internal val context: BytecodeContext
|
||||
) {
|
||||
/**
|
||||
* Returns a mutable clone of [classDef]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [classDef] where possible.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val mutableClass by lazy { context.proxy(classDef).mutableClass }
|
||||
|
||||
/**
|
||||
* Returns a mutable clone of [method]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [method] where possible.
|
||||
*/
|
||||
val mutableMethod by lazy {
|
||||
mutableClass.methods.first {
|
||||
MethodUtil.methodSignaturesMatch(it, this.method)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of scanning on the [MethodFingerprint].
|
||||
@@ -244,7 +475,7 @@ data class MethodFingerprintResult(
|
||||
* The result of scanning strings on the [MethodFingerprint].
|
||||
* @param matches The list of strings that were matched.
|
||||
*/
|
||||
data class StringsScanResult(val matches: List<StringMatch>){
|
||||
data class StringsScanResult(val matches: List<StringMatch>) {
|
||||
/**
|
||||
* Represents a match for a string at an index.
|
||||
* @param string The string that was matched.
|
||||
@@ -279,25 +510,4 @@ data class MethodFingerprintResult(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mutable clone of [classDef]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [classDef] where possible.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val mutableClass by lazy { context.proxy(classDef).mutableClass }
|
||||
|
||||
/**
|
||||
* Returns a mutable clone of [method]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [method] where possible.
|
||||
*/
|
||||
val mutableMethod by lazy {
|
||||
mutableClass.methods.first {
|
||||
MethodUtil.methodSignaturesMatch(it, this.method)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.revanced.patcher.logging
|
||||
|
||||
@Deprecated("This will be removed in a future release")
|
||||
interface Logger {
|
||||
fun error(msg: String) {}
|
||||
fun warn(msg: String) {}
|
||||
|
||||
@@ -2,4 +2,5 @@ package app.revanced.patcher.logging.impl
|
||||
|
||||
import app.revanced.patcher.logging.Logger
|
||||
|
||||
@Deprecated("This will be removed in a future release")
|
||||
object NopLogger : Logger
|
||||
@@ -6,27 +6,22 @@ import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import java.io.Closeable
|
||||
|
||||
typealias PatchClass = Class<out Patch<Context<*>>>
|
||||
|
||||
/**
|
||||
* A ReVanced patch.
|
||||
*
|
||||
* If it implements [Closeable], it will be closed after all patches have been executed.
|
||||
* Closing will be done in reverse execution order.
|
||||
*/
|
||||
sealed interface Patch<out T : Context> : Closeable {
|
||||
sealed interface Patch<out T : Context<*>> {
|
||||
/**
|
||||
* The main function of the [Patch] which the patcher will call.
|
||||
*
|
||||
* @param context The [Context] the patch will work on.
|
||||
* @return The result of executing the patch.
|
||||
*/
|
||||
fun execute(context: @UnsafeVariance T): PatchResult
|
||||
|
||||
/**
|
||||
* The closing function for this patch.
|
||||
*
|
||||
* This can be treated like popping the patch from the current patch stack.
|
||||
*/
|
||||
override fun close() {}
|
||||
fun execute(context: @UnsafeVariance T)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
12
src/main/kotlin/app/revanced/patcher/patch/PatchException.kt
Normal file
12
src/main/kotlin/app/revanced/patcher/patch/PatchException.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
/**
|
||||
* An exception thrown when patching.
|
||||
*
|
||||
* @param errorMessage The exception message.
|
||||
* @param cause The corresponding [Throwable].
|
||||
*/
|
||||
class PatchException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
|
||||
constructor(errorMessage: String) : this(errorMessage, null)
|
||||
constructor(cause: Throwable) : this(cause.message, cause)
|
||||
}
|
||||
@@ -79,10 +79,10 @@ class PatchOptions(vararg options: PatchOption<*>) : Iterable<PatchOption<*>> {
|
||||
|
||||
/**
|
||||
* A [Patch] option.
|
||||
* @param key Unique identifier of the option. Example: _`settings.microg.enabled`_
|
||||
* @param key Unique identifier of the option. Example: _`settings`_
|
||||
* @param default The default value of the option.
|
||||
* @param title A human-readable title of the option. Example: _MicroG Settings_
|
||||
* @param description A human-readable description of the option. Example: _Settings integration for MicroG._
|
||||
* @param title A human-readable title of the option. Example: _Patch Settings_
|
||||
* @param description A human-readable description of the option. Example: _Settings for the patches._
|
||||
* @param required Whether the option is required.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@@ -229,20 +229,4 @@ sealed class PatchOption<T>(
|
||||
) : ListOption<Int>(
|
||||
key, default, options, title, description, required, validator
|
||||
)
|
||||
|
||||
/**
|
||||
* A [PatchOption] representing a [Path], backed by a [String].
|
||||
* The validator passes a [String], if you need a [Path] you will have to convert it yourself.
|
||||
* @see PatchOption
|
||||
*/
|
||||
class PathOption(
|
||||
key: String,
|
||||
default: Path?,
|
||||
title: String,
|
||||
description: String,
|
||||
required: Boolean = false,
|
||||
validator: (String?) -> Boolean = { true }
|
||||
) : PatchOption<String>(
|
||||
key, default?.pathString, title, description, required, validator
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,35 +1,10 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
interface PatchResult {
|
||||
fun error(): PatchResultError? {
|
||||
if (this is PatchResultError) {
|
||||
return this
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun success(): PatchResultSuccess? {
|
||||
if (this is PatchResultSuccess) {
|
||||
return this
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun isError(): Boolean {
|
||||
return this is PatchResultError
|
||||
}
|
||||
|
||||
fun isSuccess(): Boolean {
|
||||
return this is PatchResultSuccess
|
||||
}
|
||||
}
|
||||
|
||||
class PatchResultError(
|
||||
errorMessage: String?, cause: Exception?
|
||||
) : Exception(errorMessage, cause), PatchResult {
|
||||
constructor(errorMessage: String) : this(errorMessage, null)
|
||||
constructor(cause: Exception) : this(cause.message, cause)
|
||||
|
||||
}
|
||||
|
||||
class PatchResultSuccess : PatchResult
|
||||
/**
|
||||
* A result of executing a [Patch].
|
||||
*
|
||||
* @param patchName The name of the [Patch].
|
||||
* @param exception The [PatchException] thrown, if any.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class PatchResult internal constructor(val patchName: String, val exception: PatchException? = null)
|
||||
@@ -5,20 +5,23 @@ import app.revanced.patcher.patch.Patch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Annotation to mark a Class as a patch.
|
||||
* Annotation to mark a class as a patch.
|
||||
* @param include If false, the patch should be treated as optional by default.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Patch(val include: Boolean = true)
|
||||
|
||||
/**
|
||||
* Annotation for dependencies of [Patch]es.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class DependsOn(
|
||||
val dependencies: Array<KClass<out Patch<Context>>> = []
|
||||
)
|
||||
val dependencies: Array<KClass<out Patch<Context<*>>>> = []
|
||||
)
|
||||
|
||||
// TODO: Remove this annotation, once integrations are coupled with patches.
|
||||
/**
|
||||
* Annotation to mark [Patch]es which depend on integrations.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class RequiresIntegrations
|
||||
@@ -1,23 +1,23 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.PatcherContext
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.logging.Logger
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.filterAny
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.isPublic
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.toPublic
|
||||
import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.traverseClassHierarchy
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.logging.Logger
|
||||
import kotlin.reflect.KFunction2
|
||||
|
||||
/**
|
||||
@@ -25,27 +25,28 @@ import kotlin.reflect.KFunction2
|
||||
* Note: This will not consider method implementations or if the class is missing a superclass or interfaces.
|
||||
*/
|
||||
internal object ClassMerger {
|
||||
private val logger = Logger.getLogger(ClassMerger::class.java.name)
|
||||
|
||||
/**
|
||||
* Merge a class with [otherClass].
|
||||
*
|
||||
* @param otherClass The class to merge with
|
||||
* @param context The context to traverse the class hierarchy in.
|
||||
* @param logger A logger.
|
||||
* @return The merged class or the original class if no merge was needed.
|
||||
*/
|
||||
fun ClassDef.merge(otherClass: ClassDef, context: PatcherContext, logger: Logger? = null) = this
|
||||
//.fixFieldAccess(otherClass, logger)
|
||||
//.fixMethodAccess(otherClass, logger)
|
||||
.addMissingFields(otherClass, logger)
|
||||
.addMissingMethods(otherClass, logger)
|
||||
.publicize(otherClass, context, logger)
|
||||
fun ClassDef.merge(otherClass: ClassDef, context: BytecodeContext) = this
|
||||
//.fixFieldAccess(otherClass)
|
||||
//.fixMethodAccess(otherClass)
|
||||
.addMissingFields(otherClass)
|
||||
.addMissingMethods(otherClass)
|
||||
.publicize(otherClass, context)
|
||||
|
||||
/**
|
||||
* Add methods which are missing but existing in [fromClass].
|
||||
*
|
||||
* @param fromClass The class to add missing methods from.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.addMissingMethods(fromClass: ClassDef, logger: Logger? = null): ClassDef {
|
||||
private fun ClassDef.addMissingMethods(fromClass: ClassDef): ClassDef {
|
||||
val missingMethods = fromClass.methods.let { fromMethods ->
|
||||
methods.filterNot { method ->
|
||||
fromMethods.any { fromMethod ->
|
||||
@@ -56,7 +57,7 @@ internal object ClassMerger {
|
||||
|
||||
if (missingMethods.isEmpty()) return this
|
||||
|
||||
logger?.trace("Found ${missingMethods.size} missing methods")
|
||||
logger.fine("Found ${missingMethods.size} missing methods")
|
||||
|
||||
return asMutableClass().apply {
|
||||
methods.addAll(missingMethods.map { it.toMutable() })
|
||||
@@ -67,16 +68,15 @@ internal object ClassMerger {
|
||||
* Add fields which are missing but existing in [fromClass].
|
||||
*
|
||||
* @param fromClass The class to add missing fields from.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.addMissingFields(fromClass: ClassDef, logger: Logger? = null): ClassDef {
|
||||
private fun ClassDef.addMissingFields(fromClass: ClassDef): ClassDef {
|
||||
val missingFields = fields.filterNotAny(fromClass.fields) { field, fromField ->
|
||||
fromField.name == field.name
|
||||
}
|
||||
|
||||
if (missingFields.isEmpty()) return this
|
||||
|
||||
logger?.trace("Found ${missingFields.size} missing fields")
|
||||
logger.fine("Found ${missingFields.size} missing fields")
|
||||
|
||||
return asMutableClass().apply {
|
||||
fields.addAll(missingFields.map { it.toMutable() })
|
||||
@@ -87,15 +87,14 @@ internal object ClassMerger {
|
||||
* Make a class and its super class public recursively.
|
||||
* @param reference The class to check the [AccessFlags] of.
|
||||
* @param context The context to traverse the class hierarchy in.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.publicize(reference: ClassDef, context: PatcherContext, logger: Logger? = null) =
|
||||
private fun ClassDef.publicize(reference: ClassDef, context: BytecodeContext) =
|
||||
if (reference.accessFlags.isPublic() && !accessFlags.isPublic())
|
||||
this.asMutableClass().apply {
|
||||
context.bytecodeContext.traverseClassHierarchy(this) {
|
||||
context.traverseClassHierarchy(this) {
|
||||
if (accessFlags.isPublic()) return@traverseClassHierarchy
|
||||
|
||||
logger?.trace("Publicizing ${this.type}")
|
||||
logger.fine("Publicizing ${this.type}")
|
||||
|
||||
accessFlags = accessFlags.toPublic()
|
||||
}
|
||||
@@ -106,9 +105,8 @@ internal object ClassMerger {
|
||||
* Publicize fields if they are public in [reference].
|
||||
*
|
||||
* @param reference The class to check the [AccessFlags] of the fields in.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.fixFieldAccess(reference: ClassDef, logger: Logger? = null): ClassDef {
|
||||
private fun ClassDef.fixFieldAccess(reference: ClassDef): ClassDef {
|
||||
val brokenFields = fields.filterAny(reference.fields) { field, referenceField ->
|
||||
if (field.name != referenceField.name) return@filterAny false
|
||||
|
||||
@@ -117,7 +115,7 @@ internal object ClassMerger {
|
||||
|
||||
if (brokenFields.isEmpty()) return this
|
||||
|
||||
logger?.trace("Found ${brokenFields.size} broken fields")
|
||||
logger.fine("Found ${brokenFields.size} broken fields")
|
||||
|
||||
/**
|
||||
* Make a field public.
|
||||
@@ -135,9 +133,8 @@ internal object ClassMerger {
|
||||
* Publicize methods if they are public in [reference].
|
||||
*
|
||||
* @param reference The class to check the [AccessFlags] of the methods in.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.fixMethodAccess(reference: ClassDef, logger: Logger? = null): ClassDef {
|
||||
private fun ClassDef.fixMethodAccess(reference: ClassDef): ClassDef {
|
||||
val brokenMethods = methods.filterAny(reference.methods) { method, referenceMethod ->
|
||||
if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false
|
||||
|
||||
@@ -146,7 +143,7 @@ internal object ClassMerger {
|
||||
|
||||
if (brokenMethods.isEmpty()) return this
|
||||
|
||||
logger?.trace("Found ${brokenMethods.size} methods")
|
||||
logger.fine("Found ${brokenMethods.size} methods")
|
||||
|
||||
/**
|
||||
* Make a method public.
|
||||
@@ -161,6 +158,19 @@ internal object ClassMerger {
|
||||
}
|
||||
|
||||
private object Utils {
|
||||
/**
|
||||
* traverse the class hierarchy starting from the given root class
|
||||
*
|
||||
* @param targetClass the class to start traversing the class hierarchy from
|
||||
* @param callback function that is called for every class in the hierarchy
|
||||
*/
|
||||
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
|
||||
callback(targetClass)
|
||||
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
|
||||
traverseClassHierarchy(it, callback)
|
||||
}
|
||||
}
|
||||
|
||||
fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable()
|
||||
|
||||
/**
|
||||
@@ -180,7 +190,6 @@ internal object ClassMerger {
|
||||
/**
|
||||
* Filter [this] on [needles] matching the given [predicate].
|
||||
*
|
||||
* @param this The hay to filter for [needles].
|
||||
* @param needles The needles to filter [this] with.
|
||||
* @param predicate The filter.
|
||||
* @return The [this] filtered on [needles] matching the given [predicate].
|
||||
@@ -192,7 +201,6 @@ internal object ClassMerger {
|
||||
/**
|
||||
* Filter [this] on [needles] not matching the given [predicate].
|
||||
*
|
||||
* @param this The hay to filter for [needles].
|
||||
* @param needles The needles to filter [this] with.
|
||||
* @param predicate The filter.
|
||||
* @return The [this] filtered on [needles] not matching the given [predicate].
|
||||
|
||||
87
src/main/kotlin/app/revanced/patcher/util/DomFileEditor.kt
Normal file
87
src/main/kotlin/app/revanced/patcher/util/DomFileEditor.kt
Normal file
@@ -0,0 +1,87 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import org.w3c.dom.Document
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
|
||||
/**
|
||||
* Wrapper for a file that can be edited as a dom document.
|
||||
*
|
||||
* This constructor does not check for locks to the file when writing.
|
||||
* Use the secondary constructor.
|
||||
*
|
||||
* @param inputStream the input stream to read the xml file from.
|
||||
* @param outputStream the output stream to write the xml file to. If null, the file will be read only.
|
||||
*
|
||||
*/
|
||||
class DomFileEditor internal constructor(
|
||||
private val inputStream: InputStream,
|
||||
private val outputStream: Lazy<OutputStream>? = null,
|
||||
) : Closeable {
|
||||
// path to the xml file to unlock the resource when closing the editor
|
||||
private var filePath: String? = null
|
||||
private var closed: Boolean = false
|
||||
|
||||
/**
|
||||
* The document of the xml file
|
||||
*/
|
||||
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
|
||||
.also(Document::normalize)
|
||||
|
||||
|
||||
// lazily open an output stream
|
||||
// this is required because when constructing a DomFileEditor the output stream is created along with the input stream, which is not allowed
|
||||
// the workaround is to lazily create the output stream. This way it would be used after the input stream is closed, which happens in the constructor
|
||||
constructor(file: File) : this(file.inputStream(), lazy { file.outputStream() }) {
|
||||
// increase the lock
|
||||
locks.merge(file.path, 1, Integer::sum)
|
||||
filePath = file.path
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the editor. Write backs and decreases the lock count.
|
||||
*
|
||||
* Will not write back to the file if the file is still locked.
|
||||
*/
|
||||
override fun close() {
|
||||
if (closed) return
|
||||
|
||||
inputStream.close()
|
||||
|
||||
// if the output stream is not null, do not close it
|
||||
outputStream?.let {
|
||||
// prevent writing to same file, if it is being locked
|
||||
// isLocked will be false if the editor was created through a stream
|
||||
val isLocked = filePath?.let { path ->
|
||||
val isLocked = locks[path]!! > 1
|
||||
// decrease the lock count if the editor was opened for a file
|
||||
locks.merge(path, -1, Integer::sum)
|
||||
isLocked
|
||||
} ?: false
|
||||
|
||||
// if unlocked, write back to the file
|
||||
if (!isLocked) {
|
||||
it.value.use { stream ->
|
||||
val result = StreamResult(stream)
|
||||
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)
|
||||
}
|
||||
|
||||
it.value.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
closed = true
|
||||
}
|
||||
|
||||
private companion object {
|
||||
// map of concurrent open files
|
||||
val locks = mutableMapOf<String, Int>()
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
internal class ListBackedSet<E>(private val list: MutableList<E>) : MutableSet<E> {
|
||||
override val size get() = list.size
|
||||
override fun add(element: E) = list.add(element)
|
||||
override fun addAll(elements: Collection<E>) = list.addAll(elements)
|
||||
override fun clear() = list.clear()
|
||||
override fun iterator() = list.listIterator()
|
||||
override fun remove(element: E) = list.remove(element)
|
||||
override fun removeAll(elements: Collection<E>) = list.removeAll(elements)
|
||||
override fun retainAll(elements: Collection<E>) = list.retainAll(elements)
|
||||
override fun contains(element: E) = list.contains(element)
|
||||
override fun containsAll(elements: Collection<E>) = list.containsAll(elements)
|
||||
override fun isEmpty() = list.isEmpty()
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
/**
|
||||
* A class that represents a set of classes and proxies.
|
||||
*
|
||||
* @param classes The classes to be backed by proxies.
|
||||
*/
|
||||
class ProxyBackedClassList(internal val classes: MutableList<ClassDef>) : Set<ClassDef> {
|
||||
internal val proxies = mutableListOf<ClassProxy>()
|
||||
|
||||
/**
|
||||
* Add a [ClassDef].
|
||||
*/
|
||||
fun add(classDef: ClassDef) = classes.add(classDef)
|
||||
|
||||
/**
|
||||
* Add a [ClassProxy].
|
||||
*/
|
||||
fun add(classProxy: ClassProxy) = proxies.add(classProxy)
|
||||
|
||||
/**
|
||||
* Replace all classes with their mutated versions.
|
||||
*/
|
||||
internal fun replaceClasses() =
|
||||
proxies.removeIf { proxy ->
|
||||
// if the proxy is unused, keep it in the list
|
||||
if (!proxy.resolved) return@removeIf false
|
||||
|
||||
// if it has been used, replace the original class with the new class
|
||||
val index = classes.indexOfFirst { it.type == proxy.immutableClass.type }
|
||||
classes[index] = proxy.mutableClass
|
||||
|
||||
// return true to remove it from the proxies list
|
||||
return@removeIf true
|
||||
}
|
||||
|
||||
|
||||
override val size get() = classes.size
|
||||
override fun contains(element: ClassDef) = classes.contains(element)
|
||||
override fun containsAll(elements: Collection<ClassDef>) = classes.containsAll(elements)
|
||||
override fun isEmpty() = classes.isEmpty()
|
||||
override fun iterator() = classes.iterator()
|
||||
}
|
||||
33
src/main/kotlin/app/revanced/patcher/util/ProxyClassList.kt
Normal file
33
src/main/kotlin/app/revanced/patcher/util/ProxyClassList.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
|
||||
/**
|
||||
* A class that represents a set of classes and proxies.
|
||||
*
|
||||
* @param classes The classes to be backed by proxies.
|
||||
*/
|
||||
class ProxyClassList internal constructor(classes: MutableSet<ClassDef>) : MutableSet<ClassDef> by classes {
|
||||
internal val proxies = mutableListOf<ClassProxy>()
|
||||
|
||||
/**
|
||||
* Add a [ClassProxy].
|
||||
*/
|
||||
fun add(classProxy: ClassProxy) = proxies.add(classProxy)
|
||||
|
||||
/**
|
||||
* Replace all classes with their mutated versions.
|
||||
*/
|
||||
internal fun replaceClasses() = proxies.removeIf { proxy ->
|
||||
// If the proxy is unused, return false to keep it in the proxies list.
|
||||
if (!proxy.resolved) return@removeIf false
|
||||
|
||||
// If it has been used, replace the original class with the mutable class.
|
||||
remove(proxy.immutableClass)
|
||||
add(proxy.mutableClass)
|
||||
|
||||
// Return true to remove the proxy from the proxies list.
|
||||
return@removeIf true
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
|
||||
object TypeUtil {
|
||||
/**
|
||||
* traverse the class hierarchy starting from the given root class
|
||||
*
|
||||
* @param targetClass the class to start traversing the class hierarchy from
|
||||
* @param callback function that is called for every class in the hierarchy
|
||||
*/
|
||||
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
|
||||
callback(targetClass)
|
||||
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
|
||||
traverseClassHierarchy(it, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import java.util.*
|
||||
|
||||
internal object VersionReader {
|
||||
@JvmStatic
|
||||
private val props = Properties().apply {
|
||||
load(
|
||||
VersionReader::class.java.getResourceAsStream("/revanced-patcher/version.properties")
|
||||
?: throw IllegalStateException("Could not load version.properties")
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun read(): String {
|
||||
return props.getProperty("version") ?: throw IllegalStateException("Version not found")
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package app.revanced.patcher.util.dex
|
||||
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Wrapper for dex files.
|
||||
* @param name The original name of the dex file.
|
||||
* @param stream The dex file as [InputStream].
|
||||
*/
|
||||
data class DexFile(val name: String, val stream: InputStream)
|
||||
@@ -2,10 +2,10 @@ package app.revanced.patcher.util.method
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
/**
|
||||
* Find a method from another method via instruction offsets.
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package app.revanced.patcher.util.patch
|
||||
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import org.jf.dexlib2.DexFileFactory
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
|
||||
/**
|
||||
* A patch bundle.
|
||||
|
||||
* @param path The path to the patch bundle.
|
||||
*/
|
||||
sealed class PatchBundle(path: String) : File(path) {
|
||||
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
||||
classNames.forEach { className ->
|
||||
val clazz = classLoader.loadClass(className)
|
||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
|
||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
||||
}
|
||||
}.sortedBy { it.patchName }
|
||||
|
||||
/**
|
||||
* A patch bundle of type [Jar].
|
||||
*
|
||||
* @param patchBundlePath The path to the patch bundle.
|
||||
*/
|
||||
class Jar(patchBundlePath: String) : PatchBundle(patchBundlePath) {
|
||||
|
||||
/**
|
||||
* Load patches from the patch bundle.
|
||||
*
|
||||
* Patches will be loaded with a new [URLClassLoader].
|
||||
*/
|
||||
fun loadPatches() = loadPatches(
|
||||
URLClassLoader(
|
||||
arrayOf(this.toURI().toURL()),
|
||||
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
||||
),
|
||||
StringIterator(
|
||||
JarFile(this)
|
||||
.entries()
|
||||
.toList() // TODO: find a cleaner solution than that to filter non class files
|
||||
.filter {
|
||||
it.name.endsWith(".class") && !it.name.contains("$")
|
||||
}
|
||||
.iterator()
|
||||
) {
|
||||
it.realName.replace('/', '.').replace(".class", "")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A patch bundle of type [Dex] format.
|
||||
*
|
||||
* @param patchBundlePath The path to a patch bundle of dex format.
|
||||
* @param dexClassLoader The dex class loader.
|
||||
*/
|
||||
class Dex(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
||||
/**
|
||||
* Load patches from the patch bundle.
|
||||
*
|
||||
* Patches will be loaded to the provided [dexClassLoader].
|
||||
*/
|
||||
fun loadPatches() = loadPatches(dexClassLoader,
|
||||
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package app.revanced.patcher.util.patch
|
||||
|
||||
internal class StringIterator<T, I : Iterator<T>>(
|
||||
private val iterator: I,
|
||||
private val _next: (T) -> String
|
||||
) : Iterator<String> {
|
||||
override fun hasNext() = iterator.hasNext()
|
||||
|
||||
override fun next() = _next(iterator.next())
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
|
||||
/**
|
||||
* A proxy class for a [ClassDef].
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
|
||||
import org.jf.dexlib2.base.BaseAnnotation
|
||||
import org.jf.dexlib2.iface.Annotation
|
||||
import com.android.tools.smali.dexlib2.base.BaseAnnotation
|
||||
import com.android.tools.smali.dexlib2.iface.Annotation
|
||||
|
||||
class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
|
||||
private val visibility = annotation.visibility
|
||||
|
||||
@@ -2,9 +2,9 @@ package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||
import org.jf.dexlib2.base.BaseAnnotationElement
|
||||
import org.jf.dexlib2.iface.AnnotationElement
|
||||
import org.jf.dexlib2.iface.value.EncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.BaseAnnotationElement
|
||||
import com.android.tools.smali.dexlib2.iface.AnnotationElement
|
||||
import com.android.tools.smali.dexlib2.iface.value.EncodedValue
|
||||
|
||||
class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() {
|
||||
private var name = annotationElement.name
|
||||
|
||||
@@ -4,10 +4,10 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import com.google.common.collect.Iterables
|
||||
import org.jf.dexlib2.base.reference.BaseTypeReference
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.util.FieldUtil
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
import com.android.tools.smali.dexlib2.base.reference.BaseTypeReference
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.util.FieldUtil
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
|
||||
// Class
|
||||
|
||||
@@ -3,9 +3,9 @@ package app.revanced.patcher.util.proxy.mutableTypes
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||
import org.jf.dexlib2.HiddenApiRestriction
|
||||
import org.jf.dexlib2.base.reference.BaseFieldReference
|
||||
import org.jf.dexlib2.iface.Field
|
||||
import com.android.tools.smali.dexlib2.HiddenApiRestriction
|
||||
import com.android.tools.smali.dexlib2.base.reference.BaseFieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.Field
|
||||
|
||||
class MutableField(field: Field) : Field, BaseFieldReference() {
|
||||
private var definingClass = field.definingClass
|
||||
|
||||
@@ -2,10 +2,10 @@ package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethodParameter.Companion.toMutable
|
||||
import org.jf.dexlib2.HiddenApiRestriction
|
||||
import org.jf.dexlib2.base.reference.BaseMethodReference
|
||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.HiddenApiRestriction
|
||||
import com.android.tools.smali.dexlib2.base.reference.BaseMethodReference
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
|
||||
class MutableMethod(method: Method) : Method, BaseMethodReference() {
|
||||
private var definingClass = method.definingClass
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import org.jf.dexlib2.base.BaseMethodParameter
|
||||
import org.jf.dexlib2.iface.MethodParameter
|
||||
import com.android.tools.smali.dexlib2.base.BaseMethodParameter
|
||||
import com.android.tools.smali.dexlib2.iface.MethodParameter
|
||||
|
||||
// TODO: finish overriding all members if necessary
|
||||
class MutableMethodParameter(parameter: MethodParameter) : MethodParameter, BaseMethodParameter() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
|
||||
import org.jf.dexlib2.base.value.BaseAnnotationEncodedValue
|
||||
import org.jf.dexlib2.iface.AnnotationElement
|
||||
import org.jf.dexlib2.iface.value.AnnotationEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseAnnotationEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.AnnotationElement
|
||||
import com.android.tools.smali.dexlib2.iface.value.AnnotationEncodedValue
|
||||
|
||||
class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) : BaseAnnotationEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||
import org.jf.dexlib2.base.value.BaseArrayEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ArrayEncodedValue
|
||||
import org.jf.dexlib2.iface.value.EncodedValue
|
||||
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.EncodedValue
|
||||
|
||||
class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) : BaseArrayEncodedValue(), MutableEncodedValue {
|
||||
private val _value by lazy {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseBooleanEncodedValue
|
||||
import org.jf.dexlib2.iface.value.BooleanEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseBooleanEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.BooleanEncodedValue
|
||||
|
||||
class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) : BaseBooleanEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseByteEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ByteEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseByteEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
||||
|
||||
class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) : BaseByteEncodedValue(), MutableEncodedValue {
|
||||
private var value = byteEncodedValue.value
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseCharEncodedValue
|
||||
import org.jf.dexlib2.iface.value.CharEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseCharEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.CharEncodedValue
|
||||
|
||||
class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) : BaseCharEncodedValue(), MutableEncodedValue {
|
||||
private var value = charEncodedValue.value
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseDoubleEncodedValue
|
||||
import org.jf.dexlib2.iface.value.DoubleEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseDoubleEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.DoubleEncodedValue
|
||||
|
||||
class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) : BaseDoubleEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.ValueType
|
||||
import org.jf.dexlib2.iface.value.*
|
||||
import com.android.tools.smali.dexlib2.ValueType
|
||||
import com.android.tools.smali.dexlib2.iface.value.*
|
||||
|
||||
interface MutableEncodedValue : EncodedValue {
|
||||
companion object {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseEnumEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.FieldReference
|
||||
import org.jf.dexlib2.iface.value.EnumEncodedValue
|
||||
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.value.EnumEncodedValue
|
||||
|
||||
class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) : BaseEnumEncodedValue(), MutableEncodedValue {
|
||||
private var value = enumEncodedValue.value
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.ValueType
|
||||
import org.jf.dexlib2.base.value.BaseFieldEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.FieldReference
|
||||
import org.jf.dexlib2.iface.value.FieldEncodedValue
|
||||
import com.android.tools.smali.dexlib2.ValueType
|
||||
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.value.FieldEncodedValue
|
||||
|
||||
class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) : BaseFieldEncodedValue(), MutableEncodedValue {
|
||||
private var value = fieldEncodedValue.value
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseFloatEncodedValue
|
||||
import org.jf.dexlib2.iface.value.FloatEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseFloatEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.FloatEncodedValue
|
||||
|
||||
class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) : BaseFloatEncodedValue(), MutableEncodedValue {
|
||||
private var value = floatEncodedValue.value
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseIntEncodedValue
|
||||
import org.jf.dexlib2.iface.value.IntEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseIntEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.IntEncodedValue
|
||||
|
||||
class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) : BaseIntEncodedValue(), MutableEncodedValue {
|
||||
private var value = intEncodedValue.value
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseLongEncodedValue
|
||||
import org.jf.dexlib2.iface.value.LongEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseLongEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.LongEncodedValue
|
||||
|
||||
class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) : BaseLongEncodedValue(), MutableEncodedValue {
|
||||
private var value = longEncodedValue.value
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseMethodEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
import org.jf.dexlib2.iface.value.MethodEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseMethodEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.value.MethodEncodedValue
|
||||
|
||||
class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) : BaseMethodEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseMethodHandleEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.MethodHandleReference
|
||||
import org.jf.dexlib2.iface.value.MethodHandleEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseMethodHandleEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodHandleReference
|
||||
import com.android.tools.smali.dexlib2.iface.value.MethodHandleEncodedValue
|
||||
|
||||
class MutableMethodHandleEncodedValue(methodHandleEncodedValue: MethodHandleEncodedValue) :
|
||||
BaseMethodHandleEncodedValue(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseMethodTypeEncodedValue
|
||||
import org.jf.dexlib2.iface.reference.MethodProtoReference
|
||||
import org.jf.dexlib2.iface.value.MethodTypeEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseMethodTypeEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodProtoReference
|
||||
import com.android.tools.smali.dexlib2.iface.value.MethodTypeEncodedValue
|
||||
|
||||
class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) : BaseMethodTypeEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseNullEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ByteEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseNullEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
||||
|
||||
class MutableNullEncodedValue : BaseNullEncodedValue(), MutableEncodedValue {
|
||||
companion object {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseShortEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ShortEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseShortEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.ShortEncodedValue
|
||||
|
||||
class MutableShortEncodedValue(shortEncodedValue: ShortEncodedValue) : BaseShortEncodedValue(), MutableEncodedValue {
|
||||
private var value = shortEncodedValue.value
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseStringEncodedValue
|
||||
import org.jf.dexlib2.iface.value.ByteEncodedValue
|
||||
import org.jf.dexlib2.iface.value.StringEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseStringEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
|
||||
|
||||
class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) : BaseStringEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import org.jf.dexlib2.base.value.BaseTypeEncodedValue
|
||||
import org.jf.dexlib2.iface.value.TypeEncodedValue
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseTypeEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.TypeEncodedValue
|
||||
|
||||
class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) : BaseTypeEncodedValue(), MutableEncodedValue {
|
||||
private var value = typeEncodedValue.value
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package app.revanced.patcher.util.smali
|
||||
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
|
||||
/**
|
||||
* A class that represents a label for an instruction.
|
||||
|
||||
@@ -4,14 +4,14 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import org.antlr.runtime.CommonTokenStream
|
||||
import org.antlr.runtime.TokenSource
|
||||
import org.antlr.runtime.tree.CommonTreeNodeStream
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Opcodes
|
||||
import org.jf.dexlib2.builder.BuilderInstruction
|
||||
import org.jf.dexlib2.writer.builder.DexBuilder
|
||||
import org.jf.smali.LexerErrorInterface
|
||||
import org.jf.smali.smaliFlexLexer
|
||||
import org.jf.smali.smaliParser
|
||||
import org.jf.smali.smaliTreeWalker
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcodes
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
|
||||
import com.android.tools.smali.dexlib2.writer.builder.DexBuilder
|
||||
import com.android.tools.smali.smali.LexerErrorInterface
|
||||
import com.android.tools.smali.smali.smaliFlexLexer
|
||||
import com.android.tools.smali.smali.smaliParser
|
||||
import com.android.tools.smali.smali.smaliTreeWalker
|
||||
import java.io.InputStreamReader
|
||||
|
||||
private const val METHOD_TEMPLATE = """
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21s
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
private object InstructionExtensionsTest {
|
||||
private lateinit var testMethod: MutableMethod
|
||||
private lateinit var testMethodImplementation: MutableMethodImplementation
|
||||
|
||||
@BeforeEach
|
||||
fun createTestMethod() = ImmutableMethod(
|
||||
"TestClass;",
|
||||
"testMethod",
|
||||
null,
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(16).also { testMethodImplementation = it }.apply {
|
||||
repeat(10) { i -> this.addInstruction(TestInstruction(i)) }
|
||||
},
|
||||
).let { testMethod = it.toMutable() }
|
||||
|
||||
@Test
|
||||
fun addInstructionsToImplementationIndexed() = applyToImplementation {
|
||||
addInstructions(5, getTestInstructions(5..6)).also {
|
||||
assertRegisterIs(5, 5)
|
||||
assertRegisterIs(6, 6)
|
||||
|
||||
assertRegisterIs(5, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionsToImplementation() = applyToImplementation {
|
||||
addInstructions(getTestInstructions(10..11)).also {
|
||||
assertRegisterIs(10, 10)
|
||||
assertRegisterIs(11, 11)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionsFromImplementationIndexed() = applyToImplementation {
|
||||
removeInstructions(5, 5).also { assertRegisterIs(4, 4) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionsFromImplementation() = applyToImplementation {
|
||||
removeInstructions(0).also { assertRegisterIs(9, 9) }
|
||||
removeInstructions(1).also { assertRegisterIs(1, 0) }
|
||||
removeInstructions(2).also { assertRegisterIs(3, 0) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceInstructionsInImplementationIndexed() = applyToImplementation {
|
||||
replaceInstructions(5, getTestInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
assertRegisterIs(7, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionToMethodIndexed() = applyToMethod {
|
||||
addInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionToMethod() = applyToMethod {
|
||||
addInstruction(TestInstruction(0)).also { assertRegisterIs(0, 10) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionToMethodIndexed() = applyToMethod {
|
||||
addInstruction(5, getTestSmaliInstruction(0)).also { assertRegisterIs(0, 5) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionToMethod() = applyToMethod {
|
||||
addInstruction(getTestSmaliInstruction(0)).also { assertRegisterIs(0, 10) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionsToMethodIndexed() = applyToMethod {
|
||||
addInstructions(5, getTestInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
|
||||
assertRegisterIs(5, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionsToMethod() = applyToMethod {
|
||||
addInstructions(getTestInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 10)
|
||||
assertRegisterIs(1, 11)
|
||||
|
||||
assertRegisterIs(9, 9)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionsToMethodIndexed() = applyToMethod {
|
||||
addInstructionsWithLabels(5, getTestSmaliInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
|
||||
assertRegisterIs(5, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionsToMethod() = applyToMethod {
|
||||
addInstructions(getTestSmaliInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 10)
|
||||
assertRegisterIs(1, 11)
|
||||
|
||||
assertRegisterIs(9, 9)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionsWithExternalLabelToMethodIndexed() = applyToMethod {
|
||||
val label = ExternalLabel("testLabel", getInstruction(5))
|
||||
|
||||
addInstructionsWithLabels(
|
||||
5,
|
||||
getTestSmaliInstructions(0..1).plus("\n").plus("goto :${label.name}"),
|
||||
label
|
||||
).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
assertRegisterIs(5, 8)
|
||||
|
||||
val gotoTarget = getInstruction<BuilderOffsetInstruction>(7)
|
||||
.target.location.instruction as OneRegisterInstruction
|
||||
|
||||
assertEquals(5, gotoTarget.registerA)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionFromMethodIndexed() = applyToMethod {
|
||||
removeInstruction(5).also {
|
||||
assertRegisterIs(4, 4)
|
||||
assertRegisterIs(6, 5)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionsFromMethodIndexed() = applyToMethod {
|
||||
removeInstructions(5, 5).also { assertRegisterIs(4, 4) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionsFromMethod() = applyToMethod {
|
||||
removeInstructions(0).also { assertRegisterIs(9, 9) }
|
||||
removeInstructions(1).also { assertRegisterIs(1, 0) }
|
||||
removeInstructions(2).also { assertRegisterIs(3, 0) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceInstructionInMethodIndexed() = applyToMethod {
|
||||
replaceInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceInstructionsInMethodIndexed() = applyToMethod {
|
||||
replaceInstructions(5, getTestInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
assertRegisterIs(7, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceSmaliInstructionsInMethodIndexed() = applyToMethod {
|
||||
replaceInstructions(5, getTestSmaliInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
assertRegisterIs(7, 7)
|
||||
}
|
||||
}
|
||||
|
||||
// region Helper methods
|
||||
|
||||
private fun applyToImplementation(block: MutableMethodImplementation.() -> Unit) {
|
||||
testMethodImplementation.apply(block)
|
||||
}
|
||||
|
||||
private fun applyToMethod(block: MutableMethod.() -> Unit) {
|
||||
testMethod.apply(block)
|
||||
}
|
||||
|
||||
private fun MutableMethodImplementation.assertRegisterIs(register: Int, atIndex: Int) = assertEquals(
|
||||
register, getInstruction<OneRegisterInstruction>(atIndex).registerA
|
||||
)
|
||||
|
||||
private fun MutableMethod.assertRegisterIs(register: Int, atIndex: Int) =
|
||||
implementation!!.assertRegisterIs(register, atIndex)
|
||||
|
||||
private fun getTestInstructions(range: IntRange) = range.map { TestInstruction(it) }
|
||||
|
||||
private fun getTestSmaliInstruction(register: Int) = "const/16 v$register, 0"
|
||||
|
||||
private fun getTestSmaliInstructions(range: IntRange) = range.joinToString("\n") {
|
||||
getTestSmaliInstruction(it)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private class TestInstruction(register: Int) : BuilderInstruction21s(Opcode.CONST_16, register, 0)
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package app.revanced.patcher.patch
|
||||
import app.revanced.patcher.usage.bytecode.ExampleBytecodePatch
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
internal class PatchOptionsTest {
|
||||
@@ -35,10 +33,6 @@ internal class PatchOptionsTest {
|
||||
println(choice)
|
||||
}
|
||||
}
|
||||
|
||||
is PatchOption.PathOption -> {
|
||||
option.value = Path("test.txt").pathString
|
||||
}
|
||||
}
|
||||
}
|
||||
val option = options.get<String>("key1")
|
||||
|
||||
@@ -9,6 +9,5 @@ import app.revanced.patcher.annotation.Package
|
||||
)]
|
||||
)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class ExampleBytecodeCompatibility
|
||||
|
||||
|
||||
@@ -2,45 +2,44 @@ package app.revanced.patcher.usage.bytecode
|
||||
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.extensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.*
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.OptionsContainer
|
||||
import app.revanced.patcher.patch.PatchOption
|
||||
import app.revanced.patcher.patch.annotations.DependsOn
|
||||
import app.revanced.patcher.patch.annotations.Patch
|
||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||
import app.revanced.patcher.usage.resource.patch.ExampleResourcePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Format
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction11x
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableFieldReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
|
||||
import com.android.tools.smali.dexlib2.immutable.value.ImmutableFieldEncodedValue
|
||||
import com.android.tools.smali.dexlib2.util.Preconditions
|
||||
import com.google.common.collect.ImmutableList
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Format
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction11x
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
|
||||
import org.jf.dexlib2.immutable.ImmutableField
|
||||
import org.jf.dexlib2.immutable.ImmutableMethod
|
||||
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
||||
import org.jf.dexlib2.immutable.reference.ImmutableFieldReference
|
||||
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
||||
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
|
||||
import org.jf.dexlib2.util.Preconditions
|
||||
import kotlin.io.path.Path
|
||||
|
||||
@Patch
|
||||
@Name("example-bytecode-patch")
|
||||
@Description("Example demonstration of a bytecode patch.")
|
||||
@ExampleResourceCompatibility
|
||||
@Version("0.0.1")
|
||||
@DependsOn([ExampleResourcePatch::class])
|
||||
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||
// This function will be executed by the patcher.
|
||||
// You can treat it as a constructor
|
||||
override fun execute(context: BytecodeContext): PatchResult {
|
||||
override fun execute(context: BytecodeContext) {
|
||||
// Get the resolved method by its fingerprint from the resolver cache
|
||||
val result = ExampleFingerprint.result!!
|
||||
|
||||
@@ -119,7 +118,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||
// For this sake of example I reuse the TestClass field dummyField inside the virtual register 0.
|
||||
//
|
||||
// Control flow instructions are not supported as of now.
|
||||
method.addInstructions(
|
||||
method.addInstructionsWithLabels(
|
||||
startIndex + 2,
|
||||
"""
|
||||
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
||||
@@ -127,12 +126,6 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
"""
|
||||
)
|
||||
|
||||
// Finally, tell the patcher that this patch was a success.
|
||||
// You can also return PatchResultError with a message.
|
||||
// If an exception is thrown inside this function,
|
||||
// a PatchResultError will be returned with the error message.
|
||||
return PatchResultSuccess()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,10 +186,5 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||
"key5", null, "title", "description", true
|
||||
)
|
||||
)
|
||||
private var key6 by option(
|
||||
PatchOption.PathOption(
|
||||
"key6", Path("test.txt"), "title", "description", true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package app.revanced.patcher.usage.bytecode
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@FuzzyPatternScanMethod(2)
|
||||
object ExampleFingerprint : MethodFingerprint(
|
||||
|
||||
@@ -9,6 +9,5 @@ import app.revanced.patcher.annotation.Package
|
||||
)]
|
||||
)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class ExampleResourceCompatibility
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user