mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-20 01:43:56 +00:00
Compare commits
111 Commits
v6.4.0-dev
...
arsclib-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6fdf97794 | ||
|
|
c52f0b80f2 | ||
|
|
4b5e25b29c | ||
|
|
c752a3c596 | ||
|
|
740911a2a3 | ||
|
|
242d805c6c | ||
|
|
c543fdc18b | ||
|
|
d48a8e697f | ||
|
|
8749a61d39 | ||
|
|
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 | ||
|
|
6b410a0eea | ||
|
|
73a013d75b | ||
|
|
7159f3db4c | ||
|
|
7d5ecf095c | ||
|
|
fa015a424d | ||
|
|
dd7dd38357 | ||
|
|
22356f2d26 | ||
|
|
66701f6076 |
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.
|
||||||
1
.github/workflows/pull_request.yml
vendored
1
.github/workflows/pull_request.yml
vendored
@@ -11,6 +11,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pull-request:
|
pull-request:
|
||||||
|
name: Open pull request
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
36
.github/workflows/release.yml
vendored
36
.github/workflows/release.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
@@ -9,6 +10,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- dev
|
- dev
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
@@ -17,21 +19,27 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
# Make sure the release step uses its own credentials:
|
||||||
|
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||||
|
persist-credentials: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup JDK
|
- name: Cache
|
||||||
uses: actions/setup-java@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
path: |
|
||||||
distribution: 'zulu'
|
${{ runner.home }}/.gradle/caches
|
||||||
cache: gradle
|
${{ runner.home }}/.gradle/wrapper
|
||||||
- name: Setup Node.js
|
.gradle
|
||||||
uses: actions/setup-node@v3
|
build
|
||||||
with:
|
node_modules
|
||||||
node-version: "latest"
|
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
|
||||||
cache: 'npm'
|
- name: Build with Gradle
|
||||||
- name: Setup semantic-release
|
|
||||||
run: npm install semantic-release @saithodev/semantic-release-backmerge @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin -D
|
|
||||||
- name: Release
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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
|
# IntelliJ
|
||||||
out/
|
out/
|
||||||
|
.idea/
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
# mpeltonen/sbt-idea plugin
|
||||||
.idea_modules/
|
.idea_modules/
|
||||||
@@ -117,4 +118,7 @@ gradle-app.setting
|
|||||||
!src/test/resources/*
|
!src/test/resources/*
|
||||||
|
|
||||||
# Dependency directories
|
# 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>
|
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
[
|
[
|
||||||
"@saithodev/semantic-release-backmerge",
|
"@saithodev/semantic-release-backmerge",
|
||||||
{
|
{
|
||||||
branches: [{from: "main", to: "dev"}],
|
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||||
clearWorkspace: true
|
clearWorkspace: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
305
CHANGELOG.md
305
CHANGELOG.md
@@ -1,3 +1,308 @@
|
|||||||
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](https://github.com/revanced/revanced-patcher/commit/dd7dd383577dcfc95e97f77b446a89b41b589dc0))
|
||||||
|
|
||||||
|
# [6.4.0](https://github.com/revanced/revanced-patcher/compare/v6.3.2...v6.4.0) (2023-01-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add missing setter to `MutableMethod` ([8f3ecc3](https://github.com/revanced/revanced-patcher/commit/8f3ecc318c39f0270aff53efdee7a1c8d82af421))
|
||||||
|
* do not fix methods or methods in class merger ([4102f43](https://github.com/revanced/revanced-patcher/commit/4102f43b8a9473fd0ee96c5d4fb8f6e9b4e30e70))
|
||||||
|
* fix method and field access when merging classes ([5c09ef7](https://github.com/revanced/revanced-patcher/commit/5c09ef7837f9b731e137b66c19da77f63c007595))
|
||||||
|
* make `aaptPath` nullable ([#146](https://github.com/revanced/revanced-patcher/issues/146)) ([9f0a09a](https://github.com/revanced/revanced-patcher/commit/9f0a09a7569fd5dd78afa27cb66a73d1662edc69))
|
||||||
|
|
||||||
# [6.4.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v6.4.0-dev.1...v6.4.0-dev.2) (2023-01-02)
|
# [6.4.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v6.4.0-dev.1...v6.4.0-dev.2) (2023-01-02)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
# Patcher
|
# 💉 ReVanced Patcher
|
||||||
Patcher framework used in the ReVanced project.
|
|
||||||
|
ReVanced Patcher used to patch Android applications.
|
||||||
|
|||||||
42
arsclib-utils/.gitignore
vendored
Normal file
42
arsclib-utils/.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
||||||
18
arsclib-utils/build.gradle.kts
Normal file
18
arsclib-utils/build.gradle.kts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm")
|
||||||
|
`maven-publish`
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "app.revanced"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("io.github.reandroid:ARSCLib:1.1.7")
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
withSourcesJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(11)
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package app.revanced.arsc
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when there is an error with APK resources.
|
||||||
|
*
|
||||||
|
* @param message The exception message.
|
||||||
|
* @param throwable The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
sealed class ApkResourceException(message: String, throwable: Throwable? = null) : Exception(message, throwable) {
|
||||||
|
/**
|
||||||
|
* An exception when locking resources.
|
||||||
|
*
|
||||||
|
* @param message The exception message.
|
||||||
|
* @param throwable The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
class Locked(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception when writing resources.
|
||||||
|
*
|
||||||
|
* @param message The exception message.
|
||||||
|
* @param throwable The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
class Write(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception when reading resources.
|
||||||
|
*
|
||||||
|
* @param message The exception message.
|
||||||
|
* @param throwable The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
class Read(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
|
||||||
|
/**
|
||||||
|
* An exception when decoding resources.
|
||||||
|
*
|
||||||
|
* @param message The exception message.
|
||||||
|
* @param throwable The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
class Decode(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception when encoding resources.
|
||||||
|
*
|
||||||
|
* @param message The exception message.
|
||||||
|
* @param throwable The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
class Encode(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when a reference could not be resolved.
|
||||||
|
*
|
||||||
|
* @param reference The invalid reference.
|
||||||
|
* @param throwable The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
class InvalidReference(reference: String, throwable: Throwable? = null) :
|
||||||
|
ApkResourceException("Failed to resolve: $reference", throwable) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when a reference could not be resolved.
|
||||||
|
*
|
||||||
|
* @param type The type of the reference.
|
||||||
|
* @param name The name of the reference.
|
||||||
|
* @param throwable The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
constructor(type: String, name: String, throwable: Throwable? = null) : this("@$type/$name", throwable)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when the Apk file not have a resource table, but was expected to have one.
|
||||||
|
*/
|
||||||
|
class MissingResourceTable : ApkResourceException("Apk does not have a resource table.")
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
|
package app.revanced.arsc.archive
|
||||||
|
|
||||||
|
import app.revanced.arsc.resource.ResourceContainer
|
||||||
|
import com.reandroid.apk.ApkModule
|
||||||
|
import com.reandroid.apk.DexFileInputSource
|
||||||
|
import com.reandroid.archive.InputSource
|
||||||
|
import java.io.File
|
||||||
|
import java.io.Flushable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for reading/writing files in an [ApkModule].
|
||||||
|
*
|
||||||
|
* @param module The [ApkModule] to operate on.
|
||||||
|
*/
|
||||||
|
class Archive(internal val module: ApkModule) : Flushable {
|
||||||
|
val mainPackageResources = ResourceContainer(this, module.tableBlock)
|
||||||
|
|
||||||
|
fun save(output: File) {
|
||||||
|
flush()
|
||||||
|
module.writeApk(output)
|
||||||
|
}
|
||||||
|
fun readDexFiles(): MutableList<DexFileInputSource> = module.listDexFiles()
|
||||||
|
fun write(inputSource: InputSource) = module.apkArchive.add(inputSource) // Overwrites existing files.
|
||||||
|
fun read(name: String): InputSource? = module.apkArchive.getInputSource(name)
|
||||||
|
override fun flush() = mainPackageResources.flush()
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package app.revanced.arsc.logging
|
||||||
|
interface Logger {
|
||||||
|
fun error(msg: String)
|
||||||
|
fun warn(msg: String)
|
||||||
|
fun info(msg: String)
|
||||||
|
fun trace(msg: String)
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package app.revanced.arsc.resource
|
||||||
|
|
||||||
|
import app.revanced.arsc.ApkResourceException
|
||||||
|
import com.reandroid.arsc.coder.EncodeResult
|
||||||
|
import com.reandroid.arsc.coder.ValueDecoder
|
||||||
|
import com.reandroid.arsc.value.Entry
|
||||||
|
import com.reandroid.arsc.value.ValueType
|
||||||
|
import com.reandroid.arsc.value.array.ArrayBag
|
||||||
|
import com.reandroid.arsc.value.array.ArrayBagItem
|
||||||
|
import com.reandroid.arsc.value.plurals.PluralsBag
|
||||||
|
import com.reandroid.arsc.value.plurals.PluralsBagItem
|
||||||
|
import com.reandroid.arsc.value.plurals.PluralsQuantity
|
||||||
|
import com.reandroid.arsc.value.style.StyleBag
|
||||||
|
import com.reandroid.arsc.value.style.StyleBagItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A resource value.
|
||||||
|
*/
|
||||||
|
sealed class Resource {
|
||||||
|
internal abstract fun write(entry: Entry, resources: ResourceContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val Resource.isComplex get() = when (this) {
|
||||||
|
is Scalar -> false
|
||||||
|
is Complex -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple resource.
|
||||||
|
*/
|
||||||
|
open class Scalar internal constructor(private val valueType: ValueType, private val value: Int) : Resource() {
|
||||||
|
protected open fun data(resources: ResourceContainer) = value
|
||||||
|
|
||||||
|
override fun write(entry: Entry, resources: ResourceContainer) {
|
||||||
|
entry.setValueAsRaw(valueType, data(resources))
|
||||||
|
}
|
||||||
|
|
||||||
|
internal open fun toArrayItem(resources: ResourceContainer) = ArrayBagItem.create(valueType, data(resources))
|
||||||
|
internal open fun toStyleItem(resources: ResourceContainer) = StyleBagItem.create(valueType, data(resources))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A marker class for complex resources.
|
||||||
|
*/
|
||||||
|
sealed class Complex : Resource()
|
||||||
|
|
||||||
|
private fun encoded(encodeResult: EncodeResult?) = encodeResult?.let { Scalar(it.valueType, it.value) }
|
||||||
|
?: throw ApkResourceException.Encode("Failed to encode value")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a color.
|
||||||
|
*
|
||||||
|
* @param hex The hex value of the color.
|
||||||
|
* @return The encoded [Resource].
|
||||||
|
*/
|
||||||
|
fun color(hex: String) = encoded(ValueDecoder.encodeColor(hex))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a dimension or fraction.
|
||||||
|
*
|
||||||
|
* @param value The dimension value such as 24dp.
|
||||||
|
* @return The encoded [Resource].
|
||||||
|
*/
|
||||||
|
fun dimension(value: String) = encoded(ValueDecoder.encodeDimensionOrFraction(value))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a boolean resource.
|
||||||
|
*
|
||||||
|
* @param value The boolean.
|
||||||
|
* @return The encoded [Resource].
|
||||||
|
*/
|
||||||
|
fun boolean(value: Boolean) = Scalar(ValueType.INT_BOOLEAN, if (value) -Int.MAX_VALUE else 0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a float.
|
||||||
|
*
|
||||||
|
* @param n The number to encode.
|
||||||
|
* @return The encoded [Resource].
|
||||||
|
*/
|
||||||
|
fun float(n: Float) = Scalar(ValueType.FLOAT, n.toBits())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an integer [Resource].
|
||||||
|
*
|
||||||
|
* @param n The number to encode.
|
||||||
|
* @return The integer [Resource].
|
||||||
|
*/
|
||||||
|
fun integer(n: Int) = Scalar(ValueType.INT_DEC, n)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a reference [Resource].
|
||||||
|
*
|
||||||
|
* @param resourceId The target resource.
|
||||||
|
* @return The reference resource.
|
||||||
|
*/
|
||||||
|
fun reference(resourceId: Int) = Scalar(ValueType.REFERENCE, resourceId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve and create a reference [Resource].
|
||||||
|
*
|
||||||
|
* @see reference
|
||||||
|
* @param ref The reference string to resolve.
|
||||||
|
* @param resourceTable The resource table to resolve the reference with.
|
||||||
|
* @return The reference resource.
|
||||||
|
*/
|
||||||
|
fun reference(resourceTable: ResourceTable, ref: String) = reference(resourceTable.resolve(ref))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array [Resource].
|
||||||
|
*
|
||||||
|
* @param elements The elements of the array.
|
||||||
|
*/
|
||||||
|
class Array(private val elements: Collection<Scalar>) : Complex() {
|
||||||
|
override fun write(entry: Entry, resources: ResourceContainer) {
|
||||||
|
ArrayBag.create(entry).addAll(elements.map { it.toArrayItem(resources) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A style resource.
|
||||||
|
*
|
||||||
|
* @param elements The attributes to override.
|
||||||
|
* @param parent A reference to the parent style.
|
||||||
|
*/
|
||||||
|
class Style(private val elements: Map<String, Scalar>, private val parent: String? = null) : Complex() {
|
||||||
|
override fun write(entry: Entry, resources: ResourceContainer) {
|
||||||
|
val resTable = resources.resourceTable
|
||||||
|
val style = StyleBag.create(entry)
|
||||||
|
parent?.let {
|
||||||
|
style.parentId = resTable.resolve(parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
style.putAll(
|
||||||
|
elements.asIterable().associate {
|
||||||
|
StyleBag.resolve(resTable.encodeMaterials, it.key) to it.value.toStyleItem(resources)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A quantity string [Resource].
|
||||||
|
*
|
||||||
|
* @param elements A map of the quantity to the corresponding string.
|
||||||
|
*/
|
||||||
|
class Plurals(private val elements: Map<String, String>) : Complex() {
|
||||||
|
override fun write(entry: Entry, resources: ResourceContainer) {
|
||||||
|
val plurals = PluralsBag.create(entry)
|
||||||
|
|
||||||
|
plurals.putAll(elements.asIterable().associate { (k, v) ->
|
||||||
|
PluralsQuantity.value(k) to PluralsBagItem.string(resources.getOrCreateString(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string [Resource].
|
||||||
|
*
|
||||||
|
* @param value The string value.
|
||||||
|
*/
|
||||||
|
class StringResource(val value: String) : Scalar(ValueType.STRING, 0) {
|
||||||
|
private fun tableString(resources: ResourceContainer) = resources.getOrCreateString(value)
|
||||||
|
|
||||||
|
override fun data(resources: ResourceContainer) = tableString(resources).index
|
||||||
|
override fun toArrayItem(resources: ResourceContainer) = ArrayBagItem.string(tableString(resources))
|
||||||
|
override fun toStyleItem(resources: ResourceContainer) = StyleBagItem.string(tableString(resources))
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package app.revanced.arsc.resource
|
||||||
|
|
||||||
|
import app.revanced.arsc.ApkResourceException
|
||||||
|
import app.revanced.arsc.archive.Archive
|
||||||
|
import com.reandroid.apk.xmlencoder.EncodeUtil
|
||||||
|
import com.reandroid.arsc.chunk.TableBlock
|
||||||
|
import com.reandroid.arsc.chunk.xml.ResXmlDocument
|
||||||
|
import com.reandroid.arsc.value.Entry
|
||||||
|
import com.reandroid.arsc.value.ResConfig
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
import java.io.Flushable
|
||||||
|
|
||||||
|
class ResourceContainer(private val archive: Archive, internal val tableBlock: TableBlock) : Flushable {
|
||||||
|
private val packageBlock = tableBlock.pickOne() // Pick the main package block.
|
||||||
|
internal lateinit var resourceTable: ResourceTable // TODO: Set this.
|
||||||
|
|
||||||
|
private val lockedResourceFileNames = mutableSetOf<String>()
|
||||||
|
|
||||||
|
private fun lock(resourceFile: ResourceFile) {
|
||||||
|
if (resourceFile.name in lockedResourceFileNames) {
|
||||||
|
throw ApkResourceException.Locked("Resource file ${resourceFile.name} is already locked.")
|
||||||
|
}
|
||||||
|
|
||||||
|
lockedResourceFileNames.add(resourceFile.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unlock(resourceFile: ResourceFile) {
|
||||||
|
lockedResourceFileNames.remove(resourceFile.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun <T : ResourceFile> openResource(name: String): ResourceFileEditor<T> {
|
||||||
|
val inputSource = archive.read(name)
|
||||||
|
?: throw ApkResourceException.Read("Resource file $name not found.")
|
||||||
|
|
||||||
|
val resourceFile = when {
|
||||||
|
ResXmlDocument.isResXmlBlock(inputSource.openStream()) -> {
|
||||||
|
val xmlDocument = archive.module
|
||||||
|
.loadResXmlDocument(inputSource)
|
||||||
|
.decodeToXml(resourceTable.entryStore, packageBlock.id)
|
||||||
|
|
||||||
|
ResourceFile.XmlResourceFile(name, xmlDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val bytes = inputSource.openStream().use { it.readAllBytes() }
|
||||||
|
|
||||||
|
ResourceFile.BinaryResourceFile(name, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return ResourceFileEditor(resourceFile as T).also {
|
||||||
|
lockedResourceFileNames.add(name)
|
||||||
|
}
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
throw ApkResourceException.Decode("Resource file $name is not ${resourceFile::class}.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ResourceFileEditor<T : ResourceFile> internal constructor(
|
||||||
|
private val resourceFile: T,
|
||||||
|
) : Closeable {
|
||||||
|
fun use(block: (T) -> Unit) = block(resourceFile)
|
||||||
|
override fun close() {
|
||||||
|
lockedResourceFileNames.remove(resourceFile.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flush() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a resource file, creating it if the file does not exist.
|
||||||
|
*
|
||||||
|
* @param path The resource file path.
|
||||||
|
* @return The corresponding [ResourceFiles],
|
||||||
|
*/
|
||||||
|
fun openFile(path: String) = ResourceFiles(createHandle(path), archive)
|
||||||
|
|
||||||
|
private fun getPackageBlock() = packageBlock ?: throw ApkResourceException.MissingResourceTable
|
||||||
|
|
||||||
|
internal fun getOrCreateString(value: String) =
|
||||||
|
tableBlock?.stringPool?.getOrCreate(value) ?: throw ApkResourceException.MissingResourceTable
|
||||||
|
|
||||||
|
private fun Entry.set(resource: Resource) {
|
||||||
|
val existingEntryNameReference = specReference
|
||||||
|
|
||||||
|
// Sets this.specReference if the entry is not yet initialized.
|
||||||
|
// Sets this.specReference to 0 if the resource type of the existing entry changes.
|
||||||
|
ensureComplex(resource.isComplex)
|
||||||
|
|
||||||
|
if (existingEntryNameReference != 0) {
|
||||||
|
// Preserve the entry name by restoring the previous spec block reference (if present).
|
||||||
|
specReference = existingEntryNameReference
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.write(this, this@ResourceContainer)
|
||||||
|
resourceTable.registerChanged(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an [Entry] from the resource table.
|
||||||
|
*
|
||||||
|
* @param type The resource type.
|
||||||
|
* @param name The resource name.
|
||||||
|
* @param qualifiers The variant to use.
|
||||||
|
*/
|
||||||
|
private fun getEntry(type: String, name: String, qualifiers: String?): Entry? {
|
||||||
|
val resourceId = try {
|
||||||
|
resourceTable.resolve("@$type/$name")
|
||||||
|
} catch (_: ApkResourceException.InvalidReference) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val config = ResConfig.parse(qualifiers)
|
||||||
|
return tableBlock?.resolveReference(resourceId)?.singleOrNull { it.resConfig == config }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [ResourceFiles.Handle] that can be used to open a [ResourceFiles].
|
||||||
|
* This may involve looking it up in the resource table to find the actual location in the archive.
|
||||||
|
*
|
||||||
|
* @param path The path of the resource.
|
||||||
|
*/
|
||||||
|
private fun createHandle(path: String): ResourceFiles.Handle {
|
||||||
|
if (path.startsWith("res/values")) throw ApkResourceException.Decode("Decoding the resource table as a file is not supported")
|
||||||
|
|
||||||
|
var onClose = {}
|
||||||
|
var archivePath = path
|
||||||
|
|
||||||
|
if (tableBlock != null && path.startsWith("res/") && path.count { it == '/' } == 2) {
|
||||||
|
val file = File(path)
|
||||||
|
|
||||||
|
val qualifiers = EncodeUtil.getQualifiersFromResFile(file)
|
||||||
|
val type = EncodeUtil.getTypeNameFromResFile(file)
|
||||||
|
val name = file.nameWithoutExtension
|
||||||
|
|
||||||
|
// The resource file names that the app developers used may have been minified, so we have to resolve it with the resource table.
|
||||||
|
// Example: res/drawable-hdpi/icon.png -> res/4a.png
|
||||||
|
getEntry(type, name, qualifiers)?.resValue?.valueAsString?.let {
|
||||||
|
archivePath = it
|
||||||
|
} ?: run {
|
||||||
|
// An entry for this specific resource file was not found in the resource table, so we have to register it after we save.
|
||||||
|
onClose = { setResource(type, name, StringResource(archivePath), qualifiers) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResourceFiles.Handle(path, archivePath, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setResource(type: String, entryName: String, resource: Resource, qualifiers: String? = null) =
|
||||||
|
getPackageBlock().getOrCreate(qualifiers, type, entryName).also { it.set(resource) }.resourceId
|
||||||
|
|
||||||
|
fun setResources(type: String, resources: Map<String, Resource>, configuration: String? = null) {
|
||||||
|
getPackageBlock().getOrCreateSpecTypePair(type).getOrCreateTypeBlock(configuration).apply {
|
||||||
|
resources.forEach { (entryName, resource) -> getOrCreateEntry(entryName).set(resource) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flush() {
|
||||||
|
packageBlock?.name = archive
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package app.revanced.arsc.resource
|
||||||
|
|
||||||
|
import app.revanced.arsc.ApkResourceException
|
||||||
|
import app.revanced.arsc.archive.Archive
|
||||||
|
import com.reandroid.archive.InputSource
|
||||||
|
import com.reandroid.xml.XMLDocument
|
||||||
|
import com.reandroid.xml.XMLException
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
|
||||||
|
abstract class ResourceFile(val name: String) {
|
||||||
|
internal var realName: String? = null
|
||||||
|
|
||||||
|
class XmlResourceFile(name: String, val document: XMLDocument) : ResourceFile(name)
|
||||||
|
class BinaryResourceFile(name: String, var bytes: ByteArray) : ResourceFile(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFiles private constructor(
|
||||||
|
) : Closeable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a [ResourceFiles].
|
||||||
|
*
|
||||||
|
* @param handle The [Handle] associated with this file.
|
||||||
|
* @param archive The [Archive] that the file resides in.
|
||||||
|
*/
|
||||||
|
internal constructor(handle: Handle, archive: Archive) : this(
|
||||||
|
handle,
|
||||||
|
archive,
|
||||||
|
try {
|
||||||
|
archive.read(handle.archivePath)
|
||||||
|
} catch (e: XMLException) {
|
||||||
|
throw ApkResourceException.Decode("Failed to decode XML while reading ${handle.virtualPath}", e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw ApkResourceException.Decode("Could not read ${handle.virtualPath}", e)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_BUFFER_SIZE = 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
var contents = readResult?.data ?: ByteArray(0)
|
||||||
|
set(value) {
|
||||||
|
pendingWrite = true
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
val exists = readResult != null
|
||||||
|
|
||||||
|
override fun toString() = handle.virtualPath
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
if (pendingWrite) {
|
||||||
|
val path = handle.archivePath
|
||||||
|
|
||||||
|
if (isXmlResource) archive.writeXml(
|
||||||
|
path,
|
||||||
|
try {
|
||||||
|
XMLDocument.load(inputStream())
|
||||||
|
} catch (e: XMLException) {
|
||||||
|
throw ApkResourceException.Encode("Failed to parse XML while writing ${handle.virtualPath}", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
) else archive.writeRaw(path, contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.onClose()
|
||||||
|
|
||||||
|
|
||||||
|
archive.unlock(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun inputStream(): InputStream = ByteArrayInputStream(contents)
|
||||||
|
|
||||||
|
fun outputStream(bufferSize: Int = DEFAULT_BUFFER_SIZE): OutputStream =
|
||||||
|
object : ByteArrayOutputStream(bufferSize) {
|
||||||
|
override fun close() {
|
||||||
|
this@ResourceFiles.contents = if (buf.size > count) buf.copyOf(count) else buf
|
||||||
|
super.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param virtualPath The resource file path. Example: /res/drawable-hdpi/icon.png.
|
||||||
|
* @param archivePath The actual file path in the archive. Example: res/4a.png.
|
||||||
|
* @param onClose An action to perform when the file associated with this handle is closed
|
||||||
|
*/
|
||||||
|
internal data class Handle(val virtualPath: String, val archivePath: String, val onClose: () -> Unit)
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package app.revanced.arsc.resource
|
||||||
|
|
||||||
|
import app.revanced.arsc.ApkResourceException
|
||||||
|
import com.reandroid.apk.xmlencoder.EncodeException
|
||||||
|
import com.reandroid.apk.xmlencoder.EncodeMaterials
|
||||||
|
import com.reandroid.arsc.util.FrameworkTable
|
||||||
|
import com.reandroid.arsc.value.Entry
|
||||||
|
import com.reandroid.common.TableEntryStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A high-level API for resolving resources in the resource table, which spans the entire ApkBundle.
|
||||||
|
*/
|
||||||
|
class ResourceTable(base: ResourceContainer, all: Sequence<ResourceContainer>) {
|
||||||
|
private val packageName = base.tableBlock!!.name
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [TableEntryStore] used to decode XML.
|
||||||
|
*/
|
||||||
|
internal val entryStore = TableEntryStore()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [EncodeMaterials] to use for resolving resources and encoding XML.
|
||||||
|
*/
|
||||||
|
internal val encodeMaterials: EncodeMaterials = object : EncodeMaterials() {
|
||||||
|
/*
|
||||||
|
Our implementation is more efficient because it does not have to loop through every single entry group
|
||||||
|
when the resource id cannot be found in the TableIdentifier, which does not update when you create a new resource.
|
||||||
|
It also looks at the entire table instead of just the current package.
|
||||||
|
*/
|
||||||
|
override fun resolveLocalResourceId(type: String, name: String) = resolveLocal(type, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource mappings which are generated when the ApkBundle is created.
|
||||||
|
*/
|
||||||
|
private val tableIdentifier = encodeMaterials.tableIdentifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A table of all the resources that have been changed or added.
|
||||||
|
*/
|
||||||
|
private val modifiedResources = HashMap<String, HashMap<String, Int>>()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a resource id for the specified resource.
|
||||||
|
* Cannot resolve resources from the android framework.
|
||||||
|
*
|
||||||
|
* @param type The type of the resource.
|
||||||
|
* @param name The name of the resource.
|
||||||
|
* @return The id of the resource.
|
||||||
|
*/
|
||||||
|
fun resolveLocal(type: String, name: String) =
|
||||||
|
modifiedResources[type]?.get(name)
|
||||||
|
?: tableIdentifier.get(packageName, type, name)?.resourceId
|
||||||
|
?: throw ApkResourceException.InvalidReference(
|
||||||
|
type,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a resource id for the specified resource.
|
||||||
|
*
|
||||||
|
* @param reference The resource reference string.
|
||||||
|
* @return The id of the resource.
|
||||||
|
*/
|
||||||
|
fun resolve(reference: String) = try {
|
||||||
|
encodeMaterials.resolveReference(reference)
|
||||||
|
} catch (e: EncodeException) {
|
||||||
|
throw ApkResourceException.InvalidReference(reference, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the [ResourceTable] that an [Entry] has been created or modified.
|
||||||
|
*/
|
||||||
|
internal fun registerChanged(entry: Entry) {
|
||||||
|
modifiedResources.getOrPut(entry.typeName, ::HashMap)[entry.name] = entry.resourceId
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
all.forEach {
|
||||||
|
it.tableBlock?.let { table ->
|
||||||
|
entryStore.add(table)
|
||||||
|
tableIdentifier.load(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.resourceTable = this
|
||||||
|
}
|
||||||
|
|
||||||
|
base.also {
|
||||||
|
encodeMaterials.currentPackage = it.tableBlock
|
||||||
|
|
||||||
|
it.tableBlock!!.frameWorks.forEach { fw ->
|
||||||
|
if (fw is FrameworkTable) {
|
||||||
|
entryStore.add(fw)
|
||||||
|
encodeMaterials.addFramework(fw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package app.revanced.arsc.xml
|
||||||
|
|
||||||
|
import app.revanced.arsc.resource.ResourceContainer
|
||||||
|
import app.revanced.arsc.resource.boolean
|
||||||
|
import com.reandroid.apk.xmlencoder.EncodeException
|
||||||
|
import com.reandroid.apk.xmlencoder.XMLEncodeSource
|
||||||
|
import com.reandroid.arsc.chunk.xml.ResXmlDocument
|
||||||
|
import com.reandroid.xml.XMLDocument
|
||||||
|
import com.reandroid.xml.XMLElement
|
||||||
|
import com.reandroid.xml.source.XMLDocumentSource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Archive input source to lazily encode an [XMLDocument] after it has been modified.
|
||||||
|
*
|
||||||
|
* @param name The file name of this input source.
|
||||||
|
* @param document The [XMLDocument] to encode.
|
||||||
|
* @param resources The [ResourceContainer] to use for encoding.
|
||||||
|
*/
|
||||||
|
internal class LazyXMLEncodeSource(
|
||||||
|
name: String,
|
||||||
|
val document: XMLDocument,
|
||||||
|
private val resources: ResourceContainer
|
||||||
|
) : XMLEncodeSource(resources.resourceTable.encodeMaterials, XMLDocumentSource(name, document)) {
|
||||||
|
private var encoded = false
|
||||||
|
|
||||||
|
override fun getResXmlBlock(): ResXmlDocument {
|
||||||
|
if (encoded) return super.getResXmlBlock()
|
||||||
|
|
||||||
|
XMLEncodeSource(resources.resourceTable.encodeMaterials, XMLDocumentSource(name, document))
|
||||||
|
|
||||||
|
fun XMLElement.registerIds() {
|
||||||
|
listAttributes().forEach { attr ->
|
||||||
|
if (!attr.value.startsWith("@+id/")) return@forEach
|
||||||
|
|
||||||
|
val name = attr.value.split('/').last()
|
||||||
|
resources.setResource("id", name, boolean(false))
|
||||||
|
attr.value = "@id/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
listChildElements().forEach { it.registerIds() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle all @+id/id_name references in the document.
|
||||||
|
document.documentElement.registerIds()
|
||||||
|
|
||||||
|
encoded = true
|
||||||
|
|
||||||
|
// This will call XMLEncodeSource.getResXmlBlock(),
|
||||||
|
// which will encode the document if it has not already been encoded.
|
||||||
|
try {
|
||||||
|
return super.getResXmlBlock()
|
||||||
|
} catch (e: EncodeException) {
|
||||||
|
throw EncodeException("Failed to encode $name", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,68 +1,3 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.7.0"
|
kotlin("jvm") version "1.8.20" apply false
|
||||||
java
|
|
||||||
`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.2.r2")
|
|
||||||
implementation("org.apktool:apktool-lib:2.9.0-SNAPSHOT")
|
|
||||||
|
|
||||||
implementation(kotlin("reflect"))
|
|
||||||
testImplementation(kotlin("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
testLogging {
|
|
||||||
events("PASSED", "SKIPPED", "FAILED")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processResources {
|
|
||||||
expand("projectVersion" to project.version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
withSourcesJar()
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mavenLocal()
|
|
||||||
}
|
|
||||||
publications {
|
|
||||||
register<MavenPublication>("gpr") {
|
|
||||||
from(components["java"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
|
org.gradle.parallel=true
|
||||||
|
org.gradle.caching=true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 6.4.0-dev.2
|
version = 11.0.4
|
||||||
|
|||||||
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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
40
gradlew
vendored
40
gradlew
vendored
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/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");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with 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
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
# requires all of these POSIX shell features:
|
# requires all of these POSIX shell features:
|
||||||
# * functions;
|
# * functions;
|
||||||
# * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>,
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
# <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>;
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
# * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>;
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
# * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>.
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
#
|
#
|
||||||
# Important for patching:
|
# Important for patching:
|
||||||
#
|
#
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/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.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +80,10 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
# 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"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -133,22 +130,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | 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" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -193,6 +197,10 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
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;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
@@ -205,6 +213,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
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.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# 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 limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
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
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
727
package-lock.json
generated
727
package-lock.json
generated
@@ -5,10 +5,11 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
||||||
"@semantic-release/changelog": "^6.0.2",
|
"@semantic-release/changelog": "^6.0.2",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"gradle-semantic-release-plugin": "^1.7.4",
|
"gradle-semantic-release-plugin": "^1.7.6",
|
||||||
"semantic-release": "^19.0.5"
|
"semantic-release": "^20.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -312,6 +313,20 @@
|
|||||||
"@octokit/openapi-types": "^14.0.0"
|
"@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": {
|
"node_modules/@semantic-release/changelog": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.2.tgz",
|
||||||
@@ -498,12 +513,6 @@
|
|||||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@types/retry": {
|
||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||||
@@ -580,6 +589,12 @@
|
|||||||
"integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==",
|
"integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/argv-formatter": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz",
|
||||||
@@ -735,14 +750,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "7.0.4",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"string-width": "^4.2.0",
|
"string-width": "^4.2.0",
|
||||||
"strip-ansi": "^6.0.0",
|
"strip-ansi": "^6.0.1",
|
||||||
"wrap-ansi": "^7.0.0"
|
"wrap-ansi": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
@@ -887,19 +905,18 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/cosmiconfig": {
|
"node_modules/cosmiconfig": {
|
||||||
"version": "7.1.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz",
|
||||||
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
"integrity": "sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/parse-json": "^4.0.0",
|
|
||||||
"import-fresh": "^3.2.1",
|
"import-fresh": "^3.2.1",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"parse-json": "^5.0.0",
|
"parse-json": "^5.0.0",
|
||||||
"path-type": "^4.0.0",
|
"path-type": "^4.0.0"
|
||||||
"yaml": "^1.10.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
@@ -1077,17 +1094,126 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/env-ci": {
|
"node_modules/env-ci": {
|
||||||
"version": "5.5.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/env-ci/-/env-ci-8.0.0.tgz",
|
||||||
"integrity": "sha512-o0JdWIbOLP+WJKIUt36hz1ImQQFuN92nhsfTkHHap+J8CiI8WgGpH/a9jEGHh4/TU5BUUGjlnKXNoDb57+ne+A==",
|
"integrity": "sha512-W+3BqGZozFua9MPeXpmTm5eYEBtGgL76jGu/pwMVp/L8PdECSCEWaIp7d4Mw7kuUrbUldK0oV0bNd6ZZjLiMiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"execa": "^5.0.0",
|
"execa": "^6.1.0",
|
||||||
"fromentries": "^1.3.2",
|
"java-properties": "^1.0.2"
|
||||||
"java-properties": "^1.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"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": {
|
"node_modules/error-ex": {
|
||||||
@@ -1179,15 +1305,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/figures": {
|
"node_modules/figures": {
|
||||||
"version": "3.2.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz",
|
||||||
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
|
"integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"escape-string-regexp": "^1.0.5"
|
"escape-string-regexp": "^5.0.0",
|
||||||
|
"is-unicode-supported": "^1.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"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": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
@@ -1219,15 +1358,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/find-versions": {
|
"node_modules/find-versions": {
|
||||||
"version": "4.0.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz",
|
||||||
"integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==",
|
"integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver-regex": "^3.1.2"
|
"semver-regex": "^4.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
@@ -1243,26 +1382,6 @@
|
|||||||
"readable-stream": "^2.0.0"
|
"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": {
|
"node_modules/fs-extra": {
|
||||||
"version": "11.1.0",
|
"version": "11.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
|
||||||
@@ -1402,9 +1521,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/gradle-semantic-release-plugin": {
|
"node_modules/gradle-semantic-release-plugin": {
|
||||||
"version": "1.7.4",
|
"version": "1.7.6",
|
||||||
"resolved": "https://registry.npmjs.org/gradle-semantic-release-plugin/-/gradle-semantic-release-plugin-1.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/gradle-semantic-release-plugin/-/gradle-semantic-release-plugin-1.7.6.tgz",
|
||||||
"integrity": "sha512-Mm2JnmodHxQlCmn0GtSi5j8z4PS2+2VBY993b9GuNGGM+JaIrr8T3tF9uTLR2nGH/oJba6a9jx1ZqVEJdOXDPw==",
|
"integrity": "sha512-FNoZAm9jntxOXLee5uJLlCb9hsFsJ1d4jUnvz08NF6p72OwSmaSBeFN7Wnl6RjW8mPrAuOSwkuinuWWjVeO2aw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -1413,14 +1532,14 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"promisified-properties": "^2.0.3",
|
"promisified-properties": "^2.0.27",
|
||||||
"split2": "^4.1.0"
|
"split2": "^4.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || ^14.17"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"semantic-release": "^19.0.2"
|
"semantic-release": "^20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/handlebars": {
|
"node_modules/handlebars": {
|
||||||
@@ -1475,12 +1594,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hook-std": {
|
"node_modules/hook-std": {
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz",
|
||||||
"integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==",
|
"integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"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": {
|
"node_modules/hosted-git-info": {
|
||||||
@@ -1750,6 +1872,18 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/isarray": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
@@ -1793,6 +1927,18 @@
|
|||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/json-parse-better-errors": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
"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==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/lodash.capitalize": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz",
|
||||||
@@ -4817,12 +4969,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-each-series": {
|
"node_modules/p-each-series": {
|
||||||
"version": "2.2.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz",
|
||||||
"integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==",
|
"integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
@@ -5102,9 +5254,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/promisified-properties": {
|
"node_modules/promisified-properties": {
|
||||||
"version": "2.0.25",
|
"version": "2.0.27",
|
||||||
"resolved": "https://registry.npmjs.org/promisified-properties/-/promisified-properties-2.0.25.tgz",
|
"resolved": "https://registry.npmjs.org/promisified-properties/-/promisified-properties-2.0.27.tgz",
|
||||||
"integrity": "sha512-FtyMDSNf3IwVBjVq4uj5JD4lG+YmOMzt/Huuc+VwPHVqbzim3/U04iMBnVH3lYgOc9s5Jj9sC4G0OVGoo2KbWA==",
|
"integrity": "sha512-fmx256M3b0QcHnqOj+Ok127LoYpmnYRf7g2OyLl7qD7Z0fzNbIZhHHIPKyvegbA29iAPP5BVWv7BJ/y2cMHHjA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parsimmon": "^1.13.0"
|
"parsimmon": "^1.13.0"
|
||||||
@@ -5393,9 +5545,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/semantic-release": {
|
"node_modules/semantic-release": {
|
||||||
"version": "19.0.5",
|
"version": "20.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-20.1.0.tgz",
|
||||||
"integrity": "sha512-NMPKdfpXTnPn49FDogMBi36SiBfXkSOJqCkk0E4iWOY1tusvvgBwqUmxTX1kmlT6kIYed9YwNKD1sfPpqa5yaA==",
|
"integrity": "sha512-+9+n6RIr0Fz0F53cXrjpawxWlUg3O7/qr1jF9lrE+/v6WqwBrSWnavVHTPaf2WLerET2EngoqI0M4pahkKl6XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@semantic-release/commit-analyzer": "^9.0.2",
|
"@semantic-release/commit-analyzer": "^9.0.2",
|
||||||
@@ -5403,35 +5555,350 @@
|
|||||||
"@semantic-release/github": "^8.0.0",
|
"@semantic-release/github": "^8.0.0",
|
||||||
"@semantic-release/npm": "^9.0.0",
|
"@semantic-release/npm": "^9.0.0",
|
||||||
"@semantic-release/release-notes-generator": "^10.0.0",
|
"@semantic-release/release-notes-generator": "^10.0.0",
|
||||||
"aggregate-error": "^3.0.0",
|
"aggregate-error": "^4.0.1",
|
||||||
"cosmiconfig": "^7.0.0",
|
"cosmiconfig": "^8.0.0",
|
||||||
"debug": "^4.0.0",
|
"debug": "^4.0.0",
|
||||||
"env-ci": "^5.0.0",
|
"env-ci": "^8.0.0",
|
||||||
"execa": "^5.0.0",
|
"execa": "^6.1.0",
|
||||||
"figures": "^3.0.0",
|
"figures": "^5.0.0",
|
||||||
"find-versions": "^4.0.0",
|
"find-versions": "^5.1.0",
|
||||||
"get-stream": "^6.0.0",
|
"get-stream": "^6.0.0",
|
||||||
"git-log-parser": "^1.2.0",
|
"git-log-parser": "^1.2.0",
|
||||||
"hook-std": "^2.0.0",
|
"hook-std": "^3.0.0",
|
||||||
"hosted-git-info": "^4.0.0",
|
"hosted-git-info": "^6.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^4.0.10",
|
"marked": "^4.1.0",
|
||||||
"marked-terminal": "^5.0.0",
|
"marked-terminal": "^5.1.1",
|
||||||
"micromatch": "^4.0.2",
|
"micromatch": "^4.0.2",
|
||||||
"p-each-series": "^2.1.0",
|
"p-each-series": "^3.0.0",
|
||||||
"p-reduce": "^2.0.0",
|
"p-reduce": "^3.0.0",
|
||||||
"read-pkg-up": "^7.0.0",
|
"read-pkg-up": "^9.1.0",
|
||||||
"resolve-from": "^5.0.0",
|
"resolve-from": "^5.0.0",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"semver-diff": "^3.1.1",
|
"semver-diff": "^4.0.0",
|
||||||
"signale": "^1.2.1",
|
"signale": "^1.2.1",
|
||||||
"yargs": "^16.2.0"
|
"yargs": "^17.5.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"semantic-release": "bin/semantic-release.js"
|
"semantic-release": "bin/semantic-release.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"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": {
|
"node_modules/semver": {
|
||||||
@@ -5450,33 +5917,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/semver-diff": {
|
"node_modules/semver-diff": {
|
||||||
"version": "3.1.1",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz",
|
||||||
"integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==",
|
"integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver": "^6.3.0"
|
"semver": "^7.3.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=12"
|
||||||
}
|
},
|
||||||
},
|
"funding": {
|
||||||
"node_modules/semver-diff/node_modules/semver": {
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
"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_modules/semver-regex": {
|
"node_modules/semver-regex": {
|
||||||
"version": "3.1.4",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz",
|
||||||
"integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==",
|
"integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
@@ -6067,31 +6528,22 @@
|
|||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/yargs": {
|
||||||
"version": "16.2.0",
|
"version": "17.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
|
||||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
"integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cliui": "^7.0.2",
|
"cliui": "^8.0.1",
|
||||||
"escalade": "^3.1.1",
|
"escalade": "^3.1.1",
|
||||||
"get-caller-file": "^2.0.5",
|
"get-caller-file": "^2.0.5",
|
||||||
"require-directory": "^2.1.1",
|
"require-directory": "^2.1.1",
|
||||||
"string-width": "^4.2.0",
|
"string-width": "^4.2.3",
|
||||||
"y18n": "^5.0.5",
|
"y18n": "^5.0.5",
|
||||||
"yargs-parser": "^20.2.2"
|
"yargs-parser": "^21.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yargs-parser": {
|
"node_modules/yargs-parser": {
|
||||||
@@ -6102,6 +6554,27 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"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": {
|
"devDependencies": {
|
||||||
|
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
||||||
"@semantic-release/changelog": "^6.0.2",
|
"@semantic-release/changelog": "^6.0.2",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"gradle-semantic-release-plugin": "^1.7.4",
|
"gradle-semantic-release-plugin": "^1.7.6",
|
||||||
"semantic-release": "^19.0.5"
|
"semantic-release": "^20.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
revanced-patcher/build.gradle.kts
Normal file
61
revanced-patcher/build.gradle.kts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm")
|
||||||
|
`maven-publish`
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "app.revanced"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("xpp3:xpp3:1.1.4c")
|
||||||
|
implementation("app.revanced:smali:2.5.3-a3836654")
|
||||||
|
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
|
||||||
|
implementation("io.github.reandroid:ARSCLib:1.1.7")
|
||||||
|
implementation(project(":arsclib-utils"))
|
||||||
|
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
|
||||||
|
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
|
||||||
|
|
||||||
|
compileOnly("com.google.android:android:4.1.1.4")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
testLogging {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
|
publications {
|
||||||
|
register<MavenPublication>("gpr") {
|
||||||
|
from(components["java"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
revanced-patcher/settings.gradle.kts
Normal file
1
revanced-patcher/settings.gradle.kts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = "revanced-patcher"
|
||||||
113
revanced-patcher/src/main/kotlin/app/revanced/patcher/Context.kt
Normal file
113
revanced-patcher/src/main/kotlin/app/revanced/patcher/Context.kt
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.arsc.resource.ResourceContainer
|
||||||
|
import app.revanced.patcher.apk.Apk
|
||||||
|
import app.revanced.patcher.apk.ApkBundle
|
||||||
|
import app.revanced.arsc.resource.ResourceFiles
|
||||||
|
import app.revanced.patcher.util.method.MethodWalker
|
||||||
|
import org.jf.dexlib2.iface.Method
|
||||||
|
import org.w3c.dom.Document
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.StringWriter
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
import javax.xml.transform.TransformerFactory
|
||||||
|
import javax.xml.transform.dom.DOMSource
|
||||||
|
import javax.xml.transform.stream.StreamResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common class to constrain [Context] to [BytecodeContext] and [ResourceContext].
|
||||||
|
* @param apkBundle The [ApkBundle] for this context.
|
||||||
|
*/
|
||||||
|
sealed class Context(val apkBundle: ApkBundle)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for the bytecode of an [Apk.Base] file.
|
||||||
|
*
|
||||||
|
* @param apkBundle The [ApkBundle] for this context.
|
||||||
|
*/
|
||||||
|
class BytecodeContext internal constructor(apkBundle: ApkBundle) : Context(apkBundle) {
|
||||||
|
/**
|
||||||
|
* The list of classes.
|
||||||
|
*/
|
||||||
|
val classes = apkBundle.base.bytecodeData.classes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [MethodWalker] instance for the current [BytecodeContext].
|
||||||
|
*
|
||||||
|
* @param startMethod The method to start at.
|
||||||
|
* @return A [MethodWalker] instance.
|
||||||
|
*/
|
||||||
|
fun traceMethodCalls(startMethod: Method) = MethodWalker(this, startMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for [Apk] file resources.
|
||||||
|
*
|
||||||
|
* @param apkBundle the [ApkBundle] for this context.
|
||||||
|
*/
|
||||||
|
class ResourceContext internal constructor(apkBundle: ApkBundle) : Context(apkBundle) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open an [DomFileEditor] for a given DOM file.
|
||||||
|
*
|
||||||
|
* @param inputStream The input stream to read the DOM file from.
|
||||||
|
* @return A [DomFileEditor] instance.
|
||||||
|
*/
|
||||||
|
fun openXmlFile(inputStream: InputStream) = DomFileEditor(inputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a [DomFileEditor] for a resource file in the archive.
|
||||||
|
*
|
||||||
|
* @see [ResourceContainer.openFile]
|
||||||
|
* @param path The resource file path.
|
||||||
|
* @return A [DomFileEditor].
|
||||||
|
*/
|
||||||
|
fun ResourceContainer.openXmlFile(path: String) = DomFileEditor(openFile(path))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for a file that can be edited as a dom document.
|
||||||
|
*
|
||||||
|
* @param inputStream the input stream to read the xml file from.
|
||||||
|
* @param onSave A callback that will be called when the editor is closed to save the file.
|
||||||
|
*/
|
||||||
|
class DomFileEditor internal constructor(
|
||||||
|
private val inputStream: InputStream,
|
||||||
|
private val onSave: ((String) -> Unit)? = null
|
||||||
|
) : Closeable {
|
||||||
|
private var closed: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The document of the xml file.
|
||||||
|
*/
|
||||||
|
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
|
||||||
|
.also(Document::normalize)
|
||||||
|
|
||||||
|
internal constructor(file: ResourceFiles) : this(
|
||||||
|
file.inputStream(),
|
||||||
|
{
|
||||||
|
file.contents = it.toByteArray()
|
||||||
|
file.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the editor and writes back to the file.
|
||||||
|
*/
|
||||||
|
override fun close() {
|
||||||
|
if (closed) return
|
||||||
|
|
||||||
|
inputStream.close()
|
||||||
|
|
||||||
|
onSave?.let { callback ->
|
||||||
|
// Save the updated file.
|
||||||
|
val writer = StringWriter()
|
||||||
|
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), StreamResult(writer))
|
||||||
|
callback(writer.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
222
revanced-patcher/src/main/kotlin/app/revanced/patcher/Patcher.kt
Normal file
222
revanced-patcher/src/main/kotlin/app/revanced/patcher/Patcher.kt
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.apk.Apk
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap
|
||||||
|
import app.revanced.patcher.patch.*
|
||||||
|
import app.revanced.patcher.util.VersionReader
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
typealias ExecutedPatchResults = Flow<Pair<String, PatchException?>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ReVanced Patcher.
|
||||||
|
* @param options The options for the patcher.
|
||||||
|
* @param patches The patches to use.
|
||||||
|
* @param integrations The integrations to merge if necessary. Must be dex files or dex file container such as ZIP, APK or DEX files.
|
||||||
|
*/
|
||||||
|
class Patcher(private val options: PatcherOptions, patches: Iterable<PatchClass>, integrations: Iterable<File>) :
|
||||||
|
Function<Boolean, ExecutedPatchResults> {
|
||||||
|
private val context = PatcherContext(options, patches.toList(), integrations)
|
||||||
|
private val logger = options.logger
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* The version of the ReVanced Patcher.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
val version = VersionReader.read()
|
||||||
|
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
|
internal val dexFileNamer = BasicDexFileNamer()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
/**
|
||||||
|
* Returns true if at least one patches or its dependencies matches the given predicate.
|
||||||
|
*/
|
||||||
|
fun PatchClass.anyRecursively(predicate: (PatchClass) -> Boolean): Boolean =
|
||||||
|
predicate(this) || dependencies?.any { it.java.anyRecursively(predicate) } == true
|
||||||
|
|
||||||
|
// Determine if merging integrations is required.
|
||||||
|
for (patch in context.patches) {
|
||||||
|
if (patch.anyRecursively { it.requiresIntegrations }) {
|
||||||
|
context.integrations.merge = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the patcher.
|
||||||
|
*
|
||||||
|
* @param stopOnError If true, the patches will stop on the first error.
|
||||||
|
* @return A pair of the name of the [Patch] and a [PatchException] if it failed.
|
||||||
|
*/
|
||||||
|
override fun apply(stopOnError: Boolean) = flow {
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
suspend fun executePatch(
|
||||||
|
patchClass: PatchClass,
|
||||||
|
executedPatches: HashMap<String, ExecutedPatch>
|
||||||
|
) {
|
||||||
|
val patchName = patchClass.patchName
|
||||||
|
|
||||||
|
// If the patch has already executed silently skip it.
|
||||||
|
if (executedPatches.contains(patchName)) {
|
||||||
|
if (!executedPatches[patchName]!!.success)
|
||||||
|
throw PatchException("'$patchName' did not succeed previously")
|
||||||
|
|
||||||
|
logger.trace("Skipping '$patchName' because it has already been executed")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively execute all dependency patches.
|
||||||
|
patchClass.dependencies?.forEach { dependencyClass ->
|
||||||
|
val dependency = dependencyClass.java
|
||||||
|
|
||||||
|
try {
|
||||||
|
executePatch(dependency, executedPatches)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
throw PatchException(
|
||||||
|
"'$patchName' depends on '${dependency.patchName}' " +
|
||||||
|
"but the following exception was raised: ${throwable.cause?.stackTraceToString() ?: throwable.message}",
|
||||||
|
throwable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.apply {
|
||||||
|
val bytecodePatch = patchInstance as BytecodePatch
|
||||||
|
bytecodePatch.fingerprints?.resolveUsingLookupMap(context.bytecodeContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
||||||
|
|
||||||
|
var success = false
|
||||||
|
try {
|
||||||
|
patchInstance.execute(patchContext)
|
||||||
|
|
||||||
|
success = true
|
||||||
|
} catch (patchException: PatchException) {
|
||||||
|
throw patchException
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
throw PatchException("Unhandled patch exception: ${throwable.message}", throwable)
|
||||||
|
} finally {
|
||||||
|
executedPatches[patchName] = ExecutedPatch(patchInstance, success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.integrations.merge) context.integrations.merge(logger)
|
||||||
|
|
||||||
|
logger.trace("Initialize lookup maps for method MethodFingerprint resolution")
|
||||||
|
|
||||||
|
MethodFingerprint.initializeFingerprintResolutionLookupMaps(context.bytecodeContext)
|
||||||
|
|
||||||
|
logger.info("Executing patches")
|
||||||
|
|
||||||
|
// Key is patch name.
|
||||||
|
LinkedHashMap<String, ExecutedPatch>().apply {
|
||||||
|
context.patches.forEach { patch ->
|
||||||
|
var exception: PatchException? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
executePatch(patch, this)
|
||||||
|
} catch (patchException: PatchException) {
|
||||||
|
exception = patchException
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: only emit if the patch is not a closeable.
|
||||||
|
// If it is a closeable, this should be done when closing the patch.
|
||||||
|
emit(patch.patchName to exception)
|
||||||
|
|
||||||
|
if (stopOnError && exception != null) return@flow
|
||||||
|
}
|
||||||
|
}.let {
|
||||||
|
it.values
|
||||||
|
.filter(ExecutedPatch::success)
|
||||||
|
.map(ExecutedPatch::patchInstance)
|
||||||
|
.filterIsInstance(Closeable::class.java)
|
||||||
|
.asReversed().forEach { patch ->
|
||||||
|
try {
|
||||||
|
patch.close()
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
val patchException =
|
||||||
|
if (throwable is PatchException) throwable
|
||||||
|
else PatchException(throwable)
|
||||||
|
|
||||||
|
val patchName = (patch as Patch<Context>).javaClass.patchName
|
||||||
|
|
||||||
|
logger.error("Failed to close '$patchName': ${patchException.stackTraceToString()}")
|
||||||
|
|
||||||
|
emit(patchName to patchException)
|
||||||
|
|
||||||
|
// This is not failsafe. If a patch throws an exception while closing,
|
||||||
|
// the other patches that depend on it may fail.
|
||||||
|
if (stopOnError) return@flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodFingerprint.clearFingerprintResolutionLookupMaps()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish patching all [Apk]s.
|
||||||
|
*
|
||||||
|
* @return The [PatcherResult] of the [Patcher].
|
||||||
|
*/
|
||||||
|
fun finish(): PatcherResult {
|
||||||
|
val patchResults = buildList {
|
||||||
|
logger.info("Processing patched apks")
|
||||||
|
options.apkBundle.cleanup(options).forEach { result ->
|
||||||
|
if (result.exception != null) {
|
||||||
|
logger.error("Got exception while processing ${result.apk}: ${result.exception.stackTraceToString()}")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
val patch = result.let {
|
||||||
|
when (it.apk) {
|
||||||
|
is Apk.Base -> PatcherResult.Patch.Base(it.apk)
|
||||||
|
is Apk.Split -> PatcherResult.Patch.Split(it.apk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(patch)
|
||||||
|
|
||||||
|
logger.info("Patched ${result.apk}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatcherResult(patchResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A result of executing a [Patch].
|
||||||
|
*
|
||||||
|
* @param patchInstance The instance of the [Patch] that was executed.
|
||||||
|
* @param success The result of the [Patch].
|
||||||
|
*/
|
||||||
|
internal data class ExecutedPatch(val patchInstance: Patch<Context>, val success: Boolean)
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.logging.Logger
|
||||||
|
import app.revanced.patcher.patch.PatchClass
|
||||||
|
import app.revanced.patcher.util.ClassMerger.merge
|
||||||
|
import lanchon.multidexlib2.MultiDexIO
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class PatcherContext(
|
||||||
|
options: PatcherOptions,
|
||||||
|
internal val patches: List<PatchClass>,
|
||||||
|
integrations: Iterable<File>
|
||||||
|
) {
|
||||||
|
internal val integrations = Integrations(this, integrations)
|
||||||
|
internal val bytecodeContext = BytecodeContext(options.apkBundle)
|
||||||
|
internal val resourceContext = ResourceContext(options.apkBundle)
|
||||||
|
|
||||||
|
internal class Integrations(val context: PatcherContext, private val dexContainers: Iterable<File>) {
|
||||||
|
var merge = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge integrations.
|
||||||
|
* @param logger A logger.
|
||||||
|
*/
|
||||||
|
fun merge(logger: Logger) {
|
||||||
|
context.bytecodeContext.classes.apply {
|
||||||
|
for (integrations in dexContainers) {
|
||||||
|
logger.info("Merging $integrations")
|
||||||
|
|
||||||
|
for (classDef in MultiDexIO.readDexFile(true, integrations, Patcher.dexFileNamer, null, null).classes) {
|
||||||
|
val type = classDef.type
|
||||||
|
|
||||||
|
val existingClassIndex = this.indexOfFirst { it.type == type }
|
||||||
|
if (existingClassIndex == -1) {
|
||||||
|
logger.trace("Merging type $type")
|
||||||
|
add(classDef)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
logger.trace("Type $type exists. Adding missing methods and fields.")
|
||||||
|
|
||||||
|
get(existingClassIndex).apply {
|
||||||
|
merge(classDef, context.bytecodeContext, logger).let { mergedClass ->
|
||||||
|
if (mergedClass !== this) // referential equality check
|
||||||
|
set(existingClassIndex, mergedClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.apk.ApkBundle
|
||||||
|
import app.revanced.patcher.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for the [Patcher].
|
||||||
|
* @param apkBundle The [ApkBundle].
|
||||||
|
* @param logger Custom logger implementation for the [Patcher].
|
||||||
|
*/
|
||||||
|
class PatcherOptions(
|
||||||
|
internal val apkBundle: ApkBundle,
|
||||||
|
internal val logger: Logger = Logger.Nop
|
||||||
|
)
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.apk.Apk
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a patcher.
|
||||||
|
* @param apkFiles The patched [Apk] files.
|
||||||
|
*/
|
||||||
|
data class PatcherResult(val apkFiles: List<Patch>) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a patch.
|
||||||
|
*
|
||||||
|
* @param apk The patched [Apk] file.
|
||||||
|
*/
|
||||||
|
sealed class Patch(val apk: Apk) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a patch of an [Apk.Split] file.
|
||||||
|
*
|
||||||
|
* @param apk The patched [Apk.Split] file.
|
||||||
|
*/
|
||||||
|
class Split(apk: Apk.Split) : Patch(apk)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a patch of an [Apk.Split] file.
|
||||||
|
*
|
||||||
|
* @param apk The patched [Apk.Base] file.
|
||||||
|
*/
|
||||||
|
class Base(apk: Apk.Base) : Patch(apk)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,6 @@ import app.revanced.patcher.patch.Patch
|
|||||||
* @param compatiblePackages A list of packages a [Patch] is compatible with.
|
* @param compatiblePackages A list of packages a [Patch] is compatible with.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Compatibility(
|
annotation class Compatibility(
|
||||||
val compatiblePackages: Array<Package>,
|
val compatiblePackages: Array<Package>,
|
||||||
)
|
)
|
||||||
@@ -19,8 +17,6 @@ annotation class Compatibility(
|
|||||||
* @param versions The versions of the package the [Patch] is compatible with.
|
* @param versions The versions of the package the [Patch] is compatible with.
|
||||||
*/
|
*/
|
||||||
@Target()
|
@Target()
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Package(
|
annotation class Package(
|
||||||
val name: String,
|
val name: String,
|
||||||
val versions: Array<String> = [],
|
val versions: Array<String> = [],
|
||||||
@@ -7,8 +7,6 @@ import app.revanced.patcher.patch.Patch
|
|||||||
* @param name A suggestive name for the [Patch].
|
* @param name A suggestive name for the [Patch].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Name(
|
annotation class Name(
|
||||||
val name: String,
|
val name: String,
|
||||||
)
|
)
|
||||||
@@ -18,8 +16,6 @@ annotation class Name(
|
|||||||
* @param description A description for the [Patch].
|
* @param description A description for the [Patch].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Description(
|
annotation class Description(
|
||||||
val description: String,
|
val description: String,
|
||||||
)
|
)
|
||||||
@@ -30,8 +26,7 @@ annotation class Description(
|
|||||||
* @param version The version of a [Patch].
|
* @param version The version of a [Patch].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Deprecated("This annotation is deprecated and will be removed in the future.")
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Version(
|
annotation class Version(
|
||||||
val version: String,
|
val version: String,
|
||||||
)
|
)
|
||||||
285
revanced-patcher/src/main/kotlin/app/revanced/patcher/apk/Apk.kt
Normal file
285
revanced-patcher/src/main/kotlin/app/revanced/patcher/apk/Apk.kt
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
|
package app.revanced.patcher.apk
|
||||||
|
|
||||||
|
import app.revanced.arsc.ApkResourceException
|
||||||
|
import app.revanced.arsc.archive.Archive
|
||||||
|
import app.revanced.patcher.Patcher
|
||||||
|
import app.revanced.patcher.PatcherOptions
|
||||||
|
import app.revanced.patcher.logging.asArscLogger
|
||||||
|
import app.revanced.patcher.util.ProxyBackedClassList
|
||||||
|
import com.reandroid.apk.ApkModule
|
||||||
|
import com.reandroid.apk.xmlencoder.EncodeException
|
||||||
|
import com.reandroid.archive.InputSource
|
||||||
|
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock
|
||||||
|
import com.reandroid.arsc.value.ResConfig
|
||||||
|
import lanchon.multidexlib2.*
|
||||||
|
import org.jf.dexlib2.Opcodes
|
||||||
|
import org.jf.dexlib2.dexbacked.DexBackedDexFile
|
||||||
|
import org.jf.dexlib2.iface.DexFile
|
||||||
|
import org.jf.dexlib2.iface.MultiDexContainer
|
||||||
|
import org.jf.dexlib2.writer.io.MemoryDataStore
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [Apk] file.
|
||||||
|
*/
|
||||||
|
sealed class Apk private constructor(module: ApkModule) {
|
||||||
|
/**
|
||||||
|
* A wrapper around the zip archive of this [Apk].
|
||||||
|
*
|
||||||
|
* @see Archive
|
||||||
|
*/
|
||||||
|
private val archive = Archive(module)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata of the [Apk].
|
||||||
|
*/
|
||||||
|
val packageMetadata = PackageMetadata(module.androidManifestBlock)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh updated resources and close any open files.
|
||||||
|
*
|
||||||
|
* @param options The [PatcherOptions] of the [Patcher].
|
||||||
|
*/
|
||||||
|
internal open fun cleanup(options: PatcherOptions) {
|
||||||
|
try {
|
||||||
|
archive.cleanup(options.logger.asArscLogger())
|
||||||
|
} catch (e: EncodeException) {
|
||||||
|
throw ApkResourceException.Encode(e.message!!, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
archive.mainPackageResources.refreshPackageName()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the [Apk] to a file.
|
||||||
|
*
|
||||||
|
* @param output The target file.
|
||||||
|
*/
|
||||||
|
fun write(output: File) = archive.save(output)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MANIFEST_FILE_NAME = "AndroidManifest.xml"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the [Module] and [Type] of an [ApkModule].
|
||||||
|
*
|
||||||
|
* @return A [Pair] containing the [Module] and [Type] of the [ApkModule].
|
||||||
|
*/
|
||||||
|
fun ApkModule.identify(): Pair<Module, Type> {
|
||||||
|
val manifestElement = androidManifestBlock.manifestElement
|
||||||
|
return when {
|
||||||
|
isBaseModule -> Module.Main to Type.Base
|
||||||
|
// The module is a base apk for a dynamic feature module if the "isFeatureModule" attribute is set to true.
|
||||||
|
manifestElement.searchAttributeByName("isFeatureModule")?.valueAsBoolean == true -> Module.DynamicFeature(
|
||||||
|
split
|
||||||
|
) to Type.Base
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val module = manifestElement.searchAttributeByName("configForSplit")
|
||||||
|
?.let { Module.DynamicFeature(it.valueAsString) } ?: Module.Main
|
||||||
|
|
||||||
|
// Examples:
|
||||||
|
// config.xhdpi
|
||||||
|
// df_my_feature.config.en
|
||||||
|
val config = this.split.split(".").last()
|
||||||
|
|
||||||
|
val type = when {
|
||||||
|
// Language splits have a two-letter country code.
|
||||||
|
config.length == 2 -> Type.Language(config)
|
||||||
|
// Library splits use the target CPU architecture.
|
||||||
|
Split.Library.architectures.contains(config) -> Type.Library(config)
|
||||||
|
// Asset splits use the density.
|
||||||
|
ResConfig.Density.valueOf(config) != null -> Type.Asset(config)
|
||||||
|
else -> throw IllegalArgumentException("Invalid split config: $config")
|
||||||
|
}
|
||||||
|
|
||||||
|
module to type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inner class BytecodeData {
|
||||||
|
private val opcodes: Opcodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The classes and proxied classes of the [Base] apk file.
|
||||||
|
*/
|
||||||
|
val classes: ProxyBackedClassList
|
||||||
|
|
||||||
|
init {
|
||||||
|
MultiDexContainerBackedDexFile(object : MultiDexContainer<DexBackedDexFile> {
|
||||||
|
// Load all dex files from the apk module and create a dex entry for each of them.
|
||||||
|
private val entries = archive.readDexFiles().associateBy { it.name }
|
||||||
|
.mapValues { (name, inputSource) ->
|
||||||
|
BasicDexEntry(
|
||||||
|
this,
|
||||||
|
name,
|
||||||
|
RawDexIO.readRawDexFile(inputSource.openStream(), inputSource.length, null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDexEntryNames() = entries.keys.toList()
|
||||||
|
override fun getEntry(entryName: String) = entries[entryName]
|
||||||
|
}).let {
|
||||||
|
opcodes = it.opcodes
|
||||||
|
classes = ProxyBackedClassList(it.classes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write [classes] to the archive.
|
||||||
|
*/
|
||||||
|
internal fun writeDexFiles() {
|
||||||
|
// Create patched dex files.
|
||||||
|
mutableMapOf<String, MemoryDataStore>().also {
|
||||||
|
val newDexFile = object : DexFile {
|
||||||
|
override fun getClasses() =
|
||||||
|
this@BytecodeData.classes.also(ProxyBackedClassList::applyProxies).toSet()
|
||||||
|
override fun getOpcodes() = this@BytecodeData.opcodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write modified dex files.
|
||||||
|
MultiDexIO.writeDexFile(
|
||||||
|
true, -1, // Core count.
|
||||||
|
it, Patcher.dexFileNamer, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
|
||||||
|
)
|
||||||
|
}.forEach { (name, store) ->
|
||||||
|
val dexFileInputSource = object : InputSource(name) {
|
||||||
|
override fun openStream() = store.readAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
archive.write(dexFileInputSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata about an [Apk] file.
|
||||||
|
*
|
||||||
|
* @param packageName The package name of the [Apk] file.
|
||||||
|
* @param packageVersion The package version of the [Apk] file.
|
||||||
|
*/
|
||||||
|
data class PackageMetadata(val packageName: String?, val packageVersion: String?) {
|
||||||
|
internal constructor(manifestBlock: AndroidManifestBlock) : this(
|
||||||
|
manifestBlock.packageName,
|
||||||
|
manifestBlock.versionName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [Apk] of type [Split].
|
||||||
|
*
|
||||||
|
* @param config The device configuration associated with this [Split], such as arm64_v8a, en or xhdpi.
|
||||||
|
* @see Apk
|
||||||
|
*/
|
||||||
|
sealed class Split(val config: String, module: ApkModule) : Apk(module) {
|
||||||
|
override fun toString() = "split_config.$config.apk"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The split apk file which contains libraries.
|
||||||
|
*
|
||||||
|
* @see Split
|
||||||
|
*/
|
||||||
|
class Library internal constructor(config: String, module: ApkModule) : Split(config, module) {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* A set of all architectures supported by android.
|
||||||
|
*/
|
||||||
|
val architectures = setOf("armeabi_v7a", "arm64_v8a", "x86", "x86_64")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The split apk file which contains language strings.
|
||||||
|
*
|
||||||
|
* @see Split
|
||||||
|
*/
|
||||||
|
class Language internal constructor(config: String, module: ApkModule) : Split(config, module)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The split apk file which contains assets.
|
||||||
|
*
|
||||||
|
* @see Split
|
||||||
|
*/
|
||||||
|
class Asset internal constructor(config: String, module: ApkModule) : Split(config, module)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base [Apk] file..
|
||||||
|
*
|
||||||
|
* @see Apk
|
||||||
|
*/
|
||||||
|
class Base internal constructor(module: ApkModule) : Apk(module) {
|
||||||
|
/**
|
||||||
|
* Data of the [Base] apk file.
|
||||||
|
*/
|
||||||
|
internal val bytecodeData = BytecodeData()
|
||||||
|
|
||||||
|
override fun toString() = "base.apk"
|
||||||
|
|
||||||
|
override fun cleanup(options: PatcherOptions) {
|
||||||
|
super.cleanup(options)
|
||||||
|
|
||||||
|
options.logger.info("Writing patched dex files")
|
||||||
|
bytecodeData.writeDexFiles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The module that the [ApkModule] belongs to.
|
||||||
|
*/
|
||||||
|
sealed class Module {
|
||||||
|
/**
|
||||||
|
* The default [Module] that is always installed by software repositories.
|
||||||
|
*/
|
||||||
|
object Main : Module()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Module] that can be installed later by software repositories when requested by the application.
|
||||||
|
*
|
||||||
|
* @param name The name of the feature.
|
||||||
|
*/
|
||||||
|
data class DynamicFeature(val name: String) : Module()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the [ApkModule].
|
||||||
|
*/
|
||||||
|
sealed class Type {
|
||||||
|
/**
|
||||||
|
* The main Apk of a [Module].
|
||||||
|
*/
|
||||||
|
object Base : Type()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A superclass for all split configuration types.
|
||||||
|
*
|
||||||
|
* @param target The target device configuration.
|
||||||
|
*/
|
||||||
|
sealed class SplitConfig(val target: String) : Type()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Type] of an apk containing native libraries.
|
||||||
|
*
|
||||||
|
* @param architecture The target CPU architecture.
|
||||||
|
*/
|
||||||
|
data class Library(val architecture: String) : SplitConfig(architecture)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Type] for an Apk containing language resources.
|
||||||
|
*
|
||||||
|
* @param language The target language code.
|
||||||
|
*/
|
||||||
|
data class Language(val language: String) : SplitConfig(language)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Type] for an Apk containing assets.
|
||||||
|
*
|
||||||
|
* @param pixelDensity The target screen density.
|
||||||
|
*/
|
||||||
|
data class Asset(val pixelDensity: String) : SplitConfig(pixelDensity)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
|
package app.revanced.patcher.apk
|
||||||
|
|
||||||
|
import app.revanced.arsc.ApkResourceException
|
||||||
|
import app.revanced.arsc.resource.ResourceTable
|
||||||
|
import app.revanced.patcher.Patcher
|
||||||
|
import app.revanced.patcher.PatcherOptions
|
||||||
|
import app.revanced.patcher.apk.Apk.Companion.identify
|
||||||
|
import com.reandroid.apk.ApkModule
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [Apk] file of type [Apk.Split].
|
||||||
|
*
|
||||||
|
* @param files A list of apk files to load.
|
||||||
|
*/
|
||||||
|
class ApkBundle(files: List<File>) : Sequence<Apk> {
|
||||||
|
/**
|
||||||
|
* The [Apk.Base] of this [ApkBundle].
|
||||||
|
*/
|
||||||
|
val base: Apk.Base
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map containing all the [Apk.Split]s in this bundle associated by their configuration.
|
||||||
|
*/
|
||||||
|
val splits: Map<String, Apk.Split>?
|
||||||
|
|
||||||
|
init {
|
||||||
|
var baseApk: Apk.Base? = null
|
||||||
|
|
||||||
|
splits = buildMap {
|
||||||
|
files.forEach {
|
||||||
|
val apk = ApkModule.loadApkFile(it)
|
||||||
|
val (module, type) = apk.identify()
|
||||||
|
if (module is Apk.Module.DynamicFeature) {
|
||||||
|
return@forEach // Dynamic feature modules are not supported yet.
|
||||||
|
}
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
Apk.Type.Base -> {
|
||||||
|
if (baseApk != null) {
|
||||||
|
throw IllegalArgumentException("Cannot have more than one base apk")
|
||||||
|
}
|
||||||
|
baseApk = Apk.Base(apk)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Apk.Type.SplitConfig -> {
|
||||||
|
val target = type.target
|
||||||
|
if (this.contains(target)) {
|
||||||
|
throw IllegalArgumentException("Duplicate split: $target")
|
||||||
|
}
|
||||||
|
|
||||||
|
val constructor = when (type) {
|
||||||
|
is Apk.Type.Asset -> Apk.Split::Asset
|
||||||
|
is Apk.Type.Library -> Apk.Split::Library
|
||||||
|
is Apk.Type.Language -> Apk.Split::Language
|
||||||
|
}
|
||||||
|
|
||||||
|
this[target] = constructor(target, apk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
|
base = baseApk ?: throw IllegalArgumentException("Base apk not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [ResourceTable] of this [ApkBundle].
|
||||||
|
*/
|
||||||
|
val resources = ResourceTable(base.resources, map { it.resources })
|
||||||
|
|
||||||
|
override fun iterator() = sequence {
|
||||||
|
yield(base)
|
||||||
|
splits?.values?.let {
|
||||||
|
yieldAll(it)
|
||||||
|
}
|
||||||
|
}.iterator()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh all updated resources in an [ApkBundle].
|
||||||
|
*
|
||||||
|
* @param options The [PatcherOptions] of the [Patcher].
|
||||||
|
* @return A sequence of the [Apk] files which are being refreshed.
|
||||||
|
*/
|
||||||
|
internal fun cleanup(options: PatcherOptions) = map {
|
||||||
|
var exception: ApkResourceException? = null
|
||||||
|
try {
|
||||||
|
it.cleanup(options)
|
||||||
|
} catch (e: ApkResourceException) {
|
||||||
|
exception = e
|
||||||
|
}
|
||||||
|
|
||||||
|
SplitApkResult(it, exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of writing an [Apk] file.
|
||||||
|
*
|
||||||
|
* @param apk The corresponding [Apk] file.
|
||||||
|
* @param exception The optional [ApkResourceException] when an exception occurred.
|
||||||
|
*/
|
||||||
|
data class SplitApkResult(val apk: Apk, val exception: ApkResourceException? = null)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
for (annotation in this.annotations) {
|
||||||
|
if (traversed.contains(annotation)) continue
|
||||||
|
traversed.add(annotation)
|
||||||
|
|
||||||
|
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed))
|
||||||
|
?: continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import org.jf.dexlib2.AccessFlags
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.newLabel(index: Int) = implementation!!.newLabelForIndex(index)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a bitwise OR operation between two [AccessFlags].
|
||||||
|
*
|
||||||
|
* @param other The other [AccessFlags] to perform the operation with.
|
||||||
|
*/
|
||||||
|
infix fun AccessFlags.or(other: AccessFlags) = value or other.value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a bitwise OR operation between an [AccessFlags] and an [Int].
|
||||||
|
*
|
||||||
|
* @param other The [Int] to perform the operation with.
|
||||||
|
*/
|
||||||
|
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a bitwise OR operation between an [Int] and an [AccessFlags].
|
||||||
|
*
|
||||||
|
* @param other The [AccessFlags] to perform the operation with.
|
||||||
|
*/
|
||||||
|
infix fun AccessFlags.or(other: Int) = value or other
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
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 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.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.
|
||||||
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <T> MutableMethod.getInstruction(index: Int): T = implementation!!.getInstruction<T>(index)
|
||||||
|
}
|
||||||
@@ -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,72 @@
|
|||||||
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
|
import app.revanced.patcher.annotation.Compatibility
|
||||||
|
import app.revanced.patcher.annotation.Description
|
||||||
|
import app.revanced.patcher.annotation.Name
|
||||||
|
import app.revanced.patcher.annotation.Version
|
||||||
|
import app.revanced.patcher.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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The version of a [Patch].
|
||||||
|
*/
|
||||||
|
@Deprecated("This property is deprecated and will be removed in the future.")
|
||||||
|
val PatchClass.version
|
||||||
|
get() = findAnnotationRecursively(Version::class)?.version
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class FuzzyPatternScanMethod(
|
annotation class FuzzyPatternScanMethod(
|
||||||
val threshold: Int = 1
|
val threshold: Int = 1
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,513 @@
|
|||||||
|
package app.revanced.patcher.fingerprint.method.impl
|
||||||
|
|
||||||
|
import app.revanced.patcher.BytecodeContext
|
||||||
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||||
|
import app.revanced.patcher.fingerprint.Fingerprint
|
||||||
|
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||||
|
import app.revanced.patcher.patch.PatchException
|
||||||
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
|
import org.jf.dexlib2.AccessFlags
|
||||||
|
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 java.util.*
|
||||||
|
|
||||||
|
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
||||||
|
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
||||||
|
private typealias MethodClassPair = Pair<Method, ClassDef>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fingerprint to resolve methods.
|
||||||
|
*
|
||||||
|
* @param returnType The method's return type compared using [String.startsWith].
|
||||||
|
* @param accessFlags The method's exact access flags using values of [AccessFlags].
|
||||||
|
* @param parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
|
||||||
|
* @param opcodes An opcode pattern of the method's instructions. Wildcard or unknown opcodes can be specified by `null`.
|
||||||
|
* @param strings A list of the method's strings compared each using [String.contains].
|
||||||
|
* @param customFingerprint A custom condition for this fingerprint.
|
||||||
|
*/
|
||||||
|
abstract class MethodFingerprint(
|
||||||
|
internal val returnType: String? = null,
|
||||||
|
internal val accessFlags: Int? = null,
|
||||||
|
internal val parameters: Iterable<String>? = null,
|
||||||
|
internal val opcodes: Iterable<Opcode?>? = null,
|
||||||
|
internal val strings: Iterable<String>? = null,
|
||||||
|
internal val customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null
|
||||||
|
) : Fingerprint {
|
||||||
|
/**
|
||||||
|
* The result of the [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
var result: MethodFingerprintResult? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* A list of methods and the class they were found in.
|
||||||
|
*/
|
||||||
|
private val methods = mutableListOf<MethodClassPair>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for methods keyed to the methods access flags, return type and parameter.
|
||||||
|
*/
|
||||||
|
private val methodSignatureLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for methods keyed to the strings contained in the method.
|
||||||
|
*/
|
||||||
|
private val methodStringsLookupMap = mutableMapOf<String, MutableList<MethodClassPair>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a string based on the parameter reference types of this method.
|
||||||
|
*/
|
||||||
|
private fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
|
||||||
|
// Maximum parameters to use in the signature key.
|
||||||
|
// Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
|
||||||
|
// To keep the signature map from becoming needlessly bloated,
|
||||||
|
// group together in the same map entry all methods with the same access/return and 5 or more parameters.
|
||||||
|
// The value of 5 was chosen based on local performance testing and is not set in stone.
|
||||||
|
val maxSignatureParameters = 5
|
||||||
|
// Must append a unique value before the parameters to distinguish this key includes the parameters.
|
||||||
|
// If this is not appended, then methods with no parameters
|
||||||
|
// will collide with different keys that specify access/return but omit the parameters.
|
||||||
|
append("p:")
|
||||||
|
parameters.forEachIndexed { index, parameter ->
|
||||||
|
if (index >= maxSignatureParameters) return
|
||||||
|
append(parameter.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes lookup maps for [MethodFingerprint] resolution
|
||||||
|
* using attributes of methods such as the method signature or strings.
|
||||||
|
*
|
||||||
|
* @param context The [BytecodeContext] containing the classes to initialize the lookup maps with.
|
||||||
|
*/
|
||||||
|
internal fun initializeFingerprintResolutionLookupMaps(context: BytecodeContext) {
|
||||||
|
fun MutableMap<String, MutableList<MethodClassPair>>.add(
|
||||||
|
key: String,
|
||||||
|
methodClassPair: MethodClassPair
|
||||||
|
) {
|
||||||
|
var methodClassPairs = this[key]
|
||||||
|
|
||||||
|
methodClassPairs ?: run {
|
||||||
|
methodClassPairs = LinkedList<MethodClassPair>().also { this[key] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
methodClassPairs!!.add(methodClassPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methods.isNotEmpty()) throw PatchException("Map already initialized")
|
||||||
|
|
||||||
|
context.classes.forEach { classDef ->
|
||||||
|
classDef.methods.forEach { method ->
|
||||||
|
val methodClassPair = method to classDef
|
||||||
|
|
||||||
|
// For fingerprints with no access or return type specified.
|
||||||
|
methods += methodClassPair
|
||||||
|
|
||||||
|
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
|
||||||
|
|
||||||
|
// Add <access><returnType> as the key.
|
||||||
|
methodSignatureLookupMap.add(accessFlagsReturnKey, methodClassPair)
|
||||||
|
|
||||||
|
// Add <access><returnType>[parameters] as the key.
|
||||||
|
methodSignatureLookupMap.add(
|
||||||
|
buildString {
|
||||||
|
append(accessFlagsReturnKey)
|
||||||
|
appendParameters(method.parameterTypes)
|
||||||
|
},
|
||||||
|
methodClassPair
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add strings contained in the method as the key.
|
||||||
|
method.implementation?.instructions?.forEach instructions@{ instruction ->
|
||||||
|
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO)
|
||||||
|
return@instructions
|
||||||
|
|
||||||
|
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||||
|
|
||||||
|
methodStringsLookupMap.add(string, methodClassPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the future, the class type could be added to the lookup map.
|
||||||
|
// This would require MethodFingerprint to be changed to include the class type.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the internal lookup maps created in [initializeFingerprintResolutionLookupMaps]
|
||||||
|
*/
|
||||||
|
internal fun clearFingerprintResolutionLookupMaps() {
|
||||||
|
methods.clear()
|
||||||
|
methodSignatureLookupMap.clear()
|
||||||
|
methodStringsLookupMap.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a list of [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
|
||||||
|
*
|
||||||
|
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||||
|
* amount of time because they are resolved in sequence.
|
||||||
|
*
|
||||||
|
* For apps with many fingerprints, resolving performance can be improved by:
|
||||||
|
* - Slowest: Specify [opcodes] and nothing else.
|
||||||
|
* - Fast: Specify [accessFlags], [returnType].
|
||||||
|
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||||
|
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||||
|
*/
|
||||||
|
internal fun Iterable<MethodFingerprint>.resolveUsingLookupMap(context: BytecodeContext) {
|
||||||
|
if (methods.isEmpty()) throw PatchException("lookup map not initialized")
|
||||||
|
|
||||||
|
for (fingerprint in this) {
|
||||||
|
fingerprint.resolveUsingLookupMap(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] using the lookup map built by [initializeFingerprintResolutionLookupMaps].
|
||||||
|
*
|
||||||
|
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||||
|
* amount of time because they are resolved in sequence.
|
||||||
|
*
|
||||||
|
* For apps with many fingerprints, resolving performance can be improved by:
|
||||||
|
* - Slowest: Specify [opcodes] and nothing else.
|
||||||
|
* - Fast: Specify [accessFlags], [returnType].
|
||||||
|
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||||
|
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||||
|
*/
|
||||||
|
internal fun MethodFingerprint.resolveUsingLookupMap(context: BytecodeContext): Boolean {
|
||||||
|
/**
|
||||||
|
* Lookup [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||||
|
*
|
||||||
|
* @return A list of [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.methodStringsLookup(): List<MethodClassPair>? {
|
||||||
|
strings?.forEach {
|
||||||
|
val methods = methodStringsLookupMap[it]
|
||||||
|
if (methods != null) return methods
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||||
|
*
|
||||||
|
* @return A list of [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.methodSignatureLookup(): List<MethodClassPair> {
|
||||||
|
if (accessFlags == null) return methods
|
||||||
|
|
||||||
|
var returnTypeValue = returnType
|
||||||
|
if (returnTypeValue == null) {
|
||||||
|
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
||||||
|
// Constructors always have void return type
|
||||||
|
returnTypeValue = "V"
|
||||||
|
} else {
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val key = buildString {
|
||||||
|
append(accessFlags)
|
||||||
|
append(returnTypeValue.first())
|
||||||
|
if (parameters != null) appendParameters(parameters)
|
||||||
|
}
|
||||||
|
return methodSignatureLookupMap[key] ?: return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] using a list of [MethodClassPair].
|
||||||
|
*
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.resolveUsingMethodClassPair(classMethods: Iterable<MethodClassPair>): Boolean {
|
||||||
|
classMethods.forEach { classAndMethod ->
|
||||||
|
if (resolve(context, classAndMethod.first, classAndMethod.second)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val methodsWithSameStrings = methodStringsLookup()
|
||||||
|
if (methodsWithSameStrings != null) if (resolveUsingMethodClassPair(methodsWithSameStrings)) return true
|
||||||
|
|
||||||
|
// No strings declared or none matched (partial matches are allowed).
|
||||||
|
// Use signature matching.
|
||||||
|
return resolveUsingMethodClassPair(methodSignatureLookup())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||||
|
*
|
||||||
|
* @param classes The classes on which to resolve the [MethodFingerprint] in.
|
||||||
|
* @param context The [BytecodeContext] to host proxies.
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) {
|
||||||
|
for (fingerprint in this) // For each fingerprint...
|
||||||
|
classes@ for (classDef in classes) // ...search through all classes for the MethodFingerprint
|
||||||
|
if (fingerprint.resolve(context, classDef))
|
||||||
|
break@classes // ...if the resolution succeeded, continue with the next MethodFingerprint.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] against a [ClassDef].
|
||||||
|
*
|
||||||
|
* @param forClass The class on which to resolve the [MethodFingerprint] in.
|
||||||
|
* @param context The [BytecodeContext] to host proxies.
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.resolve(context: BytecodeContext, forClass: ClassDef): Boolean {
|
||||||
|
for (method in forClass.methods)
|
||||||
|
if (this.resolve(context, method, forClass))
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] against a [Method].
|
||||||
|
*
|
||||||
|
* @param method The class on which to resolve the [MethodFingerprint] in.
|
||||||
|
* @param forClass The class on which to resolve the [MethodFingerprint].
|
||||||
|
* @param context The [BytecodeContext] to host proxies.
|
||||||
|
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.resolve(context: BytecodeContext, method: Method, forClass: ClassDef): Boolean {
|
||||||
|
val methodFingerprint = this
|
||||||
|
|
||||||
|
if (methodFingerprint.result != null) return true
|
||||||
|
|
||||||
|
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType))
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (methodFingerprint.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags)
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
fun parametersEqual(
|
||||||
|
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
|
||||||
|
): Boolean {
|
||||||
|
if (parameters1.count() != parameters2.count()) return false
|
||||||
|
val iterator1 = parameters1.iterator()
|
||||||
|
parameters2.forEach {
|
||||||
|
if (!it.startsWith(iterator1.next())) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodFingerprint.parameters != null && !parametersEqual(
|
||||||
|
methodFingerprint.parameters, // TODO: parseParameters()
|
||||||
|
method.parameterTypes
|
||||||
|
)
|
||||||
|
) return false
|
||||||
|
|
||||||
|
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
||||||
|
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method, forClass))
|
||||||
|
return false
|
||||||
|
|
||||||
|
val stringsScanResult: StringsScanResult? =
|
||||||
|
if (methodFingerprint.strings != null) {
|
||||||
|
StringsScanResult(
|
||||||
|
buildList {
|
||||||
|
val implementation = method.implementation ?: return false
|
||||||
|
|
||||||
|
val stringsList = methodFingerprint.strings.toMutableList()
|
||||||
|
|
||||||
|
implementation.instructions.forEachIndexed { instructionIndex, instruction ->
|
||||||
|
if (
|
||||||
|
instruction.opcode != Opcode.CONST_STRING &&
|
||||||
|
instruction.opcode != Opcode.CONST_STRING_JUMBO
|
||||||
|
) return@forEachIndexed
|
||||||
|
|
||||||
|
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||||
|
val index = stringsList.indexOfFirst(string::contains)
|
||||||
|
if (index == -1) return@forEachIndexed
|
||||||
|
|
||||||
|
add(
|
||||||
|
StringMatch(
|
||||||
|
string,
|
||||||
|
instructionIndex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
stringsList.removeAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stringsList.isNotEmpty()) return false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else null
|
||||||
|
|
||||||
|
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||||
|
method.implementation?.instructions ?: return false
|
||||||
|
|
||||||
|
fun Method.patternScan(
|
||||||
|
fingerprint: MethodFingerprint
|
||||||
|
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
||||||
|
val instructions = this.implementation!!.instructions
|
||||||
|
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
|
||||||
|
|
||||||
|
val pattern = fingerprint.opcodes!!
|
||||||
|
val instructionLength = instructions.count()
|
||||||
|
val patternLength = pattern.count()
|
||||||
|
|
||||||
|
for (index in 0 until instructionLength) {
|
||||||
|
var patternIndex = 0
|
||||||
|
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||||
|
|
||||||
|
while (index + patternIndex < instructionLength) {
|
||||||
|
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||||
|
val patternOpcode = pattern.elementAt(patternIndex)
|
||||||
|
|
||||||
|
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||||
|
// Reaching maximum threshold (0) means,
|
||||||
|
// the pattern does not match to the current instructions.
|
||||||
|
if (threshold-- == 0) break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patternIndex < patternLength - 1) {
|
||||||
|
// If the entire pattern has not been scanned yet
|
||||||
|
// continue the scan.
|
||||||
|
patternIndex++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
||||||
|
val result =
|
||||||
|
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
|
||||||
|
index,
|
||||||
|
index + patternIndex
|
||||||
|
)
|
||||||
|
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
||||||
|
result.warnings = result.createWarnings(pattern, instructions)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
method.patternScan(methodFingerprint) ?: return false
|
||||||
|
} else null
|
||||||
|
|
||||||
|
methodFingerprint.result = MethodFingerprintResult(
|
||||||
|
method,
|
||||||
|
forClass,
|
||||||
|
MethodFingerprintResult.MethodFingerprintScanResult(
|
||||||
|
patternScanResult,
|
||||||
|
stringsScanResult
|
||||||
|
),
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.createWarnings(
|
||||||
|
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
||||||
|
) = buildList {
|
||||||
|
for ((patternIndex, instructionIndex) in (this@createWarnings.startIndex until this@createWarnings.endIndex).withIndex()) {
|
||||||
|
val originalOpcode = instructions.elementAt(instructionIndex).opcode
|
||||||
|
val patternOpcode = pattern.elementAt(patternIndex)
|
||||||
|
|
||||||
|
if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue
|
||||||
|
|
||||||
|
this.add(
|
||||||
|
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.Warning(
|
||||||
|
originalOpcode,
|
||||||
|
patternOpcode,
|
||||||
|
instructionIndex,
|
||||||
|
patternIndex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the result of a [MethodFingerprintResult].
|
||||||
|
*
|
||||||
|
* @param method The matching method.
|
||||||
|
* @param classDef The [ClassDef] that contains the matching [method].
|
||||||
|
* @param scanResult The result of scanning for the [MethodFingerprint].
|
||||||
|
* @param context The [BytecodeContext] this [MethodFingerprintResult] is attached to, to create proxies.
|
||||||
|
*/
|
||||||
|
data class MethodFingerprintResult(
|
||||||
|
val method: Method,
|
||||||
|
val classDef: ClassDef,
|
||||||
|
val scanResult: MethodFingerprintScanResult,
|
||||||
|
internal val context: BytecodeContext
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Returns a mutable clone of [classDef]
|
||||||
|
*
|
||||||
|
* Please note, this method allocates a [ClassProxy].
|
||||||
|
* Use [classDef] where possible.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
val mutableClass by lazy { context.classes.proxy(classDef).mutableClass }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a mutable clone of [method]
|
||||||
|
*
|
||||||
|
* Please note, this method allocates a [ClassProxy].
|
||||||
|
* Use [method] where possible.
|
||||||
|
*/
|
||||||
|
val mutableMethod by lazy {
|
||||||
|
mutableClass.methods.first {
|
||||||
|
MethodUtil.methodSignaturesMatch(it, this.method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of scanning on the [MethodFingerprint].
|
||||||
|
* @param patternScanResult The result of the pattern scan.
|
||||||
|
* @param stringsScanResult The result of the string scan.
|
||||||
|
*/
|
||||||
|
data class MethodFingerprintScanResult(
|
||||||
|
val patternScanResult: PatternScanResult?,
|
||||||
|
val stringsScanResult: StringsScanResult?
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The result of scanning strings on the [MethodFingerprint].
|
||||||
|
* @param matches The list of strings that were matched.
|
||||||
|
*/
|
||||||
|
data class StringsScanResult(val matches: List<StringMatch>) {
|
||||||
|
/**
|
||||||
|
* Represents a match for a string at an index.
|
||||||
|
* @param string The string that was matched.
|
||||||
|
* @param index The index of the string.
|
||||||
|
*/
|
||||||
|
data class StringMatch(val string: String, val index: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a pattern scan.
|
||||||
|
* @param startIndex The start index of the instructions where to which this pattern matches.
|
||||||
|
* @param endIndex The end index of the instructions where to which this pattern matches.
|
||||||
|
* @param warnings A list of warnings considering this [PatternScanResult].
|
||||||
|
*/
|
||||||
|
data class PatternScanResult(
|
||||||
|
val startIndex: Int,
|
||||||
|
val endIndex: Int,
|
||||||
|
var warnings: List<Warning>? = null
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Represents warnings of the pattern scan.
|
||||||
|
* @param correctOpcode The opcode the instruction list has.
|
||||||
|
* @param wrongOpcode The opcode the pattern list of the signature currently has.
|
||||||
|
* @param instructionIndex The index of the opcode relative to the instruction list.
|
||||||
|
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
|
||||||
|
*/
|
||||||
|
data class Warning(
|
||||||
|
val correctOpcode: Opcode,
|
||||||
|
val wrongOpcode: Opcode,
|
||||||
|
val instructionIndex: Int,
|
||||||
|
val patternIndex: Int,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package app.revanced.patcher.logging
|
||||||
|
|
||||||
|
interface Logger {
|
||||||
|
fun error(msg: String) {}
|
||||||
|
fun warn(msg: String) {}
|
||||||
|
fun info(msg: String) {}
|
||||||
|
fun trace(msg: String) {}
|
||||||
|
|
||||||
|
object Nop : Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn a Patcher [Logger] into an [app.revanced.arsc.logging.Logger].
|
||||||
|
*/
|
||||||
|
internal fun Logger.asArscLogger() = object : app.revanced.arsc.logging.Logger {
|
||||||
|
override fun error(msg: String) = this@asArscLogger.error(msg)
|
||||||
|
override fun warn(msg: String) = this@asArscLogger.warn(msg)
|
||||||
|
override fun info(msg: String) = this@asArscLogger.info(msg)
|
||||||
|
override fun trace(msg: String) = this@asArscLogger.error(msg)
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package app.revanced.patcher.patch
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.BytecodeContext
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.Context
|
||||||
import app.revanced.patcher.data.ResourceContext
|
import app.revanced.patcher.ResourceContext
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
|
|
||||||
@@ -12,21 +12,13 @@ import java.io.Closeable
|
|||||||
* If it implements [Closeable], it will be closed after all patches have been executed.
|
* If it implements [Closeable], it will be closed after all patches have been executed.
|
||||||
* Closing will be done in reverse execution order.
|
* 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.
|
* The main function of the [Patch] which the patcher will call.
|
||||||
*
|
*
|
||||||
* @param context The [Context] the patch will work on.
|
* @param context The [Context] the patch will work on.
|
||||||
* @return The result of executing the patch.
|
|
||||||
*/
|
*/
|
||||||
fun execute(context: @UnsafeVariance T): PatchResult
|
suspend fun execute(context: @UnsafeVariance T)
|
||||||
|
|
||||||
/**
|
|
||||||
* The closing function for this patch.
|
|
||||||
*
|
|
||||||
* This can be treated like popping the patch from the current patch stack.
|
|
||||||
*/
|
|
||||||
override fun close() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,4 +33,10 @@ interface ResourcePatch : Patch<ResourceContext>
|
|||||||
*/
|
*/
|
||||||
abstract class BytecodePatch(
|
abstract class BytecodePatch(
|
||||||
internal val fingerprints: Iterable<MethodFingerprint>? = null
|
internal val fingerprints: Iterable<MethodFingerprint>? = null
|
||||||
) : Patch<BytecodeContext>
|
) : Patch<BytecodeContext>
|
||||||
|
|
||||||
|
// TODO: populate this everywhere where the alias is not used yet
|
||||||
|
/**
|
||||||
|
* The class type of [Patch].
|
||||||
|
*/
|
||||||
|
typealias PatchClass = Class<out Patch<Context>>
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
package app.revanced.patcher.patch
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.pathString
|
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
class NoSuchOptionException(val option: String) : Exception("No such option: $option")
|
class NoSuchOptionException(val option: String) : Exception("No such option: $option")
|
||||||
@@ -229,20 +227,4 @@ sealed class PatchOption<T>(
|
|||||||
) : ListOption<Int>(
|
) : ListOption<Int>(
|
||||||
key, default, options, title, description, required, validator
|
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,24 +1,27 @@
|
|||||||
package app.revanced.patcher.patch.annotations
|
package app.revanced.patcher.patch.annotations
|
||||||
|
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.Context
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import kotlin.reflect.KClass
|
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.
|
* @param include If false, the patch should be treated as optional by default.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Patch(val include: Boolean = true)
|
annotation class Patch(val include: Boolean = true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation for dependencies of [Patch]es.
|
* Annotation for dependencies of [Patch]es.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class DependsOn(
|
annotation class DependsOn(
|
||||||
val dependencies: Array<KClass<out Patch<Context>>> = []
|
val dependencies: Array<KClass<out Patch<Context>>> = [] // TODO: This should be a list of PatchClass instead
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation to mark [Patch]es which depend on integrations.
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class RequiresIntegrations // TODO: Remove this annotation and replace it with a proper system
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package app.revanced.patcher.util
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
import app.revanced.patcher.PatcherContext
|
import app.revanced.patcher.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.or
|
import app.revanced.patcher.extensions.or
|
||||||
import app.revanced.patcher.logging.Logger
|
import app.revanced.patcher.logging.Logger
|
||||||
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
|
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
|
||||||
@@ -32,7 +32,7 @@ internal object ClassMerger {
|
|||||||
* @param context The context to traverse the class hierarchy in.
|
* @param context The context to traverse the class hierarchy in.
|
||||||
* @param logger A logger.
|
* @param logger A logger.
|
||||||
*/
|
*/
|
||||||
fun ClassDef.merge(otherClass: ClassDef, context: PatcherContext, logger: Logger? = null) = this
|
fun ClassDef.merge(otherClass: ClassDef, context: BytecodeContext, logger: Logger? = null) = this
|
||||||
//.fixFieldAccess(otherClass, logger)
|
//.fixFieldAccess(otherClass, logger)
|
||||||
//.fixMethodAccess(otherClass, logger)
|
//.fixMethodAccess(otherClass, logger)
|
||||||
.addMissingFields(otherClass, logger)
|
.addMissingFields(otherClass, logger)
|
||||||
@@ -89,10 +89,10 @@ internal object ClassMerger {
|
|||||||
* @param context The context to traverse the class hierarchy in.
|
* @param context The context to traverse the class hierarchy in.
|
||||||
* @param logger A logger.
|
* @param logger A logger.
|
||||||
*/
|
*/
|
||||||
private fun ClassDef.publicize(reference: ClassDef, context: PatcherContext, logger: Logger? = null) =
|
private fun ClassDef.publicize(reference: ClassDef, context: BytecodeContext, logger: Logger? = null) =
|
||||||
if (reference.accessFlags.isPublic() && !accessFlags.isPublic())
|
if (reference.accessFlags.isPublic() && !accessFlags.isPublic())
|
||||||
this.asMutableClass().apply {
|
this.asMutableClass().apply {
|
||||||
context.bytecodeContext.traverseClassHierarchy(this) {
|
context.traverseClassHierarchy(this) {
|
||||||
if (accessFlags.isPublic()) return@traverseClassHierarchy
|
if (accessFlags.isPublic()) return@traverseClassHierarchy
|
||||||
|
|
||||||
logger?.trace("Publicizing ${this.type}")
|
logger?.trace("Publicizing ${this.type}")
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that represents a list of classes and proxies.
|
||||||
|
*
|
||||||
|
* @param classes The classes to be backed by proxies.
|
||||||
|
*/
|
||||||
|
class ProxyBackedClassList(classes: Set<ClassDef>) : Iterable<ClassDef> {
|
||||||
|
// A list for pending proxied classes to be added to the current ProxyBackedClassList instance.
|
||||||
|
private val proxiedClasses = mutableListOf<ClassProxy>()
|
||||||
|
private val mutableClasses = classes.toMutableList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the [mutableClasses]es with their proxies.
|
||||||
|
*/
|
||||||
|
internal fun applyProxies() {
|
||||||
|
proxiedClasses.removeIf { proxy ->
|
||||||
|
// If the proxy is unused, keep it in the proxiedClasses list.
|
||||||
|
if (!proxy.resolved) return@removeIf false
|
||||||
|
|
||||||
|
with(mutableClasses) {
|
||||||
|
remove(proxy.immutableClass)
|
||||||
|
add(proxy.mutableClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@removeIf true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace a [ClassDef] at a given [index].
|
||||||
|
*
|
||||||
|
* @param index The index of the class to be replaced.
|
||||||
|
* @param classDef The new class to replace the old one.
|
||||||
|
*/
|
||||||
|
operator fun set(index: Int, classDef: ClassDef) {
|
||||||
|
mutableClasses[index] = classDef
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a [ClassDef] at a given [index].
|
||||||
|
*
|
||||||
|
* @param index The index of the class.
|
||||||
|
*/
|
||||||
|
operator fun get(index: Int) = mutableClasses[index]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator for the classes in [ProxyBackedClassList].
|
||||||
|
*
|
||||||
|
* @return The iterator for the classes.
|
||||||
|
*/
|
||||||
|
override fun iterator() = mutableClasses.iterator()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy a [ClassDef].
|
||||||
|
*
|
||||||
|
* Note: This creates a [ClassProxy] of the [ClassDef], if not already present.
|
||||||
|
*
|
||||||
|
* @return A proxy for the given class.
|
||||||
|
*/
|
||||||
|
fun proxy(classDef: ClassDef) = proxiedClasses
|
||||||
|
.find { it.immutableClass.type == classDef.type } ?: ClassProxy(classDef).also(proxiedClasses::add)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a [ClassDef].
|
||||||
|
*/
|
||||||
|
fun add(classDef: ClassDef) = mutableClasses.add(classDef)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 findClassProxied(className: String) = findClassProxied { 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 findClassProxied(predicate: (ClassDef) -> Boolean) = this.find(predicate)?.let(::proxy)
|
||||||
|
|
||||||
|
val size get() = mutableClasses.size
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import app.revanced.patcher.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 The function that is called for every class in the hierarchy.
|
||||||
|
*/
|
||||||
|
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
|
||||||
|
callback(targetClass)
|
||||||
|
this.classes.findClassProxied(targetClass.superclass ?: return)?.mutableClass?.let {
|
||||||
|
traverseClassHierarchy(it, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Deprecated("This class serves no purpose anymore")
|
||||||
|
internal object VersionReader {
|
||||||
|
@JvmStatic
|
||||||
|
private val properties = Properties().apply {
|
||||||
|
load(
|
||||||
|
VersionReader::class.java.getResourceAsStream("/app/revanced/patcher/version.properties")
|
||||||
|
?: throw IllegalStateException("Could not load version.properties")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun read(): String {
|
||||||
|
return properties.getProperty("version") ?: throw IllegalStateException("Version not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package app.revanced.patcher.util.method
|
package app.revanced.patcher.util.method
|
||||||
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.BytecodeContext
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
import org.jf.dexlib2.iface.Method
|
import org.jf.dexlib2.iface.Method
|
||||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
@@ -41,7 +41,7 @@ class MethodWalker internal constructor(
|
|||||||
val instruction = instructions.elementAt(offset)
|
val instruction = instructions.elementAt(offset)
|
||||||
|
|
||||||
val newMethod = (instruction as ReferenceInstruction).reference as MethodReference
|
val newMethod = (instruction as ReferenceInstruction).reference as MethodReference
|
||||||
val proxy = bytecodeContext.findClass(newMethod.definingClass)!!
|
val proxy = bytecodeContext.classes.findClassProxied(newMethod.definingClass)!!
|
||||||
|
|
||||||
val methods = if (walkMutable) proxy.mutableClass.methods else proxy.immutableClass.methods
|
val methods = if (walkMutable) proxy.mutableClass.methods else proxy.immutableClass.methods
|
||||||
currentMethod = methods.first {
|
currentMethod = methods.first {
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package app.revanced.patcher.util.patch
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import app.revanced.patcher.patch.PatchClass
|
||||||
|
import dalvik.system.PathClassLoader
|
||||||
|
import org.jf.dexlib2.DexFileFactory
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.util.jar.JarFile
|
||||||
|
import kotlin.streams.toList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A patch bundle.
|
||||||
|
*
|
||||||
|
* @param fromClasses The classes to get [Patch]es from.
|
||||||
|
*/
|
||||||
|
sealed class PatchBundle private constructor(fromClasses: Iterable<Class<*>>) : Iterable<PatchClass> {
|
||||||
|
private val patches = 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
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iterator() = patches.iterator()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A patch bundle of type [Jar].
|
||||||
|
*
|
||||||
|
* @param patchBundlePath The path to a patch bundle.
|
||||||
|
*/
|
||||||
|
class Jar(private val patchBundlePath: File) : PatchBundle(
|
||||||
|
with(URLClassLoader(arrayOf(patchBundlePath.toURI().toURL()), PatchBundle::class.java.classLoader)) {
|
||||||
|
JarFile(patchBundlePath).stream().filter { it.name.endsWith(".class") }.map {
|
||||||
|
loadClass(
|
||||||
|
it.realName.replace('/', '.').replace(".class", "")
|
||||||
|
)
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A patch bundle of type [Dex] format.
|
||||||
|
*
|
||||||
|
* @param patchBundlePath The path to a patch bundle of dex format.
|
||||||
|
*/
|
||||||
|
class Dex(private val patchBundlePath: File) : PatchBundle(
|
||||||
|
with(PathClassLoader(patchBundlePath.absolutePath, null, PatchBundle::class.java.classLoader)) {
|
||||||
|
DexFileFactory.loadDexFile(patchBundlePath, null).classes.map { classDef ->
|
||||||
|
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||||
|
}.map { loadClass(it) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ class MutableMethod(method: Method) : Method, BaseMethodReference() {
|
|||||||
private var accessFlags = method.accessFlags
|
private var accessFlags = method.accessFlags
|
||||||
private var returnType = method.returnType
|
private var returnType = method.returnType
|
||||||
|
|
||||||
// Create own mutable MethodImplementation (due to not being able to change members like register count)
|
// TODO: Create own mutable MethodImplementation (due to not being able to change members like register count).
|
||||||
private val _implementation by lazy { method.implementation?.let { MutableMethodImplementation(it) } }
|
private val _implementation by lazy { method.implementation?.let { MutableMethodImplementation(it) } }
|
||||||
private val _annotations by lazy { method.annotations.map { annotation -> annotation.toMutable() }.toMutableSet() }
|
private val _annotations by lazy { method.annotations.map { annotation -> annotation.toMutable() }.toMutableSet() }
|
||||||
private val _parameters by lazy { method.parameters.map { parameter -> parameter.toMutable() }.toMutableList() }
|
private val _parameters by lazy { method.parameters.map { parameter -> parameter.toMutable() }.toMutableList() }
|
||||||
@@ -4,7 +4,7 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.
|
|||||||
import org.jf.dexlib2.base.BaseMethodParameter
|
import org.jf.dexlib2.base.BaseMethodParameter
|
||||||
import org.jf.dexlib2.iface.MethodParameter
|
import org.jf.dexlib2.iface.MethodParameter
|
||||||
|
|
||||||
// TODO: finish overriding all members if necessary
|
// TODO: Finish overriding all members if necessary.
|
||||||
class MutableMethodParameter(parameter: MethodParameter) : MethodParameter, BaseMethodParameter() {
|
class MutableMethodParameter(parameter: MethodParameter) : MethodParameter, BaseMethodParameter() {
|
||||||
private var type = parameter.type
|
private var type = parameter.type
|
||||||
private var name = parameter.name
|
private var name = parameter.name
|
||||||
@@ -3,8 +3,6 @@ package app.revanced.patcher.patch
|
|||||||
import app.revanced.patcher.usage.bytecode.ExampleBytecodePatch
|
import app.revanced.patcher.usage.bytecode.ExampleBytecodePatch
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import kotlin.io.path.Path
|
|
||||||
import kotlin.io.path.pathString
|
|
||||||
import kotlin.test.assertNotEquals
|
import kotlin.test.assertNotEquals
|
||||||
|
|
||||||
internal class PatchOptionsTest {
|
internal class PatchOptionsTest {
|
||||||
@@ -35,10 +33,6 @@ internal class PatchOptionsTest {
|
|||||||
println(choice)
|
println(choice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is PatchOption.PathOption -> {
|
|
||||||
option.value = Path("test.txt").pathString
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val option = options.get<String>("key1")
|
val option = options.get<String>("key1")
|
||||||
@@ -9,6 +9,5 @@ import app.revanced.patcher.annotation.Package
|
|||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
internal annotation class ExampleBytecodeCompatibility
|
internal annotation class ExampleBytecodeCompatibility
|
||||||
|
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
package app.revanced.patcher.usage.bytecode
|
package app.revanced.patcher.usage.bytecode
|
||||||
|
|
||||||
|
import app.revanced.patcher.BytecodeContext
|
||||||
import app.revanced.patcher.annotation.Description
|
import app.revanced.patcher.annotation.Description
|
||||||
import app.revanced.patcher.annotation.Name
|
import app.revanced.patcher.annotation.Name
|
||||||
import app.revanced.patcher.annotation.Version
|
import app.revanced.patcher.annotation.Version
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
import app.revanced.patcher.extensions.addInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patcher.extensions.or
|
import app.revanced.patcher.extensions.or
|
||||||
import app.revanced.patcher.extensions.replaceInstruction
|
import app.revanced.patcher.patch.BytecodePatch
|
||||||
import app.revanced.patcher.patch.*
|
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.DependsOn
|
||||||
import app.revanced.patcher.patch.annotations.Patch
|
import app.revanced.patcher.patch.annotations.Patch
|
||||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||||
@@ -29,7 +31,6 @@ import org.jf.dexlib2.immutable.reference.ImmutableFieldReference
|
|||||||
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
import org.jf.dexlib2.immutable.reference.ImmutableStringReference
|
||||||
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
|
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue
|
||||||
import org.jf.dexlib2.util.Preconditions
|
import org.jf.dexlib2.util.Preconditions
|
||||||
import kotlin.io.path.Path
|
|
||||||
|
|
||||||
@Patch
|
@Patch
|
||||||
@Name("example-bytecode-patch")
|
@Name("example-bytecode-patch")
|
||||||
@@ -40,7 +41,7 @@ import kotlin.io.path.Path
|
|||||||
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||||
// This function will be executed by the patcher.
|
// This function will be executed by the patcher.
|
||||||
// You can treat it as a constructor
|
// You can treat it as a constructor
|
||||||
override fun execute(context: BytecodeContext): PatchResult {
|
override suspend fun execute(context: BytecodeContext) {
|
||||||
// Get the resolved method by its fingerprint from the resolver cache
|
// Get the resolved method by its fingerprint from the resolver cache
|
||||||
val result = ExampleFingerprint.result!!
|
val result = ExampleFingerprint.result!!
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
|||||||
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
||||||
|
|
||||||
// Get the class in which the method matching our fingerprint is defined in.
|
// Get the class in which the method matching our fingerprint is defined in.
|
||||||
val mainClass = context.findClass {
|
val mainClass = context.classes.findClassProxied {
|
||||||
it.type == result.classDef.type
|
it.type == result.classDef.type
|
||||||
}!!.mutableClass
|
}!!.mutableClass
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
|||||||
// For this sake of example I reuse the TestClass field dummyField inside the virtual register 0.
|
// 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.
|
// Control flow instructions are not supported as of now.
|
||||||
method.addInstructions(
|
method.addInstructionsWithLabels(
|
||||||
startIndex + 2,
|
startIndex + 2,
|
||||||
"""
|
"""
|
||||||
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
||||||
@@ -127,12 +128,6 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
|||||||
invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
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 +188,5 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
|||||||
"key5", null, "title", "description", true
|
"key5", null, "title", "description", true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
private var key6 by option(
|
|
||||||
PatchOption.PathOption(
|
|
||||||
"key6", Path("test.txt"), "title", "description", true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user