mirror of
https://github.com/ReVanced/revanced-library.git
synced 2026-01-11 22:06:18 +00:00
Compare commits
173 Commits
v1.1.2-dev
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c71fc671ae | ||
|
|
0673307204 | ||
|
|
5df2bb81bf | ||
|
|
be0f6bf247 | ||
|
|
9d060c188f | ||
|
|
254f36d03c | ||
|
|
4065c87d5f | ||
|
|
be8d7bf643 | ||
|
|
2328902b6b | ||
|
|
7ec6504619 | ||
|
|
e7a98b5795 | ||
|
|
649f06b19d | ||
|
|
cace51700a | ||
|
|
91cefc8598 | ||
|
|
735c1e39cd | ||
|
|
84cc315541 | ||
|
|
4fe9304570 | ||
|
|
8bb41be8fc | ||
|
|
4b8ac026c3 | ||
|
|
557b6035f8 | ||
|
|
bfc5394b4e | ||
|
|
5b1cf1f190 | ||
|
|
dd5c37ddec | ||
|
|
9adccc04dd | ||
|
|
ed94d29461 | ||
|
|
efc72cdc55 | ||
|
|
737e272481 | ||
|
|
92ff93d6e6 | ||
|
|
d56126aa58 | ||
|
|
079776f241 | ||
|
|
7a554a85a8 | ||
|
|
b9bf3bc882 | ||
|
|
27b3359d66 | ||
|
|
893d22d793 | ||
|
|
7f5d6dad7b | ||
|
|
8aca650ebc | ||
|
|
db59d2cd0b | ||
|
|
fe3e1c9dc8 | ||
|
|
825e6490fe | ||
|
|
43d655aea5 | ||
|
|
8d0e4b2470 | ||
|
|
4486c59674 | ||
|
|
8e4d5765e1 | ||
|
|
b15efa41f8 | ||
|
|
af0aba4a8e | ||
|
|
4c6a6360cf | ||
|
|
80e35270c4 | ||
|
|
13b6a846d7 | ||
|
|
2df3484b68 | ||
|
|
f2959b610a | ||
|
|
2a30845f61 | ||
|
|
1b4d87e563 | ||
|
|
7f7dfdd5b5 | ||
|
|
56773fa3d3 | ||
|
|
249372c31f | ||
|
|
e7bed8565e | ||
|
|
4a24e2e92d | ||
|
|
fca8a3f4c0 | ||
|
|
f59ecbccd1 | ||
|
|
c92be32607 | ||
|
|
51109c4768 | ||
|
|
f2bd3f5eee | ||
|
|
cc5ee29d14 | ||
|
|
c4bad9a653 | ||
|
|
3f713cf76b | ||
|
|
dfe48a24bf | ||
|
|
132ad13670 | ||
|
|
1670ceff9f | ||
|
|
41805fcb0b | ||
|
|
0e9939f70d | ||
|
|
f28a7ddeec | ||
|
|
3e574e723c | ||
|
|
a73ca721f3 | ||
|
|
393d74b7d9 | ||
|
|
51d6e9976c | ||
|
|
24cecb1e57 | ||
|
|
d4938c57e8 | ||
|
|
1526f98f53 | ||
|
|
244ebc2186 | ||
|
|
486a7acff2 | ||
|
|
f0b8bc5438 | ||
|
|
806f45c77f | ||
|
|
259bcd04c9 | ||
|
|
0d040dcfd0 | ||
|
|
13af960de1 | ||
|
|
d21fd5798c | ||
|
|
1c348f3621 | ||
|
|
0c02680b70 | ||
|
|
b86b8aacea | ||
|
|
3aa6dc223a | ||
|
|
c664a6eaed | ||
|
|
d18e436de1 | ||
|
|
9ff06a2f1b | ||
|
|
57e36ab5c1 | ||
|
|
8d934bde00 | ||
|
|
d1a7699256 | ||
|
|
e08d0ad8f3 | ||
|
|
4a14c5ccaa | ||
|
|
4bbe2fa150 | ||
|
|
938eac53b1 | ||
|
|
ab004b91f0 | ||
|
|
b637e0d7d7 | ||
|
|
9fda407441 | ||
|
|
44f7c13a77 | ||
|
|
ecff6fe0d3 | ||
|
|
3515e331ac | ||
|
|
a921c40306 | ||
|
|
b6e1de5eaf | ||
|
|
bf5780e2f7 | ||
|
|
99e90c02ed | ||
|
|
ac3917fecc | ||
|
|
98b7b347cb | ||
|
|
cdf94ffeca | ||
|
|
37434cf4a4 | ||
|
|
73c97abedd | ||
|
|
762b7e3bc0 | ||
|
|
e4be6dbccd | ||
|
|
96845ba265 | ||
|
|
34171b534b | ||
|
|
c5f3536cbb | ||
|
|
893274074b | ||
|
|
b6c09d42ae | ||
|
|
fe8a2334e6 | ||
|
|
a9e5966145 | ||
|
|
50c2b3921c | ||
|
|
10f8cd1470 | ||
|
|
1a5f868ecd | ||
|
|
969ca16b77 | ||
|
|
4fe0fb0a61 | ||
|
|
462a35ddcd | ||
|
|
00e1895c0f | ||
|
|
75d4c658b9 | ||
|
|
2e54af8f82 | ||
|
|
143c13b175 | ||
|
|
ed8ab35d60 | ||
|
|
97bf79be27 | ||
|
|
1cbf407ce2 | ||
|
|
5b3a597ad7 | ||
|
|
b0fe1c8bfc | ||
|
|
3331a3cc3a | ||
|
|
a00146c6f1 | ||
|
|
e23d21c982 | ||
|
|
90b612bee8 | ||
|
|
49e6081286 | ||
|
|
de4276630b | ||
|
|
9d39995f48 | ||
|
|
1dc8e2e2eb | ||
|
|
6555fcdffe | ||
|
|
8b89993505 | ||
|
|
5896968358 | ||
|
|
cd73bd39ce | ||
|
|
77416b6bf4 | ||
|
|
4227a57fc7 | ||
|
|
cc3de84d25 | ||
|
|
0545341f9a | ||
|
|
035870e08d | ||
|
|
b58ea35f6a | ||
|
|
3f61394f0a | ||
|
|
9536413fa3 | ||
|
|
7b1ca25a6c | ||
|
|
79640bcb8c | ||
|
|
aea1d69157 | ||
|
|
7a6977aff2 | ||
|
|
c221ab1482 | ||
|
|
fda3eca74f | ||
|
|
d2e367e901 | ||
|
|
3e460805bc | ||
|
|
6019320d9e | ||
|
|
0dc83c3e0a | ||
|
|
dc0c9f6cc9 | ||
|
|
33f48f1f3f | ||
|
|
9de5348351 | ||
|
|
adfb41c737 |
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@@ -0,0 +1,3 @@
|
||||
[*.{kt,kts}]
|
||||
ktlint_code_style = intellij_idea
|
||||
ktlint_standard_no-wildcard-imports = disabled
|
||||
49
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
49
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: 🐞 Bug report
|
||||
description: Report a bug or an issue.
|
||||
title: 'bug: '
|
||||
labels: ['Bug report']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# ReVanced Library bug report
|
||||
|
||||
Please check for existing bug reports
|
||||
[here](https://github.com/ReVanced/revanced-library/labels/Bug%20report)
|
||||
before creating a new one.
|
||||
|
||||
- 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 issue will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: This request is not a duplicate of an existing issue.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided 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-library/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-library/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-library/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-library/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 Library bug report
|
||||
|
||||
Before creating a new bug report, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-library/issues?q=label%3A%22Bug+report%22).
|
||||
- **Review the contribution guidelines**: Make sure your bug request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-library/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
|
||||
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: I have checked all open and closed bug reports and this is not a duplicate.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
|
||||
45
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
45
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,45 +0,0 @@
|
||||
name: ⭐ Feature request
|
||||
description: Create a detailed request for a new feature.
|
||||
title: 'feat: '
|
||||
labels: ['Feature request']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# ReVanced Library feature request
|
||||
|
||||
Please check for existing feature requests
|
||||
[here](https://github.com/ReVanced/revanced-library/labels/Feature%20request)
|
||||
before creating a new one.
|
||||
|
||||
- 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 issue will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: This request is not a duplicate of an existing issue.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided 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-library/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-library/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-library/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-library/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 Library feature request
|
||||
|
||||
Before creating a new feature request, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-library/issues?q=label%3A%22Feature+request%22).
|
||||
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-library/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: I have checked all open and closed feature requests and this is not a duplicate.
|
||||
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: >
|
||||
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.
|
||||
|
||||
22
.github/dependabot.yml
vendored
Normal file
22
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
|
||||
- package-ecosystem: npm
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
|
||||
- package-ecosystem: gradle
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
28
.github/workflows/build_pull_request.yml
vendored
Normal file
28
.github/workflows/build_pull_request.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Build pull request
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
|
||||
- name: Setup Java
|
||||
run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build --no-daemon
|
||||
@@ -16,6 +16,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Open pull request
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
46
.github/workflows/publish.yml
vendored
46
.github/workflows/publish.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: Publish ReVanced Library
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{ runner.home }}/.gradle/caches
|
||||
${{ runner.home }}/.gradle/wrapper
|
||||
.gradle
|
||||
build
|
||||
node_modules
|
||||
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Cleaning is necessary to avoid uploading two identical artifacts with different versions
|
||||
run: ./gradlew clean --no-daemon
|
||||
- name: Setup semantic-release
|
||||
run: npm install
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
run: npm exec semantic-release
|
||||
56
.github/workflows/release.yml
vendored
Normal file
56
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
|
||||
- name: Setup Java
|
||||
run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build clean
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npm exec semantic-release
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Dispatch event to documentation repository
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: peter-evans/repository-dispatch@v2
|
||||
- uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
||||
repository: revanced/revanced-documentation
|
||||
123
.gitignore
vendored
123
.gitignore
vendored
@@ -1,118 +1,11 @@
|
||||
### Java template
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
.idea/artifacts
|
||||
.idea/compiler.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/modules.xml
|
||||
.idea/*.iml
|
||||
.idea/modules
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
.idea
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Gradle template
|
||||
.gradle
|
||||
**/build/
|
||||
!src/**/build/
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
|
||||
# gradle/wrapper/gradle-wrapper.properties
|
||||
|
||||
# Dependency directories
|
||||
.idea
|
||||
.DS_Store
|
||||
build
|
||||
captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
xcuserdata
|
||||
node_modules/
|
||||
@@ -23,7 +23,8 @@
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"gradle.properties"
|
||||
]
|
||||
],
|
||||
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -32,6 +33,12 @@
|
||||
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||
clearWorkspace: true
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
successComment: false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
426
CHANGELOG.md
426
CHANGELOG.md
@@ -1,3 +1,429 @@
|
||||
# [3.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.1.1-dev.1...v3.2.0-dev.1) (2025-05-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Request the update ownership enforcement ([#71](https://github.com/ReVanced/revanced-library/issues/71)) ([be0f6bf](https://github.com/ReVanced/revanced-library/commit/be0f6bf247461d16fbf649a9f2dc6facbb5b0c93))
|
||||
|
||||
## [3.1.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.1.0...v3.1.1-dev.1) (2025-05-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Interpret package name as a string instead of Regex when using grep ([#68](https://github.com/ReVanced/revanced-library/issues/68)) ([254f36d](https://github.com/ReVanced/revanced-library/commit/254f36d03cc8fd3e2508a5e8f69bb5c8e1eb9775))
|
||||
|
||||
# [3.1.0](https://github.com/ReVanced/revanced-library/compare/v3.0.2...v3.1.0) (2024-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Detect if app is installed by fixing inversion ([649f06b](https://github.com/ReVanced/revanced-library/commit/649f06b19dd4d2a3f3216a0b3ea947b9fe0d475f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Warn when option could not be set because the option does not exist ([7ec6504](https://github.com/ReVanced/revanced-library/commit/7ec650461935faf2a8fbb667db3cf137157b70b5))
|
||||
|
||||
# [3.1.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.3-dev.1...v3.1.0-dev.1) (2024-11-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Warn when option could not be set because the option does not exist ([7ec6504](https://github.com/ReVanced/revanced-library/commit/7ec650461935faf2a8fbb667db3cf137157b70b5))
|
||||
|
||||
## [3.0.3-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.2...v3.0.3-dev.1) (2024-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Detect if app is installed by fixing inversion ([649f06b](https://github.com/ReVanced/revanced-library/commit/649f06b19dd4d2a3f3216a0b3ea947b9fe0d475f))
|
||||
|
||||
## [3.0.2](https://github.com/ReVanced/revanced-library/compare/v3.0.1...v3.0.2) (2024-11-05)
|
||||
|
||||
## [3.0.2-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.1...v3.0.2-dev.1) (2024-11-05)
|
||||
|
||||
## [3.0.1](https://github.com/ReVanced/revanced-library/compare/v3.0.0...v3.0.1) (2024-10-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Serialize compatible packages as a map instead of a set of pairs. ([737e272](https://github.com/ReVanced/revanced-library/commit/737e272481fe3b0b4c89233d139b5e657a0c1de4))
|
||||
|
||||
## [3.0.1-dev.3](https://github.com/ReVanced/revanced-library/compare/v3.0.1-dev.2...v3.0.1-dev.3) (2024-10-06)
|
||||
|
||||
## [3.0.1-dev.2](https://github.com/ReVanced/revanced-library/compare/v3.0.1-dev.1...v3.0.1-dev.2) (2024-10-01)
|
||||
|
||||
## [3.0.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v3.0.0...v3.0.1-dev.1) (2024-08-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Serialize compatible packages as a map instead of a set of pairs. ([737e272](https://github.com/ReVanced/revanced-library/commit/737e272481fe3b0b4c89233d139b5e657a0c1de4))
|
||||
|
||||
# [3.0.0](https://github.com/ReVanced/revanced-library/compare/v2.3.0...v3.0.0) (2024-08-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make functions internal which are supposed to be internal ([893d22d](https://github.com/ReVanced/revanced-library/commit/893d22d7938fa1c7544795635ed2ffacdd0cbf0d))
|
||||
|
||||
|
||||
### Build System
|
||||
|
||||
* Refactor to DSL to bump ReVanced Patcher ([7f5d6da](https://github.com/ReVanced/revanced-library/commit/7f5d6dad7ba73e2ee53010241ba3204d04860a22))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add local Android installer ([#25](https://github.com/ReVanced/revanced-library/issues/25)) ([43d655a](https://github.com/ReVanced/revanced-library/commit/43d655aea5d86288ae9916630e0f30de219d5cfb))
|
||||
* Remove deprecated functions ([b9bf3bc](https://github.com/ReVanced/revanced-library/commit/b9bf3bc88284c0381c7370c3606b662da2ef380d))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Some functions have been removed.
|
||||
* Some functions are not available anymore.
|
||||
* The signature of some functions has changed.
|
||||
|
||||
# [3.0.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.4.0-dev.1...v3.0.0-dev.1) (2024-08-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make functions internal which are supposed to be internal ([893d22d](https://github.com/ReVanced/revanced-library/commit/893d22d7938fa1c7544795635ed2ffacdd0cbf0d))
|
||||
|
||||
|
||||
### Build System
|
||||
|
||||
* Refactor to DSL to bump ReVanced Patcher ([7f5d6da](https://github.com/ReVanced/revanced-library/commit/7f5d6dad7ba73e2ee53010241ba3204d04860a22))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Remove deprecated functions ([b9bf3bc](https://github.com/ReVanced/revanced-library/commit/b9bf3bc88284c0381c7370c3606b662da2ef380d))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Some functions have been removed.
|
||||
* Some functions are not available anymore.
|
||||
* The signature of some functions has changed.
|
||||
|
||||
# [2.4.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.3.0...v2.4.0-dev.1) (2024-04-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add local Android installer ([#25](https://github.com/ReVanced/revanced-library/issues/25)) ([43d655a](https://github.com/ReVanced/revanced-library/commit/43d655aea5d86288ae9916630e0f30de219d5cfb))
|
||||
|
||||
# [2.3.0](https://github.com/ReVanced/revanced-library/compare/v2.2.1...v2.3.0) (2024-03-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Support mounting even when Magisk is not installed ([2a30845](https://github.com/ReVanced/revanced-library/commit/2a30845f61d5f77ded7a72ee3d6ab55b4c512d52))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add utility function around key certificate pairs ([2df3484](https://github.com/ReVanced/revanced-library/commit/2df3484b68ed72338a52e76fb4b7ceb9c9c644ed))
|
||||
* Improve exception message ([b15efa4](https://github.com/ReVanced/revanced-library/commit/b15efa41f8dc7d73865d0eab15be274b9ee3d7a3))
|
||||
* Simplify signing utility API ([4c6a636](https://github.com/ReVanced/revanced-library/commit/4c6a6360cf83659d1f5c3a7c5710ac54426e9235))
|
||||
|
||||
# [2.3.0-dev.3](https://github.com/ReVanced/revanced-library/compare/v2.3.0-dev.2...v2.3.0-dev.3) (2024-03-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Improve exception message ([b15efa4](https://github.com/ReVanced/revanced-library/commit/b15efa41f8dc7d73865d0eab15be274b9ee3d7a3))
|
||||
|
||||
# [2.3.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v2.3.0-dev.1...v2.3.0-dev.2) (2024-03-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Simplify signing utility API ([4c6a636](https://github.com/ReVanced/revanced-library/commit/4c6a6360cf83659d1f5c3a7c5710ac54426e9235))
|
||||
|
||||
# [2.3.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.2-dev.1...v2.3.0-dev.1) (2024-03-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add utility function around key certificate pairs ([2df3484](https://github.com/ReVanced/revanced-library/commit/2df3484b68ed72338a52e76fb4b7ceb9c9c644ed))
|
||||
|
||||
## [2.2.2-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.1...v2.2.2-dev.1) (2024-03-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Support mounting even when Magisk is not installed ([2a30845](https://github.com/ReVanced/revanced-library/commit/2a30845f61d5f77ded7a72ee3d6ab55b4c512d52))
|
||||
|
||||
## [2.2.1](https://github.com/ReVanced/revanced-library/compare/v2.2.0...v2.2.1) (2024-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Do not specify a provider to automatically select an available one ([249372c](https://github.com/ReVanced/revanced-library/commit/249372c31f7e7975fc9eacb5361bd07dbc5dfb92))
|
||||
|
||||
## [2.2.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.2.0...v2.2.1-dev.1) (2024-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Do not specify a provider to automatically select an available one ([249372c](https://github.com/ReVanced/revanced-library/commit/249372c31f7e7975fc9eacb5361bd07dbc5dfb92))
|
||||
|
||||
# [2.2.0](https://github.com/ReVanced/revanced-library/compare/v2.1.0...v2.2.0) (2024-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make property private ([51109c4](https://github.com/ReVanced/revanced-library/commit/51109c476837828535dcd395a5222d2fcf7fc22c))
|
||||
* Sign APKs using `apksig` ([f59ecbc](https://github.com/ReVanced/revanced-library/commit/f59ecbccd14a08d87d4f18c3c0cc47a884088b99))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Increase default expiration date of certificate ([f2bd3f5](https://github.com/ReVanced/revanced-library/commit/f2bd3f5eeee14ca32094be0d41c32b231a16bcc3))
|
||||
|
||||
# [2.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.1.0...v2.2.0-dev.1) (2024-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make property private ([51109c4](https://github.com/ReVanced/revanced-library/commit/51109c476837828535dcd395a5222d2fcf7fc22c))
|
||||
* Sign APKs using `apksig` ([f59ecbc](https://github.com/ReVanced/revanced-library/commit/f59ecbccd14a08d87d4f18c3c0cc47a884088b99))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Increase default expiration date of certificate ([f2bd3f5](https://github.com/ReVanced/revanced-library/commit/f2bd3f5eeee14ca32094be0d41c32b231a16bcc3))
|
||||
|
||||
# [2.1.0](https://github.com/ReVanced/revanced-library/compare/v2.0.0...v2.1.0) (2024-03-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use `BKS` instead of default signing provider to fix backwards compatibility ([41805fc](https://github.com/ReVanced/revanced-library/commit/41805fcb0bdc778fe0870427a0a1caa6d4369cee))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Mention APK file name when logging aligning ([244ebc2](https://github.com/ReVanced/revanced-library/commit/244ebc21868c07d1852857f6858c1a53a5561155))
|
||||
|
||||
# [2.1.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v2.1.0-dev.1...v2.1.0-dev.2) (2024-03-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use `BKS` instead of default signing provider to fix backwards compatibility ([41805fc](https://github.com/ReVanced/revanced-library/commit/41805fcb0bdc778fe0870427a0a1caa6d4369cee))
|
||||
|
||||
# [2.1.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v2.0.0...v2.1.0-dev.1) (2024-02-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Mention APK file name when logging aligning ([244ebc2](https://github.com/ReVanced/revanced-library/commit/244ebc21868c07d1852857f6858c1a53a5561155))
|
||||
|
||||
# [2.0.0](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v2.0.0) (2024-02-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Map dependencies from `KClass` into `String` to fix serialization ([57e36ab](https://github.com/ReVanced/revanced-library/commit/57e36ab5c15a5fa7c50fb689ee43ad4eb9a4a515))
|
||||
* Use the JVM name instead of the value from `KClass#toString` ([d18e436](https://github.com/ReVanced/revanced-library/commit/d18e436de1df14452ecaa7d827be5e6596ba8a2d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Use `apkzlib` instead of own implementations and bump ReVanced Patcher ([3aa6dc2](https://github.com/ReVanced/revanced-library/commit/3aa6dc223a9a1a2f735eda407917548ecbd366aa))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
|
||||
|
||||
# [2.0.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.5.1-dev.2...v2.0.0-dev.1) (2024-02-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Use `apkzlib` instead of own implementations and bump ReVanced Patcher ([3aa6dc2](https://github.com/ReVanced/revanced-library/commit/3aa6dc223a9a1a2f735eda407917548ecbd366aa))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This commit removes deprecated APIs and bumps ReVanced Patcher. Because of it's changes, `apkzlib` is now used instead of own implementations of `ZipFile`
|
||||
|
||||
## [1.5.1-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.5.1-dev.1...v1.5.1-dev.2) (2024-02-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use the JVM name instead of the value from `KClass#toString` ([d18e436](https://github.com/ReVanced/revanced-library/commit/d18e436de1df14452ecaa7d827be5e6596ba8a2d))
|
||||
|
||||
## [1.5.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.5.0...v1.5.1-dev.1) (2024-02-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Map dependencies from `KClass` into `String` to fix serialization ([57e36ab](https://github.com/ReVanced/revanced-library/commit/57e36ab5c15a5fa7c50fb689ee43ad4eb9a4a515))
|
||||
|
||||
# [1.5.0](https://github.com/ReVanced/revanced-library/compare/v1.4.0...v1.5.0) (2023-12-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add JSON de- and serialization of patches ([ecff6fe](https://github.com/ReVanced/revanced-library/commit/ecff6fe0d3889d729a0badcfa28b89610bd27d48))
|
||||
* Improve mount reliability by unmounting existing mounts and killing running apps ([9fda407](https://github.com/ReVanced/revanced-library/commit/9fda40744173669c84b0c2599ae5ac5d39591798))
|
||||
|
||||
# [1.5.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2023-12-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Improve mount reliability by unmounting existing mounts and killing running apps ([9fda407](https://github.com/ReVanced/revanced-library/commit/9fda40744173669c84b0c2599ae5ac5d39591798))
|
||||
|
||||
# [1.5.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.4.0...v1.5.0-dev.1) (2023-12-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add JSON de- and serialization of patches ([ecff6fe](https://github.com/ReVanced/revanced-library/commit/ecff6fe0d3889d729a0badcfa28b89610bd27d48))
|
||||
|
||||
# [1.4.0](https://github.com/ReVanced/revanced-library/compare/v1.3.0...v1.4.0) (2023-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Differentiate no package compatibility to any version compatibility ([762b7e3](https://github.com/ReVanced/revanced-library/commit/762b7e3bc01e2ca33dfcdbb1b5028d60ef6e0a48))
|
||||
* Sort the version maps by the most common version ([e4be6db](https://github.com/ReVanced/revanced-library/commit/e4be6dbccd86700ffafe7cd8395e845bbd3d5138))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add `PatchUtils#getMostCommonCompatibleVersions` utility function ([c5f3536](https://github.com/ReVanced/revanced-library/commit/c5f3536cbb6997766076595dc0b2b5d2e861ca73))
|
||||
* Allow getting most common compatible versions for all packages ([96845ba](https://github.com/ReVanced/revanced-library/commit/96845ba265e6dc208c7ac96f5e58734209cd1720))
|
||||
|
||||
# [1.4.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.4.0-dev.1...v1.4.0-dev.2) (2023-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Differentiate no package compatibility to any version compatibility ([762b7e3](https://github.com/ReVanced/revanced-library/commit/762b7e3bc01e2ca33dfcdbb1b5028d60ef6e0a48))
|
||||
* Sort the version maps by the most common version ([e4be6db](https://github.com/ReVanced/revanced-library/commit/e4be6dbccd86700ffafe7cd8395e845bbd3d5138))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Allow getting most common compatible versions for all packages ([96845ba](https://github.com/ReVanced/revanced-library/commit/96845ba265e6dc208c7ac96f5e58734209cd1720))
|
||||
|
||||
# [1.4.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.3.0...v1.4.0-dev.1) (2023-11-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add `PatchUtils#getMostCommonCompatibleVersions` utility function ([c5f3536](https://github.com/ReVanced/revanced-library/commit/c5f3536cbb6997766076595dc0b2b5d2e861ca73))
|
||||
|
||||
# [1.3.0](https://github.com/ReVanced/revanced-library/compare/v1.2.0...v1.3.0) (2023-11-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add missing log when calling `UserAdbManager#install` ([90b612b](https://github.com/ReVanced/revanced-library/commit/90b612bee8591c01b8befabde4147c7de7a2a09f))
|
||||
* Delete mount script ([4fe0fb0](https://github.com/ReVanced/revanced-library/commit/4fe0fb0a617082b24199331671193e4fa7f485e2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Increase certainty of the possibility to mount ([10f8cd1](https://github.com/ReVanced/revanced-library/commit/10f8cd1470fd29cfefe53bf00a4a014f71a3f706))
|
||||
* Select first Adb device, if none supplied automatically ([1a5f868](https://github.com/ReVanced/revanced-library/commit/1a5f868ecd0d278d574c12664ee95139c2423c17))
|
||||
|
||||
# [1.3.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.2.1-dev.1...v1.3.0-dev.1) (2023-11-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Delete mount script ([4fe0fb0](https://github.com/ReVanced/revanced-library/commit/4fe0fb0a617082b24199331671193e4fa7f485e2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Increase certainty of the possibility to mount ([10f8cd1](https://github.com/ReVanced/revanced-library/commit/10f8cd1470fd29cfefe53bf00a4a014f71a3f706))
|
||||
* Select first Adb device, if none supplied automatically ([1a5f868](https://github.com/ReVanced/revanced-library/commit/1a5f868ecd0d278d574c12664ee95139c2423c17))
|
||||
|
||||
## [1.2.1-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.2.0...v1.2.1-dev.1) (2023-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add missing log when calling `UserAdbManager#install` ([90b612b](https://github.com/ReVanced/revanced-library/commit/90b612bee8591c01b8befabde4147c7de7a2a09f))
|
||||
|
||||
# [1.2.0](https://github.com/ReVanced/revanced-library/compare/v1.1.5...v1.2.0) (2023-11-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Catch exceptions when serializing invalid patch options ([cd73bd3](https://github.com/ReVanced/revanced-library/commit/cd73bd39ce7be5963a3c84ec43409b87c327579b))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Log warnings when compiling resources ([1dc8e2e](https://github.com/ReVanced/revanced-library/commit/1dc8e2e2eb76bcade9167be0c4b4a628ff114f63))
|
||||
* Use better log messages when handling exceptions ([5896968](https://github.com/ReVanced/revanced-library/commit/58969683582e70f36d6ed169b41c37928a2cf602))
|
||||
|
||||
# [1.2.0-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.2.0-dev.1...v1.2.0-dev.2) (2023-11-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Log warnings when compiling resources ([1dc8e2e](https://github.com/ReVanced/revanced-library/commit/1dc8e2e2eb76bcade9167be0c4b4a628ff114f63))
|
||||
|
||||
# [1.2.0-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.5...v1.2.0-dev.1) (2023-10-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Catch exceptions when serializing invalid patch options ([cd73bd3](https://github.com/ReVanced/revanced-library/commit/cd73bd39ce7be5963a3c84ec43409b87c327579b))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Use better log messages when handling exceptions ([5896968](https://github.com/ReVanced/revanced-library/commit/58969683582e70f36d6ed169b41c37928a2cf602))
|
||||
|
||||
## [1.1.5](https://github.com/ReVanced/revanced-library/compare/v1.1.4...v1.1.5) (2023-10-23)
|
||||
|
||||
## [1.1.5-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.4...v1.1.5-dev.1) (2023-10-23)
|
||||
|
||||
## [1.1.4](https://github.com/ReVanced/revanced-library/compare/v1.1.3...v1.1.4) (2023-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Ask for root permissions before trying to use them ([aea1d69](https://github.com/ReVanced/revanced-library/commit/aea1d6915766d9757075ee459955aa335d906bab))
|
||||
* Execute ADB commands sequentially to fix mounting issues ([#12](https://github.com/ReVanced/revanced-library/issues/12)) ([fda3eca](https://github.com/ReVanced/revanced-library/commit/fda3eca74f30b968d8ee816d63a3dcf493e026de))
|
||||
|
||||
## [1.1.4-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.1.4-dev.1...v1.1.4-dev.2) (2023-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Ask for root permissions before trying to use them ([aea1d69](https://github.com/ReVanced/revanced-library/commit/aea1d6915766d9757075ee459955aa335d906bab))
|
||||
|
||||
## [1.1.4-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.3...v1.1.4-dev.1) (2023-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Execute ADB commands sequentially to fix mounting issues ([#12](https://github.com/ReVanced/revanced-library/issues/12)) ([fda3eca](https://github.com/ReVanced/revanced-library/commit/fda3eca74f30b968d8ee816d63a3dcf493e026de))
|
||||
|
||||
## [1.1.3](https://github.com/ReVanced/revanced-library/compare/v1.1.2...v1.1.3) (2023-10-09)
|
||||
|
||||
## [1.1.3-dev.2](https://github.com/ReVanced/revanced-library/compare/v1.1.3-dev.1...v1.1.3-dev.2) (2023-10-09)
|
||||
|
||||
## [1.1.3-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.2...v1.1.3-dev.1) (2023-10-09)
|
||||
|
||||
## [1.1.2](https://github.com/ReVanced/revanced-library/compare/v1.1.1...v1.1.2) (2023-10-05)
|
||||
|
||||
## [1.1.2-dev.1](https://github.com/ReVanced/revanced-library/compare/v1.1.1...v1.1.2-dev.1) (2023-10-05)
|
||||
|
||||
## [1.1.1](https://github.com/ReVanced/revanced-library/compare/v1.1.0...v1.1.1) (2023-10-04)
|
||||
|
||||
@@ -6,34 +6,53 @@
|
||||
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/">
|
||||
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" />
|
||||
<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">
|
||||
<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">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<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">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<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">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<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://twitter.com/revancedapp">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032018-6da37214-7474-4641-a1da-7af7db3a31cd.png" />
|
||||
<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">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</a>
|
||||
<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
|
||||
@@ -46,22 +65,23 @@ This document describes how to contribute to ReVanced Library.
|
||||
## 📖 Resources to help you get started
|
||||
|
||||
<!-- * The [documentation](/docs) explains how to use ReVanced Library -->
|
||||
* [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
|
||||
|
||||
- [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+).
|
||||
[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**
|
||||
> [!NOTE]
|
||||
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Library.
|
||||
> Good motivation has to be provided for a request to be accepted.
|
||||
|
||||
## 🐞 Submitting a bug report
|
||||
|
||||
If you encounter a bug while using ReVanced Library, 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+).
|
||||
[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
|
||||
|
||||
@@ -69,11 +89,11 @@ If you encounter a bug while using ReVanced Library, open an issue using the
|
||||
with the maintainers of ReVanced Library. 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.
|
||||
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 Library
|
||||
|
||||
❤️ Thank you for considering contributing to ReVanced Library,
|
||||
ReVanced
|
||||
❤️ Thank you for considering contributing to ReVanced Library,
|
||||
ReVanced
|
||||
|
||||
117
README.md
Normal file
117
README.md
Normal file
@@ -0,0 +1,117 @@
|
||||
<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 Library
|
||||
|
||||

|
||||

|
||||
|
||||
Library containing common utilities for ReVanced.
|
||||
|
||||
## ❓ About
|
||||
|
||||
ReVanced Library powers projects such as [ReVanced Manager](https://github.com/ReVanced/revanced-manager),
|
||||
[ReVanced CLI](https://github.com/ReVanced/revanced-cli) with common utilities and functionalities
|
||||
by providing shared code.
|
||||
|
||||
## 💪 Features
|
||||
|
||||
Some of the features the ReVanced Library provides are:
|
||||
|
||||
- 📝 **Signing APKs**: Read and write keystores, and sign APK files
|
||||
- 🧩 **Common utility functions**: Various APIs for ReVanced patches such as JSON serialization,
|
||||
reading and setting patch options, calculating the most common compatible version for a set of patches and more
|
||||
- 💾 **Install and uninstall APKs**: Install and uninstall APK files via ADB or locally,
|
||||
the Android package manager, or by mounting using root permissions
|
||||
- 📦 **Repackage patched files to an APK**: Apply patched files from
|
||||
[ReVanced Patcher](https://github.com/revanced/revanced-patcher) to an APK file, and align & sign the APK file automatically
|
||||
|
||||
## 🚀 How to get started
|
||||
|
||||
To use ReVanced Library in your project, follow these steps:
|
||||
|
||||
1. [Add the repository](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#using-a-published-package)
|
||||
to your project
|
||||
2. Add the dependency to your project:
|
||||
|
||||
```kt
|
||||
dependencies {
|
||||
implementation("app.revanced:revanced-library:{$version}")
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 Everything else
|
||||
|
||||
### 📙 Contributing
|
||||
|
||||
Thank you for considering contributing to ReVanced Library.
|
||||
You can find the contribution guidelines [here](CONTRIBUTING.md).
|
||||
|
||||
### 🛠️ Building
|
||||
|
||||
To build ReVanced Library,
|
||||
you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
|
||||
|
||||
## 📜 License
|
||||
|
||||
ReVanced Library is licensed under the GPLv3 license. Please see the [license 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 Library as long as you track changes/dates in source files.
|
||||
Any modifications to ReVanced Library must also be made available under the GPL,
|
||||
along with build & install instructions.
|
||||
221
api/android/revanced-library.api
Normal file
221
api/android/revanced-library.api
Normal file
@@ -0,0 +1,221 @@
|
||||
public final class app/revanced/library/ApkSigner {
|
||||
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
|
||||
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
|
||||
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
|
||||
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
|
||||
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
|
||||
public final fun getAlias ()Ljava/lang/String;
|
||||
public final fun getPassword ()Ljava/lang/String;
|
||||
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
|
||||
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
|
||||
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
|
||||
public final fun getPrivateKey ()Ljava/security/PrivateKey;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkSigner$Signer {
|
||||
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkUtils {
|
||||
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
|
||||
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
|
||||
public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkUtils$KeyStoreDetails {
|
||||
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getAlias ()Ljava/lang/String;
|
||||
public final fun getKeyStore ()Ljava/io/File;
|
||||
public final fun getKeyStorePassword ()Ljava/lang/String;
|
||||
public final fun getPassword ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Ljava/lang/String;Ljava/util/Date;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getCommonName ()Ljava/lang/String;
|
||||
public final fun getValidUntil ()Ljava/util/Date;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/OptionsKt {
|
||||
public static final fun setOptions (Ljava/util/Set;Ljava/util/Map;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/PatchKt {
|
||||
public static final fun mostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
|
||||
public static synthetic fun mostCommonCompatibleVersions$default (Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/SerializationKt {
|
||||
public static final fun serializeTo (Ljava/util/Set;Ljava/io/OutputStream;Z)V
|
||||
public static synthetic fun serializeTo$default (Ljava/util/Set;Ljava/io/OutputStream;ZILjava/lang/Object;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/Utils {
|
||||
public static final field INSTANCE Lapp/revanced/library/Utils;
|
||||
public final fun isAndroidEnvironment ()Z
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
|
||||
}
|
||||
|
||||
public abstract interface class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService : android/os/IInterface {
|
||||
public static final field DESCRIPTOR Ljava/lang/String;
|
||||
public abstract fun getFileSystemService ()Landroid/os/IBinder;
|
||||
}
|
||||
|
||||
public class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Default : app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
|
||||
public fun <init> ()V
|
||||
public fun asBinder ()Landroid/os/IBinder;
|
||||
public fun getFileSystemService ()Landroid/os/IBinder;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Stub : android/os/Binder, app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
|
||||
public fun <init> ()V
|
||||
public fun asBinder ()Landroid/os/IBinder;
|
||||
public static fun asInterface (Landroid/os/IBinder;)Lapp/revanced/library/installation/command/ILocalShellCommandRunnerRootService;
|
||||
public fun onTransact (ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/command/LocalShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner, android/content/ServiceConnection, java/io/Closeable {
|
||||
public fun close ()V
|
||||
public fun onServiceConnected (Landroid/content/ComponentName;Landroid/os/IBinder;)V
|
||||
public fun onServiceDisconnected (Landroid/content/ComponentName;)V
|
||||
}
|
||||
|
||||
public abstract interface class app/revanced/library/installation/command/RunResult {
|
||||
public abstract fun getError ()Ljava/lang/String;
|
||||
public abstract fun getExitCode ()I
|
||||
public abstract fun getOutput ()Ljava/lang/String;
|
||||
public abstract fun waitFor ()V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
|
||||
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
|
||||
}
|
||||
|
||||
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
|
||||
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
|
||||
public final fun getException ()Ljava/lang/Exception;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
|
||||
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/AdbRootInstaller : app/revanced/library/installation/installer/RootInstaller {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/DeviceNotFoundException : java/lang/Exception {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public class app/revanced/library/installation/installer/Installation {
|
||||
public final fun getApkFilePath ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/library/installation/installer/Installer {
|
||||
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/Installer$Apk {
|
||||
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getFile ()Ljava/io/File;
|
||||
public final fun getPackageName ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/LocalInstaller : app/revanced/library/installation/installer/Installer, java/io/Closeable {
|
||||
public static final field Companion Lapp/revanced/library/installation/installer/LocalInstaller$Companion;
|
||||
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
|
||||
public fun close ()V
|
||||
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/LocalInstaller$Companion {
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/LocalInstallerResult {
|
||||
public final fun getExtra ()Ljava/lang/String;
|
||||
public final fun getPackageName ()Ljava/lang/String;
|
||||
public final fun getPmStatus ()I
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/LocalInstallerService : android/app/Service {
|
||||
public fun <init> ()V
|
||||
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
|
||||
public fun onStartCommand (Landroid/content/Intent;II)I
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/LocalRootInstaller : app/revanced/library/installation/installer/RootInstaller, java/io/Closeable {
|
||||
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
|
||||
public synthetic fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun close ()V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/RootInstallation : app/revanced/library/installation/installer/Installation {
|
||||
public final fun getInstalledApkFilePath ()Ljava/lang/String;
|
||||
public final fun getMounted ()Z
|
||||
}
|
||||
|
||||
public abstract class app/revanced/library/installation/installer/RootInstaller : app/revanced/library/installation/installer/Installer {
|
||||
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
|
||||
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
|
||||
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
|
||||
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/RootInstallerResult : java/lang/Enum {
|
||||
public static final field FAILURE Lapp/revanced/library/installation/installer/RootInstallerResult;
|
||||
public static final field SUCCESS Lapp/revanced/library/installation/installer/RootInstallerResult;
|
||||
public static fun getEntries ()Lkotlin/enums/EnumEntries;
|
||||
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/RootInstallerResult;
|
||||
public static fun values ()[Lapp/revanced/library/installation/installer/RootInstallerResult;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/logging/Logger {
|
||||
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
|
||||
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
|
||||
public final fun removeAllHandlers ()V
|
||||
public final fun setDefault ()V
|
||||
public final fun setFormat (Ljava/lang/String;)V
|
||||
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
|
||||
}
|
||||
|
||||
167
api/jvm/revanced-library.api
Normal file
167
api/jvm/revanced-library.api
Normal file
@@ -0,0 +1,167 @@
|
||||
public final class app/revanced/library/ApkSigner {
|
||||
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
|
||||
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
|
||||
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
|
||||
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
|
||||
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
|
||||
public final fun getAlias ()Ljava/lang/String;
|
||||
public final fun getPassword ()Ljava/lang/String;
|
||||
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
|
||||
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
|
||||
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
|
||||
public final fun getPrivateKey ()Ljava/security/PrivateKey;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkSigner$Signer {
|
||||
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkUtils {
|
||||
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
|
||||
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
|
||||
public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkUtils$KeyStoreDetails {
|
||||
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getAlias ()Ljava/lang/String;
|
||||
public final fun getKeyStore ()Ljava/io/File;
|
||||
public final fun getKeyStorePassword ()Ljava/lang/String;
|
||||
public final fun getPassword ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Ljava/lang/String;Ljava/util/Date;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getCommonName ()Ljava/lang/String;
|
||||
public final fun getValidUntil ()Ljava/util/Date;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/OptionsKt {
|
||||
public static final fun setOptions (Ljava/util/Set;Ljava/util/Map;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/PatchKt {
|
||||
public static final fun mostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
|
||||
public static synthetic fun mostCommonCompatibleVersions$default (Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/SerializationKt {
|
||||
public static final fun serializeTo (Ljava/util/Set;Ljava/io/OutputStream;Z)V
|
||||
public static synthetic fun serializeTo$default (Ljava/util/Set;Ljava/io/OutputStream;ZILjava/lang/Object;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/Utils {
|
||||
public static final field INSTANCE Lapp/revanced/library/Utils;
|
||||
public final fun isAndroidEnvironment ()Z
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
|
||||
}
|
||||
|
||||
public abstract interface class app/revanced/library/installation/command/RunResult {
|
||||
public abstract fun getError ()Ljava/lang/String;
|
||||
public abstract fun getExitCode ()I
|
||||
public abstract fun getOutput ()Ljava/lang/String;
|
||||
public abstract fun waitFor ()V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
|
||||
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
|
||||
}
|
||||
|
||||
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
|
||||
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
|
||||
public final fun getException ()Ljava/lang/Exception;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
|
||||
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/AdbRootInstaller : app/revanced/library/installation/installer/RootInstaller {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/DeviceNotFoundException : java/lang/Exception {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public class app/revanced/library/installation/installer/Installation {
|
||||
public final fun getApkFilePath ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/library/installation/installer/Installer {
|
||||
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/Installer$Apk {
|
||||
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getFile ()Ljava/io/File;
|
||||
public final fun getPackageName ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/RootInstallation : app/revanced/library/installation/installer/Installation {
|
||||
public final fun getInstalledApkFilePath ()Ljava/lang/String;
|
||||
public final fun getMounted ()Z
|
||||
}
|
||||
|
||||
public abstract class app/revanced/library/installation/installer/RootInstaller : app/revanced/library/installation/installer/Installer {
|
||||
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
|
||||
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
|
||||
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
|
||||
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/installation/installer/RootInstallerResult : java/lang/Enum {
|
||||
public static final field FAILURE Lapp/revanced/library/installation/installer/RootInstallerResult;
|
||||
public static final field SUCCESS Lapp/revanced/library/installation/installer/RootInstallerResult;
|
||||
public static fun getEntries ()Lkotlin/enums/EnumEntries;
|
||||
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/RootInstallerResult;
|
||||
public static fun values ()[Lapp/revanced/library/installation/installer/RootInstallerResult;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/logging/Logger {
|
||||
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
|
||||
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
|
||||
public final fun removeAllHandlers ()V
|
||||
public final fun setDefault ()V
|
||||
public final fun setFormat (Ljava/lang/String;)V
|
||||
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
public final class app/revanced/library/ApkSigner {
|
||||
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
|
||||
public final fun newApkSignerBuilder (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
|
||||
public final fun newApkSignerBuilder (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
|
||||
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/List;)V
|
||||
public final fun newKeyStore (Ljava/util/List;)Ljava/security/KeyStore;
|
||||
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
public static synthetic fun newPrivateKeyCertificatePair$default (Lapp/revanced/library/ApkSigner;Ljava/lang/String;Ljava/util/Date;ILjava/lang/Object;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
|
||||
public final fun signApk (Lcom/android/apksig/ApkSigner$Builder;Ljava/io/File;Ljava/io/File;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getAlias ()Ljava/lang/String;
|
||||
public final fun getPassword ()Ljava/lang/String;
|
||||
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkSigner$PrivateKeyCertificatePair {
|
||||
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
|
||||
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
|
||||
public final fun getPrivateKey ()Ljava/security/PrivateKey;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkUtils {
|
||||
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
|
||||
public final fun copyAligned (Ljava/io/File;Ljava/io/File;Lapp/revanced/patcher/PatcherResult;)V
|
||||
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/ApkUtils$SigningOptions {
|
||||
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getAlias ()Ljava/lang/String;
|
||||
public final fun getKeyStore ()Ljava/io/File;
|
||||
public final fun getKeyStorePassword ()Ljava/lang/String;
|
||||
public final fun getPassword ()Ljava/lang/String;
|
||||
public final fun getSigner ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/Options {
|
||||
public static final field INSTANCE Lapp/revanced/library/Options;
|
||||
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
|
||||
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
|
||||
public static synthetic fun serialize$default (Lapp/revanced/library/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
|
||||
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
|
||||
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/Options$Patch {
|
||||
public final fun getOptions ()Ljava/util/List;
|
||||
public final fun getPatchName ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/Options$Patch$Option {
|
||||
public final fun getKey ()Ljava/lang/String;
|
||||
public final fun getValue ()Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/PatchUtils {
|
||||
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
|
||||
public final fun getMostCommonCompatibleVersion (Ljava/util/Set;Ljava/lang/String;)Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/library/adb/AdbManager {
|
||||
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
|
||||
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
protected final fun getDevice ()Lse/vidstige/jadb/JadbDevice;
|
||||
protected final fun getLogger ()Ljava/util/logging/Logger;
|
||||
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
|
||||
public fun uninstall (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/adb/AdbManager$Apk {
|
||||
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getFile ()Ljava/io/File;
|
||||
public final fun getPackageName ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/adb/AdbManager$Companion {
|
||||
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager;
|
||||
public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
|
||||
}
|
||||
|
||||
public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
|
||||
}
|
||||
|
||||
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
|
||||
}
|
||||
|
||||
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
|
||||
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
|
||||
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
|
||||
public fun uninstall (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
|
||||
}
|
||||
|
||||
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
|
||||
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)V
|
||||
public fun uninstall (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/logging/Logger {
|
||||
public static final field INSTANCE Lapp/revanced/library/logging/Logger;
|
||||
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
|
||||
public final fun removeAllHandlers ()V
|
||||
public final fun setDefault ()V
|
||||
public final fun setFormat (Ljava/lang/String;)V
|
||||
public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/zip/ZipFile : java/io/Closeable {
|
||||
public static final field ApkZipFile Lapp/revanced/library/zip/ZipFile$ApkZipFile;
|
||||
public fun <init> (Ljava/io/File;)V
|
||||
public final fun addEntryCompressData (Lapp/revanced/library/zip/structures/ZipEntry;[B)V
|
||||
public fun close ()V
|
||||
public final fun copyEntriesFromFileAligned (Lapp/revanced/library/zip/ZipFile;Lkotlin/jvm/functions/Function1;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/zip/ZipFile$ApkZipFile {
|
||||
public final fun getApkZipEntryAlignment ()Lkotlin/jvm/functions/Function1;
|
||||
}
|
||||
|
||||
public final class app/revanced/library/zip/structures/ZipEntry {
|
||||
public static final field Companion Lapp/revanced/library/zip/structures/ZipEntry$Companion;
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/library/zip/structures/ZipEntry$Companion {
|
||||
}
|
||||
|
||||
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 |
151
build.gradle.kts
151
build.gradle.kts
@@ -1,82 +1,141 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.9.0"
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.binary.compatibility.validator)
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
dependencies {
|
||||
implementation(libs.revanced.patcher)
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.jadb) // Updated fork
|
||||
implementation(libs.apksig)
|
||||
implementation(libs.bcpkix.jdk18on)
|
||||
implementation(libs.jackson.module.kotlin)
|
||||
|
||||
testImplementation(libs.revanced.patcher)
|
||||
testImplementation(libs.kotlin.test)
|
||||
// Because access to the project is necessary to authenticate with GitHub,
|
||||
// the following block must be placed in the root build.gradle.kts file
|
||||
// instead of the settings.gradle.kts file inside the dependencyResolutionManagement block.
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
google()
|
||||
maven {
|
||||
// A repository must be specified for some reason. "registry" is a dummy.
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials {
|
||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
|
||||
tasks {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("PASSED", "SKIPPED", "FAILED")
|
||||
kotlin {
|
||||
jvm {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
}
|
||||
}
|
||||
|
||||
androidTarget {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
}
|
||||
|
||||
publishLibraryVariants("release")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
androidMain.dependencies {
|
||||
implementation(libs.core.ktx)
|
||||
implementation(libs.libsu.nio)
|
||||
implementation(libs.libsu.service)
|
||||
}
|
||||
|
||||
commonMain.dependencies {
|
||||
implementation(libs.apksig)
|
||||
implementation(libs.apkzlib)
|
||||
implementation(libs.bcpkix.jdk18on)
|
||||
implementation(libs.guava)
|
||||
implementation(libs.jadb)
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.revanced.patcher)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test.junit)
|
||||
implementation(libs.revanced.patcher)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin { jvmToolchain(11) }
|
||||
android {
|
||||
namespace = "app.revanced.library"
|
||||
compileSdk = 34
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
create<MavenPublication>("gpr") {
|
||||
from(components["java"])
|
||||
|
||||
version = project.version.toString()
|
||||
// KMP plugin creates a publication already, so just configure the POM.
|
||||
publications.all {
|
||||
if (this !is MavenPublication) return@all
|
||||
|
||||
pom {
|
||||
name = "ReVanced Library"
|
||||
description = "Library containing common utilities for ReVanced"
|
||||
url = "https://revanced.app"
|
||||
pom {
|
||||
name = "ReVanced Library"
|
||||
description = "Library containing common utilities for ReVanced"
|
||||
url = "https://revanced.app"
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name = "GNU General Public License v3.0"
|
||||
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name = "GNU General Public License v3.0"
|
||||
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||
}
|
||||
}
|
||||
|
||||
developers {
|
||||
developer {
|
||||
id = "ReVanced"
|
||||
name = "ReVanced"
|
||||
email = "contact@revanced.app"
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "ReVanced"
|
||||
name = "ReVanced"
|
||||
email = "contact@revanced.app"
|
||||
}
|
||||
}
|
||||
|
||||
scm {
|
||||
connection = "scm:git:git://github.com/revanced/revanced-library.git"
|
||||
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
|
||||
url = "https://github.com/revanced/revanced-library"
|
||||
}
|
||||
scm {
|
||||
connection = "scm:git:git://github.com/revanced/revanced-library.git"
|
||||
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
|
||||
url = "https://github.com/revanced/revanced-library"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(publishing.publications)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
org.gradle.parallel = true
|
||||
version = 3.2.0-dev.1
|
||||
#Gradle
|
||||
org.gradle.jvmargs = -Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options="-Xmx2048M"
|
||||
org.gradle.caching = true
|
||||
org.gradle.configuration-cache = true
|
||||
org.gradle.parallel = true
|
||||
#Kotlin
|
||||
kotlin.code.style = official
|
||||
version = 1.1.2-dev.1
|
||||
#Android
|
||||
android.useAndroidX = true
|
||||
android.nonTransitiveRClass = true
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
[versions]
|
||||
apksig = "8.1.1"
|
||||
bcpkix-jdk18on = "1.76"
|
||||
jackson-module-kotlin = "2.14.3"
|
||||
jadb = "2531a28109"
|
||||
kotlin-reflect = "1.9.0"
|
||||
kotlin-test = "1.8.20-RC"
|
||||
revanced-patcher = "16.0.1"
|
||||
binary-compatibility-validator = "0.13.2"
|
||||
android = "8.13.2"
|
||||
bcpkix-jdk18on = "1.77"
|
||||
binary-compatibility-validator = "0.15.1"
|
||||
core-ktx = "1.15.0"
|
||||
guava = "33.2.1-jre"
|
||||
jadb = "1.2.1.1"
|
||||
kotlin = "2.0.20"
|
||||
kotlinx-coroutines = "1.8.1"
|
||||
kotlinx-serialization = "1.7.1"
|
||||
libsu = "5.2.2"
|
||||
revanced-patcher = "21.0.0"
|
||||
|
||||
[libraries]
|
||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
||||
apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "android" }
|
||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "android" }
|
||||
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkix-jdk18on" }
|
||||
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" }
|
||||
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
|
||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||
jadb = { module = "app.revanced:jadb", version.ref = "jadb" } # Fork with Shell v2 support.
|
||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
||||
libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
|
||||
libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" }
|
||||
libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" }
|
||||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
||||
|
||||
[plugins]
|
||||
android-library = { id = "com.android.library", version.ref = "android" }
|
||||
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
22
gradlew
vendored
22
gradlew
vendored
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -83,7 +85,9 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -201,11 +205,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
||||
22
gradlew.bat
vendored
22
gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
6823
package-lock.json
generated
6823
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
||||
"@semantic-release/changelog": "^6.0.2",
|
||||
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.7.6",
|
||||
"semantic-release": "^20.1.0"
|
||||
"gradle-semantic-release-plugin": "^1.10.1",
|
||||
"semantic-release": "^24.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
val githubUsername: String = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
|
||||
val githubPassword: String = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
|
||||
// TODO: Figure out why this causes problems.
|
||||
rootProject.name = "revanced-library"
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
buildCache {
|
||||
local {
|
||||
isEnabled = "CI" !in System.getenv()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "revanced-library"
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package app.revanced.library.installation.command;
|
||||
|
||||
interface ILocalShellCommandRunnerRootService {
|
||||
IBinder getFileSystemService();
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package app.revanced.library.installation.command
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.BuilderImpl
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import com.topjohnwu.superuser.nio.FileSystemManager
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* The [LocalShellCommandRunner] for running commands locally on the device.
|
||||
*
|
||||
* @param context The [Context] to use for binding to the [RootService].
|
||||
* @param onReady A callback to be invoked when [LocalShellCommandRunner] is ready to be used.
|
||||
* @throws IllegalStateException If the main shell was already created
|
||||
*
|
||||
* @see ShellCommandRunner
|
||||
*/
|
||||
class LocalShellCommandRunner internal constructor(
|
||||
private val context: Context,
|
||||
private val onReady: () -> Unit,
|
||||
) : ShellCommandRunner(), ServiceConnection, Closeable {
|
||||
private var fileSystemManager: FileSystemManager? = null
|
||||
|
||||
init {
|
||||
logger.info("Binding to RootService")
|
||||
val intent = Intent(context, LocalShellCommandRunnerRootService::class.java)
|
||||
RootService.bind(intent, this)
|
||||
}
|
||||
|
||||
override fun runCommand(command: String) = shell.newJob().add(command).exec().let {
|
||||
object : RunResult {
|
||||
override val exitCode = it.code
|
||||
override val output by lazy { it.out.joinToString("\n") }
|
||||
override val error by lazy { it.err.joinToString("\n") }
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasRootPermission() = shell.isRoot
|
||||
|
||||
/**
|
||||
* Writes the given [content] to the given [targetFilePath].
|
||||
*
|
||||
* @param content The [InputStream] to write.
|
||||
* @param targetFilePath The path to write to.
|
||||
* @throws NotReadyException If the [LocalShellCommandRunner] is not ready yet.
|
||||
*/
|
||||
override fun write(content: InputStream, targetFilePath: String) {
|
||||
fileSystemManager?.let {
|
||||
it.getFile(targetFilePath).newOutputStream().use { outputStream ->
|
||||
content.copyTo(outputStream)
|
||||
}
|
||||
} ?: throw NotReadyException("FileSystemManager service is not ready yet")
|
||||
}
|
||||
|
||||
override fun move(file: File, targetFilePath: String) {
|
||||
invoke("mv ${file.absolutePath} $targetFilePath")
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
val ipc = ILocalShellCommandRunnerRootService.Stub.asInterface(service)
|
||||
fileSystemManager = FileSystemManager.getRemote(ipc.fileSystemService)
|
||||
|
||||
logger.info("LocalShellCommandRunner service is ready")
|
||||
|
||||
onReady()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
fileSystemManager = null
|
||||
|
||||
logger.info("LocalShellCommandRunner service is disconnected")
|
||||
}
|
||||
|
||||
override fun close() = RootService.unbind(this)
|
||||
|
||||
private companion object {
|
||||
private val shell = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER).build()
|
||||
}
|
||||
|
||||
internal class NotReadyException internal constructor(message: String) : Exception(message)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package app.revanced.library.installation.command
|
||||
|
||||
import android.content.Intent
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import com.topjohnwu.superuser.nio.FileSystemManager
|
||||
|
||||
/**
|
||||
* The [RootService] for the [LocalShellCommandRunner].
|
||||
*/
|
||||
internal class LocalShellCommandRunnerRootService : RootService() {
|
||||
override fun onBind(intent: Intent) = object : ILocalShellCommandRunnerRootService.Stub() {
|
||||
override fun getFileSystemService() =
|
||||
FileSystemManager.getService()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import app.revanced.library.installation.installer.Installer.Apk
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* [LocalInstaller] for installing and uninstalling [Apk] files locally.
|
||||
*
|
||||
* @param context The [Context] to use for installing and uninstalling.
|
||||
* @param onResult The callback to be invoked when the [Apk] is installed or uninstalled.
|
||||
*
|
||||
* @see Installer
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class LocalInstaller(
|
||||
private val context: Context,
|
||||
onResult: (result: LocalInstallerResult) -> Unit,
|
||||
) : Installer<Unit, Installation>(), Closeable {
|
||||
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val pmStatus = intent.getIntExtra(LocalInstallerService.EXTRA_STATUS, -999)
|
||||
val extra = intent.getStringExtra(LocalInstallerService.EXTRA_STATUS_MESSAGE)!!
|
||||
val packageName = intent.getStringExtra(LocalInstallerService.EXTRA_PACKAGE_NAME)!!
|
||||
|
||||
onResult.invoke(LocalInstallerResult(pmStatus, extra, packageName))
|
||||
}
|
||||
}
|
||||
|
||||
private val intentSender
|
||||
get() = PendingIntent.getService(
|
||||
context,
|
||||
0,
|
||||
Intent(context, LocalInstallerService::class.java),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
).intentSender
|
||||
|
||||
init {
|
||||
ContextCompat.registerReceiver(
|
||||
context,
|
||||
broadcastReceiver,
|
||||
IntentFilter().apply {
|
||||
addAction(LocalInstallerService.ACTION)
|
||||
},
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun install(apk: Apk) {
|
||||
logger.info("Installing ${apk.file.name}")
|
||||
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams)).use { session ->
|
||||
session.writeApk(apk.file)
|
||||
session.commit(intentSender)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override suspend fun uninstall(packageName: String) {
|
||||
logger.info("Uninstalling $packageName")
|
||||
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
|
||||
packageInstaller.uninstall(packageName, intentSender)
|
||||
}
|
||||
|
||||
override suspend fun getInstallation(packageName: String) = try {
|
||||
val packageInfo = context.packageManager.getPackageInfo(packageName, 0)
|
||||
|
||||
Installation(packageInfo.applicationInfo.sourceDir)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
|
||||
override fun close() = context.unregisterReceiver(broadcastReceiver)
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
companion object {
|
||||
private val sessionParams = PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL,
|
||||
).apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
setRequestUpdateOwnership(true)
|
||||
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||
}
|
||||
|
||||
private fun PackageInstaller.Session.writeApk(apk: File) {
|
||||
apk.inputStream().use { inputStream ->
|
||||
openWrite(apk.name, 0, apk.length()).use { outputStream ->
|
||||
inputStream.copyTo(outputStream, 1024 * 1024)
|
||||
fsync(outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import app.revanced.library.installation.installer.Installer.Apk
|
||||
|
||||
/**
|
||||
* The result of installing or uninstalling an [Apk] locally using [LocalInstaller].
|
||||
*
|
||||
* @param pmStatus The status code returned by the package manager.
|
||||
* @param extra The extra information returned by the package manager.
|
||||
* @param packageName The package name of the installed app.
|
||||
*
|
||||
* @see LocalInstaller
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class LocalInstallerResult internal constructor(val pmStatus: Int, val extra: String, val packageName: String)
|
||||
@@ -0,0 +1,57 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
|
||||
class LocalInstallerService : Service() {
|
||||
override fun onStartCommand(
|
||||
intent: Intent, flags: Int, startId: Int
|
||||
): Int {
|
||||
val extraStatus = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)
|
||||
val extraStatusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||
val extraPackageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
||||
|
||||
when (extraStatus) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
startActivity(
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||
}?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
sendBroadcast(
|
||||
Intent().apply {
|
||||
action = ACTION
|
||||
`package` = packageName
|
||||
|
||||
putExtra(EXTRA_STATUS, extraStatus)
|
||||
putExtra(EXTRA_STATUS_MESSAGE, extraStatusMessage)
|
||||
putExtra(EXTRA_PACKAGE_NAME, extraPackageName)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
stopSelf()
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
internal companion object {
|
||||
internal const val ACTION = "PACKAGE_INSTALLER_ACTION"
|
||||
|
||||
internal const val EXTRA_STATUS = "EXTRA_STATUS"
|
||||
internal const val EXTRA_STATUS_MESSAGE = "EXTRA_STATUS_MESSAGE"
|
||||
internal const val EXTRA_PACKAGE_NAME = "EXTRA_PACKAGE_NAME"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import android.content.Context
|
||||
import app.revanced.library.installation.command.LocalShellCommandRunner
|
||||
import app.revanced.library.installation.installer.Installer.Apk
|
||||
import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* [LocalRootInstaller] for installing and uninstalling [Apk] files locally with using root permissions by mounting.
|
||||
*
|
||||
* @param context The [Context] to use for binding to the [RootService].
|
||||
* @param onReady A callback to be invoked when [LocalRootInstaller] is ready to be used.
|
||||
*
|
||||
* @throws NoRootPermissionException If the device does not have root permission.
|
||||
*
|
||||
* @see Installer
|
||||
* @see LocalShellCommandRunner
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class LocalRootInstaller(
|
||||
context: Context,
|
||||
onReady: LocalRootInstaller.() -> Unit = {},
|
||||
) : RootInstaller(
|
||||
{ installer ->
|
||||
LocalShellCommandRunner(context) {
|
||||
(installer as LocalRootInstaller).onReady()
|
||||
}
|
||||
},
|
||||
),
|
||||
Closeable {
|
||||
override fun close() = (shellCommandRunner as LocalShellCommandRunner).close()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package app.revanced.library
|
||||
|
||||
import com.android.apksig.ApkSigner
|
||||
import com.android.apksig.ApkSigner.SignerConfig
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||
@@ -10,112 +10,41 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.logging.Logger
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
* Utility class for writing or reading keystore files and entries as well as signing APK files.
|
||||
* Utility class for reading or writing keystore files and entries as well as signing APK files.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
object ApkSigner {
|
||||
private val logger = Logger.getLogger(app.revanced.library.ApkSigner::class.java.name)
|
||||
private val logger = Logger.getLogger(ApkSigner::class.java.name)
|
||||
|
||||
init {
|
||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
|
||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [PrivateKeyCertificatePair].
|
||||
*
|
||||
* @param commonName The common name of the certificate.
|
||||
* @param validUntil The date until the certificate is valid.
|
||||
* @return The created [PrivateKeyCertificatePair].
|
||||
*/
|
||||
fun newPrivateKeyCertificatePair(
|
||||
commonName: String = "ReVanced",
|
||||
validUntil: Date = Date(System.currentTimeMillis() + 356.days.inWholeMilliseconds * 24)
|
||||
): PrivateKeyCertificatePair {
|
||||
logger.fine("Creating certificate for $commonName")
|
||||
|
||||
// Generate a new key pair.
|
||||
val keyPair = KeyPairGenerator.getInstance("RSA").apply {
|
||||
initialize(4096)
|
||||
}.generateKeyPair()
|
||||
|
||||
var serialNumber: BigInteger
|
||||
do serialNumber = BigInteger.valueOf(SecureRandom().nextLong())
|
||||
while (serialNumber < BigInteger.ZERO)
|
||||
|
||||
val name = X500Name("CN=$commonName")
|
||||
|
||||
// Create a new certificate.
|
||||
val certificate = JcaX509CertificateConverter().getCertificate(
|
||||
X509v3CertificateBuilder(
|
||||
name,
|
||||
serialNumber,
|
||||
Date(System.currentTimeMillis()),
|
||||
validUntil,
|
||||
Locale.ENGLISH,
|
||||
name,
|
||||
SubjectPublicKeyInfo.getInstance(keyPair.public.encoded)
|
||||
).build(JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private))
|
||||
)
|
||||
|
||||
return PrivateKeyCertificatePair(keyPair.private, certificate)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a [PrivateKeyCertificatePair] from a keystore entry.
|
||||
*
|
||||
* @param keyStore The keystore to read the entry from.
|
||||
* @param keyStoreEntryAlias The alias of the key store entry to read.
|
||||
* @param keyStoreEntryPassword The password for recovering the signing key.
|
||||
* @return The read [PrivateKeyCertificatePair].
|
||||
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
|
||||
*/
|
||||
fun readKeyCertificatePair(
|
||||
keyStore: KeyStore,
|
||||
keyStoreEntryAlias: String,
|
||||
keyStoreEntryPassword: String,
|
||||
): PrivateKeyCertificatePair {
|
||||
logger.fine("Reading key and certificate pair from keystore entry $keyStoreEntryAlias")
|
||||
|
||||
if (!keyStore.containsAlias(keyStoreEntryAlias))
|
||||
throw IllegalArgumentException("Keystore does not contain alias $keyStoreEntryAlias")
|
||||
|
||||
// Read the private key and certificate from the keystore.
|
||||
|
||||
val privateKey = try {
|
||||
keyStore.getKey(keyStoreEntryAlias, keyStoreEntryPassword.toCharArray()) as PrivateKey
|
||||
} catch (exception: UnrecoverableKeyException) {
|
||||
throw IllegalArgumentException("Invalid password for keystore entry $keyStoreEntryAlias")
|
||||
}
|
||||
|
||||
val certificate = keyStore.getCertificate(keyStoreEntryAlias) as X509Certificate
|
||||
|
||||
return PrivateKeyCertificatePair(privateKey, certificate)
|
||||
}
|
||||
|
||||
private fun newKeyStoreInstance() = KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME)
|
||||
|
||||
/**
|
||||
* Create a new keystore with a new keypair.
|
||||
*
|
||||
* @param entries The entries to add to the keystore.
|
||||
*
|
||||
* @return The created keystore.
|
||||
*
|
||||
* @see KeyStoreEntry
|
||||
* @see KeyStore
|
||||
*/
|
||||
fun newKeyStore(
|
||||
entries: List<KeyStoreEntry>
|
||||
): KeyStore {
|
||||
fun newKeyStore(entries: Set<KeyStoreEntry>): KeyStore {
|
||||
logger.fine("Creating keystore")
|
||||
|
||||
return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
|
||||
return newKeyStoreInstance().apply {
|
||||
load(null)
|
||||
|
||||
entries.forEach { entry ->
|
||||
@@ -124,133 +53,159 @@ object ApkSigner {
|
||||
entry.alias,
|
||||
entry.privateKeyCertificatePair.privateKey,
|
||||
entry.password.toCharArray(),
|
||||
arrayOf(entry.privateKeyCertificatePair.certificate)
|
||||
arrayOf(entry.privateKeyCertificatePair.certificate),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new keystore with a new keypair and saves it to the given [keyStoreOutputStream].
|
||||
*
|
||||
* @param keyStoreOutputStream The stream to write the keystore to.
|
||||
* @param keyStorePassword The password for the keystore.
|
||||
* @param entries The entries to add to the keystore.
|
||||
*/
|
||||
fun newKeyStore(
|
||||
keyStoreOutputStream: OutputStream,
|
||||
keyStorePassword: String,
|
||||
entries: List<KeyStoreEntry>
|
||||
) = newKeyStore(entries).store(
|
||||
keyStoreOutputStream,
|
||||
keyStorePassword.toCharArray()
|
||||
) // Save the keystore.
|
||||
|
||||
/**
|
||||
* Read a keystore from the given [keyStoreInputStream].
|
||||
*
|
||||
* @param keyStoreInputStream The stream to read the keystore from.
|
||||
* @param keyStorePassword The password for the keystore.
|
||||
*
|
||||
* @return The keystore.
|
||||
*
|
||||
* @throws IllegalArgumentException If the keystore password is invalid.
|
||||
*
|
||||
* @see KeyStore
|
||||
*/
|
||||
fun readKeyStore(
|
||||
keyStoreInputStream: InputStream,
|
||||
keyStorePassword: String?
|
||||
keyStorePassword: String?,
|
||||
): KeyStore {
|
||||
logger.fine("Reading keystore")
|
||||
|
||||
return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
|
||||
return newKeyStoreInstance().apply {
|
||||
try {
|
||||
load(keyStoreInputStream, keyStorePassword?.toCharArray())
|
||||
} catch (exception: IOException) {
|
||||
if (exception.cause is UnrecoverableKeyException)
|
||||
if (exception.cause is UnrecoverableKeyException) {
|
||||
throw IllegalArgumentException("Invalid keystore password")
|
||||
else
|
||||
} else {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [ApkSigner.Builder].
|
||||
* Create a new private key and certificate pair.
|
||||
*
|
||||
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
|
||||
* @param signer The name of the signer.
|
||||
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
|
||||
* @return The created [ApkSigner.Builder] instance.
|
||||
* @param commonName The common name of the certificate.
|
||||
* @param validUntil The date until which the certificate is valid.
|
||||
*
|
||||
* @return The newly created private key and certificate pair.
|
||||
*
|
||||
* @see PrivateKeyCertificatePair
|
||||
*/
|
||||
fun newApkSignerBuilder(
|
||||
privateKeyCertificatePair: PrivateKeyCertificatePair,
|
||||
signer: String,
|
||||
createdBy: String
|
||||
): ApkSigner.Builder {
|
||||
logger.fine(
|
||||
"Creating new ApkSigner " +
|
||||
"with $signer as signer and " +
|
||||
"$createdBy as Created-By attribute in the APK's manifest"
|
||||
)
|
||||
fun newPrivateKeyCertificatePair(
|
||||
commonName: String,
|
||||
validUntil: Date,
|
||||
): PrivateKeyCertificatePair {
|
||||
logger.fine("Creating certificate for $commonName")
|
||||
|
||||
// Create the signer config.
|
||||
val signerConfig = ApkSigner.SignerConfig.Builder(
|
||||
signer,
|
||||
privateKeyCertificatePair.privateKey,
|
||||
listOf(privateKeyCertificatePair.certificate)
|
||||
).build()
|
||||
// Generate a new key pair.
|
||||
val keyPair = KeyPairGenerator.getInstance("RSA").apply {
|
||||
initialize(4096)
|
||||
}.generateKeyPair()
|
||||
|
||||
// Create the signer.
|
||||
return ApkSigner.Builder(listOf(signerConfig)).apply {
|
||||
setCreatedBy(createdBy)
|
||||
}
|
||||
val contentSigner = JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private)
|
||||
|
||||
val name = X500Name("CN=$commonName")
|
||||
val certificateHolder = X509v3CertificateBuilder(
|
||||
name,
|
||||
BigInteger.valueOf(SecureRandom().nextLong()),
|
||||
Date(System.currentTimeMillis()),
|
||||
validUntil,
|
||||
Locale.ENGLISH,
|
||||
name,
|
||||
SubjectPublicKeyInfo.getInstance(keyPair.public.encoded),
|
||||
).build(contentSigner)
|
||||
val certificate = JcaX509CertificateConverter().getCertificate(certificateHolder)
|
||||
|
||||
return PrivateKeyCertificatePair(keyPair.private, certificate)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [ApkSigner.Builder].
|
||||
* Read a [PrivateKeyCertificatePair] from a keystore entry.
|
||||
*
|
||||
* @param keyStore The keystore to use for signing.
|
||||
* @param keyStoreEntryAlias The alias of the key store entry to use for signing.
|
||||
* @param keyStore The keystore to read the entry from.
|
||||
* @param keyStoreEntryAlias The alias of the key store entry to read.
|
||||
* @param keyStoreEntryPassword The password for recovering the signing key.
|
||||
* @param signer The name of the signer.
|
||||
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
|
||||
* @return The created [ApkSigner.Builder] instance.
|
||||
* @see KeyStoreEntry
|
||||
*
|
||||
* @return The read [PrivateKeyCertificatePair].
|
||||
*
|
||||
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
|
||||
*
|
||||
* @see PrivateKeyCertificatePair
|
||||
* @see ApkSigner.Builder.setCreatedBy
|
||||
* @see ApkSigner.Builder.signApk
|
||||
* @see KeyStore
|
||||
*/
|
||||
fun newApkSignerBuilder(
|
||||
fun readPrivateKeyCertificatePair(
|
||||
keyStore: KeyStore,
|
||||
keyStoreEntryAlias: String,
|
||||
keyStoreEntryPassword: String,
|
||||
signer: String,
|
||||
createdBy: String,
|
||||
) = newApkSignerBuilder(
|
||||
readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword),
|
||||
signer,
|
||||
createdBy
|
||||
)
|
||||
): PrivateKeyCertificatePair {
|
||||
logger.fine("Reading key and certificate pair from keystore entry $keyStoreEntryAlias")
|
||||
|
||||
fun ApkSigner.Builder.signApk(input: File, output: File) {
|
||||
logger.info("Signing ${input.name}")
|
||||
if (!keyStore.containsAlias(keyStoreEntryAlias)) {
|
||||
throw IllegalArgumentException("Keystore does not contain entry with alias $keyStoreEntryAlias")
|
||||
}
|
||||
|
||||
setInputApk(input)
|
||||
setOutputApk(output)
|
||||
// Read the private key and certificate from the keystore.
|
||||
|
||||
build().sign()
|
||||
val privateKey =
|
||||
try {
|
||||
keyStore.getKey(keyStoreEntryAlias, keyStoreEntryPassword.toCharArray()) as PrivateKey
|
||||
} catch (exception: UnrecoverableKeyException) {
|
||||
throw IllegalArgumentException("Invalid password for keystore entry $keyStoreEntryAlias")
|
||||
}
|
||||
|
||||
val certificate = keyStore.getCertificate(keyStoreEntryAlias) as X509Certificate
|
||||
|
||||
return PrivateKeyCertificatePair(privateKey, certificate)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [Signer].
|
||||
*
|
||||
* @param signer The name of the signer.
|
||||
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
|
||||
*
|
||||
* @return The new [Signer].
|
||||
*
|
||||
* @see PrivateKeyCertificatePair
|
||||
* @see Signer
|
||||
*/
|
||||
fun newApkSigner(
|
||||
signer: String,
|
||||
privateKeyCertificatePair: PrivateKeyCertificatePair,
|
||||
) = Signer(
|
||||
com.android.apksig.ApkSigner.Builder(
|
||||
listOf(
|
||||
SignerConfig.Builder(
|
||||
signer,
|
||||
privateKeyCertificatePair.privateKey,
|
||||
listOf(privateKeyCertificatePair.certificate),
|
||||
).build(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* An entry in a keystore.
|
||||
*
|
||||
* @param alias The alias of the entry.
|
||||
* @param password The password for recovering the signing key.
|
||||
* @param privateKeyCertificatePair The private key and certificate pair.
|
||||
*
|
||||
* @see PrivateKeyCertificatePair
|
||||
*/
|
||||
class KeyStoreEntry(
|
||||
val alias: String,
|
||||
val password: String,
|
||||
val privateKeyCertificatePair: PrivateKeyCertificatePair = newPrivateKeyCertificatePair()
|
||||
val privateKeyCertificatePair: PrivateKeyCertificatePair,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -263,4 +218,12 @@ object ApkSigner {
|
||||
val privateKey: PrivateKey,
|
||||
val certificate: X509Certificate,
|
||||
)
|
||||
}
|
||||
|
||||
class Signer internal constructor(private val signerBuilder: com.android.apksig.ApkSigner.Builder) {
|
||||
fun signApk(inputApkFile: File, outputApkFile: File) {
|
||||
logger.info("Signing APK")
|
||||
|
||||
signerBuilder.setInputApk(inputApkFile)?.setOutputApk(outputApkFile)?.build()?.sign()
|
||||
}
|
||||
}
|
||||
}
|
||||
194
src/commonMain/kotlin/app/revanced/library/ApkUtils.kt
Normal file
194
src/commonMain/kotlin/app/revanced/library/ApkUtils.kt
Normal file
@@ -0,0 +1,194 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.library.ApkSigner.newPrivateKeyCertificatePair
|
||||
import app.revanced.patcher.PatcherResult
|
||||
import com.android.tools.build.apkzlib.zip.AlignmentRules
|
||||
import com.android.tools.build.apkzlib.zip.StoredEntry
|
||||
import com.android.tools.build.apkzlib.zip.ZFile
|
||||
import com.android.tools.build.apkzlib.zip.ZFileOptions
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.logging.Logger
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
* Utility functions to work with APK files.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
object ApkUtils {
|
||||
private val logger = Logger.getLogger(ApkUtils::class.java.name)
|
||||
|
||||
private const val LIBRARY_EXTENSION = ".so"
|
||||
|
||||
// Alignment for native libraries.
|
||||
private const val LIBRARY_ALIGNMENT = 1024 * 4
|
||||
|
||||
// Alignment for all other files.
|
||||
private const val DEFAULT_ALIGNMENT = 4
|
||||
|
||||
// Prefix for resources.
|
||||
private const val RES_PREFIX = "res/"
|
||||
|
||||
private val zFileOptions =
|
||||
ZFileOptions().setAlignmentRule(
|
||||
AlignmentRules.compose(
|
||||
AlignmentRules.constantForSuffix(LIBRARY_EXTENSION, LIBRARY_ALIGNMENT),
|
||||
AlignmentRules.constant(DEFAULT_ALIGNMENT),
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Applies the [PatcherResult] to the given [apkFile].
|
||||
*
|
||||
* The order of operation is as follows:
|
||||
* 1. Write patched dex files.
|
||||
* 2. Delete all resources in the target APK
|
||||
* 3. Merge resources.apk compiled by AAPT.
|
||||
* 4. Write raw resources.
|
||||
* 5. Delete resources staged for deletion.
|
||||
* 6. Realign the APK.
|
||||
*
|
||||
* @param apkFile The file to apply the patched files to.
|
||||
*/
|
||||
fun PatcherResult.applyTo(apkFile: File) {
|
||||
ZFile.openReadWrite(apkFile, zFileOptions).use { targetApkZFile ->
|
||||
dexFiles.forEach { dexFile ->
|
||||
targetApkZFile.add(dexFile.name, dexFile.stream)
|
||||
}
|
||||
|
||||
resources?.let { resources ->
|
||||
// Add resources compiled by AAPT.
|
||||
resources.resourcesApk?.let { resourcesApk ->
|
||||
ZFile.openReadOnly(resourcesApk).use { resourcesApkZFile ->
|
||||
// Delete all resources in the target APK before merging the new ones.
|
||||
// This is necessary because the resources.apk renames resources.
|
||||
// So unless, the old resources are deleted, there will be orphaned resources in the APK.
|
||||
// It is not necessary, but for the sake of cleanliness, it is done.
|
||||
targetApkZFile.entries().filter { entry ->
|
||||
entry.centralDirectoryHeader.name.startsWith(RES_PREFIX)
|
||||
}.forEach(StoredEntry::delete)
|
||||
|
||||
targetApkZFile.mergeFrom(resourcesApkZFile) { false }
|
||||
}
|
||||
}
|
||||
|
||||
// Add resources not compiled by AAPT.
|
||||
resources.otherResources?.let { otherResources ->
|
||||
targetApkZFile.addAllRecursively(otherResources) { file ->
|
||||
file.relativeTo(otherResources).invariantSeparatorsPath !in resources.doNotCompress
|
||||
}
|
||||
}
|
||||
|
||||
// Delete resources that were staged for deletion.
|
||||
if (resources.deleteResources.isNotEmpty()) {
|
||||
targetApkZFile.entries().filter { entry ->
|
||||
entry.centralDirectoryHeader.name in resources.deleteResources
|
||||
}.forEach(StoredEntry::delete)
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Aligning APK")
|
||||
|
||||
targetApkZFile.realign()
|
||||
|
||||
logger.fine("Writing changes")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new private key and certificate pair and saves it to the keystore in [keyStoreDetails].
|
||||
*
|
||||
* @param privateKeyCertificatePairDetails The details for the private key and certificate pair.
|
||||
* @param keyStoreDetails The details for the keystore.
|
||||
*
|
||||
* @return The newly created private key and certificate pair.
|
||||
*/
|
||||
private fun newPrivateKeyCertificatePair(
|
||||
privateKeyCertificatePairDetails: PrivateKeyCertificatePairDetails,
|
||||
keyStoreDetails: KeyStoreDetails,
|
||||
) = newPrivateKeyCertificatePair(
|
||||
privateKeyCertificatePairDetails.commonName,
|
||||
privateKeyCertificatePairDetails.validUntil,
|
||||
).also { privateKeyCertificatePair ->
|
||||
ApkSigner.newKeyStore(
|
||||
setOf(
|
||||
ApkSigner.KeyStoreEntry(
|
||||
keyStoreDetails.alias,
|
||||
keyStoreDetails.password,
|
||||
privateKeyCertificatePair,
|
||||
),
|
||||
),
|
||||
).store(
|
||||
keyStoreDetails.keyStore.outputStream(),
|
||||
keyStoreDetails.keyStorePassword?.toCharArray(),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the private key and certificate pair from an existing keystore.
|
||||
*
|
||||
* @param keyStoreDetails The details for the keystore.
|
||||
*
|
||||
* @return The private key and certificate pair.
|
||||
*/
|
||||
private fun readPrivateKeyCertificatePairFromKeyStore(
|
||||
keyStoreDetails: KeyStoreDetails,
|
||||
) = ApkSigner.readPrivateKeyCertificatePair(
|
||||
ApkSigner.readKeyStore(
|
||||
keyStoreDetails.keyStore.inputStream(),
|
||||
keyStoreDetails.keyStorePassword,
|
||||
),
|
||||
keyStoreDetails.alias,
|
||||
keyStoreDetails.password,
|
||||
)
|
||||
|
||||
/**
|
||||
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
|
||||
* If [KeyStoreDetails.keyStore] does not exist,
|
||||
* a new private key and certificate pair will be created and saved to the keystore.
|
||||
*
|
||||
* @param inputApkFile The apk file to sign.
|
||||
* @param outputApkFile The file to save the signed apk to.
|
||||
* @param signer The name of the signer.
|
||||
* @param keyStoreDetails The details for the keystore.
|
||||
*/
|
||||
fun signApk(
|
||||
inputApkFile: File,
|
||||
outputApkFile: File,
|
||||
signer: String,
|
||||
keyStoreDetails: KeyStoreDetails,
|
||||
) = ApkSigner.newApkSigner(
|
||||
signer,
|
||||
if (keyStoreDetails.keyStore.exists()) {
|
||||
readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails)
|
||||
} else {
|
||||
newPrivateKeyCertificatePair(PrivateKeyCertificatePairDetails(), keyStoreDetails)
|
||||
},
|
||||
).signApk(inputApkFile, outputApkFile)
|
||||
|
||||
/**
|
||||
* Details for a keystore.
|
||||
*
|
||||
* @param keyStore The file to save the keystore to.
|
||||
* @param keyStorePassword The password for the keystore.
|
||||
* @param alias The alias of the key store entry to use for signing.
|
||||
* @param password The password for recovering the signing key.
|
||||
*/
|
||||
class KeyStoreDetails(
|
||||
val keyStore: File,
|
||||
val keyStorePassword: String? = null,
|
||||
val alias: String = "ReVanced Key",
|
||||
val password: String = "",
|
||||
)
|
||||
|
||||
/**
|
||||
* Details for a private key and certificate pair.
|
||||
*
|
||||
* @param commonName The common name for the certificate saved in the keystore.
|
||||
* @param validUntil The date until which the certificate is valid.
|
||||
*/
|
||||
class PrivateKeyCertificatePairDetails(
|
||||
val commonName: String = "ReVanced",
|
||||
val validUntil: Date = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24),
|
||||
)
|
||||
}
|
||||
36
src/commonMain/kotlin/app/revanced/library/Options.kt
Normal file
36
src/commonMain/kotlin/app/revanced/library/Options.kt
Normal file
@@ -0,0 +1,36 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.patcher.patch.OptionException
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import java.util.logging.Logger
|
||||
|
||||
typealias PatchName = String
|
||||
typealias OptionKey = String
|
||||
typealias OptionValue = Any?
|
||||
typealias PatchesOptions = Map<PatchName, Map<OptionKey, OptionValue>>
|
||||
|
||||
private val logger = Logger.getLogger("Options")
|
||||
|
||||
/**
|
||||
* Set the options for a set of patches that have a name.
|
||||
*
|
||||
* @param options The options to set. The key is the patch name and the value is a map of option keys to option values.
|
||||
*/
|
||||
fun Set<Patch<*>>.setOptions(options: PatchesOptions) = filter { it.name != null }.forEach { patch ->
|
||||
options[patch.name]?.forEach setOption@{ (optionKey, optionValue) ->
|
||||
if (optionKey !in patch.options) {
|
||||
return@setOption logger.warning(
|
||||
"Could not set option for the \"${patch.name}\" patch because " +
|
||||
"option with key \"${optionKey}\" does not exist",
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
patch.options[optionKey] = optionValue
|
||||
} catch (e: OptionException) {
|
||||
logger.warning("Could not set option value for the \"${patch.name}\" patch: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/commonMain/kotlin/app/revanced/library/Patch.kt
Normal file
52
src/commonMain/kotlin/app/revanced/library/Patch.kt
Normal file
@@ -0,0 +1,52 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.patcher.patch.Package
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
typealias PackageName = String
|
||||
typealias Version = String
|
||||
typealias Count = Int
|
||||
|
||||
typealias VersionMap = LinkedHashMap<Version, Count>
|
||||
typealias PackageNameMap = Map<PackageName, VersionMap>
|
||||
|
||||
/**
|
||||
* Get the count of versions for each compatible package from the set of [Patch] ordered by the most common version.
|
||||
*
|
||||
* @param packageNames The names of the compatible packages to include. If null, all packages will be included.
|
||||
* @param countUnusedPatches Whether to count patches that are not used.
|
||||
* @return A map of package names to a map of versions to their count.
|
||||
*/
|
||||
fun Set<Patch<*>>.mostCommonCompatibleVersions(
|
||||
packageNames: Set<String>? = null,
|
||||
countUnusedPatches: Boolean = false,
|
||||
): PackageNameMap = buildMap {
|
||||
fun filterWantedPackages(compatiblePackages: List<Package>): List<Package> {
|
||||
val wantedPackages = packageNames?.toHashSet() ?: return compatiblePackages
|
||||
return compatiblePackages.filter { (name, _) -> name in wantedPackages }
|
||||
}
|
||||
|
||||
this@mostCommonCompatibleVersions.filter { it.use || countUnusedPatches }
|
||||
.flatMap { it.compatiblePackages ?: emptyList() }
|
||||
.let(::filterWantedPackages)
|
||||
.forEach { (name, versions) ->
|
||||
if (versions?.isEmpty() == true) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val versionMap = getOrPut(name) { linkedMapOf() }
|
||||
|
||||
versions?.forEach { version ->
|
||||
versionMap[version] = versionMap.getOrDefault(version, 0) + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the version maps by the most common version.
|
||||
forEach { (packageName, versionMap) ->
|
||||
this[packageName] =
|
||||
versionMap
|
||||
.asIterable()
|
||||
.sortedWith(compareByDescending { it.value })
|
||||
.associate { it.key to it.value } as VersionMap
|
||||
}
|
||||
}
|
||||
119
src/commonMain/kotlin/app/revanced/library/Serialization.kt
Normal file
119
src/commonMain/kotlin/app/revanced/library/Serialization.kt
Normal file
@@ -0,0 +1,119 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.patcher.patch.Option
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.VersionName
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.*
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.encoding.encodeStructure
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.encodeToStream
|
||||
import kotlinx.serialization.serializer
|
||||
import java.io.OutputStream
|
||||
|
||||
private class PatchSerializer : KSerializer<Patch<*>> {
|
||||
override val descriptor = buildClassSerialDescriptor("Patch") {
|
||||
element<String?>("name")
|
||||
element<String?>("description")
|
||||
element<Boolean>("use")
|
||||
element<List<String>>("dependencies")
|
||||
element<Map<PackageName, Set<VersionName>?>?>("compatiblePackages")
|
||||
element("options", OptionSerializer.descriptor)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder) = throw NotImplementedError("Deserialization is unsupported")
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
override fun serialize(encoder: Encoder, value: Patch<*>) {
|
||||
encoder.encodeStructure(descriptor) {
|
||||
encodeNullableSerializableElement(
|
||||
descriptor,
|
||||
0,
|
||||
String.serializer(),
|
||||
value.name,
|
||||
)
|
||||
encodeNullableSerializableElement(
|
||||
descriptor,
|
||||
1,
|
||||
String.serializer(),
|
||||
value.description,
|
||||
)
|
||||
encodeBooleanElement(
|
||||
descriptor,
|
||||
2,
|
||||
value.use,
|
||||
)
|
||||
encodeSerializableElement(
|
||||
descriptor,
|
||||
3,
|
||||
ListSerializer(String.serializer()),
|
||||
value.dependencies.map { it.name ?: it.toString() },
|
||||
)
|
||||
encodeNullableSerializableElement(
|
||||
descriptor,
|
||||
4,
|
||||
MapSerializer(String.serializer(), SetSerializer(String.serializer()).nullable),
|
||||
value.compatiblePackages?.associate { (packageName, versions) -> packageName to versions },
|
||||
)
|
||||
encodeSerializableElement(
|
||||
descriptor,
|
||||
5,
|
||||
SetSerializer(OptionSerializer),
|
||||
value.options.values.toSet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private object OptionSerializer : KSerializer<Option<*>> {
|
||||
override val descriptor = buildClassSerialDescriptor("Option") {
|
||||
element<String>("key")
|
||||
element<String?>("title")
|
||||
element<String?>("description")
|
||||
element<Boolean>("required")
|
||||
// Type does not matter for serialization. Using String.
|
||||
element<String>("type")
|
||||
element<String?>("default")
|
||||
// Map value type does not matter for serialization. Using String.
|
||||
element<Map<String, String?>?>("values")
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder) = throw NotImplementedError("Deserialization is unsupported")
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
override fun serialize(encoder: Encoder, value: Option<*>) {
|
||||
encoder.encodeStructure(descriptor) {
|
||||
encodeStringElement(descriptor, 0, value.key)
|
||||
encodeNullableSerializableElement(descriptor, 1, String.serializer(), value.title)
|
||||
encodeNullableSerializableElement(descriptor, 2, String.serializer(), value.description)
|
||||
encodeBooleanElement(descriptor, 3, value.required)
|
||||
encodeSerializableElement(descriptor, 4, String.serializer(), value.type.toString())
|
||||
encodeNullableSerializableElement(descriptor, 5, serializer(value.type), value.default)
|
||||
encodeNullableSerializableElement(descriptor, 6, MapSerializer(String.serializer(), serializer(value.type)), value.values)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val patchPrettySerializer by lazy { Json { prettyPrint = true } }
|
||||
private val patchSerializer by lazy { Json }
|
||||
|
||||
/**
|
||||
* Serialize this set of [Patch] to JSON and write it to the given [outputStream].
|
||||
*
|
||||
* @param outputStream The output stream to write the JSON to.
|
||||
* @param prettyPrint Whether to pretty print the JSON.
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
fun Set<Patch<*>>.serializeTo(
|
||||
outputStream: OutputStream,
|
||||
prettyPrint: Boolean = true,
|
||||
) = if (prettyPrint) {
|
||||
patchPrettySerializer
|
||||
} else {
|
||||
patchSerializer
|
||||
}.encodeToStream(SetSerializer(PatchSerializer()), this, outputStream)
|
||||
18
src/commonMain/kotlin/app/revanced/library/Utils.kt
Normal file
18
src/commonMain/kotlin/app/revanced/library/Utils.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
package app.revanced.library
|
||||
|
||||
/**
|
||||
* Utils for the library.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
object Utils {
|
||||
/**
|
||||
* True if the environment is Android.
|
||||
*/
|
||||
val isAndroidEnvironment =
|
||||
try {
|
||||
Class.forName("android.app.Application")
|
||||
true
|
||||
} catch (e: ClassNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package app.revanced.library.installation.command
|
||||
|
||||
import app.revanced.library.installation.installer.getDevice
|
||||
import se.vidstige.jadb.JadbDevice
|
||||
import se.vidstige.jadb.RemoteFile
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* [AdbShellCommandRunner] for running commands on a device remotely using ADB.
|
||||
*
|
||||
* @see ShellCommandRunner
|
||||
*/
|
||||
class AdbShellCommandRunner : ShellCommandRunner {
|
||||
private val device: JadbDevice
|
||||
|
||||
/**
|
||||
* Creates a [AdbShellCommandRunner] for the given device.
|
||||
*
|
||||
* @param device The device.
|
||||
*/
|
||||
internal constructor(device: JadbDevice) {
|
||||
this.device = device
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [AdbShellCommandRunner] for the device with the given serial.
|
||||
*
|
||||
* @param deviceSerial deviceSerial The device serial. If null, the first connected device will be used.
|
||||
*/
|
||||
internal constructor(deviceSerial: String?) {
|
||||
device = getDevice(deviceSerial, logger)
|
||||
}
|
||||
|
||||
override fun runCommand(command: String) = device.shellProcessBuilder(command).start().let { process ->
|
||||
object : RunResult {
|
||||
override val exitCode by lazy { process.waitFor() }
|
||||
override val output by lazy { process.inputStream.bufferedReader().readText() }
|
||||
override val error by lazy { process.errorStream.bufferedReader().readText() }
|
||||
|
||||
override fun waitFor() {
|
||||
process.waitFor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasRootPermission(): Boolean = invoke("whoami").exitCode == 0
|
||||
|
||||
override fun write(content: InputStream, targetFilePath: String) =
|
||||
device.push(content, System.currentTimeMillis(), 644, RemoteFile(targetFilePath))
|
||||
|
||||
/**
|
||||
* Moves the given [file] from the local to the target file path on the device.
|
||||
*
|
||||
* @param file The file to move.
|
||||
* @param targetFilePath The target file path.
|
||||
*/
|
||||
override fun move(file: File, targetFilePath: String) = device.push(file, RemoteFile(targetFilePath))
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package app.revanced.library.installation.command
|
||||
|
||||
/**
|
||||
* The result of a command execution.
|
||||
*/
|
||||
interface RunResult {
|
||||
/**
|
||||
* The exit code of the command.
|
||||
*/
|
||||
val exitCode: Int
|
||||
|
||||
/**
|
||||
* The output of the command.
|
||||
*/
|
||||
val output: String
|
||||
|
||||
/**
|
||||
* The error of the command.
|
||||
*/
|
||||
val error: String
|
||||
|
||||
/**
|
||||
* Waits for the command to finish.
|
||||
*/
|
||||
fun waitFor() {}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package app.revanced.library.installation.command
|
||||
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* [ShellCommandRunner] for running commands on a device.
|
||||
*/
|
||||
abstract class ShellCommandRunner internal constructor() {
|
||||
protected val logger: Logger = Logger.getLogger(this::class.java.name)
|
||||
|
||||
/**
|
||||
* Writes the given [content] to the file at the given [targetFilePath] path.
|
||||
*
|
||||
* @param content The content of the file.
|
||||
* @param targetFilePath The target file path.
|
||||
*/
|
||||
internal abstract fun write(
|
||||
content: InputStream,
|
||||
targetFilePath: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Moves the given [file] to the given [targetFilePath] path.
|
||||
*
|
||||
* @param file The file to move.
|
||||
* @param targetFilePath The target file path.
|
||||
*/
|
||||
internal abstract fun move(
|
||||
file: File,
|
||||
targetFilePath: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Runs the given [command] on the device as root.
|
||||
*
|
||||
* @param command The command to run.
|
||||
* @return The [RunResult].
|
||||
*/
|
||||
protected abstract fun runCommand(command: String): RunResult
|
||||
|
||||
/**
|
||||
* Checks if the device has root permission.
|
||||
*
|
||||
* @return True if the device has root permission, false otherwise.
|
||||
*/
|
||||
internal abstract fun hasRootPermission(): Boolean
|
||||
|
||||
/**
|
||||
* Runs a command on the device as root.
|
||||
*
|
||||
* @param command The command to run.
|
||||
* @return The [RunResult].
|
||||
*/
|
||||
internal operator fun invoke(
|
||||
command: String,
|
||||
) = runCommand("su -c \'$command\'")
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import app.revanced.library.installation.command.AdbShellCommandRunner
|
||||
import app.revanced.library.installation.command.ShellCommandRunner
|
||||
import app.revanced.library.installation.installer.Constants.GET_SDK_VERSION
|
||||
import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH
|
||||
import se.vidstige.jadb.JadbException
|
||||
import se.vidstige.jadb.managers.Package
|
||||
import se.vidstige.jadb.managers.PackageManager
|
||||
import se.vidstige.jadb.managers.PackageManager.UPDATE_OWNERSHIP
|
||||
|
||||
/**
|
||||
* [AdbInstaller] for installing and uninstalling [Apk] files using ADB.
|
||||
*
|
||||
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
||||
*
|
||||
* @see Installer
|
||||
*/
|
||||
class AdbInstaller(
|
||||
deviceSerial: String? = null,
|
||||
) : Installer<AdbInstallerResult, Installation>() {
|
||||
private val shellCommandRunner: ShellCommandRunner
|
||||
private val packageManager: PackageManager
|
||||
|
||||
init {
|
||||
val device = getDevice(deviceSerial, logger)
|
||||
shellCommandRunner = AdbShellCommandRunner(device)
|
||||
packageManager = PackageManager(device)
|
||||
|
||||
logger.fine("Connected to $deviceSerial")
|
||||
}
|
||||
|
||||
override suspend fun install(apk: Apk): AdbInstallerResult {
|
||||
return runPackageManager {
|
||||
val sdkVersion = shellCommandRunner(GET_SDK_VERSION).output.toInt()
|
||||
if (sdkVersion < 34) install(apk.file)
|
||||
else installWithOptions(apk.file, listOf(UPDATE_OWNERSHIP))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun uninstall(packageName: String): AdbInstallerResult {
|
||||
logger.info("Uninstalling $packageName")
|
||||
|
||||
return runPackageManager { uninstall(Package(packageName)) }
|
||||
}
|
||||
|
||||
override suspend fun getInstallation(packageName: String): Installation? = packageManager.packages.find {
|
||||
it.toString() == packageName
|
||||
}?.let { Installation(shellCommandRunner(INSTALLED_APK_PATH).output) }
|
||||
|
||||
private fun runPackageManager(block: PackageManager.() -> Unit) = try {
|
||||
packageManager.run(block)
|
||||
|
||||
AdbInstallerResult.Success
|
||||
} catch (e: JadbException) {
|
||||
AdbInstallerResult.Failure(e)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import app.revanced.library.installation.installer.Installer.Apk
|
||||
|
||||
/**
|
||||
* The result of installing or uninstalling an [Apk] via ADB using [AdbInstaller].
|
||||
*
|
||||
* @see AdbInstaller
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
interface AdbInstallerResult {
|
||||
/**
|
||||
* The result of installing an [Apk] successfully.
|
||||
*/
|
||||
object Success : AdbInstallerResult
|
||||
|
||||
/**
|
||||
* The result of installing an [Apk] unsuccessfully.
|
||||
*
|
||||
* @param exception The exception that caused the installation to fail.
|
||||
*/
|
||||
class Failure internal constructor(val exception: Exception) : AdbInstallerResult
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import app.revanced.library.installation.command.AdbShellCommandRunner
|
||||
import app.revanced.library.installation.installer.Installer.Apk
|
||||
import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException
|
||||
|
||||
/**
|
||||
* [AdbRootInstaller] for installing and uninstalling [Apk] files with using ADB root permissions by mounting.
|
||||
*
|
||||
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
||||
*
|
||||
* @throws NoRootPermissionException If the device does not have root permission.
|
||||
*
|
||||
* @see RootInstaller
|
||||
* @see AdbShellCommandRunner
|
||||
*/
|
||||
class AdbRootInstaller(
|
||||
deviceSerial: String? = null,
|
||||
) : RootInstaller({ AdbShellCommandRunner(deviceSerial) }) {
|
||||
init {
|
||||
logger.fine("Connected to $deviceSerial")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
internal object Constants {
|
||||
const val PLACEHOLDER = "PLACEHOLDER"
|
||||
|
||||
const val SELINUX_CONTEXT = "u:object_r:apk_data_file:s0"
|
||||
const val TMP_FILE_PATH = "/data/local/tmp/revanced.tmp"
|
||||
const val MOUNT_PATH = "/data/adb/revanced/"
|
||||
const val MOUNTED_APK_PATH = "$MOUNT_PATH$PLACEHOLDER.apk"
|
||||
const val MOUNT_SCRIPT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
|
||||
|
||||
const val EXISTS = "[[ -f $PLACEHOLDER ]] || exit 1"
|
||||
const val MOUNT_GREP = "grep -F $PLACEHOLDER /proc/mounts"
|
||||
const val DELETE = "rm -rf $PLACEHOLDER"
|
||||
const val CREATE_DIR = "mkdir -p"
|
||||
const val RESTART = "am start -S $PLACEHOLDER"
|
||||
const val KILL = "am force-stop $PLACEHOLDER"
|
||||
const val INSTALLED_APK_PATH = "pm path $PLACEHOLDER"
|
||||
const val CREATE_INSTALLATION_PATH = "$CREATE_DIR $MOUNT_PATH"
|
||||
const val GET_SDK_VERSION = "getprop ro.build.version.sdk"
|
||||
|
||||
const val MOUNT_APK =
|
||||
"base_path=\"$MOUNTED_APK_PATH\" && " +
|
||||
"mv $TMP_FILE_PATH \$base_path && " +
|
||||
"chmod 644 \$base_path && " +
|
||||
"chown system:system \$base_path && " +
|
||||
"chcon $SELINUX_CONTEXT \$base_path"
|
||||
|
||||
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"
|
||||
|
||||
const val INSTALL_MOUNT_SCRIPT = "mv $TMP_FILE_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH"
|
||||
|
||||
val MOUNT_SCRIPT =
|
||||
"""
|
||||
#!/system/bin/sh
|
||||
until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done
|
||||
until [ -d "/sdcard/Android" ]; do sleep 1; done
|
||||
|
||||
stock_path=$( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
|
||||
|
||||
# Make sure the app is installed.
|
||||
if [ -z "${'$'}stock_path" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Unmount any existing installations to prevent multiple unnecessary mounts.
|
||||
$UMOUNT
|
||||
|
||||
base_path="$MOUNTED_APK_PATH"
|
||||
|
||||
chcon $SELINUX_CONTEXT ${'$'}base_path
|
||||
|
||||
# Use Magisk mirror, if possible.
|
||||
if command -v magisk &> /dev/null; then
|
||||
MIRROR="${'$'}(magisk --path)/.magisk/mirror"
|
||||
fi
|
||||
|
||||
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
|
||||
|
||||
# Kill the app to force it to restart the mounted APK in case it's currently running.
|
||||
$KILL
|
||||
""".trimIndent()
|
||||
|
||||
/**
|
||||
* Replaces the [PLACEHOLDER] with the given [replacement].
|
||||
*
|
||||
* @param replacement The replacement to use.
|
||||
* @return The replaced string.
|
||||
*/
|
||||
operator fun String.invoke(replacement: String) = replace(PLACEHOLDER, replacement)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
/**
|
||||
* [Installation] of an apk file.
|
||||
*
|
||||
* @param apkFilePath The apk file path.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
open class Installation internal constructor(
|
||||
val apkFilePath: String,
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import app.revanced.library.installation.installer.Installer.Apk
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* [Installer] for installing and uninstalling [Apk] files.
|
||||
*
|
||||
* @param TInstallerResult The type of the result of the installation.
|
||||
* @param TInstallation The type of the installation.
|
||||
*/
|
||||
abstract class Installer<TInstallerResult, TInstallation : Installation> internal constructor() {
|
||||
/**
|
||||
* The [Logger].
|
||||
*/
|
||||
protected val logger: Logger = Logger.getLogger(this::class.java.name)
|
||||
|
||||
/**
|
||||
* Installs the [Apk] file.
|
||||
*
|
||||
* @param apk The [Apk] file.
|
||||
*
|
||||
* @return The result of the installation.
|
||||
*/
|
||||
abstract suspend fun install(apk: Apk): TInstallerResult
|
||||
|
||||
/**
|
||||
* Uninstalls the package.
|
||||
*
|
||||
* @param packageName The package name.
|
||||
*
|
||||
* @return The result of the uninstallation.
|
||||
*/
|
||||
abstract suspend fun uninstall(packageName: String): TInstallerResult
|
||||
|
||||
/**
|
||||
* Gets the current installation or null if not installed.
|
||||
*
|
||||
* @param packageName The package name.
|
||||
*
|
||||
* @return The installation.
|
||||
*/
|
||||
abstract suspend fun getInstallation(packageName: String): TInstallation?
|
||||
|
||||
/**
|
||||
* Apk file for [Installer].
|
||||
*
|
||||
* @param file The [Apk] file.
|
||||
* @param packageName The package name of the [Apk] file.
|
||||
*/
|
||||
class Apk(val file: File, val packageName: String? = null)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
/**
|
||||
* [RootInstallation] of the apk file that is mounted to [installedApkFilePath] with root permissions.
|
||||
*
|
||||
* @param installedApkFilePath The installed apk file path or null if the apk is not installed.
|
||||
* @param apkFilePath The mounting apk file path.
|
||||
* @param mounted Whether the apk is mounted to [installedApkFilePath].
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class RootInstallation internal constructor(
|
||||
val installedApkFilePath: String?,
|
||||
apkFilePath: String,
|
||||
val mounted: Boolean,
|
||||
) : Installation(apkFilePath)
|
||||
@@ -0,0 +1,134 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import app.revanced.library.installation.command.ShellCommandRunner
|
||||
import app.revanced.library.installation.installer.Constants.CREATE_INSTALLATION_PATH
|
||||
import app.revanced.library.installation.installer.Constants.DELETE
|
||||
import app.revanced.library.installation.installer.Constants.EXISTS
|
||||
import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH
|
||||
import app.revanced.library.installation.installer.Constants.INSTALL_MOUNT_SCRIPT
|
||||
import app.revanced.library.installation.installer.Constants.KILL
|
||||
import app.revanced.library.installation.installer.Constants.MOUNTED_APK_PATH
|
||||
import app.revanced.library.installation.installer.Constants.MOUNT_APK
|
||||
import app.revanced.library.installation.installer.Constants.MOUNT_GREP
|
||||
import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT
|
||||
import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT_PATH
|
||||
import app.revanced.library.installation.installer.Constants.RESTART
|
||||
import app.revanced.library.installation.installer.Constants.TMP_FILE_PATH
|
||||
import app.revanced.library.installation.installer.Constants.UMOUNT
|
||||
import app.revanced.library.installation.installer.Constants.invoke
|
||||
import app.revanced.library.installation.installer.Installer.Apk
|
||||
import app.revanced.library.installation.installer.RootInstaller.NoRootPermissionException
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* [RootInstaller] for installing and uninstalling [Apk] files using root permissions by mounting.
|
||||
*
|
||||
* @param shellCommandRunnerSupplier A supplier for the [ShellCommandRunner] to use.
|
||||
*
|
||||
* @throws NoRootPermissionException If the device does not have root permission.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
abstract class RootInstaller internal constructor(
|
||||
shellCommandRunnerSupplier: (RootInstaller) -> ShellCommandRunner,
|
||||
) : Installer<RootInstallerResult, RootInstallation>() {
|
||||
|
||||
/**
|
||||
* The command runner used to run commands on the device.
|
||||
*/
|
||||
@Suppress("LeakingThis")
|
||||
protected val shellCommandRunner = shellCommandRunnerSupplier(this)
|
||||
|
||||
init {
|
||||
if (!shellCommandRunner.hasRootPermission()) throw NoRootPermissionException()
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the given [apk] by mounting.
|
||||
*
|
||||
* @param apk The [Apk] to install.
|
||||
*
|
||||
* @throws PackageNameRequiredException If the [Apk] does not have a package name.
|
||||
*/
|
||||
override suspend fun install(apk: Apk): RootInstallerResult {
|
||||
logger.info("Installing ${apk.packageName} by mounting")
|
||||
|
||||
val packageName = apk.packageName?.also { it.assertInstalled() } ?: throw PackageNameRequiredException()
|
||||
|
||||
// Setup files.
|
||||
apk.file.move(TMP_FILE_PATH)
|
||||
CREATE_INSTALLATION_PATH().waitFor()
|
||||
MOUNT_APK(packageName)().waitFor()
|
||||
|
||||
// Install and run.
|
||||
TMP_FILE_PATH.write(MOUNT_SCRIPT(packageName))
|
||||
INSTALL_MOUNT_SCRIPT(packageName)().waitFor()
|
||||
MOUNT_SCRIPT_PATH(packageName)().waitFor()
|
||||
RESTART(packageName)()
|
||||
|
||||
DELETE(TMP_FILE_PATH)()
|
||||
|
||||
return RootInstallerResult.SUCCESS
|
||||
}
|
||||
|
||||
override suspend fun uninstall(packageName: String): RootInstallerResult {
|
||||
logger.info("Uninstalling $packageName by unmounting")
|
||||
|
||||
UMOUNT(packageName)()
|
||||
|
||||
DELETE(MOUNTED_APK_PATH)(packageName)()
|
||||
DELETE(MOUNT_SCRIPT_PATH)(packageName)()
|
||||
DELETE(TMP_FILE_PATH)() // Remove residual.
|
||||
|
||||
KILL(packageName)()
|
||||
|
||||
return RootInstallerResult.SUCCESS
|
||||
}
|
||||
|
||||
override suspend fun getInstallation(packageName: String): RootInstallation? {
|
||||
val patchedApkPath = MOUNTED_APK_PATH(packageName)
|
||||
|
||||
val patchedApkExists = EXISTS(patchedApkPath)().exitCode == 0
|
||||
if (patchedApkExists) return null
|
||||
|
||||
return RootInstallation(
|
||||
INSTALLED_APK_PATH(packageName)().output.ifEmpty { null },
|
||||
patchedApkPath,
|
||||
MOUNT_GREP(patchedApkPath)().exitCode == 0,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a command on the device.
|
||||
*/
|
||||
protected operator fun String.invoke() = shellCommandRunner(this)
|
||||
|
||||
/**
|
||||
* Moves the given file to the given [targetFilePath].
|
||||
*
|
||||
* @param targetFilePath The target file path.
|
||||
*/
|
||||
protected fun File.move(targetFilePath: String) = shellCommandRunner.move(this, targetFilePath)
|
||||
|
||||
/**
|
||||
* Writes the given [content] to the file.
|
||||
*
|
||||
* @param content The content of the file.
|
||||
*/
|
||||
protected fun String.write(content: String) = shellCommandRunner.write(content.byteInputStream(), this)
|
||||
|
||||
/**
|
||||
* Asserts that the package is installed.
|
||||
*
|
||||
* @throws FailedToFindInstalledPackageException If the package is not installed.
|
||||
*/
|
||||
private fun String.assertInstalled() {
|
||||
if (INSTALLED_APK_PATH(this)().output.isEmpty()) {
|
||||
throw FailedToFindInstalledPackageException(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal class FailedToFindInstalledPackageException internal constructor(packageName: String) : Exception("Failed to find installed package \"$packageName\" because no activity was found")
|
||||
|
||||
internal class PackageNameRequiredException internal constructor() : Exception("Package name is required")
|
||||
internal class NoRootPermissionException internal constructor() : Exception("No root permission")
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import app.revanced.library.installation.installer.Installer.Apk
|
||||
|
||||
/**
|
||||
* The result of installing or uninstalling an [Apk] with root permissions using [RootInstaller].
|
||||
*
|
||||
* @see RootInstaller
|
||||
*/
|
||||
enum class RootInstallerResult {
|
||||
/**
|
||||
* The result of installing an [Apk] successfully.
|
||||
*/
|
||||
SUCCESS,
|
||||
|
||||
/**
|
||||
* The result of installing an [Apk] unsuccessfully.
|
||||
*/
|
||||
FAILURE,
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package app.revanced.library.installation.installer
|
||||
|
||||
import se.vidstige.jadb.JadbConnection
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* Gets the device with the given serial.
|
||||
*
|
||||
* @param deviceSerial The device serial. If null, the first connected device will be used.
|
||||
* @param logger The logger.
|
||||
* @return The device.
|
||||
* @throws DeviceNotFoundException If no device with the given serial is found.
|
||||
*/
|
||||
internal fun getDevice(
|
||||
deviceSerial: String? = null,
|
||||
logger: Logger,
|
||||
) = with(JadbConnection().devices) {
|
||||
if (isEmpty()) throw DeviceNotFoundException()
|
||||
|
||||
deviceSerial?.let {
|
||||
firstOrNull { it.serial == deviceSerial } ?: throw DeviceNotFoundException(
|
||||
deviceSerial,
|
||||
)
|
||||
} ?: first().also {
|
||||
logger.warning("No device serial supplied. Using device with serial ${it.serial}")
|
||||
}
|
||||
}!!
|
||||
|
||||
class DeviceNotFoundException internal constructor(deviceSerial: String? = null) :
|
||||
Exception(
|
||||
deviceSerial?.let {
|
||||
"The device with the ADB device serial \"$deviceSerial\" can not be found"
|
||||
} ?: "No ADB device found",
|
||||
)
|
||||
@@ -5,8 +5,19 @@ import java.util.logging.Level
|
||||
import java.util.logging.LogRecord
|
||||
import java.util.logging.SimpleFormatter
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
object Logger {
|
||||
/**
|
||||
* Rules for allowed loggers.
|
||||
*/
|
||||
private val allowedLoggersRules =
|
||||
arrayOf<String.() -> Boolean>(
|
||||
// ReVanced loggers.
|
||||
{ startsWith("app.revanced") },
|
||||
// Logs warnings when compiling resources (Logger in class brut.util.OS).
|
||||
{ this == "" },
|
||||
)
|
||||
|
||||
private val rootLogger = java.util.logging.Logger.getLogger("")
|
||||
|
||||
/**
|
||||
@@ -40,13 +51,14 @@ object Logger {
|
||||
fun addHandler(
|
||||
publishHandler: (log: String, level: Level, loggerName: String?) -> Unit,
|
||||
flushHandler: () -> Unit,
|
||||
closeHandler: () -> Unit
|
||||
closeHandler: () -> Unit,
|
||||
) = object : Handler() {
|
||||
override fun publish(record: LogRecord) = publishHandler(
|
||||
formatter.format(record),
|
||||
record.level,
|
||||
record.loggerName
|
||||
)
|
||||
override fun publish(record: LogRecord) =
|
||||
publishHandler(
|
||||
formatter.format(record),
|
||||
record.level,
|
||||
record.loggerName,
|
||||
)
|
||||
|
||||
override fun flush() = flushHandler()
|
||||
|
||||
@@ -64,13 +76,16 @@ object Logger {
|
||||
removeAllHandlers()
|
||||
|
||||
val publishHandler = handler@{ log: String, level: Level, loggerName: String? ->
|
||||
if (loggerName?.startsWith("app.revanced") != true) return@handler
|
||||
loggerName?.let { name ->
|
||||
if (allowedLoggersRules.none { it(name) }) return@handler
|
||||
}
|
||||
|
||||
log.toByteArray().let {
|
||||
if (level.intValue() > Level.WARNING.intValue())
|
||||
if (level.intValue() > Level.WARNING.intValue()) {
|
||||
System.err.write(it)
|
||||
else
|
||||
} else {
|
||||
System.out.write(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,4 +96,4 @@ object Logger {
|
||||
|
||||
addHandler(publishHandler, flushHandler, flushHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.patcher.patch.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class MostCommonCompatibleVersionsTest {
|
||||
private val patches =
|
||||
arrayOf(
|
||||
newPatch("some.package", setOf("a")) { stringOption("string", "value") },
|
||||
newPatch("some.package", setOf("a", "b"), use = false),
|
||||
newPatch("some.package", setOf("a", "b", "c"), use = false),
|
||||
newPatch("some.other.package", setOf("b"), use = false),
|
||||
newPatch("some.other.package", setOf("b", "c")) { booleanOption("bool", true) },
|
||||
newPatch("some.other.package", setOf("b", "c", "d")),
|
||||
newPatch("some.other.other.package") { intsOption("intArray", listOf(1, 2, 3)) },
|
||||
newPatch("some.other.other.package", setOf("a")),
|
||||
newPatch("some.other.other.package", setOf("b")),
|
||||
newPatch("some.other.other.other.package", use = false),
|
||||
newPatch("some.other.other.other.package", use = false),
|
||||
).toSet()
|
||||
|
||||
@Test
|
||||
fun `empty because package is incompatible with any version`() {
|
||||
assertEqualsVersions(
|
||||
expected = emptyMap(),
|
||||
patches = setOf(newPatch("some.package", emptySet(), use = true)),
|
||||
compatiblePackageNames = setOf("some.package"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty list of versions because package is unconstrained to any version`() {
|
||||
assertEqualsVersions(
|
||||
expected = mapOf("some.package" to linkedMapOf()),
|
||||
patches = setOf(newPatch("some.package")),
|
||||
compatiblePackageNames = setOf("some.package"),
|
||||
countUnusedPatches = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty because no known package was supplied`() {
|
||||
assertEqualsVersions(
|
||||
expected = emptyMap(),
|
||||
patches,
|
||||
compatiblePackageNames = setOf("unknown.package"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `common versions correctly ordered for each package`() {
|
||||
fun assertEqualsExpected(compatiblePackageNames: Set<String>?) =
|
||||
assertEqualsVersions(
|
||||
expected =
|
||||
mapOf(
|
||||
"some.package" to linkedMapOf("a" to 3, "b" to 2, "c" to 1),
|
||||
"some.other.package" to linkedMapOf("b" to 3, "c" to 2, "d" to 1),
|
||||
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
|
||||
"some.other.other.other.package" to linkedMapOf(),
|
||||
),
|
||||
patches,
|
||||
compatiblePackageNames,
|
||||
countUnusedPatches = true,
|
||||
)
|
||||
|
||||
assertEqualsExpected(
|
||||
compatiblePackageNames =
|
||||
setOf(
|
||||
"some.package",
|
||||
"some.other.package",
|
||||
"some.other.other.package",
|
||||
"some.other.other.other.package",
|
||||
),
|
||||
)
|
||||
|
||||
assertEqualsExpected(
|
||||
compatiblePackageNames = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `common versions correctly ordered for each package without counting unused patches`() {
|
||||
assertEqualsVersions(
|
||||
expected =
|
||||
mapOf(
|
||||
"some.package" to linkedMapOf("a" to 1),
|
||||
"some.other.package" to linkedMapOf("b" to 2, "c" to 2, "d" to 1),
|
||||
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
|
||||
),
|
||||
patches,
|
||||
compatiblePackageNames =
|
||||
setOf(
|
||||
"some.package",
|
||||
"some.other.package",
|
||||
"some.other.other.package",
|
||||
"some.other.other.other.package",
|
||||
),
|
||||
countUnusedPatches = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return 'a' because it is the most common version`() {
|
||||
val patches =
|
||||
arrayOf("a", "a", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d")
|
||||
.map { version -> newPatch("some.package", setOf(version)) }
|
||||
.toSet()
|
||||
|
||||
assertEqualsVersion("a", patches, "some.package")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return null because no patches were supplied`() {
|
||||
assertEqualsVersion(null, emptySet<BytecodePatch>(), "some.package")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return null because no patch is compatible with the supplied package name`() {
|
||||
val patches = setOf(newPatch("some.package", setOf("a")))
|
||||
|
||||
assertEqualsVersion(null, patches, "other.package")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return null because no compatible package is constrained to a version`() {
|
||||
val patches =
|
||||
setOf(
|
||||
newPatch("other.package"),
|
||||
newPatch("other.package"),
|
||||
)
|
||||
|
||||
assertEqualsVersion(null, patches, "other.package")
|
||||
}
|
||||
|
||||
private fun assertEqualsVersions(
|
||||
expected: PackageNameMap,
|
||||
patches: Set<Patch<*>>,
|
||||
compatiblePackageNames: Set<String>?,
|
||||
countUnusedPatches: Boolean = false,
|
||||
) = assertEquals(
|
||||
expected,
|
||||
patches.mostCommonCompatibleVersions(compatiblePackageNames, countUnusedPatches),
|
||||
)
|
||||
|
||||
private fun assertEqualsVersion(
|
||||
expected: String?,
|
||||
patches: Set<Patch<*>>,
|
||||
compatiblePackageName: String,
|
||||
) {
|
||||
assertEquals(
|
||||
expected,
|
||||
patches.mostCommonCompatibleVersions(setOf(compatiblePackageName))
|
||||
.entries.firstOrNull()?.value?.keys?.firstOrNull(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun newPatch(
|
||||
packageName: String,
|
||||
versions: Set<String>? = null,
|
||||
use: Boolean = true,
|
||||
options: PatchBuilder<*>.() -> Unit = {},
|
||||
) = bytecodePatch(
|
||||
name = "test",
|
||||
use = use,
|
||||
) {
|
||||
if (versions == null) {
|
||||
compatibleWith(packageName)
|
||||
} else {
|
||||
compatibleWith(
|
||||
if (versions.isEmpty()) {
|
||||
packageName()
|
||||
} else {
|
||||
packageName(*versions.toTypedArray())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
options()
|
||||
}
|
||||
}
|
||||
36
src/commonTest/kotlin/app/revanced/library/OptionsTest.kt
Normal file
36
src/commonTest/kotlin/app/revanced/library/OptionsTest.kt
Normal file
@@ -0,0 +1,36 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.patcher.patch.booleanOption
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class OptionsTest {
|
||||
@Test
|
||||
fun `serializes and deserializes`() {
|
||||
val options = mapOf(
|
||||
"Test patch" to mapOf("key1" to "test", "key2" to false),
|
||||
)
|
||||
|
||||
val patch = bytecodePatch("Test patch") {
|
||||
stringOption("key1")
|
||||
booleanOption("key2", true)
|
||||
}
|
||||
val duplicatePatch = bytecodePatch("Test patch") {
|
||||
stringOption("key1")
|
||||
}
|
||||
val unnamedPatch = bytecodePatch {
|
||||
booleanOption("key1")
|
||||
}
|
||||
|
||||
setOf(patch, duplicatePatch, unnamedPatch).setOptions(options)
|
||||
|
||||
assert(patch.options["key1"].value == "test")
|
||||
assert(patch.options["key2"].value == false)
|
||||
|
||||
assertEquals(patch.options["key1"].value, duplicatePatch.options["key1"].value)
|
||||
|
||||
assert(unnamedPatch.options["key1"].value == null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.patcher.patch.booleanOption
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.floatsOption
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import kotlinx.serialization.json.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertIs
|
||||
|
||||
class SerializationTest {
|
||||
private val testPatch = bytecodePatch("Test patch") {
|
||||
compatibleWith("com.example.package"("1.0.0"))
|
||||
compatibleWith("com.example.package2")
|
||||
|
||||
dependsOn(bytecodePatch(), bytecodePatch())
|
||||
|
||||
stringOption("key1", null, null, "title1", "description1")
|
||||
booleanOption("key2", true, null, "title2", "description2")
|
||||
floatsOption("key3", listOf(1.0f), mapOf("list" to listOf(1f)), "title3", "description3")
|
||||
}
|
||||
|
||||
private var patches = setOf(testPatch)
|
||||
|
||||
@Test
|
||||
fun `serializes and deserializes`() {
|
||||
val serializedJson = ByteArrayOutputStream().apply { patches.serializeTo(this) }.toString()
|
||||
val deserializedJson = Json.parseToJsonElement(serializedJson)
|
||||
|
||||
// Test patch serialization.
|
||||
|
||||
assertIs<JsonArray>(deserializedJson)
|
||||
|
||||
val deserializedPatch = deserializedJson[0].jsonObject
|
||||
|
||||
assert(deserializedPatch["name"]!!.jsonPrimitive.content == "Test patch")
|
||||
|
||||
assert(deserializedPatch["compatiblePackages"]!!.jsonObject.size == 2) {
|
||||
"The patch should be compatible with two packages."
|
||||
}
|
||||
|
||||
assert(deserializedPatch["dependencies"]!!.jsonArray.size == 2) {
|
||||
"Even though the dependencies are named the same, they are different objects."
|
||||
}
|
||||
|
||||
// Test option serialization.
|
||||
|
||||
val options = deserializedPatch["options"]!!.jsonArray
|
||||
|
||||
assert(options.size == 3) { "The patch should have three options." }
|
||||
|
||||
assert(options[0].jsonObject["title"]!!.jsonPrimitive.content == "title1")
|
||||
assert(options[0].jsonObject["default"]!!.jsonPrimitive.contentOrNull == null)
|
||||
assert(options[1].jsonObject["default"]!!.jsonPrimitive.boolean)
|
||||
assert(options[2].jsonObject["values"]!!.jsonObject["list"]!!.jsonArray[0].jsonPrimitive.float == 1f)
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.library.ApkSigner.signApk
|
||||
import app.revanced.library.zip.ZipFile
|
||||
import app.revanced.library.zip.structures.ZipEntry
|
||||
import app.revanced.patcher.PatcherResult
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
import kotlin.io.path.deleteIfExists
|
||||
|
||||
/**
|
||||
* Utility functions for working with apks.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
object ApkUtils {
|
||||
private val logger = Logger.getLogger(ApkUtils::class.java.name)
|
||||
|
||||
/**
|
||||
* Creates a new apk from [apkFile] and [patchedEntriesSource] and writes it to [outputFile].
|
||||
*
|
||||
* @param apkFile The apk to copy entries from.
|
||||
* @param outputFile The apk to write the new entries to.
|
||||
* @param patchedEntriesSource The result of the patcher to add the patched dex files and resources.
|
||||
*/
|
||||
fun copyAligned(apkFile: File, outputFile: File, patchedEntriesSource: PatcherResult) {
|
||||
logger.info("Aligning ${apkFile.name}")
|
||||
|
||||
outputFile.toPath().deleteIfExists()
|
||||
|
||||
ZipFile(outputFile).use { file ->
|
||||
patchedEntriesSource.dexFiles.forEach {
|
||||
file.addEntryCompressData(
|
||||
ZipEntry(it.name), it.stream.readBytes()
|
||||
)
|
||||
}
|
||||
|
||||
patchedEntriesSource.resourceFile?.let {
|
||||
file.copyEntriesFromFileAligned(
|
||||
ZipFile(it), ZipFile.apkZipEntryAlignment
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Do not compress result.doNotCompress
|
||||
|
||||
// TODO: Fix copying resources that are not needed anymore.
|
||||
file.copyEntriesFromFileAligned(
|
||||
ZipFile(apkFile), ZipFile.apkZipEntryAlignment
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the [apk] file and writes it to [output].
|
||||
*
|
||||
* @param apk The apk to sign.
|
||||
* @param output The apk to write the signed apk to.
|
||||
* @param signingOptions The options to use for signing.
|
||||
*/
|
||||
fun sign(
|
||||
apk: File,
|
||||
output: File,
|
||||
signingOptions: SigningOptions,
|
||||
) {
|
||||
// Get the keystore from the file or create a new one.
|
||||
val keyStore = if (signingOptions.keyStore.exists()) {
|
||||
ApkSigner.readKeyStore(signingOptions.keyStore.inputStream(), signingOptions.keyStorePassword)
|
||||
} else {
|
||||
val entry = ApkSigner.KeyStoreEntry(signingOptions.alias, signingOptions.password)
|
||||
|
||||
// Create a new keystore with a new keypair and saves it.
|
||||
ApkSigner.newKeyStore(listOf(entry)).also { keyStore ->
|
||||
keyStore.store(
|
||||
signingOptions.keyStore.outputStream(),
|
||||
signingOptions.keyStorePassword?.toCharArray()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ApkSigner.newApkSignerBuilder(
|
||||
keyStore,
|
||||
signingOptions.alias,
|
||||
signingOptions.password,
|
||||
signingOptions.signer,
|
||||
signingOptions.signer
|
||||
).signApk(apk, output)
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for signing an apk.
|
||||
*
|
||||
* @param keyStore The keystore to use for signing.
|
||||
* @param keyStorePassword The password for the keystore.
|
||||
* @param alias The alias of the key store entry to use for signing.
|
||||
* @param password The password for recovering the signing key.
|
||||
* @param signer The name of the signer.
|
||||
*/
|
||||
class SigningOptions(
|
||||
val keyStore: File,
|
||||
val keyStorePassword: String?,
|
||||
val alias: String = "ReVanced Key",
|
||||
val password: String = "",
|
||||
val signer: String = "ReVanced",
|
||||
)
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package app.revanced.library
|
||||
|
||||
|
||||
import app.revanced.library.Options.Patch.Option
|
||||
import app.revanced.patcher.PatchClass
|
||||
import app.revanced.patcher.PatchSet
|
||||
import app.revanced.patcher.patch.options.PatchOptionException
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
private typealias PatchList = List<PatchClass>
|
||||
|
||||
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.
|
||||
*/
|
||||
fun serialize(patches: PatchSet, prettyPrint: Boolean = false): String = patches
|
||||
.filter { it.options.any() }
|
||||
.map { patch ->
|
||||
Patch(
|
||||
patch.name!!,
|
||||
patch.options.values.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 [Patch]s.
|
||||
* @see Patch
|
||||
* @see PatchList
|
||||
*/
|
||||
fun deserialize(json: String): Array<Patch> = mapper.readValue(json, Array<Patch>::class.java)
|
||||
|
||||
/**
|
||||
* Sets the options for the patches in the list.
|
||||
*
|
||||
* @param json The JSON string containing the options.
|
||||
*/
|
||||
fun PatchSet.setOptions(json: String) {
|
||||
filter { it.options.any() }.let { patches ->
|
||||
if (patches.isEmpty()) return
|
||||
|
||||
val jsonPatches = deserialize(json).associate {
|
||||
it.patchName to it.options.associate { option -> option.key to option.value }
|
||||
}
|
||||
|
||||
patches.forEach { patch ->
|
||||
jsonPatches[patch.name]?.let { jsonPatchOptions ->
|
||||
jsonPatchOptions.forEach { (option, value) ->
|
||||
try {
|
||||
patch.options[option] = value
|
||||
} catch (e: PatchOptionException) {
|
||||
logger.severe(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 PatchSet.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.
|
||||
*/
|
||||
class Patch internal constructor(
|
||||
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.
|
||||
*/
|
||||
class Option internal constructor(val key: String, val value: Any?)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.patcher.PatchSet
|
||||
|
||||
/**
|
||||
* Utility functions for working with patches.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
object PatchUtils {
|
||||
/**
|
||||
* Get the version that is most common for [packageName] in the supplied set of [patches].
|
||||
*
|
||||
* @param patches The set of patches to check.
|
||||
* @param packageName The name of the compatible package.
|
||||
* @return The most common version of.
|
||||
*/
|
||||
fun getMostCommonCompatibleVersion(patches: PatchSet, packageName: String) = patches
|
||||
.mapNotNull {
|
||||
// Map all patches to their compatible packages with version constraints.
|
||||
it.compatiblePackages?.firstOrNull { compatiblePackage ->
|
||||
compatiblePackage.name == packageName && compatiblePackage.versions?.isNotEmpty() == true
|
||||
}
|
||||
}
|
||||
.flatMap { it.versions!! }
|
||||
.groupingBy { it }
|
||||
.eachCount()
|
||||
.maxByOrNull { it.value }?.key
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package app.revanced.library.adb
|
||||
|
||||
import app.revanced.library.adb.AdbManager.Apk
|
||||
import app.revanced.library.adb.Constants.CREATE_DIR
|
||||
import app.revanced.library.adb.Constants.DELETE
|
||||
import app.revanced.library.adb.Constants.INSTALLATION_PATH
|
||||
import app.revanced.library.adb.Constants.INSTALL_MOUNT
|
||||
import app.revanced.library.adb.Constants.INSTALL_PATCHED_APK
|
||||
import app.revanced.library.adb.Constants.MOUNT_PATH
|
||||
import app.revanced.library.adb.Constants.MOUNT_SCRIPT
|
||||
import app.revanced.library.adb.Constants.PATCHED_APK_PATH
|
||||
import app.revanced.library.adb.Constants.PLACEHOLDER
|
||||
import app.revanced.library.adb.Constants.RESOLVE_ACTIVITY
|
||||
import app.revanced.library.adb.Constants.RESTART
|
||||
import app.revanced.library.adb.Constants.TMP_PATH
|
||||
import app.revanced.library.adb.Constants.UMOUNT
|
||||
import se.vidstige.jadb.JadbConnection
|
||||
import se.vidstige.jadb.JadbDevice
|
||||
import se.vidstige.jadb.managers.Package
|
||||
import se.vidstige.jadb.managers.PackageManager
|
||||
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.
|
||||
*/
|
||||
sealed class AdbManager private constructor(deviceSerial: String? = null) {
|
||||
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")
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets an [AdbManager] for the supplied device serial.
|
||||
*
|
||||
* @param deviceSerial The device serial.
|
||||
* @param root Whether to use root or not.
|
||||
* @return The [AdbManager].
|
||||
* @throws DeviceNotFoundException If the device can not be found.
|
||||
*/
|
||||
fun getAdbManager(deviceSerial: String, root: Boolean = false): AdbManager =
|
||||
if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial)
|
||||
}
|
||||
|
||||
class RootAdbManager internal constructor(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 packageName = apk.packageName ?: throw PackageNameRequiredException()
|
||||
|
||||
device.run(RESOLVE_ACTIVITY, packageName).inputStream.bufferedReader().readLine().let { line ->
|
||||
if (line != "No activity found") return@let
|
||||
throw throw FailedToFindInstalledPackageException(packageName)
|
||||
}
|
||||
|
||||
device.push(apk.file, TMP_PATH)
|
||||
|
||||
device.run("$CREATE_DIR $INSTALLATION_PATH")
|
||||
device.run(INSTALL_PATCHED_APK, packageName)
|
||||
|
||||
device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement(packageName))
|
||||
|
||||
device.run(INSTALL_MOUNT, packageName)
|
||||
device.run(UMOUNT, packageName) // Sanity check.
|
||||
device.run(MOUNT_PATH, packageName)
|
||||
device.run(RESTART, packageName)
|
||||
device.run(DELETE, TMP_PATH)
|
||||
|
||||
super.install(apk)
|
||||
}
|
||||
|
||||
override fun uninstall(packageName: String) {
|
||||
logger.info("Uninstalling $packageName by unmounting")
|
||||
|
||||
device.run(UMOUNT, packageName)
|
||||
device.run(DELETE.applyReplacement(PATCHED_APK_PATH), packageName)
|
||||
device.run(DELETE, MOUNT_PATH)
|
||||
device.run(DELETE, TMP_PATH)
|
||||
|
||||
super.uninstall(packageName)
|
||||
}
|
||||
|
||||
companion object Utils {
|
||||
private fun JadbDevice.run(command: String, with: String) = run(command.applyReplacement(with))
|
||||
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
|
||||
}
|
||||
}
|
||||
|
||||
class UserAdbManager internal constructor(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.
|
||||
*/
|
||||
class Apk(val file: File, val packageName: String? = null)
|
||||
|
||||
class DeviceNotFoundException internal constructor(deviceSerial: String?) :
|
||||
Exception(deviceSerial?.let {
|
||||
"The device with the ADB device serial \"$deviceSerial\" can not be found"
|
||||
} ?: "No ADB device found")
|
||||
|
||||
class FailedToFindInstalledPackageException internal constructor(packageName: String) :
|
||||
Exception("Failed to find installed package \"$packageName\" because no activity was found")
|
||||
|
||||
class PackageNameRequiredException internal constructor() :
|
||||
Exception("Package name is required")
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package app.revanced.library.adb
|
||||
|
||||
import se.vidstige.jadb.JadbDevice
|
||||
import se.vidstige.jadb.RemoteFile
|
||||
import se.vidstige.jadb.ShellProcess
|
||||
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): ShellProcess {
|
||||
return this.buildCommand(command, su).start()!!
|
||||
}
|
||||
|
||||
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,41 +0,0 @@
|
||||
package app.revanced.library.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 RESOLVE_ACTIVITY = "pm resolve-activity --brief $PLACEHOLDER"
|
||||
internal const val RESTART = "$RESOLVE_ACTIVITY | 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,33 +0,0 @@
|
||||
package app.revanced.library.zip
|
||||
|
||||
import java.io.DataInput
|
||||
import java.io.DataOutput
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
internal 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()
|
||||
|
||||
internal fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
|
||||
|
||||
internal 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()
|
||||
|
||||
internal fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
|
||||
|
||||
internal fun ByteBuffer.getUShort() = this.getShort().toUShort()
|
||||
internal fun ByteBuffer.getUInt() = this.getInt().toUInt()
|
||||
|
||||
internal fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort())
|
||||
internal fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt())
|
||||
|
||||
internal fun DataInput.readUShort() = this.readShort().toUShort()
|
||||
internal fun DataInput.readUInt() = this.readInt().toUInt()
|
||||
|
||||
internal fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
|
||||
internal fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
|
||||
|
||||
internal fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
|
||||
internal fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
|
||||
|
||||
internal fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
|
||||
internal fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
|
||||
@@ -1,197 +0,0 @@
|
||||
package app.revanced.library.zip
|
||||
|
||||
import app.revanced.library.zip.structures.ZipEndRecord
|
||||
import app.revanced.library.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()
|
||||
|
||||
// Open file for writing if it doesn't exist (because the intention is to write) or is writable.
|
||||
private val filePointer: RandomAccessFile = RandomAccessFile(
|
||||
file,
|
||||
if (!file.exists() || file.canWrite()) "rw" else "r"
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
companion object ApkZipFile {
|
||||
private const val DEFAULT_ALIGNMENT = 4
|
||||
private const val LIBRARY_ALIGNMENT = 4096
|
||||
|
||||
val apkZipEntryAlignment = { entry: ZipEntry ->
|
||||
if (entry.compression.toUInt() != 0u) null
|
||||
else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT
|
||||
else DEFAULT_ALIGNMENT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package app.revanced.library.zip.structures
|
||||
|
||||
import app.revanced.library.zip.putUInt
|
||||
import app.revanced.library.zip.putUShort
|
||||
import app.revanced.library.zip.readUIntLE
|
||||
import app.revanced.library.zip.readUShortLE
|
||||
import java.io.DataInput
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
internal 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,187 +0,0 @@
|
||||
package app.revanced.library.zip.structures
|
||||
|
||||
import app.revanced.library.zip.*
|
||||
import java.io.DataInput
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
class ZipEntry private constructor(
|
||||
internal val version: UShort,
|
||||
internal val versionNeeded: UShort,
|
||||
internal val flags: UShort,
|
||||
internal var compression: UShort,
|
||||
internal val modificationTime: UShort,
|
||||
internal val modificationDate: UShort,
|
||||
internal var crc32: UInt,
|
||||
internal var compressedSize: UInt,
|
||||
internal var uncompressedSize: UInt,
|
||||
internal val diskNumber: UShort,
|
||||
internal val internalAttributes: UShort,
|
||||
internal val externalAttributes: UInt,
|
||||
internal var localHeaderOffset: UInt,
|
||||
internal val fileName: String,
|
||||
internal val extraField: ByteArray,
|
||||
internal val fileComment: String,
|
||||
internal var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
||||
) {
|
||||
internal val LFHSize: Int
|
||||
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
||||
|
||||
internal val dataOffset: UInt
|
||||
get() = localHeaderOffset + LFHSize.toUInt()
|
||||
|
||||
constructor(fileName: String) : this(
|
||||
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),
|
||||
""
|
||||
)
|
||||
|
||||
companion object {
|
||||
internal const val CDE_HEADER_SIZE = 46
|
||||
internal const val CDE_SIGNATURE = 0x02014b50u
|
||||
|
||||
internal const val LFH_HEADER_SIZE = 30
|
||||
internal const val LFH_SIGNATURE = 0x04034b50u
|
||||
|
||||
internal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun readLocalExtra(buffer: ByteBuffer) {
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
||||
localExtraField = ByteArray(buffer.getUShort().toInt())
|
||||
}
|
||||
|
||||
internal 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
|
||||
}
|
||||
|
||||
internal 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,46 +0,0 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.library.Options.setOptions
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.options.types.BooleanPatchOption.Companion.booleanPatchOption
|
||||
import app.revanced.patcher.patch.options.types.StringPatchOption.Companion.stringPatchOption
|
||||
import org.junit.jupiter.api.MethodOrderer
|
||||
import org.junit.jupiter.api.Order
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestMethodOrder
|
||||
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
||||
internal object PatchOptionsTest {
|
||||
private var patches = setOf(PatchOptionsTestPatch)
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
fun serializeTest() {
|
||||
assert(SERIALIZED_JSON == Options.serialize(patches))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
fun loadOptionsTest() {
|
||||
patches.setOptions(CHANGED_JSON)
|
||||
|
||||
assert(PatchOptionsTestPatch.option1 == "test")
|
||||
assert(PatchOptionsTestPatch.option2 == 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}]}]"
|
||||
|
||||
object PatchOptionsTestPatch : BytecodePatch(name = "PatchOptionsTestPatch") {
|
||||
var option1 by stringPatchOption("key1", null, "title1", "description1")
|
||||
var option2 by booleanPatchOption("key2", true, "title2", "description2")
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package app.revanced.library
|
||||
|
||||
import app.revanced.patcher.PatchSet
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal object PatchUtilsTest {
|
||||
@Test
|
||||
fun `return 'a' because it is the most common version`() {
|
||||
val patches = arrayOf("a", "a", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d")
|
||||
.map { version -> newPatch("some.package", version) }
|
||||
.toSet()
|
||||
|
||||
assertEqualsVersion("a", patches, "some.package")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return null because no patches were supplied`() {
|
||||
assertEqualsVersion(null, emptySet<BytecodePatch>(), "some.package")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return null because no patch is compatible with the supplied package name`() {
|
||||
val patches = setOf(newPatch("some.package", "a"))
|
||||
|
||||
assertEqualsVersion(null, patches, "other.package")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return null because no patch compatible package is constrained to a version`() {
|
||||
val patches = setOf(
|
||||
newPatch("other.package"),
|
||||
newPatch("other.package"),
|
||||
)
|
||||
|
||||
assertEqualsVersion(null, patches, "other.package")
|
||||
}
|
||||
|
||||
private fun assertEqualsVersion(
|
||||
expected: String?, patches: PatchSet, compatiblePackageName: String
|
||||
) = assertEquals(expected, PatchUtils.getMostCommonCompatibleVersion(patches, compatiblePackageName))
|
||||
|
||||
private fun newPatch(packageName: String, vararg versions: String) = object : BytecodePatch(
|
||||
compatiblePackages = setOf(CompatiblePackage(packageName, versions.toSet()))
|
||||
) {
|
||||
override fun execute(context: BytecodeContext) {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user