Compare commits
683 Commits
v0.0.49
...
docs/readm
Author | SHA1 | Date | |
---|---|---|---|
a5d09b2bf5 | |||
8731e7267a | |||
c2a942b46c | |||
b9401b5f89 | |||
0eee869b99 | |||
15cfeb6ba7 | |||
d47819cae7 | |||
351e00c380 | |||
046179db1b | |||
5993dc34cb | |||
91f6672711 | |||
d3748cf305 | |||
db304a70c8 | |||
8f9ea54790 | |||
a53a0bd2d4 | |||
1c6a9dbbbd | |||
802330c062 | |||
d1ef14a25a | |||
617481f26f | |||
6598db5078 | |||
5b790a86e2 | |||
bec0d823dc | |||
2616576b3d | |||
44fc4551aa | |||
6dfb2a2de0 | |||
03c91687ae | |||
91e06cbce6 | |||
b0624a3544 | |||
04a2a78914 | |||
4364f2e4c2 | |||
177b716fd0 | |||
818dc09aa4 | |||
a762969966 | |||
74338931b8 | |||
0ab424bfdb | |||
fff1a41fee | |||
7644a74648 | |||
9db3bd5b3f | |||
b81bd17fbc | |||
cf3866f892 | |||
5d3a81f4b9 | |||
f9831d4da5 | |||
8a20d8cf9b | |||
49f75f9edd | |||
9916e4da4d | |||
2ec1c0238d | |||
9dc716b1c8 | |||
31fb8b1404 | |||
d987845bba | |||
50c46dc20d | |||
5125084279 | |||
0685479d53 | |||
20c13ee71c | |||
cf322147d5 | |||
b4c37e6ddc | |||
911201ad9f | |||
8442bf2e14 | |||
697386c36c | |||
f6f72387b9 | |||
d201bdc422 | |||
2055400565 | |||
05bf940cdf | |||
3bbad156c4 | |||
db4ce6bb77 | |||
a290369d8d | |||
adf5f9f6e8 | |||
ace6701aaf | |||
10e7e4b39f | |||
edb4e421e2 | |||
747017a5f9 | |||
e992a99783 | |||
e869db0555 | |||
936a9efd0b | |||
edc11b6e1d | |||
32f2710763 | |||
4257c32bf5 | |||
c18901c35b | |||
f126fe9fa8 | |||
4cbd480e84 | |||
afc72ffd85 | |||
cdc69ea8ff | |||
52a89b1638 | |||
641d518b6e | |||
2eca45d397 | |||
de6fddf405 | |||
45f32040f8 | |||
42ef2ca99d | |||
563f2f37f7 | |||
881b13740a | |||
d39804f7ed | |||
b8a85c4891 | |||
37e9630b9c | |||
40492b67d1 | |||
f655a6e03a | |||
36f864efbb | |||
a995f43b7b | |||
1ee1330e47 | |||
3f4a234915 | |||
032ca39cf6 | |||
6f9a984541 | |||
e6e043f168 | |||
7c7fb7b343 | |||
b26fe30861 | |||
f99cdfe926 | |||
ec0a077539 | |||
a22158d070 | |||
48fe3a707e | |||
8d3d500b7b | |||
d8248cc915 | |||
397a1f8f9c | |||
a0f187354f | |||
1bf004ddee | |||
495100dea9 | |||
e3bd8a8b22 | |||
d987ac6c7a | |||
9883dcd0a7 | |||
d63133189d | |||
39fbb87010 | |||
f197760efd | |||
fa13d4a538 | |||
71b5f539c1 | |||
603a827e45 | |||
47bdc69a43 | |||
1ce56af3b1 | |||
a12c5c583b | |||
5455cf20ab | |||
75fcdb139c | |||
269fa79136 | |||
16fea59605 | |||
c7d183ee8d | |||
413083c58d | |||
1f671aba33 | |||
6bfd9098d6 | |||
4e7d96e91d | |||
ac0a036035 | |||
2a1445d61f | |||
0df39a1136 | |||
634d793839 | |||
afd6c5d6b7 | |||
ab0682cc5c | |||
60ca901ac7 | |||
91ca5be57a | |||
ac47a7eaa4 | |||
ce134224a8 | |||
ca49d3a465 | |||
5d7f9d1387 | |||
8d5d86fea8 | |||
6709505e9e | |||
088de60c91 | |||
ef041869e5 | |||
16cdc7aca4 | |||
39536c0e18 | |||
607d8b67c9 | |||
0b9889ea44 | |||
4acef776b2 | |||
e186dfdaa9 | |||
c0f3d02e6f | |||
36c8f59d6f | |||
f38b31a591 | |||
3232bb10e6 | |||
b7cb6b94f5 | |||
aa6e612fba | |||
d9d7b98409 | |||
4fdd6bbe5f | |||
439f6250f3 | |||
d55abf5dda | |||
94de170490 | |||
3c56db4121 | |||
a8b9d9316f | |||
55c7800f39 | |||
d9eb1c42bc | |||
8cd617e32d | |||
a17a05995a | |||
de4e616dcc | |||
12b00e5c8d | |||
9cab91959e | |||
bd9778a3d1 | |||
62a5fce66c | |||
2bd84636d6 | |||
62bb0d34ce | |||
1521d21e4e | |||
35996b6c69 | |||
d32a213457 | |||
2a3395ce15 | |||
944b57c336 | |||
ac561e7aca | |||
59daceef99 | |||
1dc41badd9 | |||
1a83315424 | |||
3c5776214f | |||
5fff0a2923 | |||
8df7f2992d | |||
7741394c9c | |||
25bd91debc | |||
7fe4724e10 | |||
123ae37524 | |||
172604fcdb | |||
7d887c73e8 | |||
6abaac25d9 | |||
cc897840e2 | |||
757840b76f | |||
bc09af9d0b | |||
0419b2f86b | |||
2f31fc7d6e | |||
dcf51c1777 | |||
94eb893a01 | |||
af49457bfc | |||
a8682d671d | |||
10815c8f73 | |||
891fb57531 | |||
07ee005613 | |||
a3c48d14ab | |||
f14b697769 | |||
50e8d1f8f4 | |||
d06fb08239 | |||
cf9a14c762 | |||
7b49af21d0 | |||
18774084c9 | |||
a70ad3d666 | |||
0d2d879603 | |||
8acdc17cc4 | |||
1f5331dbfe | |||
98747f4afb | |||
2f782b4963 | |||
3c083edfda | |||
7bf1a5af65 | |||
8a3d163266 | |||
d58fd96bb0 | |||
259f76379e | |||
b8378fbb02 | |||
2f0cdfff59 | |||
cd5787a4f7 | |||
a280fc2446 | |||
a503f4830b | |||
f732df1b9a | |||
9b9525ece5 | |||
e0dfbaf4a3 | |||
949b1dad0e | |||
830a666a58 | |||
26b9652135 | |||
85c6e7283d | |||
67603f64cd | |||
65f8d38c59 | |||
e70c10adbd | |||
64ec73d821 | |||
32e8a37f33 | |||
5290713504 | |||
18cfb56b45 | |||
4b12ae1531 | |||
9df98edca5 | |||
c3af6acb2c | |||
7ba00cafd9 | |||
5aefb3bc59 | |||
212e55ffd8 | |||
bf54d38c91 | |||
cee2240cdc | |||
4c1ad868a9 | |||
f5b3b29d6d | |||
8f5449527d | |||
8f6d720454 | |||
56a4a7043d | |||
5762859906 | |||
676eb1e9e2 | |||
e7b94868d6 | |||
608bac6854 | |||
723f9cd98c | |||
abf4d91703 | |||
d8392ad3eb | |||
39caad18a5 | |||
6437f7bb65 | |||
e232044157 | |||
f78b56ef0a | |||
ca3c9af3b8 | |||
b8b2e74151 | |||
1f8341ac42 | |||
0964f15475 | |||
63fd7957c6 | |||
65377ffd9e | |||
f79320c013 | |||
cf71ea26ec | |||
ee96c37c20 | |||
a86923aee1 | |||
e0f8d06152 | |||
4cb4ce298a | |||
36de61a57f | |||
6f2ca5bb89 | |||
940885768d | |||
fc577b4c3e | |||
bf10af2ae2 | |||
b4dfcf1bb4 | |||
0b0ba21852 | |||
42e0346e25 | |||
212db84d0b | |||
e6eb8accf2 | |||
8bd73c3afa | |||
5369a25fa2 | |||
eeae46a415 | |||
c0badbe96b | |||
2bb51c136a | |||
3cfa4ea6d6 | |||
f01adf5eb0 | |||
a0b92554e9 | |||
ac4c7e06e7 | |||
0f9a6f4340 | |||
9586a9c0dd | |||
f6563b265b | |||
7aea9473de | |||
3f059d7748 | |||
7e3c31c4b2 | |||
1707a9690a | |||
379ce917a9 | |||
299aaa2b68 | |||
5cf5e87fa8 | |||
55f22562eb | |||
272d911464 | |||
6beb34baa8 | |||
61de0b67fa | |||
aec8cec9b8 | |||
83b9573b52 | |||
21d99a1f24 | |||
1331479072 | |||
b472a36a9a | |||
3238fcdae7 | |||
cd2587b1fd | |||
879884a9fa | |||
5d3b963682 | |||
955e7a4f1c | |||
d2dcd4209d | |||
6299ff5b48 | |||
94a4dbaba1 | |||
c36deea045 | |||
7030d43aa5 | |||
aa02e9f8cf | |||
37e177b56e | |||
453f4da8ec | |||
400163b820 | |||
4ae9904c8a | |||
fe5e191cb5 | |||
d9d83df9de | |||
8dd8f88d2b | |||
01fd4c8ffa | |||
7ac3bb74e0 | |||
3b65cd0edc | |||
a9606728bf | |||
4d4f1a242c | |||
6b7143dd8f | |||
7e4ee00cb2 | |||
4868c45b43 | |||
81f485da6b | |||
18cbe51e6b | |||
149c8cc8b2 | |||
0dccb8c27b | |||
4302ea8832 | |||
1eac42dab8 | |||
9dd74f1f22 | |||
923ce74735 | |||
2d9f9adfee | |||
9a55e51a3a | |||
5681c917c5 | |||
6309e8bdf5 | |||
535efa3d73 | |||
b8a51d32f5 | |||
919b6b7014 | |||
971277ed39 | |||
7ce4de7a8b | |||
9591f4e14f | |||
27426b1390 | |||
fcb75dd780 | |||
1be9c9c1bd | |||
e088d053ab | |||
ffa8d9c063 | |||
7a5596a281 | |||
9f46f74357 | |||
36c4e2dfe0 | |||
5cb31dbe9d | |||
399fc98dec | |||
c22371e0c5 | |||
a4842c078b | |||
c332760786 | |||
ea4247c688 | |||
fec8c0cc14 | |||
9b585c73fb | |||
c695fa525f | |||
93f3e27d48 | |||
52ab7937bd | |||
762bfa8514 | |||
ca20996b62 | |||
ad14818de8 | |||
32839656f8 | |||
a48faad17a | |||
40487923f9 | |||
f1656c6d1e | |||
4c3dbbd8d5 | |||
4088ed747e | |||
bca8df8efd | |||
54f0a69596 | |||
9065c0d260 | |||
cb0150a0f9 | |||
ec0f7e3f7a | |||
e5d898f025 | |||
52bdb1cd6a | |||
49f9dfcf95 | |||
9536cdcae1 | |||
57e2632f38 | |||
b372f7ee84 | |||
70e8253b63 | |||
d9953b1473 | |||
c6ac898390 | |||
43f98cec43 | |||
63175cdec6 | |||
6436a1ec61 | |||
c400619338 | |||
dcd5ba41cf | |||
7525e52fab | |||
72fd24e624 | |||
11d8f9fd30 | |||
4b0c8cecc8 | |||
372ce174c9 | |||
381daff980 | |||
94acebbebd | |||
f90f6e81ee | |||
97e37f304b | |||
f5e45ead26 | |||
14f765f4b4 | |||
b77d46b2c2 | |||
1442916b20 | |||
c6a5f42d23 | |||
580d50eb8d | |||
9a66b6e50d | |||
acec064cb7 | |||
ea05d13a1f | |||
fd741f2ccf | |||
055c52178f | |||
722a5859a5 | |||
1714c3fa86 | |||
131df28110 | |||
1be284f8d8 | |||
218e53ae75 | |||
264d8d90c4 | |||
0b529c2629 | |||
5abcc7191f | |||
5346f6e1bf | |||
2f471b3de4 | |||
3cf06efddf | |||
1f4f0a3bb7 | |||
c07f8eae9b | |||
374eb3d06d | |||
fe75b75ddc | |||
d3790bf64b | |||
f905a52988 | |||
e55f427b05 | |||
0f000fc4d1 | |||
a0339e3c19 | |||
9cfa274d81 | |||
8c79f5e371 | |||
0d716513d7 | |||
08f6724060 | |||
29daf51e64 | |||
b3b2b01c0f | |||
c3212d0308 | |||
aaa114ba13 | |||
8ca6418630 | |||
95632b7f55 | |||
64744b2abf | |||
096b315701 | |||
c27ca08d3a | |||
0011222371 | |||
29db947e25 | |||
fd43ac7581 | |||
e49c19b3cd | |||
06f0e59967 | |||
c5fc5ee93b | |||
159c85bd1f | |||
2460acf0f4 | |||
6495687841 | |||
d229ccb36c | |||
4d6a57ddcf | |||
d161d55aaf | |||
768ad0c9bc | |||
29323d4e20 | |||
630b22e193 | |||
79116f9e67 | |||
eb58475259 | |||
a879ac30fb | |||
c5b0621323 | |||
0462815014 | |||
e64318c947 | |||
b784482788 | |||
2834e8b348 | |||
b23dfd4289 | |||
217d525cb2 | |||
85b166cbda | |||
9a57f8b858 | |||
3bfdc932c2 | |||
48c878af21 | |||
490a7a58fc | |||
567b1a3ace | |||
6a45db8a38 | |||
b272988929 | |||
9828857570 | |||
ecb54d8e44 | |||
344717b021 | |||
da6cf585c0 | |||
9e93177afd | |||
16318efb01 | |||
e83e13b6d4 | |||
7c2c695d88 | |||
c7b227529d | |||
14f49e9d30 | |||
1875c4ee73 | |||
76c68baa1f | |||
68876a4414 | |||
26d7e5b60f | |||
e78af6ae99 | |||
6fe05cd86e | |||
4100d7a391 | |||
c1cc812ea4 | |||
9663e3f0f4 | |||
941f618153 | |||
716a30bf7b | |||
d051ae576b | |||
35e99cb014 | |||
af054fba49 | |||
58d837d641 | |||
6cc1bd21cd | |||
cebfa7c8ae | |||
e2d7ab8f8f | |||
ec77987fcd | |||
b161608d02 | |||
5e7458ff1c | |||
ed06aaa1f5 | |||
13b7179941 | |||
da0d88d86f | |||
b7347c312a | |||
c876f2f7e3 | |||
adad5fd8ff | |||
2aaa7ae8c9 | |||
1f64ea37bd | |||
cac1525da0 | |||
1ad906fedc | |||
20ffef39a3 | |||
185460c054 | |||
0079e74d77 | |||
a8e019482f | |||
f01b8e47aa | |||
e43dfb7599 | |||
6b8cfe2b49 | |||
7f7b14bae3 | |||
810b02d9fd | |||
62813145b2 | |||
4877058253 | |||
2a9fd3abb8 | |||
a244f7b598 | |||
5396457ad5 | |||
0186b6ea61 | |||
f6e99f7e88 | |||
c24a3828be | |||
2e38a4567a | |||
67c5d67a61 | |||
9592dde534 | |||
d030b0af70 | |||
4ccb9ac94d | |||
8af62b917c | |||
311f114132 | |||
d015bd03f7 | |||
a61b9de0fa | |||
ef1b283917 | |||
c677f00105 | |||
8d2f778dfe | |||
c549d102f6 | |||
39a9ee4e9d | |||
a27dc6ad1c | |||
8ccb75fc8d | |||
8fc86dbe02 | |||
359f052608 | |||
4150e2265c | |||
b803ce7435 | |||
289c6cd7a9 | |||
31fc7b74c2 | |||
3e565f25be | |||
e509be4e21 | |||
170fc537ac | |||
3fe5882145 | |||
a290791410 | |||
2ebd38ff68 | |||
cd987a5b19 | |||
7f1dab7ee1 | |||
bed3945aa5 | |||
15a32a18b7 | |||
d2e8e7dd5d | |||
ce12ec89c4 | |||
b286444ad9 | |||
941263102d | |||
e2ed296dc7 | |||
cfc866bef2 | |||
affba669ce | |||
7230152ab8 | |||
21fee7171f | |||
ad17995f28 | |||
ac830cbe7f | |||
65da6af3f9 | |||
3d90bf7588 | |||
62ef1c88fe | |||
d6918920b6 | |||
cdfb09fbfa | |||
bb681e31c9 | |||
c7483936ec | |||
0a1f2da33d | |||
f5aafdb7d6 | |||
c9adf1c492 | |||
4c9cb560e3 | |||
f0b028279c | |||
197770b68b | |||
37b583f560 | |||
dca2d4fe12 | |||
3b677f8ae3 | |||
0b952578d1 | |||
054afbbedd | |||
866a6e4a44 | |||
8ea7dd478b | |||
7839252934 | |||
fa4063220f | |||
d214a02abd | |||
d1c12edd1b | |||
ded1a44c37 | |||
790a6cd1e3 | |||
a79f883a0f | |||
d9c5a540a3 | |||
276f33b9ec | |||
ded59d2da0 | |||
62f7a820d8 | |||
7063ffa013 | |||
bf4dc3c095 | |||
c10e5848bf | |||
92a3b0d6e0 | |||
b475bd25c8 | |||
d318224a6f | |||
0074fee865 | |||
5617535a63 | |||
68ccefc59f | |||
6d60541626 | |||
a635e5b8d0 | |||
48a10440fe | |||
8e3ba88318 | |||
ab8fccc544 | |||
8319dc9164 | |||
6829d3cdea | |||
3ae4d69110 | |||
dc665f227e | |||
a83496568f | |||
12d25570af | |||
378c947654 | |||
bd39a3140e | |||
7d3ca3dec1 | |||
1cb556c8f8 | |||
8c8f96de1c | |||
318cd87a9a | |||
5d63d5c2d3 | |||
7d347fccc6 | |||
a54ca799b9 | |||
f5bc1a996f | |||
8591bc4d01 | |||
40888c07f3 | |||
1c965c3788 | |||
4df690c2a2 | |||
d6abb61e2b | |||
3434c862e9 | |||
ea8af926fa | |||
c3df48174c | |||
f1e60f96c4 | |||
cdd852678b | |||
bf518b5467 | |||
ffd53fab26 | |||
5aad7dad35 | |||
b1c1a9f4e1 | |||
d847a8e0b2 | |||
9668730b5d | |||
dc049cf26a | |||
82c2b2f128 | |||
5f81d65911 | |||
19f990c564 | |||
62467007b2 | |||
d7624e5e1f |
181
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
@ -1,120 +1,61 @@
|
|||||||
name: 🐞 Bug report
|
name: 🐞 Bug report
|
||||||
description: Report a very clearly broken issue.
|
description: Create a new bug report.
|
||||||
title: 'bug: <title>'
|
title: 'bug: <title>'
|
||||||
labels: [bug]
|
labels: [bug]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
# ReVanced Manager bug report
|
# ReVanced Manager bug report
|
||||||
|
|
||||||
Important to note that your issue may have already been reported before. Please check for existing issues [here](https://github.com/revanced/revanced-manager/labels/bug).
|
Please check for existing issues [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
|
||||||
|
- type: textarea
|
||||||
- type: dropdown
|
attributes:
|
||||||
attributes:
|
label: Bug description
|
||||||
label: Type
|
description: |
|
||||||
options:
|
- Describe your bug in detail
|
||||||
- Error while running the manager
|
- Add steps to reproduce the bug if possible (Step 1. Download some files. Step 2. ...)
|
||||||
- Error at runtime
|
- Add images and videos if possible
|
||||||
- Cosmetic
|
- List selected patches if applicable
|
||||||
- Other
|
validations:
|
||||||
validations:
|
required: true
|
||||||
required: true
|
- type: textarea
|
||||||
- type: textarea
|
attributes:
|
||||||
attributes:
|
label: Version of ReVanced Manager and version & name of application you tried to patch
|
||||||
label: Bug description
|
validations:
|
||||||
description: How did you find the bug? Any additional details that might help?
|
required: true
|
||||||
validations:
|
- type: dropdown
|
||||||
required: true
|
attributes:
|
||||||
- type: textarea
|
label: Installation type
|
||||||
attributes:
|
options:
|
||||||
label: Steps to reproduce
|
- Non-root
|
||||||
description: Add the steps to reproduce this bug including your environment.
|
- Root
|
||||||
placeholder: Step 1. Download some files. Step 2. ...
|
validations:
|
||||||
validations:
|
required: false
|
||||||
required: true
|
- type: textarea
|
||||||
- type: textarea
|
attributes:
|
||||||
attributes:
|
label: Device logs
|
||||||
label: Android version
|
description: Export logs in ReVanced Manager settings.
|
||||||
description: Android version used.
|
render: shell
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Manager version
|
label: Patcher logs
|
||||||
description: Manager version used.
|
description: Export logs in "Patcher" screen.
|
||||||
validations:
|
render: shell
|
||||||
required: true
|
validations:
|
||||||
- type: textarea
|
required: false
|
||||||
attributes:
|
- type: checkboxes
|
||||||
label: Target package name
|
attributes:
|
||||||
description: App you tried to patch.
|
label: Acknowledgements
|
||||||
validations:
|
description: Your issue will be closed if you don't follow the checklist below!
|
||||||
required: true
|
options:
|
||||||
- type: textarea
|
- label: This request is not a duplicate of an existing issue.
|
||||||
attributes:
|
required: true
|
||||||
label: Target package version.
|
- label: I have chosen an appropriate title.
|
||||||
description: Version of the app you tried to patch.
|
required: true
|
||||||
validations:
|
- label: All requested information has been provided properly.
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- label: The issue is solely related to the ReVanced Manager
|
||||||
attributes:
|
required: true
|
||||||
label: Installation type
|
|
||||||
options:
|
|
||||||
- Non-root
|
|
||||||
- Root
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Patches selected.
|
|
||||||
description: Patches you selected for the app.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Device logs (exported using Manager settings).
|
|
||||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
|
||||||
render: shell
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Installer logs (exported using Installer menu option) [unneeded if issue is not during patching].
|
|
||||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
|
||||||
render: shell
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Screenshots or videos
|
|
||||||
description: Add screenshots or videos that show the bug here.
|
|
||||||
placeholder: Drag and drop the screenshots/videos into this box.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Solution
|
|
||||||
description: If applicable, add a possible solution.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Additional context
|
|
||||||
description: Add additional context here.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: checkboxes
|
|
||||||
id: acknowledgements
|
|
||||||
attributes:
|
|
||||||
label: Acknowledgements
|
|
||||||
description: Your issue will be closed if you haven't done these steps.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
required: true
|
|
||||||
- label: I filled out all of the requested information in this issue properly.
|
|
||||||
required: true
|
|
||||||
- label: The issue is related solely to the ReVanced Manager
|
|
||||||
required: true
|
|
||||||
|
94
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
@ -1,52 +1,42 @@
|
|||||||
name: ⭐ Feature request
|
name: ⭐ Feature request
|
||||||
description: Create a detailed feature request.
|
description: Create a new feature request.
|
||||||
title: 'feat: <title>'
|
title: 'feat: <title>'
|
||||||
labels: [feature-request]
|
labels: [feature request]
|
||||||
body:
|
body:
|
||||||
- type: dropdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
label: Type
|
value: |
|
||||||
options:
|
# ReVanced Manager feature request
|
||||||
- Functionality
|
|
||||||
- Cosmetic
|
Please check for existing feature requests [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
|
||||||
- Other
|
- type: textarea
|
||||||
validations:
|
attributes:
|
||||||
required: true
|
label: Feature description
|
||||||
- type: textarea
|
description: Describe your feature in detail.
|
||||||
attributes:
|
validations:
|
||||||
label: Issue
|
required: true
|
||||||
description: What is the current problem. Why does it require a feature request?
|
- type: textarea
|
||||||
validations:
|
attributes:
|
||||||
required: true
|
label: Motivation
|
||||||
- type: textarea
|
description: Explain why the lack of it is a problem.
|
||||||
attributes:
|
validations:
|
||||||
label: Feature
|
required: true
|
||||||
description: Describe your feature in detail. How does it solve the issue?
|
- type: textarea
|
||||||
validations:
|
attributes:
|
||||||
required: true
|
label: Additional context
|
||||||
- type: textarea
|
description: In case there is something else you want to add.
|
||||||
attributes:
|
validations:
|
||||||
label: Motivation
|
required: false
|
||||||
description: Why should your feature should be considered?
|
- type: checkboxes
|
||||||
validations:
|
attributes:
|
||||||
required: true
|
label: Acknowledgements
|
||||||
- type: textarea
|
description: Your issue will be closed if you don't follow the checklist below!
|
||||||
attributes:
|
options:
|
||||||
label: Additional context
|
- label: This request is not a duplicate of an existing issue.
|
||||||
description: Add additional context here.
|
required: true
|
||||||
validations:
|
- label: I have chosen an appropriate title.
|
||||||
required: false
|
required: true
|
||||||
- type: checkboxes
|
- label: All requested information has been provided properly.
|
||||||
id: acknowledgements
|
required: true
|
||||||
attributes:
|
- label: The issue is solely related to the ReVanced Manager
|
||||||
label: Acknowledgements
|
required: true
|
||||||
description: Your issue will be closed if you haven't done these steps.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
|
||||||
required: true
|
|
||||||
- label: I have written a short but informative title.
|
|
||||||
required: true
|
|
||||||
- label: I filled out all of the requested information in this issue properly.
|
|
||||||
required: true
|
|
||||||
- label: The issue is related solely to the ReVanced Manager
|
|
||||||
required: true
|
|
||||||
|
2
.github/config.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
firstPRMergeComment: >
|
||||||
|
❤️ Thank you for contributing to ReVanced Manager. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
|
55
.github/workflows/commit-build.yml
vendored
@ -1,55 +0,0 @@
|
|||||||
name: "Android CI Actions"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "**"
|
|
||||||
tags-ignore:
|
|
||||||
- "v*" # Ignore tags that start with "v" (e.g. v1.0.0) because they are handled by release-build.yml
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Set env
|
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
|
||||||
- name: Set up JDK 12
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '12'
|
|
||||||
distribution: 'zulu'
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: 'stable'
|
|
||||||
- name: Set environment variables
|
|
||||||
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
|
|
||||||
env:
|
|
||||||
SECRETS: ${{ secrets.SECRETS }}
|
|
||||||
- name: Set up Flutter
|
|
||||||
run: flutter pub get
|
|
||||||
- name: Generate files with Builder
|
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
|
||||||
- name: Build with Flutter
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
|
||||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
|
||||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
|
||||||
run: flutter build apk
|
|
||||||
- name: Sign APK
|
|
||||||
id: sign_apk
|
|
||||||
uses: ilharp/sign-android-release@v1
|
|
||||||
with:
|
|
||||||
releaseDir: build/app/outputs/apk/release
|
|
||||||
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
|
|
||||||
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
|
||||||
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
|
||||||
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
|
||||||
- name: Add version to APK
|
|
||||||
run: mv ${{ steps.sign_apk.outputs.signedFile }} revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
|
||||||
- name: Upload APK
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
|
||||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
|
46
.github/workflows/crowdin.yml
vendored
@ -1,46 +0,0 @@
|
|||||||
name: Sync Crowdin translations
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "flutter"
|
|
||||||
paths:
|
|
||||||
- "assets/i18n/en_US.json"
|
|
||||||
- ".github/workflows/crowdin.yml"
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *" # daily
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync-crowdin:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Crowdin
|
|
||||||
uses: crowdin/github-action@1.5.0
|
|
||||||
with:
|
|
||||||
config: crowdin.yml
|
|
||||||
upload_sources: true
|
|
||||||
upload_translations: false
|
|
||||||
download_translations: true
|
|
||||||
push_translations: true
|
|
||||||
create_pull_request: false
|
|
||||||
localization_branch_name: i18n_flutter
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
|
||||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
|
||||||
|
|
||||||
# commented due to Manager not being ready for the translated files to be in the main branch
|
|
||||||
# - name: GitHub is so dumb i just cant
|
|
||||||
# run: |
|
|
||||||
# sudo chmod -R ugo+rwX .
|
|
||||||
|
|
||||||
# - name: Merge
|
|
||||||
# run: |
|
|
||||||
# git checkout flutter
|
|
||||||
# git add *
|
|
||||||
# git merge i18n_flutter
|
|
||||||
# git push
|
|
44
.github/workflows/pr-build.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
name: Build pull request
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/pr-build.yml"
|
||||||
|
- "app/**"
|
||||||
|
- "gradle/**"
|
||||||
|
- "*.properties"
|
||||||
|
- ".kts"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Set up Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Build with Gradle
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: ./gradlew assembleRelease --no-daemon -PnoProguard -PsignAsDebug
|
||||||
|
|
||||||
|
- name: Set env
|
||||||
|
run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Add hash to APK
|
||||||
|
run: mv app/build/outputs/apk/release/app-release.apk revanced-manager-${{ env.COMMIT_HASH }}.apk
|
||||||
|
|
||||||
|
- name: Upload build
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: revanced-manager
|
||||||
|
path: revanced-manager-${{ env.COMMIT_HASH }}.apk
|
41
.github/workflows/pull-request-build.yml
vendored
@ -1,41 +0,0 @@
|
|||||||
name: "Android CI PR Build"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- "**"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Set env
|
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
|
||||||
- name: Set up JDK 12
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '12'
|
|
||||||
distribution: 'zulu'
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: 'stable'
|
|
||||||
- name: Set environment variables
|
|
||||||
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
|
|
||||||
env:
|
|
||||||
SECRETS: ${{ secrets.SECRETS }}
|
|
||||||
- name: Set up Flutter
|
|
||||||
run: flutter pub get
|
|
||||||
- name: Generate files with Builder
|
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
|
||||||
- name: Build with Flutter
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: flutter build apk
|
|
||||||
- name: Add version to APK
|
|
||||||
run: mv build/app/outputs/flutter-apk/app-release.apk revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
|
||||||
- name: Upload APK
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
|
||||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
|
45
.github/workflows/release-build.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: "Release Build"
|
name: Release Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -6,46 +6,41 @@ on:
|
|||||||
- "v*"
|
- "v*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
build:
|
||||||
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set env
|
- name: Set env
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
- name: Set up JDK 12
|
|
||||||
uses: actions/setup-java@v3
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: '12'
|
java-version: '17'
|
||||||
distribution: 'zulu'
|
distribution: 'temurin'
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
- name: Set up Gradle
|
||||||
channel: 'stable'
|
uses: gradle/actions/setup-gradle@v4
|
||||||
- name: Set environment variables
|
|
||||||
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
|
- name: Build with Gradle
|
||||||
env:
|
|
||||||
SECRETS: ${{ secrets.SECRETS }}
|
|
||||||
- name: Set up Flutter
|
|
||||||
run: flutter pub get
|
|
||||||
- name: Generate files with Builder
|
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
|
||||||
- name: Build with Flutter
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
run: ./gradlew assembleRelease --no-daemon
|
||||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
|
||||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
|
||||||
run: flutter build apk
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
id: sign_apk
|
id: sign_apk
|
||||||
uses: ilharp/sign-android-release@v1
|
uses: ilharp/sign-android-release@v1
|
||||||
with:
|
with:
|
||||||
releaseDir: build/app/outputs/apk/release
|
releaseDir: ./app/build/outputs/apk/release/
|
||||||
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
|
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
|
||||||
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||||
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||||
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Add version to APK
|
- name: Add version to APK
|
||||||
run: mv ${{steps.sign_apk.outputs.signedFile}} revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
run: mv ${{ steps.sign_apk.outputs.signedFile }} revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||||
|
|
||||||
- name: Publish release APK
|
- name: Publish release APK
|
||||||
uses: "marvinpinto/action-automatic-releases@latest"
|
uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
with:
|
with:
|
||||||
|
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 }}"}'
|
146
.gitignore
vendored
@ -1,138 +1,12 @@
|
|||||||
# Miscellaneous
|
|
||||||
*.class
|
|
||||||
*.lock
|
|
||||||
*.log
|
|
||||||
*.pyc
|
|
||||||
*.swp
|
|
||||||
.DS_Store
|
|
||||||
.atom/
|
|
||||||
.buildlog/
|
|
||||||
.history
|
|
||||||
.svn/
|
|
||||||
|
|
||||||
# IntelliJ related
|
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
.gradle
|
||||||
*.iws
|
/local.properties
|
||||||
.idea/
|
/.idea
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
||||||
|
|
||||||
# Visual Studio Code related
|
.kotlin/
|
||||||
.classpath
|
|
||||||
.project
|
|
||||||
.settings/
|
|
||||||
|
|
||||||
# Flutter repo-specific
|
|
||||||
/bin/cache/
|
|
||||||
/bin/mingit/
|
|
||||||
/dev/benchmarks/mega_gallery/
|
|
||||||
/dev/bots/.recipe_deps
|
|
||||||
/dev/bots/android_tools/
|
|
||||||
/dev/docs/doc/
|
|
||||||
/dev/docs/flutter.docs.zip
|
|
||||||
/dev/docs/lib/
|
|
||||||
/dev/docs/pubspec.yaml
|
|
||||||
/dev/integration_tests/**/xcuserdata
|
|
||||||
/dev/integration_tests/**/Pods
|
|
||||||
/packages/flutter/coverage/
|
|
||||||
version
|
|
||||||
|
|
||||||
# packages file containing multi-root paths
|
|
||||||
.packages.generated
|
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
|
||||||
**/doc/api/
|
|
||||||
**/*.g.dart
|
|
||||||
**/*.locator.dart
|
|
||||||
**/*.router.dart
|
|
||||||
.dart_tool/
|
|
||||||
.flutter-plugins
|
|
||||||
.flutter-plugins-dependencies
|
|
||||||
**/generated_plugin_registrant.dart
|
|
||||||
.packages
|
|
||||||
.pub-cache/
|
|
||||||
.pub/
|
|
||||||
build/
|
|
||||||
flutter_*.png
|
|
||||||
linked_*.ds
|
|
||||||
unlinked.ds
|
|
||||||
unlinked_spec.ds
|
|
||||||
|
|
||||||
# Android related
|
|
||||||
**/android/**/gradle-wrapper.jar
|
|
||||||
**/android/.gradle
|
|
||||||
**/android/captures/
|
|
||||||
**/android/gradlew
|
|
||||||
**/android/gradlew.bat
|
|
||||||
**/android/local.properties
|
|
||||||
**/android/**/GeneratedPluginRegistrant.java
|
|
||||||
**/android/key.properties
|
|
||||||
*.jks
|
|
||||||
|
|
||||||
# iOS/XCode related
|
|
||||||
**/ios/**/*.mode1v3
|
|
||||||
**/ios/**/*.mode2v3
|
|
||||||
**/ios/**/*.moved-aside
|
|
||||||
**/ios/**/*.pbxuser
|
|
||||||
**/ios/**/*.perspectivev3
|
|
||||||
**/ios/**/*sync/
|
|
||||||
**/ios/**/.sconsign.dblite
|
|
||||||
**/ios/**/.tags*
|
|
||||||
**/ios/**/.vagrant/
|
|
||||||
**/ios/**/DerivedData/
|
|
||||||
**/ios/**/Icon?
|
|
||||||
**/ios/**/Pods/
|
|
||||||
**/ios/**/.symlinks/
|
|
||||||
**/ios/**/profile
|
|
||||||
**/ios/**/xcuserdata
|
|
||||||
**/ios/.generated/
|
|
||||||
**/ios/Flutter/.last_build_id
|
|
||||||
**/ios/Flutter/App.framework
|
|
||||||
**/ios/Flutter/Flutter.framework
|
|
||||||
**/ios/Flutter/Flutter.podspec
|
|
||||||
**/ios/Flutter/Generated.xcconfig
|
|
||||||
**/ios/Flutter/app.flx
|
|
||||||
**/ios/Flutter/app.zip
|
|
||||||
**/ios/Flutter/flutter_assets/
|
|
||||||
**/ios/Flutter/flutter_export_environment.sh
|
|
||||||
**/ios/ServiceDefinitions.json
|
|
||||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
|
||||||
|
|
||||||
# macOS related
|
|
||||||
**/macos/Flutter/GeneratedPluginRegistrant.swift
|
|
||||||
**/macos/Flutter/Flutter-Debug.xcconfig
|
|
||||||
**/macos/Flutter/Flutter-Release.xcconfig
|
|
||||||
**/macos/Flutter/Flutter-Profile.xcconfig
|
|
||||||
|
|
||||||
# Windows related
|
|
||||||
**/windows/flutter/ephemeral/
|
|
||||||
**/windows/**/*.suo
|
|
||||||
**/windows/**/*.user
|
|
||||||
**/windows/**/*.userosscache
|
|
||||||
**/windows/**/*.sln.docstates
|
|
||||||
**/windows/x64/
|
|
||||||
**/windows/x86/
|
|
||||||
**/windows/**/*.[Cc]ache
|
|
||||||
**/windows/**/!*.[Cc]ache/
|
|
||||||
|
|
||||||
# Web related
|
|
||||||
lib/generated_plugin_registrant.dart
|
|
||||||
|
|
||||||
# Coverage
|
|
||||||
coverage/
|
|
||||||
|
|
||||||
# Symbolication related
|
|
||||||
app.*.symbols
|
|
||||||
|
|
||||||
# Obfuscation related
|
|
||||||
app.*.map.json
|
|
||||||
|
|
||||||
# Exceptions to above rules.
|
|
||||||
!**/ios/**/default.mode1v3
|
|
||||||
!**/ios/**/default.mode2v3
|
|
||||||
!**/ios/**/default.pbxuser
|
|
||||||
!**/ios/**/default.perspectivev3
|
|
||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
|
||||||
!/dev/ci/**/Gemfile.lock
|
|
||||||
|
|
||||||
Firebase related
|
|
||||||
.firebase
|
|
||||||
|
45
.metadata
@ -1,45 +0,0 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
|
||||||
#
|
|
||||||
# This file should be version controlled.
|
|
||||||
|
|
||||||
version:
|
|
||||||
revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
channel: stable
|
|
||||||
|
|
||||||
project_type: app
|
|
||||||
|
|
||||||
# Tracks metadata for the flutter migrate command
|
|
||||||
migration:
|
|
||||||
platforms:
|
|
||||||
- platform: root
|
|
||||||
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
- platform: android
|
|
||||||
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
- platform: ios
|
|
||||||
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
- platform: linux
|
|
||||||
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
- platform: macos
|
|
||||||
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
- platform: web
|
|
||||||
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
- platform: windows
|
|
||||||
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
|
||||||
|
|
||||||
# User provided section
|
|
||||||
|
|
||||||
# List of Local paths (relative to this file) that should be
|
|
||||||
# ignored by the migrate tool.
|
|
||||||
#
|
|
||||||
# Files that are not part of the templates will be ignored by default.
|
|
||||||
unmanaged_files:
|
|
||||||
- 'lib/main.dart'
|
|
||||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
|
@ -1,6 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
91
.vscode/tasks.json
vendored
@ -1,91 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "Generate (Builder)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "flutter packages pub run build_runner build --delete-conflicting-outputs",
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Build (Android)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "flutter build apk",
|
|
||||||
"problemMatcher": [],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Install (Android)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "adb install build\\app\\outputs\\flutter-apk\\app-release.apk",
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Clean (Flutter)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "flutter clean && flutter pub get",
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Clean (Builder)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "flutter packages pub run build_runner clean",
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Build all (Android)",
|
|
||||||
"dependsOrder": "sequence",
|
|
||||||
"dependsOn": [
|
|
||||||
"Generate (Builder)",
|
|
||||||
"Build (Android)"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Clean all",
|
|
||||||
"dependsOrder": "sequence",
|
|
||||||
"dependsOn": [
|
|
||||||
"Clean (Flutter)",
|
|
||||||
"Clean (Builder)"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Clean all & Build all (Android)",
|
|
||||||
"dependsOrder": "sequence",
|
|
||||||
"dependsOn": [
|
|
||||||
"Clean all",
|
|
||||||
"Build all (Android)"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Clean all & Install (Android)",
|
|
||||||
"dependsOrder": "sequence",
|
|
||||||
"dependsOn": [
|
|
||||||
"Clean all",
|
|
||||||
"Build all (Android)",
|
|
||||||
"Install (Android)",
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Build & Install (Android)",
|
|
||||||
"dependsOrder": "sequence",
|
|
||||||
"dependsOn": [
|
|
||||||
"Build (Android)",
|
|
||||||
"Install (Android)"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Validate translations",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "flutter pub run flutter_i18n diff en.json pt.json",
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
111
CONTRIBUTING.md
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<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
|
||||||
|
|
||||||
|
Welcome to contribution guidelines, this document contains
|
||||||
|
everything you'll need to contribute to ReVanced Manager (and might even possibly apply to our other project like ReVanced Patches!)
|
||||||
|
|
||||||
|
There are many ways to contribute like writing docs and code, opening issues,
|
||||||
|
helping people out in our community, translating or sponsoring our project, etc.
|
||||||
|
|
||||||
|
## 📖 Resources to help you get started
|
||||||
|
|
||||||
|
* The [documentation](/docs/developer/README.md) provides steps to build ReVanced Manager from source
|
||||||
|
* 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-manager/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-manager/issues/new?assignees=&labels=feature-request&projects=&template=feature-issue.yml&title=feat%3A+%3Ctitle%3E).
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Manager.
|
||||||
|
> Good motivation has to be provided for a request to be accepted.
|
||||||
|
|
||||||
|
## 🐞 Submitting a bug report
|
||||||
|
|
||||||
|
If you encounter a bug while using the ReVanced Manager app, open an issue using the
|
||||||
|
[bug report issue template](https://github.com/ReVanced/revanced-manager/issues/new?assignees=&labels=bug&projects=&template=bug-issue.yml&title=bug%3A+%3Ctitle%3E).
|
||||||
|
|
||||||
|
## 🌐 Submitting translations
|
||||||
|
|
||||||
|
You can contribute translations at translate.revanced.app
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> * Try to keep the translated text roughly the same length as the original.
|
||||||
|
> * Consider opting for gender-neutral words for language with variations of words based on gender.
|
||||||
|
|
||||||
|
## 🧑💻 Developing for ReVanced Manager
|
||||||
|
|
||||||
|
See the guidelines for developing for ReVanced Manager [here](/docs/developer/README.md)
|
||||||
|
|
||||||
|
## 🔒 Submitting a vulnerability
|
||||||
|
|
||||||
|
See the guideline for reporting security vulnerability [here](/SECURITY.md)
|
||||||
|
|
||||||
|
## 🤚 I don't want to do any of that but I want to contribute either way
|
||||||
|
|
||||||
|
You can still contribute by spreading positive word about us or if you'd like to sponsor us, checkout https://revanced.app/donate
|
||||||
|
to learn more about how you can sponsor via GitHub, Open Collective, and cryptocurrencies.
|
||||||
|
|
||||||
|
❤️ Thank you for considering contributing to ReVanced Manager,
|
||||||
|
ReVanced
|
115
README.md
@ -1,35 +1,104 @@
|
|||||||
|
<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 Manager
|
# 💊 ReVanced Manager
|
||||||
|
|
||||||
The official ReVanced Manager based on Flutter.
|
[](https://github.com/ReVanced/revanced-manager/actions/workflows/release.yml)
|
||||||
|
[](#️-license)
|
||||||
|
|
||||||
|
Application to use ReVanced on Android
|
||||||
|
|
||||||
|
## ❓ About
|
||||||
|
|
||||||
|
ReVanced Manager is an application that uses [ReVanced Patcher](https://github.com/revanced/revanced-patcher) to patch Android apps.
|
||||||
|
|
||||||
|
## 💪 Features
|
||||||
|
|
||||||
|
Some of the features ReVanced Manager provides are:
|
||||||
|
|
||||||
|
- 💉 **Patch apps**: Apply any patch of your choice to Android apps
|
||||||
|
- 📱 **Portable**: ReVanced Patcher that fits in your pocket
|
||||||
|
- 🤗 **Simple UI**: Quickly understand the ins and outs of ReVanced Manager
|
||||||
|
- 🛠️ **Customization**: Configurable API, custom sources, language, signing keystore, theme and more
|
||||||
|
|
||||||
## 🔽 Download
|
## 🔽 Download
|
||||||
To download the Alpha version of Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
|
|
||||||
|
|
||||||
## 📝 Prerequisites
|
You can get the most recent version of ReVanced Manager by downloading from
|
||||||
1. Android 8 or higher
|
the [ReVanced site](https://revanced.app/download).
|
||||||
2. Does not work on some armv7 devices
|
|
||||||
3. [Vanced MicroG](https://github.com/TeamVanced/VancedMicroG/releases) required for YouTube and YouTube Music (Only for non-root)
|
|
||||||
|
|
||||||
## ⚠️ Disclaimer
|
Learn how to use ReVanced Manager by following the [documentation](/docs).
|
||||||
*Please note that even though we're releasing the Manager, it is an ALPHA version. There's a big chance that the Manager might not work at all for you.*
|
|
||||||
|
|
||||||
## 🔴 Issues
|
## 📚 Everything else
|
||||||
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
|
|
||||||
|
|
||||||
## 💭 Discussion
|
### 📙 Contributing
|
||||||
If you wish to discuss the Manager, a thread has been made under the [#development](https://discord.com/channels/952946952348270622/1002922226443632761) channel in the Discord server, please note that this thread may be temporary and may be removed in the future.
|
|
||||||
|
|
||||||
|
Thank you for considering contributing to ReVanced Manager.
|
||||||
|
|
||||||
## 🌐 Translation
|
The [contribution guidelines](CONTRIBUTING.md) provides information you'll need to open an issue, develop for ReVanced Manager and translations.
|
||||||
[](https://crowdin.com/project/revanced)
|
|
||||||
|
|
||||||
If you wish to translate ReVanced Manager, we're accepting translations on [Crowdin](https://translate.revanced.app)
|
### 📄 Documentation
|
||||||
|
|
||||||
## 🛠️ Building Manager from source
|
You can find the documentation for ReVanced Manager [here](/docs).
|
||||||
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
|
||||||
2. Clone the repository locally
|
## ⚖️ License
|
||||||
3. Add your github token in gradle.properties like [this](https://github.com/revanced/revanced-documentation/blob/main/docs/revanced-development/2_building_from_source.md)
|
|
||||||
4. Open the project in terminal
|
ReVanced Manager is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
|
||||||
5. Run `flutter pub get` in terminal
|
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Manager as long as you track changes/dates in source files.
|
||||||
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
|
Any modifications to ReVanced Manager must also be made available under the GPL, along with build & install instructions.
|
||||||
7. To build release apk run `flutter build apk`
|
|
||||||
|
83
SECURITY.md
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
# 🔒 Security Policy
|
||||||
|
|
||||||
|
This document describes how to report security vulnerabilities for ReVanced Manager.
|
||||||
|
|
||||||
|
## 🚨 Reporting a Vulnerability
|
||||||
|
|
||||||
|
Please open an issue in our [advisory tracker](https://github.com/ReVanced/revanced-manager/security/advisories/new)
|
||||||
|
or reach out privately to us on [Discord](https://discord.gg/revanced).
|
||||||
|
|
||||||
|
If a vulnerability is confirmed and accepted, they will be published and
|
||||||
|
you can join our [Discord](https://discord.gg/revanced) server to receive a
|
||||||
|
special contributor role.
|
||||||
|
|
||||||
|
### ⏳ Supported Versions
|
||||||
|
|
||||||
|
| Version | Branch | Supported |
|
||||||
|
|------------------------------------------|---------------|--------------------|
|
||||||
|
| ![Latest stable release][LatestRelBadge] | `main` | :white_check_mark: |
|
||||||
|
| ![Latest version][LatestVerBadge] | `dev` | :white_check_mark: |
|
||||||
|
| ![Latest version][LatestVerBadge] | `compose-dev` | :white_check_mark: |
|
||||||
|
|
||||||
|
[LatestRelBadge]: https://img.shields.io/github/v/release/ReVanced/revanced-manager?style=for-the-badge "Latest stable release"
|
||||||
|
[LatestVerBadge]: https://img.shields.io/badge/version-latest-brightgreen?style=for-the-badge "Latest version"
|
@ -1,33 +0,0 @@
|
|||||||
# This file configures the analyzer, which statically analyzes Dart code to
|
|
||||||
# check for errors, warnings, and lints.
|
|
||||||
#
|
|
||||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
|
||||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
|
||||||
# invoked from the command line by running `flutter analyze`.
|
|
||||||
|
|
||||||
# The following line activates a set of recommended lints for Flutter apps,
|
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
|
||||||
include: package:flutter_lints/flutter.yaml
|
|
||||||
|
|
||||||
analyzer:
|
|
||||||
exclude:
|
|
||||||
- lib/utils/env_class.g.dart
|
|
||||||
|
|
||||||
linter:
|
|
||||||
# The lint rules applied to this project can be customized in the
|
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
|
||||||
# included above or to enable additional rules. A list of all available lints
|
|
||||||
# and their documentation is published at
|
|
||||||
# https://dart-lang.github.io/linter/lints/index.html.
|
|
||||||
#
|
|
||||||
# Instead of disabling a lint rule for the entire project in the
|
|
||||||
# section below, it can also be suppressed for a single line of code
|
|
||||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
|
||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
|
||||||
# producing the lint.
|
|
||||||
rules:
|
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
13
android/.gitignore
vendored
@ -1,13 +0,0 @@
|
|||||||
gradle-wrapper.jar
|
|
||||||
/.gradle
|
|
||||||
/captures/
|
|
||||||
/gradlew
|
|
||||||
/gradlew.bat
|
|
||||||
/local.properties
|
|
||||||
GeneratedPluginRegistrant.java
|
|
||||||
|
|
||||||
# Remember to never publicly share your keystore.
|
|
||||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
|
||||||
key.properties
|
|
||||||
**/*.keystore
|
|
||||||
**/*.jks
|
|
@ -1,84 +0,0 @@
|
|||||||
def localProperties = new Properties()
|
|
||||||
def localPropertiesFile = rootProject.file('local.properties')
|
|
||||||
if (localPropertiesFile.exists()) {
|
|
||||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
|
||||||
localProperties.load(reader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
|
||||||
if (flutterRoot == null) {
|
|
||||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
|
||||||
if (flutterVersionCode == null) {
|
|
||||||
flutterVersionCode = '1'
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
|
||||||
if (flutterVersionName == null) {
|
|
||||||
flutterVersionName = '1.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion 33
|
|
||||||
ndkVersion flutter.ndkVersion
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = '11'
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "app.revanced.manager.flutter"
|
|
||||||
minSdkVersion 26
|
|
||||||
targetSdkVersion 33
|
|
||||||
versionCode flutterVersionCode.toInteger()
|
|
||||||
versionName flutterVersionName
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
shrinkResources false
|
|
||||||
minifyEnabled false
|
|
||||||
signingConfig signingConfigs.debug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
packagingOptions {
|
|
||||||
exclude '/prebuilt/**'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flutter {
|
|
||||||
source '../..'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
|
|
||||||
// ReVanced
|
|
||||||
implementation "app.revanced:revanced-patcher:6.3.1"
|
|
||||||
|
|
||||||
// Signing & aligning
|
|
||||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
|
||||||
implementation("com.android.tools.build:apksig:7.2.2")
|
|
||||||
|
|
||||||
// MicroG cronet
|
|
||||||
implementation("org.microg:cronet-common:$cronetVersion")
|
|
||||||
implementation("org.microg:cronet-native:$cronetVersion")
|
|
||||||
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="app.revanced.manager.flutter">
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
</manifest>
|
|
@ -1,51 +0,0 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="app.revanced.manager.flutter">
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:label="ReVanced Manager"
|
|
||||||
android:name="${applicationName}"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:largeHeap="true"
|
|
||||||
android:extractNativeLibs="true">
|
|
||||||
<activity
|
|
||||||
android:name=".MainActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:launchMode="singleTop"
|
|
||||||
android:theme="@style/LaunchTheme"
|
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
|
||||||
android:hardwareAccelerated="true"
|
|
||||||
android:windowSoftInputMode="adjustResize">
|
|
||||||
<meta-data
|
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
|
||||||
android:resource="@style/NormalTheme"
|
|
||||||
/>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<meta-data
|
|
||||||
android:name="flutterEmbedding"
|
|
||||||
android:value="2" />
|
|
||||||
<provider
|
|
||||||
android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities="${applicationId}.fileProvider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/file_paths" />
|
|
||||||
</provider>
|
|
||||||
</application>
|
|
||||||
</manifest>
|
|
@ -1,317 +0,0 @@
|
|||||||
package app.revanced.manager.flutter
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import androidx.annotation.NonNull
|
|
||||||
import app.revanced.manager.flutter.utils.Aapt
|
|
||||||
import app.revanced.manager.flutter.utils.aligning.ZipAligner
|
|
||||||
import app.revanced.manager.flutter.utils.signing.Signer
|
|
||||||
import app.revanced.manager.flutter.utils.zip.ZipFile
|
|
||||||
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
|
||||||
import app.revanced.patcher.Patcher
|
|
||||||
import app.revanced.patcher.PatcherOptions
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
|
||||||
import app.revanced.patcher.logging.Logger
|
|
||||||
import app.revanced.patcher.util.patch.PatchBundle
|
|
||||||
import dalvik.system.DexClassLoader
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
private const val PATCHER_CHANNEL = "app.revanced.manager.flutter/patcher"
|
|
||||||
private const val INSTALLER_CHANNEL = "app.revanced.manager.flutter/installer"
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
|
||||||
private lateinit var installerChannel: MethodChannel
|
|
||||||
|
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
|
||||||
super.configureFlutterEngine(flutterEngine)
|
|
||||||
val mainChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PATCHER_CHANNEL)
|
|
||||||
installerChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL)
|
|
||||||
mainChannel.setMethodCallHandler { call, result ->
|
|
||||||
when (call.method) {
|
|
||||||
"runPatcher" -> {
|
|
||||||
val patchBundleFilePath = call.argument<String>("patchBundleFilePath")
|
|
||||||
val originalFilePath = call.argument<String>("originalFilePath")
|
|
||||||
val inputFilePath = call.argument<String>("inputFilePath")
|
|
||||||
val patchedFilePath = call.argument<String>("patchedFilePath")
|
|
||||||
val outFilePath = call.argument<String>("outFilePath")
|
|
||||||
val integrationsPath = call.argument<String>("integrationsPath")
|
|
||||||
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
|
||||||
val cacheDirPath = call.argument<String>("cacheDirPath")
|
|
||||||
val mergeIntegrations = call.argument<Boolean>("mergeIntegrations")
|
|
||||||
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
|
|
||||||
if (patchBundleFilePath != null &&
|
|
||||||
originalFilePath != null &&
|
|
||||||
inputFilePath != null &&
|
|
||||||
patchedFilePath != null &&
|
|
||||||
outFilePath != null &&
|
|
||||||
integrationsPath != null &&
|
|
||||||
selectedPatches != null &&
|
|
||||||
cacheDirPath != null &&
|
|
||||||
mergeIntegrations != null &&
|
|
||||||
keyStoreFilePath != null
|
|
||||||
) {
|
|
||||||
runPatcher(
|
|
||||||
result,
|
|
||||||
patchBundleFilePath,
|
|
||||||
originalFilePath,
|
|
||||||
inputFilePath,
|
|
||||||
patchedFilePath,
|
|
||||||
outFilePath,
|
|
||||||
integrationsPath,
|
|
||||||
selectedPatches,
|
|
||||||
cacheDirPath,
|
|
||||||
mergeIntegrations,
|
|
||||||
keyStoreFilePath
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
result.notImplemented()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> result.notImplemented()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun runPatcher(
|
|
||||||
result: MethodChannel.Result,
|
|
||||||
patchBundleFilePath: String,
|
|
||||||
originalFilePath: String,
|
|
||||||
inputFilePath: String,
|
|
||||||
patchedFilePath: String,
|
|
||||||
outFilePath: String,
|
|
||||||
integrationsPath: String,
|
|
||||||
selectedPatches: List<String>,
|
|
||||||
cacheDirPath: String,
|
|
||||||
mergeIntegrations: Boolean,
|
|
||||||
keyStoreFilePath: String
|
|
||||||
) {
|
|
||||||
val originalFile = File(originalFilePath)
|
|
||||||
val inputFile = File(inputFilePath)
|
|
||||||
val patchedFile = File(patchedFilePath)
|
|
||||||
val outFile = File(outFilePath)
|
|
||||||
val integrations = File(integrationsPath)
|
|
||||||
val keyStoreFile = File(keyStoreFilePath)
|
|
||||||
|
|
||||||
Thread {
|
|
||||||
try {
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.1,
|
|
||||||
"header" to "",
|
|
||||||
"log" to "Copying original apk"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
originalFile.copyTo(inputFile, true)
|
|
||||||
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.2,
|
|
||||||
"header" to "Unpacking apk...",
|
|
||||||
"log" to "Unpacking input apk"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val patcher =
|
|
||||||
Patcher(
|
|
||||||
PatcherOptions(
|
|
||||||
inputFile,
|
|
||||||
cacheDirPath,
|
|
||||||
Aapt.binary(applicationContext).absolutePath,
|
|
||||||
cacheDirPath,
|
|
||||||
logger = ManagerLogger()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to 0.3, "header" to "", "log" to "")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (mergeIntegrations) {
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.4,
|
|
||||||
"header" to "Merging integrations...",
|
|
||||||
"log" to "Merging integrations"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
patcher.addFiles(listOf(integrations)) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.5,
|
|
||||||
"header" to "Applying patches...",
|
|
||||||
"log" to ""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val patches = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
|
|
||||||
PatchBundle.Dex(
|
|
||||||
patchBundleFilePath,
|
|
||||||
DexClassLoader(
|
|
||||||
patchBundleFilePath,
|
|
||||||
cacheDirPath,
|
|
||||||
null,
|
|
||||||
javaClass.classLoader
|
|
||||||
)
|
|
||||||
).loadPatches().filter { patch ->
|
|
||||||
(patch.compatiblePackages?.any { it.name == patcher.context.packageMetadata.packageName } == true || patch.compatiblePackages.isNullOrEmpty()) &&
|
|
||||||
selectedPatches.any { it == patch.patchName }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TODO("VERSION.SDK_INT < CUPCAKE")
|
|
||||||
}
|
|
||||||
patcher.addPatches(patches)
|
|
||||||
patcher.executePatches().forEach { (patch, res) ->
|
|
||||||
if (res.isSuccess) {
|
|
||||||
val msg = "Applied $patch"
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.5,
|
|
||||||
"header" to "",
|
|
||||||
"log" to msg
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
val msg = "Failed to apply $patch: " + "${res.exceptionOrNull()!!.message ?: res.exceptionOrNull()!!.cause!!::class.simpleName}"
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to 0.5, "header" to "", "log" to msg)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.7,
|
|
||||||
"header" to "Repacking apk...",
|
|
||||||
"log" to "Repacking patched apk"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val res = patcher.save()
|
|
||||||
ZipFile(patchedFile).use { file ->
|
|
||||||
res.dexFiles.forEach {
|
|
||||||
file.addEntryCompressData(
|
|
||||||
ZipEntry.createWithName(it.name),
|
|
||||||
it.stream.readBytes()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
res.resourceFile?.let {
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(it),
|
|
||||||
ZipAligner::getEntryAlignment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(inputFile),
|
|
||||||
ZipAligner::getEntryAlignment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.9,
|
|
||||||
"header" to "Signing apk...",
|
|
||||||
"log" to ""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
|
|
||||||
|
|
||||||
try {
|
|
||||||
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
//log to console
|
|
||||||
print("Error signing apk: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 1.0,
|
|
||||||
"header" to "Finished!",
|
|
||||||
"log" to "Finished!"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
val stack = ex.stackTraceToString()
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to -100.0,
|
|
||||||
"header" to "Aborting...",
|
|
||||||
"log" to "An error occurred! Aborting\nError:\n$stack"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handler.post { result.success(null) }
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ManagerLogger : Logger {
|
|
||||||
override fun error(msg: String) {
|
|
||||||
handler.post {
|
|
||||||
installerChannel
|
|
||||||
.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to -1.0, "header" to "", "log" to msg)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun warn(msg: String) {
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to -1.0, "header" to "", "log" to msg)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun info(msg: String) {
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to -1.0, "header" to "", "log" to msg)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun trace(_msg: String) { /* unused */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package app.revanced.manager.flutter.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
object Aapt {
|
|
||||||
fun binary(context: Context): File {
|
|
||||||
return File(context.applicationInfo.nativeLibraryDir).resolveAapt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun File.resolveAapt() = resolve(list { _, f -> !File(f).isDirectory && f.contains("aapt") }!!.first())
|
|
@ -1,11 +0,0 @@
|
|||||||
package app.revanced.manager.flutter.utils.aligning
|
|
||||||
|
|
||||||
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
|
||||||
|
|
||||||
internal object ZipAligner {
|
|
||||||
private const val DEFAULT_ALIGNMENT = 4
|
|
||||||
private const val LIBRARY_ALIGNMENT = 4096
|
|
||||||
|
|
||||||
fun getEntryAlignment(entry: ZipEntry): Int? =
|
|
||||||
if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
package app.revanced.manager.flutter.utils.signing
|
|
||||||
|
|
||||||
import com.android.apksig.ApkSigner
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
|
||||||
import org.bouncycastle.operator.ContentSigner
|
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.math.BigInteger
|
|
||||||
import java.security.*
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
internal class Signer(
|
|
||||||
private val cn: String, password: String
|
|
||||||
) {
|
|
||||||
private val passwordCharArray = 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=$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, ks: File) {
|
|
||||||
Security.addProvider(BouncyCastleProvider())
|
|
||||||
|
|
||||||
if (!ks.exists()) newKeystore(ks)
|
|
||||||
|
|
||||||
val keyStore = KeyStore.getInstance("BKS", "BC")
|
|
||||||
FileInputStream(ks).use { fis -> keyStore.load(fis, null) }
|
|
||||||
val alias = keyStore.aliases().nextElement()
|
|
||||||
|
|
||||||
val config = ApkSigner.SignerConfig.Builder(
|
|
||||||
cn,
|
|
||||||
keyStore.getKey(alias, passwordCharArray) as PrivateKey,
|
|
||||||
listOf(keyStore.getCertificate(alias) as X509Certificate)
|
|
||||||
).build()
|
|
||||||
|
|
||||||
val signer = ApkSigner.Builder(listOf(config))
|
|
||||||
signer.setCreatedBy(cn)
|
|
||||||
signer.setInputApk(input)
|
|
||||||
signer.setOutputApk(output)
|
|
||||||
|
|
||||||
signer.build().sign()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package app.revanced.manager.flutter.utils.zip
|
|
||||||
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.io.DataOutput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
fun UInt.toLittleEndian() =
|
|
||||||
(((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt()
|
|
||||||
|
|
||||||
fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
|
|
||||||
|
|
||||||
fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8)
|
|
||||||
or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt()
|
|
||||||
|
|
||||||
fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
|
|
||||||
|
|
||||||
fun ByteBuffer.getUShort() = this.short.toUShort()
|
|
||||||
fun ByteBuffer.getUInt() = this.int.toUInt()
|
|
||||||
|
|
||||||
fun ByteBuffer.putUShort(ushort: UShort): ByteBuffer = this.putShort(ushort.toShort())
|
|
||||||
fun ByteBuffer.putUInt(uint: UInt): ByteBuffer = this.putInt(uint.toInt())
|
|
||||||
|
|
||||||
fun DataInput.readUShort() = this.readShort().toUShort()
|
|
||||||
fun DataInput.readUInt() = this.readInt().toUInt()
|
|
||||||
|
|
||||||
fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
|
|
||||||
fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
|
|
||||||
|
|
||||||
fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
|
|
||||||
fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
|
|
||||||
|
|
||||||
fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
|
|
||||||
fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
|
|
@ -1,176 +0,0 @@
|
|||||||
package app.revanced.manager.flutter.utils.zip
|
|
||||||
|
|
||||||
import app.revanced.manager.flutter.utils.zip.structures.ZipEndRecord
|
|
||||||
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
|
||||||
import java.io.Closeable
|
|
||||||
import java.io.File
|
|
||||||
import java.io.RandomAccessFile
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.channels.FileChannel
|
|
||||||
import java.util.zip.CRC32
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
|
|
||||||
class ZipFile(file: File) : Closeable {
|
|
||||||
var entries: MutableList<ZipEntry> = mutableListOf()
|
|
||||||
|
|
||||||
private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
|
|
||||||
private var CDNeedsRewrite = false
|
|
||||||
|
|
||||||
private val compressionLevel = 5
|
|
||||||
|
|
||||||
init {
|
|
||||||
//if file isn't empty try to load entries
|
|
||||||
if (file.length() > 0) {
|
|
||||||
val endRecord = findEndRecord()
|
|
||||||
|
|
||||||
if (endRecord.diskNumber > 0u || endRecord.totalEntries != endRecord.diskEntries)
|
|
||||||
throw IllegalArgumentException("Multi-file archives are not supported")
|
|
||||||
|
|
||||||
entries = readEntries(endRecord).toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
//seek back to start for writing
|
|
||||||
filePointer.seek(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findEndRecord(): ZipEndRecord {
|
|
||||||
//look from end to start since end record is at the end
|
|
||||||
for (i in filePointer.length() - 1 downTo 0) {
|
|
||||||
filePointer.seek(i)
|
|
||||||
//possible beginning of signature
|
|
||||||
if (filePointer.readByte() == 0x50.toByte()) {
|
|
||||||
//seek back to get the full int
|
|
||||||
filePointer.seek(i)
|
|
||||||
val possibleSignature = filePointer.readUIntLE()
|
|
||||||
if (possibleSignature == ZipEndRecord.ECD_SIGNATURE) {
|
|
||||||
filePointer.seek(i)
|
|
||||||
return ZipEndRecord.fromECD(filePointer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception("Couldn't find end record")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readEntries(endRecord: ZipEndRecord): List<ZipEntry> {
|
|
||||||
filePointer.seek(endRecord.centralDirectoryStartOffset.toLong())
|
|
||||||
|
|
||||||
val numberOfEntries = endRecord.diskEntries.toInt()
|
|
||||||
|
|
||||||
return buildList(numberOfEntries) {
|
|
||||||
for (i in 1..numberOfEntries) {
|
|
||||||
add(
|
|
||||||
ZipEntry.fromCDE(filePointer).also
|
|
||||||
{
|
|
||||||
//for some reason the local extra field can be different from the central one
|
|
||||||
it.readLocalExtra(
|
|
||||||
filePointer.channel.map(
|
|
||||||
FileChannel.MapMode.READ_ONLY,
|
|
||||||
it.localHeaderOffset.toLong() + 28,
|
|
||||||
2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeCD() {
|
|
||||||
val CDStart = filePointer.channel.position().toUInt()
|
|
||||||
|
|
||||||
entries.forEach {
|
|
||||||
filePointer.channel.write(it.toCDE())
|
|
||||||
}
|
|
||||||
|
|
||||||
val entriesCount = entries.size.toUShort()
|
|
||||||
|
|
||||||
val endRecord = ZipEndRecord(
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
entriesCount,
|
|
||||||
entriesCount,
|
|
||||||
filePointer.channel.position().toUInt() - CDStart,
|
|
||||||
CDStart,
|
|
||||||
""
|
|
||||||
)
|
|
||||||
|
|
||||||
filePointer.channel.write(endRecord.toECD())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
|
||||||
CDNeedsRewrite = true
|
|
||||||
|
|
||||||
entry.localHeaderOffset = filePointer.channel.position().toUInt()
|
|
||||||
|
|
||||||
filePointer.channel.write(entry.toLFH())
|
|
||||||
filePointer.channel.write(data)
|
|
||||||
|
|
||||||
entries.add(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
|
|
||||||
val compressor = Deflater(compressionLevel, true)
|
|
||||||
compressor.setInput(data)
|
|
||||||
compressor.finish()
|
|
||||||
|
|
||||||
val uncompressedSize = data.size
|
|
||||||
val compressedData =
|
|
||||||
ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger
|
|
||||||
|
|
||||||
val compressedDataLength = compressor.deflate(compressedData)
|
|
||||||
val compressedBuffer =
|
|
||||||
ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
|
|
||||||
|
|
||||||
compressor.end()
|
|
||||||
|
|
||||||
val crc = CRC32()
|
|
||||||
crc.update(data)
|
|
||||||
|
|
||||||
entry.compression = 8u //deflate compression
|
|
||||||
entry.uncompressedSize = uncompressedSize.toUInt()
|
|
||||||
entry.compressedSize = compressedDataLength.toUInt()
|
|
||||||
entry.crc32 = crc.value.toUInt()
|
|
||||||
|
|
||||||
addEntry(entry, compressedBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) {
|
|
||||||
alignment?.let {
|
|
||||||
//calculate where data would end up
|
|
||||||
val dataOffset = filePointer.filePointer + entry.LFHSize
|
|
||||||
|
|
||||||
val mod = dataOffset % alignment
|
|
||||||
|
|
||||||
//wrong alignment
|
|
||||||
if (mod != 0L) {
|
|
||||||
//add padding at end of extra field
|
|
||||||
entry.localExtraField =
|
|
||||||
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addEntry(entry, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getDataForEntry(entry: ZipEntry): ByteBuffer {
|
|
||||||
return filePointer.channel.map(
|
|
||||||
FileChannel.MapMode.READ_ONLY,
|
|
||||||
entry.dataOffset.toLong(),
|
|
||||||
entry.compressedSize.toLong()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
|
|
||||||
for (entry in file.entries) {
|
|
||||||
if (entries.any { it.fileName == entry.fileName }) continue //don't add duplicates
|
|
||||||
|
|
||||||
val data = file.getDataForEntry(entry)
|
|
||||||
addEntryCopyData(entry, data, entryAlignment(entry))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
if (CDNeedsRewrite) writeCD()
|
|
||||||
filePointer.close()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
package app.revanced.manager.flutter.utils.zip.structures
|
|
||||||
|
|
||||||
import app.revanced.manager.flutter.utils.zip.putUInt
|
|
||||||
import app.revanced.manager.flutter.utils.zip.putUShort
|
|
||||||
import app.revanced.manager.flutter.utils.zip.readUIntLE
|
|
||||||
import app.revanced.manager.flutter.utils.zip.readUShortLE
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
data class ZipEndRecord(
|
|
||||||
val diskNumber: UShort,
|
|
||||||
val startingDiskNumber: UShort,
|
|
||||||
val diskEntries: UShort,
|
|
||||||
val totalEntries: UShort,
|
|
||||||
val centralDirectorySize: UInt,
|
|
||||||
val centralDirectoryStartOffset: UInt,
|
|
||||||
val fileComment: String,
|
|
||||||
) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val ECD_HEADER_SIZE = 22
|
|
||||||
const val ECD_SIGNATURE = 0x06054b50u
|
|
||||||
|
|
||||||
fun fromECD(input: DataInput): ZipEndRecord {
|
|
||||||
val signature = input.readUIntLE()
|
|
||||||
|
|
||||||
if (signature != ECD_SIGNATURE)
|
|
||||||
throw IllegalArgumentException("Input doesn't start with end record signature")
|
|
||||||
|
|
||||||
val diskNumber = input.readUShortLE()
|
|
||||||
val startingDiskNumber = input.readUShortLE()
|
|
||||||
val diskEntries = input.readUShortLE()
|
|
||||||
val totalEntries = input.readUShortLE()
|
|
||||||
val centralDirectorySize = input.readUIntLE()
|
|
||||||
val centralDirectoryStartOffset = input.readUIntLE()
|
|
||||||
val fileCommentLength = input.readUShortLE()
|
|
||||||
var fileComment = ""
|
|
||||||
|
|
||||||
if (fileCommentLength > 0u) {
|
|
||||||
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
|
||||||
input.readFully(fileCommentBytes)
|
|
||||||
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ZipEndRecord(
|
|
||||||
diskNumber,
|
|
||||||
startingDiskNumber,
|
|
||||||
diskEntries,
|
|
||||||
totalEntries,
|
|
||||||
centralDirectorySize,
|
|
||||||
centralDirectoryStartOffset,
|
|
||||||
fileComment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toECD(): ByteBuffer {
|
|
||||||
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size)
|
|
||||||
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
|
||||||
|
|
||||||
buffer.putUInt(ECD_SIGNATURE)
|
|
||||||
buffer.putUShort(diskNumber)
|
|
||||||
buffer.putUShort(startingDiskNumber)
|
|
||||||
buffer.putUShort(diskEntries)
|
|
||||||
buffer.putUShort(totalEntries)
|
|
||||||
buffer.putUInt(centralDirectorySize)
|
|
||||||
buffer.putUInt(centralDirectoryStartOffset)
|
|
||||||
buffer.putUShort(commentBytes.size.toUShort())
|
|
||||||
|
|
||||||
buffer.put(commentBytes)
|
|
||||||
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
package app.revanced.manager.flutter.utils.zip.structures
|
|
||||||
|
|
||||||
import app.revanced.manager.flutter.utils.zip.*
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
data class ZipEntry(
|
|
||||||
val version: UShort,
|
|
||||||
val versionNeeded: UShort,
|
|
||||||
val flags: UShort,
|
|
||||||
var compression: UShort,
|
|
||||||
val modificationTime: UShort,
|
|
||||||
val modificationDate: UShort,
|
|
||||||
var crc32: UInt,
|
|
||||||
var compressedSize: UInt,
|
|
||||||
var uncompressedSize: UInt,
|
|
||||||
val diskNumber: UShort,
|
|
||||||
val internalAttributes: UShort,
|
|
||||||
val externalAttributes: UInt,
|
|
||||||
var localHeaderOffset: UInt,
|
|
||||||
val fileName: String,
|
|
||||||
val extraField: ByteArray,
|
|
||||||
val fileComment: String,
|
|
||||||
var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
|
||||||
) {
|
|
||||||
val LFHSize: Int
|
|
||||||
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
|
||||||
|
|
||||||
val dataOffset: UInt
|
|
||||||
get() = localHeaderOffset + LFHSize.toUInt()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val CDE_HEADER_SIZE = 46
|
|
||||||
const val CDE_SIGNATURE = 0x02014b50u
|
|
||||||
|
|
||||||
const val LFH_HEADER_SIZE = 30
|
|
||||||
const val LFH_SIGNATURE = 0x04034b50u
|
|
||||||
|
|
||||||
fun createWithName(fileName: String): ZipEntry {
|
|
||||||
return ZipEntry(
|
|
||||||
0x1403u, //made by unix, version 20
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0x0821u, //seems to be static time google uses, no idea
|
|
||||||
0x0221u, //same as above
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
fileName,
|
|
||||||
ByteArray(0),
|
|
||||||
""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromCDE(input: DataInput): ZipEntry {
|
|
||||||
val signature = input.readUIntLE()
|
|
||||||
|
|
||||||
if (signature != CDE_SIGNATURE)
|
|
||||||
throw IllegalArgumentException("Input doesn't start with central directory entry signature")
|
|
||||||
|
|
||||||
val version = input.readUShortLE()
|
|
||||||
val versionNeeded = input.readUShortLE()
|
|
||||||
var flags = input.readUShortLE()
|
|
||||||
val compression = input.readUShortLE()
|
|
||||||
val modificationTime = input.readUShortLE()
|
|
||||||
val modificationDate = input.readUShortLE()
|
|
||||||
val crc32 = input.readUIntLE()
|
|
||||||
val compressedSize = input.readUIntLE()
|
|
||||||
val uncompressedSize = input.readUIntLE()
|
|
||||||
val fileNameLength = input.readUShortLE()
|
|
||||||
var fileName = ""
|
|
||||||
val extraFieldLength = input.readUShortLE()
|
|
||||||
val extraField = ByteArray(extraFieldLength.toInt())
|
|
||||||
val fileCommentLength = input.readUShortLE()
|
|
||||||
var fileComment = ""
|
|
||||||
val diskNumber = input.readUShortLE()
|
|
||||||
val internalAttributes = input.readUShortLE()
|
|
||||||
val externalAttributes = input.readUIntLE()
|
|
||||||
val localHeaderOffset = input.readUIntLE()
|
|
||||||
|
|
||||||
val variableFieldsLength =
|
|
||||||
fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
|
|
||||||
|
|
||||||
if (variableFieldsLength > 0) {
|
|
||||||
val fileNameBytes = ByteArray(fileNameLength.toInt())
|
|
||||||
input.readFully(fileNameBytes)
|
|
||||||
fileName = fileNameBytes.toString(Charsets.UTF_8)
|
|
||||||
|
|
||||||
input.readFully(extraField)
|
|
||||||
|
|
||||||
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
|
||||||
input.readFully(fileCommentBytes)
|
|
||||||
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
flags = (flags and 0b1000u.inv()
|
|
||||||
.toUShort()) //disable data descriptor flag as they are not used
|
|
||||||
|
|
||||||
return ZipEntry(
|
|
||||||
version,
|
|
||||||
versionNeeded,
|
|
||||||
flags,
|
|
||||||
compression,
|
|
||||||
modificationTime,
|
|
||||||
modificationDate,
|
|
||||||
crc32,
|
|
||||||
compressedSize,
|
|
||||||
uncompressedSize,
|
|
||||||
diskNumber,
|
|
||||||
internalAttributes,
|
|
||||||
externalAttributes,
|
|
||||||
localHeaderOffset,
|
|
||||||
fileName,
|
|
||||||
extraField,
|
|
||||||
fileComment,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readLocalExtra(buffer: ByteBuffer) {
|
|
||||||
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
localExtraField = ByteArray(buffer.getUShort().toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toLFH(): ByteBuffer {
|
|
||||||
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size)
|
|
||||||
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
|
||||||
|
|
||||||
buffer.putUInt(LFH_SIGNATURE)
|
|
||||||
buffer.putUShort(versionNeeded)
|
|
||||||
buffer.putUShort(flags)
|
|
||||||
buffer.putUShort(compression)
|
|
||||||
buffer.putUShort(modificationTime)
|
|
||||||
buffer.putUShort(modificationDate)
|
|
||||||
buffer.putUInt(crc32)
|
|
||||||
buffer.putUInt(compressedSize)
|
|
||||||
buffer.putUInt(uncompressedSize)
|
|
||||||
buffer.putUShort(nameBytes.size.toUShort())
|
|
||||||
buffer.putUShort(localExtraField.size.toUShort())
|
|
||||||
|
|
||||||
buffer.put(nameBytes)
|
|
||||||
buffer.put(localExtraField)
|
|
||||||
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toCDE(): ByteBuffer {
|
|
||||||
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
|
||||||
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val buffer =
|
|
||||||
ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
|
|
||||||
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
|
||||||
|
|
||||||
buffer.putUInt(CDE_SIGNATURE)
|
|
||||||
buffer.putUShort(version)
|
|
||||||
buffer.putUShort(versionNeeded)
|
|
||||||
buffer.putUShort(flags)
|
|
||||||
buffer.putUShort(compression)
|
|
||||||
buffer.putUShort(modificationTime)
|
|
||||||
buffer.putUShort(modificationDate)
|
|
||||||
buffer.putUInt(crc32)
|
|
||||||
buffer.putUInt(compressedSize)
|
|
||||||
buffer.putUInt(uncompressedSize)
|
|
||||||
buffer.putUShort(nameBytes.size.toUShort())
|
|
||||||
buffer.putUShort(extraField.size.toUShort())
|
|
||||||
buffer.putUShort(commentBytes.size.toUShort())
|
|
||||||
buffer.putUShort(diskNumber)
|
|
||||||
buffer.putUShort(internalAttributes)
|
|
||||||
buffer.putUInt(externalAttributes)
|
|
||||||
buffer.putUInt(localHeaderOffset)
|
|
||||||
|
|
||||||
buffer.put(nameBytes)
|
|
||||||
buffer.put(extraField)
|
|
||||||
buffer.put(commentBytes)
|
|
||||||
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 814 B |
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:drawable="?android:colorBackground" />
|
|
||||||
|
|
||||||
<!-- You can insert your own image assets here -->
|
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
|
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 13 KiB |
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:drawable="@android:color/white" />
|
|
||||||
|
|
||||||
<!-- You can insert your own image assets here -->
|
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 11 KiB |
@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
|
||||||
the Flutter engine draws its first frame -->
|
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_round</item>
|
|
||||||
</style>
|
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
|
||||||
This theme determines the color of the Android Window while your
|
|
||||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
|
||||||
running.
|
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
|
||||||
the Flutter engine draws its first frame -->
|
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
|
||||||
</style>
|
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
|
||||||
This theme determines the color of the Android Window while your
|
|
||||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
|
||||||
running.
|
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
|
||||||
the Flutter engine draws its first frame -->
|
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_round</item>
|
|
||||||
</style>
|
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
|
||||||
This theme determines the color of the Android Window while your
|
|
||||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
|
||||||
running.
|
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">#1B1B1B</color>
|
|
||||||
</resources>
|
|
@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
|
||||||
the Flutter engine draws its first frame -->
|
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
|
||||||
</style>
|
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
|
||||||
This theme determines the color of the Android Window while your
|
|
||||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
|
||||||
running.
|
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<paths>
|
|
||||||
<cache-path name="cache" path="." />
|
|
||||||
</paths>
|
|
@ -1,4 +0,0 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="app.revanced.manager.flutter">
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
</manifest>
|
|
@ -1,37 +0,0 @@
|
|||||||
buildscript {
|
|
||||||
ext.cronetVersion = '102.5005.125'
|
|
||||||
ext.kotlin_version = '1.7.20'
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
|
||||||
credentials {
|
|
||||||
username = (project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR")) as String
|
|
||||||
password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.buildDir = '../build'
|
|
||||||
subprojects {
|
|
||||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
|
||||||
project.evaluationDependsOn(':app')
|
|
||||||
}
|
|
||||||
|
|
||||||
task clean(type: Delete) {
|
|
||||||
delete rootProject.buildDir
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
org.gradle.jvmargs=-Xmx1536M
|
|
||||||
android.useAndroidX=true
|
|
||||||
android.enableJetifier=true
|
|
@ -1,11 +0,0 @@
|
|||||||
include ':app'
|
|
||||||
|
|
||||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
|
||||||
def properties = new Properties()
|
|
||||||
|
|
||||||
assert localPropertiesFile.exists()
|
|
||||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
|
||||||
|
|
||||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
|
||||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
|
||||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
|
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
208
app/build.gradle.kts
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
alias(libs.plugins.kotlin.parcelize)
|
||||||
|
alias(libs.plugins.compose.compiler)
|
||||||
|
alias(libs.plugins.devtools)
|
||||||
|
alias(libs.plugins.about.libraries)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.revanced.manager"
|
||||||
|
compileSdk = 35
|
||||||
|
buildToolsVersion = "35.0.0"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "app.revanced.manager"
|
||||||
|
minSdk = 26
|
||||||
|
targetSdk = 35
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "0.0.1"
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
applicationIdSuffix = ".debug"
|
||||||
|
resValue("string", "app_name", "ReVanced Manager (dev)")
|
||||||
|
|
||||||
|
buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
|
||||||
|
}
|
||||||
|
|
||||||
|
release {
|
||||||
|
if (!project.hasProperty("noProguard")) {
|
||||||
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty("signAsDebug")) {
|
||||||
|
applicationIdSuffix = ".debug"
|
||||||
|
resValue("string", "app_name", "ReVanced Manager Debug")
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
|
|
||||||
|
buildConfigField("long", "BUILD_ID", "0L")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
dependenciesInfo {
|
||||||
|
includeInApk = false
|
||||||
|
includeInBundle = false
|
||||||
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
resources.excludes.addAll(listOf(
|
||||||
|
"/prebuilt/**",
|
||||||
|
"META-INF/DEPENDENCIES",
|
||||||
|
"META-INF/**.version",
|
||||||
|
"DebugProbesKt.bin",
|
||||||
|
"kotlin-tooling-metadata.json",
|
||||||
|
"org/bouncycastle/pqc/**.properties",
|
||||||
|
"org/bouncycastle/x509/**.properties",
|
||||||
|
))
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksp {
|
||||||
|
arg("room.schemaLocation", "$projectDir/schemas")
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
aidl = true
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
androidResources {
|
||||||
|
generateLocaleConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path = file("src/main/cpp/CMakeLists.txt")
|
||||||
|
version = "3.22.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(17)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
// AndroidX Core
|
||||||
|
implementation(libs.androidx.ktx)
|
||||||
|
implementation(libs.runtime.ktx)
|
||||||
|
implementation(libs.runtime.compose)
|
||||||
|
implementation(libs.splash.screen)
|
||||||
|
implementation(libs.activity.compose)
|
||||||
|
implementation(libs.work.runtime.ktx)
|
||||||
|
implementation(libs.preferences.datastore)
|
||||||
|
implementation(libs.appcompat)
|
||||||
|
|
||||||
|
// Compose
|
||||||
|
implementation(platform(libs.compose.bom))
|
||||||
|
implementation(libs.compose.ui)
|
||||||
|
implementation(libs.compose.ui.preview)
|
||||||
|
implementation(libs.compose.ui.tooling)
|
||||||
|
implementation(libs.compose.livedata)
|
||||||
|
implementation(libs.compose.material.icons.extended)
|
||||||
|
implementation(libs.compose.material3)
|
||||||
|
implementation(libs.navigation.compose)
|
||||||
|
|
||||||
|
// Accompanist
|
||||||
|
implementation(libs.accompanist.drawablepainter)
|
||||||
|
|
||||||
|
// Placeholder
|
||||||
|
implementation(libs.placeholder.material3)
|
||||||
|
|
||||||
|
// HTML Scraper
|
||||||
|
implementation(libs.skrapeit.dsl)
|
||||||
|
implementation(libs.skrapeit.parser)
|
||||||
|
|
||||||
|
// Coil (async image loading, network image)
|
||||||
|
implementation(libs.coil.compose)
|
||||||
|
implementation(libs.coil.appiconloader)
|
||||||
|
|
||||||
|
// KotlinX
|
||||||
|
implementation(libs.kotlinx.serialization.json)
|
||||||
|
implementation(libs.kotlinx.collection.immutable)
|
||||||
|
implementation(libs.kotlinx.datetime)
|
||||||
|
|
||||||
|
// Room
|
||||||
|
implementation(libs.room.runtime)
|
||||||
|
implementation(libs.room.ktx)
|
||||||
|
annotationProcessor(libs.room.compiler)
|
||||||
|
ksp(libs.room.compiler)
|
||||||
|
|
||||||
|
// ReVanced
|
||||||
|
implementation(libs.revanced.patcher)
|
||||||
|
implementation(libs.revanced.library)
|
||||||
|
|
||||||
|
// Downloader plugins
|
||||||
|
implementation(project(":downloader-plugin"))
|
||||||
|
|
||||||
|
// Native processes
|
||||||
|
implementation(libs.kotlin.process)
|
||||||
|
|
||||||
|
// HiddenAPI
|
||||||
|
compileOnly(libs.hidden.api.stub)
|
||||||
|
|
||||||
|
// LibSU
|
||||||
|
implementation(libs.libsu.core)
|
||||||
|
implementation(libs.libsu.service)
|
||||||
|
implementation(libs.libsu.nio)
|
||||||
|
|
||||||
|
// Koin
|
||||||
|
implementation(libs.koin.android)
|
||||||
|
implementation(libs.koin.compose)
|
||||||
|
implementation(libs.koin.compose.navigation)
|
||||||
|
implementation(libs.koin.workmanager)
|
||||||
|
|
||||||
|
// Licenses
|
||||||
|
implementation(libs.about.libraries)
|
||||||
|
|
||||||
|
// Ktor
|
||||||
|
implementation(libs.ktor.core)
|
||||||
|
implementation(libs.ktor.logging)
|
||||||
|
implementation(libs.ktor.okhttp)
|
||||||
|
implementation(libs.ktor.content.negotiation)
|
||||||
|
implementation(libs.ktor.serialization)
|
||||||
|
|
||||||
|
// Markdown
|
||||||
|
implementation(libs.markdown.renderer)
|
||||||
|
|
||||||
|
// Fading Edges
|
||||||
|
implementation(libs.fading.edges)
|
||||||
|
|
||||||
|
// Scrollbars
|
||||||
|
implementation(libs.scrollbars)
|
||||||
|
|
||||||
|
// EnumUtil
|
||||||
|
implementation(libs.enumutil)
|
||||||
|
ksp(libs.enumutil.ksp)
|
||||||
|
|
||||||
|
// Reorderable lists
|
||||||
|
implementation(libs.reorderable)
|
||||||
|
|
||||||
|
// Compose Icons
|
||||||
|
implementation(libs.compose.icons.fontawesome)
|
||||||
|
}
|
63
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.kts.kts.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
-dontobfuscate
|
||||||
|
|
||||||
|
# Required for serialization to work properly
|
||||||
|
-if @kotlinx.serialization.Serializable class **
|
||||||
|
-keepclassmembers class <1> {
|
||||||
|
static <1>$Companion Companion;
|
||||||
|
}
|
||||||
|
-if @kotlinx.serialization.Serializable class ** {
|
||||||
|
static **$* *;
|
||||||
|
}
|
||||||
|
-keepclassmembers class <2>$<3> {
|
||||||
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
|
}
|
||||||
|
-if @kotlinx.serialization.Serializable class ** {
|
||||||
|
public static ** INSTANCE;
|
||||||
|
}
|
||||||
|
-keepclassmembers class <1> {
|
||||||
|
public static <1> INSTANCE;
|
||||||
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
# This required for the process runtime.
|
||||||
|
-keep class app.revanced.manager.patcher.runtime.process.* {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
# Required for the patcher to function correctly
|
||||||
|
-keep class app.revanced.patcher.** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
-keep class brut.** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
-keep class org.xmlpull.** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
-keep class kotlin.** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
-keep class org.jf.** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
-keep class com.android.** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
-keep class app.revanced.manager.plugin.** {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|
||||||
|
-dontwarn com.google.auto.value.**
|
||||||
|
-dontwarn java.awt.**
|
||||||
|
-dontwarn javax.**
|
||||||
|
-dontwarn org.slf4j.**
|
||||||
|
-dontwarn it.skrape.fetcher.*
|
||||||
|
-dontwarn com.google.j2objc.annotations.*
|
||||||
|
|
||||||
|
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
429
app/schemas/app.revanced.manager.data.room.AppDatabase/1.json
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 1,
|
||||||
|
"identityHash": "d0119047505da435972c5247181de675",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "patch_bundles",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `version` TEXT, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, PRIMARY KEY(`uid`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "version",
|
||||||
|
"columnName": "version",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "source",
|
||||||
|
"columnName": "source",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "autoUpdate",
|
||||||
|
"columnName": "auto_update",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "patch_selections",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `patch_bundle` INTEGER NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`uid`), FOREIGN KEY(`patch_bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "patchBundle",
|
||||||
|
"columnName": "patch_bundle",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "packageName",
|
||||||
|
"columnName": "package_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_patch_selections_patch_bundle_package_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"patch_bundle",
|
||||||
|
"package_name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_patch_selections_patch_bundle_package_name` ON `${TABLE_NAME}` (`patch_bundle`, `package_name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "patch_bundles",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"patch_bundle"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "selected_patches",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`selection` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`selection`, `patch_name`), FOREIGN KEY(`selection`) REFERENCES `patch_selections`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "selection",
|
||||||
|
"columnName": "selection",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "patchName",
|
||||||
|
"columnName": "patch_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"selection",
|
||||||
|
"patch_name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "patch_selections",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"selection"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "downloaded_app",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `directory` TEXT NOT NULL, `last_used` INTEGER NOT NULL, PRIMARY KEY(`package_name`, `version`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "packageName",
|
||||||
|
"columnName": "package_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "version",
|
||||||
|
"columnName": "version",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "directory",
|
||||||
|
"columnName": "directory",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastUsed",
|
||||||
|
"columnName": "last_used",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"package_name",
|
||||||
|
"version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "installed_app",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`current_package_name` TEXT NOT NULL, `original_package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `install_type` TEXT NOT NULL, PRIMARY KEY(`current_package_name`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "currentPackageName",
|
||||||
|
"columnName": "current_package_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalPackageName",
|
||||||
|
"columnName": "original_package_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "version",
|
||||||
|
"columnName": "version",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "installType",
|
||||||
|
"columnName": "install_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"current_package_name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "applied_patch",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `bundle` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`package_name`, `bundle`, `patch_name`), FOREIGN KEY(`package_name`) REFERENCES `installed_app`(`current_package_name`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "packageName",
|
||||||
|
"columnName": "package_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bundle",
|
||||||
|
"columnName": "bundle",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "patchName",
|
||||||
|
"columnName": "patch_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"package_name",
|
||||||
|
"bundle",
|
||||||
|
"patch_name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_applied_patch_bundle",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"bundle"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_applied_patch_bundle` ON `${TABLE_NAME}` (`bundle`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "installed_app",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"package_name"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"current_package_name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "patch_bundles",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"bundle"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "option_groups",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `patch_bundle` INTEGER NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`uid`), FOREIGN KEY(`patch_bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "patchBundle",
|
||||||
|
"columnName": "patch_bundle",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "packageName",
|
||||||
|
"columnName": "package_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_option_groups_patch_bundle_package_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"patch_bundle",
|
||||||
|
"package_name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_option_groups_patch_bundle_package_name` ON `${TABLE_NAME}` (`patch_bundle`, `package_name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "patch_bundles",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"patch_bundle"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "options",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, `key` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`group`, `patch_name`, `key`), FOREIGN KEY(`group`) REFERENCES `option_groups`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "group",
|
||||||
|
"columnName": "group",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "patchName",
|
||||||
|
"columnName": "patch_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "key",
|
||||||
|
"columnName": "key",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "value",
|
||||||
|
"columnName": "value",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"group",
|
||||||
|
"patch_name",
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "option_groups",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"group"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "trusted_downloader_plugins",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `signature` BLOB NOT NULL, PRIMARY KEY(`package_name`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "packageName",
|
||||||
|
"columnName": "package_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "signature",
|
||||||
|
"columnName": "signature",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"package_name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd0119047505da435972c5247181de675')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
78
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<permission
|
||||||
|
android:name="app.revanced.manager.permission.PLUGIN_HOST"
|
||||||
|
android:protectionLevel="signature"
|
||||||
|
android:label="@string/plugin_host_permission_label"
|
||||||
|
android:description="@string/plugin_host_permission_description"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<uses-permission android:name="app.revanced.manager.permission.PLUGIN_HOST" />
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
|
tools:ignore="ScopedStorage" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".ManagerApplication"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:largeHeap="true"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.ReVancedManager"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
|
tools:targetApi="34">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.ReVancedManager">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".plugin.downloader.webview.WebViewActivity" android:exported="false" android:theme="@style/Theme.WebViewActivity" />
|
||||||
|
|
||||||
|
<service android:name=".service.InstallService" />
|
||||||
|
<service android:name=".service.UninstallService" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||||
|
android:foregroundServiceType="specialUse"
|
||||||
|
android:exported="false"
|
||||||
|
tools:node="merge">
|
||||||
|
<property
|
||||||
|
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||||
|
android:value="patching"
|
||||||
|
/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.startup.InitializationProvider"
|
||||||
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
|
android:exported="false"
|
||||||
|
tools:node="merge">
|
||||||
|
<meta-data
|
||||||
|
android:name="androidx.work.WorkManagerInitializer"
|
||||||
|
android:value="androidx.startup"
|
||||||
|
tools:node="remove" />
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,8 @@
|
|||||||
|
// IRootService.aidl
|
||||||
|
package app.revanced.manager;
|
||||||
|
|
||||||
|
// Declare any non-default types here with import statements
|
||||||
|
|
||||||
|
interface IRootSystemService {
|
||||||
|
IBinder getFileSystemService();
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
// IPatcherEvents.aidl
|
||||||
|
package app.revanced.manager.patcher.runtime.process;
|
||||||
|
|
||||||
|
// Interface for sending events back to the main app process.
|
||||||
|
oneway interface IPatcherEvents {
|
||||||
|
void log(String level, String msg);
|
||||||
|
void patchSucceeded();
|
||||||
|
void progress(String name, String state, String msg);
|
||||||
|
// The patching process has ended. The exceptionStackTrace is null if it finished successfully.
|
||||||
|
void finished(String exceptionStackTrace);
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
// IPatcherProcess.aidl
|
||||||
|
package app.revanced.manager.patcher.runtime.process;
|
||||||
|
|
||||||
|
import app.revanced.manager.patcher.runtime.process.Parameters;
|
||||||
|
import app.revanced.manager.patcher.runtime.process.IPatcherEvents;
|
||||||
|
|
||||||
|
interface IPatcherProcess {
|
||||||
|
// Returns BuildConfig.BUILD_ID, which is used to ensure the main app and runner process are running the same code.
|
||||||
|
long buildId();
|
||||||
|
// Makes the patcher process exit with code 0
|
||||||
|
oneway void exit();
|
||||||
|
// Starts patching.
|
||||||
|
oneway void start(in Parameters parameters, IPatcherEvents events);
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
// Parameters.aidl
|
||||||
|
package app.revanced.manager.patcher.runtime.process;
|
||||||
|
|
||||||
|
parcelable Parameters;
|
6
app/src/main/assets/root/module.prop
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
id=__PKG_NAME__-ReVanced
|
||||||
|
name=__LABEL__ ReVanced
|
||||||
|
version=__VERSION__
|
||||||
|
versionCode=0
|
||||||
|
author=ReVanced
|
||||||
|
description=Mounts the patched APK on top of the original one
|
40
app/src/main/assets/root/service.sh
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#!/system/bin/sh
|
||||||
|
DIR=${0%/*}
|
||||||
|
|
||||||
|
package_name="__PKG_NAME__"
|
||||||
|
version="__VERSION__"
|
||||||
|
|
||||||
|
rm "$DIR/log"
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
until [ "$(getprop sys.boot_completed)" = 1 ]; do sleep 5; done
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
base_path="$DIR/$package_name.apk"
|
||||||
|
stock_path="$(pm path "$package_name" | grep base | sed 's/package://g')"
|
||||||
|
stock_version="$(dumpsys package "$package_name" | grep versionName | cut -d "=" -f2)"
|
||||||
|
|
||||||
|
echo "base_path: $base_path"
|
||||||
|
echo "stock_path: $stock_path"
|
||||||
|
echo "base_version: $version"
|
||||||
|
echo "stock_version: $stock_version"
|
||||||
|
|
||||||
|
if mount | grep -q "$stock_path" ; then
|
||||||
|
echo "Not mounting as stock path is already mounted"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$version" != "$stock_version" ]; then
|
||||||
|
echo "Not mounting as versions don't match"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$stock_path" ]; then
|
||||||
|
echo "Not mounting as app info could not be loaded"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mount -o bind "$base_path" "$stock_path"
|
||||||
|
|
||||||
|
} >> "$DIR/log"
|
38
app/src/main/cpp/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
# For more information about using CMake with Android Studio, read the
|
||||||
|
# documentation: https://d.android.com/studio/projects/add-native-code.html.
|
||||||
|
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
|
||||||
|
|
||||||
|
# Sets the minimum CMake version required for this project.
|
||||||
|
cmake_minimum_required(VERSION 3.22.1)
|
||||||
|
|
||||||
|
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
|
||||||
|
# Since this is the top level CMakeLists.txt, the project name is also accessible
|
||||||
|
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
|
||||||
|
# build script scope).
|
||||||
|
project("prop_override")
|
||||||
|
|
||||||
|
# Creates and names a library, sets it as either STATIC
|
||||||
|
# or SHARED, and provides the relative paths to its source code.
|
||||||
|
# You can define multiple libraries, and CMake builds them for you.
|
||||||
|
# Gradle automatically packages shared libraries with your APK.
|
||||||
|
#
|
||||||
|
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
|
||||||
|
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
|
||||||
|
# is preferred for the same purpose.
|
||||||
|
#
|
||||||
|
# In order to load a library into your app from Java/Kotlin, you must call
|
||||||
|
# System.loadLibrary() and pass the name of the library defined here;
|
||||||
|
# for GameActivity/NativeActivity derived applications, the same library name must be
|
||||||
|
# used in the AndroidManifest.xml file.
|
||||||
|
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
|
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||||
|
prop_override.cpp)
|
||||||
|
|
||||||
|
# Specifies libraries CMake should link to your target library. You
|
||||||
|
# can link libraries from various origins, such as libraries defined in this
|
||||||
|
# build script, prebuilt third-party libraries, or Android system libraries.
|
||||||
|
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||||
|
# List libraries link to the target library
|
||||||
|
android
|
||||||
|
log)
|
62
app/src/main/cpp/prop_override.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Library for overriding Android system properties via environment variables.
|
||||||
|
//
|
||||||
|
// Usage: LD_PRELOAD=prop_override.so PROP_dalvik.vm.heapsize=123M getprop dalvik.vm.heapsize
|
||||||
|
// Output: 123M
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
// Source: https://android.googlesource.com/platform/system/core/+/100b08a848d018eeb1caa5d5e7c7c2aaac65da15/libcutils/include/cutils/properties.h
|
||||||
|
#define PROP_VALUE_MAX 92
|
||||||
|
// This is the mangled name of "android::base::GetProperty".
|
||||||
|
#define GET_PROPERTY_MANGLED_NAME "_ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_"
|
||||||
|
|
||||||
|
extern "C" typedef int (*property_get_ptr)(const char *, char *, const char *);
|
||||||
|
typedef std::string (*GetProperty_ptr)(const std::string &, const std::string &);
|
||||||
|
|
||||||
|
char *GetPropOverride(const std::string &key) {
|
||||||
|
auto envKey = "PROP_" + key;
|
||||||
|
|
||||||
|
return getenv(envKey.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// See: https://android.googlesource.com/platform/system/core/+/100b08a848d018eeb1caa5d5e7c7c2aaac65da15/libcutils/properties.cpp
|
||||||
|
extern "C" int property_get(const char *key, char *value, const char *default_value) {
|
||||||
|
auto replacement = GetPropOverride(std::string(key));
|
||||||
|
if (replacement) {
|
||||||
|
int len = strnlen(replacement, PROP_VALUE_MAX);
|
||||||
|
|
||||||
|
strncpy(value, replacement, len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static property_get_ptr original = NULL;
|
||||||
|
if (!original) {
|
||||||
|
// Get the address of the original function.
|
||||||
|
original = reinterpret_cast<property_get_ptr>(dlsym(RTLD_NEXT, "property_get"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return original(key, value, default_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defining android::base::GetProperty ourselves won't work because std::string has a slightly different "path" in the NDK version of the C++ standard library.
|
||||||
|
// We can get around this by forcing the function to adopt a specific name using the asm keyword.
|
||||||
|
std::string GetProperty(const std::string &, const std::string &) asm(GET_PROPERTY_MANGLED_NAME);
|
||||||
|
|
||||||
|
|
||||||
|
// See: https://android.googlesource.com/platform/system/libbase/+/1a34bb67c4f3ba0a1ea6f4f20ac9fe117ba4fe64/properties.cpp
|
||||||
|
// This isn't used for the properties we want to override, but property_get is deprecated so that could change in the future.
|
||||||
|
std::string GetProperty(const std::string &key, const std::string &default_value) {
|
||||||
|
auto replacement = GetPropOverride(key);
|
||||||
|
if (replacement) {
|
||||||
|
return std::string(replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GetProperty_ptr original = NULL;
|
||||||
|
if (!original) {
|
||||||
|
original = reinterpret_cast<GetProperty_ptr>(dlsym(RTLD_NEXT, GET_PROPERTY_MANGLED_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
return original(key, default_value);
|
||||||
|
}
|
307
app/src/main/java/app/revanced/manager/MainActivity.kt
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
package app.revanced.manager
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.NavBackStackEntry
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.navigation
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import androidx.navigation.toRoute
|
||||||
|
import app.revanced.manager.ui.model.navigation.*
|
||||||
|
import app.revanced.manager.ui.screen.*
|
||||||
|
import app.revanced.manager.ui.screen.settings.*
|
||||||
|
import app.revanced.manager.ui.screen.settings.update.ChangelogsScreen
|
||||||
|
import app.revanced.manager.ui.screen.settings.update.UpdatesSettingsScreen
|
||||||
|
import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
||||||
|
import app.revanced.manager.ui.theme.Theme
|
||||||
|
import app.revanced.manager.ui.viewmodel.MainViewModel
|
||||||
|
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
|
||||||
|
import app.revanced.manager.util.EventEffect
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
import org.koin.androidx.compose.navigation.koinNavViewModel
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.getViewModel as getActivityViewModel
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
installSplashScreen()
|
||||||
|
|
||||||
|
val vm: MainViewModel = getActivityViewModel()
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
val launcher = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult(),
|
||||||
|
onResult = vm::applyLegacySettings
|
||||||
|
)
|
||||||
|
val theme by vm.prefs.theme.getAsState()
|
||||||
|
val dynamicColor by vm.prefs.dynamicColor.getAsState()
|
||||||
|
|
||||||
|
EventEffect(vm.legacyImportActivityFlow) {
|
||||||
|
try {
|
||||||
|
launcher.launch(it)
|
||||||
|
} catch (_: ActivityNotFoundException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReVancedManagerTheme(
|
||||||
|
darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK,
|
||||||
|
dynamicColor = dynamicColor
|
||||||
|
) {
|
||||||
|
ReVancedManager(vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReVancedManager(vm: MainViewModel) {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
|
||||||
|
EventEffect(vm.appSelectFlow) { app ->
|
||||||
|
navController.navigateComplex(
|
||||||
|
SelectedApplicationInfo,
|
||||||
|
SelectedApplicationInfo.ViewModelParams(app)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = Dashboard,
|
||||||
|
) {
|
||||||
|
composable<Dashboard> {
|
||||||
|
DashboardScreen(
|
||||||
|
onSettingsClick = { navController.navigate(Settings) },
|
||||||
|
onAppSelectorClick = {
|
||||||
|
navController.navigate(AppSelector)
|
||||||
|
},
|
||||||
|
onUpdateClick = {
|
||||||
|
navController.navigate(Update())
|
||||||
|
},
|
||||||
|
onDownloaderPluginClick = {
|
||||||
|
navController.navigate(Settings.Downloads)
|
||||||
|
},
|
||||||
|
onAppClick = { packageName ->
|
||||||
|
navController.navigate(InstalledApplicationInfo(packageName))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<InstalledApplicationInfo> {
|
||||||
|
val data = it.toRoute<InstalledApplicationInfo>()
|
||||||
|
|
||||||
|
InstalledAppInfoScreen(
|
||||||
|
onPatchClick = vm::selectApp,
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
viewModel = koinViewModel { parametersOf(data.packageName) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<AppSelector> {
|
||||||
|
AppSelectorScreen(
|
||||||
|
onSelect = vm::selectApp,
|
||||||
|
onStorageSelect = vm::selectApp,
|
||||||
|
onBackClick = navController::popBackStack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Patcher> {
|
||||||
|
PatcherScreen(
|
||||||
|
onBackClick = {
|
||||||
|
navController.navigate(route = Dashboard) {
|
||||||
|
launchSingleTop = true
|
||||||
|
popUpTo<Dashboard> {
|
||||||
|
inclusive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
vm = koinViewModel { parametersOf(it.getComplexArg<Patcher.ViewModelParams>()) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Update> {
|
||||||
|
val data = it.toRoute<Update>()
|
||||||
|
|
||||||
|
UpdateScreen(
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
vm = koinViewModel { parametersOf(data.downloadOnScreenEntry) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
navigation<SelectedApplicationInfo>(startDestination = SelectedApplicationInfo.Main) {
|
||||||
|
composable<SelectedApplicationInfo.Main> {
|
||||||
|
val parentBackStackEntry = navController.navGraphEntry(it)
|
||||||
|
val data =
|
||||||
|
parentBackStackEntry.getComplexArg<SelectedApplicationInfo.ViewModelParams>()
|
||||||
|
val viewModel =
|
||||||
|
koinNavViewModel<SelectedAppInfoViewModel>(viewModelStoreOwner = parentBackStackEntry) {
|
||||||
|
parametersOf(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedAppInfoScreen(
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
onPatchClick = {
|
||||||
|
it.lifecycleScope.launch {
|
||||||
|
navController.navigateComplex(
|
||||||
|
Patcher,
|
||||||
|
viewModel.getPatcherParams()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPatchSelectorClick = { app, patches, options ->
|
||||||
|
navController.navigateComplex(
|
||||||
|
SelectedApplicationInfo.PatchesSelector,
|
||||||
|
SelectedApplicationInfo.PatchesSelector.ViewModelParams(
|
||||||
|
app,
|
||||||
|
patches,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onRequiredOptions = { app, patches, options ->
|
||||||
|
navController.navigateComplex(
|
||||||
|
SelectedApplicationInfo.RequiredOptions,
|
||||||
|
SelectedApplicationInfo.PatchesSelector.ViewModelParams(
|
||||||
|
app,
|
||||||
|
patches,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
vm = viewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<SelectedApplicationInfo.PatchesSelector> {
|
||||||
|
val data =
|
||||||
|
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
|
||||||
|
val selectedAppInfoVm = koinNavViewModel<SelectedAppInfoViewModel>(
|
||||||
|
viewModelStoreOwner = navController.navGraphEntry(it)
|
||||||
|
)
|
||||||
|
|
||||||
|
PatchesSelectorScreen(
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
onSave = { patches, options ->
|
||||||
|
selectedAppInfoVm.updateConfiguration(patches, options)
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
vm = koinViewModel { parametersOf(data) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<SelectedApplicationInfo.RequiredOptions> {
|
||||||
|
val data =
|
||||||
|
it.getComplexArg<SelectedApplicationInfo.PatchesSelector.ViewModelParams>()
|
||||||
|
val selectedAppInfoVm = koinNavViewModel<SelectedAppInfoViewModel>(
|
||||||
|
viewModelStoreOwner = navController.navGraphEntry(it)
|
||||||
|
)
|
||||||
|
|
||||||
|
RequiredOptionsScreen(
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
onContinue = { patches, options ->
|
||||||
|
selectedAppInfoVm.updateConfiguration(patches, options)
|
||||||
|
it.lifecycleScope.launch {
|
||||||
|
navController.navigateComplex(
|
||||||
|
Patcher,
|
||||||
|
selectedAppInfoVm.getPatcherParams()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
vm = koinViewModel { parametersOf(data) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navigation<Settings>(startDestination = Settings.Main) {
|
||||||
|
composable<Settings.Main> {
|
||||||
|
SettingsScreen(
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
navigate = navController::navigate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.General> {
|
||||||
|
GeneralSettingsScreen(onBackClick = navController::popBackStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.Advanced> {
|
||||||
|
AdvancedSettingsScreen(onBackClick = navController::popBackStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.Updates> {
|
||||||
|
UpdatesSettingsScreen(
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
onChangelogClick = { navController.navigate(Settings.Changelogs) },
|
||||||
|
onUpdateClick = { navController.navigate(Update()) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.Downloads> {
|
||||||
|
DownloadsSettingsScreen(onBackClick = navController::popBackStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.ImportExport> {
|
||||||
|
ImportExportSettingsScreen(onBackClick = navController::popBackStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.About> {
|
||||||
|
AboutSettingsScreen(
|
||||||
|
onBackClick = navController::popBackStack,
|
||||||
|
navigate = navController::navigate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.Changelogs> {
|
||||||
|
ChangelogsScreen(onBackClick = navController::popBackStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.Contributors> {
|
||||||
|
ContributorScreen(onBackClick = navController::popBackStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.Licenses> {
|
||||||
|
LicensesScreen(onBackClick = navController::popBackStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable<Settings.DeveloperOptions> {
|
||||||
|
DeveloperOptionsScreen(onBackClick = navController::popBackStack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NavController.navGraphEntry(entry: NavBackStackEntry) =
|
||||||
|
remember(entry) { getBackStackEntry(entry.destination.parent!!.id) }
|
||||||
|
|
||||||
|
// Androidx Navigation does not support storing complex types in route objects, so we have to store them inside the saved state handle of the back stack entry instead.
|
||||||
|
private fun <T : Parcelable, R : ComplexParameter<T>> NavController.navigateComplex(
|
||||||
|
route: R,
|
||||||
|
data: T
|
||||||
|
) {
|
||||||
|
navigate(route)
|
||||||
|
getBackStackEntry(route).savedStateHandle["args"] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T : Parcelable> NavBackStackEntry.getComplexArg() = savedStateHandle.get<T>("args")!!
|
110
app/src/main/java/app/revanced/manager/ManagerApplication.kt
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package app.revanced.manager
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import app.revanced.manager.data.platform.Filesystem
|
||||||
|
import app.revanced.manager.di.*
|
||||||
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
|
import app.revanced.manager.domain.repository.DownloaderPluginRepository
|
||||||
|
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||||
|
import app.revanced.manager.util.tag
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import coil.Coil
|
||||||
|
import coil.ImageLoader
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.internal.BuilderImpl
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||||
|
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.android.ext.koin.androidLogger
|
||||||
|
import org.koin.androidx.workmanager.koin.workManagerFactory
|
||||||
|
import org.koin.core.context.startKoin
|
||||||
|
|
||||||
|
class ManagerApplication : Application() {
|
||||||
|
private val scope = MainScope()
|
||||||
|
private val prefs: PreferencesManager by inject()
|
||||||
|
private val patchBundleRepository: PatchBundleRepository by inject()
|
||||||
|
private val downloaderPluginRepository: DownloaderPluginRepository by inject()
|
||||||
|
private val fs: Filesystem by inject()
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
startKoin {
|
||||||
|
androidContext(this@ManagerApplication)
|
||||||
|
androidLogger()
|
||||||
|
workManagerFactory()
|
||||||
|
modules(
|
||||||
|
httpModule,
|
||||||
|
preferencesModule,
|
||||||
|
repositoryModule,
|
||||||
|
serviceModule,
|
||||||
|
managerModule,
|
||||||
|
workerModule,
|
||||||
|
viewModelModule,
|
||||||
|
databaseModule,
|
||||||
|
rootModule
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pixels = 512
|
||||||
|
Coil.setImageLoader(
|
||||||
|
ImageLoader.Builder(this)
|
||||||
|
.components {
|
||||||
|
add(AppIconKeyer())
|
||||||
|
add(AppIconFetcher.Factory(pixels, true, this@ManagerApplication))
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
val shellBuilder = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||||
|
Shell.setDefaultBuilder(shellBuilder)
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
prefs.preload()
|
||||||
|
}
|
||||||
|
scope.launch(Dispatchers.Default) {
|
||||||
|
downloaderPluginRepository.reload()
|
||||||
|
}
|
||||||
|
scope.launch(Dispatchers.Default) {
|
||||||
|
with(patchBundleRepository) {
|
||||||
|
reload()
|
||||||
|
updateCheck()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
|
||||||
|
private var firstActivityCreated = false
|
||||||
|
|
||||||
|
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||||
|
if (firstActivityCreated) return
|
||||||
|
firstActivityCreated = true
|
||||||
|
|
||||||
|
// We do not want to call onFreshProcessStart() if there is state to restore.
|
||||||
|
// This can happen on system-initiated process death.
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
Log.d(tag, "Fresh process created")
|
||||||
|
onFreshProcessStart()
|
||||||
|
} else Log.d(tag, "System-initiated process death detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStarted(activity: Activity) {}
|
||||||
|
override fun onActivityResumed(activity: Activity) {}
|
||||||
|
override fun onActivityPaused(activity: Activity) {}
|
||||||
|
override fun onActivityStopped(activity: Activity) {}
|
||||||
|
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
||||||
|
override fun onActivityDestroyed(activity: Activity) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onFreshProcessStart() {
|
||||||
|
fs.uiTempDir.apply {
|
||||||
|
deleteRecursively()
|
||||||
|
mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package app.revanced.manager.data.platform
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import app.revanced.manager.util.RequestManageStorageContract
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
class Filesystem(private val app: Application) {
|
||||||
|
val contentResolver = app.contentResolver // TODO: move Content Resolver operations to here.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A directory that gets cleared when the app restarts.
|
||||||
|
* Do not store paths to this directory in a parcel.
|
||||||
|
*/
|
||||||
|
val tempDir: File = app.getDir("ephemeral", Context.MODE_PRIVATE).apply {
|
||||||
|
deleteRecursively()
|
||||||
|
mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A directory for storing temporary files related to UI.
|
||||||
|
* This is the same as [tempDir], but does not get cleared on system-initiated process death.
|
||||||
|
* Paths to this directory can be safely stored in parcels.
|
||||||
|
*/
|
||||||
|
val uiTempDir: File = app.getDir("ui_ephemeral", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun externalFilesDir(): Path = Environment.getExternalStorageDirectory().toPath()
|
||||||
|
|
||||||
|
private fun usesManagePermission() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||||
|
|
||||||
|
private val storagePermissionName =
|
||||||
|
if (usesManagePermission()) Manifest.permission.MANAGE_EXTERNAL_STORAGE else Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
|
||||||
|
fun permissionContract(): Pair<ActivityResultContract<String, Boolean>, String> {
|
||||||
|
val contract =
|
||||||
|
if (usesManagePermission()) RequestManageStorageContract() else ActivityResultContracts.RequestPermission()
|
||||||
|
return contract to storagePermissionName
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasStoragePermission() =
|
||||||
|
if (usesManagePermission()) Environment.isExternalStorageManager() else app.checkSelfPermission(
|
||||||
|
storagePermissionName
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package app.revanced.manager.data.platform
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
|
||||||
|
class NetworkInfo(app: Application) {
|
||||||
|
private val connectivityManager = app.getSystemService<ConnectivityManager>()!!
|
||||||
|
|
||||||
|
private fun getCapabilities() = connectivityManager.activeNetwork?.let { connectivityManager.getNetworkCapabilities(it) }
|
||||||
|
fun isConnected() = connectivityManager.activeNetwork != null
|
||||||
|
fun isUnmetered() = getCapabilities()?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ?: true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if it is safe to download large files.
|
||||||
|
*/
|
||||||
|
fun isSafe() = isConnected() && isUnmetered()
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package app.revanced.manager.data.room
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.TypeConverters
|
||||||
|
import app.revanced.manager.data.room.apps.downloaded.DownloadedAppDao
|
||||||
|
import app.revanced.manager.data.room.apps.downloaded.DownloadedApp
|
||||||
|
import app.revanced.manager.data.room.apps.installed.AppliedPatch
|
||||||
|
import app.revanced.manager.data.room.apps.installed.InstalledApp
|
||||||
|
import app.revanced.manager.data.room.apps.installed.InstalledAppDao
|
||||||
|
import app.revanced.manager.data.room.selection.PatchSelection
|
||||||
|
import app.revanced.manager.data.room.selection.SelectedPatch
|
||||||
|
import app.revanced.manager.data.room.selection.SelectionDao
|
||||||
|
import app.revanced.manager.data.room.bundles.PatchBundleDao
|
||||||
|
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
||||||
|
import app.revanced.manager.data.room.options.Option
|
||||||
|
import app.revanced.manager.data.room.options.OptionDao
|
||||||
|
import app.revanced.manager.data.room.options.OptionGroup
|
||||||
|
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
|
||||||
|
import app.revanced.manager.data.room.plugins.TrustedDownloaderPluginDao
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@Database(
|
||||||
|
entities = [PatchBundleEntity::class, PatchSelection::class, SelectedPatch::class, DownloadedApp::class, InstalledApp::class, AppliedPatch::class, OptionGroup::class, Option::class, TrustedDownloaderPlugin::class],
|
||||||
|
version = 1
|
||||||
|
)
|
||||||
|
@TypeConverters(Converters::class)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun patchBundleDao(): PatchBundleDao
|
||||||
|
abstract fun selectionDao(): SelectionDao
|
||||||
|
abstract fun downloadedAppDao(): DownloadedAppDao
|
||||||
|
abstract fun installedAppDao(): InstalledAppDao
|
||||||
|
abstract fun optionDao(): OptionDao
|
||||||
|
abstract fun trustedDownloaderPluginDao(): TrustedDownloaderPluginDao
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun generateUid() = Random.Default.nextInt()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.manager.data.room
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import app.revanced.manager.data.room.bundles.Source
|
||||||
|
import app.revanced.manager.data.room.options.Option.SerializedValue
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class Converters {
|
||||||
|
@TypeConverter
|
||||||
|
fun sourceFromString(value: String) = Source.from(value)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun sourceToString(value: Source) = value.toString()
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fileFromString(value: String) = File(value)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fileToString(file: File): String = file.path
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun serializedOptionFromString(value: String) = SerializedValue.fromJsonString(value)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun serializedOptionToString(value: SerializedValue) = value.toJsonString()
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.manager.data.room.apps.downloaded
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "downloaded_app",
|
||||||
|
primaryKeys = ["package_name", "version"]
|
||||||
|
)
|
||||||
|
data class DownloadedApp(
|
||||||
|
@ColumnInfo(name = "package_name") val packageName: String,
|
||||||
|
@ColumnInfo(name = "version") val version: String,
|
||||||
|
@ColumnInfo(name = "directory") val directory: File,
|
||||||
|
@ColumnInfo(name = "last_used") val lastUsed: Long = System.currentTimeMillis()
|
||||||
|
)
|
@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.manager.data.room.apps.downloaded
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface DownloadedAppDao {
|
||||||
|
@Query("SELECT * FROM downloaded_app")
|
||||||
|
fun getAllApps(): Flow<List<DownloadedApp>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM downloaded_app WHERE package_name = :packageName AND version = :version")
|
||||||
|
suspend fun get(packageName: String, version: String): DownloadedApp?
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
suspend fun upsert(downloadedApp: DownloadedApp)
|
||||||
|
|
||||||
|
@Query("UPDATE downloaded_app SET last_used = :newValue WHERE package_name = :packageName AND version = :version")
|
||||||
|
suspend fun markUsed(packageName: String, version: String, newValue: Long = System.currentTimeMillis())
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(downloadedApps: Collection<DownloadedApp>)
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package app.revanced.manager.data.room.apps.installed
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.Index
|
||||||
|
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@Entity(
|
||||||
|
tableName = "applied_patch",
|
||||||
|
primaryKeys = ["package_name", "bundle", "patch_name"],
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
InstalledApp::class,
|
||||||
|
parentColumns = ["current_package_name"],
|
||||||
|
childColumns = ["package_name"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
),
|
||||||
|
ForeignKey(
|
||||||
|
PatchBundleEntity::class,
|
||||||
|
parentColumns = ["uid"],
|
||||||
|
childColumns = ["bundle"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
],
|
||||||
|
indices = [Index(value = ["bundle"], unique = false)]
|
||||||
|
)
|
||||||
|
data class AppliedPatch(
|
||||||
|
@ColumnInfo(name = "package_name") val packageName: String,
|
||||||
|
@ColumnInfo(name = "bundle") val bundle: Int,
|
||||||
|
@ColumnInfo(name = "patch_name") val patchName: String
|
||||||
|
) : Parcelable
|
@ -0,0 +1,20 @@
|
|||||||
|
package app.revanced.manager.data.room.apps.installed
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import app.revanced.manager.R
|
||||||
|
|
||||||
|
enum class InstallType(val stringResource: Int) {
|
||||||
|
DEFAULT(R.string.default_install),
|
||||||
|
MOUNT(R.string.mount_install)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(tableName = "installed_app")
|
||||||
|
data class InstalledApp(
|
||||||
|
@PrimaryKey
|
||||||
|
@ColumnInfo(name = "current_package_name") val currentPackageName: String,
|
||||||
|
@ColumnInfo(name = "original_package_name") val originalPackageName: String,
|
||||||
|
@ColumnInfo(name = "version") val version: String,
|
||||||
|
@ColumnInfo(name = "install_type") val installType: InstallType
|
||||||
|
)
|
@ -0,0 +1,46 @@
|
|||||||
|
package app.revanced.manager.data.room.apps.installed
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.MapColumn
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface InstalledAppDao {
|
||||||
|
@Query("SELECT * FROM installed_app")
|
||||||
|
fun getAll(): Flow<List<InstalledApp>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM installed_app WHERE current_package_name = :packageName")
|
||||||
|
suspend fun get(packageName: String): InstalledApp?
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT bundle, patch_name FROM applied_patch" +
|
||||||
|
" WHERE package_name = :packageName"
|
||||||
|
)
|
||||||
|
suspend fun getPatchesSelection(packageName: String): Map<@MapColumn("bundle") Int, List<@MapColumn(
|
||||||
|
"patch_name"
|
||||||
|
) String>>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
suspend fun upsertApp(installedApp: InstalledApp, appliedPatches: List<AppliedPatch>) {
|
||||||
|
upsertApp(installedApp)
|
||||||
|
deleteAppliedPatches(installedApp.currentPackageName)
|
||||||
|
insertAppliedPatches(appliedPatches)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
suspend fun upsertApp(installedApp: InstalledApp)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun insertAppliedPatches(appliedPatches: List<AppliedPatch>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM applied_patch WHERE package_name = :packageName")
|
||||||
|
suspend fun deleteAppliedPatches(packageName: String)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(installedApp: InstalledApp)
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package app.revanced.manager.data.room.bundles
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface PatchBundleDao {
|
||||||
|
@Query("SELECT * FROM patch_bundles")
|
||||||
|
suspend fun all(): List<PatchBundleEntity>
|
||||||
|
|
||||||
|
@Query("SELECT version, auto_update FROM patch_bundles WHERE uid = :uid")
|
||||||
|
fun getPropsById(uid: Int): Flow<BundleProperties?>
|
||||||
|
|
||||||
|
@Query("UPDATE patch_bundles SET version = :patches WHERE uid = :uid")
|
||||||
|
suspend fun updateVersion(uid: Int, patches: String?)
|
||||||
|
|
||||||
|
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
|
||||||
|
suspend fun setAutoUpdate(uid: Int, value: Boolean)
|
||||||
|
|
||||||
|
@Query("UPDATE patch_bundles SET name = :value WHERE uid = :uid")
|
||||||
|
suspend fun setName(uid: Int, value: String)
|
||||||
|
|
||||||
|
@Query("DELETE FROM patch_bundles WHERE uid != 0")
|
||||||
|
suspend fun purgeCustomBundles()
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
suspend fun reset() {
|
||||||
|
purgeCustomBundles()
|
||||||
|
updateVersion(0, null) // Reset the main source
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("DELETE FROM patch_bundles WHERE uid = :uid")
|
||||||
|
suspend fun remove(uid: Int)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun add(source: PatchBundleEntity)
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package app.revanced.manager.data.room.bundles
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
|
||||||
|
sealed class Source {
|
||||||
|
object Local : Source() {
|
||||||
|
const val SENTINEL = "local"
|
||||||
|
|
||||||
|
override fun toString() = SENTINEL
|
||||||
|
}
|
||||||
|
|
||||||
|
object API : Source() {
|
||||||
|
const val SENTINEL = "api"
|
||||||
|
|
||||||
|
override fun toString() = SENTINEL
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Remote(val url: Url) : Source() {
|
||||||
|
override fun toString() = url.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun from(value: String) = when (value) {
|
||||||
|
Local.SENTINEL -> Local
|
||||||
|
API.SENTINEL -> API
|
||||||
|
else -> Remote(Url(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(tableName = "patch_bundles")
|
||||||
|
data class PatchBundleEntity(
|
||||||
|
@PrimaryKey val uid: Int,
|
||||||
|
@ColumnInfo(name = "name") val name: String,
|
||||||
|
@ColumnInfo(name = "version") val version: String? = null,
|
||||||
|
@ColumnInfo(name = "source") val source: Source,
|
||||||
|
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BundleProperties(
|
||||||
|
@ColumnInfo(name = "version") val version: String? = null,
|
||||||
|
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||||
|
)
|
@ -0,0 +1,116 @@
|
|||||||
|
package app.revanced.manager.data.room.options
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import app.revanced.manager.patcher.patch.Option
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonNull
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
import kotlinx.serialization.json.add
|
||||||
|
import kotlinx.serialization.json.boolean
|
||||||
|
import kotlinx.serialization.json.buildJsonArray
|
||||||
|
import kotlinx.serialization.json.float
|
||||||
|
import kotlinx.serialization.json.int
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.long
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "options",
|
||||||
|
primaryKeys = ["group", "patch_name", "key"],
|
||||||
|
foreignKeys = [ForeignKey(
|
||||||
|
OptionGroup::class,
|
||||||
|
parentColumns = ["uid"],
|
||||||
|
childColumns = ["group"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)]
|
||||||
|
)
|
||||||
|
data class Option(
|
||||||
|
@ColumnInfo(name = "group") val group: Int,
|
||||||
|
@ColumnInfo(name = "patch_name") val patchName: String,
|
||||||
|
@ColumnInfo(name = "key") val key: String,
|
||||||
|
// Encoded as Json.
|
||||||
|
@ColumnInfo(name = "value") val value: SerializedValue,
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class SerializedValue(val raw: JsonElement) {
|
||||||
|
fun toJsonString() = json.encodeToString(raw)
|
||||||
|
fun deserializeFor(option: Option<*>): Any? {
|
||||||
|
if (raw is JsonNull) return null
|
||||||
|
|
||||||
|
val errorMessage = "Cannot deserialize value as ${option.type}"
|
||||||
|
try {
|
||||||
|
if (option.type.classifier == List::class) {
|
||||||
|
val elementType = option.type.arguments.first().type!!
|
||||||
|
return raw.jsonArray.map { deserializeBasicType(elementType, it.jsonPrimitive) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return deserializeBasicType(option.type, raw.jsonPrimitive)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw SerializationException(errorMessage, e)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
throw SerializationException(errorMessage, e)
|
||||||
|
} catch (e: kotlinx.serialization.SerializationException) {
|
||||||
|
throw SerializationException(errorMessage, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val json = Json {
|
||||||
|
// Patcher does not forbid the use of these values, so we should support them.
|
||||||
|
allowSpecialFloatingPointValues = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deserializeBasicType(type: KType, value: JsonPrimitive) = when (type) {
|
||||||
|
typeOf<Boolean>() -> value.boolean
|
||||||
|
typeOf<Int>() -> value.int
|
||||||
|
typeOf<Long>() -> value.long
|
||||||
|
typeOf<Float>() -> value.float
|
||||||
|
typeOf<String>() -> value.content.also {
|
||||||
|
if (!value.isString) throw SerializationException(
|
||||||
|
"Expected value to be a string: $value"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw SerializationException("Unknown type: $type")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromJsonString(value: String) = SerializedValue(json.decodeFromString(value))
|
||||||
|
fun fromValue(value: Any?) = SerializedValue(when (value) {
|
||||||
|
null -> JsonNull
|
||||||
|
is Number -> JsonPrimitive(value)
|
||||||
|
is Boolean -> JsonPrimitive(value)
|
||||||
|
is String -> JsonPrimitive(value)
|
||||||
|
is List<*> -> buildJsonArray {
|
||||||
|
var elementClass: KClass<out Any>? = null
|
||||||
|
|
||||||
|
value.forEach {
|
||||||
|
when (it) {
|
||||||
|
null -> throw SerializationException("List elements must not be null")
|
||||||
|
is Number -> add(it)
|
||||||
|
is Boolean -> add(it)
|
||||||
|
is String -> add(it)
|
||||||
|
else -> throw SerializationException("Unknown element type: ${it::class.simpleName}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementClass == null) elementClass = it::class
|
||||||
|
else if (elementClass != it::class) throw SerializationException("List elements must have the same type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw SerializationException("Unknown type: ${value::class.simpleName}")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SerializationException(message: String, cause: Throwable? = null) :
|
||||||
|
Exception(message, cause)
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package app.revanced.manager.data.room.options
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.MapColumn
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class OptionDao {
|
||||||
|
@Transaction
|
||||||
|
@Query(
|
||||||
|
"SELECT patch_bundle, `group`, patch_name, `key`, value FROM option_groups" +
|
||||||
|
" LEFT JOIN options ON uid = options.`group`" +
|
||||||
|
" WHERE package_name = :packageName"
|
||||||
|
)
|
||||||
|
abstract suspend fun getOptions(packageName: String): Map<@MapColumn("patch_bundle") Int, List<Option>>
|
||||||
|
|
||||||
|
@Query("SELECT uid FROM option_groups WHERE patch_bundle = :bundleUid AND package_name = :packageName")
|
||||||
|
abstract suspend fun getGroupId(bundleUid: Int, packageName: String): Int?
|
||||||
|
|
||||||
|
@Query("SELECT package_name FROM option_groups")
|
||||||
|
abstract fun getPackagesWithOptions(): Flow<List<String>>
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract suspend fun createOptionGroup(group: OptionGroup)
|
||||||
|
|
||||||
|
@Query("DELETE FROM option_groups WHERE patch_bundle = :uid")
|
||||||
|
abstract suspend fun clearForPatchBundle(uid: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM option_groups WHERE package_name = :packageName")
|
||||||
|
abstract suspend fun clearForPackage(packageName: String)
|
||||||
|
|
||||||
|
@Query("DELETE FROM option_groups")
|
||||||
|
abstract suspend fun reset()
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
protected abstract suspend fun insertOptions(patches: List<Option>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM options WHERE `group` = :groupId")
|
||||||
|
protected abstract suspend fun clearGroup(groupId: Int)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun updateOptions(options: Map<Int, List<Option>>) =
|
||||||
|
options.forEach { (groupId, options) ->
|
||||||
|
clearGroup(groupId)
|
||||||
|
insertOptions(options)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package app.revanced.manager.data.room.options
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.Index
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "option_groups",
|
||||||
|
foreignKeys = [ForeignKey(
|
||||||
|
PatchBundleEntity::class,
|
||||||
|
parentColumns = ["uid"],
|
||||||
|
childColumns = ["patch_bundle"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)],
|
||||||
|
indices = [Index(value = ["patch_bundle", "package_name"], unique = true)]
|
||||||
|
)
|
||||||
|
data class OptionGroup(
|
||||||
|
@PrimaryKey val uid: Int,
|
||||||
|
@ColumnInfo(name = "patch_bundle") val patchBundle: Int,
|
||||||
|
@ColumnInfo(name = "package_name") val packageName: String
|
||||||
|
)
|
@ -0,0 +1,11 @@
|
|||||||
|
package app.revanced.manager.data.room.plugins
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "trusted_downloader_plugins")
|
||||||
|
class TrustedDownloaderPlugin(
|
||||||
|
@PrimaryKey @ColumnInfo(name = "package_name") val packageName: String,
|
||||||
|
@ColumnInfo(name = "signature") val signature: ByteArray
|
||||||
|
)
|
@ -0,0 +1,22 @@
|
|||||||
|
package app.revanced.manager.data.room.plugins
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Upsert
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface TrustedDownloaderPluginDao {
|
||||||
|
@Query("SELECT signature FROM trusted_downloader_plugins WHERE package_name = :packageName")
|
||||||
|
suspend fun getTrustedSignature(packageName: String): ByteArray?
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
suspend fun upsertTrust(plugin: TrustedDownloaderPlugin)
|
||||||
|
|
||||||
|
@Query("DELETE FROM trusted_downloader_plugins WHERE package_name = :packageName")
|
||||||
|
suspend fun remove(packageName: String)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("DELETE FROM trusted_downloader_plugins WHERE package_name IN (:packageNames)")
|
||||||
|
suspend fun removeAll(packageNames: Set<String>)
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package app.revanced.manager.data.room.selection
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.Index
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "patch_selections",
|
||||||
|
foreignKeys = [ForeignKey(
|
||||||
|
PatchBundleEntity::class,
|
||||||
|
parentColumns = ["uid"],
|
||||||
|
childColumns = ["patch_bundle"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)],
|
||||||
|
indices = [Index(value = ["patch_bundle", "package_name"], unique = true)]
|
||||||
|
)
|
||||||
|
data class PatchSelection(
|
||||||
|
@PrimaryKey val uid: Int,
|
||||||
|
@ColumnInfo(name = "patch_bundle") val patchBundle: Int,
|
||||||
|
@ColumnInfo(name = "package_name") val packageName: String
|
||||||
|
)
|
@ -0,0 +1,20 @@
|
|||||||
|
package app.revanced.manager.data.room.selection
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "selected_patches",
|
||||||
|
primaryKeys = ["selection", "patch_name"],
|
||||||
|
foreignKeys = [ForeignKey(
|
||||||
|
PatchSelection::class,
|
||||||
|
parentColumns = ["uid"],
|
||||||
|
childColumns = ["selection"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)]
|
||||||
|
)
|
||||||
|
data class SelectedPatch(
|
||||||
|
@ColumnInfo(name = "selection") val selection: Int,
|
||||||
|
@ColumnInfo(name = "patch_name") val patchName: String
|
||||||
|
)
|
@ -0,0 +1,58 @@
|
|||||||
|
package app.revanced.manager.data.room.selection
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.MapColumn
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class SelectionDao {
|
||||||
|
@Transaction
|
||||||
|
@Query(
|
||||||
|
"SELECT patch_bundle, patch_name FROM patch_selections" +
|
||||||
|
" LEFT JOIN selected_patches ON uid = selected_patches.selection" +
|
||||||
|
" WHERE package_name = :packageName"
|
||||||
|
)
|
||||||
|
abstract suspend fun getSelectedPatches(packageName: String): Map<@MapColumn("patch_bundle") Int, List<@MapColumn(
|
||||||
|
"patch_name"
|
||||||
|
) String>>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query(
|
||||||
|
"SELECT package_name, patch_name FROM patch_selections" +
|
||||||
|
" LEFT JOIN selected_patches ON uid = selected_patches.selection" +
|
||||||
|
" WHERE patch_bundle = :bundleUid"
|
||||||
|
)
|
||||||
|
abstract suspend fun exportSelection(bundleUid: Int): Map<@MapColumn("package_name") String, List<@MapColumn(
|
||||||
|
"patch_name"
|
||||||
|
) String>>
|
||||||
|
|
||||||
|
@Query("SELECT uid FROM patch_selections WHERE patch_bundle = :bundleUid AND package_name = :packageName")
|
||||||
|
abstract suspend fun getSelectionId(bundleUid: Int, packageName: String): Int?
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract suspend fun createSelection(selection: PatchSelection)
|
||||||
|
|
||||||
|
@Query("DELETE FROM patch_selections WHERE patch_bundle = :uid")
|
||||||
|
abstract suspend fun clearForPatchBundle(uid: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM patch_selections WHERE package_name = :packageName")
|
||||||
|
abstract suspend fun clearForPackage(packageName: String)
|
||||||
|
|
||||||
|
@Query("DELETE FROM patch_selections")
|
||||||
|
abstract suspend fun reset()
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
protected abstract suspend fun selectPatches(patches: List<SelectedPatch>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM selected_patches WHERE selection = :selectionId")
|
||||||
|
protected abstract suspend fun clearSelection(selectionId: Int)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun updateSelections(selections: Map<Int, Set<String>>) =
|
||||||
|
selections.forEach { (selectionUid, patches) ->
|
||||||
|
clearSelection(selectionUid)
|
||||||
|
selectPatches(patches.map { SelectedPatch(selectionUid, it) })
|
||||||
|
}
|
||||||
|
}
|
15
app/src/main/java/app/revanced/manager/di/DatabaseModule.kt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.manager.di
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Room
|
||||||
|
import app.revanced.manager.data.room.AppDatabase
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val databaseModule = module {
|
||||||
|
fun provideAppDatabase(context: Context) = Room.databaseBuilder(context, AppDatabase::class.java, "manager").build()
|
||||||
|
|
||||||
|
single {
|
||||||
|
provideAppDatabase(androidContext())
|
||||||
|
}
|
||||||
|
}
|
60
app/src/main/java/app/revanced/manager/di/HttpModule.kt
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package app.revanced.manager.di
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import app.revanced.manager.BuildConfig
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.okhttp.*
|
||||||
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
|
import io.ktor.client.plugins.UserAgent
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Cache
|
||||||
|
import okhttp3.Dns
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import java.net.Inet4Address
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
val httpModule = module {
|
||||||
|
fun provideHttpClient(context: Context, json: Json) = HttpClient(OkHttp) {
|
||||||
|
engine {
|
||||||
|
config {
|
||||||
|
dns(object : Dns {
|
||||||
|
override fun lookup(hostname: String): List<InetAddress> {
|
||||||
|
val addresses = Dns.SYSTEM.lookup(hostname)
|
||||||
|
return if (hostname == "raw.githubusercontent.com") {
|
||||||
|
addresses.filterIsInstance<Inet4Address>()
|
||||||
|
} else {
|
||||||
|
addresses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cache(Cache(context.cacheDir.resolve("cache").also { it.mkdirs() }, 1024 * 1024 * 100))
|
||||||
|
followRedirects(true)
|
||||||
|
followSslRedirects(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(json)
|
||||||
|
}
|
||||||
|
install(HttpTimeout) {
|
||||||
|
socketTimeoutMillis = 10000
|
||||||
|
}
|
||||||
|
install(UserAgent) {
|
||||||
|
agent = "ReVanced-Manager/${BuildConfig.VERSION_CODE}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun provideJson() = Json {
|
||||||
|
encodeDefaults = true
|
||||||
|
isLenient = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
single {
|
||||||
|
provideHttpClient(androidContext(), get())
|
||||||
|
}
|
||||||
|
singleOf(::provideJson)
|
||||||
|
}
|
11
app/src/main/java/app/revanced/manager/di/ManagerModule.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package app.revanced.manager.di
|
||||||
|
|
||||||
|
import app.revanced.manager.domain.manager.KeystoreManager
|
||||||
|
import app.revanced.manager.util.PM
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val managerModule = module {
|
||||||
|
singleOf(::KeystoreManager)
|
||||||
|
singleOf(::PM)
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.manager.di
|
||||||
|
|
||||||
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val preferencesModule = module {
|
||||||
|
singleOf(::PreferencesManager)
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package app.revanced.manager.di
|
||||||
|
|
||||||
|
import app.revanced.manager.data.platform.Filesystem
|
||||||
|
import app.revanced.manager.data.platform.NetworkInfo
|
||||||
|
import app.revanced.manager.domain.repository.*
|
||||||
|
import app.revanced.manager.domain.worker.WorkerRepository
|
||||||
|
import app.revanced.manager.network.api.ReVancedAPI
|
||||||
|
import org.koin.core.module.dsl.createdAtStart
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val repositoryModule = module {
|
||||||
|
singleOf(::ReVancedAPI)
|
||||||
|
singleOf(::Filesystem) {
|
||||||
|
createdAtStart()
|
||||||
|
}
|
||||||
|
singleOf(::NetworkInfo)
|
||||||
|
singleOf(::PatchBundlePersistenceRepository)
|
||||||
|
singleOf(::PatchSelectionRepository)
|
||||||
|
singleOf(::PatchOptionsRepository)
|
||||||
|
singleOf(::PatchBundleRepository) {
|
||||||
|
// It is best to load patch bundles ASAP
|
||||||
|
createdAtStart()
|
||||||
|
}
|
||||||
|
singleOf(::DownloaderPluginRepository)
|
||||||
|
singleOf(::WorkerRepository)
|
||||||
|
singleOf(::DownloadedAppRepository)
|
||||||
|
singleOf(::InstalledAppRepository)
|
||||||
|
}
|
9
app/src/main/java/app/revanced/manager/di/RootModule.kt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.manager.di
|
||||||
|
|
||||||
|
import app.revanced.manager.domain.installer.RootInstaller
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val rootModule = module {
|
||||||
|
singleOf(::RootInstaller)
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.manager.di
|
||||||
|
|
||||||
|
import app.revanced.manager.network.service.HttpService
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val serviceModule = module {
|
||||||
|
singleOf(::HttpService)
|
||||||
|
}
|
26
app/src/main/java/app/revanced/manager/di/ViewModelModule.kt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.manager.di
|
||||||
|
|
||||||
|
import app.revanced.manager.ui.viewmodel.*
|
||||||
|
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val viewModelModule = module {
|
||||||
|
viewModelOf(::MainViewModel)
|
||||||
|
viewModelOf(::DashboardViewModel)
|
||||||
|
viewModelOf(::SelectedAppInfoViewModel)
|
||||||
|
viewModelOf(::PatchesSelectorViewModel)
|
||||||
|
viewModelOf(::GeneralSettingsViewModel)
|
||||||
|
viewModelOf(::AdvancedSettingsViewModel)
|
||||||
|
viewModelOf(::AppSelectorViewModel)
|
||||||
|
viewModelOf(::PatcherViewModel)
|
||||||
|
viewModelOf(::UpdateViewModel)
|
||||||
|
viewModelOf(::ChangelogsViewModel)
|
||||||
|
viewModelOf(::ImportExportViewModel)
|
||||||
|
viewModelOf(::AboutViewModel)
|
||||||
|
viewModelOf(::DeveloperOptionsViewModel)
|
||||||
|
viewModelOf(::ContributorViewModel)
|
||||||
|
viewModelOf(::DownloadsViewModel)
|
||||||
|
viewModelOf(::InstalledAppsViewModel)
|
||||||
|
viewModelOf(::InstalledAppInfoViewModel)
|
||||||
|
viewModelOf(::UpdatesSettingsViewModel)
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.manager.di
|
||||||
|
|
||||||
|
import app.revanced.manager.patcher.worker.PatcherWorker
|
||||||
|
import org.koin.androidx.workmanager.dsl.workerOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val workerModule = module {
|
||||||
|
workerOf(::PatcherWorker)
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package app.revanced.manager.domain.bundles
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
class LocalPatchBundle(name: String, id: Int, directory: File) :
|
||||||
|
PatchBundleSource(name, id, directory) {
|
||||||
|
suspend fun replace(patches: InputStream) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
patchBundleOutputStream().use { outputStream ->
|
||||||
|
patches.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reload()?.also {
|
||||||
|
saveVersion(it.readManifestAttribute("Version"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
package app.revanced.manager.domain.bundles
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository
|
||||||
|
import app.revanced.manager.patcher.patch.PatchBundle
|
||||||
|
import app.revanced.manager.util.tag
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import java.io.File
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [PatchBundle] source.
|
||||||
|
*/
|
||||||
|
@Stable
|
||||||
|
sealed class PatchBundleSource(initialName: String, val uid: Int, directory: File) : KoinComponent {
|
||||||
|
protected val configRepository: PatchBundlePersistenceRepository by inject()
|
||||||
|
private val app: Application by inject()
|
||||||
|
protected val patchesFile = directory.resolve("patches.jar")
|
||||||
|
|
||||||
|
private val _state = MutableStateFlow(load())
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
private val _nameFlow = MutableStateFlow(initialName)
|
||||||
|
val nameFlow =
|
||||||
|
_nameFlow.map { it.ifEmpty { app.getString(if (isDefault) R.string.bundle_name_default else R.string.bundle_name_fallback) } }
|
||||||
|
|
||||||
|
suspend fun getName() = nameFlow.first()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the bundle has been downloaded to local storage.
|
||||||
|
*/
|
||||||
|
fun hasInstalled() = patchesFile.exists()
|
||||||
|
|
||||||
|
protected fun patchBundleOutputStream(): OutputStream = with(patchesFile) {
|
||||||
|
// Android 14+ requires dex containers to be readonly.
|
||||||
|
try {
|
||||||
|
setWritable(true, true)
|
||||||
|
outputStream()
|
||||||
|
} finally {
|
||||||
|
setReadOnly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun load(): State {
|
||||||
|
if (!hasInstalled()) return State.Missing
|
||||||
|
|
||||||
|
return try {
|
||||||
|
State.Loaded(PatchBundle(patchesFile))
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.e(tag, "Failed to load patch bundle with UID $uid", t)
|
||||||
|
State.Failed(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun reload(): PatchBundle? {
|
||||||
|
val newState = load()
|
||||||
|
_state.value = newState
|
||||||
|
|
||||||
|
val bundle = newState.patchBundleOrNull()
|
||||||
|
// Try to read the name from the patch bundle manifest if the bundle does not have a name.
|
||||||
|
if (bundle != null && _nameFlow.value.isEmpty()) {
|
||||||
|
bundle.readManifestAttribute("Name")?.let { setName(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundle
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a flow that emits the [app.revanced.manager.data.room.bundles.BundleProperties] of this [PatchBundleSource].
|
||||||
|
* The flow will emit null if the associated [PatchBundleSource] is deleted.
|
||||||
|
*/
|
||||||
|
fun propsFlow() = configRepository.getProps(uid).flowOn(Dispatchers.Default)
|
||||||
|
suspend fun getProps() = propsFlow().first()!!
|
||||||
|
|
||||||
|
suspend fun currentVersion() = getProps().version
|
||||||
|
protected suspend fun saveVersion(version: String?) =
|
||||||
|
configRepository.updateVersion(uid, version)
|
||||||
|
|
||||||
|
suspend fun setName(name: String) {
|
||||||
|
configRepository.setName(uid, name)
|
||||||
|
_nameFlow.value = name
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface State {
|
||||||
|
fun patchBundleOrNull(): PatchBundle? = null
|
||||||
|
|
||||||
|
data object Missing : State
|
||||||
|
data class Failed(val throwable: Throwable) : State
|
||||||
|
data class Loaded(val bundle: PatchBundle) : State {
|
||||||
|
override fun patchBundleOrNull() = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Extensions {
|
||||||
|
val PatchBundleSource.isDefault inline get() = uid == 0
|
||||||
|
val PatchBundleSource.asRemoteOrNull inline get() = this as? RemotePatchBundle
|
||||||
|
val PatchBundleSource.nameState
|
||||||
|
@Composable inline get() = nameFlow.collectAsStateWithLifecycle(
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|