mirror of
https://github.com/ReVanced/revanced-cli.git
synced 2026-01-11 22:06:20 +00:00
Compare commits
145 Commits
v3.0.2-dev
...
v4.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c264572a2 | ||
|
|
63e8585652 | ||
|
|
8f59d94dc7 | ||
|
|
3bcee04a7d | ||
|
|
821035107d | ||
|
|
8becebaa42 | ||
|
|
fe563fff93 | ||
|
|
2d17459fa3 | ||
|
|
16109bd8bc | ||
|
|
09bc652317 | ||
|
|
1d051365f3 | ||
|
|
ab7d9d8e1e | ||
|
|
5e089ea9af | ||
|
|
06c6a97915 | ||
|
|
a974b8ea80 | ||
|
|
89c35ee21b | ||
|
|
41bdb04ab0 | ||
|
|
a5542467c8 | ||
|
|
5fd205f77d | ||
|
|
e7c3d64bf1 | ||
|
|
3765957043 | ||
|
|
5e63e0a276 | ||
|
|
36c6a6a5f7 | ||
|
|
860fb7b957 | ||
|
|
9d96bb7b4c | ||
|
|
3afa590616 | ||
|
|
db0b943ad3 | ||
|
|
8c3d4d3aa1 | ||
|
|
04d312df31 | ||
|
|
158f59ab88 | ||
|
|
a078b53a21 | ||
|
|
fb75b0f115 | ||
|
|
c52383eca0 | ||
|
|
0562ee85c5 | ||
|
|
1cde672a05 | ||
|
|
d5419dbb6b | ||
|
|
d29016b428 | ||
|
|
1eb51e9d4f | ||
|
|
86b8415ac6 | ||
|
|
8f4aebc60b | ||
|
|
bd3171c7c5 | ||
|
|
6481ba68db | ||
|
|
799882e6a6 | ||
|
|
616d14f009 | ||
|
|
b2055ce07d | ||
|
|
4fc42089a5 | ||
|
|
0139dfe0bf | ||
|
|
7d1c0e663f | ||
|
|
6cc6960493 | ||
|
|
9f15ac6ec5 | ||
|
|
6670d89c74 | ||
|
|
a60d07fc5d | ||
|
|
362e6f5904 | ||
|
|
51402ab8a7 | ||
|
|
a8c8682116 | ||
|
|
a86919eb71 | ||
|
|
8b04b6790b | ||
|
|
59dfc988e3 | ||
|
|
b01fef730a | ||
|
|
a564aaa1b5 | ||
|
|
f315485713 | ||
|
|
28648a1c53 | ||
|
|
daac98817f | ||
|
|
9328475e11 | ||
|
|
6507b5fe8a | ||
|
|
349ff5749a | ||
|
|
495735f44c | ||
|
|
f48da18e37 | ||
|
|
cf58f55b38 | ||
|
|
da4469f402 | ||
|
|
df2c137ae9 | ||
|
|
8a2f219f8b | ||
|
|
719d9fcfa3 | ||
|
|
6c44a730c3 | ||
|
|
ba573f73d0 | ||
|
|
e95a392016 | ||
|
|
fc62f7f475 | ||
|
|
ff2e10dbe6 | ||
|
|
7ff433860d | ||
|
|
7121e5fa4c | ||
|
|
23b5e8b7f9 | ||
|
|
d1c4d8df3e | ||
|
|
89acffe788 | ||
|
|
64d9127291 | ||
|
|
e3c55507cf | ||
|
|
64afc95a81 | ||
|
|
022fd230f6 | ||
|
|
a177693ece | ||
|
|
4b6dbffd7b | ||
|
|
78e89dd3bf | ||
|
|
a08530d47b | ||
|
|
eec2234226 | ||
|
|
d5ea5a0ab1 | ||
|
|
6fc3eebc21 | ||
|
|
c84041f942 | ||
|
|
995f2ec99b | ||
|
|
84b24f1b6f | ||
|
|
95a974a5ac | ||
|
|
ee6d883777 | ||
|
|
b65e4083d6 | ||
|
|
e46d855643 | ||
|
|
80a8d88406 | ||
|
|
50c0f98ce5 | ||
|
|
77d91735ff | ||
|
|
3846f721ca | ||
|
|
f199298317 | ||
|
|
592dc1c64a | ||
|
|
8da0c2bdfe | ||
|
|
e8e69aaf5a | ||
|
|
157278c9ba | ||
|
|
049a385473 | ||
|
|
e7c9da7c33 | ||
|
|
67bf8c1849 | ||
|
|
075f6ad565 | ||
|
|
6942b22a68 | ||
|
|
ac5742dd6c | ||
|
|
a022febd0c | ||
|
|
978032cce1 | ||
|
|
52c5259451 | ||
|
|
43fc20d90e | ||
|
|
e3851d58c8 | ||
|
|
3e13fb5d56 | ||
|
|
db50cee12c | ||
|
|
578e16b099 | ||
|
|
1319ab7629 | ||
|
|
2a08af3cb9 | ||
|
|
7794327a11 | ||
|
|
2b77608651 | ||
|
|
d09aca65df | ||
|
|
c8d4f56d61 | ||
|
|
c67e3c70c7 | ||
|
|
05878a6e06 | ||
|
|
29bc0e8df8 | ||
|
|
c4a89e39b9 | ||
|
|
847e384d9e | ||
|
|
96c196dcb1 | ||
|
|
dfd535c201 | ||
|
|
2b6051e7f3 | ||
|
|
0304988733 | ||
|
|
ca809f0948 | ||
|
|
5d50d1a622 | ||
|
|
83c28d9f71 | ||
|
|
93cbcc28aa | ||
|
|
3b3f7c7a7a | ||
|
|
bbb1a63abd |
73
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
73
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
@@ -1,73 +0,0 @@
|
|||||||
name: 🐞 Bug report
|
|
||||||
description: Report a very clearly broken issue.
|
|
||||||
title: 'bug: <title>'
|
|
||||||
labels: [bug]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
# ReVanced bug report
|
|
||||||
|
|
||||||
Important to note that your issue may have already been reported before. Please check for existing issues [here](https://github.com/revanced/revanced-cli/labels/bug).
|
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: Type
|
|
||||||
options:
|
|
||||||
- Error while running the CLI
|
|
||||||
- Error at runtime
|
|
||||||
- Cosmetic
|
|
||||||
- Other
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Bug description
|
|
||||||
description: How did you find the bug? Any additional details that might help?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Steps to reproduce
|
|
||||||
description: Add the steps to reproduce this bug including your environment.
|
|
||||||
placeholder: Step 1. Download some files. Step 2. ...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Relevant log output
|
|
||||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
|
||||||
render: shell
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Screenshots or videos
|
|
||||||
description: Add screenshots or videos that show the bug here.
|
|
||||||
placeholder: Drag and drop the screenshots/videos into this box.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Solution
|
|
||||||
description: If applicable, add a possible solution.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Additional context
|
|
||||||
description: Add additional context here.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: checkboxes
|
|
||||||
id: acknowledgements
|
|
||||||
attributes:
|
|
||||||
label: Acknowledgements
|
|
||||||
description: Your issue will be closed if you haven't done these steps.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
required: true
|
|
||||||
- label: I filled out all of the requested information in this issue properly.
|
|
||||||
required: true
|
|
||||||
110
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
110
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
name: 🐞 Bug report
|
||||||
|
description: Report a bug or an issue.
|
||||||
|
title: 'bug: '
|
||||||
|
labels: ['Bug report']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
<p align="center">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
width="256px"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
srcset="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
width="256px"
|
||||||
|
src="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
|
<br>
|
||||||
|
<a href="https://revanced.app/">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||||
|
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="http://revanced.app/discord">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/app_revanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Continuing the legacy of Vanced
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# ReVanced CLI bug report
|
||||||
|
|
||||||
|
Before creating a new bug report, please keep the following in mind:
|
||||||
|
|
||||||
|
- **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/ReVanced/revanced-cli/labels/Bug%20report).
|
||||||
|
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli/blob/main/CONTRIBUTING.md).
|
||||||
|
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Bug description
|
||||||
|
description: |
|
||||||
|
- Describe your bug in detail
|
||||||
|
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
||||||
|
- Add images and videos if possible
|
||||||
|
- List used patches if applicable
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Error logs
|
||||||
|
description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell.
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Solution
|
||||||
|
description: If applicable, add a possible solution to the bug.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add additional context here.
|
||||||
|
- type: checkboxes
|
||||||
|
id: acknowledgements
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Your bug report will be closed if you don't follow the checklist below.
|
||||||
|
options:
|
||||||
|
- label: This issue is not a duplicate of an existing bug report.
|
||||||
|
required: true
|
||||||
|
- label: I have chosen an appropriate title.
|
||||||
|
required: true
|
||||||
|
- label: All requested information has been provided properly.
|
||||||
|
required: true
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 📃 Documentation
|
|
||||||
url: https://github.com/revanced/revanced-documentation/
|
|
||||||
about: Don't know how or where to start? Check out our documentation!
|
|
||||||
- name: 🗨 Discussions
|
- name: 🗨 Discussions
|
||||||
url: https://github.com/revanced/revanced-suggestions/discussions
|
url: https://github.com/revanced/revanced-suggestions/discussions
|
||||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
about: Have something unspecific to ReVanced CLI in mind? Search for or start a new discussion!
|
||||||
58
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
58
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
@@ -1,58 +0,0 @@
|
|||||||
name: ⭐ Feature request
|
|
||||||
description: Create a detailed feature request.
|
|
||||||
title: 'feat: <title>'
|
|
||||||
labels: [feature-request]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
# ReVanced feature request
|
|
||||||
|
|
||||||
Do not submit requests for patches here. Please submit them [here](https://github.com/orgs/revanced/discussions/categories/patches) instead.
|
|
||||||
Important to note that your feature request may have already been made before. Please check for existing feature requests [here](https://github.com/revanced/revanced-cli/labels/feature-request).
|
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: Type
|
|
||||||
options:
|
|
||||||
- Functionality
|
|
||||||
- Cosmetic
|
|
||||||
- Other
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Issue
|
|
||||||
description: What is the current problem. Why does it require a feature request?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Feature
|
|
||||||
description: Describe your feature in detail. How does it solve the issue?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Motivation
|
|
||||||
description: Why should your feature should be considered?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Additional context
|
|
||||||
description: Add additional context here.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: checkboxes
|
|
||||||
id: acknowledgements
|
|
||||||
attributes:
|
|
||||||
label: Acknowledgements
|
|
||||||
description: Your issue will be closed if you haven't done these steps.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
required: true
|
|
||||||
- label: I filled out all of the requested information in this issue properly.
|
|
||||||
required: true
|
|
||||||
106
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
106
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
name: ⭐ Feature request
|
||||||
|
description: Create a detailed request for a new feature.
|
||||||
|
title: 'feat: '
|
||||||
|
labels: ['Feature request']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
<p align="center">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
width="256px"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
srcset="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
width="256px"
|
||||||
|
src="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
|
<br>
|
||||||
|
<a href="https://revanced.app/">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||||
|
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="http://revanced.app/discord">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/app_revanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Continuing the legacy of Vanced
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# ReVanced CLI feature request
|
||||||
|
|
||||||
|
Before creating a new feature request, please keep the following in mind:
|
||||||
|
|
||||||
|
- **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/ReVanced/revanced-cli//labels/Feature%20request).
|
||||||
|
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli//blob/main/CONTRIBUTING.md).
|
||||||
|
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Feature description
|
||||||
|
description: |
|
||||||
|
- Describe your feature in detail
|
||||||
|
- Add images, videos, links, examples, references, etc. if possible
|
||||||
|
- Add the target application name in case you request a new patch
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Motivation
|
||||||
|
description: |
|
||||||
|
A strong motivation is necessary for a feature request to be considered.
|
||||||
|
|
||||||
|
- Why should this feature be implemented?
|
||||||
|
- What is the explicit use case?
|
||||||
|
- What are the benefits?
|
||||||
|
- What makes this feature important?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: acknowledgements
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Your feature request will be closed if you don't follow the checklist below.
|
||||||
|
options:
|
||||||
|
- label: This issue is not a duplicate of an existing feature request.
|
||||||
|
required: true
|
||||||
|
- label: I have chosen an appropriate title.
|
||||||
|
required: true
|
||||||
|
- label: All requested information has been provided properly.
|
||||||
|
required: true
|
||||||
2
.github/config.yml
vendored
2
.github/config.yml
vendored
@@ -1,2 +1,2 @@
|
|||||||
firstPRMergeComment: >
|
firstPRMergeComment: >
|
||||||
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
|
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution.
|
||||||
|
|||||||
7
.github/workflows/pull_request.yml
vendored
7
.github/workflows/pull_request.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: PR to main
|
name: Open a PR to main
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
MESSAGE: merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pull-request:
|
pull-request:
|
||||||
@@ -15,7 +15,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Open pull request
|
- name: Open pull request
|
||||||
uses: repo-sync/pull-request@v2
|
uses: repo-sync/pull-request@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@@ -17,29 +17,32 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Make sure the release step uses its own credentials:
|
# Make sure the release step uses its own credentials:
|
||||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Cache
|
|
||||||
|
- name: Cache Node modules
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ runner.home }}/.gradle/caches
|
|
||||||
${{ runner.home }}/.gradle/wrapper
|
|
||||||
.gradle
|
|
||||||
build
|
|
||||||
node_modules
|
node_modules
|
||||||
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
|
key: npm-${{ hashFiles('package-lock.json') }}
|
||||||
|
|
||||||
|
- name: Cache Gradle
|
||||||
|
uses: burrunan/gradle-cache-action@v1
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# Cleaning is necessary to avoid uploading two identical artifacts with different versions
|
# Cleaning is necessary to avoid uploading two identical artifacts with different versions
|
||||||
run: ./gradlew clean --no-daemon
|
run: ./gradlew clean --no-daemon
|
||||||
|
|
||||||
- name: Setup semantic-release
|
- name: Setup semantic-release
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||||
|
|||||||
303
CHANGELOG.md
303
CHANGELOG.md
@@ -1,3 +1,306 @@
|
|||||||
|
# [4.4.0](https://github.com/ReVanced/revanced-cli/compare/v4.3.0...v4.4.0) (2023-12-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add missing punctuation in command description ([8210351](https://github.com/ReVanced/revanced-cli/commit/821035107d7264580275f395e9e3fcef91394afd))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Log saved patched APK file path ([16109bd](https://github.com/ReVanced/revanced-cli/commit/16109bd8bc6236debf71cbc8db78fe452b2ed00d))
|
||||||
|
|
||||||
|
# [4.4.0-dev.2](https://github.com/ReVanced/revanced-cli/compare/v4.4.0-dev.1...v4.4.0-dev.2) (2023-12-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add missing punctuation in command description ([8210351](https://github.com/ReVanced/revanced-cli/commit/821035107d7264580275f395e9e3fcef91394afd))
|
||||||
|
|
||||||
|
# [4.4.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.3.0...v4.4.0-dev.1) (2023-12-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Log saved patched APK file path ([16109bd](https://github.com/ReVanced/revanced-cli/commit/16109bd8bc6236debf71cbc8db78fe452b2ed00d))
|
||||||
|
|
||||||
|
# [4.3.0](https://github.com/ReVanced/revanced-cli/compare/v4.2.0...v4.3.0) (2023-12-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `list-versions` command ([a974b8e](https://github.com/ReVanced/revanced-cli/commit/a974b8ea80acd85f8dc472a3f93b8fd7bea08007))
|
||||||
|
|
||||||
|
# [4.3.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.2.0...v4.3.0-dev.1) (2023-11-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `list-versions` command ([a974b8e](https://github.com/ReVanced/revanced-cli/commit/a974b8ea80acd85f8dc472a3f93b8fd7bea08007))
|
||||||
|
|
||||||
|
# [4.2.0](https://github.com/ReVanced/revanced-cli/compare/v4.1.0...v4.2.0) (2023-11-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix typo ([#300](https://github.com/ReVanced/revanced-cli/issues/300)) ([9d96bb7](https://github.com/ReVanced/revanced-cli/commit/9d96bb7b4cc4538da416db50bd689af1a632fc45))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Allow selecting first Adb device, if none supplied automatically by updating dependencies ([e7c3d64](https://github.com/ReVanced/revanced-cli/commit/e7c3d64bf15bf84f3853e7ef699511bf72c13767))
|
||||||
|
* Exit application with CLI exit code ([36c6a6a](https://github.com/ReVanced/revanced-cli/commit/36c6a6a5f75f2e770a7941b3f83f430f62de13de))
|
||||||
|
* Make `--out´ option optional ([3765957](https://github.com/ReVanced/revanced-cli/commit/3765957043989fe7a8932a0c548566a78d04fc41))
|
||||||
|
|
||||||
|
# [4.2.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.1.1-dev.1...v4.2.0-dev.1) (2023-11-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Allow selecting first Adb device, if none supplied automatically by updating dependencies ([e7c3d64](https://github.com/ReVanced/revanced-cli/commit/e7c3d64bf15bf84f3853e7ef699511bf72c13767))
|
||||||
|
* Exit application with CLI exit code ([36c6a6a](https://github.com/ReVanced/revanced-cli/commit/36c6a6a5f75f2e770a7941b3f83f430f62de13de))
|
||||||
|
* Make `--out´ option optional ([3765957](https://github.com/ReVanced/revanced-cli/commit/3765957043989fe7a8932a0c548566a78d04fc41))
|
||||||
|
|
||||||
|
## [4.1.1-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.1.0...v4.1.1-dev.1) (2023-11-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix typo ([#300](https://github.com/ReVanced/revanced-cli/issues/300)) ([9d96bb7](https://github.com/ReVanced/revanced-cli/commit/9d96bb7b4cc4538da416db50bd689af1a632fc45))
|
||||||
|
|
||||||
|
# [4.1.0](https://github.com/ReVanced/revanced-cli/compare/v4.0.2...v4.1.0) (2023-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Include or exclude patches by their index in relation to supplied patch bundles ([b2055ce](https://github.com/ReVanced/revanced-cli/commit/b2055ce07df3ab9a9f3f73ab17d8c2cf02f2ae62))
|
||||||
|
* List patches which are compatible with any app ([#297](https://github.com/ReVanced/revanced-cli/issues/297)) ([0139dfe](https://github.com/ReVanced/revanced-cli/commit/0139dfe0bfa06a13f56dc03e7718aaf644029614))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* Use a `HashSet` to check for included and excluded patches ([616d14f](https://github.com/ReVanced/revanced-cli/commit/616d14f0097c1ee7ba6dc07be417590f6418e8e5))
|
||||||
|
|
||||||
|
# [4.1.0-dev.3](https://github.com/ReVanced/revanced-cli/compare/v4.1.0-dev.2...v4.1.0-dev.3) (2023-11-03)
|
||||||
|
|
||||||
|
# [4.1.0-dev.2](https://github.com/ReVanced/revanced-cli/compare/v4.1.0-dev.1...v4.1.0-dev.2) (2023-11-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Include or exclude patches by their index in relation to supplied patch bundles ([b2055ce](https://github.com/ReVanced/revanced-cli/commit/b2055ce07df3ab9a9f3f73ab17d8c2cf02f2ae62))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* Use a `HashSet` to check for included and excluded patches ([616d14f](https://github.com/ReVanced/revanced-cli/commit/616d14f0097c1ee7ba6dc07be417590f6418e8e5))
|
||||||
|
|
||||||
|
# [4.1.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.0.3-dev.2...v4.1.0-dev.1) (2023-11-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* List patches which are compatible with any app ([#297](https://github.com/ReVanced/revanced-cli/issues/297)) ([0139dfe](https://github.com/ReVanced/revanced-cli/commit/0139dfe0bfa06a13f56dc03e7718aaf644029614))
|
||||||
|
|
||||||
|
## [4.0.3-dev.2](https://github.com/ReVanced/revanced-cli/compare/v4.0.3-dev.1...v4.0.3-dev.2) (2023-10-30)
|
||||||
|
|
||||||
|
## [4.0.3-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.0.2...v4.0.3-dev.1) (2023-10-23)
|
||||||
|
|
||||||
|
## [4.0.2](https://github.com/ReVanced/revanced-cli/compare/v4.0.1...v4.0.2) (2023-10-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Move file to output even when mounting ([59dfc98](https://github.com/ReVanced/revanced-cli/commit/59dfc988e351374eb718923d19bd9bdd94e8d3c0))
|
||||||
|
* Use punctuation in option descriptions ([da4469f](https://github.com/ReVanced/revanced-cli/commit/da4469f402c26ad95898404236b0acf0202907e2))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* Use multiple threads for writing dex files ([28648a1](https://github.com/ReVanced/revanced-cli/commit/28648a1c53520eef8c799504ff61a35947f878b8))
|
||||||
|
|
||||||
|
## [4.0.2-dev.3](https://github.com/ReVanced/revanced-cli/compare/v4.0.2-dev.2...v4.0.2-dev.3) (2023-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Move file to output even when mounting ([59dfc98](https://github.com/ReVanced/revanced-cli/commit/59dfc988e351374eb718923d19bd9bdd94e8d3c0))
|
||||||
|
|
||||||
|
## [4.0.2-dev.2](https://github.com/ReVanced/revanced-cli/compare/v4.0.2-dev.1...v4.0.2-dev.2) (2023-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* Use multiple threads for writing dex files ([28648a1](https://github.com/ReVanced/revanced-cli/commit/28648a1c53520eef8c799504ff61a35947f878b8))
|
||||||
|
|
||||||
|
## [4.0.2-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.0.1...v4.0.2-dev.1) (2023-10-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Use punctuation in option descriptions ([da4469f](https://github.com/ReVanced/revanced-cli/commit/da4469f402c26ad95898404236b0acf0202907e2))
|
||||||
|
|
||||||
|
## [4.0.1](https://github.com/ReVanced/revanced-cli/compare/v4.0.0...v4.0.1) (2023-10-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Correct warning message ([ba573f7](https://github.com/ReVanced/revanced-cli/commit/ba573f73d0e310fdeb8be2831fd40a7188473fff))
|
||||||
|
|
||||||
|
## [4.0.1-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.0.0...v4.0.1-dev.1) (2023-10-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Correct warning message ([e4e339d](https://github.com/ReVanced/revanced-cli/commit/e4e339dff40542d6265de59496545c859312cf11))
|
||||||
|
|
||||||
|
# [4.0.0](https://github.com/ReVanced/revanced-cli/compare/v3.1.1...v4.0.0) (2023-10-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Check, if mounting is possible ([3e13fb5](https://github.com/ReVanced/revanced-cli/commit/3e13fb5d56eb2a90c2a4a1ddfc05852b1f70add5))
|
||||||
|
* Delete temporal files if it exists ([a022feb](https://github.com/ReVanced/revanced-cli/commit/a022febd0c70807ddc3fa9948207a2a70d5191da))
|
||||||
|
* Do not sign if mounting ([578e16b](https://github.com/ReVanced/revanced-cli/commit/578e16b099fddfd2bb56accb225d04dfcd409b0c))
|
||||||
|
* Filter logs correctly ([43fc20d](https://github.com/ReVanced/revanced-cli/commit/43fc20d90e0a694b231b17bb7d9ecfa22bb5d9a0))
|
||||||
|
* Log correct options command ([#262](https://github.com/ReVanced/revanced-cli/issues/262)) ([96c196d](https://github.com/ReVanced/revanced-cli/commit/96c196dcb14e37ad91b751af61ee8382547c1ca3))
|
||||||
|
* Log logs with levels over warning to error output stream ([075f6ad](https://github.com/ReVanced/revanced-cli/commit/075f6ad56528a667dca1f0bed704cf7e5381026f))
|
||||||
|
* Only open files for reading and writing if writeable ([3846f72](https://github.com/ReVanced/revanced-cli/commit/3846f721ca015ab39a7e4b8d3f3d61163a6f1650))
|
||||||
|
* Only set options for filtered patches ([64d9127](https://github.com/ReVanced/revanced-cli/commit/64d9127291ea9a8abe21a0e82721721495094472))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add function to get the most common compatible version ([77d9173](https://github.com/ReVanced/revanced-cli/commit/77d91735ffbbd6e733f08176f535bfd39ece0c29))
|
||||||
|
* Add option to filter patches to be listed by package name ([50c0f98](https://github.com/ReVanced/revanced-cli/commit/50c0f98ce5927e07839437a2e550aa85f5a7e62d))
|
||||||
|
* Add option to warn about patches not being found in supplied patch bundles ([e46d855](https://github.com/ReVanced/revanced-cli/commit/e46d85564320f46c6faa54b2d3fa7fca3fa60019))
|
||||||
|
* Add ReVanced Library subproject ([#265](https://github.com/ReVanced/revanced-cli/issues/265)) ([157278c](https://github.com/ReVanced/revanced-cli/commit/157278c9ba25f0f786c5fe58e3e23f6890107118))
|
||||||
|
* Do not format patch names ([80a8d88](https://github.com/ReVanced/revanced-cli/commit/80a8d88406b2b04d13dca4fb0d7d7d62e1910317))
|
||||||
|
* Extend signing API ([592dc1c](https://github.com/ReVanced/revanced-cli/commit/592dc1c64ae4078e73bb71eba11380b301c79dea))
|
||||||
|
* Improve option descriptions ([d5ea5a0](https://github.com/ReVanced/revanced-cli/commit/d5ea5a0ab1cc015730063e5be94ee18bd88cc906))
|
||||||
|
* Log stacktrace in new line ([c67e3c7](https://github.com/ReVanced/revanced-cli/commit/c67e3c70c7eaa514cde1bebe775a2216bc4a6074))
|
||||||
|
* Use ReVanced Library in ReVanced CLI ([7794327](https://github.com/ReVanced/revanced-cli/commit/7794327a11e8a0e0f28176cd45fad797b924c45f))
|
||||||
|
* Word log message better ([6942b22](https://github.com/ReVanced/revanced-cli/commit/6942b22a68de5e991987ea89882915917aec93a3))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* Do not check, if the options file exists twice ([e3c5550](https://github.com/ReVanced/revanced-cli/commit/e3c55507cf52e697b9ce9d59decc1cbe4cfe5b43))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This changes many signatures of existing APIs and adds new functions for signing
|
||||||
|
* This changes the log handler signature
|
||||||
|
|
||||||
|
# [4.0.0-dev.5](https://github.com/ReVanced/revanced-cli/compare/v4.0.0-dev.4...v4.0.0-dev.5) (2023-10-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Only set options for filtered patches ([64d9127](https://github.com/ReVanced/revanced-cli/commit/64d9127291ea9a8abe21a0e82721721495094472))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* Do not check, if the options file exists twice ([e3c5550](https://github.com/ReVanced/revanced-cli/commit/e3c55507cf52e697b9ce9d59decc1cbe4cfe5b43))
|
||||||
|
|
||||||
|
# [4.0.0-dev.4](https://github.com/ReVanced/revanced-cli/compare/v4.0.0-dev.3...v4.0.0-dev.4) (2023-10-01)
|
||||||
|
|
||||||
|
# [4.0.0-dev.3](https://github.com/ReVanced/revanced-cli/compare/v4.0.0-dev.2...v4.0.0-dev.3) (2023-09-27)
|
||||||
|
|
||||||
|
# [4.0.0-dev.2](https://github.com/ReVanced/revanced-cli/compare/v4.0.0-dev.1...v4.0.0-dev.2) (2023-09-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Improve option descriptions ([d5ea5a0](https://github.com/ReVanced/revanced-cli/commit/d5ea5a0ab1cc015730063e5be94ee18bd88cc906))
|
||||||
|
|
||||||
|
# [4.0.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.1.2-dev.1...v4.0.0-dev.1) (2023-09-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Check, if mounting is possible ([3e13fb5](https://github.com/ReVanced/revanced-cli/commit/3e13fb5d56eb2a90c2a4a1ddfc05852b1f70add5))
|
||||||
|
* Delete temporal files if it exists ([a022feb](https://github.com/ReVanced/revanced-cli/commit/a022febd0c70807ddc3fa9948207a2a70d5191da))
|
||||||
|
* Do not sign if mounting ([578e16b](https://github.com/ReVanced/revanced-cli/commit/578e16b099fddfd2bb56accb225d04dfcd409b0c))
|
||||||
|
* Filter logs correctly ([43fc20d](https://github.com/ReVanced/revanced-cli/commit/43fc20d90e0a694b231b17bb7d9ecfa22bb5d9a0))
|
||||||
|
* Log logs with levels over warning to error output stream ([075f6ad](https://github.com/ReVanced/revanced-cli/commit/075f6ad56528a667dca1f0bed704cf7e5381026f))
|
||||||
|
* Only open files for reading and writing if writeable ([3846f72](https://github.com/ReVanced/revanced-cli/commit/3846f721ca015ab39a7e4b8d3f3d61163a6f1650))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add function to get the most common compatible version ([77d9173](https://github.com/ReVanced/revanced-cli/commit/77d91735ffbbd6e733f08176f535bfd39ece0c29))
|
||||||
|
* Add option to filter patches to be listed by package name ([50c0f98](https://github.com/ReVanced/revanced-cli/commit/50c0f98ce5927e07839437a2e550aa85f5a7e62d))
|
||||||
|
* Add option to warn about patches not being found in supplied patch bundles ([e46d855](https://github.com/ReVanced/revanced-cli/commit/e46d85564320f46c6faa54b2d3fa7fca3fa60019))
|
||||||
|
* Add ReVanced Library subproject ([#265](https://github.com/ReVanced/revanced-cli/issues/265)) ([157278c](https://github.com/ReVanced/revanced-cli/commit/157278c9ba25f0f786c5fe58e3e23f6890107118))
|
||||||
|
* Do not format patch names ([80a8d88](https://github.com/ReVanced/revanced-cli/commit/80a8d88406b2b04d13dca4fb0d7d7d62e1910317))
|
||||||
|
* Extend signing API ([592dc1c](https://github.com/ReVanced/revanced-cli/commit/592dc1c64ae4078e73bb71eba11380b301c79dea))
|
||||||
|
* Log stacktrace in new line ([c67e3c7](https://github.com/ReVanced/revanced-cli/commit/c67e3c70c7eaa514cde1bebe775a2216bc4a6074))
|
||||||
|
* Use ReVanced Library in ReVanced CLI ([7794327](https://github.com/ReVanced/revanced-cli/commit/7794327a11e8a0e0f28176cd45fad797b924c45f))
|
||||||
|
* Word log message better ([6942b22](https://github.com/ReVanced/revanced-cli/commit/6942b22a68de5e991987ea89882915917aec93a3))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This changes many signatures of existing APIs and adds new functions for signing
|
||||||
|
* This changes the log handler signature
|
||||||
|
|
||||||
|
# [3.2.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.1.2-dev.1...v3.2.0-dev.1) (2023-09-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Log stacktrace in new line ([c67e3c7](https://github.com/ReVanced/revanced-cli/commit/c67e3c70c7eaa514cde1bebe775a2216bc4a6074))
|
||||||
|
|
||||||
|
## [3.1.2-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.1.1...v3.1.2-dev.1) (2023-09-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Log correct options command ([#262](https://github.com/ReVanced/revanced-cli/issues/262)) ([96c196d](https://github.com/ReVanced/revanced-cli/commit/96c196dcb14e37ad91b751af61ee8382547c1ca3))
|
||||||
|
|
||||||
|
## [3.1.1](https://github.com/ReVanced/revanced-cli/compare/v3.1.0...v3.1.1) (2023-09-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Create options if it does not exist when updating them ([ca809f0](https://github.com/ReVanced/revanced-cli/commit/ca809f0948379e3a825f24d7a49aba8b6b8767d1))
|
||||||
|
|
||||||
|
## [3.1.1-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.1.0...v3.1.1-dev.1) (2023-09-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Create options if it does not exist when updating them ([ca809f0](https://github.com/ReVanced/revanced-cli/commit/ca809f0948379e3a825f24d7a49aba8b6b8767d1))
|
||||||
|
|
||||||
|
# [3.1.0](https://github.com/ReVanced/revanced-cli/compare/v3.0.1...v3.1.0) (2023-08-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* check for package compatibility at first ([9fe5a0b](https://github.com/ReVanced/revanced-cli/commit/9fe5a0b6d93304f630436ed0e954723d9a27b0f6))
|
||||||
|
* do not filter explicitly included patches ([a3d8f00](https://github.com/ReVanced/revanced-cli/commit/a3d8f004ec405f696d99d96c74ca41b573ecf425))
|
||||||
|
* format patches input ([bbb1a63](https://github.com/ReVanced/revanced-cli/commit/bbb1a63abd80dcbecdcf362158c0429cf3e6318f))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Simplify command description ([3b3f7c7](https://github.com/ReVanced/revanced-cli/commit/3b3f7c7a7a7b2795e3d1fad776f6b457f2e68c7b))
|
||||||
|
|
||||||
|
# [3.1.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v3.0.2-dev.2...v3.1.0-dev.1) (2023-08-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* format patches input ([bbb1a63](https://github.com/ReVanced/revanced-cli/commit/bbb1a63abd80dcbecdcf362158c0429cf3e6318f))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Simplify command description ([3b3f7c7](https://github.com/ReVanced/revanced-cli/commit/3b3f7c7a7a7b2795e3d1fad776f6b457f2e68c7b))
|
||||||
|
|
||||||
## [3.0.2-dev.2](https://github.com/ReVanced/revanced-cli/compare/v3.0.2-dev.1...v3.0.2-dev.2) (2023-08-28)
|
## [3.0.2-dev.2](https://github.com/ReVanced/revanced-cli/compare/v3.0.2-dev.1...v3.0.2-dev.2) (2023-08-28)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
98
CONTRIBUTING.md
Normal file
98
CONTRIBUTING.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<p align="center">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
width="256px"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
width="256px"
|
||||||
|
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
|
<br>
|
||||||
|
<a href="https://revanced.app/">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||||
|
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="http://revanced.app/discord">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/app_revanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Continuing the legacy of Vanced
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# 👋 Contribution guidelines
|
||||||
|
|
||||||
|
This document describes how to contribute to ReVanced CLI.
|
||||||
|
|
||||||
|
## 📖 Resources to help you get started
|
||||||
|
|
||||||
|
* The [documentation](/docs) explains how to use ReVanced CLI
|
||||||
|
* [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
|
||||||
|
* [Issues](https://github.com/ReVanced/revanced-cli/issues) are where we keep track of bugs and feature requests
|
||||||
|
|
||||||
|
## 🙏 Submitting a feature request
|
||||||
|
|
||||||
|
Features can be requested by opening an issue using the
|
||||||
|
[Feature request issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Feature+request&projects=&template=feature-request.yml&title=feat%3A+).
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced CLI.
|
||||||
|
> Good motivation has to be provided for a request to be accepted.
|
||||||
|
|
||||||
|
## 🐞 Submitting a bug report
|
||||||
|
|
||||||
|
If you encounter a bug while using ReVanced CLI, open an issue using the
|
||||||
|
[Bug report issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Bug+report&projects=&template=bug-report.yml&title=bug%3A+).
|
||||||
|
|
||||||
|
## 📝 How to contribute
|
||||||
|
|
||||||
|
1. Before contributing, it is recommended to open an issue to discuss your change
|
||||||
|
with the maintainers of ReVanced CLI. This will help you determine whether your change is acceptable
|
||||||
|
and whether it is worth your time to implement it
|
||||||
|
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
|
||||||
|
3. Commit your changes.
|
||||||
|
4. Submit a pull request to the `dev` branch of the repository and reference issues
|
||||||
|
that your pull request closes in the description of your pull request
|
||||||
|
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
|
||||||
|
it will be merged into the `dev` branch and will be included in the next release of ReVanced CLI
|
||||||
|
|
||||||
|
❤️ Thank you for considering contributing to ReVanced CLI,
|
||||||
|
ReVanced
|
||||||
90
README.md
90
README.md
@@ -1,3 +1,93 @@
|
|||||||
|
<p align="center">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
width="256px"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
width="256px"
|
||||||
|
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
|
<br>
|
||||||
|
<a href="https://revanced.app/">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||||
|
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="http://revanced.app/discord">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/app_revanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Continuing the legacy of Vanced
|
||||||
|
</p>
|
||||||
|
|
||||||
# 💻 ReVanced CLI
|
# 💻 ReVanced CLI
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
Command line application to use ReVanced.
|
Command line application to use ReVanced.
|
||||||
|
|
||||||
|
## ❓ About
|
||||||
|
|
||||||
|
ReVanced CLI is a command line application to patch apps using ReVanced.
|
||||||
|
ReVanced CLI also comes with commands to uninstall or install patched apps and list patches from supplied patch bundles.
|
||||||
|
|
||||||
|
## 🚀 Download
|
||||||
|
|
||||||
|
You can download the most recent version of ReVanced CLI from
|
||||||
|
[here](https://github.com/ReVanced/revanced-cli/releases/latest). Learn how to use ReVanced CLI by following the [documentation](/docs).
|
||||||
|
|
||||||
|
## 📚 Everything else
|
||||||
|
|
||||||
|
### 📙 Contributing
|
||||||
|
|
||||||
|
Thank you for considering contributing to ReVanced CLI.
|
||||||
|
You can find the contribution guidelines [here](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
### 🛠️ Building
|
||||||
|
|
||||||
|
In order to build ReVanced CLI, you can follow the [documentation](/docs).
|
||||||
|
|
||||||
|
## 📜 Licence
|
||||||
|
|
||||||
|
ReVanced CLI is licensed under the GPLv3 licence. Please see the [licence file](LICENSE) for more information.
|
||||||
|
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced CLI as long as you track changes/dates in source files.
|
||||||
|
Any modifications to ReVanced CLI must also be made available under the GPL along with build & install instructions.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
1
assets/revanced-logo/revanced-logo.svg
Normal file
1
assets/revanced-logo/revanced-logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Logo"><g id="Ring"><circle id="Ring-Background" serif:id="Ring Background" cx="400" cy="400" r="400" style="fill:#1b1b1b;"/><path id="Ring1" serif:id="Ring" d="M400,0c220.766,0 400,179.234 400,400c-0,220.766 -179.234,400 -400,400c-220.766,-0 -400,-179.234 -400,-400c0,-220.766 179.234,-400 400,-400Zm-0,36c200.897,-0 364,163.103 364,364c0,200.897 -163.103,364 -364,364c-200.897,0 -364,-163.103 -364,-364c-0,-200.897 163.103,-364 364,-364Z" style="fill:url(#_Linear1);"/></g><g id="Shape"><path id="V-Shape" serif:id="V Shape" d="M538.74,269.872c1.481,-3.382 1.157,-7.283 -0.863,-10.373c-2.021,-3.091 -5.464,-4.954 -9.156,-4.954c-5.148,0 -10.435,0 -14.165,0c-3.1,0 -5.907,1.834 -7.153,4.672c-12.468,28.396 -78.273,178.273 -100.25,228.328c-1.246,2.838 -4.053,4.671 -7.154,4.671c-3.1,0 -5.907,-1.833 -7.153,-4.671c-21.977,-50.055 -87.782,-199.932 -100.25,-228.328c-1.246,-2.838 -4.053,-4.672 -7.153,-4.672c-3.73,0 -9.017,0 -14.164,0c-3.693,0 -7.135,1.863 -9.156,4.954c-2.02,3.09 -2.344,6.991 -0.863,10.373c23.557,53.766 101.872,232.519 117.871,269.034c1.743,3.979 5.674,6.549 10.018,6.549c6.293,-0 15.408,-0 21.701,-0c4.344,-0 8.275,-2.57 10.018,-6.549c15.999,-36.515 94.315,-215.268 117.872,-269.034Z" style="fill:#fff;"/><path id="Diamond" d="M408.119,395.312c-1.675,2.901 -4.77,4.688 -8.119,4.688c-3.349,-0 -6.444,-1.787 -8.119,-4.688c-16.997,-29.44 -56.156,-97.264 -73.153,-126.704c-1.675,-2.901 -1.675,-6.474 0,-9.375c1.675,-2.901 4.77,-4.688 8.119,-4.688c33.995,0 112.311,0 146.306,0c3.349,0 6.444,1.787 8.119,4.688c1.675,2.901 1.675,6.474 -0,9.375c-16.997,29.44 -56.156,97.264 -73.153,126.704Z" style="fill:url(#_Linear2);"/></g></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.89859e-14,800,-800,4.89859e-14,400.001,3.31681e-10)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.77155e-14,289.317,-282.535,1.73003e-14,400,254.545)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient></defs></svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1,19 +1,23 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.8.20"
|
kotlin("jvm") version "1.9.10"
|
||||||
alias(libs.plugins.shadow)
|
alias(libs.plugins.shadow)
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "app.revanced"
|
group = "app.revanced"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
|
google()
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.revanced.patcher)
|
implementation(libs.revanced.patcher)
|
||||||
implementation(libs.kotlin.reflect)
|
implementation(libs.revanced.library)
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation(libs.picocli)
|
implementation(libs.picocli)
|
||||||
implementation(libs.jadb) // Updated fork
|
|
||||||
implementation(libs.apksig)
|
|
||||||
implementation(libs.bcpkix.jdk15on)
|
|
||||||
implementation(libs.jackson.module.kotlin)
|
|
||||||
testImplementation(libs.kotlin.test)
|
testImplementation(libs.kotlin.test)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,12 +50,21 @@ tasks {
|
|||||||
dependsOn(shadowJar)
|
dependsOn(shadowJar)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dummy task to fix the Gradle semantic-release plugin.
|
/*
|
||||||
// Remove this if you forked it to support building only.
|
Dummy task to hack gradle-semantic-release-plugin to release this project.
|
||||||
// Tracking issue: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
|
||||||
|
Explanation:
|
||||||
|
SemVer is a standard for versioning libraries.
|
||||||
|
For that reason the semantic-release plugin uses the "publish" task to publish libraries.
|
||||||
|
However, this subproject is not a library, and the "publish" task is not available for this subproject.
|
||||||
|
Because semantic-release is not designed to handle this case, we need to hack it.
|
||||||
|
|
||||||
|
RE: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
||||||
|
*/
|
||||||
|
|
||||||
register<DefaultTask>("publish") {
|
register<DefaultTask>("publish") {
|
||||||
group = "publish"
|
group = "publishing"
|
||||||
description = "Dummy task"
|
description = "Dummy task to hack gradle-semantic-release-plugin to release ReVanced CLI"
|
||||||
dependsOn(build)
|
dependsOn(build)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
# 💼 Prerequisites
|
# 💼 Prerequisites
|
||||||
|
|
||||||
To use ReVanced CLI, you will need to fulfil specific requirements.
|
To use ReVanced CLI, you will need to fulfill specific requirements.
|
||||||
|
|
||||||
## 🤝 Requirements
|
## 🤝 Requirements
|
||||||
|
|
||||||
- Java SDK 11 (Azul Zulu JDK or OpenJDK)
|
- Java Runtime Environment 11 (Azul Zulu JRE or OpenJDK)
|
||||||
- [Android Debug Bridge (adb)](https://developer.android.com/studio/command-line/adb) if you want to install the patched APK file on your device
|
- [Android Debug Bridge (ADB)](https://developer.android.com/studio/command-line/adb) if you want to install the patched APK file on your device
|
||||||
- An ABI other than ARMv7 such as x86 or x86-64 (or a custom AAPT binary that supports ARMv7)
|
- An ABI other than ARMv7 such as x86 or x86-64 (or a custom AAPT binary that supports ARMv7)
|
||||||
- ReVanced Patches
|
|
||||||
- ReVanced Integrations, if the patches require it
|
|
||||||
|
|
||||||
## ⏭️ Whats next
|
## ⏭️ Whats next
|
||||||
|
|
||||||
|
|||||||
147
docs/1_usage.md
147
docs/1_usage.md
@@ -2,36 +2,17 @@
|
|||||||
|
|
||||||
Learn how to ReVanced CLI.
|
Learn how to ReVanced CLI.
|
||||||
|
|
||||||
## ⚡ Setup ADB
|
## 🔨 Usage
|
||||||
|
|
||||||
1. Ensure that ADB is working
|
ReVanced CLI is divided into the following fundamental commands:
|
||||||
|
|
||||||
```bash
|
- ### 🚀 Show all available options for ReVanced CLI
|
||||||
adb shell exit
|
|
||||||
```
|
|
||||||
|
|
||||||
Optionally, you can install the patched APK file on your device by mounting it on top of the original APK file.
|
|
||||||
You will need root permissions for this. Check if you have root permissions by running the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb shell su -c exit
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Get your device's serial
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb devices
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔨 Using ReVanced CLI
|
|
||||||
|
|
||||||
- ### ⚙️ Show all available options for ReVanced CLI
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java -jar revanced-cli.jar -h
|
java -jar revanced-cli.jar -h
|
||||||
```
|
```
|
||||||
|
|
||||||
- ### 📃 List patches from supplied patch bundles
|
- ### 📃 List patches
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java -jar revanced-cli.jar list-patches \
|
java -jar revanced-cli.jar list-patches \
|
||||||
@@ -41,71 +22,115 @@ Learn how to ReVanced CLI.
|
|||||||
revanced-patches.jar [<patch-bundle> ...]
|
revanced-patches.jar [<patch-bundle> ...]
|
||||||
```
|
```
|
||||||
|
|
||||||
- ### ⚙️ Generate options from patches using ReVanced CLI
|
- ### ⚙️ Generate options
|
||||||
|
|
||||||
This will generate an `options.json` file for the patches from a list of supplied patch bundles.
|
This will generate an `options.json` file for the patches from a list of supplied patch bundles.
|
||||||
The file can be supplied to ReVanced CLI later on.
|
The file can be supplied to ReVanced CLI later on.
|
||||||
|
|
||||||
- ```bash
|
```bash
|
||||||
java -jar revanced-cli.jar options \
|
java -jar revanced-cli.jar options \
|
||||||
--path options.json \
|
--path options.json \
|
||||||
--overwrite \
|
--overwrite \
|
||||||
revanced-patches.jar [<patch-bundle> ...]
|
revanced-patches.jar [<patch-bundle> ...]
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**: A default `options.json` file will be automatically generated, if it does not exist
|
> [!NOTE]
|
||||||
without any need for intervention when using the `patch` command.
|
> A default `options.json` file will be automatically created if it does not exist
|
||||||
|
without any need for intervention when using the `patch` command.
|
||||||
|
|
||||||
- ### 💉 Use ReVanced CLI to patch an APK file but install without root permissions
|
- ### 💉 Patch an app
|
||||||
|
|
||||||
This will install the patched APK file regularly on your device.
|
You can patch apps by supplying patch bundles and the app to patch.
|
||||||
|
After patching, ReVanced CLI can install the patched app on your device using two methods:
|
||||||
|
|
||||||
```bash
|
> [!NOTE]
|
||||||
java -jar revanced-cli.jar patch \
|
> For ReVanced CLI to be able to install the patched app on your device, make sure ADB is working:
|
||||||
--patch-bundle revanced-patches.jar \
|
>
|
||||||
--out output.apk \
|
> ```bash
|
||||||
--device-serial <device-serial> \
|
> adb shell exit
|
||||||
input.apk
|
> ```
|
||||||
```
|
>
|
||||||
|
> To get your device's serial, run the following command:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> adb devices
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> If you want to mount the patched app on top of the un-patched app, make sure you have root permissions:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> adb shell su -c exit
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
|
||||||
- ### 👾 Use ReVanced CLI to patch an APK file but install with root permissions
|
> [!WARNING]
|
||||||
|
> Some patches may require integrations
|
||||||
|
> such as [ReVanced Integrations](https://github.com/revanced/revanced-integrations).
|
||||||
|
> Supply them with the option `--merge`. ReVanced Patcher will automatically determine if they are necessary.
|
||||||
|
|
||||||
This will install the patched APK file on your device by mounting it on top of the original APK file.
|
- #### 👾 Patch an app and install it on your device regularly
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb install input.apk
|
java -jar revanced-cli.jar patch \
|
||||||
java -jar revanced-cli.jar patch \
|
--patch-bundle revanced-patches.jar \
|
||||||
--patch-bundle revanced-patches.jar \
|
--device-serial <device-serial> \
|
||||||
--include some-other-patch \
|
input.apk
|
||||||
--exclude some-patch \
|
```
|
||||||
--out patched-output.apk \
|
|
||||||
--device-serial <device-serial> \
|
|
||||||
--mount \
|
|
||||||
input.apk
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**: Some patches may require integrations
|
- #### 👾 Patch an app and mount it on top of the un-patched app with root permissions
|
||||||
such as [ReVanced Integrations](https://github.com/revanced/revanced-integrations).
|
|
||||||
Supply them with the option `--merge`. If any patches accepted by ReVanced Patcher require ReVanced Integrations,
|
> [!IMPORTANT]
|
||||||
they will be merged into the APK file automatically.
|
> Ensure that the same app you are patching and mounting over is installed on your device:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> adb install app.apk
|
||||||
|
> ```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> You can use the option `--ii` to include or `--ie` to exclude
|
||||||
|
> patches by their index in relation to supplied patch bundles,
|
||||||
|
> similarly to the option `--include` and `--exclude`.
|
||||||
|
>
|
||||||
|
> This is useful in case two patches have the same name, and you must include or exclude one.
|
||||||
|
> The patch index is calculated by the position of the patch in the list of patches
|
||||||
|
> from patch bundles supplied using the option `--patch-bundle`.
|
||||||
|
>
|
||||||
|
> You can list all patches with their indices using the command `list-patches`.
|
||||||
|
>
|
||||||
|
> Keep in mind that the indices can change based on the order of the patch bundles supplied,
|
||||||
|
> as well if the patch bundles are updated because patches can be added or removed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
java -jar revanced-cli.jar patch \
|
||||||
|
--patch-bundle revanced-patches.jar \
|
||||||
|
--include "Some patch" \
|
||||||
|
--ii 123 \
|
||||||
|
--exclude "Some other patch" \
|
||||||
|
--device-serial <device-serial> \
|
||||||
|
--mount \
|
||||||
|
app.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
- ### 🗑️ Uninstall an app
|
||||||
|
|
||||||
- ### 🗑️ Uninstall a patched APK file
|
|
||||||
```bash
|
```bash
|
||||||
java -jar revanced-cli.jar utility uninstall \
|
java -jar revanced-cli.jar utility uninstall \
|
||||||
--package-name <package-name> \
|
--package-name <package-name> \
|
||||||
<device-serial>
|
[<device-serial>]
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**: You can unmount an APK file
|
> [!NOTE]
|
||||||
with the option `--unmount`.
|
> You can unmount an APK file
|
||||||
|
by adding the option `--unmount`.
|
||||||
|
|
||||||
- ### ️ ⚙️ Manually install an APK file
|
- ### ️ 📦 Install an app
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java -jar revanced-cli.jar utility install \
|
java -jar revanced-cli.jar utility install \
|
||||||
-a input.apk \
|
-a input.apk \
|
||||||
<device-serial>
|
[<device-serial>]
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**: You can mount an APK file
|
> [!NOTE]
|
||||||
by supplying the package name of the app to mount the supplied APK file to over the option `--mount`.
|
> You can mount an APK file
|
||||||
|
> by supplying the package name of the app to mount the supplied APK file over the option `--mount`.
|
||||||
|
|||||||
26
docs/2_building.md
Normal file
26
docs/2_building.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 🔨️ Building
|
||||||
|
|
||||||
|
Build ReVanced CLI from source.
|
||||||
|
|
||||||
|
## 📝 Requirements
|
||||||
|
|
||||||
|
- Java Development Kit 11 (Azul Zulu JRE or OpenJDK)
|
||||||
|
|
||||||
|
## 🏗️ Building
|
||||||
|
|
||||||
|
To build ReVanced CLI, follow these steps:
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone git@github.com:ReVanced/revanced-cli.git
|
||||||
|
cd revanced-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build the project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
|
||||||
|
After the build succeeds, the built JAR file will be located at `build/libs/revanced-cli-<version>-all.jar`.
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
# 💻 Documentation and guides of ReVanced CLI
|
# 💻 Documentation and guides of ReVanced CLI
|
||||||
|
|
||||||
This documentation explains how to use [ReVanced CLI](https://github.com/revanced/revanced-cli).
|
This documentation contains topics around [ReVanced CLI](https://github.com/revanced/revanced-cli).
|
||||||
|
|
||||||
## 📖 Table of contents
|
## 📖 Table of contents
|
||||||
|
|
||||||
1. [💼 Prerequisites](0_prerequisites.md)
|
1. [💼 Prerequisites](0_prerequisites.md)
|
||||||
2. [🛠️ Using ReVanced CLI](1_usage.md)
|
2. [🛠️ Using ReVanced CLI](1_usage.md)
|
||||||
|
3. [🔨 Building ReVanced CLI](2_building.md)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 3.0.2-dev.2
|
version = 4.4.0
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
[versions]
|
[versions]
|
||||||
shadow = "8.1.1"
|
shadow = "8.1.1"
|
||||||
apksig = "8.1.1"
|
kotlin-test = "1.9.20"
|
||||||
bcpkix-jdk15on = "1.70"
|
kotlinx-coroutines-core = "1.7.3"
|
||||||
jackson-module-kotlin = "2.14.3"
|
|
||||||
jadb = "2531a28109"
|
|
||||||
kotlin-reflect = "1.9.0"
|
|
||||||
kotlin-test = "1.8.20-RC"
|
|
||||||
kotlinx-coroutines-core = "1.7.1"
|
|
||||||
picocli = "4.7.3"
|
picocli = "4.7.3"
|
||||||
revanced-patcher = "14.2.1"
|
revanced-patcher = "19.1.0"
|
||||||
|
revanced-library = "1.4.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
|
||||||
bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" }
|
|
||||||
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" }
|
|
||||||
jadb = { module = "com.github.revanced:jadb", version.ref = "jadb" }
|
|
||||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
|
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
|
||||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||||
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
|
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
|
||||||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
||||||
|
revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
||||||
|
|||||||
7
gradle/wrapper/gradle-wrapper.properties
vendored
7
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,7 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
networkTimeout=10000
|
distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dist
|
||||||
8612
package-lock.json
generated
8612
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
"@saithodev/semantic-release-backmerge": "^3.2.1",
|
||||||
"@semantic-release/changelog": "^6.0.2",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"gradle-semantic-release-plugin": "^1.7.6",
|
"gradle-semantic-release-plugin": "^1.8.0",
|
||||||
"semantic-release": "^20.1.0"
|
"semantic-release": "^22.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,7 @@
|
|||||||
val githubUsername: String = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
|
rootProject.name = "revanced-cli"
|
||||||
val githubPassword: String = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
|
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
buildCache {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
local {
|
||||||
repositories {
|
isEnabled = "CI" !in System.getenv()
|
||||||
mavenCentral()
|
|
||||||
mavenLocal()
|
|
||||||
google()
|
|
||||||
maven { url = uri("https://jitpack.io") }
|
|
||||||
listOf("revanced-patcher", "jadb").forEach { repo ->
|
|
||||||
maven {
|
|
||||||
url = uri("https://maven.pkg.github.com/revanced/$repo")
|
|
||||||
credentials {
|
|
||||||
username = githubUsername
|
|
||||||
password = githubPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "revanced-cli"
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package app.revanced.cli.command
|
||||||
|
|
||||||
|
import app.revanced.library.PackageName
|
||||||
|
import app.revanced.library.PatchUtils
|
||||||
|
import app.revanced.library.VersionMap
|
||||||
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
|
import picocli.CommandLine
|
||||||
|
import java.io.File
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
@CommandLine.Command(
|
||||||
|
name = "list-versions",
|
||||||
|
description = [
|
||||||
|
"List the most common compatible versions of apps that are compatible " +
|
||||||
|
"with the patches in the supplied patch bundles.",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
internal class ListCompatibleVersions : Runnable {
|
||||||
|
private val logger = Logger.getLogger(ListCompatibleVersions::class.java.name)
|
||||||
|
|
||||||
|
@CommandLine.Parameters(
|
||||||
|
description = ["Paths to patch bundles."],
|
||||||
|
arity = "1..*",
|
||||||
|
)
|
||||||
|
private lateinit var patchBundles: Array<File>
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["-f", "--filter-package-names"],
|
||||||
|
description = ["Filter patches by package name."],
|
||||||
|
)
|
||||||
|
private var packageNames: Set<String>? = null
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["-u", "--count-unused-patches"],
|
||||||
|
description = ["Count patches that are not used by default."],
|
||||||
|
showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
|
||||||
|
)
|
||||||
|
private var countUnusedPatches: Boolean = false
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val patches = PatchBundleLoader.Jar(*patchBundles)
|
||||||
|
|
||||||
|
fun VersionMap.buildVersionsString(): String {
|
||||||
|
if (isEmpty()) return "Any"
|
||||||
|
|
||||||
|
fun buildPatchesCountString(count: Int) = if (count == 1) "1 patch" else "$count patches"
|
||||||
|
|
||||||
|
return entries.joinToString("\n") { (version, count) ->
|
||||||
|
"$version (${buildPatchesCountString(count)})"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildString(entry: Map.Entry<PackageName, VersionMap>) =
|
||||||
|
buildString {
|
||||||
|
val (name, versions) = entry
|
||||||
|
appendLine("Package name: $name")
|
||||||
|
appendLine("Most common compatible versions:")
|
||||||
|
appendLine(versions.buildVersionsString().prependIndent("\t"))
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchUtils.getMostCommonCompatibleVersions(
|
||||||
|
patches,
|
||||||
|
packageNames,
|
||||||
|
countUnusedPatches,
|
||||||
|
).entries.joinToString("\n", transform = ::buildString).let(logger::info)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,91 +1,139 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
import app.revanced.patcher.PatchBundleLoader
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
import app.revanced.patcher.annotation.Package
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
import app.revanced.patcher.patch.options.PatchOption
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.description
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.options
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
|
||||||
import app.revanced.patcher.patch.PatchClass
|
|
||||||
import app.revanced.patcher.patch.PatchOption
|
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
@Command(
|
||||||
@Command(name = "list-patches", description = ["List patches from supplied patch bundles"])
|
name = "list-patches",
|
||||||
|
description = ["List patches from supplied patch bundles."],
|
||||||
|
)
|
||||||
internal object ListPatchesCommand : Runnable {
|
internal object ListPatchesCommand : Runnable {
|
||||||
private val logger = Logger.getLogger(ListPatchesCommand::class.java.name)
|
private val logger = Logger.getLogger(ListPatchesCommand::class.java.name)
|
||||||
|
|
||||||
@Parameters(
|
@Parameters(
|
||||||
description = ["Paths to patch bundles"], arity = "1..*"
|
description = ["Paths to patch bundles."],
|
||||||
|
arity = "1..*",
|
||||||
)
|
)
|
||||||
private lateinit var patchBundles: Array<File>
|
private lateinit var patchBundles: Array<File>
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["-d", "--with-descriptions"], description = ["List their descriptions"], showDefaultValue = ALWAYS
|
names = ["-d", "--with-descriptions"],
|
||||||
|
description = ["List their descriptions."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var withDescriptions: Boolean = true
|
private var withDescriptions: Boolean = true
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["-p", "--with-packages"],
|
names = ["-p", "--with-packages"],
|
||||||
description = ["List the packages the patches are compatible with"],
|
description = ["List the packages the patches are compatible with."],
|
||||||
showDefaultValue = ALWAYS
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var withPackages: Boolean = false
|
private var withPackages: Boolean = false
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["-v", "--with-versions"],
|
names = ["-v", "--with-versions"],
|
||||||
description = ["List the versions of the apps the patches are compatible with"],
|
description = ["List the versions of the apps the patches are compatible with."],
|
||||||
showDefaultValue = ALWAYS
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var withVersions: Boolean = false
|
private var withVersions: Boolean = false
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["-o", "--with-options"], description = ["List the options of the patches"], showDefaultValue = ALWAYS
|
names = ["-o", "--with-options"],
|
||||||
|
description = ["List the options of the patches."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var withOptions: Boolean = false
|
private var withOptions: Boolean = false
|
||||||
|
|
||||||
|
@Option(
|
||||||
|
names = ["-u", "--with-universal-patches"],
|
||||||
|
description = ["List patches which are compatible with any app."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
|
)
|
||||||
|
private var withUniversalPatches: Boolean = true
|
||||||
|
|
||||||
|
@Option(
|
||||||
|
names = ["-i", "--index"],
|
||||||
|
description = ["List the index of each patch in relation to the supplied patch bundles."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
|
)
|
||||||
|
private var withIndex: Boolean = true
|
||||||
|
|
||||||
|
@Option(
|
||||||
|
names = ["-f", "--filter-package-name"],
|
||||||
|
description = ["Filter patches by package name."],
|
||||||
|
)
|
||||||
|
private var packageName: String? = null
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
fun Package.buildString() = buildString {
|
fun Patch.CompatiblePackage.buildString() =
|
||||||
if (withVersions && versions.isNotEmpty()) {
|
buildString {
|
||||||
appendLine("Package name: $name")
|
if (withVersions && versions != null) {
|
||||||
appendLine("Compatible versions:")
|
appendLine("Package name: $name")
|
||||||
append(versions.joinToString("\n") { version -> version }.prependIndent("\t"))
|
appendLine("Compatible versions:")
|
||||||
} else append("Package name: $name")
|
append(versions!!.joinToString("\n") { version -> version }.prependIndent("\t"))
|
||||||
}
|
} else {
|
||||||
|
append("Package name: $name")
|
||||||
fun PatchOption<*>.buildString() = buildString {
|
}
|
||||||
appendLine("Title: $title")
|
|
||||||
appendLine("Description: $description")
|
|
||||||
|
|
||||||
value?.let {
|
|
||||||
appendLine("Key: $key")
|
|
||||||
append("Value: $it")
|
|
||||||
} ?: append("Key: $key")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PatchClass.buildString() = buildString {
|
|
||||||
append("Name: $patchName")
|
|
||||||
|
|
||||||
if (withDescriptions) append("\nDescription: $description")
|
|
||||||
|
|
||||||
if (withOptions && options != null) {
|
|
||||||
appendLine("\nOptions:")
|
|
||||||
append(
|
|
||||||
options!!.joinToString("\n\n") { option -> option.buildString() }.prependIndent("\t")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withPackages && compatiblePackages != null) {
|
fun PatchOption<*>.buildString() =
|
||||||
appendLine("\nCompatible packages:")
|
buildString {
|
||||||
append(
|
appendLine("Title: $title")
|
||||||
compatiblePackages!!.joinToString("\n") { it.buildString() }.prependIndent("\t")
|
description?.let { appendLine("Description: $it") }
|
||||||
)
|
default?.let {
|
||||||
}
|
appendLine("Key: $key")
|
||||||
}
|
append("Default: $it")
|
||||||
|
} ?: append("Key: $key")
|
||||||
|
|
||||||
logger.info(PatchBundleLoader.Jar(*patchBundles).joinToString("\n\n") { it.buildString() })
|
values?.let { values ->
|
||||||
|
appendLine("\nValid values:")
|
||||||
|
append(values.map { "${it.value} (${it.key})" }.joinToString("\n").prependIndent("\t"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IndexedValue<Patch<*>>.buildString() =
|
||||||
|
let { (index, patch) ->
|
||||||
|
buildString {
|
||||||
|
if (withIndex) appendLine("Index: $index")
|
||||||
|
|
||||||
|
append("Name: ${patch.name}")
|
||||||
|
|
||||||
|
if (withDescriptions) append("\nDescription: ${patch.description}")
|
||||||
|
|
||||||
|
if (withOptions && patch.options.isNotEmpty()) {
|
||||||
|
appendLine("\nOptions:")
|
||||||
|
append(
|
||||||
|
patch.options.values.joinToString("\n\n") { option ->
|
||||||
|
option.buildString()
|
||||||
|
}.prependIndent("\t"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (withPackages && patch.compatiblePackages != null) {
|
||||||
|
appendLine("\nCompatible packages:")
|
||||||
|
append(
|
||||||
|
patch.compatiblePackages!!.joinToString("\n") {
|
||||||
|
it.buildString()
|
||||||
|
}.prependIndent("\t"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Patch<*>.filterCompatiblePackages(name: String) =
|
||||||
|
compatiblePackages?.any { it.name == name }
|
||||||
|
?: withUniversalPatches
|
||||||
|
|
||||||
|
val patches = PatchBundleLoader.Jar(*patchBundles).withIndex().toList()
|
||||||
|
|
||||||
|
val filtered =
|
||||||
|
packageName?.let { patches.filter { (_, patch) -> patch.filterCompatiblePackages(it) } } ?: patches
|
||||||
|
|
||||||
|
if (filtered.isNotEmpty()) logger.info(filtered.joinToString("\n\n") { it.buildString() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,43 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
import app.revanced.cli.command.utility.UtilityCommand
|
import app.revanced.cli.command.utility.UtilityCommand
|
||||||
import app.revanced.patcher.patch.PatchClass
|
import app.revanced.library.logging.Logger
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
import picocli.CommandLine.IVersionProvider
|
import picocli.CommandLine.IVersionProvider
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.logging.*
|
|
||||||
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
System.setProperty("java.util.logging.SimpleFormatter.format", "%4\$s: %5\$s %n")
|
Logger.setDefault()
|
||||||
Logger.getLogger("").apply {
|
CommandLine(MainCommand).execute(*args).let(System::exit)
|
||||||
handlers.forEach {
|
|
||||||
it.close()
|
|
||||||
removeHandler(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
object : Handler() {
|
|
||||||
override fun publish(record: LogRecord) = formatter.format(record).toByteArray().let {
|
|
||||||
if (record.level.intValue() > Level.INFO.intValue())
|
|
||||||
System.err.write(it)
|
|
||||||
else
|
|
||||||
System.out.write(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun flush() {
|
|
||||||
System.out.flush()
|
|
||||||
System.err.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() = flush()
|
|
||||||
}.also {
|
|
||||||
it.level = Level.ALL
|
|
||||||
it.formatter = SimpleFormatter()
|
|
||||||
}.let(::addHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
CommandLine(MainCommand).execute(*args)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal typealias PatchList = List<PatchClass>
|
|
||||||
|
|
||||||
private object CLIVersionProvider : IVersionProvider {
|
private object CLIVersionProvider : IVersionProvider {
|
||||||
override fun getVersion(): Array<String> {
|
override fun getVersion() =
|
||||||
Properties().apply {
|
arrayOf(
|
||||||
load(MainCommand::class.java.getResourceAsStream("/app/revanced/cli/version.properties"))
|
MainCommand::class.java.getResourceAsStream(
|
||||||
}.let {
|
"/app/revanced/cli/version.properties",
|
||||||
return arrayOf("ReVanced CLI v${it.getProperty("version")}")
|
)?.use { stream ->
|
||||||
}
|
Properties().apply {
|
||||||
}
|
load(stream)
|
||||||
|
}.let {
|
||||||
|
"ReVanced CLI v${it.getProperty("version")}"
|
||||||
|
}
|
||||||
|
} ?: "ReVanced CLI",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
name = "revanced-cli",
|
name = "revanced-cli",
|
||||||
description = ["Command line application to use ReVanced"],
|
description = ["Command line application to use ReVanced."],
|
||||||
mixinStandardHelpOptions = true,
|
mixinStandardHelpOptions = true,
|
||||||
versionProvider = CLIVersionProvider::class,
|
versionProvider = CLIVersionProvider::class,
|
||||||
subcommands = [
|
subcommands = [
|
||||||
ListPatchesCommand::class,
|
|
||||||
PatchCommand::class,
|
PatchCommand::class,
|
||||||
OptionsCommand::class,
|
OptionsCommand::class,
|
||||||
|
ListPatchesCommand::class,
|
||||||
|
ListCompatibleVersions::class,
|
||||||
UtilityCommand::class,
|
UtilityCommand::class,
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
private object MainCommand
|
private object MainCommand
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
|
import app.revanced.library.Options
|
||||||
|
import app.revanced.library.Options.setOptions
|
||||||
import app.revanced.patcher.PatchBundleLoader
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
import app.revanced.utils.Options
|
|
||||||
import app.revanced.utils.Options.setOptions
|
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -10,37 +10,53 @@ import java.util.logging.Logger
|
|||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "options",
|
name = "options",
|
||||||
description = ["Generate options file from patches"],
|
description = ["Generate options file from patches."],
|
||||||
)
|
)
|
||||||
internal object OptionsCommand : Runnable {
|
internal object OptionsCommand : Runnable {
|
||||||
private val logger = Logger.getLogger(OptionsCommand::class.java.name)
|
private val logger = Logger.getLogger(OptionsCommand::class.java.name)
|
||||||
|
|
||||||
@CommandLine.Parameters(
|
@CommandLine.Parameters(
|
||||||
description = ["Paths to patch bundles"], arity = "1..*"
|
description = ["Paths to patch bundles."],
|
||||||
|
arity = "1..*",
|
||||||
)
|
)
|
||||||
private lateinit var patchBundles: Array<File>
|
private lateinit var patchBundles: Array<File>
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-p", "--path"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS
|
names = ["-p", "--path"],
|
||||||
|
description = ["Path to patch options JSON file."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var path: File = File("options.json")
|
private var filePath: File = File("options.json")
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-o", "--overwrite"], description = ["Overwrite existing options file"], showDefaultValue = ALWAYS
|
names = ["-o", "--overwrite"],
|
||||||
|
description = ["Overwrite existing options file."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var overwrite: Boolean = false
|
private var overwrite: Boolean = false
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-u", "--update"],
|
names = ["-u", "--update"],
|
||||||
description = ["Update existing options by adding missing and removing non-existent options"],
|
description = ["Update existing options by adding missing and removing non-existent options."],
|
||||||
showDefaultValue = ALWAYS
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var update: Boolean = false
|
private var update: Boolean = false
|
||||||
|
|
||||||
override fun run() = if (!path.exists() || overwrite) with(PatchBundleLoader.Jar(*patchBundles)) {
|
override fun run() =
|
||||||
if (update) setOptions(path)
|
try {
|
||||||
|
PatchBundleLoader.Jar(*patchBundles).let { patches ->
|
||||||
|
val exists = filePath.exists()
|
||||||
|
if (!exists || overwrite) {
|
||||||
|
if (exists && update) patches.setOptions(filePath)
|
||||||
|
|
||||||
Options.serialize(this, prettyPrint = true).let(path::writeText)
|
Options.serialize(patches, prettyPrint = true).let(filePath::writeText)
|
||||||
}
|
} else {
|
||||||
else logger.severe("Options file already exists, use --override to override it")
|
throw OptionsFileAlreadyExistsException()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} catch (ex: OptionsFileAlreadyExistsException) {
|
||||||
|
logger.severe("Options file already exists, use --overwrite to override it")
|
||||||
|
}
|
||||||
|
|
||||||
|
class OptionsFileAlreadyExistsException : Exception()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,152 +1,242 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
|
import app.revanced.library.ApkUtils
|
||||||
|
import app.revanced.library.Options
|
||||||
|
import app.revanced.library.Options.setOptions
|
||||||
|
import app.revanced.library.adb.AdbManager
|
||||||
import app.revanced.patcher.PatchBundleLoader
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
|
import app.revanced.patcher.PatchSet
|
||||||
import app.revanced.patcher.Patcher
|
import app.revanced.patcher.Patcher
|
||||||
import app.revanced.patcher.PatcherOptions
|
import app.revanced.patcher.PatcherOptions
|
||||||
import app.revanced.patcher.PatcherResult
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.include
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
|
||||||
import app.revanced.utils.Options
|
|
||||||
import app.revanced.utils.Options.setOptions
|
|
||||||
import app.revanced.utils.adb.AdbManager
|
|
||||||
import app.revanced.utils.align.ZipAligner
|
|
||||||
import app.revanced.utils.align.zip.ZipFile
|
|
||||||
import app.revanced.utils.align.zip.structures.ZipEntry
|
|
||||||
import app.revanced.utils.signing.ApkSigner
|
|
||||||
import app.revanced.utils.signing.SigningOptions
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||||
|
import picocli.CommandLine.Model.CommandSpec
|
||||||
|
import picocli.CommandLine.Spec
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "patch", description = ["Patch the supplied APK file with the supplied patches and integrations"]
|
name = "patch",
|
||||||
|
description = ["Patch an APK file."],
|
||||||
)
|
)
|
||||||
internal object PatchCommand : Runnable {
|
internal object PatchCommand : Runnable {
|
||||||
private val logger = Logger.getLogger(PatchCommand::class.java.name)
|
private val logger = Logger.getLogger(PatchCommand::class.java.name)
|
||||||
|
|
||||||
@CommandLine.Parameters(
|
@Spec
|
||||||
description = ["APK file to be patched"], arity = "1..1"
|
lateinit var spec: CommandSpec // injected by picocli
|
||||||
)
|
|
||||||
private lateinit var apk: File
|
private lateinit var apk: File
|
||||||
|
|
||||||
@CommandLine.Option(
|
private var integrations = listOf<File>()
|
||||||
names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches"], required = true
|
|
||||||
)
|
|
||||||
private var patchBundles = emptyList<File>()
|
private var patchBundles = emptyList<File>()
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"]
|
names = ["-i", "--include"],
|
||||||
|
description = ["List of patches to include."],
|
||||||
)
|
)
|
||||||
private var integrations = listOf<File>()
|
private var includedPatches = hashSetOf<String>()
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-i", "--include"], description = ["List of patches to include"]
|
names = ["--ii"],
|
||||||
|
description = ["List of patches to include by their index in relation to the supplied patch bundles."],
|
||||||
)
|
)
|
||||||
private var includedPatches = arrayOf<String>()
|
private var includedPatchesByIndex = arrayOf<Int>()
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-e", "--exclude"], description = ["List of patches to exclude"]
|
names = ["-e", "--exclude"],
|
||||||
|
description = ["List of patches to exclude."],
|
||||||
)
|
)
|
||||||
private var excludedPatches = arrayOf<String>()
|
private var excludedPatches = hashSetOf<String>()
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["--options"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS
|
names = ["--ei"],
|
||||||
|
description = ["List of patches to exclude by their index in relation to the supplied patch bundles."],
|
||||||
)
|
)
|
||||||
private var optionsFile: File = File("options.json")
|
private var excludedPatchesByIndex = arrayOf<Int>()
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["--options"],
|
||||||
|
description = ["Path to patch options JSON file."],
|
||||||
|
)
|
||||||
|
private var optionsFile: File? = null
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["--exclusive"],
|
names = ["--exclusive"],
|
||||||
description = ["Only include patches that are explicitly specified to be included"],
|
description = ["Only include patches that are explicitly specified to be included."],
|
||||||
showDefaultValue = ALWAYS
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var exclusive = false
|
private var exclusive = false
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-f","--force"],
|
names = ["-f", "--force"],
|
||||||
description = ["Force inclusion of patches that are incompatible with the supplied APK file's version"],
|
description = ["Bypass compatibility checks for the supplied APK's version."],
|
||||||
showDefaultValue = ALWAYS
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var force: Boolean = false
|
private var force: Boolean = false
|
||||||
|
|
||||||
@CommandLine.Option(
|
private var outputFilePath: File? = null
|
||||||
names = ["-o", "--out"], description = ["Path to save the patched APK file to"], required = true
|
|
||||||
)
|
|
||||||
private lateinit var outputFilePath: File
|
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-d", "--device-serial"], description = ["ADB device serial to install to"], showDefaultValue = ALWAYS
|
names = ["-o", "--out"],
|
||||||
|
description = ["Path to save the patched APK file to. Defaults to the same directory as the supplied APK file."],
|
||||||
|
)
|
||||||
|
private fun setOutputFilePath(outputFilePath: File?) {
|
||||||
|
this.outputFilePath = outputFilePath?.absoluteFile
|
||||||
|
}
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["-d", "--device-serial"],
|
||||||
|
description = ["ADB device serial to install to. If not supplied, the first connected device will be used."],
|
||||||
|
fallbackValue = "", // Empty string to indicate that the first connected device should be used.
|
||||||
|
arity = "0..1",
|
||||||
)
|
)
|
||||||
private var deviceSerial: String? = null
|
private var deviceSerial: String? = null
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["--mount"], description = ["Install by mounting the patched APK file"], showDefaultValue = ALWAYS
|
names = ["--mount"],
|
||||||
|
description = ["Install by mounting the patched APK file."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var mount: Boolean = false
|
private var mount: Boolean = false
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["--common-name"],
|
names = ["--keystore"],
|
||||||
description = ["The common name of the signer of the patched APK file"],
|
description = [
|
||||||
showDefaultValue = ALWAYS
|
"Path to the keystore to sign the patched APK file with. " +
|
||||||
|
"Defaults to the same directory as the supplied APK file.",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
private var commonName = "ReVanced"
|
private var keystoreFilePath: File? = null
|
||||||
|
|
||||||
|
// key store password
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["--keystore-password"],
|
||||||
|
description = ["The password of the keystore to sign the patched APK file with. Empty password by default."],
|
||||||
|
)
|
||||||
|
private var keyStorePassword: String? = null // Empty password by default
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["--keystore"], description = ["Path to the keystore to sign the patched APK file with"]
|
names = ["--alias"],
|
||||||
|
description = ["The alias of the key from the keystore to sign the patched APK file with."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var keystorePath: String? = null
|
private var alias = "ReVanced Key"
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["--password"], description = ["The password of the keystore to sign the patched APK file with"]
|
names = ["--keystore-entry-password"],
|
||||||
|
description = ["The password of the entry from the keystore for the key to sign the patched APK file with."],
|
||||||
)
|
)
|
||||||
private var password = "ReVanced"
|
private var password = "" // Empty password by default
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["--signer"],
|
||||||
|
description = ["The name of the signer to sign the patched APK file with."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
|
)
|
||||||
|
private var signer = "ReVanced"
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-r", "--resource-cache"],
|
names = ["-r", "--resource-cache"],
|
||||||
description = ["Path to temporary resource cache directory"],
|
description = ["Path to temporary resource cache directory."],
|
||||||
showDefaultValue = ALWAYS
|
|
||||||
)
|
)
|
||||||
private var resourceCachePath = File("revanced-resource-cache")
|
private var resourceCachePath: File? = null
|
||||||
|
|
||||||
@CommandLine.Option(
|
private var aaptBinaryPath: File? = null
|
||||||
names = ["--custom-aapt2-binary"], description = ["Path to a custom AAPT binary to compile resources with"]
|
|
||||||
)
|
|
||||||
private var aaptBinaryPath = File("")
|
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-p", "--purge"],
|
names = ["-p", "--purge"],
|
||||||
description = ["Purge the temporary resource cache directory after patching"],
|
description = ["Purge the temporary resource cache directory after patching."],
|
||||||
showDefaultValue = ALWAYS
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var purge: Boolean = false
|
private var purge: Boolean = false
|
||||||
|
|
||||||
override fun run() {
|
@CommandLine.Option(
|
||||||
// region Prepare
|
names = ["-w", "--warn"],
|
||||||
|
description = ["Warn if a patch can not be found in the supplied patch bundles."],
|
||||||
|
showDefaultValue = ALWAYS,
|
||||||
|
)
|
||||||
|
private var warn: Boolean = false
|
||||||
|
|
||||||
|
@CommandLine.Parameters(
|
||||||
|
description = ["APK file to be patched."],
|
||||||
|
arity = "1..1",
|
||||||
|
)
|
||||||
|
@Suppress("unused")
|
||||||
|
private fun setApk(apk: File) {
|
||||||
if (!apk.exists()) {
|
if (!apk.exists()) {
|
||||||
logger.severe("APK file ${apk.name} does not exist")
|
throw CommandLine.ParameterException(
|
||||||
return
|
spec.commandLine(),
|
||||||
|
"APK file ${apk.name} does not exist",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
this.apk = apk
|
||||||
|
}
|
||||||
|
|
||||||
integrations.filter { !it.exists() }.let {
|
@CommandLine.Option(
|
||||||
if (it.isEmpty()) return@let
|
names = ["-m", "--merge"],
|
||||||
|
description = ["One or more DEX files or containers to merge into the APK."],
|
||||||
it.forEach { integration ->
|
)
|
||||||
logger.severe("Integration file ${integration.name} does not exist")
|
@Suppress("unused")
|
||||||
}
|
private fun setIntegrations(integrations: Array<File>) {
|
||||||
return
|
integrations.firstOrNull { !it.exists() }?.let {
|
||||||
|
throw CommandLine.ParameterException(spec.commandLine(), "Integrations file ${it.name} does not exist.")
|
||||||
}
|
}
|
||||||
|
this.integrations += integrations
|
||||||
|
}
|
||||||
|
|
||||||
val adbManager = deviceSerial?.let { serial ->
|
@CommandLine.Option(
|
||||||
if (mount) AdbManager.RootAdbManager(serial)
|
names = ["-b", "--patch-bundle"],
|
||||||
else AdbManager.UserAdbManager(serial)
|
description = ["One or more bundles of patches."],
|
||||||
|
required = true,
|
||||||
|
)
|
||||||
|
@Suppress("unused")
|
||||||
|
private fun setPatchBundles(patchBundles: Array<File>) {
|
||||||
|
patchBundles.firstOrNull { !it.exists() }?.let {
|
||||||
|
throw CommandLine.ParameterException(spec.commandLine(), "Patch bundle ${it.name} does not exist")
|
||||||
}
|
}
|
||||||
|
this.patchBundles = patchBundles.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["--custom-aapt2-binary"],
|
||||||
|
description = ["Path to a custom AAPT binary to compile resources with."],
|
||||||
|
)
|
||||||
|
@Suppress("unused")
|
||||||
|
private fun setAaptBinaryPath(aaptBinaryPath: File) {
|
||||||
|
if (!aaptBinaryPath.exists()) {
|
||||||
|
throw CommandLine.ParameterException(
|
||||||
|
spec.commandLine(),
|
||||||
|
"AAPT binary ${aaptBinaryPath.name} does not exist",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.aaptBinaryPath = aaptBinaryPath
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
// region Setup
|
||||||
|
|
||||||
|
val outputFilePath =
|
||||||
|
outputFilePath ?: File("").absoluteFile.resolve(
|
||||||
|
"${apk.nameWithoutExtension}-patched.${apk.extension}",
|
||||||
|
)
|
||||||
|
|
||||||
|
val resourceCachePath =
|
||||||
|
resourceCachePath ?: outputFilePath.parentFile.resolve(
|
||||||
|
"${outputFilePath.nameWithoutExtension}-resource-cache",
|
||||||
|
)
|
||||||
|
|
||||||
|
val optionsFile =
|
||||||
|
optionsFile ?: outputFilePath.parentFile.resolve(
|
||||||
|
"${outputFilePath.nameWithoutExtension}-options.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
val keystoreFilePath =
|
||||||
|
keystoreFilePath ?: outputFilePath.parentFile
|
||||||
|
.resolve("${outputFilePath.nameWithoutExtension}.keystore")
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
@@ -155,187 +245,169 @@ internal object PatchCommand : Runnable {
|
|||||||
logger.info("Loading patches")
|
logger.info("Loading patches")
|
||||||
|
|
||||||
val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray())
|
val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray())
|
||||||
val integrations = integrations
|
|
||||||
|
|
||||||
logger.info("Setting patch options")
|
// Warn if a patch can not be found in the supplied patch bundles.
|
||||||
|
if (warn) {
|
||||||
optionsFile.let {
|
patches.map { it.name }.toHashSet().let { availableNames ->
|
||||||
if (it.exists()) patches.setOptions(it)
|
(includedPatches + excludedPatches).filter { name ->
|
||||||
else Options.serialize(patches, prettyPrint = true).let(it::writeText)
|
!availableNames.contains(name)
|
||||||
|
}
|
||||||
|
}.let { unknownPatches ->
|
||||||
|
if (unknownPatches.isEmpty()) return@let
|
||||||
|
logger.warning("Unknown input of patches:\n${unknownPatches.joinToString("\n")}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Patch
|
Patcher(
|
||||||
|
|
||||||
val patcher = Patcher(
|
|
||||||
PatcherOptions(
|
PatcherOptions(
|
||||||
apk,
|
apk,
|
||||||
resourceCachePath,
|
resourceCachePath,
|
||||||
aaptBinaryPath.path,
|
aaptBinaryPath?.path,
|
||||||
resourceCachePath.absolutePath,
|
resourceCachePath.absolutePath,
|
||||||
)
|
true,
|
||||||
)
|
),
|
||||||
|
).use { patcher ->
|
||||||
|
val filteredPatches =
|
||||||
|
patcher.filterPatchSelection(patches).also { patches ->
|
||||||
|
logger.info("Setting patch options")
|
||||||
|
|
||||||
val result = patcher.apply {
|
if (optionsFile.exists()) {
|
||||||
acceptIntegrations(integrations)
|
patches.setOptions(optionsFile)
|
||||||
acceptPatches(filterPatchSelection(patches))
|
} else {
|
||||||
|
Options.serialize(patches, prettyPrint = true).let(optionsFile::writeText)
|
||||||
// Execute patches.
|
}
|
||||||
runBlocking {
|
|
||||||
apply(false).collect { patchResult ->
|
|
||||||
patchResult.exception?.let {
|
|
||||||
StringWriter().use { writer ->
|
|
||||||
it.printStackTrace(PrintWriter(writer))
|
|
||||||
logger.severe("${patchResult.patchName} failed: $writer")
|
|
||||||
}
|
|
||||||
} ?: logger.info("${patchResult.patchName} succeeded")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// region Patch
|
||||||
|
|
||||||
|
val patcherResult =
|
||||||
|
patcher.apply {
|
||||||
|
acceptIntegrations(integrations)
|
||||||
|
acceptPatches(filteredPatches.toList())
|
||||||
|
|
||||||
|
// Execute patches.
|
||||||
|
runBlocking {
|
||||||
|
apply(false).collect { patchResult ->
|
||||||
|
patchResult.exception?.let {
|
||||||
|
StringWriter().use { writer ->
|
||||||
|
it.printStackTrace(PrintWriter(writer))
|
||||||
|
logger.severe("${patchResult.patch.name} failed:\n$writer")
|
||||||
|
}
|
||||||
|
} ?: logger.info("${patchResult.patch.name} succeeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.get()
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Save
|
||||||
|
|
||||||
|
val alignedFile =
|
||||||
|
resourceCachePath.resolve(apk.name).apply {
|
||||||
|
ApkUtils.copyAligned(apk, this, patcherResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount) {
|
||||||
|
ApkUtils.sign(
|
||||||
|
alignedFile,
|
||||||
|
outputFilePath,
|
||||||
|
ApkUtils.SigningOptions(
|
||||||
|
keystoreFilePath,
|
||||||
|
keyStorePassword,
|
||||||
|
alias,
|
||||||
|
password,
|
||||||
|
signer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
alignedFile.renameTo(outputFilePath)
|
||||||
}
|
}
|
||||||
}.get()
|
|
||||||
|
|
||||||
patcher.close()
|
logger.info("Saved to $outputFilePath")
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Finish
|
// region Install
|
||||||
|
|
||||||
val alignAndSignedFile = sign(
|
deviceSerial?.let { serial ->
|
||||||
apk.newAlignedFile(
|
AdbManager.getAdbManager(deviceSerial = serial.ifEmpty { null }, mount)
|
||||||
result, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_aligned.apk")
|
}?.install(AdbManager.Apk(outputFilePath, patcher.context.packageMetadata.packageName))
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("Copying to ${outputFilePath.name}")
|
// endregion
|
||||||
alignAndSignedFile.copyTo(outputFilePath, overwrite = true)
|
}
|
||||||
|
|
||||||
adbManager?.install(AdbManager.Apk(outputFilePath, patcher.context.packageMetadata.packageName))
|
|
||||||
|
|
||||||
if (purge) {
|
if (purge) {
|
||||||
logger.info("Purging temporary files")
|
logger.info("Purging temporary files")
|
||||||
purge(resourceCachePath)
|
purge(resourceCachePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the patches to be added to the patcher. The filter is based on the following:
|
* Filter the patches to be added to the patcher. The filter is based on the following:
|
||||||
* - [includedPatches] (explicitly included)
|
|
||||||
* - [excludedPatches] (explicitly excluded)
|
|
||||||
* - [exclusive] (only include patches that are explicitly included)
|
|
||||||
* - [force] (ignore patches incompatibility to versions)
|
|
||||||
* - Package name and version of the input APK file (if [force] is false)
|
|
||||||
*
|
*
|
||||||
* @param patches The patches to filter.
|
* @param patches The patches to filter.
|
||||||
* @return The filtered patches.
|
* @return The filtered patches.
|
||||||
*/
|
*/
|
||||||
private fun Patcher.filterPatchSelection(patches: PatchList) = buildList {
|
private fun Patcher.filterPatchSelection(patches: PatchSet): PatchSet =
|
||||||
val packageName = context.packageMetadata.packageName
|
buildSet {
|
||||||
val packageVersion = context.packageMetadata.packageVersion
|
val packageName = context.packageMetadata.packageName
|
||||||
|
val packageVersion = context.packageMetadata.packageVersion
|
||||||
|
|
||||||
patches.forEach patch@{ patch ->
|
patches.withIndex().forEach patch@{ (i, patch) ->
|
||||||
val formattedPatchName = patch.patchName.lowercase().replace(" ", "-")
|
val patchName = patch.name!!
|
||||||
|
|
||||||
val explicitlyExcluded = excludedPatches.contains(formattedPatchName)
|
val explicitlyExcluded = excludedPatches.contains(patchName) || excludedPatchesByIndex.contains(i)
|
||||||
if (explicitlyExcluded) return@patch logger.info("Excluding ${patch.patchName}")
|
if (explicitlyExcluded) return@patch logger.info("Excluding $patchName")
|
||||||
|
|
||||||
// Make sure the patch is compatible with the supplied APK files package name and version.
|
// Make sure the patch is compatible with the supplied APK files package name and version.
|
||||||
patch.compatiblePackages?.let { packages ->
|
patch.compatiblePackages?.let { packages ->
|
||||||
packages.singleOrNull { it.name == packageName }?.let { `package` ->
|
packages.singleOrNull { it.name == packageName }?.let { `package` ->
|
||||||
val matchesVersion = force || `package`.versions.let {
|
val matchesVersion =
|
||||||
it.isEmpty() || it.any { version -> version == packageVersion }
|
force || `package`.versions?.let {
|
||||||
}
|
it.any { version -> version == packageVersion }
|
||||||
|
} ?: true
|
||||||
|
|
||||||
if (!matchesVersion) return@patch logger.warning("${patch.patchName} is incompatible with version $packageVersion. " + "This patch is only compatible with version " + packages.joinToString(
|
if (!matchesVersion) {
|
||||||
";"
|
return@patch logger.warning(
|
||||||
) { pkg ->
|
"$patchName is incompatible with version $packageVersion. " +
|
||||||
"${pkg.name}: ${pkg.versions.joinToString(", ")}"
|
"This patch is only compatible with version " +
|
||||||
})
|
packages.joinToString(";") { pkg ->
|
||||||
|
pkg.versions!!.joinToString(", ")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} ?: return@patch logger.fine(
|
||||||
|
"$patchName is incompatible with $packageName. " +
|
||||||
|
"This patch is only compatible with " +
|
||||||
|
packages.joinToString(", ") { `package` -> `package`.name },
|
||||||
|
)
|
||||||
|
|
||||||
} ?: return@patch logger.fine("${patch.patchName} is incompatible with $packageName. "
|
return@let
|
||||||
+ "This patch is only compatible with "
|
} ?: logger.fine("$patchName has no constraint on packages.")
|
||||||
+ packages.joinToString(", ") { `package` -> `package`.name })
|
|
||||||
|
|
||||||
return@let
|
// If the patch is implicitly used, it will be only included if [exclusive] is false.
|
||||||
} ?: logger.fine("$formattedPatchName: No constraint on packages.")
|
val implicitlyIncluded = !exclusive && patch.use
|
||||||
|
// If the patch is explicitly used, it will be included even if [exclusive] is false.
|
||||||
|
val explicitlyIncluded = includedPatches.contains(patchName) || includedPatchesByIndex.contains(i)
|
||||||
|
|
||||||
// If the patch is implicitly included, it will be only included if [exclusive] is false.
|
val included = implicitlyIncluded || explicitlyIncluded
|
||||||
val implicitlyIncluded = !exclusive && patch.include
|
if (!included) return@patch logger.info("$patchName excluded") // Case 1.
|
||||||
// If the patch is explicitly included, it will be included even if [exclusive] is false.
|
|
||||||
val explicitlyIncluded = includedPatches.contains(formattedPatchName)
|
|
||||||
|
|
||||||
val included = implicitlyIncluded || explicitlyIncluded
|
logger.fine("Adding $patchName")
|
||||||
if (!included) return@patch logger.info("${patch.patchName} excluded by default") // Case 1.
|
|
||||||
|
|
||||||
logger.fine("Adding $formattedPatchName")
|
add(patch)
|
||||||
|
|
||||||
add(patch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new aligned APK file.
|
|
||||||
*
|
|
||||||
* @param result The result of the patching process.
|
|
||||||
* @param outputFile The file to save the aligned APK to.
|
|
||||||
*/
|
|
||||||
private fun File.newAlignedFile(
|
|
||||||
result: PatcherResult, outputFile: File
|
|
||||||
): File {
|
|
||||||
logger.info("Aligning $name")
|
|
||||||
|
|
||||||
if (outputFile.exists()) outputFile.delete()
|
|
||||||
|
|
||||||
ZipFile(outputFile).use { file ->
|
|
||||||
result.dexFiles.forEach {
|
|
||||||
file.addEntryCompressData(
|
|
||||||
ZipEntry.createWithName(it.name), it.stream.readBytes()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.resourceFile?.let {
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(it), ZipAligner::getEntryAlignment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Do not compress result.doNotCompress
|
|
||||||
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(this), ZipAligner::getEntryAlignment
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputFile
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign the APK file.
|
|
||||||
*
|
|
||||||
* @param inputFile The APK file to sign.
|
|
||||||
* @return The signed APK file. If [mount] is true, the input file will be returned.
|
|
||||||
*/
|
|
||||||
private fun sign(inputFile: File) = if (mount) inputFile
|
|
||||||
else {
|
|
||||||
logger.info("Signing ${inputFile.name}")
|
|
||||||
|
|
||||||
val keyStoreFilePath = keystorePath
|
|
||||||
?: outputFilePath.absoluteFile.parentFile.resolve("${outputFilePath.nameWithoutExtension}.keystore").canonicalPath
|
|
||||||
|
|
||||||
val options = SigningOptions(
|
|
||||||
commonName, password, keyStoreFilePath
|
|
||||||
)
|
|
||||||
|
|
||||||
ApkSigner(options).signApk(
|
|
||||||
inputFile, resourceCachePath.resolve("${outputFilePath.nameWithoutExtension}_signed.apk")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun purge(resourceCachePath: File) {
|
private fun purge(resourceCachePath: File) {
|
||||||
val result = if (resourceCachePath.deleteRecursively()) "Purged resource cache directory"
|
val result =
|
||||||
else "Failed to purge resource cache directory"
|
if (resourceCachePath.deleteRecursively()) {
|
||||||
|
"Purged resource cache directory"
|
||||||
|
} else {
|
||||||
|
"Failed to purge resource cache directory"
|
||||||
|
}
|
||||||
logger.info(result)
|
logger.info(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
package app.revanced.cli.command.utility
|
package app.revanced.cli.command.utility
|
||||||
|
|
||||||
import app.revanced.utils.adb.AdbManager
|
import app.revanced.library.adb.AdbManager
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
name = "install", description = ["Install an APK file to devices with the supplied ADB device serials"]
|
name = "install",
|
||||||
|
description = ["Install an APK file to devices with the supplied ADB device serials"],
|
||||||
)
|
)
|
||||||
internal object InstallCommand : Runnable {
|
internal object InstallCommand : Runnable {
|
||||||
private val logger = Logger.getLogger(InstallCommand::class.java.name)
|
private val logger = Logger.getLogger(InstallCommand::class.java.name)
|
||||||
|
|
||||||
@Parameters(
|
@Parameters(
|
||||||
description = ["ADB device serials"], arity = "1..*"
|
description = ["ADB device serials. If not supplied, the first connected device will be used."],
|
||||||
|
arity = "0..*",
|
||||||
)
|
)
|
||||||
private lateinit var deviceSerials: Array<String>
|
private var deviceSerials: Array<String>? = null
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["-a", "--apk"], description = ["APK file to be installed"], required = true
|
names = ["-a", "--apk"],
|
||||||
|
description = ["APK file to be installed"],
|
||||||
|
required = true,
|
||||||
)
|
)
|
||||||
private lateinit var apk: File
|
private lateinit var apk: File
|
||||||
|
|
||||||
@@ -28,15 +31,14 @@ internal object InstallCommand : Runnable {
|
|||||||
)
|
)
|
||||||
private var packageName: String? = null
|
private var packageName: String? = null
|
||||||
|
|
||||||
override fun run() = try {
|
override fun run() {
|
||||||
deviceSerials.forEach { deviceSerial ->
|
fun install(deviceSerial: String? = null) =
|
||||||
if (packageName != null) {
|
try {
|
||||||
AdbManager.RootAdbManager(deviceSerial)
|
AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName))
|
||||||
} else {
|
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||||
AdbManager.UserAdbManager(deviceSerial)
|
logger.severe(e.toString())
|
||||||
}.install(AdbManager.Apk(apk, packageName))
|
}
|
||||||
}
|
|
||||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
deviceSerials?.forEach(::install) ?: install()
|
||||||
logger.severe(e.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,45 @@
|
|||||||
package app.revanced.cli.command.utility
|
package app.revanced.cli.command.utility
|
||||||
|
|
||||||
import app.revanced.utils.adb.AdbManager
|
import app.revanced.library.adb.AdbManager
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
name = "uninstall",
|
name = "uninstall",
|
||||||
description = ["Uninstall a patched app from the devices with the supplied ADB device serials"]
|
description = ["Uninstall a patched app from the devices with the supplied ADB device serials"],
|
||||||
)
|
)
|
||||||
internal object UninstallCommand : Runnable {
|
internal object UninstallCommand : Runnable {
|
||||||
private val logger = Logger.getLogger(UninstallCommand::class.java.name)
|
private val logger = Logger.getLogger(UninstallCommand::class.java.name)
|
||||||
|
|
||||||
@Parameters(description = ["ADB device serials"], arity = "1..*")
|
@Parameters(
|
||||||
private lateinit var deviceSerials: Array<String>
|
description = ["ADB device serials. If not supplied, the first connected device will be used."],
|
||||||
|
arity = "0..*",
|
||||||
|
)
|
||||||
|
private var deviceSerials: Array<String>? = null
|
||||||
|
|
||||||
@Option(names = ["-p", "--package-name"], description = ["Package name of the app to uninstall"], required = true)
|
@Option(
|
||||||
|
names = ["-p", "--package-name"],
|
||||||
|
description = ["Package name of the app to uninstall"],
|
||||||
|
required = true,
|
||||||
|
)
|
||||||
private lateinit var packageName: String
|
private lateinit var packageName: String
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
names = ["-u", "--unmount"],
|
names = ["-u", "--unmount"],
|
||||||
description = ["Uninstall by unmounting the patched APK file"],
|
description = ["Uninstall by unmounting the patched APK file"],
|
||||||
showDefaultValue = ALWAYS
|
showDefaultValue = ALWAYS,
|
||||||
)
|
)
|
||||||
private var unmount: Boolean = false
|
private var unmount: Boolean = false
|
||||||
|
|
||||||
override fun run() = try {
|
override fun run() {
|
||||||
deviceSerials.forEach { deviceSerial ->
|
fun uninstall(deviceSerial: String? = null) =
|
||||||
if (unmount) {
|
try {
|
||||||
AdbManager.RootAdbManager(deviceSerial)
|
AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName)
|
||||||
} else {
|
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||||
AdbManager.UserAdbManager(deviceSerial)
|
logger.severe(e.toString())
|
||||||
}.uninstall(packageName)
|
}
|
||||||
}
|
|
||||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
deviceSerials?.forEach { uninstall(it) } ?: uninstall()
|
||||||
logger.severe(e.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import picocli.CommandLine
|
|||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "utility",
|
name = "utility",
|
||||||
description = ["Commands for utility purposes"],
|
description = ["Commands for utility purposes."],
|
||||||
subcommands = [InstallCommand::class, UninstallCommand::class],
|
subcommands = [InstallCommand::class, UninstallCommand::class],
|
||||||
)
|
)
|
||||||
internal object UtilityCommand
|
internal object UtilityCommand
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
package app.revanced.utils
|
|
||||||
|
|
||||||
import app.revanced.cli.command.PatchList
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.options
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
|
||||||
import app.revanced.patcher.patch.NoSuchOptionException
|
|
||||||
import app.revanced.utils.Options.PatchOption.Option
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
||||||
import java.io.File
|
|
||||||
import java.util.logging.Logger
|
|
||||||
|
|
||||||
|
|
||||||
internal object Options {
|
|
||||||
private val logger = Logger.getLogger(Options::class.java.name)
|
|
||||||
|
|
||||||
private var mapper = jacksonObjectMapper()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes the options for the patches in the list.
|
|
||||||
*
|
|
||||||
* @param patches The list of patches to serialize.
|
|
||||||
* @param prettyPrint Whether to pretty print the JSON.
|
|
||||||
* @return The JSON string containing the options.
|
|
||||||
* @see PatchList
|
|
||||||
*/
|
|
||||||
fun serialize(patches: PatchList, prettyPrint: Boolean = false): String = patches
|
|
||||||
.filter { it.options?.any() == true }
|
|
||||||
.map { patch ->
|
|
||||||
PatchOption(
|
|
||||||
patch.patchName,
|
|
||||||
patch.options!!.map { option -> Option(option.key, option.value) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// See https://github.com/revanced/revanced-patches/pull/2434/commits/60e550550b7641705e81aa72acfc4faaebb225e7.
|
|
||||||
.distinctBy { it.patchName }
|
|
||||||
.let {
|
|
||||||
if (prettyPrint)
|
|
||||||
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(it)
|
|
||||||
else
|
|
||||||
mapper.writeValueAsString(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deserializes the options for the patches in the list.
|
|
||||||
*
|
|
||||||
* @param json The JSON string containing the options.
|
|
||||||
* @return The list of [PatchOption]s.
|
|
||||||
* @see PatchOption
|
|
||||||
* @see PatchList
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
|
||||||
fun deserialize(json: String): Array<PatchOption> = mapper.readValue(json, Array<PatchOption>::class.java)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the options for the patches in the list.
|
|
||||||
*
|
|
||||||
* @param json The JSON string containing the options.
|
|
||||||
*/
|
|
||||||
fun PatchList.setOptions(json: String) {
|
|
||||||
filter { it.options?.any() == true }.let { patches ->
|
|
||||||
if (patches.isEmpty()) return
|
|
||||||
|
|
||||||
val patchOptions = deserialize(json)
|
|
||||||
|
|
||||||
patches.forEach patch@{ patch ->
|
|
||||||
patchOptions.find { option -> option.patchName == patch.patchName }?.let {
|
|
||||||
it.options.forEach { option ->
|
|
||||||
try {
|
|
||||||
patch.options?.set(option.key, option.value)
|
|
||||||
?: run{
|
|
||||||
logger.warning("${patch.patchName} has no options")
|
|
||||||
return@patch
|
|
||||||
}
|
|
||||||
} catch (e: NoSuchOptionException) {
|
|
||||||
logger.info(e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the options for the patches in the list.
|
|
||||||
*
|
|
||||||
* @param file The file containing the JSON string containing the options.
|
|
||||||
* @see setOptions
|
|
||||||
*/
|
|
||||||
fun PatchList.setOptions(file: File) = setOptions(file.readText())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data class for a patch and its [Option]s.
|
|
||||||
*
|
|
||||||
* @property patchName The name of the patch.
|
|
||||||
* @property options The [Option]s for the patch.
|
|
||||||
*/
|
|
||||||
internal data class PatchOption(
|
|
||||||
val patchName: String,
|
|
||||||
val options: List<Option>
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data class for patch option.
|
|
||||||
*
|
|
||||||
* @property key The name of the option.
|
|
||||||
* @property value The value of the option.
|
|
||||||
*/
|
|
||||||
internal data class Option(val key: String, val value: Any?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
package app.revanced.utils.adb
|
|
||||||
|
|
||||||
import app.revanced.utils.adb.AdbManager.Apk
|
|
||||||
import app.revanced.utils.adb.Constants.CREATE_DIR
|
|
||||||
import app.revanced.utils.adb.Constants.DELETE
|
|
||||||
import app.revanced.utils.adb.Constants.INSTALLATION_PATH
|
|
||||||
import app.revanced.utils.adb.Constants.INSTALL_MOUNT
|
|
||||||
import app.revanced.utils.adb.Constants.INSTALL_PATCHED_APK
|
|
||||||
import app.revanced.utils.adb.Constants.MOUNT_PATH
|
|
||||||
import app.revanced.utils.adb.Constants.MOUNT_SCRIPT
|
|
||||||
import app.revanced.utils.adb.Constants.PATCHED_APK_PATH
|
|
||||||
import app.revanced.utils.adb.Constants.PLACEHOLDER
|
|
||||||
import app.revanced.utils.adb.Constants.RESTART
|
|
||||||
import app.revanced.utils.adb.Constants.TMP_PATH
|
|
||||||
import app.revanced.utils.adb.Constants.UMOUNT
|
|
||||||
import se.vidstige.jadb.JadbConnection
|
|
||||||
import se.vidstige.jadb.managers.Package
|
|
||||||
import se.vidstige.jadb.managers.PackageManager
|
|
||||||
import java.io.Closeable
|
|
||||||
import java.io.File
|
|
||||||
import java.util.logging.Logger
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adb manager. Used to install and uninstall [Apk] files.
|
|
||||||
*
|
|
||||||
* @param deviceSerial The serial of the device.
|
|
||||||
*/
|
|
||||||
internal sealed class AdbManager(deviceSerial: String? = null) : Closeable {
|
|
||||||
protected val logger: Logger = Logger.getLogger(AdbManager::class.java.name)
|
|
||||||
|
|
||||||
protected val device = JadbConnection().devices.find { device -> device.serial == deviceSerial }
|
|
||||||
?: throw DeviceNotFoundException(deviceSerial)
|
|
||||||
|
|
||||||
init {
|
|
||||||
logger.fine("Established connection to $deviceSerial")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs the [Apk] file.
|
|
||||||
*
|
|
||||||
* @param apk The [Apk] file.
|
|
||||||
*/
|
|
||||||
open fun install(apk: Apk) {
|
|
||||||
logger.info("Finished installing ${apk.file.name}")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstalls the package.
|
|
||||||
*
|
|
||||||
* @param packageName The package name.
|
|
||||||
*/
|
|
||||||
open fun uninstall(packageName: String) {
|
|
||||||
logger.info("Finished uninstalling $packageName")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the [AdbManager] instance.
|
|
||||||
*/
|
|
||||||
override fun close() {
|
|
||||||
logger.fine("Closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
class RootAdbManager(deviceSerial: String) : AdbManager(deviceSerial) {
|
|
||||||
init {
|
|
||||||
if (!device.hasSu()) throw IllegalArgumentException("Root required on $deviceSerial. Task failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun install(apk: Apk) {
|
|
||||||
logger.info("Installing by mounting")
|
|
||||||
|
|
||||||
val applyReplacement = getPlaceholderReplacement(
|
|
||||||
apk.packageName ?: throw IllegalArgumentException("Package name is required")
|
|
||||||
)
|
|
||||||
|
|
||||||
device.push(apk.file, TMP_PATH)
|
|
||||||
|
|
||||||
device.run("$CREATE_DIR $INSTALLATION_PATH")
|
|
||||||
device.run(INSTALL_PATCHED_APK.applyReplacement())
|
|
||||||
|
|
||||||
device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement())
|
|
||||||
|
|
||||||
device.run(INSTALL_MOUNT.applyReplacement())
|
|
||||||
device.run(UMOUNT.applyReplacement()) // Sanity check.
|
|
||||||
device.run(MOUNT_PATH.applyReplacement())
|
|
||||||
device.run(RESTART.applyReplacement())
|
|
||||||
device.run(DELETE.applyReplacement(TMP_PATH).applyReplacement())
|
|
||||||
|
|
||||||
super.install(apk)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun uninstall(packageName: String) {
|
|
||||||
logger.info("Uninstalling $packageName by unmounting")
|
|
||||||
|
|
||||||
val applyReplacement = getPlaceholderReplacement(packageName)
|
|
||||||
|
|
||||||
device.run(UMOUNT.applyReplacement(packageName))
|
|
||||||
device.run(DELETE.applyReplacement(PATCHED_APK_PATH).applyReplacement())
|
|
||||||
device.run(DELETE.applyReplacement(MOUNT_PATH).applyReplacement())
|
|
||||||
device.run(DELETE.applyReplacement(TMP_PATH).applyReplacement())
|
|
||||||
|
|
||||||
super.uninstall(packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object Utils {
|
|
||||||
private fun getPlaceholderReplacement(with: String): String.() -> String = { replace(PLACEHOLDER, with) }
|
|
||||||
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserAdbManager(deviceSerial: String) : AdbManager(deviceSerial) {
|
|
||||||
private val packageManager = PackageManager(device)
|
|
||||||
|
|
||||||
override fun install(apk: Apk) {
|
|
||||||
PackageManager(device).install(apk.file)
|
|
||||||
|
|
||||||
super.install(apk)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun uninstall(packageName: String) {
|
|
||||||
logger.info("Uninstalling $packageName")
|
|
||||||
|
|
||||||
packageManager.uninstall(Package(packageName))
|
|
||||||
|
|
||||||
super.uninstall(packageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apk file for [AdbManager].
|
|
||||||
*
|
|
||||||
* @param file The [Apk] file.
|
|
||||||
* @param packageName The package name of the [Apk] file.
|
|
||||||
*/
|
|
||||||
internal class Apk(val file: File, val packageName: String? = null)
|
|
||||||
|
|
||||||
internal class DeviceNotFoundException(deviceSerial: String?) :
|
|
||||||
Exception(deviceSerial?.let {
|
|
||||||
"The device with the ADB device serial \"$deviceSerial\" can not be found"
|
|
||||||
} ?: "No ADB device found")
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package app.revanced.utils.adb
|
|
||||||
|
|
||||||
import se.vidstige.jadb.JadbDevice
|
|
||||||
import se.vidstige.jadb.RemoteFile
|
|
||||||
import se.vidstige.jadb.ShellProcessBuilder
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
internal fun JadbDevice.buildCommand(command: String, su: Boolean = true): ShellProcessBuilder {
|
|
||||||
if (su) return shellProcessBuilder("su -c \'$command\'")
|
|
||||||
|
|
||||||
val args = command.split(" ") as ArrayList<String>
|
|
||||||
val cmd = args.removeFirst()
|
|
||||||
|
|
||||||
return shellProcessBuilder(cmd, *args.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun JadbDevice.run(command: String, su: Boolean = true): Int {
|
|
||||||
return this.buildCommand(command, su).start().waitFor()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun JadbDevice.hasSu() =
|
|
||||||
this.startCommand("su -h", false).waitFor() == 0
|
|
||||||
|
|
||||||
internal fun JadbDevice.push(file: File, targetFilePath: String) =
|
|
||||||
push(file, RemoteFile(targetFilePath))
|
|
||||||
|
|
||||||
internal fun JadbDevice.createFile(targetFile: String, content: String) =
|
|
||||||
push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile))
|
|
||||||
|
|
||||||
|
|
||||||
private fun JadbDevice.startCommand(command: String, su: Boolean) =
|
|
||||||
shellProcessBuilder(if (su) "su -c '$command'" else command).start()
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package app.revanced.utils.adb
|
|
||||||
|
|
||||||
internal object Constants {
|
|
||||||
internal const val PLACEHOLDER = "PLACEHOLDER"
|
|
||||||
|
|
||||||
internal const val TMP_PATH = "/data/local/tmp/revanced.tmp"
|
|
||||||
internal const val INSTALLATION_PATH = "/data/adb/revanced/"
|
|
||||||
internal const val PATCHED_APK_PATH = "$INSTALLATION_PATH$PLACEHOLDER.apk"
|
|
||||||
internal const val MOUNT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
|
|
||||||
|
|
||||||
internal const val DELETE = "rm -rf $PLACEHOLDER"
|
|
||||||
internal const val CREATE_DIR = "mkdir -p"
|
|
||||||
internal const val RESTART = "pm resolve-activity --brief $PLACEHOLDER | tail -n 1 | " +
|
|
||||||
"xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)"
|
|
||||||
|
|
||||||
internal const val INSTALL_PATCHED_APK = "base_path=\"$PATCHED_APK_PATH\" && " +
|
|
||||||
"mv $TMP_PATH ${'$'}base_path && " +
|
|
||||||
"chmod 644 ${'$'}base_path && " +
|
|
||||||
"chown system:system ${'$'}base_path && " +
|
|
||||||
"chcon u:object_r:apk_data_file:s0 ${'$'}base_path"
|
|
||||||
|
|
||||||
internal const val UMOUNT =
|
|
||||||
"grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d \" \" -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done"
|
|
||||||
|
|
||||||
internal const val INSTALL_MOUNT = "mv $TMP_PATH $MOUNT_PATH && chmod +x $MOUNT_PATH"
|
|
||||||
|
|
||||||
internal val MOUNT_SCRIPT =
|
|
||||||
"""
|
|
||||||
#!/system/bin/sh
|
|
||||||
MAGISKTMP="${'$'}(magisk --path)" || MAGISKTMP=/sbin
|
|
||||||
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
|
|
||||||
while [ "${'$'}(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
|
|
||||||
|
|
||||||
base_path="$PATCHED_APK_PATH"
|
|
||||||
stock_path=${'$'}( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
|
|
||||||
|
|
||||||
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
|
|
||||||
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package app.revanced.utils.align
|
|
||||||
|
|
||||||
import app.revanced.utils.align.zip.structures.ZipEntry
|
|
||||||
|
|
||||||
internal object ZipAligner {
|
|
||||||
private const val DEFAULT_ALIGNMENT = 4
|
|
||||||
private const val LIBRARY_ALIGNMENT = 4096
|
|
||||||
|
|
||||||
fun getEntryAlignment(entry: ZipEntry): Int? =
|
|
||||||
if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package app.revanced.utils.align.zip
|
|
||||||
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.io.DataOutput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
fun UInt.toLittleEndian() =
|
|
||||||
(((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt()
|
|
||||||
|
|
||||||
fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
|
|
||||||
|
|
||||||
fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8)
|
|
||||||
or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt()
|
|
||||||
|
|
||||||
fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
|
|
||||||
|
|
||||||
fun ByteBuffer.getUShort() = this.getShort().toUShort()
|
|
||||||
fun ByteBuffer.getUInt() = this.getInt().toUInt()
|
|
||||||
|
|
||||||
fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort())
|
|
||||||
fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt())
|
|
||||||
|
|
||||||
fun DataInput.readUShort() = this.readShort().toUShort()
|
|
||||||
fun DataInput.readUInt() = this.readInt().toUInt()
|
|
||||||
|
|
||||||
fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
|
|
||||||
fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
|
|
||||||
|
|
||||||
fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
|
|
||||||
fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
|
|
||||||
|
|
||||||
fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
|
|
||||||
fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package app.revanced.utils.align.zip
|
|
||||||
|
|
||||||
import app.revanced.utils.align.zip.structures.ZipEndRecord
|
|
||||||
import app.revanced.utils.align.zip.structures.ZipEntry
|
|
||||||
import java.io.Closeable
|
|
||||||
import java.io.File
|
|
||||||
import java.io.RandomAccessFile
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.channels.FileChannel
|
|
||||||
import java.util.zip.CRC32
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
|
|
||||||
class ZipFile(file: File) : Closeable {
|
|
||||||
private var entries: MutableList<ZipEntry> = mutableListOf()
|
|
||||||
|
|
||||||
private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
|
|
||||||
private var centralDirectoryNeedsRewrite = false
|
|
||||||
|
|
||||||
private val compressionLevel = 5
|
|
||||||
|
|
||||||
init {
|
|
||||||
// If file isn't empty try to load entries.
|
|
||||||
if (file.length() > 0) {
|
|
||||||
val endRecord = findEndRecord()
|
|
||||||
|
|
||||||
if (endRecord.diskNumber > 0u || endRecord.totalEntries != endRecord.diskEntries)
|
|
||||||
throw IllegalArgumentException("Multi-file archives are not supported")
|
|
||||||
|
|
||||||
entries = readEntries(endRecord).toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek back to start for writing.
|
|
||||||
filePointer.seek(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findEndRecord(): ZipEndRecord {
|
|
||||||
// Look from end to start since end record is at the end.
|
|
||||||
for (i in filePointer.length() - 1 downTo 0) {
|
|
||||||
filePointer.seek(i)
|
|
||||||
// Possible beginning of signature.
|
|
||||||
if (filePointer.readByte() == 0x50.toByte()) {
|
|
||||||
// Seek back to get the full int.
|
|
||||||
filePointer.seek(i)
|
|
||||||
val possibleSignature = filePointer.readUIntLE()
|
|
||||||
if (possibleSignature == ZipEndRecord.ECD_SIGNATURE) {
|
|
||||||
filePointer.seek(i)
|
|
||||||
return ZipEndRecord.fromECD(filePointer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception("Couldn't find end record")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readEntries(endRecord: ZipEndRecord): List<ZipEntry> {
|
|
||||||
filePointer.seek(endRecord.centralDirectoryStartOffset.toLong())
|
|
||||||
|
|
||||||
val numberOfEntries = endRecord.diskEntries.toInt()
|
|
||||||
|
|
||||||
return buildList(numberOfEntries) {
|
|
||||||
for (i in 1..numberOfEntries) {
|
|
||||||
add(
|
|
||||||
ZipEntry.fromCDE(filePointer).also
|
|
||||||
{
|
|
||||||
//for some reason the local extra field can be different from the central one
|
|
||||||
it.readLocalExtra(
|
|
||||||
filePointer.channel.map(
|
|
||||||
FileChannel.MapMode.READ_ONLY,
|
|
||||||
it.localHeaderOffset.toLong() + 28,
|
|
||||||
2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeCD() {
|
|
||||||
val centralDirectoryStartOffset = filePointer.channel.position().toUInt()
|
|
||||||
|
|
||||||
entries.forEach {
|
|
||||||
filePointer.channel.write(it.toCDE())
|
|
||||||
}
|
|
||||||
|
|
||||||
val entriesCount = entries.size.toUShort()
|
|
||||||
|
|
||||||
val endRecord = ZipEndRecord(
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
entriesCount,
|
|
||||||
entriesCount,
|
|
||||||
filePointer.channel.position().toUInt() - centralDirectoryStartOffset,
|
|
||||||
centralDirectoryStartOffset,
|
|
||||||
""
|
|
||||||
)
|
|
||||||
|
|
||||||
filePointer.channel.write(endRecord.toECD())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
|
||||||
centralDirectoryNeedsRewrite = true
|
|
||||||
|
|
||||||
entry.localHeaderOffset = filePointer.channel.position().toUInt()
|
|
||||||
|
|
||||||
filePointer.channel.write(entry.toLFH())
|
|
||||||
filePointer.channel.write(data)
|
|
||||||
|
|
||||||
entries.add(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
|
|
||||||
val compressor = Deflater(compressionLevel, true)
|
|
||||||
compressor.setInput(data)
|
|
||||||
compressor.finish()
|
|
||||||
|
|
||||||
val uncompressedSize = data.size
|
|
||||||
val compressedData = ByteArray(uncompressedSize) // I'm guessing compression won't make the data bigger.
|
|
||||||
|
|
||||||
val compressedDataLength = compressor.deflate(compressedData)
|
|
||||||
val compressedBuffer =
|
|
||||||
ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
|
|
||||||
|
|
||||||
compressor.end()
|
|
||||||
|
|
||||||
val crc = CRC32()
|
|
||||||
crc.update(data)
|
|
||||||
|
|
||||||
entry.compression = 8u // Deflate compression.
|
|
||||||
entry.uncompressedSize = uncompressedSize.toUInt()
|
|
||||||
entry.compressedSize = compressedDataLength.toUInt()
|
|
||||||
entry.crc32 = crc.value.toUInt()
|
|
||||||
|
|
||||||
addEntry(entry, compressedBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) {
|
|
||||||
alignment?.let {
|
|
||||||
// Calculate where data would end up.
|
|
||||||
val dataOffset = filePointer.filePointer + entry.LFHSize
|
|
||||||
|
|
||||||
val mod = dataOffset % alignment
|
|
||||||
|
|
||||||
// Wrong alignment.
|
|
||||||
if (mod != 0L) {
|
|
||||||
// Add padding at end of extra field.
|
|
||||||
entry.localExtraField =
|
|
||||||
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addEntry(entry, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDataForEntry(entry: ZipEntry): ByteBuffer {
|
|
||||||
return filePointer.channel.map(
|
|
||||||
FileChannel.MapMode.READ_ONLY,
|
|
||||||
entry.dataOffset.toLong(),
|
|
||||||
entry.compressedSize.toLong()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies all entries from [file] to this file but skip already existing entries.
|
|
||||||
*
|
|
||||||
* @param file The file to copy entries from.
|
|
||||||
* @param entryAlignment A function that returns the alignment for a given entry.
|
|
||||||
*/
|
|
||||||
fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
|
|
||||||
for (entry in file.entries) {
|
|
||||||
if (entries.any { it.fileName == entry.fileName }) continue // Skip duplicates
|
|
||||||
|
|
||||||
val data = file.getDataForEntry(entry)
|
|
||||||
addEntryCopyData(entry, data, entryAlignment(entry))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
if (centralDirectoryNeedsRewrite) writeCD()
|
|
||||||
filePointer.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package app.revanced.utils.align.zip.structures
|
|
||||||
|
|
||||||
import app.revanced.utils.align.zip.putUInt
|
|
||||||
import app.revanced.utils.align.zip.putUShort
|
|
||||||
import app.revanced.utils.align.zip.readUIntLE
|
|
||||||
import app.revanced.utils.align.zip.readUShortLE
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
data class ZipEndRecord(
|
|
||||||
val diskNumber: UShort,
|
|
||||||
val startingDiskNumber: UShort,
|
|
||||||
val diskEntries: UShort,
|
|
||||||
val totalEntries: UShort,
|
|
||||||
val centralDirectorySize: UInt,
|
|
||||||
val centralDirectoryStartOffset: UInt,
|
|
||||||
val fileComment: String,
|
|
||||||
) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val ECD_HEADER_SIZE = 22
|
|
||||||
const val ECD_SIGNATURE = 0x06054b50u
|
|
||||||
|
|
||||||
fun fromECD(input: DataInput): ZipEndRecord {
|
|
||||||
val signature = input.readUIntLE()
|
|
||||||
|
|
||||||
if (signature != ECD_SIGNATURE)
|
|
||||||
throw IllegalArgumentException("Input doesn't start with end record signature")
|
|
||||||
|
|
||||||
val diskNumber = input.readUShortLE()
|
|
||||||
val startingDiskNumber = input.readUShortLE()
|
|
||||||
val diskEntries = input.readUShortLE()
|
|
||||||
val totalEntries = input.readUShortLE()
|
|
||||||
val centralDirectorySize = input.readUIntLE()
|
|
||||||
val centralDirectoryStartOffset = input.readUIntLE()
|
|
||||||
val fileCommentLength = input.readUShortLE()
|
|
||||||
var fileComment = ""
|
|
||||||
|
|
||||||
if (fileCommentLength > 0u) {
|
|
||||||
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
|
||||||
input.readFully(fileCommentBytes)
|
|
||||||
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ZipEndRecord(
|
|
||||||
diskNumber,
|
|
||||||
startingDiskNumber,
|
|
||||||
diskEntries,
|
|
||||||
totalEntries,
|
|
||||||
centralDirectorySize,
|
|
||||||
centralDirectoryStartOffset,
|
|
||||||
fileComment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toECD(): ByteBuffer {
|
|
||||||
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size).also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
|
||||||
|
|
||||||
buffer.putUInt(ECD_SIGNATURE)
|
|
||||||
buffer.putUShort(diskNumber)
|
|
||||||
buffer.putUShort(startingDiskNumber)
|
|
||||||
buffer.putUShort(diskEntries)
|
|
||||||
buffer.putUShort(totalEntries)
|
|
||||||
buffer.putUInt(centralDirectorySize)
|
|
||||||
buffer.putUInt(centralDirectoryStartOffset)
|
|
||||||
buffer.putUShort(commentBytes.size.toUShort())
|
|
||||||
|
|
||||||
buffer.put(commentBytes)
|
|
||||||
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
package app.revanced.utils.align.zip.structures
|
|
||||||
|
|
||||||
import app.revanced.utils.align.zip.*
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
data class ZipEntry(
|
|
||||||
val version: UShort,
|
|
||||||
val versionNeeded: UShort,
|
|
||||||
val flags: UShort,
|
|
||||||
var compression: UShort,
|
|
||||||
val modificationTime: UShort,
|
|
||||||
val modificationDate: UShort,
|
|
||||||
var crc32: UInt,
|
|
||||||
var compressedSize: UInt,
|
|
||||||
var uncompressedSize: UInt,
|
|
||||||
val diskNumber: UShort,
|
|
||||||
val internalAttributes: UShort,
|
|
||||||
val externalAttributes: UInt,
|
|
||||||
var localHeaderOffset: UInt,
|
|
||||||
val fileName: String,
|
|
||||||
val extraField: ByteArray,
|
|
||||||
val fileComment: String,
|
|
||||||
var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
|
||||||
) {
|
|
||||||
val LFHSize: Int
|
|
||||||
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
|
||||||
|
|
||||||
val dataOffset: UInt
|
|
||||||
get() = localHeaderOffset + LFHSize.toUInt()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val CDE_HEADER_SIZE = 46
|
|
||||||
const val CDE_SIGNATURE = 0x02014b50u
|
|
||||||
|
|
||||||
const val LFH_HEADER_SIZE = 30
|
|
||||||
const val LFH_SIGNATURE = 0x04034b50u
|
|
||||||
|
|
||||||
fun createWithName(fileName: String): ZipEntry {
|
|
||||||
return ZipEntry(
|
|
||||||
0x1403u, //made by unix, version 20
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0x0821u, //seems to be static time google uses, no idea
|
|
||||||
0x0221u, //same as above
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
fileName,
|
|
||||||
ByteArray(0),
|
|
||||||
""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromCDE(input: DataInput): ZipEntry {
|
|
||||||
val signature = input.readUIntLE()
|
|
||||||
|
|
||||||
if (signature != CDE_SIGNATURE)
|
|
||||||
throw IllegalArgumentException("Input doesn't start with central directory entry signature")
|
|
||||||
|
|
||||||
val version = input.readUShortLE()
|
|
||||||
val versionNeeded = input.readUShortLE()
|
|
||||||
var flags = input.readUShortLE()
|
|
||||||
val compression = input.readUShortLE()
|
|
||||||
val modificationTime = input.readUShortLE()
|
|
||||||
val modificationDate = input.readUShortLE()
|
|
||||||
val crc32 = input.readUIntLE()
|
|
||||||
val compressedSize = input.readUIntLE()
|
|
||||||
val uncompressedSize = input.readUIntLE()
|
|
||||||
val fileNameLength = input.readUShortLE()
|
|
||||||
var fileName = ""
|
|
||||||
val extraFieldLength = input.readUShortLE()
|
|
||||||
val extraField = ByteArray(extraFieldLength.toInt())
|
|
||||||
val fileCommentLength = input.readUShortLE()
|
|
||||||
var fileComment = ""
|
|
||||||
val diskNumber = input.readUShortLE()
|
|
||||||
val internalAttributes = input.readUShortLE()
|
|
||||||
val externalAttributes = input.readUIntLE()
|
|
||||||
val localHeaderOffset = input.readUIntLE()
|
|
||||||
|
|
||||||
val variableFieldsLength =
|
|
||||||
fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
|
|
||||||
|
|
||||||
if (variableFieldsLength > 0) {
|
|
||||||
val fileNameBytes = ByteArray(fileNameLength.toInt())
|
|
||||||
input.readFully(fileNameBytes)
|
|
||||||
fileName = fileNameBytes.toString(Charsets.UTF_8)
|
|
||||||
|
|
||||||
input.readFully(extraField)
|
|
||||||
|
|
||||||
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
|
||||||
input.readFully(fileCommentBytes)
|
|
||||||
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
flags = (flags and 0b1000u.inv()
|
|
||||||
.toUShort()) //disable data descriptor flag as they are not used
|
|
||||||
|
|
||||||
return ZipEntry(
|
|
||||||
version,
|
|
||||||
versionNeeded,
|
|
||||||
flags,
|
|
||||||
compression,
|
|
||||||
modificationTime,
|
|
||||||
modificationDate,
|
|
||||||
crc32,
|
|
||||||
compressedSize,
|
|
||||||
uncompressedSize,
|
|
||||||
diskNumber,
|
|
||||||
internalAttributes,
|
|
||||||
externalAttributes,
|
|
||||||
localHeaderOffset,
|
|
||||||
fileName,
|
|
||||||
extraField,
|
|
||||||
fileComment,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readLocalExtra(buffer: ByteBuffer) {
|
|
||||||
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
localExtraField = ByteArray(buffer.getUShort().toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toLFH(): ByteBuffer {
|
|
||||||
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size)
|
|
||||||
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
|
||||||
|
|
||||||
buffer.putUInt(LFH_SIGNATURE)
|
|
||||||
buffer.putUShort(versionNeeded)
|
|
||||||
buffer.putUShort(flags)
|
|
||||||
buffer.putUShort(compression)
|
|
||||||
buffer.putUShort(modificationTime)
|
|
||||||
buffer.putUShort(modificationDate)
|
|
||||||
buffer.putUInt(crc32)
|
|
||||||
buffer.putUInt(compressedSize)
|
|
||||||
buffer.putUInt(uncompressedSize)
|
|
||||||
buffer.putUShort(nameBytes.size.toUShort())
|
|
||||||
buffer.putUShort(localExtraField.size.toUShort())
|
|
||||||
|
|
||||||
buffer.put(nameBytes)
|
|
||||||
buffer.put(localExtraField)
|
|
||||||
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toCDE(): ByteBuffer {
|
|
||||||
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
|
||||||
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val buffer =
|
|
||||||
ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
|
|
||||||
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
|
||||||
|
|
||||||
buffer.putUInt(CDE_SIGNATURE)
|
|
||||||
buffer.putUShort(version)
|
|
||||||
buffer.putUShort(versionNeeded)
|
|
||||||
buffer.putUShort(flags)
|
|
||||||
buffer.putUShort(compression)
|
|
||||||
buffer.putUShort(modificationTime)
|
|
||||||
buffer.putUShort(modificationDate)
|
|
||||||
buffer.putUInt(crc32)
|
|
||||||
buffer.putUInt(compressedSize)
|
|
||||||
buffer.putUInt(uncompressedSize)
|
|
||||||
buffer.putUShort(nameBytes.size.toUShort())
|
|
||||||
buffer.putUShort(extraField.size.toUShort())
|
|
||||||
buffer.putUShort(commentBytes.size.toUShort())
|
|
||||||
buffer.putUShort(diskNumber)
|
|
||||||
buffer.putUShort(internalAttributes)
|
|
||||||
buffer.putUInt(externalAttributes)
|
|
||||||
buffer.putUInt(localHeaderOffset)
|
|
||||||
|
|
||||||
buffer.put(nameBytes)
|
|
||||||
buffer.put(extraField)
|
|
||||||
buffer.put(commentBytes)
|
|
||||||
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package app.revanced.utils.signing
|
|
||||||
|
|
||||||
import com.android.apksig.ApkSigner
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
|
||||||
import org.bouncycastle.operator.ContentSigner
|
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.math.BigInteger
|
|
||||||
import java.security.*
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.util.*
|
|
||||||
import java.util.logging.Logger
|
|
||||||
|
|
||||||
internal class ApkSigner(
|
|
||||||
private val signingOptions: SigningOptions
|
|
||||||
) {
|
|
||||||
private val logger = Logger.getLogger(ApkSigner::class.java.name)
|
|
||||||
|
|
||||||
private val signer: ApkSigner.Builder
|
|
||||||
private val passwordCharArray = signingOptions.password.toCharArray()
|
|
||||||
|
|
||||||
init {
|
|
||||||
Security.addProvider(BouncyCastleProvider())
|
|
||||||
|
|
||||||
val keyStore = KeyStore.getInstance("BKS", "BC")
|
|
||||||
val alias = keyStore.let { store ->
|
|
||||||
FileInputStream(File(signingOptions.keyStoreFilePath).also {
|
|
||||||
if (!it.exists()) {
|
|
||||||
logger.info("Creating keystore at ${it.absolutePath}")
|
|
||||||
newKeystore(it)
|
|
||||||
} else {
|
|
||||||
logger.info("Using keystore at ${it.absolutePath}")
|
|
||||||
}
|
|
||||||
}).use { fis -> store.load(fis, null) }
|
|
||||||
store.aliases().nextElement()
|
|
||||||
}
|
|
||||||
|
|
||||||
with(
|
|
||||||
ApkSigner.SignerConfig.Builder(
|
|
||||||
signingOptions.cn,
|
|
||||||
keyStore.getKey(alias, passwordCharArray) as PrivateKey,
|
|
||||||
listOf(keyStore.getCertificate(alias) as X509Certificate)
|
|
||||||
).build()
|
|
||||||
) {
|
|
||||||
this@ApkSigner.signer = ApkSigner.Builder(listOf(this))
|
|
||||||
signer.setCreatedBy(signingOptions.cn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newKeystore(out: File) {
|
|
||||||
val (publicKey, privateKey) = createKey()
|
|
||||||
val privateKS = KeyStore.getInstance("BKS", "BC")
|
|
||||||
privateKS.load(null, passwordCharArray)
|
|
||||||
privateKS.setKeyEntry("alias", privateKey, passwordCharArray, arrayOf(publicKey))
|
|
||||||
privateKS.store(FileOutputStream(out), passwordCharArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createKey(): Pair<X509Certificate, PrivateKey> {
|
|
||||||
val gen = KeyPairGenerator.getInstance("RSA")
|
|
||||||
gen.initialize(2048)
|
|
||||||
val pair = gen.generateKeyPair()
|
|
||||||
var serialNumber: BigInteger
|
|
||||||
do serialNumber = BigInteger.valueOf(SecureRandom().nextLong()) while (serialNumber < BigInteger.ZERO)
|
|
||||||
val x500Name = X500Name("CN=${signingOptions.cn}")
|
|
||||||
val builder = X509v3CertificateBuilder(
|
|
||||||
x500Name,
|
|
||||||
serialNumber,
|
|
||||||
Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L),
|
|
||||||
Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * 30L),
|
|
||||||
Locale.ENGLISH,
|
|
||||||
x500Name,
|
|
||||||
SubjectPublicKeyInfo.getInstance(pair.public.encoded)
|
|
||||||
)
|
|
||||||
val signer: ContentSigner = JcaContentSignerBuilder("SHA256withRSA").build(pair.private)
|
|
||||||
return JcaX509CertificateConverter().getCertificate(builder.build(signer)) to pair.private
|
|
||||||
}
|
|
||||||
|
|
||||||
fun signApk(input: File, output: File): File {
|
|
||||||
signer.setInputApk(input)
|
|
||||||
signer.setOutputApk(output)
|
|
||||||
|
|
||||||
signer.build().sign()
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package app.revanced.utils.signing
|
|
||||||
|
|
||||||
data class SigningOptions(
|
|
||||||
val cn: String,
|
|
||||||
val password: String,
|
|
||||||
val keyStoreFilePath: String
|
|
||||||
)
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package app.revanced.patcher.options
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
|
||||||
import app.revanced.patcher.data.Context
|
|
||||||
import app.revanced.patcher.patch.BytecodePatch
|
|
||||||
import app.revanced.patcher.patch.OptionsContainer
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
import app.revanced.patcher.patch.PatchOption
|
|
||||||
import app.revanced.utils.Options
|
|
||||||
import app.revanced.utils.Options.setOptions
|
|
||||||
import org.junit.jupiter.api.MethodOrderer
|
|
||||||
import org.junit.jupiter.api.Order
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestMethodOrder
|
|
||||||
|
|
||||||
class PatchOptionsTestPatch : BytecodePatch() {
|
|
||||||
override fun execute(context: BytecodeContext) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : OptionsContainer() {
|
|
||||||
var key1 by option(
|
|
||||||
PatchOption.StringOption(
|
|
||||||
"key1", null, "title1", "description1"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
var key2 by option(
|
|
||||||
PatchOption.BooleanOption(
|
|
||||||
"key2", true, "title2", "description2"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
|
||||||
internal object PatchOptionOptionsTest {
|
|
||||||
private var patches = listOf(PatchOptionsTestPatch::class.java as Class<out Patch<Context<*>>>)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
fun serializeTest() {
|
|
||||||
assert(SERIALIZED_JSON == Options.serialize(patches))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
fun loadOptionsTest() {
|
|
||||||
patches.setOptions(CHANGED_JSON)
|
|
||||||
|
|
||||||
assert(PatchOptionsTestPatch.key1 == "test")
|
|
||||||
assert(PatchOptionsTestPatch.key2 == false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val SERIALIZED_JSON =
|
|
||||||
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\",\"value\":true}]}]"
|
|
||||||
|
|
||||||
private const val CHANGED_JSON =
|
|
||||||
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2\",\"value\":false}]}]"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user