mirror of
https://github.com/ReVanced/revanced-cli.git
synced 2026-01-11 13:56:18 +00:00
Compare commits
540 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5c27408f3 | ||
|
|
8ee5b754da | ||
|
|
9476f5a2e4 | ||
|
|
5ff105cf6b | ||
|
|
f0f3e5614b | ||
|
|
48a1a39b94 | ||
|
|
c45ed69adf | ||
|
|
8641d301b1 | ||
|
|
44b72ca99d | ||
|
|
240f2cfb6b | ||
|
|
304275ddbb | ||
|
|
483590726a | ||
|
|
cf20efd467 | ||
|
|
164d09dec1 | ||
|
|
1e92239616 | ||
|
|
a5a5085f0f | ||
|
|
1c10a7760d | ||
|
|
4ff697ef0b | ||
|
|
628b452b7e | ||
|
|
e622616628 | ||
|
|
109b33f1ed | ||
|
|
9fe4795923 | ||
|
|
f21c56d8c0 | ||
|
|
de63cfa426 | ||
|
|
9c0ad4604a | ||
|
|
2fcb74e79d | ||
|
|
f2368ccf27 | ||
|
|
a4e8d2df0d | ||
|
|
724593a7b1 | ||
|
|
4f1982e3ad | ||
|
|
f1d7d0dc62 | ||
|
|
40c90f5c1b | ||
|
|
30b46bcb0f | ||
|
|
b6f63c7d9d | ||
|
|
b5bd988a1d | ||
|
|
95c998b0a6 | ||
|
|
24a8f6575c | ||
|
|
55f0c01e5a | ||
|
|
ee1e96b2d5 | ||
|
|
0fc1d571e8 | ||
|
|
4933fe0314 | ||
|
|
2e2ddac273 | ||
|
|
3c264572a2 | ||
|
|
63e8585652 | ||
|
|
8f59d94dc7 | ||
|
|
3bcee04a7d | ||
|
|
821035107d | ||
|
|
8becebaa42 | ||
|
|
fe563fff93 | ||
|
|
2d17459fa3 | ||
|
|
16109bd8bc | ||
|
|
09bc652317 | ||
|
|
1d051365f3 | ||
|
|
ab7d9d8e1e | ||
|
|
5e089ea9af | ||
|
|
06c6a97915 | ||
|
|
a974b8ea80 | ||
|
|
89c35ee21b | ||
|
|
41bdb04ab0 | ||
|
|
a5542467c8 | ||
|
|
5fd205f77d | ||
|
|
e7c3d64bf1 | ||
|
|
3765957043 | ||
|
|
5e63e0a276 | ||
|
|
36c6a6a5f7 | ||
|
|
860fb7b957 | ||
|
|
9d96bb7b4c | ||
|
|
3afa590616 | ||
|
|
db0b943ad3 | ||
|
|
8c3d4d3aa1 | ||
|
|
04d312df31 | ||
|
|
158f59ab88 | ||
|
|
a078b53a21 | ||
|
|
fb75b0f115 | ||
|
|
c52383eca0 | ||
|
|
0562ee85c5 | ||
|
|
1cde672a05 | ||
|
|
d5419dbb6b | ||
|
|
d29016b428 | ||
|
|
1eb51e9d4f | ||
|
|
86b8415ac6 | ||
|
|
8f4aebc60b | ||
|
|
bd3171c7c5 | ||
|
|
6481ba68db | ||
|
|
799882e6a6 | ||
|
|
616d14f009 | ||
|
|
b2055ce07d | ||
|
|
4fc42089a5 | ||
|
|
0139dfe0bf | ||
|
|
7d1c0e663f | ||
|
|
6cc6960493 | ||
|
|
9f15ac6ec5 | ||
|
|
6670d89c74 | ||
|
|
a60d07fc5d | ||
|
|
362e6f5904 | ||
|
|
51402ab8a7 | ||
|
|
a8c8682116 | ||
|
|
a86919eb71 | ||
|
|
8b04b6790b | ||
|
|
59dfc988e3 | ||
|
|
b01fef730a | ||
|
|
a564aaa1b5 | ||
|
|
f315485713 | ||
|
|
28648a1c53 | ||
|
|
daac98817f | ||
|
|
9328475e11 | ||
|
|
6507b5fe8a | ||
|
|
349ff5749a | ||
|
|
495735f44c | ||
|
|
f48da18e37 | ||
|
|
cf58f55b38 | ||
|
|
da4469f402 | ||
|
|
df2c137ae9 | ||
|
|
8a2f219f8b | ||
|
|
719d9fcfa3 | ||
|
|
6c44a730c3 | ||
|
|
ba573f73d0 | ||
|
|
e95a392016 | ||
|
|
fc62f7f475 | ||
|
|
ff2e10dbe6 | ||
|
|
7ff433860d | ||
|
|
7121e5fa4c | ||
|
|
23b5e8b7f9 | ||
|
|
d1c4d8df3e | ||
|
|
89acffe788 | ||
|
|
64d9127291 | ||
|
|
e3c55507cf | ||
|
|
64afc95a81 | ||
|
|
022fd230f6 | ||
|
|
a177693ece | ||
|
|
4b6dbffd7b | ||
|
|
78e89dd3bf | ||
|
|
a08530d47b | ||
|
|
eec2234226 | ||
|
|
d5ea5a0ab1 | ||
|
|
6fc3eebc21 | ||
|
|
c84041f942 | ||
|
|
995f2ec99b | ||
|
|
84b24f1b6f | ||
|
|
95a974a5ac | ||
|
|
ee6d883777 | ||
|
|
b65e4083d6 | ||
|
|
e46d855643 | ||
|
|
80a8d88406 | ||
|
|
50c0f98ce5 | ||
|
|
77d91735ff | ||
|
|
3846f721ca | ||
|
|
f199298317 | ||
|
|
592dc1c64a | ||
|
|
8da0c2bdfe | ||
|
|
e8e69aaf5a | ||
|
|
157278c9ba | ||
|
|
049a385473 | ||
|
|
e7c9da7c33 | ||
|
|
67bf8c1849 | ||
|
|
075f6ad565 | ||
|
|
6942b22a68 | ||
|
|
ac5742dd6c | ||
|
|
a022febd0c | ||
|
|
978032cce1 | ||
|
|
52c5259451 | ||
|
|
43fc20d90e | ||
|
|
e3851d58c8 | ||
|
|
3e13fb5d56 | ||
|
|
db50cee12c | ||
|
|
578e16b099 | ||
|
|
1319ab7629 | ||
|
|
2a08af3cb9 | ||
|
|
7794327a11 | ||
|
|
2b77608651 | ||
|
|
d09aca65df | ||
|
|
c8d4f56d61 | ||
|
|
c67e3c70c7 | ||
|
|
05878a6e06 | ||
|
|
29bc0e8df8 | ||
|
|
c4a89e39b9 | ||
|
|
847e384d9e | ||
|
|
96c196dcb1 | ||
|
|
dfd535c201 | ||
|
|
2b6051e7f3 | ||
|
|
0304988733 | ||
|
|
ca809f0948 | ||
|
|
5d50d1a622 | ||
|
|
83c28d9f71 | ||
|
|
93cbcc28aa | ||
|
|
3b3f7c7a7a | ||
|
|
bbb1a63abd | ||
|
|
f7fcbc55bb | ||
|
|
9fe5a0b6d9 | ||
|
|
4f2d2568d3 | ||
|
|
a3d8f004ec | ||
|
|
41ffc99ad0 | ||
|
|
1121376018 | ||
|
|
48e1689223 | ||
|
|
7580f5c2fc | ||
|
|
dd6c1392eb | ||
|
|
2a3dbafd17 | ||
|
|
9e39a6f8e4 | ||
|
|
2d5a7fdf1e | ||
|
|
be5d812dff | ||
|
|
c93186fb97 | ||
|
|
3a198052bb | ||
|
|
0f3e090418 | ||
|
|
6aed946183 | ||
|
|
924c1f80ec | ||
|
|
8dd709b6ef | ||
|
|
139e7facac | ||
|
|
1d26e572f7 | ||
|
|
2c7fcaf4ad | ||
|
|
52c3be23f2 | ||
|
|
3dd875d14c | ||
|
|
0350b7f1a2 | ||
|
|
a3d8705e89 | ||
|
|
a536c9f815 | ||
|
|
11c3a6cfd4 | ||
|
|
a5851f0c1a | ||
|
|
963ae3a5fa | ||
|
|
93f338a731 | ||
|
|
7dcf15e03b | ||
|
|
41898d7547 | ||
|
|
45dd15f679 | ||
|
|
c2da04a834 | ||
|
|
f556909b9e | ||
|
|
1fa3dd7b8c | ||
|
|
1fcc591222 | ||
|
|
1fd3b83d46 | ||
|
|
7ec9040d41 | ||
|
|
44cc88b25d | ||
|
|
97548f80b3 | ||
|
|
d766f0e229 | ||
|
|
b0e748daff | ||
|
|
ba758f00f4 | ||
|
|
a9c2a5f096 | ||
|
|
0dcd838de3 | ||
|
|
a7290353bf | ||
|
|
47a20afd2d | ||
|
|
f8972eac3e | ||
|
|
9edbbf3163 | ||
|
|
32da961d57 | ||
|
|
8a5daab2a3 | ||
|
|
c0cc909626 | ||
|
|
b74213f66e | ||
|
|
fe75d4ab87 | ||
|
|
07da528ce2 | ||
|
|
ef5fa9b4c9 | ||
|
|
fc359923e3 | ||
|
|
2e3cd90537 | ||
|
|
7174364ef8 | ||
|
|
4c9d414228 | ||
|
|
5e73e6d2de | ||
|
|
667ca3051c | ||
|
|
9220642a7b | ||
|
|
3f47235c64 | ||
|
|
83b475c2e3 | ||
|
|
4105b638c0 | ||
|
|
f9cf7d21b7 | ||
|
|
0a758d86df | ||
|
|
83f99e29ec | ||
|
|
00410d54c0 | ||
|
|
5eaad33dc1 | ||
|
|
fa85482c4a | ||
|
|
caed39af7f | ||
|
|
20e1fdc18d | ||
|
|
19cd541e9a | ||
|
|
f6c221d72d | ||
|
|
9c148c96bf | ||
|
|
2c5b53c7e9 | ||
|
|
77d9cb98d1 | ||
|
|
38677e2319 | ||
|
|
e5e768fa34 | ||
|
|
9082181ce5 | ||
|
|
542580ecff | ||
|
|
26112a4fd2 | ||
|
|
553fbdbb8f | ||
|
|
be429a4770 | ||
|
|
8c391cecce | ||
|
|
bd14bdb5ea | ||
|
|
d0fc886428 | ||
|
|
1ccbed8d17 | ||
|
|
3b89fb7bc2 | ||
|
|
16bb1a60fc | ||
|
|
5f1843771e | ||
|
|
9de90703c8 | ||
|
|
1201f9edc5 | ||
|
|
9c8f9608b2 | ||
|
|
be90d2e360 | ||
|
|
79dc99aa9b | ||
|
|
260ff00951 | ||
|
|
cfe4c5ed0e | ||
|
|
7133242691 | ||
|
|
55750ce16a | ||
|
|
6003e68d47 | ||
|
|
9f8c2aeedf | ||
|
|
0a16bc849b | ||
|
|
ecd5147590 | ||
|
|
ca255c9e1b | ||
|
|
e9aa490355 | ||
|
|
69fe59f97b | ||
|
|
995e1712bc | ||
|
|
a236f10bb0 | ||
|
|
4a6134ea60 | ||
|
|
b084024742 | ||
|
|
2ef48af1b3 | ||
|
|
da2c91874d | ||
|
|
379687c814 | ||
|
|
2f5577e464 | ||
|
|
6962fc2f4c | ||
|
|
a6cef26210 | ||
|
|
b4892c4413 | ||
|
|
90368405ad | ||
|
|
26967959e2 | ||
|
|
eac6f6fbe3 | ||
|
|
af4ec43352 | ||
|
|
4f5644b62c | ||
|
|
31fb3166d9 | ||
|
|
a76bd0ad35 | ||
|
|
ba07ab1ac2 | ||
|
|
92bf57f279 | ||
|
|
886d501026 | ||
|
|
4b04cc4b59 | ||
|
|
75d237058f | ||
|
|
40026fe300 | ||
|
|
dbec559ed7 | ||
|
|
00e49719ca | ||
|
|
df276846e1 | ||
|
|
ac3a8f66f7 | ||
|
|
e900ce8486 | ||
|
|
ee6dd5a57c | ||
|
|
23d9e1e841 | ||
|
|
3718b7992c | ||
|
|
bce5e86874 | ||
|
|
cda6df6238 | ||
|
|
0dc230292a | ||
|
|
695cd1ba41 | ||
|
|
d08bbf8664 | ||
|
|
d55dd0a1fc | ||
|
|
a53d3482de | ||
|
|
7a3d0420f7 | ||
|
|
5cddf5004e | ||
|
|
6393a5016a | ||
|
|
cad38ca3bd | ||
|
|
6c1169b433 | ||
|
|
bf2bb72ce0 | ||
|
|
567bdee887 | ||
|
|
9888b0e08e | ||
|
|
51c04b7b16 | ||
|
|
a39cf893d6 | ||
|
|
6485e477a1 | ||
|
|
f99d340534 | ||
|
|
4edc325978 | ||
|
|
cffa1ea3a4 | ||
|
|
d15505adaa | ||
|
|
aaf452a103 | ||
|
|
1753a1aa3e | ||
|
|
f4e74cb4f2 | ||
|
|
50df4e117e | ||
|
|
9fcd2b4cd1 | ||
|
|
e6bb05c0b9 | ||
|
|
6bd7d42e32 | ||
|
|
19200eacc1 | ||
|
|
666738ee57 | ||
|
|
961267b8cd | ||
|
|
6b7314a399 | ||
|
|
c8a47c51df | ||
|
|
72d8f73d7f | ||
|
|
1957a2c4ff | ||
|
|
07a423b19e | ||
|
|
31e4a41dd2 | ||
|
|
8284c1048f | ||
|
|
9c64f5ec3e | ||
|
|
f4c447e71b | ||
|
|
b69e784785 | ||
|
|
ccce9c926d | ||
|
|
dc4ab22f41 | ||
|
|
25d8ad4aaf | ||
|
|
3d9436e691 | ||
|
|
ee70423527 | ||
|
|
d5794b94ca | ||
|
|
3160d894da | ||
|
|
545597959a | ||
|
|
a6db0edc70 | ||
|
|
ff0d3dd224 | ||
|
|
d9a1fd33ed | ||
|
|
ad81a1b656 | ||
|
|
8c2aeff2cf | ||
|
|
eb6c659f5b | ||
|
|
bac4f839c9 | ||
|
|
c1e7215d31 | ||
|
|
18d132123f | ||
|
|
74d73ca3a7 | ||
|
|
ae91b0d597 | ||
|
|
1a67cd81da | ||
|
|
9b9fad7cc0 | ||
|
|
1620305b4a | ||
|
|
d906edbe78 | ||
|
|
4e53638cb9 | ||
|
|
7d1f200621 | ||
|
|
a7db6cb8f1 | ||
|
|
d61ab058da | ||
|
|
235c2742ec | ||
|
|
8a964efd00 | ||
|
|
8114d1dca0 | ||
|
|
5a7cadd96d | ||
|
|
1c5983b803 | ||
|
|
2c7eb7274c | ||
|
|
512152fb66 | ||
|
|
8793b5fc65 | ||
|
|
39c490d6b7 | ||
|
|
6e703eb8e8 | ||
|
|
c677eb9792 | ||
|
|
75c8ea4639 | ||
|
|
b69dd52ca9 | ||
|
|
c590bf559c | ||
|
|
ec0b847419 | ||
|
|
b3cf32e89d | ||
|
|
8a1199ba9a | ||
|
|
25b45800a1 | ||
|
|
2fe3303a27 | ||
|
|
04339f0654 | ||
|
|
456428a836 | ||
|
|
c9ae379c77 | ||
|
|
81d702949c | ||
|
|
39e377bc48 | ||
|
|
fce40421e9 | ||
|
|
cb554c8bdf | ||
|
|
269c753dfa | ||
|
|
6403e34712 | ||
|
|
f68e7697ff | ||
|
|
64b7e86252 | ||
|
|
7be9af0942 | ||
|
|
6a35cf7ea4 | ||
|
|
4914fd37bc | ||
|
|
38052b6ecf | ||
|
|
6e21d81964 | ||
|
|
3a733e5137 | ||
|
|
4f4e1f9834 | ||
|
|
e035d93d2c | ||
|
|
839854d890 | ||
|
|
43c772c98d | ||
|
|
b6dff6d832 | ||
|
|
29b057dead | ||
|
|
7df4a7e7da | ||
|
|
07c09f092c | ||
|
|
e8c9c80349 | ||
|
|
467d8387e6 | ||
|
|
65637c8b3a | ||
|
|
aa21137e99 | ||
|
|
b35302094a | ||
|
|
dfb527fc29 | ||
|
|
ac2332a592 | ||
|
|
a530624fb2 | ||
|
|
0e3ecc3a51 | ||
|
|
7fac75c6b6 | ||
|
|
7ca0b35e45 | ||
|
|
cb0fde4908 | ||
|
|
75e810e42e | ||
|
|
37cc77dfc5 | ||
|
|
075bf406fd | ||
|
|
71c81510f7 | ||
|
|
51fd16409f | ||
|
|
3f5345af6e | ||
|
|
649d9bdb2a | ||
|
|
f4b04698d8 | ||
|
|
240ab18eaf | ||
|
|
096dba2337 | ||
|
|
26780f94e5 | ||
|
|
faa52e2c68 | ||
|
|
e3e74ac0e9 | ||
|
|
de1bdb708c | ||
|
|
9b2b933998 | ||
|
|
fb5a72fdc0 | ||
|
|
86c5992630 | ||
|
|
06d2139ebf | ||
|
|
15ba4f40cd | ||
|
|
2d70037913 | ||
|
|
34108b9229 | ||
|
|
64a323e7e7 | ||
|
|
372470c77b | ||
|
|
3cef245728 | ||
|
|
aa54a2b1a9 | ||
|
|
f8c430be33 | ||
|
|
7692c18282 | ||
|
|
469d0861af | ||
|
|
208c5bc5b0 | ||
|
|
82efd9941f | ||
|
|
48e9043517 | ||
|
|
c3d8fecad0 | ||
|
|
d96eb847d7 | ||
|
|
c8e793efab | ||
|
|
63a8aa315d | ||
|
|
491874bb51 | ||
|
|
cb887ead2c | ||
|
|
cd36e710d6 | ||
|
|
1d096c3e92 | ||
|
|
8d29b8cd47 | ||
|
|
0ce680a6f1 | ||
|
|
ddebe4d43c | ||
|
|
bc17298a80 | ||
|
|
536f6f90b1 | ||
|
|
ccf5206da8 | ||
|
|
07714abe49 | ||
|
|
d954424b30 | ||
|
|
168c003a31 | ||
|
|
96ec6a0384 | ||
|
|
1cd0976415 | ||
|
|
d8dbffd7a7 | ||
|
|
5f2bccc4e5 | ||
|
|
5af2f10e14 | ||
|
|
40c8500e38 | ||
|
|
9add2f9e82 | ||
|
|
a942a57364 | ||
|
|
718df54bdc | ||
|
|
507b6f3409 | ||
|
|
c183c38f2a | ||
|
|
d9c5a179c5 | ||
|
|
6447311b0a | ||
|
|
2015c2a1dc | ||
|
|
99f123d1ca | ||
|
|
6ccbec6c95 | ||
|
|
9c30eac7c1 | ||
|
|
a26b0ea64d | ||
|
|
ab4c81965e | ||
|
|
eb83cabfff | ||
|
|
125fa06ca6 | ||
|
|
131100ef00 | ||
|
|
8e91c12c5e | ||
|
|
93a4787780 | ||
|
|
ac7c7a9a1a | ||
|
|
cd3ded1fbd | ||
|
|
bac8c67d6f | ||
|
|
387afd6bd5 | ||
|
|
ef2019ceba | ||
|
|
c1333d87f4 | ||
|
|
00fec2508a | ||
|
|
387b6bd016 | ||
|
|
5e7785fd8f | ||
|
|
19dfa2e2f1 | ||
|
|
0ddc2b54b7 | ||
|
|
3a51ce71a5 | ||
|
|
52b316150d |
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
|
||||
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# Linux start script should use lf
|
||||
/gradlew text eol=lf
|
||||
|
||||
# These are Windows script files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
|
||||
110
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
110
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
name: 🐞 Bug report
|
||||
description: Report a bug or an issue.
|
||||
title: 'bug: '
|
||||
labels: ['Bug report']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# ReVanced CLI bug report
|
||||
|
||||
Before creating a new bug report, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/ReVanced/revanced-cli/labels/Bug%20report).
|
||||
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli/blob/main/CONTRIBUTING.md).
|
||||
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: |
|
||||
- Describe your bug in detail
|
||||
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
||||
- Add images and videos if possible
|
||||
- List used patches if applicable
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Error logs
|
||||
description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell.
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Solution
|
||||
description: If applicable, add a possible solution to the bug.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add additional context here.
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your bug report will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: This issue is not a duplicate of an existing bug report.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🗨 Discussions
|
||||
url: https://github.com/revanced/revanced-suggestions/discussions
|
||||
about: Have something unspecific to ReVanced CLI in mind? Search for or start a new discussion!
|
||||
106
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
106
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
name: ⭐ Feature request
|
||||
description: Create a detailed request for a new feature.
|
||||
title: 'feat: '
|
||||
labels: ['Feature request']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-cli/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# ReVanced CLI feature request
|
||||
|
||||
Before creating a new feature request, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/ReVanced/revanced-cli//labels/Feature%20request).
|
||||
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli//blob/main/CONTRIBUTING.md).
|
||||
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature description
|
||||
description: |
|
||||
- Describe your feature in detail
|
||||
- Add images, videos, links, examples, references, etc. if possible
|
||||
- Add the target application name in case you request a new patch
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: |
|
||||
A strong motivation is necessary for a feature request to be considered.
|
||||
|
||||
- Why should this feature be implemented?
|
||||
- What is the explicit use case?
|
||||
- What are the benefits?
|
||||
- What makes this feature important?
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your feature request will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: This issue is not a duplicate of an existing feature request.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
2
.github/config.yml
vendored
Normal file
2
.github/config.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
firstPRMergeComment: >
|
||||
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) 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
|
||||
25
.github/workflows/build_pull_request.yml
vendored
Normal file
25
.github/workflows/build_pull_request.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
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: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build --no-daemon
|
||||
26
.github/workflows/open_pull_request.yml
vendored
Normal file
26
.github/workflows/open_pull_request.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Open a PR to main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
||||
|
||||
jobs:
|
||||
pull-request:
|
||||
name: Open pull request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Open pull request
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
destination_branch: 'main'
|
||||
pr_title: 'chore: ${{ env.MESSAGE }}'
|
||||
pr_body: 'This pull request will ${{ env.MESSAGE }}.'
|
||||
pr_draft: true
|
||||
50
.github/workflows/release.yml
vendored
50
.github/workflows/release.yml
vendored
@@ -1,40 +1,50 @@
|
||||
name: Release
|
||||
|
||||
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@v2
|
||||
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: Setup JDK
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
cache: gradle
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
- name: Build with Gradle
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build clean
|
||||
- name: Setup semantic-release
|
||||
run: npm install -g semantic-release @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin -D
|
||||
|
||||
- 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: ${{ env.GPG_FINGERPRINT }}
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npx semantic-release
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
run: npm exec semantic-release
|
||||
|
||||
19
.github/workflows/update_documentation.yml
vendored
Normal file
19
.github/workflows/update_documentation.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Update documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- docs/**
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
runs-on: ubuntu-latest
|
||||
name: Dispatch event to documentation repository
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
||||
repository: revanced/revanced-documentation
|
||||
event-type: update-documentation
|
||||
client-payload: '{"repo": "${{ github.event.repository.name }}", "ref": "${{ github.ref }}"}'
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -74,6 +74,7 @@ cmake-build-*/
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
.idea
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
@@ -111,4 +112,14 @@ gradle-app.setting
|
||||
.gradletasknamecache
|
||||
|
||||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
|
||||
# gradle/wrapper/gradle-wrapper.properties
|
||||
# gradle/wrapper/gradle-wrapper.properties
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# ReVanced CLI
|
||||
revanced-cache/
|
||||
options.toml
|
||||
|
||||
# Generated by an Android project (such as ReVanced Integrations)
|
||||
local.properties
|
||||
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
7
.idea/discord.xml
generated
7
.idea/discord.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
15
.idea/git_toolbox_prj.xml
generated
15
.idea/git_toolbox_prj.xml
generated
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxProjectSettings">
|
||||
<option name="commitMessageIssueKeyValidationOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
<option name="commitMessageValidationEnabledOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/misc.xml
generated
10
.idea/misc.xml
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
14
.idea/vcs.xml
generated
14
.idea/vcs.xml
generated
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CommitMessageInspectionProfile">
|
||||
<profile version="1.0">
|
||||
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../revanced-patcher" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../revanced-patches" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
24
.releaserc
24
.releaserc
@@ -7,11 +7,13 @@
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
["@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{"type": "build", "release": "patch"}
|
||||
]
|
||||
}],
|
||||
[
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{ "type": "build", "scope": "Needs bump", "release": "patch" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"gradle-semantic-release-plugin",
|
||||
@@ -29,9 +31,17 @@
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "build/libs/*all.jar"
|
||||
"path": "build/libs/*-all*"
|
||||
}
|
||||
]
|
||||
],
|
||||
successComment: false
|
||||
}
|
||||
],
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||
clearWorkspace: true
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
1069
CHANGELOG.md
1069
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
98
CONTRIBUTING.md
Normal file
98
CONTRIBUTING.md
Normal file
@@ -0,0 +1,98 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# 👋 Contribution guidelines
|
||||
|
||||
This document describes how to contribute to ReVanced CLI.
|
||||
|
||||
## 📖 Resources to help you get started
|
||||
|
||||
- The [documentation](/docs) explains how to use ReVanced CLI
|
||||
- [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
|
||||
- [Issues](https://github.com/ReVanced/revanced-cli/issues) are where we keep track of bugs and feature requests
|
||||
|
||||
## 🙏 Submitting a feature request
|
||||
|
||||
Features can be requested by opening an issue using the
|
||||
[Feature request issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
|
||||
|
||||
> **Note**
|
||||
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced CLI.
|
||||
> Good motivation has to be provided for a request to be accepted.
|
||||
|
||||
## 🐞 Submitting a bug report
|
||||
|
||||
If you encounter a bug while using ReVanced CLI, open an issue using the
|
||||
[Bug report issue template](https://github.com/ReVanced/revanced-cli/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
|
||||
|
||||
## 📝 How to contribute
|
||||
|
||||
1. Before contributing, it is recommended to open an issue to discuss your change
|
||||
with the maintainers of ReVanced CLI. This will help you determine whether your change is acceptable
|
||||
and whether it is worth your time to implement it
|
||||
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
|
||||
3. Commit your changes
|
||||
4. Submit a pull request to the `dev` branch of the repository and reference issues
|
||||
that your pull request closes in the description of your pull request
|
||||
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
|
||||
it will be merged into the `dev` branch and will be included in the next release of ReVanced CLI
|
||||
|
||||
❤️ Thank you for considering contributing to ReVanced CLI,
|
||||
ReVanced
|
||||
107
README.md
Normal file
107
README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# 💻 ReVanced CLI
|
||||
|
||||

|
||||

|
||||
|
||||
Command line application to use ReVanced.
|
||||
|
||||
## ❓ About
|
||||
|
||||
ReVanced CLI is a command line application that uses [ReVanced Patcher](https://github.com/revanced/revanced-patcher) to patch Android apps.
|
||||
|
||||
## 💪 Features
|
||||
|
||||
Some of the features ReVanced CLI provides are:
|
||||
|
||||
- 💉 **Patch apps**: Harness ReVanced Patcher to patch Android apps
|
||||
- 💾 **Install and uninstall apps**: Install and uninstall Apps via ADB,
|
||||
using the Android package manager, or by mounting using root permissions
|
||||
- 📃 **List patches from patch bundles**: List available patches, compatible packages, and versions
|
||||
- 💪 **Flexibility and functionality**: Apply any combination of patches to any version of Android apps
|
||||
|
||||
## 🔽 Download
|
||||
|
||||
You can download the most recent version of ReVanced CLI from
|
||||
[here](https://github.com/ReVanced/revanced-cli/releases/latest).
|
||||
Learn how to use ReVanced CLI by following the [documentation](/docs).
|
||||
|
||||
## 📚 Everything else
|
||||
|
||||
### 📙 Contributing
|
||||
|
||||
Thank you for considering contributing to ReVanced CLI.
|
||||
You can find the contribution guidelines [here](CONTRIBUTING.md).
|
||||
|
||||
### 🛠️ Building
|
||||
|
||||
To build a ReVanced CLI, you can follow the [documentation](/docs).
|
||||
|
||||
### 📃 Documentation
|
||||
|
||||
You can find the documentation of ReVanced CLI [here](/docs).
|
||||
|
||||
## 📜 Licence
|
||||
|
||||
ReVanced CLI 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 CLI as long as you track changes/dates in source files.
|
||||
Any modifications to ReVanced CLI must also be made available under the GPL, along with build & install instructions.
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
1
assets/revanced-logo/revanced-logo.svg
Normal file
1
assets/revanced-logo/revanced-logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Logo"><g id="Ring"><circle id="Ring-Background" serif:id="Ring Background" cx="400" cy="400" r="400" style="fill:#1b1b1b;"/><path id="Ring1" serif:id="Ring" d="M400,0c220.766,0 400,179.234 400,400c-0,220.766 -179.234,400 -400,400c-220.766,-0 -400,-179.234 -400,-400c0,-220.766 179.234,-400 400,-400Zm-0,36c200.897,-0 364,163.103 364,364c0,200.897 -163.103,364 -364,364c-200.897,0 -364,-163.103 -364,-364c-0,-200.897 163.103,-364 364,-364Z" style="fill:url(#_Linear1);"/></g><g id="Shape"><path id="V-Shape" serif:id="V Shape" d="M538.74,269.872c1.481,-3.382 1.157,-7.283 -0.863,-10.373c-2.021,-3.091 -5.464,-4.954 -9.156,-4.954c-5.148,0 -10.435,0 -14.165,0c-3.1,0 -5.907,1.834 -7.153,4.672c-12.468,28.396 -78.273,178.273 -100.25,228.328c-1.246,2.838 -4.053,4.671 -7.154,4.671c-3.1,0 -5.907,-1.833 -7.153,-4.671c-21.977,-50.055 -87.782,-199.932 -100.25,-228.328c-1.246,-2.838 -4.053,-4.672 -7.153,-4.672c-3.73,0 -9.017,0 -14.164,0c-3.693,0 -7.135,1.863 -9.156,4.954c-2.02,3.09 -2.344,6.991 -0.863,10.373c23.557,53.766 101.872,232.519 117.871,269.034c1.743,3.979 5.674,6.549 10.018,6.549c6.293,-0 15.408,-0 21.701,-0c4.344,-0 8.275,-2.57 10.018,-6.549c15.999,-36.515 94.315,-215.268 117.872,-269.034Z" style="fill:#fff;"/><path id="Diamond" d="M408.119,395.312c-1.675,2.901 -4.77,4.688 -8.119,4.688c-3.349,-0 -6.444,-1.787 -8.119,-4.688c-16.997,-29.44 -56.156,-97.264 -73.153,-126.704c-1.675,-2.901 -1.675,-6.474 0,-9.375c1.675,-2.901 4.77,-4.688 8.119,-4.688c33.995,0 112.311,0 146.306,0c3.349,0 6.444,1.787 8.119,4.688c1.675,2.901 1.675,6.474 -0,9.375c-16.997,29.44 -56.156,97.264 -73.153,126.704Z" style="fill:url(#_Linear2);"/></g></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.89859e-14,800,-800,4.89859e-14,400.001,3.31681e-10)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.77155e-14,289.317,-282.535,1.73003e-14,400,254.545)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
102
build.gradle.kts
102
build.gradle.kts
@@ -1,55 +1,95 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.7.0"
|
||||
id("com.github.johnrengelman.shadow") version "7.1.2"
|
||||
alias(libs.plugins.kotlin)
|
||||
alias(libs.plugins.shadow)
|
||||
application
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
|
||||
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
|
||||
application {
|
||||
mainClass = "app.revanced.cli.command.MainCommandKt"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
google()
|
||||
maven {
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||
// A repository must be speficied for some reason. "registry" is a dummy.
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials {
|
||||
username = githubUsername
|
||||
password = githubPassword
|
||||
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") }
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation(kotlin("reflect"))
|
||||
implementation(libs.revanced.patcher)
|
||||
implementation(libs.revanced.library)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.picocli)
|
||||
|
||||
implementation("app.revanced:revanced-patcher:2.1.2")
|
||||
implementation("info.picocli:picocli:4.6.3")
|
||||
implementation("com.android.tools.build:apksig:7.2.1")
|
||||
implementation("com.github.revanced:jadb:master-SNAPSHOT") // updated fork
|
||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||
testImplementation(libs.kotlin.test)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
tasks {
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
shadowJar {
|
||||
manifest {
|
||||
attributes("Main-Class" to "app.revanced.cli.main.MainKt")
|
||||
attributes("Implementation-Title" to project.name)
|
||||
attributes("Implementation-Version" to project.version)
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("PASSED", "SKIPPED", "FAILED")
|
||||
}
|
||||
}
|
||||
// Dummy task to fix the Gradle semantic-release plugin.
|
||||
// Remove this if you forked it to support building only.
|
||||
// Tracking issue: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
||||
register<DefaultTask>("publish") {
|
||||
group = "publish"
|
||||
description = "Dummy task"
|
||||
dependsOn(build)
|
||||
|
||||
processResources {
|
||||
expand("projectVersion" to project.version)
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
minimize {
|
||||
exclude(dependency("org.jetbrains.kotlin:.*"))
|
||||
exclude(dependency("org.bouncycastle:.*"))
|
||||
exclude(dependency("app.revanced:.*"))
|
||||
}
|
||||
}
|
||||
|
||||
publish {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
}
|
||||
|
||||
// Needed by gradle-semantic-release-plugin.
|
||||
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
||||
|
||||
// The maven-publish is also necessary to make the signing plugin work.
|
||||
publishing {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
publications {
|
||||
create<MavenPublication>("revanced-cli-publication") {
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
|
||||
sign(publishing.publications["revanced-cli-publication"])
|
||||
}
|
||||
|
||||
15
docs/0_prerequisites.md
Normal file
15
docs/0_prerequisites.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 💼 Prerequisites
|
||||
|
||||
To use ReVanced CLI, you will need to fulfill specific requirements.
|
||||
|
||||
## 🤝 Requirements
|
||||
|
||||
- Java Runtime Environment 11 ([Azul Zulu JRE](https://www.azul.com/downloads/?version=java-11-lts&package=jdk#zulu) or [OpenJDK](https://jdk.java.net/archive/))
|
||||
- [Android Debug Bridge (ADB)](https://developer.android.com/studio/command-line/adb) if you want to install the patched APK file on your device
|
||||
- An ABI other than ARMv7 such as x86 or x86-64 (or a custom AAPT binary that supports ARMv7)
|
||||
|
||||
## ⏭️ Whats next
|
||||
|
||||
The following section will show you how to use ReVanced CLI.
|
||||
|
||||
Continue: [🛠️ Using ReVanced CLI](1_usage.md)
|
||||
129
docs/1_usage.md
Normal file
129
docs/1_usage.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# 🛠️ Using ReVanced CLI
|
||||
|
||||
Learn how to use ReVanced CLI.
|
||||
|
||||
## 🔨 Usage
|
||||
|
||||
ReVanced CLI is divided into the following fundamental commands:
|
||||
|
||||
- ### 🚀 Show all available options for ReVanced CLI
|
||||
|
||||
```bash
|
||||
java -jar revanced-cli.jar -h
|
||||
```
|
||||
|
||||
- ### 📃 List patches
|
||||
|
||||
```bash
|
||||
java -jar revanced-cli.jar list-patches \
|
||||
--with-packages \
|
||||
--with-versions \
|
||||
--with-options \
|
||||
revanced-patches.jar [<patch-bundle> ...]
|
||||
```
|
||||
|
||||
- ### ⚙️ Generate options
|
||||
|
||||
This will generate an `options.json` file for the patches from a list of supplied patch bundles.
|
||||
The file can be supplied to ReVanced CLI later on.
|
||||
|
||||
```bash
|
||||
java -jar revanced-cli.jar options \
|
||||
--path options.json \
|
||||
--overwrite \
|
||||
revanced-patches.jar [<patch-bundle> ...]
|
||||
```
|
||||
|
||||
> **ℹ️ Note**
|
||||
> A default `options.json` file will be automatically created if it does not exist
|
||||
> without any need for intervention when using the `patch` command.
|
||||
|
||||
- ### 💉 Patch an app
|
||||
|
||||
You can patch apps by supplying patch bundles and the app to patch.
|
||||
After patching, ReVanced CLI can install the patched app on your device using two methods:
|
||||
|
||||
> **💡 Tip**
|
||||
> For ReVanced CLI to be able to install the patched app on your device, make sure ADB is working:
|
||||
>
|
||||
> ```bash
|
||||
> adb shell exit
|
||||
> ```
|
||||
>
|
||||
> If you want to mount the patched app on top of the un-patched app, make sure you have root permissions:
|
||||
>
|
||||
> ```bash
|
||||
> adb shell su -c exit
|
||||
> ```
|
||||
|
||||
> **⚠️ Warning**
|
||||
> Some patches may require integrations
|
||||
> such as [ReVanced Integrations](https://github.com/revanced/revanced-integrations).
|
||||
> Supply them with the option `--merge`. ReVanced Patcher will automatically determine if they are necessary.
|
||||
|
||||
- #### 👾 Patch an app and install it on your device regularly
|
||||
|
||||
```bash
|
||||
java -jar revanced-cli.jar patch \
|
||||
--patch-bundle revanced-patches.jar \
|
||||
-d \
|
||||
input.apk
|
||||
```
|
||||
|
||||
- #### 👾 Patch an app and mount it on top of the un-patched app with root permissions
|
||||
|
||||
> **❗ Caution**
|
||||
> Ensure that the same app you are patching and mounting over is installed on your device:
|
||||
>
|
||||
> ```bash
|
||||
> adb install app.apk
|
||||
> ```
|
||||
|
||||
```bash
|
||||
java -jar revanced-cli.jar patch \
|
||||
--patch-bundle revanced-patches.jar \
|
||||
--include "Some patch" \
|
||||
--ii 123 \
|
||||
--exclude "Some other patch" \
|
||||
-d \
|
||||
--mount \
|
||||
app.apk
|
||||
```
|
||||
|
||||
> **💡 Tip**
|
||||
> You can use the option `--ii` to include or `--ie` to exclude
|
||||
> patches by their index in relation to supplied patch bundles,
|
||||
> similarly to the option `--include` and `--exclude`.
|
||||
>
|
||||
> This is useful in case two patches have the same name, and you must include or exclude one.
|
||||
> The patch index is calculated by the position of the patch in the list of patches
|
||||
> from patch bundles supplied using the option `--patch-bundle`.
|
||||
>
|
||||
> You can list all patches with their indices using the command `list-patches`.
|
||||
>
|
||||
> Keep in mind that the indices can change based on the order of the patch bundles supplied,
|
||||
> as well if the patch bundles are updated because patches can be added or removed.
|
||||
|
||||
- ### 🗑️ Uninstall an app
|
||||
|
||||
```bash
|
||||
java -jar revanced-cli.jar utility uninstall \
|
||||
--package-name <package-name> \
|
||||
[<device-serial>]
|
||||
```
|
||||
|
||||
> **💡 Tip**
|
||||
> You can unmount an APK file
|
||||
> by adding the option `--unmount`.
|
||||
|
||||
- ### ️ 📦 Install an app
|
||||
|
||||
```bash
|
||||
java -jar revanced-cli.jar utility install \
|
||||
-a input.apk \
|
||||
[<device-serial>]
|
||||
```
|
||||
|
||||
> **💡 Tip**
|
||||
> You can mount an APK file
|
||||
> by supplying the app's package name to mount the supplied APK file over the option `-mount`.
|
||||
26
docs/2_building.md
Normal file
26
docs/2_building.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 🔨️ Building
|
||||
|
||||
Build ReVanced CLI from source.
|
||||
|
||||
## 📝 Requirements
|
||||
|
||||
- Java Development Kit 11 (Azul Zulu JRE or OpenJDK)
|
||||
|
||||
## 🏗️ Building
|
||||
|
||||
To build ReVanced CLI, follow these steps:
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone git@github.com:ReVanced/revanced-cli.git
|
||||
cd revanced-cli
|
||||
```
|
||||
|
||||
2. Build the project:
|
||||
|
||||
```bash
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
After the build succeeds, the built JAR file will be located at `build/libs/revanced-cli-<version>-all.jar`.
|
||||
9
docs/README.md
Normal file
9
docs/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 💻 Documentation and guides of ReVanced CLI
|
||||
|
||||
This documentation contains topics around [ReVanced CLI](https://github.com/revanced/revanced-cli).
|
||||
|
||||
## 📖 Table of contents
|
||||
|
||||
1. [💼 Prerequisites](0_prerequisites.md)
|
||||
2. [🛠️ Using ReVanced CLI](1_usage.md)
|
||||
3. [🔨 Building ReVanced CLI](2_building.md)
|
||||
@@ -1,2 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 2.1.0
|
||||
version = 4.5.0
|
||||
|
||||
18
gradle/libs.versions.toml
Normal file
18
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[versions]
|
||||
shadow = "8.1.1"
|
||||
kotlin = "1.9.22"
|
||||
kotlinx-coroutines-core = "1.7.3"
|
||||
picocli = "4.7.5"
|
||||
revanced-patcher = "19.3.1"
|
||||
revanced-library = "2.2.1"
|
||||
|
||||
[libraries]
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
|
||||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
||||
revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" }
|
||||
|
||||
[plugins]
|
||||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
zipStorePath=wrapper/dist
|
||||
293
gradlew
vendored
293
gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,67 +17,98 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# 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
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MSYS* | MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -98,88 +129,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
15
gradlew.bat
vendored
15
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
7067
package-lock.json
generated
Normal file
7067
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
package.json
Normal file
9
package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.9.1",
|
||||
"semantic-release": "^23.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1 +1,7 @@
|
||||
rootProject.name = "revanced-cli"
|
||||
rootProject.name = "revanced-cli"
|
||||
|
||||
buildCache {
|
||||
local {
|
||||
isEnabled = "CI" !in System.getenv()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.library.PackageName
|
||||
import app.revanced.library.PatchUtils
|
||||
import app.revanced.library.VersionMap
|
||||
import app.revanced.patcher.PatchBundleLoader
|
||||
import picocli.CommandLine
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "list-versions",
|
||||
description = [
|
||||
"List the most common compatible versions of apps that are compatible " +
|
||||
"with the patches in the supplied patch bundles.",
|
||||
],
|
||||
)
|
||||
internal class ListCompatibleVersions : Runnable {
|
||||
private val logger = Logger.getLogger(ListCompatibleVersions::class.java.name)
|
||||
|
||||
@CommandLine.Parameters(
|
||||
description = ["Paths to patch bundles."],
|
||||
arity = "1..*",
|
||||
)
|
||||
private lateinit var patchBundles: Array<File>
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-f", "--filter-package-names"],
|
||||
description = ["Filter patches by package name."],
|
||||
)
|
||||
private var packageNames: Set<String>? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-u", "--count-unused-patches"],
|
||||
description = ["Count patches that are not used by default."],
|
||||
showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
|
||||
)
|
||||
private var countUnusedPatches: Boolean = false
|
||||
|
||||
override fun run() {
|
||||
val patches = PatchBundleLoader.Jar(*patchBundles)
|
||||
|
||||
fun VersionMap.buildVersionsString(): String {
|
||||
if (isEmpty()) return "Any"
|
||||
|
||||
fun buildPatchesCountString(count: Int) = if (count == 1) "1 patch" else "$count patches"
|
||||
|
||||
return entries.joinToString("\n") { (version, count) ->
|
||||
"$version (${buildPatchesCountString(count)})"
|
||||
}
|
||||
}
|
||||
|
||||
fun buildString(entry: Map.Entry<PackageName, VersionMap>) =
|
||||
buildString {
|
||||
val (name, versions) = entry
|
||||
appendLine("Package name: $name")
|
||||
appendLine("Most common compatible versions:")
|
||||
appendLine(versions.buildVersionsString().prependIndent("\t"))
|
||||
}
|
||||
|
||||
PatchUtils.getMostCommonCompatibleVersions(
|
||||
patches,
|
||||
packageNames,
|
||||
countUnusedPatches,
|
||||
).entries.joinToString("\n", transform = ::buildString).let(logger::info)
|
||||
}
|
||||
}
|
||||
139
src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt
Normal file
139
src/main/kotlin/app/revanced/cli/command/ListPatchesCommand.kt
Normal file
@@ -0,0 +1,139 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.patcher.PatchBundleLoader
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.options.PatchOption
|
||||
import picocli.CommandLine.*
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
@Command(
|
||||
name = "list-patches",
|
||||
description = ["List patches from supplied patch bundles."],
|
||||
)
|
||||
internal object ListPatchesCommand : Runnable {
|
||||
private val logger = Logger.getLogger(ListPatchesCommand::class.java.name)
|
||||
|
||||
@Parameters(
|
||||
description = ["Paths to patch bundles."],
|
||||
arity = "1..*",
|
||||
)
|
||||
private lateinit var patchBundles: Array<File>
|
||||
|
||||
@Option(
|
||||
names = ["-d", "--with-descriptions"],
|
||||
description = ["List their descriptions."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var withDescriptions: Boolean = true
|
||||
|
||||
@Option(
|
||||
names = ["-p", "--with-packages"],
|
||||
description = ["List the packages the patches are compatible with."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var withPackages: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-v", "--with-versions"],
|
||||
description = ["List the versions of the apps the patches are compatible with."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var withVersions: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-o", "--with-options"],
|
||||
description = ["List the options of the patches."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var withOptions: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-u", "--with-universal-patches"],
|
||||
description = ["List patches which are compatible with any app."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var withUniversalPatches: Boolean = true
|
||||
|
||||
@Option(
|
||||
names = ["-i", "--index"],
|
||||
description = ["List the index of each patch in relation to the supplied patch bundles."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var withIndex: Boolean = true
|
||||
|
||||
@Option(
|
||||
names = ["-f", "--filter-package-name"],
|
||||
description = ["Filter patches by package name."],
|
||||
)
|
||||
private var packageName: String? = null
|
||||
|
||||
override fun run() {
|
||||
fun Patch.CompatiblePackage.buildString() =
|
||||
buildString {
|
||||
if (withVersions && versions != null) {
|
||||
appendLine("Package name: $name")
|
||||
appendLine("Compatible versions:")
|
||||
append(versions!!.joinToString("\n") { version -> version }.prependIndent("\t"))
|
||||
} else {
|
||||
append("Package name: $name")
|
||||
}
|
||||
}
|
||||
|
||||
fun PatchOption<*>.buildString() =
|
||||
buildString {
|
||||
appendLine("Title: $title")
|
||||
description?.let { appendLine("Description: $it") }
|
||||
default?.let {
|
||||
appendLine("Key: $key")
|
||||
append("Default: $it")
|
||||
} ?: append("Key: $key")
|
||||
|
||||
values?.let { values ->
|
||||
appendLine("\nValid values:")
|
||||
append(values.map { "${it.value} (${it.key})" }.joinToString("\n").prependIndent("\t"))
|
||||
}
|
||||
}
|
||||
|
||||
fun IndexedValue<Patch<*>>.buildString() =
|
||||
let { (index, patch) ->
|
||||
buildString {
|
||||
if (withIndex) appendLine("Index: $index")
|
||||
|
||||
append("Name: ${patch.name}")
|
||||
|
||||
if (withDescriptions) append("\nDescription: ${patch.description}")
|
||||
|
||||
if (withOptions && patch.options.isNotEmpty()) {
|
||||
appendLine("\nOptions:")
|
||||
append(
|
||||
patch.options.values.joinToString("\n\n") { option ->
|
||||
option.buildString()
|
||||
}.prependIndent("\t"),
|
||||
)
|
||||
}
|
||||
|
||||
if (withPackages && patch.compatiblePackages != null) {
|
||||
appendLine("\nCompatible packages:")
|
||||
append(
|
||||
patch.compatiblePackages!!.joinToString("\n") {
|
||||
it.buildString()
|
||||
}.prependIndent("\t"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Patch<*>.filterCompatiblePackages(name: String) =
|
||||
compatiblePackages?.any { it.name == name }
|
||||
?: withUniversalPatches
|
||||
|
||||
val patches = PatchBundleLoader.Jar(*patchBundles).withIndex().toList()
|
||||
|
||||
val filtered =
|
||||
packageName?.let { patches.filter { (_, patch) -> patch.filterCompatiblePackages(it) } } ?: patches
|
||||
|
||||
if (filtered.isNotEmpty()) logger.info(filtered.joinToString("\n\n") { it.buildString() })
|
||||
}
|
||||
}
|
||||
@@ -1,149 +1,43 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.cli.logging.impl.DefaultCliLogger
|
||||
import app.revanced.cli.patcher.Patcher
|
||||
import app.revanced.cli.patcher.logging.impl.PatcherLogger
|
||||
import app.revanced.cli.signing.Signing
|
||||
import app.revanced.cli.signing.SigningOptions
|
||||
import app.revanced.patcher.PatcherOptions
|
||||
import app.revanced.patcher.extensions.PatchExtensions.description
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.util.patch.implementation.JarPatchBundle
|
||||
import app.revanced.utils.adb.Adb
|
||||
import picocli.CommandLine.*
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import app.revanced.cli.command.utility.UtilityCommand
|
||||
import app.revanced.library.logging.Logger
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.IVersionProvider
|
||||
import java.util.*
|
||||
|
||||
private class CLIVersionProvider : IVersionProvider {
|
||||
override fun getVersion() = arrayOf(
|
||||
MainCommand::class.java.`package`.implementationVersion ?: "unknown"
|
||||
)
|
||||
fun main(args: Array<String>) {
|
||||
Logger.setDefault()
|
||||
CommandLine(MainCommand).execute(*args).let(System::exit)
|
||||
}
|
||||
|
||||
private object CLIVersionProvider : IVersionProvider {
|
||||
override fun getVersion() =
|
||||
arrayOf(
|
||||
MainCommand::class.java.getResourceAsStream(
|
||||
"/app/revanced/cli/version.properties",
|
||||
)?.use { stream ->
|
||||
Properties().apply {
|
||||
load(stream)
|
||||
}.let {
|
||||
"ReVanced CLI v${it.getProperty("version")}"
|
||||
}
|
||||
} ?: "ReVanced CLI",
|
||||
)
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "ReVanced-CLI",
|
||||
name = "revanced-cli",
|
||||
description = ["Command line application to use ReVanced."],
|
||||
mixinStandardHelpOptions = true,
|
||||
versionProvider = CLIVersionProvider::class
|
||||
versionProvider = CLIVersionProvider::class,
|
||||
subcommands = [
|
||||
PatchCommand::class,
|
||||
OptionsCommand::class,
|
||||
ListPatchesCommand::class,
|
||||
ListCompatibleVersions::class,
|
||||
UtilityCommand::class,
|
||||
],
|
||||
)
|
||||
internal object MainCommand : Runnable {
|
||||
val logger = DefaultCliLogger()
|
||||
|
||||
@ArgGroup(exclusive = false, multiplicity = "1")
|
||||
lateinit var args: Args
|
||||
|
||||
class Args {
|
||||
@Option(names = ["-b", "--bundles"], description = ["One or more bundles of patches"], required = true)
|
||||
var patchBundles = arrayOf<String>()
|
||||
|
||||
@ArgGroup(exclusive = false)
|
||||
var lArgs: ListingArgs? = null
|
||||
|
||||
@ArgGroup(exclusive = false)
|
||||
var pArgs: PatchingArgs? = null
|
||||
}
|
||||
|
||||
class ListingArgs {
|
||||
@Option(names = ["-l", "--list"], description = ["List patches only"], required = true)
|
||||
var listOnly: Boolean = false
|
||||
}
|
||||
|
||||
class PatchingArgs {
|
||||
@Option(names = ["-a", "--apk"], description = ["Input file to be patched"], required = true)
|
||||
lateinit var inputFile: File
|
||||
|
||||
@Option(names = ["-o", "--out"], description = ["Output file path"], required = true)
|
||||
lateinit var outputPath: String
|
||||
|
||||
@Option(names = ["-e", "--exclude"], description = ["Explicitly exclude patches"])
|
||||
var excludedPatches = arrayOf<String>()
|
||||
|
||||
@Option(names = ["-i", "--include"], description = ["Include patches"])
|
||||
var includedPatches = arrayOf<String>()
|
||||
|
||||
@Option(names = ["-r", "--resource-patcher"], description = ["Disable patching resources"])
|
||||
var disableResourcePatching: Boolean = false
|
||||
|
||||
@Option(names = ["--experimental"], description = ["Disable patch version compatibility patch"])
|
||||
var experimental: Boolean = false
|
||||
|
||||
@Option(names = ["-m", "--merge"], description = ["One or more dex file containers to merge"])
|
||||
var mergeFiles = listOf<File>()
|
||||
|
||||
@Option(names = ["--mount"], description = ["If specified, instead of installing, mount"])
|
||||
var mount: Boolean = false
|
||||
|
||||
@Option(names = ["--cn"], description = ["Overwrite the default CN for the signed file"])
|
||||
var cn = "ReVanced"
|
||||
|
||||
@Option(names = ["--keystore"], description = ["File path to your keystore"])
|
||||
var keystorePath: String? = null
|
||||
|
||||
@Option(names = ["-p", "--password"], description = ["Overwrite the default password for the signed file"])
|
||||
var password = "ReVanced"
|
||||
|
||||
@Option(names = ["-d", "--deploy-on"], description = ["If specified, deploy to adb device with given name"])
|
||||
var deploy: String? = null
|
||||
|
||||
@Option(names = ["-t", "--temp-dir"], description = ["Temporal resource cache directory"])
|
||||
var cacheDirectory = "revanced-cache"
|
||||
|
||||
@Option(
|
||||
names = ["-c", "--clean"],
|
||||
description = ["Clean the temporal resource cache directory. This will be done anyways when running the patcher"]
|
||||
)
|
||||
var clean: Boolean = false
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
if (args.lArgs?.listOnly == true) {
|
||||
for (patchBundlePath in args.patchBundles) for (patch in JarPatchBundle(patchBundlePath).loadPatches()) {
|
||||
logger.info("${patch.patchName}: ${patch.description}")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val args = args.pArgs ?: return
|
||||
|
||||
val patcher = app.revanced.patcher.Patcher(
|
||||
PatcherOptions(
|
||||
args.inputFile,
|
||||
args.cacheDirectory,
|
||||
!args.disableResourcePatching,
|
||||
logger = PatcherLogger
|
||||
)
|
||||
)
|
||||
|
||||
val outputFile = File(args.outputPath)
|
||||
|
||||
val adb: Adb? = args.deploy?.let {
|
||||
Adb(outputFile, patcher.data.packageMetadata.packageName, args.deploy!!, !args.mount)
|
||||
}
|
||||
|
||||
val patchedFile = if (args.mount) outputFile
|
||||
else File(args.cacheDirectory).resolve("${outputFile.nameWithoutExtension}_raw.apk")
|
||||
|
||||
Patcher.start(patcher, patchedFile)
|
||||
|
||||
if (!args.mount) {
|
||||
Signing.start(
|
||||
patchedFile,
|
||||
outputFile,
|
||||
SigningOptions(
|
||||
args.cn,
|
||||
args.password,
|
||||
args.keystorePath ?: outputFile.absoluteFile.parentFile
|
||||
.resolve("${outputFile.nameWithoutExtension}.keystore")
|
||||
.canonicalPath
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (args.clean) File(args.cacheDirectory).deleteRecursively()
|
||||
|
||||
adb?.deploy()
|
||||
|
||||
if (args.clean && args.deploy != null) Files.delete(outputFile.toPath())
|
||||
|
||||
logger.info("Finished")
|
||||
}
|
||||
}
|
||||
private object MainCommand
|
||||
|
||||
62
src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt
Normal file
62
src/main/kotlin/app/revanced/cli/command/OptionsCommand.kt
Normal file
@@ -0,0 +1,62 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.library.Options
|
||||
import app.revanced.library.Options.setOptions
|
||||
import app.revanced.patcher.PatchBundleLoader
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "options",
|
||||
description = ["Generate options file from patches."],
|
||||
)
|
||||
internal object OptionsCommand : Runnable {
|
||||
private val logger = Logger.getLogger(OptionsCommand::class.java.name)
|
||||
|
||||
@CommandLine.Parameters(
|
||||
description = ["Paths to patch bundles."],
|
||||
arity = "1..*",
|
||||
)
|
||||
private lateinit var patchBundles: Array<File>
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-p", "--path"],
|
||||
description = ["Path to patch options JSON file."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var filePath: File = File("options.json")
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-o", "--overwrite"],
|
||||
description = ["Overwrite existing options file."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var overwrite: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-u", "--update"],
|
||||
description = ["Update existing options by adding missing and removing non-existent options."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var update: Boolean = false
|
||||
|
||||
override fun run() =
|
||||
try {
|
||||
PatchBundleLoader.Jar(*patchBundles).let { patches ->
|
||||
val exists = filePath.exists()
|
||||
if (!exists || overwrite) {
|
||||
if (exists && update) patches.setOptions(filePath)
|
||||
|
||||
Options.serialize(patches, prettyPrint = true).let(filePath::writeText)
|
||||
} else {
|
||||
throw OptionsFileAlreadyExistsException()
|
||||
}
|
||||
}
|
||||
} catch (ex: OptionsFileAlreadyExistsException) {
|
||||
logger.severe("Options file already exists, use --overwrite to override it")
|
||||
}
|
||||
|
||||
class OptionsFileAlreadyExistsException : Exception()
|
||||
}
|
||||
410
src/main/kotlin/app/revanced/cli/command/PatchCommand.kt
Normal file
410
src/main/kotlin/app/revanced/cli/command/PatchCommand.kt
Normal file
@@ -0,0 +1,410 @@
|
||||
package app.revanced.cli.command
|
||||
|
||||
import app.revanced.library.ApkUtils
|
||||
import app.revanced.library.ApkUtils.applyTo
|
||||
import app.revanced.library.ApkUtils.sign
|
||||
import app.revanced.library.Options
|
||||
import app.revanced.library.Options.setOptions
|
||||
import app.revanced.library.adb.AdbManager
|
||||
import app.revanced.patcher.PatchBundleLoader
|
||||
import app.revanced.patcher.PatchSet
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.PatcherConfig
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import picocli.CommandLine.Model.CommandSpec
|
||||
import picocli.CommandLine.Spec
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.util.logging.Logger
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "patch",
|
||||
description = ["Patch an APK file."],
|
||||
)
|
||||
internal object PatchCommand : Runnable {
|
||||
private val logger = Logger.getLogger(PatchCommand::class.java.name)
|
||||
|
||||
@Spec
|
||||
lateinit var spec: CommandSpec // injected by picocli
|
||||
|
||||
private lateinit var apk: File
|
||||
|
||||
private var integrations = setOf<File>()
|
||||
|
||||
private var patchBundles = emptySet<File>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-i", "--include"],
|
||||
description = ["List of patches to include."],
|
||||
)
|
||||
private var includedPatches = hashSetOf<String>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--ii"],
|
||||
description = ["List of patches to include by their index in relation to the supplied patch bundles."],
|
||||
)
|
||||
private var includedPatchesByIndex = arrayOf<Int>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-e", "--exclude"],
|
||||
description = ["List of patches to exclude."],
|
||||
)
|
||||
private var excludedPatches = hashSetOf<String>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--ei"],
|
||||
description = ["List of patches to exclude by their index in relation to the supplied patch bundles."],
|
||||
)
|
||||
private var excludedPatchesByIndex = arrayOf<Int>()
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--options"],
|
||||
description = ["Path to patch options JSON file."],
|
||||
)
|
||||
private var optionsFile: File? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--exclusive"],
|
||||
description = ["Only include patches that are explicitly specified to be included."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var exclusive = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-f", "--force"],
|
||||
description = ["Bypass compatibility checks for the supplied APK's version."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var force: Boolean = false
|
||||
|
||||
private var outputFilePath: File? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-o", "--out"],
|
||||
description = ["Path to save the patched APK file to. Defaults to the same directory as the supplied APK file."],
|
||||
)
|
||||
private fun setOutputFilePath(outputFilePath: File?) {
|
||||
this.outputFilePath = outputFilePath?.absoluteFile
|
||||
}
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-d", "--device-serial"],
|
||||
description = ["ADB device serial to install to. If not supplied, the first connected device will be used."],
|
||||
// Empty string to indicate that the first connected device should be used.
|
||||
fallbackValue = "",
|
||||
arity = "0..1",
|
||||
)
|
||||
private var deviceSerial: String? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--mount"],
|
||||
description = ["Install by mounting the patched APK file."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var mount: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--keystore"],
|
||||
description = [
|
||||
"Path to the keystore to sign the patched APK file with. " +
|
||||
"Defaults to the same directory as the supplied APK file.",
|
||||
],
|
||||
)
|
||||
private var keystoreFilePath: File? = null
|
||||
|
||||
// key store password
|
||||
@CommandLine.Option(
|
||||
names = ["--keystore-password"],
|
||||
description = ["The password of the keystore to sign the patched APK file with. Empty password by default."],
|
||||
)
|
||||
private var keyStorePassword: String? = null // Empty password by default
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--alias"],
|
||||
description = ["The alias of the key from the keystore to sign the patched APK file with."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var alias = "ReVanced Key"
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--keystore-entry-password"],
|
||||
description = ["The password of the entry from the keystore for the key to sign the patched APK file with."],
|
||||
)
|
||||
private var password = "" // Empty password by default
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--signer"],
|
||||
description = ["The name of the signer to sign the patched APK file with."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var signer = "ReVanced"
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-t", "--temporary-files-path"],
|
||||
description = ["Path to temporary files directory."],
|
||||
)
|
||||
private var temporaryFilesPath: File? = null
|
||||
|
||||
private var aaptBinaryPath: File? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-p", "--purge"],
|
||||
description = ["Purge the temporary resource cache directory after patching."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var purge: Boolean = false
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-w", "--warn"],
|
||||
description = ["Warn if a patch can not be found in the supplied patch bundles."],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var warn: Boolean = false
|
||||
|
||||
@CommandLine.Parameters(
|
||||
description = ["APK file to be patched."],
|
||||
arity = "1..1",
|
||||
)
|
||||
@Suppress("unused")
|
||||
private fun setApk(apk: File) {
|
||||
if (!apk.exists()) {
|
||||
throw CommandLine.ParameterException(
|
||||
spec.commandLine(),
|
||||
"APK file ${apk.path} does not exist",
|
||||
)
|
||||
}
|
||||
this.apk = apk
|
||||
}
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-m", "--merge"],
|
||||
description = ["One or more DEX files or containers to merge into the APK."],
|
||||
)
|
||||
@Suppress("unused")
|
||||
private fun setIntegrations(integrations: Array<File>) {
|
||||
integrations.firstOrNull { !it.exists() }?.let {
|
||||
throw CommandLine.ParameterException(spec.commandLine(), "Integrations file ${it.path} does not exist.")
|
||||
}
|
||||
this.integrations += integrations
|
||||
}
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["-b", "--patch-bundle"],
|
||||
description = ["One or more bundles of patches."],
|
||||
required = true,
|
||||
)
|
||||
@Suppress("unused")
|
||||
private fun setPatchBundles(patchBundles: Set<File>) {
|
||||
patchBundles.firstOrNull { !it.exists() }?.let {
|
||||
throw CommandLine.ParameterException(spec.commandLine(), "Patch bundle ${it.name} does not exist")
|
||||
}
|
||||
this.patchBundles = patchBundles
|
||||
}
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--custom-aapt2-binary"],
|
||||
description = ["Path to a custom AAPT binary to compile resources with."],
|
||||
)
|
||||
@Suppress("unused")
|
||||
private fun setAaptBinaryPath(aaptBinaryPath: File) {
|
||||
if (!aaptBinaryPath.exists()) {
|
||||
throw CommandLine.ParameterException(
|
||||
spec.commandLine(),
|
||||
"AAPT binary ${aaptBinaryPath.name} does not exist",
|
||||
)
|
||||
}
|
||||
this.aaptBinaryPath = aaptBinaryPath
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
// region Setup
|
||||
|
||||
val outputFilePath =
|
||||
outputFilePath ?: File("").absoluteFile.resolve(
|
||||
"${apk.nameWithoutExtension}-patched.${apk.extension}",
|
||||
)
|
||||
|
||||
val temporaryFilesPath =
|
||||
temporaryFilesPath ?: outputFilePath.parentFile.resolve(
|
||||
"${outputFilePath.nameWithoutExtension}-temporary-files",
|
||||
)
|
||||
|
||||
val optionsFile =
|
||||
optionsFile ?: outputFilePath.parentFile.resolve(
|
||||
"${outputFilePath.nameWithoutExtension}-options.json",
|
||||
)
|
||||
|
||||
val keystoreFilePath =
|
||||
keystoreFilePath ?: outputFilePath.parentFile
|
||||
.resolve("${outputFilePath.nameWithoutExtension}.keystore")
|
||||
|
||||
// endregion
|
||||
|
||||
// region Load patches
|
||||
|
||||
logger.info("Loading patches")
|
||||
|
||||
val patches = PatchBundleLoader.Jar(*patchBundles.toTypedArray())
|
||||
|
||||
// Warn if a patch can not be found in the supplied patch bundles.
|
||||
if (warn) {
|
||||
patches.map { it.name }.toHashSet().let { availableNames ->
|
||||
(includedPatches + excludedPatches).filter { name ->
|
||||
!availableNames.contains(name)
|
||||
}
|
||||
}.let { unknownPatches ->
|
||||
if (unknownPatches.isEmpty()) return@let
|
||||
logger.warning("Unknown input of patches:\n${unknownPatches.joinToString("\n")}")
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
val patcherTemporaryFilesPath = temporaryFilesPath.resolve("patcher")
|
||||
val (packageName, patcherResult) = Patcher(
|
||||
PatcherConfig(
|
||||
apk,
|
||||
patcherTemporaryFilesPath,
|
||||
aaptBinaryPath?.path,
|
||||
patcherTemporaryFilesPath.absolutePath,
|
||||
true,
|
||||
),
|
||||
).use { patcher ->
|
||||
val filteredPatches =
|
||||
patcher.filterPatchSelection(patches).also { patches ->
|
||||
logger.info("Setting patch options")
|
||||
|
||||
if (optionsFile.exists()) {
|
||||
patches.setOptions(optionsFile)
|
||||
} else {
|
||||
Options.serialize(patches, prettyPrint = true).let(optionsFile::writeText)
|
||||
}
|
||||
}
|
||||
|
||||
// region Patch
|
||||
|
||||
patcher.context.packageMetadata.packageName to patcher.apply {
|
||||
acceptIntegrations(integrations)
|
||||
acceptPatches(filteredPatches)
|
||||
|
||||
// Execute patches.
|
||||
runBlocking {
|
||||
apply(false).collect { patchResult ->
|
||||
patchResult.exception?.let {
|
||||
StringWriter().use { writer ->
|
||||
it.printStackTrace(PrintWriter(writer))
|
||||
logger.severe("${patchResult.patch.name} failed:\n$writer")
|
||||
}
|
||||
} ?: logger.info("${patchResult.patch.name} succeeded")
|
||||
}
|
||||
}
|
||||
}.get()
|
||||
// endregion
|
||||
}
|
||||
|
||||
// region Save
|
||||
apk.copyTo(temporaryFilesPath.resolve(apk.name), overwrite = true).apply {
|
||||
patcherResult.applyTo(this)
|
||||
}.let {
|
||||
if (!mount) {
|
||||
sign(
|
||||
it,
|
||||
outputFilePath,
|
||||
ApkUtils.SigningOptions(
|
||||
keystoreFilePath,
|
||||
keyStorePassword,
|
||||
alias,
|
||||
password,
|
||||
signer,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Saved to $outputFilePath")
|
||||
|
||||
// endregion
|
||||
|
||||
// region Install
|
||||
|
||||
deviceSerial?.let { serial ->
|
||||
AdbManager.getAdbManager(deviceSerial = serial.ifEmpty { null }, mount)
|
||||
}?.install(AdbManager.Apk(outputFilePath, packageName))
|
||||
|
||||
// endregion
|
||||
|
||||
if (purge) {
|
||||
logger.info("Purging temporary files")
|
||||
purge(temporaryFilesPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the patches to be added to the patcher. The filter is based on the following:
|
||||
*
|
||||
* @param patches The patches to filter.
|
||||
* @return The filtered patches.
|
||||
*/
|
||||
private fun Patcher.filterPatchSelection(patches: PatchSet): PatchSet =
|
||||
buildSet {
|
||||
val packageName = context.packageMetadata.packageName
|
||||
val packageVersion = context.packageMetadata.packageVersion
|
||||
|
||||
patches.withIndex().forEach patch@{ (i, patch) ->
|
||||
val patchName = patch.name!!
|
||||
|
||||
val explicitlyExcluded = excludedPatches.contains(patchName) || excludedPatchesByIndex.contains(i)
|
||||
if (explicitlyExcluded) return@patch logger.info("Excluding $patchName")
|
||||
|
||||
// Make sure the patch is compatible with the supplied APK files package name and version.
|
||||
patch.compatiblePackages?.let { packages ->
|
||||
packages.singleOrNull { it.name == packageName }?.let { `package` ->
|
||||
val matchesVersion =
|
||||
force || `package`.versions?.let {
|
||||
it.any { version -> version == packageVersion }
|
||||
} ?: true
|
||||
|
||||
if (!matchesVersion) {
|
||||
return@patch logger.warning(
|
||||
"$patchName is incompatible with version $packageVersion. " +
|
||||
"This patch is only compatible with version " +
|
||||
packages.joinToString(";") { pkg ->
|
||||
pkg.versions!!.joinToString(", ")
|
||||
},
|
||||
)
|
||||
}
|
||||
} ?: return@patch logger.fine(
|
||||
"$patchName is incompatible with $packageName. " +
|
||||
"This patch is only compatible with " +
|
||||
packages.joinToString(", ") { `package` -> `package`.name },
|
||||
)
|
||||
|
||||
return@let
|
||||
} ?: logger.fine("$patchName has no constraint on packages.")
|
||||
|
||||
// If the patch is implicitly used, it will be only included if [exclusive] is false.
|
||||
val implicitlyIncluded = !exclusive && patch.use
|
||||
// If the patch is explicitly used, it will be included even if [exclusive] is false.
|
||||
val explicitlyIncluded = includedPatches.contains(patchName) || includedPatchesByIndex.contains(i)
|
||||
|
||||
val included = implicitlyIncluded || explicitlyIncluded
|
||||
if (!included) return@patch logger.info("$patchName excluded") // Case 1.
|
||||
|
||||
logger.fine("Adding $patchName")
|
||||
|
||||
add(patch)
|
||||
}
|
||||
}
|
||||
|
||||
private fun purge(resourceCachePath: File) {
|
||||
val result =
|
||||
if (resourceCachePath.deleteRecursively()) {
|
||||
"Purged resource cache directory"
|
||||
} else {
|
||||
"Failed to purge resource cache directory"
|
||||
}
|
||||
logger.info(result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package app.revanced.cli.command.utility
|
||||
|
||||
import app.revanced.library.adb.AdbManager
|
||||
import picocli.CommandLine.*
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
@Command(
|
||||
name = "install",
|
||||
description = ["Install an APK file to devices with the supplied ADB device serials"],
|
||||
)
|
||||
internal object InstallCommand : Runnable {
|
||||
private val logger = Logger.getLogger(InstallCommand::class.java.name)
|
||||
|
||||
@Parameters(
|
||||
description = ["ADB device serials. If not supplied, the first connected device will be used."],
|
||||
arity = "0..*",
|
||||
)
|
||||
private var deviceSerials: Array<String>? = null
|
||||
|
||||
@Option(
|
||||
names = ["-a", "--apk"],
|
||||
description = ["APK file to be installed"],
|
||||
required = true,
|
||||
)
|
||||
private lateinit var apk: File
|
||||
|
||||
@Option(
|
||||
names = ["-m", "--mount"],
|
||||
description = ["Mount the supplied APK file over the app with the supplied package name"],
|
||||
)
|
||||
private var packageName: String? = null
|
||||
|
||||
override fun run() {
|
||||
fun install(deviceSerial: String? = null) =
|
||||
try {
|
||||
AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName))
|
||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||
logger.severe(e.toString())
|
||||
}
|
||||
|
||||
deviceSerials?.forEach(::install) ?: install()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package app.revanced.cli.command.utility
|
||||
|
||||
import app.revanced.library.adb.AdbManager
|
||||
import picocli.CommandLine.*
|
||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||
import java.util.logging.Logger
|
||||
|
||||
@Command(
|
||||
name = "uninstall",
|
||||
description = ["Uninstall a patched app from the devices with the supplied ADB device serials"],
|
||||
)
|
||||
internal object UninstallCommand : Runnable {
|
||||
private val logger = Logger.getLogger(UninstallCommand::class.java.name)
|
||||
|
||||
@Parameters(
|
||||
description = ["ADB device serials. If not supplied, the first connected device will be used."],
|
||||
arity = "0..*",
|
||||
)
|
||||
private var deviceSerials: Array<String>? = null
|
||||
|
||||
@Option(
|
||||
names = ["-p", "--package-name"],
|
||||
description = ["Package name of the app to uninstall"],
|
||||
required = true,
|
||||
)
|
||||
private lateinit var packageName: String
|
||||
|
||||
@Option(
|
||||
names = ["-u", "--unmount"],
|
||||
description = ["Uninstall by unmounting the patched APK file"],
|
||||
showDefaultValue = ALWAYS,
|
||||
)
|
||||
private var unmount: Boolean = false
|
||||
|
||||
override fun run() {
|
||||
fun uninstall(deviceSerial: String? = null) =
|
||||
try {
|
||||
AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName)
|
||||
} catch (e: AdbManager.DeviceNotFoundException) {
|
||||
logger.severe(e.toString())
|
||||
}
|
||||
|
||||
deviceSerials?.forEach { uninstall(it) } ?: uninstall()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package app.revanced.cli.command.utility
|
||||
|
||||
import picocli.CommandLine
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "utility",
|
||||
description = ["Commands for utility purposes."],
|
||||
subcommands = [InstallCommand::class, UninstallCommand::class],
|
||||
)
|
||||
internal object UtilityCommand
|
||||
@@ -1,8 +0,0 @@
|
||||
package app.revanced.cli.logging
|
||||
|
||||
internal interface CliLogger {
|
||||
fun error(msg: String)
|
||||
fun info(msg: String)
|
||||
fun trace(msg: String)
|
||||
fun warn(msg: String)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package app.revanced.cli.logging.impl
|
||||
|
||||
import app.revanced.cli.command.MainCommand
|
||||
import app.revanced.cli.logging.CliLogger
|
||||
import java.util.logging.Logger
|
||||
|
||||
internal class DefaultCliLogger(
|
||||
private val logger: Logger = Logger.getLogger(MainCommand::javaClass.name)
|
||||
) : CliLogger {
|
||||
companion object {
|
||||
init {
|
||||
System.setProperty("java.util.logging.SimpleFormatter.format", "%4\$s: %5\$s %n")
|
||||
}
|
||||
}
|
||||
|
||||
override fun error(msg: String) = logger.severe(msg)
|
||||
override fun info(msg: String) = logger.info(msg)
|
||||
override fun trace(msg: String) = logger.finest(msg)
|
||||
override fun warn(msg: String) = logger.warning(msg)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package app.revanced.cli.main
|
||||
|
||||
import app.revanced.cli.command.MainCommand
|
||||
import picocli.CommandLine
|
||||
|
||||
internal fun main(args: Array<String>) {
|
||||
CommandLine(MainCommand).execute(*args)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package app.revanced.cli.patcher
|
||||
|
||||
import app.revanced.cli.command.MainCommand.args
|
||||
import app.revanced.cli.command.MainCommand.logger
|
||||
import app.revanced.utils.filesystem.ZipFileSystemUtils
|
||||
import app.revanced.utils.patcher.addPatchesFiltered
|
||||
import app.revanced.utils.patcher.applyPatchesVerbose
|
||||
import app.revanced.utils.patcher.mergeFiles
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
internal object Patcher {
|
||||
internal fun start(patcher: app.revanced.patcher.Patcher, output: File) {
|
||||
val args = args.pArgs!!
|
||||
|
||||
// merge files like necessary integrations
|
||||
patcher.mergeFiles()
|
||||
// add patches, but filter incompatible or excluded patches
|
||||
patcher.addPatchesFiltered(excludePatches = args.excludedPatches.isNotEmpty())
|
||||
// apply patches
|
||||
patcher.applyPatchesVerbose()
|
||||
|
||||
// write output file
|
||||
if (output.exists()) Files.delete(output.toPath())
|
||||
args.inputFile.copyTo(output)
|
||||
|
||||
val result = patcher.save()
|
||||
ZipFileSystemUtils(output).use { outputFileSystem ->
|
||||
// replace all dex files
|
||||
result.dexFiles.forEach {
|
||||
logger.info("Writing dex file ${it.name}")
|
||||
outputFileSystem.write(it.name, it.dexFileInputStream.readAllBytes())
|
||||
}
|
||||
|
||||
if (!args.disableResourcePatching) {
|
||||
logger.info("Writing resources...")
|
||||
|
||||
ZipFileSystemUtils(result.resourceFile!!).use { resourceFileSystem ->
|
||||
val resourceFiles = resourceFileSystem.getFile(File.separator)
|
||||
outputFileSystem.writePathRecursively(resourceFiles)
|
||||
}
|
||||
}
|
||||
|
||||
outputFileSystem.uncompress(*result.doNotCompress!!.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.cli.patcher.logging.impl
|
||||
|
||||
import app.revanced.cli.logging.impl.DefaultCliLogger
|
||||
import java.util.logging.Logger
|
||||
|
||||
internal object PatcherLogger : app.revanced.patcher.logging.Logger{
|
||||
private val logger = DefaultCliLogger(Logger.getLogger(app.revanced.patcher.Patcher::javaClass.name))
|
||||
|
||||
override fun error(msg: String) = logger.error(msg)
|
||||
override fun info(msg: String) = logger.info(msg)
|
||||
override fun warn(msg: String)= logger.warn(msg)
|
||||
override fun trace(msg: String)= logger.trace(msg)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package app.revanced.cli.signing
|
||||
|
||||
import app.revanced.cli.command.MainCommand.args
|
||||
import app.revanced.cli.command.MainCommand.logger
|
||||
import app.revanced.utils.signing.Signer
|
||||
import app.revanced.utils.signing.align.ZipAligner
|
||||
import java.io.File
|
||||
|
||||
object Signing {
|
||||
fun start(inputFile: File, outputFile: File, signingOptions: SigningOptions) {
|
||||
val cacheDirectory = File(args.pArgs!!.cacheDirectory)
|
||||
val alignedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk")
|
||||
val signedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_signed.apk")
|
||||
|
||||
// align the inputFile and write to alignedOutput
|
||||
logger.info("Aligning ${inputFile.name}")
|
||||
ZipAligner.align(inputFile, alignedOutput)
|
||||
// sign the alignedOutput and write to signedOutput
|
||||
// the reason is, in case the signer fails
|
||||
// it does not damage the output file
|
||||
logger.info("Signing ${alignedOutput.name}")
|
||||
Signer(signingOptions).signApk(alignedOutput, signedOutput)
|
||||
|
||||
// afterwards copy over the file to the output
|
||||
logger.info("Copying ${signedOutput.name} to ${outputFile.name}")
|
||||
signedOutput.copyTo(outputFile, true)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package app.revanced.cli.signing
|
||||
|
||||
data class SigningOptions(
|
||||
val cn: String,
|
||||
val password: String,
|
||||
val keyStoreFilePath: String
|
||||
)
|
||||
@@ -1,106 +0,0 @@
|
||||
package app.revanced.utils.adb
|
||||
|
||||
import app.revanced.cli.command.MainCommand.logger
|
||||
import se.vidstige.jadb.JadbConnection
|
||||
import se.vidstige.jadb.JadbDevice
|
||||
import se.vidstige.jadb.managers.PackageManager
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
internal class Adb(
|
||||
private val file: File,
|
||||
private val packageName: String,
|
||||
deviceName: String,
|
||||
private val modeInstall: Boolean = false,
|
||||
private val logging: Boolean = true
|
||||
) {
|
||||
private val device: JadbDevice
|
||||
|
||||
init {
|
||||
device = JadbConnection().devices.find { it.serial == deviceName }
|
||||
?: throw IllegalArgumentException("No such device with name $deviceName")
|
||||
|
||||
if (!modeInstall && device.run("su -h", false) != 0)
|
||||
throw IllegalArgumentException("Root required on $deviceName. Deploying failed")
|
||||
}
|
||||
|
||||
private fun String.replacePlaceholder(with: String? = null): String {
|
||||
return this.replace(Constants.PLACEHOLDER, with ?: packageName)
|
||||
}
|
||||
|
||||
internal fun deploy() {
|
||||
if (modeInstall) {
|
||||
logger.info("Installing without mounting")
|
||||
|
||||
PackageManager(device).install(file)
|
||||
} else {
|
||||
logger.info("Installing by mounting")
|
||||
|
||||
// push patched file
|
||||
device.copy(Constants.PATH_INIT_PUSH, file)
|
||||
|
||||
// create revanced path
|
||||
device.run("${Constants.COMMAND_CREATE_DIR} ${Constants.PATH_REVANCED}")
|
||||
|
||||
// prepare mounting the apk
|
||||
device.run(Constants.COMMAND_PREPARE_MOUNT_APK.replacePlaceholder())
|
||||
|
||||
// push mount script
|
||||
device.createFile(
|
||||
Constants.PATH_INIT_PUSH,
|
||||
Constants.CONTENT_MOUNT_SCRIPT.replacePlaceholder()
|
||||
)
|
||||
// install mount script
|
||||
device.run(Constants.COMMAND_INSTALL_MOUNT.replacePlaceholder())
|
||||
|
||||
// push umount script
|
||||
device.createFile(
|
||||
Constants.PATH_INIT_PUSH,
|
||||
Constants.CONTENT_UMOUNT_SCRIPT.replacePlaceholder()
|
||||
)
|
||||
// install unmount script
|
||||
device.run(Constants.COMMAND_INSTALL_UMOUNT.replacePlaceholder())
|
||||
|
||||
// unmount the apk for sanity
|
||||
device.run(Constants.PATH_UMOUNT.replacePlaceholder())
|
||||
// mount the apk
|
||||
device.run(Constants.PATH_MOUNT.replacePlaceholder())
|
||||
|
||||
// relaunch app
|
||||
device.run(Constants.COMMAND_RESTART.replacePlaceholder())
|
||||
|
||||
// log the app
|
||||
log()
|
||||
}
|
||||
}
|
||||
|
||||
private fun log() {
|
||||
val executor = Executors.newSingleThreadExecutor()
|
||||
val pipe = if (logging) {
|
||||
ProcessBuilder.Redirect.INHERIT
|
||||
} else {
|
||||
ProcessBuilder.Redirect.PIPE
|
||||
}
|
||||
|
||||
val process = device.buildCommand(Constants.COMMAND_LOGCAT.replacePlaceholder())
|
||||
.redirectOutput(pipe)
|
||||
.redirectError(pipe)
|
||||
.useExecutor(executor)
|
||||
.start()
|
||||
|
||||
Thread.sleep(500) // give the app some time to start up.
|
||||
while (true) {
|
||||
try {
|
||||
while (device.run("${Constants.COMMAND_PID_OF} $packageName") == 0) {
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("An error occurred while monitoring the state of app", e)
|
||||
}
|
||||
}
|
||||
logger.info("Stopped logging because the app was closed")
|
||||
process.destroy()
|
||||
executor.shutdown()
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package app.revanced.utils.adb
|
||||
|
||||
import se.vidstige.jadb.JadbDevice
|
||||
import se.vidstige.jadb.RemoteFile
|
||||
import se.vidstige.jadb.ShellProcessBuilder
|
||||
import java.io.File
|
||||
|
||||
internal fun JadbDevice.buildCommand(command: String, su: Boolean = true): ShellProcessBuilder {
|
||||
if (su) {
|
||||
return shellProcessBuilder("su -c \'$command\'")
|
||||
}
|
||||
|
||||
val args = command.split(" ") as ArrayList<String>
|
||||
val cmd = args.removeFirst()
|
||||
|
||||
return shellProcessBuilder(cmd, *args.toTypedArray())
|
||||
}
|
||||
|
||||
internal fun JadbDevice.run(command: String, su: Boolean = true): Int {
|
||||
return this.buildCommand(command, su).start().waitFor()
|
||||
}
|
||||
|
||||
internal fun JadbDevice.copy(targetPath: String, file: File) {
|
||||
push(file, RemoteFile(targetPath))
|
||||
}
|
||||
|
||||
internal fun JadbDevice.createFile(targetFile: String, content: String) {
|
||||
push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile))
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package app.revanced.utils.adb
|
||||
|
||||
internal object Constants {
|
||||
// template placeholder to replace a string in commands
|
||||
internal const val PLACEHOLDER = "TEMPLATE_PACKAGE_NAME"
|
||||
|
||||
// utility commands
|
||||
private const val COMMAND_CHMOD_MOUNT = "chmod +x"
|
||||
internal const val COMMAND_PID_OF = "pidof -s"
|
||||
internal const val COMMAND_CREATE_DIR = "mkdir -p"
|
||||
internal const val COMMAND_LOGCAT = "logcat -c && logcat | grep AndroidRuntime"
|
||||
internal const val COMMAND_RESTART = "monkey -p $PLACEHOLDER 1 && kill ${'$'}($COMMAND_PID_OF $PLACEHOLDER)"
|
||||
|
||||
// default mount file name
|
||||
private const val NAME_MOUNT_SCRIPT = "mount_revanced_$PLACEHOLDER.sh"
|
||||
|
||||
// initial directory to push files to via adb push
|
||||
internal const val PATH_INIT_PUSH = "/data/local/tmp/revanced.delete"
|
||||
|
||||
// revanced path
|
||||
internal const val PATH_REVANCED = "/data/adb/revanced/"
|
||||
|
||||
// revanced apk path
|
||||
private const val PATH_REVANCED_APP = "$PATH_REVANCED$PLACEHOLDER.apk"
|
||||
|
||||
// (un)mount script paths
|
||||
internal const val PATH_MOUNT = "/data/adb/service.d/$NAME_MOUNT_SCRIPT"
|
||||
internal const val PATH_UMOUNT = "/data/adb/post-fs-data.d/un$NAME_MOUNT_SCRIPT"
|
||||
|
||||
// move to revanced apk path & set permissions
|
||||
internal const val COMMAND_PREPARE_MOUNT_APK =
|
||||
"base_path=\"$PATH_REVANCED_APP\" && mv $PATH_INIT_PUSH ${'$'}base_path && chmod 644 ${'$'}base_path && chown system:system ${'$'}base_path && chcon u:object_r:apk_data_file:s0 ${'$'}base_path"
|
||||
|
||||
// install mount script & set permissions
|
||||
internal const val COMMAND_INSTALL_MOUNT = "mv $PATH_INIT_PUSH $PATH_MOUNT && $COMMAND_CHMOD_MOUNT $PATH_MOUNT"
|
||||
|
||||
// install umount script & set permissions
|
||||
internal const val COMMAND_INSTALL_UMOUNT = "mv $PATH_INIT_PUSH $PATH_UMOUNT && $COMMAND_CHMOD_MOUNT $PATH_UMOUNT"
|
||||
|
||||
// unmount script
|
||||
internal val CONTENT_UMOUNT_SCRIPT =
|
||||
"""
|
||||
#!/system/bin/sh
|
||||
|
||||
stock_path=${'$'}( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
|
||||
umount -l ${'$'}stock_path
|
||||
""".trimIndent()
|
||||
|
||||
// mount script
|
||||
internal val CONTENT_MOUNT_SCRIPT =
|
||||
"""
|
||||
#!/system/bin/sh
|
||||
while [ "${'$'}(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
|
||||
|
||||
base_path="$PATH_REVANCED_APP"
|
||||
stock_path=${'$'}( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
|
||||
|
||||
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
|
||||
mount -o bind ${'$'}base_path ${'$'}stock_path
|
||||
""".trimIndent()
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package app.revanced.utils.filesystem
|
||||
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.zip.ZipEntry
|
||||
|
||||
internal class ZipFileSystemUtils(
|
||||
file: File
|
||||
) : Closeable {
|
||||
private var zipFileSystem = FileSystems.newFileSystem(file.toPath(), mapOf("noCompression" to true))
|
||||
|
||||
private fun Path.deleteRecursively() {
|
||||
if (!Files.exists(this)) {
|
||||
throw IllegalStateException("File exists in real folder but not in zip file system")
|
||||
}
|
||||
|
||||
if (Files.isDirectory(this)) {
|
||||
Files.list(this).forEach { path ->
|
||||
path.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
Files.delete(this)
|
||||
}
|
||||
|
||||
internal fun getFile(path: String) = zipFileSystem.getPath(path)
|
||||
|
||||
internal fun writePathRecursively(path: Path) {
|
||||
Files.list(path).use { fileStream ->
|
||||
fileStream.forEach { filePath ->
|
||||
val fileSystemPath = filePath.getRelativePath(path)
|
||||
fileSystemPath.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
Files.walk(path).use { fileStream ->
|
||||
// don't include build directory
|
||||
// by skipping the root node.
|
||||
fileStream.skip(1).forEach { filePath ->
|
||||
val relativePath = filePath.getRelativePath(path)
|
||||
|
||||
if (Files.isDirectory(filePath)) {
|
||||
Files.createDirectory(relativePath)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
Files.copy(filePath, relativePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun write(path: String, content: ByteArray) = Files.write(zipFileSystem.getPath(path), content)
|
||||
|
||||
private fun Path.getRelativePath(path: Path): Path = zipFileSystem.getPath(path.relativize(this).toString())
|
||||
|
||||
// TODO: figure out why the file system is uncompressed by default and how to fix it
|
||||
internal fun uncompress(vararg paths: String) =
|
||||
paths.forEach { Files.setAttribute(zipFileSystem.getPath(it), "zip:method", ZipEntry.STORED) }
|
||||
|
||||
override fun close() = zipFileSystem.close()
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package app.revanced.utils.patcher
|
||||
|
||||
import app.revanced.cli.command.MainCommand
|
||||
import app.revanced.cli.command.MainCommand.args
|
||||
import app.revanced.cli.command.MainCommand.logger
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||
import app.revanced.patcher.extensions.PatchExtensions.include
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.util.patch.implementation.JarPatchBundle
|
||||
|
||||
fun Patcher.addPatchesFiltered(
|
||||
excludePatches: Boolean = false
|
||||
) {
|
||||
val packageName = this.data.packageMetadata.packageName
|
||||
val packageVersion = this.data.packageMetadata.packageVersion
|
||||
|
||||
args.patchBundles.forEach { bundle ->
|
||||
val includedPatches = mutableListOf<Class<out Patch<Data>>>()
|
||||
JarPatchBundle(bundle).loadPatches().forEach patch@{ patch ->
|
||||
val compatiblePackages = patch.compatiblePackages
|
||||
val patchName = patch.patchName
|
||||
|
||||
val prefix = "Skipping $patchName"
|
||||
|
||||
val args = MainCommand.args.pArgs!!
|
||||
|
||||
if (excludePatches && args.excludedPatches.contains(patchName)) {
|
||||
logger.info("$prefix: Explicitly excluded")
|
||||
return@patch
|
||||
} else if (!patch.include && !args.includedPatches.contains(patchName)) {
|
||||
logger.info("$prefix: Explicitly excluded")
|
||||
return@patch
|
||||
}
|
||||
|
||||
if (compatiblePackages == null) logger.warn("$prefix: Missing compatibility annotation. Continuing.")
|
||||
else {
|
||||
if (!compatiblePackages.any { it.name == packageName }) {
|
||||
logger.warn("$prefix: Incompatible with $packageName. This patch is only compatible with ${
|
||||
compatiblePackages.joinToString(
|
||||
", "
|
||||
) { it.name }
|
||||
}")
|
||||
return@patch
|
||||
}
|
||||
|
||||
if (!(args.experimental || compatiblePackages.any { it.versions.isEmpty() || it.versions.any { version -> version == packageVersion } })) {
|
||||
val compatibleWith = compatiblePackages.map { _package ->
|
||||
"${_package.name}: ${_package.versions.joinToString(", ")}"
|
||||
}.joinToString(";")
|
||||
logger.warn("$prefix: Incompatible with version $packageVersion. This patch is only compatible with version $compatibleWith")
|
||||
return@patch
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace("Adding $patchName")
|
||||
includedPatches.add(patch)
|
||||
}
|
||||
this.addPatches(includedPatches)
|
||||
}
|
||||
}
|
||||
|
||||
fun Patcher.applyPatchesVerbose() {
|
||||
this.applyPatches().forEach { (patch, result) ->
|
||||
if (result.isSuccess) {
|
||||
logger.info("$patch succeeded")
|
||||
return@forEach
|
||||
}
|
||||
logger.error("$patch failed:")
|
||||
result.exceptionOrNull()!!.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun Patcher.mergeFiles() {
|
||||
this.addFiles(args.pArgs!!.mergeFiles) { file ->
|
||||
logger.info("Merging $file")
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package app.revanced.utils.signing
|
||||
|
||||
import app.revanced.cli.command.MainCommand.logger
|
||||
import app.revanced.cli.signing.SigningOptions
|
||||
import com.android.apksig.ApkSigner
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
|
||||
internal class Signer(
|
||||
private val signingOptions: SigningOptions
|
||||
) {
|
||||
private val passwordCharArray = signingOptions.password.toCharArray()
|
||||
private fun newKeystore(out: File) {
|
||||
val (publicKey, privateKey) = createKey()
|
||||
val privateKS = KeyStore.getInstance("BKS", "BC")
|
||||
privateKS.load(null, passwordCharArray)
|
||||
privateKS.setKeyEntry("alias", privateKey, passwordCharArray, arrayOf(publicKey))
|
||||
privateKS.store(FileOutputStream(out), passwordCharArray)
|
||||
}
|
||||
|
||||
private fun createKey(): Pair<X509Certificate, PrivateKey> {
|
||||
val gen = KeyPairGenerator.getInstance("RSA")
|
||||
gen.initialize(2048)
|
||||
val pair = gen.generateKeyPair()
|
||||
var serialNumber: BigInteger
|
||||
do serialNumber = BigInteger.valueOf(SecureRandom().nextLong()) while (serialNumber < BigInteger.ZERO)
|
||||
val x500Name = X500Name("CN=${signingOptions.cn}")
|
||||
val builder = X509v3CertificateBuilder(
|
||||
x500Name,
|
||||
serialNumber,
|
||||
Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L),
|
||||
Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * 30L),
|
||||
Locale.ENGLISH,
|
||||
x500Name,
|
||||
SubjectPublicKeyInfo.getInstance(pair.public.encoded)
|
||||
)
|
||||
val signer: ContentSigner = JcaContentSignerBuilder("SHA256withRSA").build(pair.private)
|
||||
return JcaX509CertificateConverter().getCertificate(builder.build(signer)) to pair.private
|
||||
}
|
||||
|
||||
fun signApk(input: File, output: File) {
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
|
||||
// TODO: keystore should be saved securely
|
||||
val ks = File(signingOptions.keyStoreFilePath)
|
||||
if (!ks.exists()) newKeystore(ks) else {
|
||||
logger.info("Found existing keystore: ${ks.nameWithoutExtension}")
|
||||
}
|
||||
|
||||
val keyStore = KeyStore.getInstance("BKS", "BC")
|
||||
FileInputStream(ks).use { fis -> keyStore.load(fis, null) }
|
||||
val alias = keyStore.aliases().nextElement()
|
||||
|
||||
val config = ApkSigner.SignerConfig.Builder(
|
||||
signingOptions.cn,
|
||||
keyStore.getKey(alias, passwordCharArray) as PrivateKey,
|
||||
listOf(keyStore.getCertificate(alias) as X509Certificate)
|
||||
).build()
|
||||
|
||||
val signer = ApkSigner.Builder(listOf(config))
|
||||
signer.setCreatedBy(signingOptions.cn)
|
||||
signer.setInputApk(input)
|
||||
signer.setOutputApk(output)
|
||||
|
||||
signer.build().sign()
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package app.revanced.utils.signing.align
|
||||
|
||||
import app.revanced.utils.signing.align.stream.MultiOutputStream
|
||||
import app.revanced.utils.signing.align.stream.PeekingFakeStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
internal object ZipAligner {
|
||||
fun align(input: File, output: File, alignment: Int = 4) {
|
||||
val zipFile = ZipFile(input)
|
||||
|
||||
val entries: Enumeration<out ZipEntry?> = zipFile.entries()
|
||||
|
||||
// fake
|
||||
val peekingFakeStream = PeekingFakeStream()
|
||||
val fakeOutputStream = ZipOutputStream(peekingFakeStream)
|
||||
// real
|
||||
val zipOutputStream = ZipOutputStream(BufferedOutputStream(output.outputStream()))
|
||||
|
||||
val multiOutputStream = MultiOutputStream(
|
||||
listOf(
|
||||
fakeOutputStream, // fake, used to add the data to the fake stream
|
||||
zipOutputStream // real
|
||||
)
|
||||
)
|
||||
|
||||
var bias = 0
|
||||
while (entries.hasMoreElements()) {
|
||||
var padding = 0
|
||||
|
||||
val entry: ZipEntry = entries.nextElement()!!
|
||||
// fake, used to calculate the file offset of the entry
|
||||
fakeOutputStream.putNextEntry(entry)
|
||||
|
||||
if (entry.size == entry.compressedSize) {
|
||||
val fileOffset = peekingFakeStream.peek()
|
||||
val newOffset = fileOffset + bias
|
||||
padding = ((alignment - (newOffset % alignment)) % alignment).toInt()
|
||||
|
||||
// real
|
||||
entry.extra = if (entry.extra == null) ByteArray(padding)
|
||||
else Arrays.copyOf(entry.extra, entry.extra.size + padding)
|
||||
}
|
||||
|
||||
zipOutputStream.putNextEntry(entry)
|
||||
zipFile.getInputStream(entry).copyTo(multiOutputStream)
|
||||
|
||||
// fake, used to add remaining bytes
|
||||
fakeOutputStream.closeEntry()
|
||||
// real
|
||||
zipOutputStream.closeEntry()
|
||||
|
||||
bias += padding
|
||||
}
|
||||
|
||||
zipFile.close()
|
||||
zipOutputStream.close()
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package app.revanced.utils.signing.align.stream
|
||||
|
||||
import java.io.OutputStream
|
||||
|
||||
internal class MultiOutputStream(
|
||||
private val streams: Iterable<OutputStream>,
|
||||
) : OutputStream() {
|
||||
override fun write(b: ByteArray, off: Int, len: Int) = streams.forEach {
|
||||
it.write(b, off, len)
|
||||
}
|
||||
|
||||
override fun write(b: ByteArray) = streams.forEach {
|
||||
it.write(b)
|
||||
}
|
||||
|
||||
override fun write(b: Int) = streams.forEach {
|
||||
it.write(b)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package app.revanced.utils.signing.align.stream
|
||||
|
||||
import java.io.OutputStream
|
||||
|
||||
internal class PeekingFakeStream : OutputStream() {
|
||||
private var numberOfBytes: Long = 0
|
||||
|
||||
fun peek() = numberOfBytes
|
||||
|
||||
override fun write(b: Int) {
|
||||
numberOfBytes++
|
||||
}
|
||||
|
||||
override fun write(b: ByteArray) {
|
||||
numberOfBytes += b.size
|
||||
}
|
||||
|
||||
override fun write(b: ByteArray, offset: Int, len: Int) {
|
||||
numberOfBytes += len
|
||||
}
|
||||
}
|
||||
1
src/main/resources/app/revanced/cli/version.properties
Normal file
1
src/main/resources/app/revanced/cli/version.properties
Normal file
@@ -0,0 +1 @@
|
||||
version=${projectVersion}
|
||||
Reference in New Issue
Block a user