mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-18 17:03:56 +00:00
Compare commits
532 Commits
v2.2.0-dev
...
v19.3.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc5c16f112 | ||
|
|
f1d7217495 | ||
|
|
64dd1526cd | ||
|
|
c9a82608f7 | ||
|
|
9fc42e132c | ||
|
|
efa98ece45 | ||
|
|
68e2acebba | ||
|
|
7a7a8fc353 | ||
|
|
f8306ac43d | ||
|
|
d03591b735 | ||
|
|
4a9184597b | ||
|
|
0a482f8c9a | ||
|
|
e7dacfba8c | ||
|
|
2d7fffd4ec | ||
|
|
f8baabbcec | ||
|
|
716825f232 | ||
|
|
58bd46750b | ||
|
|
288240f163 | ||
|
|
ff02452cb8 | ||
|
|
462fbe2cad | ||
|
|
7aeae93f3d | ||
|
|
f1de9b39ef | ||
|
|
db5b0ed7be | ||
|
|
9f28a01c03 | ||
|
|
80407b6102 | ||
|
|
287841d806 | ||
|
|
10c3be1195 | ||
|
|
0c0e22013b | ||
|
|
f35c8d4446 | ||
|
|
17418d4b9c | ||
|
|
ec1fbdf2ae | ||
|
|
56e5a46fd5 | ||
|
|
32e86d44a3 | ||
|
|
7100606dfc | ||
|
|
d7eb111460 | ||
|
|
27ea46653e | ||
|
|
12c43072cb | ||
|
|
671aa6d507 | ||
|
|
b697bbad2b | ||
|
|
f05a404e48 | ||
|
|
a46e948b5a | ||
|
|
dc09ea639f | ||
|
|
49ed096e85 | ||
|
|
167bd83f4e | ||
|
|
aed1eac315 | ||
|
|
54a2f8f16f | ||
|
|
2ca543ffb9 | ||
|
|
58e7f815a5 | ||
|
|
15b38fc841 | ||
|
|
e2ca50729d | ||
|
|
6192089b71 | ||
|
|
a4212f6bf9 | ||
|
|
124a2e9d3e | ||
|
|
f77624b3b9 | ||
|
|
a76ac04214 | ||
|
|
0447fa9c28 | ||
|
|
54ac1394a9 | ||
|
|
0b04c73ac5 | ||
|
|
079de45238 | ||
|
|
56ce9ec2f9 | ||
|
|
1b52e4b0f9 | ||
|
|
098c2c1efa | ||
|
|
64343e5a7c | ||
|
|
0caf6caeb9 | ||
|
|
55f6c2a9fc | ||
|
|
c6095bc38a | ||
|
|
09cd6aa568 | ||
|
|
ebbaafb78e | ||
|
|
e6de90d300 | ||
|
|
c7922e90d0 | ||
|
|
c1f4c0445a | ||
|
|
caa634fac6 | ||
|
|
f28bfe0dbd | ||
|
|
155e787ff4 | ||
|
|
c38f0ef42a | ||
|
|
4456031459 | ||
|
|
5fb59a227f | ||
|
|
d9fb241d57 | ||
|
|
642c4ea97e | ||
|
|
8c8a251626 | ||
|
|
5953d6cfb5 | ||
|
|
a1962fe600 | ||
|
|
77dbee3d6a | ||
|
|
cb5e39d73e | ||
|
|
38ef2f470a | ||
|
|
129d84e108 | ||
|
|
affeba76b8 | ||
|
|
6059d3ca26 | ||
|
|
444dee5a16 | ||
|
|
d314466ce2 | ||
|
|
fdaf9c21c8 | ||
|
|
06c2b76f11 | ||
|
|
3896b30738 | ||
|
|
2c4b88e1a0 | ||
|
|
dfc7e1596b | ||
|
|
f590436399 | ||
|
|
cbfb9ba02f | ||
|
|
b4cfe80ad5 | ||
|
|
b37906fa35 | ||
|
|
356f1f1553 | ||
|
|
e882af74ee | ||
|
|
46875fb28e | ||
|
|
417c3e4234 | ||
|
|
6d2c28807b | ||
|
|
4d6e08a650 | ||
|
|
5cebc1fd30 | ||
|
|
ac61731dc6 | ||
|
|
9e4ffabd5c | ||
|
|
3f410bd39f | ||
|
|
d51bc32e37 | ||
|
|
b7f6aa94cc | ||
|
|
ff965e6953 | ||
|
|
468d5d7421 | ||
|
|
fc95b28c49 | ||
|
|
69184187d9 | ||
|
|
a802d0df46 | ||
|
|
8de30633ae | ||
|
|
a1fbb7990f | ||
|
|
aa71146b1b | ||
|
|
9fdb8f087f | ||
|
|
670f0153de | ||
|
|
1d7aeca696 | ||
|
|
4e7811ea07 | ||
|
|
e11283744a | ||
|
|
91cdfd53ef | ||
|
|
bc7d6b9941 | ||
|
|
6b1e0a1656 | ||
|
|
72c9eb2129 | ||
|
|
4bc4b0dc01 | ||
|
|
637d48746f | ||
|
|
9a109c129b | ||
|
|
d49e4ee5ea | ||
|
|
30f0ea29a3 | ||
|
|
49930f6565 | ||
|
|
909d89fa8d | ||
|
|
81d1d7f544 | ||
|
|
67b7dff67a | ||
|
|
4b76d19596 | ||
|
|
080fbe9feb | ||
|
|
d3721229bf | ||
|
|
86c1c9c772 | ||
|
|
c299817193 | ||
|
|
fcc1de45ed | ||
|
|
a29931f2ec | ||
|
|
3fc6a139ee | ||
|
|
4dd04975d9 | ||
|
|
3b4db3ddb7 | ||
|
|
c4a7117ee8 | ||
|
|
b4e900fde8 | ||
|
|
9818d730e4 | ||
|
|
11a3378659 | ||
|
|
1bb05f22d3 | ||
|
|
26b70554c4 | ||
|
|
93b29d2e83 | ||
|
|
072986374a | ||
|
|
2c590d212a | ||
|
|
6cc863efb3 | ||
|
|
b832812767 | ||
|
|
c44558cacd | ||
|
|
6d83a720cd | ||
|
|
8d0dd9c448 | ||
|
|
64020eec49 | ||
|
|
4dedfb85cb | ||
|
|
55d694579a | ||
|
|
86db64edff | ||
|
|
983563efb6 | ||
|
|
37abb2db99 | ||
|
|
5ba0b47e60 | ||
|
|
e8f2087a6f | ||
|
|
6ce99f5cdf | ||
|
|
13c0c9cdd3 | ||
|
|
58ffdb60d7 | ||
|
|
ba56a6a2ee | ||
|
|
ccccf5b1d2 | ||
|
|
b507ac0a54 | ||
|
|
e985676c2d | ||
|
|
f7f4ba6c55 | ||
|
|
4292f43814 | ||
|
|
30bd4fd9fe | ||
|
|
76de39369d | ||
|
|
88a703ce36 | ||
|
|
5938f6b7ea | ||
|
|
5c0c0d6c37 | ||
|
|
0f15077225 | ||
|
|
273dd8d388 | ||
|
|
1795f376ef | ||
|
|
e7360a7692 | ||
|
|
e1fc86934f | ||
|
|
6b8977f178 | ||
|
|
12c6c73de0 | ||
|
|
db62a1607b | ||
|
|
58bb879ef5 | ||
|
|
254912438a | ||
|
|
0e48918bcc | ||
|
|
783ccf8529 | ||
|
|
8fb2f2dc1d | ||
|
|
2a8cc283c7 | ||
|
|
433fe3af9f | ||
|
|
c2d89c622e | ||
|
|
02d6ff15fe | ||
|
|
f2cb7ee7df | ||
|
|
a2ac44dcc1 | ||
|
|
3cf9d74efa | ||
|
|
d5f89a903f | ||
|
|
496c2242bc | ||
|
|
98fbff87df | ||
|
|
ddb51a1c45 | ||
|
|
8df1155215 | ||
|
|
53f2a61409 | ||
|
|
746544f9d5 | ||
|
|
c65c3df11c | ||
|
|
b29b8f12b3 | ||
|
|
d6945677c4 | ||
|
|
aedf4aea08 | ||
|
|
dc28d414dc | ||
|
|
9755bab298 | ||
|
|
fae4029cfc | ||
|
|
1790f0d706 | ||
|
|
0ba2c51676 | ||
|
|
03cd97b49c | ||
|
|
16a162c1dd | ||
|
|
c9bbcf2bf2 | ||
|
|
86e1bf6078 | ||
|
|
1bca84ef0b | ||
|
|
e0f8e1b71a | ||
|
|
416d69142f | ||
|
|
426807aeaa | ||
|
|
90cb075a97 | ||
|
|
ac2ca8fbd3 | ||
|
|
69e4a49065 | ||
|
|
a4a030f2b2 | ||
|
|
dcc4ecd237 | ||
|
|
57f3036a96 | ||
|
|
753e55dfc3 | ||
|
|
9d81baf4b4 | ||
|
|
7cb4d4c596 | ||
|
|
2c8565508e | ||
|
|
c7f156e4c9 | ||
|
|
fcef4342e8 | ||
|
|
72783a5e74 | ||
|
|
a379b69eeb | ||
|
|
0a8ccba33e | ||
|
|
519359a9eb | ||
|
|
b615ed6aab | ||
|
|
d718134ab2 | ||
|
|
5e681ed381 | ||
|
|
6e1b6479b6 | ||
|
|
f3c9e28a62 | ||
|
|
d5d6f85084 | ||
|
|
b8151ebccb | ||
|
|
5650e34432 | ||
|
|
c893d16d52 | ||
|
|
34f08bf206 | ||
|
|
f02a42610b | ||
|
|
c95e6fa92f | ||
|
|
fd738e723b | ||
|
|
b1d1956323 | ||
|
|
725a8012ac | ||
|
|
bb9a73e53b | ||
|
|
ef2de35a74 | ||
|
|
2a453d51a8 | ||
|
|
43d6868d1f | ||
|
|
cea9379b32 | ||
|
|
a12fe7dd9e | ||
|
|
efdd01a988 | ||
|
|
eafe1c631f | ||
|
|
aacf900764 | ||
|
|
f82494e9bb | ||
|
|
1e0ffa176e | ||
|
|
b7eb2d2249 | ||
|
|
b6d6a7591b | ||
|
|
8f1c835299 | ||
|
|
a188c16a99 | ||
|
|
3e6804f06c | ||
|
|
526a3d7c35 | ||
|
|
28fc6a2ddd | ||
|
|
d4f08d7bff | ||
|
|
ca9fe322eb | ||
|
|
239ea0bcaa | ||
|
|
7f02b8df48 | ||
|
|
a2052202b2 | ||
|
|
223cea7021 | ||
|
|
ac9337f694 | ||
|
|
549651d04a | ||
|
|
966bbd902e | ||
|
|
81e6f8784e | ||
|
|
9c53877888 | ||
|
|
98f8eedecd | ||
|
|
4ed429d25c | ||
|
|
119d05f469 | ||
|
|
2432fde6bf | ||
|
|
49c173dc14 | ||
|
|
d83e9372bb | ||
|
|
7e8cd3bede | ||
|
|
d67436271d | ||
|
|
aa07f35f06 | ||
|
|
77e0536838 | ||
|
|
a49e78234b | ||
|
|
a3ae825e48 | ||
|
|
146c8504ed | ||
|
|
2eb125ad69 | ||
|
|
6e24a85eab | ||
|
|
e4c3e9ffc5 | ||
|
|
4c1778a62f | ||
|
|
d99261cdbb | ||
|
|
ac1c0f2773 | ||
|
|
eddd4ec7ac | ||
|
|
07a2829c65 | ||
|
|
3d77e299d9 | ||
|
|
f1336f89e4 | ||
|
|
0502f84c20 | ||
|
|
058d292ad5 | ||
|
|
1029d56a52 | ||
|
|
709b5a0fec | ||
|
|
e1accc5041 | ||
|
|
6dbbf2e03e | ||
|
|
16557eeab0 | ||
|
|
6bca3e2bb5 | ||
|
|
a263fdfd41 | ||
|
|
e4b4bacae8 | ||
|
|
cbc97af155 | ||
|
|
d5533788e2 | ||
|
|
5a4ea5cd7d | ||
|
|
70f3c8b38c | ||
|
|
6b410a0eea | ||
|
|
73a013d75b | ||
|
|
7159f3db4c | ||
|
|
7d5ecf095c | ||
|
|
fa015a424d | ||
|
|
dd7dd38357 | ||
|
|
22356f2d26 | ||
|
|
66701f6076 | ||
|
|
6a6ded084e | ||
|
|
5887c69bde | ||
|
|
4102f43b8a | ||
|
|
5c09ef7837 | ||
|
|
3e0bf8c863 | ||
|
|
8f3ecc318c | ||
|
|
365da96e2b | ||
|
|
cd68ec4803 | ||
|
|
35265e029c | ||
|
|
9f0a09a756 | ||
|
|
e802141df5 | ||
|
|
abebc0862c | ||
|
|
96ef150e89 | ||
|
|
c5de9e2988 | ||
|
|
c391ca648b | ||
|
|
7cf79e68e0 | ||
|
|
f07db3c214 | ||
|
|
88bb3a8845 | ||
|
|
b9e6bd6775 | ||
|
|
cd1b72e078 | ||
|
|
6b889557ab | ||
|
|
4b1be8c647 | ||
|
|
73c893c6e7 | ||
|
|
75b36823b8 | ||
|
|
d2d93cd075 | ||
|
|
26b8621ac8 | ||
|
|
f365a41741 | ||
|
|
9ec720e983 | ||
|
|
0f432b3fdd | ||
|
|
96cd5618dd | ||
|
|
c2a5a55e67 | ||
|
|
6c5de8b414 | ||
|
|
ea773cfa56 | ||
|
|
a306561b55 | ||
|
|
b6dcd88495 | ||
|
|
a925650044 | ||
|
|
77bbf6be1f | ||
|
|
bd053b7e99 | ||
|
|
fd742eba63 | ||
|
|
ba9d998681 | ||
|
|
75df245ec3 | ||
|
|
4164cb0dea | ||
|
|
18fe35ae73 | ||
|
|
f9bc95f220 | ||
|
|
d2f91a8545 | ||
|
|
4016bdc37f | ||
|
|
538b2a8599 | ||
|
|
4aa14bbb85 | ||
|
|
d37452997b | ||
|
|
db21d5e953 | ||
|
|
4d581811db | ||
|
|
8c502448be | ||
|
|
fec16d9442 | ||
|
|
5583904994 | ||
|
|
797286b758 | ||
|
|
4ae9ad09d6 | ||
|
|
447e1ad30e | ||
|
|
843e62ad29 | ||
|
|
9c07ffcc7a | ||
|
|
438321330e | ||
|
|
3ba4be240b | ||
|
|
98ce0abfa9 | ||
|
|
db4348c4fa | ||
|
|
4839f87519 | ||
|
|
809862c997 | ||
|
|
fd5c878cee | ||
|
|
124332f0e9 | ||
|
|
d4cf0cea52 | ||
|
|
76676fb567 | ||
|
|
d802ef844e | ||
|
|
90fc547673 | ||
|
|
3813e28ac2 | ||
|
|
a2bb4004c7 | ||
|
|
a0cb449c60 | ||
|
|
e0271790b8 | ||
|
|
4bfd7ebff8 | ||
|
|
2f7e62ef65 | ||
|
|
4485af8036 | ||
|
|
085a3a479d | ||
|
|
f75c9a78b8 | ||
|
|
172655bde0 | ||
|
|
456db7289a | ||
|
|
e722e3f4f9 | ||
|
|
c348c1f0a0 | ||
|
|
ed1851013e | ||
|
|
e31ac1f132 | ||
|
|
8f78f85e4a | ||
|
|
0be2677519 | ||
|
|
b873228ef0 | ||
|
|
639ff1c0ba | ||
|
|
f30671ddd1 | ||
|
|
76c45dd7c1 | ||
|
|
1bafb77355 | ||
|
|
25f74dc5e9 | ||
|
|
6e73631d4d | ||
|
|
7761d5b85e | ||
|
|
62aa295e73 | ||
|
|
596ede1b12 | ||
|
|
7debe62738 | ||
|
|
002f84da1a | ||
|
|
aff4968e6f | ||
|
|
1d989abd55 | ||
|
|
f1775f83d0 | ||
|
|
4055939c08 | ||
|
|
85120374d6 | ||
|
|
8b4819faa1 | ||
|
|
d219276298 | ||
|
|
79f91e0e5a | ||
|
|
fadf62f594 | ||
|
|
ad3d332e27 | ||
|
|
8f66df7666 | ||
|
|
80c2e80925 | ||
|
|
c3db23d3c7 | ||
|
|
c28584736e | ||
|
|
6b909c1ee6 | ||
|
|
0e8446516e | ||
|
|
aa46b953db | ||
|
|
a562e476c0 | ||
|
|
75d2be8803 | ||
|
|
d6308e126c | ||
|
|
bb97af4d86 | ||
|
|
392164862c | ||
|
|
53e807dec1 | ||
|
|
288d50a8b4 | ||
|
|
131dedd4b0 | ||
|
|
5a92d5c29d | ||
|
|
4b81318710 | ||
|
|
44f6a3ebc5 | ||
|
|
7882a8d928 | ||
|
|
cc3d32748b | ||
|
|
f9da2ad531 | ||
|
|
b19e1131e8 | ||
|
|
123ad54c15 | ||
|
|
09f6ab4155 | ||
|
|
01cf3fb50f | ||
|
|
6c5f9d4198 | ||
|
|
7b2d058144 | ||
|
|
db2804270e | ||
|
|
2572cd04b5 | ||
|
|
5eb8b428b9 | ||
|
|
3a118d9b9d | ||
|
|
14a73bfcaf | ||
|
|
567bf52e16 | ||
|
|
35c6489dba | ||
|
|
371f0c4d0b | ||
|
|
1b42f65d95 | ||
|
|
2aee0cbd0f | ||
|
|
19256b5437 | ||
|
|
67a5237541 | ||
|
|
4e2e772389 | ||
|
|
799bc9e163 | ||
|
|
2431785d0e | ||
|
|
fb3c0e87d4 | ||
|
|
5f7ef2dbff | ||
|
|
ec1d8a8fba | ||
|
|
814ce0b9ae | ||
|
|
8d95b14f35 | ||
|
|
711b8a25a7 | ||
|
|
fbb09f38dc | ||
|
|
9a4d30e152 | ||
|
|
368c61c1bf | ||
|
|
bbd40bf2f6 | ||
|
|
3de999a2d3 | ||
|
|
83cbb2f110 | ||
|
|
fcc7fa75d0 | ||
|
|
495ebface8 | ||
|
|
8c12f8d488 | ||
|
|
523f67b238 | ||
|
|
4813a8b48e | ||
|
|
d5c66022c9 | ||
|
|
1d4034b36c | ||
|
|
dd2d696d00 | ||
|
|
6326321b65 | ||
|
|
f291a4ae3e | ||
|
|
817b8db019 | ||
|
|
a321b8971b | ||
|
|
783b2de9db | ||
|
|
77604d4078 | ||
|
|
21b5404180 | ||
|
|
9ac6d5c7da | ||
|
|
1b39278b24 | ||
|
|
0ebab8bf59 | ||
|
|
112bc998f4 | ||
|
|
12c96bf818 | ||
|
|
91298a8790 | ||
|
|
f2a7cff41c | ||
|
|
dd941233ca | ||
|
|
fc06dd1c29 | ||
|
|
482af78f2b | ||
|
|
89a27dfbe6 | ||
|
|
4ea030d0a0 | ||
|
|
4cc2fa17f5 | ||
|
|
48068cb3d7 | ||
|
|
d107c7245c | ||
|
|
4b2e3230ec | ||
|
|
fb5b82da4e | ||
|
|
5970e32aa5 | ||
|
|
0f00d33f4e | ||
|
|
83187c9edd | ||
|
|
6bfe5716c3 |
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[*.{kt,kts}]
|
||||||
|
ktlint_code_style = intellij_idea
|
||||||
|
ktlint_standard_no-wildcard-imports = disabled
|
||||||
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# https://help.github.com/articles/dealing-with-line-endings/
|
||||||
|
#
|
||||||
|
# Linux start script should use lf
|
||||||
|
/gradlew text eol=lf
|
||||||
|
|
||||||
|
# These are Windows script files and should use crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|
||||||
108
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
108
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
name: 🐞 Bug report
|
||||||
|
description: Report a bug or an issue.
|
||||||
|
title: 'bug: '
|
||||||
|
labels: ['Bug report']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
<p align="center">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
width="256px"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
srcset="https://raw.githubusercontent.com/revanced/revanced-patcher/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
width="256px"
|
||||||
|
src="https://raw.githubusercontent.com/revanced/revanced-patcher/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
|
<br>
|
||||||
|
<a href="https://revanced.app/">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-patcher/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-patcher/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||||
|
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="http://revanced.app/discord">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/app_revanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Continuing the legacy of Vanced
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# ReVanced Patcher bug report
|
||||||
|
|
||||||
|
Before creating a new bug report, please keep the following in mind:
|
||||||
|
|
||||||
|
- **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/ReVanced/revanced-patcher/labels/Bug%20report).
|
||||||
|
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Bug description
|
||||||
|
description: |
|
||||||
|
- Describe your bug in detail
|
||||||
|
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
||||||
|
- Add images and videos if possible
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Error logs
|
||||||
|
description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell.
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Solution
|
||||||
|
description: If applicable, add a possible solution to the bug.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add additional context here.
|
||||||
|
- type: checkboxes
|
||||||
|
id: acknowledgements
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Your bug report will be closed if you don't follow the checklist below.
|
||||||
|
options:
|
||||||
|
- label: This issue is not a duplicate of an existing bug report.
|
||||||
|
required: true
|
||||||
|
- label: I have chosen an appropriate title.
|
||||||
|
required: true
|
||||||
|
- label: All requested information has been provided properly.
|
||||||
|
required: true
|
||||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 📃 Documentation
|
||||||
|
url: https://github.com/revanced/revanced-documentation/
|
||||||
|
about: Don't know how or where to start? Check out our documentation!
|
||||||
|
- name: 🗨 Discussions
|
||||||
|
url: https://github.com/revanced/revanced-suggestions/discussions
|
||||||
|
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||||
106
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
106
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
name: ⭐ Feature request
|
||||||
|
description: Create a detailed request for a new feature.
|
||||||
|
title: 'feat: '
|
||||||
|
labels: ['Feature request']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
<p align="center">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
width="256px"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
srcset="https://raw.githubusercontent.com/revanced/revanced-patcher/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
width="256px"
|
||||||
|
src="https://raw.githubusercontent.com/revanced/revanced-patcher/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
|
<br>
|
||||||
|
<a href="https://revanced.app/">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-patcher/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-patcher/main/assets/revanced-logo/revanced-logo.svg" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||||
|
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="http://revanced.app/discord">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://t.me/app_revanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://x.com/revancedapp">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Continuing the legacy of Vanced
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# ReVanced Patcher feature request
|
||||||
|
|
||||||
|
Before creating a new feature request, please keep the following in mind:
|
||||||
|
|
||||||
|
- **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/ReVanced/revanced-patcher/labels/Feature%20request).
|
||||||
|
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Feature description
|
||||||
|
description: |
|
||||||
|
- Describe your feature in detail
|
||||||
|
- Add images, videos, links, examples, references, etc. if possible
|
||||||
|
- Add the target application name in case you request a new patch
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Motivation
|
||||||
|
description: |
|
||||||
|
A strong motivation is necessary for a feature request to be considered.
|
||||||
|
|
||||||
|
- Why should this feature be implemented?
|
||||||
|
- What is the explicit use case?
|
||||||
|
- What are the benefits?
|
||||||
|
- What makes this feature important?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: acknowledgements
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Your feature request will be closed if you don't follow the checklist below.
|
||||||
|
options:
|
||||||
|
- label: This issue is not a duplicate of an existing feature request.
|
||||||
|
required: true
|
||||||
|
- label: I have chosen an appropriate title.
|
||||||
|
required: true
|
||||||
|
- label: All requested information has been provided properly.
|
||||||
|
required: true
|
||||||
2
.github/config.yml
vendored
Normal file
2
.github/config.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
firstPRMergeComment: >
|
||||||
|
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution.
|
||||||
22
.github/dependabot.yml
vendored
Normal file
22
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
labels: []
|
||||||
|
directory: /
|
||||||
|
target-branch: dev
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
|
||||||
|
- package-ecosystem: npm
|
||||||
|
labels: []
|
||||||
|
directory: /
|
||||||
|
target-branch: dev
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
|
||||||
|
- package-ecosystem: gradle
|
||||||
|
labels: []
|
||||||
|
directory: /
|
||||||
|
target-branch: dev
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
26
.github/workflows/pull_request.yml
vendored
Normal file
26
.github/workflows/pull_request.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Open a PR to main
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pull-request:
|
||||||
|
name: Open pull request
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Open pull request
|
||||||
|
uses: repo-sync/pull-request@v2
|
||||||
|
with:
|
||||||
|
destination_branch: 'main'
|
||||||
|
pr_title: 'chore: ${{ env.MESSAGE }}'
|
||||||
|
pr_body: 'This pull request will ${{ env.MESSAGE }}.'
|
||||||
|
pr_draft: true
|
||||||
39
.github/workflows/release.yml
vendored
39
.github/workflows/release.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
@@ -9,32 +10,38 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- dev
|
- dev
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
# Make sure the release step uses its own credentials:
|
||||||
|
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||||
|
persist-credentials: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup JDK
|
|
||||||
uses: actions/setup-java@v2
|
- name: Cache Gradle
|
||||||
with:
|
uses: burrunan/gradle-cache-action@v1
|
||||||
java-version: '17'
|
|
||||||
distribution: 'adopt'
|
- name: Build
|
||||||
cache: gradle
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: ./gradlew build clean
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
- name: Build with Gradle
|
cache: 'npm'
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
- name: Install dependencies
|
||||||
run: ./gradlew build
|
run: npm install
|
||||||
- name: Setup semantic-release
|
|
||||||
run: npm install -g semantic-release @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin -D
|
|
||||||
- name: Release
|
- name: Release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||||
run: npx semantic-release
|
run: npm exec semantic-release
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -74,6 +74,7 @@ cmake-build-*/
|
|||||||
|
|
||||||
# IntelliJ
|
# IntelliJ
|
||||||
out/
|
out/
|
||||||
|
.idea/
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
# mpeltonen/sbt-idea plugin
|
||||||
.idea_modules/
|
.idea_modules/
|
||||||
@@ -115,3 +116,9 @@ gradle-app.setting
|
|||||||
|
|
||||||
# Avoid ignoring test resources
|
# Avoid ignoring test resources
|
||||||
!src/test/resources/*
|
!src/test/resources/*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Gradle props, to avoid sharing the gpr key
|
||||||
|
gradle.properties
|
||||||
|
|||||||
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
10
.idea/codeStyles/Project.xml
generated
10
.idea/codeStyles/Project.xml
generated
@@ -1,10 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<code_scheme name="Project" version="173">
|
|
||||||
<JetCodeStyleSettings>
|
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
|
||||||
</JetCodeStyleSettings>
|
|
||||||
<codeStyleSettings language="kotlin">
|
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
|
||||||
</codeStyleSettings>
|
|
||||||
</code_scheme>
|
|
||||||
</component>
|
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<state>
|
|
||||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
|
||||||
</state>
|
|
||||||
</component>
|
|
||||||
7
.idea/discord.xml
generated
7
.idea/discord.xml
generated
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="DiscordProjectSettings">
|
|
||||||
<option name="show" value="PROJECT_FILES" />
|
|
||||||
<option name="description" value="" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
15
.idea/git_toolbox_prj.xml
generated
15
.idea/git_toolbox_prj.xml
generated
@@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GitToolBoxProjectSettings">
|
|
||||||
<option name="commitMessageIssueKeyValidationOverride">
|
|
||||||
<BoolValueOverride>
|
|
||||||
<option name="enabled" value="true" />
|
|
||||||
</BoolValueOverride>
|
|
||||||
</option>
|
|
||||||
<option name="commitMessageValidationEnabledOverride">
|
|
||||||
<BoolValueOverride>
|
|
||||||
<option name="enabled" value="true" />
|
|
||||||
</BoolValueOverride>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="UnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
10
.idea/misc.xml
generated
10
.idea/misc.xml
generated
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
12
.idea/vcs.xml
generated
12
.idea/vcs.xml
generated
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CommitMessageInspectionProfile">
|
|
||||||
<profile version="1.0">
|
|
||||||
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
||||||
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
26
.releaserc
26
.releaserc
@@ -7,11 +7,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
["@semantic-release/commit-analyzer", {
|
[
|
||||||
"releaseRules": [
|
"@semantic-release/commit-analyzer", {
|
||||||
{"type": "build", "release": "patch"}
|
"releaseRules": [
|
||||||
]
|
{ "type": "build", "scope": "Needs bump", "release": "patch" }
|
||||||
}],
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"@semantic-release/release-notes-generator",
|
"@semantic-release/release-notes-generator",
|
||||||
"@semantic-release/changelog",
|
"@semantic-release/changelog",
|
||||||
"gradle-semantic-release-plugin",
|
"gradle-semantic-release-plugin",
|
||||||
@@ -24,6 +26,18 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@semantic-release/github"
|
[
|
||||||
|
"@saithodev/semantic-release-backmerge",
|
||||||
|
{
|
||||||
|
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||||
|
clearWorkspace: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/github",
|
||||||
|
{
|
||||||
|
successComment: false
|
||||||
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
1540
CHANGELOG.md
1540
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -1 +1,3 @@
|
|||||||
# Patcher
|
# 💉 ReVanced Patcher
|
||||||
|
|
||||||
|
ReVanced Patcher used to patch Android applications.
|
||||||
|
|||||||
892
api/revanced-patcher.api
Normal file
892
api/revanced-patcher.api
Normal file
@@ -0,0 +1,892 @@
|
|||||||
|
public abstract interface class app/revanced/patcher/IntegrationsConsumer {
|
||||||
|
public abstract fun acceptIntegrations (Ljava/util/List;)V
|
||||||
|
public abstract fun acceptIntegrations (Ljava/util/Set;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface annotation class app/revanced/patcher/InternalApi : java/lang/annotation/Annotation {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PackageMetadata {
|
||||||
|
public final fun getPackageName ()Ljava/lang/String;
|
||||||
|
public final fun getPackageVersion ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/patcher/PatchBundleLoader : java/util/Set, kotlin/jvm/internal/markers/KMappedMarker {
|
||||||
|
public synthetic fun <init> (Ljava/lang/ClassLoader;[Ljava/io/File;Lkotlin/jvm/functions/Function1;Ljava/util/Set;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public fun add (Lapp/revanced/patcher/patch/Patch;)Z
|
||||||
|
public synthetic fun add (Ljava/lang/Object;)Z
|
||||||
|
public fun addAll (Ljava/util/Collection;)Z
|
||||||
|
public fun clear ()V
|
||||||
|
public fun contains (Lapp/revanced/patcher/patch/Patch;)Z
|
||||||
|
public final fun contains (Ljava/lang/Object;)Z
|
||||||
|
public fun containsAll (Ljava/util/Collection;)Z
|
||||||
|
public fun getSize ()I
|
||||||
|
public fun isEmpty ()Z
|
||||||
|
public fun iterator ()Ljava/util/Iterator;
|
||||||
|
public fun remove (Ljava/lang/Object;)Z
|
||||||
|
public fun removeAll (Ljava/util/Collection;)Z
|
||||||
|
public fun retainAll (Ljava/util/Collection;)Z
|
||||||
|
public final fun size ()I
|
||||||
|
public fun toArray ()[Ljava/lang/Object;
|
||||||
|
public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatchBundleLoader$Dex : app/revanced/patcher/PatchBundleLoader {
|
||||||
|
public fun <init> ([Ljava/io/File;)V
|
||||||
|
public fun <init> ([Ljava/io/File;Ljava/io/File;)V
|
||||||
|
public synthetic fun <init> ([Ljava/io/File;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatchBundleLoader$Jar : app/revanced/patcher/PatchBundleLoader {
|
||||||
|
public fun <init> ([Ljava/io/File;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/patcher/PatchExecutorFunction : java/util/function/Function {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/Patcher : app/revanced/patcher/IntegrationsConsumer, app/revanced/patcher/PatchExecutorFunction, app/revanced/patcher/PatchesConsumer, java/io/Closeable, java/util/function/Supplier {
|
||||||
|
public fun <init> (Lapp/revanced/patcher/PatcherConfig;)V
|
||||||
|
public fun <init> (Lapp/revanced/patcher/PatcherOptions;)V
|
||||||
|
public fun acceptIntegrations (Ljava/util/List;)V
|
||||||
|
public fun acceptIntegrations (Ljava/util/Set;)V
|
||||||
|
public fun acceptPatches (Ljava/util/List;)V
|
||||||
|
public fun acceptPatches (Ljava/util/Set;)V
|
||||||
|
public synthetic fun apply (Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
public fun apply (Z)Lkotlinx/coroutines/flow/Flow;
|
||||||
|
public fun close ()V
|
||||||
|
public fun get ()Lapp/revanced/patcher/PatcherResult;
|
||||||
|
public synthetic fun get ()Ljava/lang/Object;
|
||||||
|
public final fun getContext ()Lapp/revanced/patcher/PatcherContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatcherConfig {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Z)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatcherContext {
|
||||||
|
public final fun getPackageMetadata ()Lapp/revanced/patcher/PackageMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/patcher/PatcherException : java/lang/Exception {
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatcherException$CircularDependencyException : app/revanced/patcher/PatcherException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatcherOptions {
|
||||||
|
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Z)V
|
||||||
|
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun copy (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Z)Lapp/revanced/patcher/PatcherOptions;
|
||||||
|
public static synthetic fun copy$default (Lapp/revanced/patcher/PatcherOptions;Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/patcher/PatcherOptions;
|
||||||
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
|
public fun hashCode ()I
|
||||||
|
public final fun recreateResourceCacheDirectory ()Ljava/io/File;
|
||||||
|
public fun toString ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatcherResult {
|
||||||
|
public fun <init> (Ljava/util/List;Ljava/io/File;Ljava/util/List;)V
|
||||||
|
public synthetic fun <init> (Ljava/util/List;Ljava/io/File;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun component1 ()Ljava/util/List;
|
||||||
|
public final fun component2 ()Ljava/io/File;
|
||||||
|
public final fun component3 ()Ljava/util/List;
|
||||||
|
public final fun copy (Ljava/util/List;Ljava/io/File;Ljava/util/List;)Lapp/revanced/patcher/PatcherResult;
|
||||||
|
public static synthetic fun copy$default (Lapp/revanced/patcher/PatcherResult;Ljava/util/List;Ljava/io/File;Ljava/util/List;ILjava/lang/Object;)Lapp/revanced/patcher/PatcherResult;
|
||||||
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
|
public final fun getDexFiles ()Ljava/util/List;
|
||||||
|
public final fun getDexFiles ()Ljava/util/Set;
|
||||||
|
public final fun getDoNotCompress ()Ljava/util/List;
|
||||||
|
public final fun getResourceFile ()Ljava/io/File;
|
||||||
|
public final fun getResources ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
|
||||||
|
public fun hashCode ()I
|
||||||
|
public fun toString ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatcherResult$PatchedDexFile {
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/io/InputStream;)V
|
||||||
|
public final fun getName ()Ljava/lang/String;
|
||||||
|
public final fun getStream ()Ljava/io/InputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatcherResult$PatchedResources {
|
||||||
|
public final fun getDeleteResources ()Ljava/util/Set;
|
||||||
|
public final fun getDoNotCompress ()Ljava/util/Set;
|
||||||
|
public final fun getOtherResources ()Ljava/io/File;
|
||||||
|
public final fun getResourcesApk ()Ljava/io/File;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/patcher/PatchesConsumer {
|
||||||
|
public abstract fun acceptPatches (Ljava/util/List;)V
|
||||||
|
public abstract fun acceptPatches (Ljava/util/Set;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/PatchesConsumer$DefaultImpls {
|
||||||
|
public static fun acceptPatches (Lapp/revanced/patcher/PatchesConsumer;Ljava/util/List;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/data/BytecodeContext : app/revanced/patcher/data/Context {
|
||||||
|
public final fun findClass (Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
||||||
|
public final fun findClass (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
||||||
|
public synthetic fun get ()Ljava/lang/Object;
|
||||||
|
public fun get ()Ljava/util/Set;
|
||||||
|
public final fun getClasses ()Lapp/revanced/patcher/util/ProxyClassList;
|
||||||
|
public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
||||||
|
public final fun toMethodWalker (Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/method/MethodWalker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/patcher/data/Context : java/util/function/Supplier {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/data/ResourceContext : app/revanced/patcher/data/Context, java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker {
|
||||||
|
public fun get ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
|
||||||
|
public synthetic fun get ()Ljava/lang/Object;
|
||||||
|
public final fun get (Ljava/lang/String;)Ljava/io/File;
|
||||||
|
public final fun get (Ljava/lang/String;Z)Ljava/io/File;
|
||||||
|
public static synthetic fun get$default (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
|
||||||
|
public final fun getDocument ()Lapp/revanced/patcher/data/ResourceContext$DocumentOperatable;
|
||||||
|
public final fun getXmlEditor ()Lapp/revanced/patcher/data/ResourceContext$XmlFileHolder;
|
||||||
|
public fun iterator ()Ljava/util/Iterator;
|
||||||
|
public final fun stageDelete (Lkotlin/jvm/functions/Function1;)Z
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/data/ResourceContext$DocumentOperatable {
|
||||||
|
public fun <init> (Lapp/revanced/patcher/data/ResourceContext;)V
|
||||||
|
public final fun get (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
|
||||||
|
public final fun get (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/data/ResourceContext$XmlFileHolder {
|
||||||
|
public fun <init> (Lapp/revanced/patcher/data/ResourceContext;)V
|
||||||
|
public final fun get (Ljava/io/InputStream;)Lapp/revanced/patcher/util/DomFileEditor;
|
||||||
|
public final fun get (Ljava/lang/String;)Lapp/revanced/patcher/util/DomFileEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/extensions/ExtensionsKt {
|
||||||
|
public static final fun newLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Lcom/android/tools/smali/dexlib2/builder/Label;
|
||||||
|
public static final fun or (ILcom/android/tools/smali/dexlib2/AccessFlags;)I
|
||||||
|
public static final fun or (Lcom/android/tools/smali/dexlib2/AccessFlags;I)I
|
||||||
|
public static final fun or (Lcom/android/tools/smali/dexlib2/AccessFlags;Lcom/android/tools/smali/dexlib2/AccessFlags;)I
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/extensions/InstructionExtensions {
|
||||||
|
public static final field INSTANCE Lapp/revanced/patcher/extensions/InstructionExtensions;
|
||||||
|
public final fun addInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILcom/android/tools/smali/dexlib2/builder/BuilderInstruction;)V
|
||||||
|
public final fun addInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||||
|
public final fun addInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;)V
|
||||||
|
public final fun addInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
|
||||||
|
public final fun addInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||||
|
public final fun addInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/util/List;)V
|
||||||
|
public final fun addInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
|
||||||
|
public final fun addInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/util/List;)V
|
||||||
|
public final fun addInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;ILjava/util/List;)V
|
||||||
|
public final fun addInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;Ljava/util/List;)V
|
||||||
|
public final fun addInstructionsWithLabels (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;[Lapp/revanced/patcher/util/smali/ExternalLabel;)V
|
||||||
|
public final fun getInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;
|
||||||
|
public final fun getInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Ljava/lang/Object;
|
||||||
|
public final fun getInstruction (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;I)Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;
|
||||||
|
public final fun getInstruction (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;I)Ljava/lang/Object;
|
||||||
|
public final fun getInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)Ljava/util/List;
|
||||||
|
public final fun removeInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
|
||||||
|
public final fun removeInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
|
||||||
|
public final fun removeInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;II)V
|
||||||
|
public final fun removeInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;I)V
|
||||||
|
public final fun removeInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;II)V
|
||||||
|
public final fun replaceInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILcom/android/tools/smali/dexlib2/builder/BuilderInstruction;)V
|
||||||
|
public final fun replaceInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||||
|
public final fun replaceInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||||
|
public final fun replaceInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/util/List;)V
|
||||||
|
public final fun replaceInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;ILjava/util/List;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/extensions/MethodFingerprintExtensions {
|
||||||
|
public static final field INSTANCE Lapp/revanced/patcher/extensions/MethodFingerprintExtensions;
|
||||||
|
public final fun getFuzzyPatternScanMethod (Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Lapp/revanced/patcher/fingerprint/annotation/FuzzyPatternScanMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/patcher/fingerprint/MethodFingerprint {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/fingerprint/MethodFingerprint$Companion;
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getFuzzyPatternScanMethod ()Lapp/revanced/patcher/fingerprint/annotation/FuzzyPatternScanMethod;
|
||||||
|
public final fun getResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
|
||||||
|
public final fun resolve (Lapp/revanced/patcher/data/BytecodeContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
|
||||||
|
public final fun resolve (Lapp/revanced/patcher/data/BytecodeContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/fingerprint/MethodFingerprint$Companion {
|
||||||
|
public final fun resolve (Ljava/lang/Iterable;Lapp/revanced/patcher/data/BytecodeContext;Ljava/lang/Iterable;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult {
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult;Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||||
|
public final fun getClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||||
|
public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
|
||||||
|
public final fun getMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||||
|
public final fun getMutableMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||||
|
public final fun getScanResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult {
|
||||||
|
public fun <init> (Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;)V
|
||||||
|
public final fun getPatternScanResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult;
|
||||||
|
public final fun getStringsScanResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult {
|
||||||
|
public fun <init> (IILjava/util/List;)V
|
||||||
|
public synthetic fun <init> (IILjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getEndIndex ()I
|
||||||
|
public final fun getStartIndex ()I
|
||||||
|
public final fun getWarnings ()Ljava/util/List;
|
||||||
|
public final fun setWarnings (Ljava/util/List;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$PatternScanResult$Warning {
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/Opcode;Lcom/android/tools/smali/dexlib2/Opcode;II)V
|
||||||
|
public final fun getCorrectOpcode ()Lcom/android/tools/smali/dexlib2/Opcode;
|
||||||
|
public final fun getInstructionIndex ()I
|
||||||
|
public final fun getPatternIndex ()I
|
||||||
|
public final fun getWrongOpcode ()Lcom/android/tools/smali/dexlib2/Opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult {
|
||||||
|
public fun <init> (Ljava/util/List;)V
|
||||||
|
public final fun getMatches ()Ljava/util/List;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/fingerprint/MethodFingerprintResult$MethodFingerprintScanResult$StringsScanResult$StringMatch {
|
||||||
|
public fun <init> (Ljava/lang/String;I)V
|
||||||
|
public final fun getIndex ()I
|
||||||
|
public final fun getString ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface annotation class app/revanced/patcher/fingerprint/annotation/FuzzyPatternScanMethod : java/lang/annotation/Annotation {
|
||||||
|
public abstract fun threshold ()I
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZLjava/util/Set;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZLjava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public fun <init> (Ljava/util/Set;)V
|
||||||
|
public synthetic fun <init> (Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/patcher/patch/Patch {
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZLkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
|
public abstract fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||||
|
public final fun getCompatiblePackages ()Ljava/util/Set;
|
||||||
|
public final fun getDependencies ()Ljava/util/Set;
|
||||||
|
public final fun getDescription ()Ljava/lang/String;
|
||||||
|
public final fun getName ()Ljava/lang/String;
|
||||||
|
public final fun getOptions ()Lapp/revanced/patcher/patch/options/PatchOptions;
|
||||||
|
public final fun getRequiresIntegrations ()Z
|
||||||
|
public final fun getUse ()Z
|
||||||
|
public fun hashCode ()I
|
||||||
|
public fun toString ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/Patch$CompatiblePackage {
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/util/Set;)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
public final fun getName ()Ljava/lang/String;
|
||||||
|
public final fun getVersions ()Ljava/util/Set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/PatchException : java/lang/Exception {
|
||||||
|
public fun <init> (Ljava/lang/String;)V
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
|
||||||
|
public fun <init> (Ljava/lang/Throwable;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/PatchResult {
|
||||||
|
public final fun getException ()Lapp/revanced/patcher/patch/PatchException;
|
||||||
|
public final fun getPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/patcher/patch/RawResourcePatch : app/revanced/patcher/patch/Patch {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZ)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/patcher/patch/ResourcePatch : app/revanced/patcher/patch/Patch {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZ)V
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface annotation class app/revanced/patcher/patch/annotation/CompatiblePackage : java/lang/annotation/Annotation {
|
||||||
|
public abstract fun name ()Ljava/lang/String;
|
||||||
|
public abstract fun versions ()[Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface annotation class app/revanced/patcher/patch/annotation/Patch : java/lang/annotation/Annotation {
|
||||||
|
public abstract fun compatiblePackages ()[Lapp/revanced/patcher/patch/annotation/CompatiblePackage;
|
||||||
|
public abstract fun dependencies ()[Ljava/lang/Class;
|
||||||
|
public abstract fun description ()Ljava/lang/String;
|
||||||
|
public abstract fun name ()Ljava/lang/String;
|
||||||
|
public abstract fun requiresIntegrations ()Z
|
||||||
|
public abstract fun use ()Z
|
||||||
|
}
|
||||||
|
|
||||||
|
public class app/revanced/patcher/patch/options/PatchOption {
|
||||||
|
public static final field PatchExtensions Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Lkotlin/jvm/functions/Function2;)V
|
||||||
|
public final fun getDefault ()Ljava/lang/Object;
|
||||||
|
public final fun getDescription ()Ljava/lang/String;
|
||||||
|
public final fun getKey ()Ljava/lang/String;
|
||||||
|
public final fun getRequired ()Z
|
||||||
|
public final fun getTitle ()Ljava/lang/String;
|
||||||
|
public final fun getValidator ()Lkotlin/jvm/functions/Function2;
|
||||||
|
public final fun getValue ()Ljava/lang/Object;
|
||||||
|
public final fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object;
|
||||||
|
public final fun getValueType ()Ljava/lang/String;
|
||||||
|
public final fun getValues ()Ljava/util/Map;
|
||||||
|
public fun reset ()V
|
||||||
|
public final fun setValue (Ljava/lang/Object;)V
|
||||||
|
public final fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V
|
||||||
|
public fun toString ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/options/PatchOption$PatchExtensions {
|
||||||
|
public final fun booleanArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun booleanArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun booleanPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun booleanPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun floatArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun floatArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun floatPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun floatPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun intArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun intArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun intPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun intPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun longArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun longArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun longPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun longPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun registerNewPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Lkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun registerNewPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun stringArrayPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun stringArrayPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;[Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun stringPatchOption (Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public static synthetic fun stringPatchOption$default (Lapp/revanced/patcher/patch/options/PatchOption$PatchExtensions;Lapp/revanced/patcher/patch/Patch;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class app/revanced/patcher/patch/options/PatchOptionException : java/lang/Exception {
|
||||||
|
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/options/PatchOptionException$InvalidValueTypeException : app/revanced/patcher/patch/options/PatchOptionException {
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/options/PatchOptionException$PatchOptionNotFoundException : app/revanced/patcher/patch/options/PatchOptionException {
|
||||||
|
public fun <init> (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/options/PatchOptionException$ValueRequiredException : app/revanced/patcher/patch/options/PatchOptionException {
|
||||||
|
public fun <init> (Lapp/revanced/patcher/patch/options/PatchOption;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/options/PatchOptionException$ValueValidationException : app/revanced/patcher/patch/options/PatchOptionException {
|
||||||
|
public fun <init> (Ljava/lang/Object;Lapp/revanced/patcher/patch/options/PatchOption;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/patch/options/PatchOptions : java/util/Map, kotlin/jvm/internal/markers/KMutableMap {
|
||||||
|
public fun <init> ()V
|
||||||
|
public fun clear ()V
|
||||||
|
public final fun containsKey (Ljava/lang/Object;)Z
|
||||||
|
public fun containsKey (Ljava/lang/String;)Z
|
||||||
|
public fun containsValue (Lapp/revanced/patcher/patch/options/PatchOption;)Z
|
||||||
|
public final fun containsValue (Ljava/lang/Object;)Z
|
||||||
|
public final fun entrySet ()Ljava/util/Set;
|
||||||
|
public final fun get (Ljava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
public fun get (Ljava/lang/String;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public fun getEntries ()Ljava/util/Set;
|
||||||
|
public fun getKeys ()Ljava/util/Set;
|
||||||
|
public fun getSize ()I
|
||||||
|
public fun getValues ()Ljava/util/Collection;
|
||||||
|
public fun isEmpty ()Z
|
||||||
|
public final fun keySet ()Ljava/util/Set;
|
||||||
|
public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
public fun put (Ljava/lang/String;Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public fun putAll (Ljava/util/Map;)V
|
||||||
|
public final fun register (Lapp/revanced/patcher/patch/options/PatchOption;)V
|
||||||
|
public final fun remove (Ljava/lang/Object;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
public fun remove (Ljava/lang/String;)Lapp/revanced/patcher/patch/options/PatchOption;
|
||||||
|
public final fun set (Ljava/lang/String;Ljava/lang/Object;)V
|
||||||
|
public final fun size ()I
|
||||||
|
public final fun values ()Ljava/util/Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w3c/dom/Document {
|
||||||
|
public fun adoptNode (Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||||
|
public fun appendChild (Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||||
|
public fun cloneNode (Z)Lorg/w3c/dom/Node;
|
||||||
|
public fun close ()V
|
||||||
|
public fun compareDocumentPosition (Lorg/w3c/dom/Node;)S
|
||||||
|
public fun createAttribute (Ljava/lang/String;)Lorg/w3c/dom/Attr;
|
||||||
|
public fun createAttributeNS (Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/Attr;
|
||||||
|
public fun createCDATASection (Ljava/lang/String;)Lorg/w3c/dom/CDATASection;
|
||||||
|
public fun createComment (Ljava/lang/String;)Lorg/w3c/dom/Comment;
|
||||||
|
public fun createDocumentFragment ()Lorg/w3c/dom/DocumentFragment;
|
||||||
|
public fun createElement (Ljava/lang/String;)Lorg/w3c/dom/Element;
|
||||||
|
public fun createElementNS (Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/Element;
|
||||||
|
public fun createEntityReference (Ljava/lang/String;)Lorg/w3c/dom/EntityReference;
|
||||||
|
public fun createProcessingInstruction (Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/ProcessingInstruction;
|
||||||
|
public fun createTextNode (Ljava/lang/String;)Lorg/w3c/dom/Text;
|
||||||
|
public fun getAttributes ()Lorg/w3c/dom/NamedNodeMap;
|
||||||
|
public fun getBaseURI ()Ljava/lang/String;
|
||||||
|
public fun getChildNodes ()Lorg/w3c/dom/NodeList;
|
||||||
|
public fun getDoctype ()Lorg/w3c/dom/DocumentType;
|
||||||
|
public fun getDocumentElement ()Lorg/w3c/dom/Element;
|
||||||
|
public fun getDocumentURI ()Ljava/lang/String;
|
||||||
|
public fun getDomConfig ()Lorg/w3c/dom/DOMConfiguration;
|
||||||
|
public fun getElementById (Ljava/lang/String;)Lorg/w3c/dom/Element;
|
||||||
|
public fun getElementsByTagName (Ljava/lang/String;)Lorg/w3c/dom/NodeList;
|
||||||
|
public fun getElementsByTagNameNS (Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/NodeList;
|
||||||
|
public fun getFeature (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
|
||||||
|
public fun getFirstChild ()Lorg/w3c/dom/Node;
|
||||||
|
public fun getImplementation ()Lorg/w3c/dom/DOMImplementation;
|
||||||
|
public fun getInputEncoding ()Ljava/lang/String;
|
||||||
|
public fun getLastChild ()Lorg/w3c/dom/Node;
|
||||||
|
public fun getLocalName ()Ljava/lang/String;
|
||||||
|
public fun getNamespaceURI ()Ljava/lang/String;
|
||||||
|
public fun getNextSibling ()Lorg/w3c/dom/Node;
|
||||||
|
public fun getNodeName ()Ljava/lang/String;
|
||||||
|
public fun getNodeType ()S
|
||||||
|
public fun getNodeValue ()Ljava/lang/String;
|
||||||
|
public fun getOwnerDocument ()Lorg/w3c/dom/Document;
|
||||||
|
public fun getParentNode ()Lorg/w3c/dom/Node;
|
||||||
|
public fun getPrefix ()Ljava/lang/String;
|
||||||
|
public fun getPreviousSibling ()Lorg/w3c/dom/Node;
|
||||||
|
public fun getStrictErrorChecking ()Z
|
||||||
|
public fun getTextContent ()Ljava/lang/String;
|
||||||
|
public fun getUserData (Ljava/lang/String;)Ljava/lang/Object;
|
||||||
|
public fun getXmlEncoding ()Ljava/lang/String;
|
||||||
|
public fun getXmlStandalone ()Z
|
||||||
|
public fun getXmlVersion ()Ljava/lang/String;
|
||||||
|
public fun hasAttributes ()Z
|
||||||
|
public fun hasChildNodes ()Z
|
||||||
|
public fun importNode (Lorg/w3c/dom/Node;Z)Lorg/w3c/dom/Node;
|
||||||
|
public fun insertBefore (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||||
|
public fun isDefaultNamespace (Ljava/lang/String;)Z
|
||||||
|
public fun isEqualNode (Lorg/w3c/dom/Node;)Z
|
||||||
|
public fun isSameNode (Lorg/w3c/dom/Node;)Z
|
||||||
|
public fun isSupported (Ljava/lang/String;Ljava/lang/String;)Z
|
||||||
|
public fun lookupNamespaceURI (Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
public fun lookupPrefix (Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
public fun normalize ()V
|
||||||
|
public fun normalizeDocument ()V
|
||||||
|
public fun removeChild (Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||||
|
public fun renameNode (Lorg/w3c/dom/Node;Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/Node;
|
||||||
|
public fun replaceChild (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||||
|
public fun setDocumentURI (Ljava/lang/String;)V
|
||||||
|
public fun setNodeValue (Ljava/lang/String;)V
|
||||||
|
public fun setPrefix (Ljava/lang/String;)V
|
||||||
|
public fun setStrictErrorChecking (Z)V
|
||||||
|
public fun setTextContent (Ljava/lang/String;)V
|
||||||
|
public fun setUserData (Ljava/lang/String;Ljava/lang/Object;Lorg/w3c/dom/UserDataHandler;)Ljava/lang/Object;
|
||||||
|
public fun setXmlStandalone (Z)V
|
||||||
|
public fun setXmlVersion (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/DomFileEditor : java/io/Closeable {
|
||||||
|
public fun <init> (Ljava/io/File;)V
|
||||||
|
public fun close ()V
|
||||||
|
public final fun getFile ()Lorg/w3c/dom/Document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/ProxyClassList : java/util/Set, kotlin/jvm/internal/markers/KMutableSet {
|
||||||
|
public final fun add (Lapp/revanced/patcher/util/proxy/ClassProxy;)Z
|
||||||
|
public fun add (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
|
||||||
|
public synthetic fun add (Ljava/lang/Object;)Z
|
||||||
|
public fun addAll (Ljava/util/Collection;)Z
|
||||||
|
public fun clear ()V
|
||||||
|
public fun contains (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
|
||||||
|
public final fun contains (Ljava/lang/Object;)Z
|
||||||
|
public fun containsAll (Ljava/util/Collection;)Z
|
||||||
|
public fun getSize ()I
|
||||||
|
public fun isEmpty ()Z
|
||||||
|
public fun iterator ()Ljava/util/Iterator;
|
||||||
|
public fun remove (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
|
||||||
|
public final fun remove (Ljava/lang/Object;)Z
|
||||||
|
public fun removeAll (Ljava/util/Collection;)Z
|
||||||
|
public fun retainAll (Ljava/util/Collection;)Z
|
||||||
|
public final fun size ()I
|
||||||
|
public fun toArray ()[Ljava/lang/Object;
|
||||||
|
public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/method/MethodWalker {
|
||||||
|
public final fun getMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
|
||||||
|
public final fun nextMethod (IZ)Lapp/revanced/patcher/util/method/MethodWalker;
|
||||||
|
public static synthetic fun nextMethod$default (Lapp/revanced/patcher/util/method/MethodWalker;IZILjava/lang/Object;)Lapp/revanced/patcher/util/method/MethodWalker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/ClassProxy {
|
||||||
|
public final fun getImmutableClass ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||||
|
public final fun getMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableAnnotation : com/android/tools/smali/dexlib2/base/BaseAnnotation {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableAnnotation$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Annotation;)V
|
||||||
|
public fun getElements ()Ljava/util/Set;
|
||||||
|
public fun getType ()Ljava/lang/String;
|
||||||
|
public fun getVisibility ()I
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableAnnotation$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/Annotation;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableAnnotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableAnnotationElement : com/android/tools/smali/dexlib2/base/BaseAnnotationElement {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableAnnotationElement$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/AnnotationElement;)V
|
||||||
|
public fun getName ()Ljava/lang/String;
|
||||||
|
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/value/EncodedValue;
|
||||||
|
public final fun setName (Ljava/lang/String;)V
|
||||||
|
public final fun setValue (Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableAnnotationElement$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/AnnotationElement;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableAnnotationElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableClass : com/android/tools/smali/dexlib2/base/reference/BaseTypeReference, com/android/tools/smali/dexlib2/iface/ClassDef {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)V
|
||||||
|
public final fun charAt (I)C
|
||||||
|
public fun get (I)C
|
||||||
|
public fun getAccessFlags ()I
|
||||||
|
public fun getAnnotations ()Ljava/util/Set;
|
||||||
|
public synthetic fun getDirectMethods ()Ljava/lang/Iterable;
|
||||||
|
public fun getDirectMethods ()Ljava/util/Set;
|
||||||
|
public synthetic fun getFields ()Ljava/lang/Iterable;
|
||||||
|
public fun getFields ()Ljava/util/Set;
|
||||||
|
public synthetic fun getInstanceFields ()Ljava/lang/Iterable;
|
||||||
|
public fun getInstanceFields ()Ljava/util/Set;
|
||||||
|
public fun getInterfaces ()Ljava/util/List;
|
||||||
|
public fun getLength ()I
|
||||||
|
public synthetic fun getMethods ()Ljava/lang/Iterable;
|
||||||
|
public fun getMethods ()Ljava/util/Set;
|
||||||
|
public fun getSourceFile ()Ljava/lang/String;
|
||||||
|
public synthetic fun getStaticFields ()Ljava/lang/Iterable;
|
||||||
|
public fun getStaticFields ()Ljava/util/Set;
|
||||||
|
public fun getSuperclass ()Ljava/lang/String;
|
||||||
|
public fun getType ()Ljava/lang/String;
|
||||||
|
public synthetic fun getVirtualMethods ()Ljava/lang/Iterable;
|
||||||
|
public fun getVirtualMethods ()Ljava/util/Set;
|
||||||
|
public final fun length ()I
|
||||||
|
public final fun setAccessFlags (I)V
|
||||||
|
public final fun setSourceFile (Ljava/lang/String;)V
|
||||||
|
public final fun setSuperClass (Ljava/lang/String;)V
|
||||||
|
public final fun setType (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableClass$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableField : com/android/tools/smali/dexlib2/base/reference/BaseFieldReference, com/android/tools/smali/dexlib2/iface/Field {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableField$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Field;)V
|
||||||
|
public fun getAccessFlags ()I
|
||||||
|
public fun getAnnotations ()Ljava/util/Set;
|
||||||
|
public fun getDefiningClass ()Ljava/lang/String;
|
||||||
|
public fun getHiddenApiRestrictions ()Ljava/util/Set;
|
||||||
|
public fun getInitialValue ()Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue;
|
||||||
|
public synthetic fun getInitialValue ()Lcom/android/tools/smali/dexlib2/iface/value/EncodedValue;
|
||||||
|
public fun getName ()Ljava/lang/String;
|
||||||
|
public fun getType ()Ljava/lang/String;
|
||||||
|
public final fun setAccessFlags (I)V
|
||||||
|
public final fun setDefiningClass (Ljava/lang/String;)V
|
||||||
|
public final fun setInitialValue (Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue;)V
|
||||||
|
public final fun setName (Ljava/lang/String;)V
|
||||||
|
public final fun setType (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableField$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/Field;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableMethod : com/android/tools/smali/dexlib2/base/reference/BaseMethodReference, com/android/tools/smali/dexlib2/iface/Method {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Method;)V
|
||||||
|
public fun getAccessFlags ()I
|
||||||
|
public fun getAnnotations ()Ljava/util/Set;
|
||||||
|
public fun getDefiningClass ()Ljava/lang/String;
|
||||||
|
public fun getHiddenApiRestrictions ()Ljava/util/Set;
|
||||||
|
public fun getImplementation ()Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;
|
||||||
|
public synthetic fun getImplementation ()Lcom/android/tools/smali/dexlib2/iface/MethodImplementation;
|
||||||
|
public fun getName ()Ljava/lang/String;
|
||||||
|
public fun getParameterTypes ()Ljava/util/List;
|
||||||
|
public fun getParameters ()Ljava/util/List;
|
||||||
|
public fun getReturnType ()Ljava/lang/String;
|
||||||
|
public final fun setAccessFlags (I)V
|
||||||
|
public final fun setDefiningClass (Ljava/lang/String;)V
|
||||||
|
public final fun setName (Ljava/lang/String;)V
|
||||||
|
public final fun setReturnType (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableMethod$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableMethodParameter : com/android/tools/smali/dexlib2/base/BaseMethodParameter, com/android/tools/smali/dexlib2/iface/MethodParameter {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethodParameter$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/MethodParameter;)V
|
||||||
|
public final fun charAt (I)C
|
||||||
|
public fun get (I)C
|
||||||
|
public fun getAnnotations ()Ljava/util/Set;
|
||||||
|
public fun getLength ()I
|
||||||
|
public fun getName ()Ljava/lang/String;
|
||||||
|
public fun getSignature ()Ljava/lang/String;
|
||||||
|
public fun getType ()Ljava/lang/String;
|
||||||
|
public final fun length ()I
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableMethodParameter$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/MethodParameter;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethodParameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseAnnotationEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/AnnotationEncodedValue;)V
|
||||||
|
public fun getElements ()Ljava/util/Set;
|
||||||
|
public fun getType ()Ljava/lang/String;
|
||||||
|
public final fun setType (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/AnnotationEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseArrayEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/ArrayEncodedValue;)V
|
||||||
|
public fun getValue ()Ljava/util/List;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ArrayEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseBooleanEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/BooleanEncodedValue;)V
|
||||||
|
public fun getValue ()Z
|
||||||
|
public final fun setValue (Z)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/BooleanEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseByteEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/ByteEncodedValue;)V
|
||||||
|
public fun getValue ()B
|
||||||
|
public final fun setValue (B)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ByteEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableCharEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseCharEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableCharEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/CharEncodedValue;)V
|
||||||
|
public fun getValue ()C
|
||||||
|
public final fun setValue (C)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableCharEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/CharEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableCharEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseDoubleEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/DoubleEncodedValue;)V
|
||||||
|
public fun getValue ()D
|
||||||
|
public final fun setValue (D)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/DoubleEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract interface class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue : com/android/tools/smali/dexlib2/iface/value/EncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue$Companion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/EncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseEnumEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/EnumEncodedValue;)V
|
||||||
|
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/FieldReference;
|
||||||
|
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/FieldReference;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/EnumEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseFieldEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/FieldEncodedValue;)V
|
||||||
|
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/FieldReference;
|
||||||
|
public fun getValueType ()I
|
||||||
|
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/FieldReference;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/FieldEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseFloatEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/FloatEncodedValue;)V
|
||||||
|
public fun getValue ()F
|
||||||
|
public final fun setValue (F)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/FloatEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableIntEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseIntEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableIntEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/IntEncodedValue;)V
|
||||||
|
public fun getValue ()I
|
||||||
|
public final fun setValue (I)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableIntEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/IntEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableIntEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableLongEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseLongEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableLongEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/LongEncodedValue;)V
|
||||||
|
public fun getValue ()J
|
||||||
|
public final fun setValue (J)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableLongEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/LongEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableLongEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseMethodEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/MethodEncodedValue;)V
|
||||||
|
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;
|
||||||
|
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/MethodEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseMethodHandleEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/MethodHandleEncodedValue;)V
|
||||||
|
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/MethodHandleReference;
|
||||||
|
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/MethodHandleReference;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/MethodHandleEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseMethodTypeEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/MethodTypeEncodedValue;)V
|
||||||
|
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/MethodProtoReference;
|
||||||
|
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/MethodProtoReference;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/MethodTypeEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableNullEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseNullEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableNullEncodedValue$Companion;
|
||||||
|
public fun <init> ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableNullEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ByteEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableShortEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseShortEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableShortEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/ShortEncodedValue;)V
|
||||||
|
public fun getValue ()S
|
||||||
|
public final fun setValue (S)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableShortEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ShortEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableShortEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableStringEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseStringEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableStringEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/StringEncodedValue;)V
|
||||||
|
public fun getValue ()Ljava/lang/String;
|
||||||
|
public final fun setValue (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableStringEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ByteEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseTypeEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue$Companion;
|
||||||
|
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/TypeEncodedValue;)V
|
||||||
|
public fun getValue ()Ljava/lang/String;
|
||||||
|
public final fun setValue (Ljava/lang/String;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue$Companion {
|
||||||
|
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/TypeEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/smali/ExternalLabel {
|
||||||
|
public fun <init> (Ljava/lang/String;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;)V
|
||||||
|
public final fun copy (Ljava/lang/String;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;)Lapp/revanced/patcher/util/smali/ExternalLabel;
|
||||||
|
public static synthetic fun copy$default (Lapp/revanced/patcher/util/smali/ExternalLabel;Ljava/lang/String;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;ILjava/lang/Object;)Lapp/revanced/patcher/util/smali/ExternalLabel;
|
||||||
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
|
public fun hashCode ()I
|
||||||
|
public fun toString ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/smali/InlineSmaliCompiler {
|
||||||
|
public static final field Companion Lapp/revanced/patcher/util/smali/InlineSmaliCompiler$Companion;
|
||||||
|
public fun <init> ()V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/smali/InlineSmaliCompiler$Companion {
|
||||||
|
public final fun compile (Ljava/lang/String;Ljava/lang/String;IZ)Ljava/util/List;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patcher/util/smali/InlineSmaliCompilerKt {
|
||||||
|
public static final fun toInstruction (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;
|
||||||
|
public static synthetic fun toInstruction$default (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/Object;)Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;
|
||||||
|
public static final fun toInstructions (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)Ljava/util/List;
|
||||||
|
public static synthetic fun toInstructions$default (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/Object;)Ljava/util/List;
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
1
assets/revanced-logo/revanced-logo.svg
Normal file
1
assets/revanced-logo/revanced-logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Logo"><g id="Ring"><circle id="Ring-Background" serif:id="Ring Background" cx="400" cy="400" r="400" style="fill:#1b1b1b;"/><path id="Ring1" serif:id="Ring" d="M400,0c220.766,0 400,179.234 400,400c-0,220.766 -179.234,400 -400,400c-220.766,-0 -400,-179.234 -400,-400c0,-220.766 179.234,-400 400,-400Zm-0,36c200.897,-0 364,163.103 364,364c0,200.897 -163.103,364 -364,364c-200.897,0 -364,-163.103 -364,-364c-0,-200.897 163.103,-364 364,-364Z" style="fill:url(#_Linear1);"/></g><g id="Shape"><path id="V-Shape" serif:id="V Shape" d="M538.74,269.872c1.481,-3.382 1.157,-7.283 -0.863,-10.373c-2.021,-3.091 -5.464,-4.954 -9.156,-4.954c-5.148,0 -10.435,0 -14.165,0c-3.1,0 -5.907,1.834 -7.153,4.672c-12.468,28.396 -78.273,178.273 -100.25,228.328c-1.246,2.838 -4.053,4.671 -7.154,4.671c-3.1,0 -5.907,-1.833 -7.153,-4.671c-21.977,-50.055 -87.782,-199.932 -100.25,-228.328c-1.246,-2.838 -4.053,-4.672 -7.153,-4.672c-3.73,0 -9.017,0 -14.164,0c-3.693,0 -7.135,1.863 -9.156,4.954c-2.02,3.09 -2.344,6.991 -0.863,10.373c23.557,53.766 101.872,232.519 117.871,269.034c1.743,3.979 5.674,6.549 10.018,6.549c6.293,-0 15.408,-0 21.701,-0c4.344,-0 8.275,-2.57 10.018,-6.549c15.999,-36.515 94.315,-215.268 117.872,-269.034Z" style="fill:#fff;"/><path id="Diamond" d="M408.119,395.312c-1.675,2.901 -4.77,4.688 -8.119,4.688c-3.349,-0 -6.444,-1.787 -8.119,-4.688c-16.997,-29.44 -56.156,-97.264 -73.153,-126.704c-1.675,-2.901 -1.675,-6.474 0,-9.375c1.675,-2.901 4.77,-4.688 8.119,-4.688c33.995,0 112.311,0 146.306,0c3.349,0 6.444,1.787 8.119,4.688c1.675,2.901 1.675,6.474 -0,9.375c-16.997,29.44 -56.156,97.264 -73.153,126.704Z" style="fill:url(#_Linear2);"/></g></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.89859e-14,800,-800,4.89859e-14,400.001,3.31681e-10)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.77155e-14,289.317,-282.535,1.73003e-14,400,254.545)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient></defs></svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
104
build.gradle.kts
104
build.gradle.kts
@@ -1,35 +1,18 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.7.0"
|
alias(libs.plugins.kotlin)
|
||||||
java
|
alias(libs.plugins.binary.compatibility.validator)
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
|
signing
|
||||||
|
java
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "app.revanced"
|
group = "app.revanced"
|
||||||
|
|
||||||
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
|
|
||||||
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
url = uri("https://maven.pkg.github.com/revanced/multidexlib2")
|
|
||||||
credentials {
|
|
||||||
username = githubUsername
|
|
||||||
password = githubPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("xpp3:xpp3:1.1.4c")
|
|
||||||
implementation("org.smali:smali:2.5.2")
|
|
||||||
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
|
||||||
implementation("org.apktool:apktool-lib:2.6.9-SNAPSHOT")
|
|
||||||
|
|
||||||
testImplementation(kotlin("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
processResources {
|
||||||
|
expand("projectVersion" to project.version)
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
testLogging {
|
testLogging {
|
||||||
@@ -38,27 +21,70 @@ tasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
implementation(libs.xpp3)
|
||||||
|
implementation(libs.smali)
|
||||||
|
implementation(libs.multidexlib2)
|
||||||
|
implementation(libs.apktool.lib)
|
||||||
|
implementation(libs.kotlin.reflect)
|
||||||
|
|
||||||
|
// TODO: Convert project to KMP.
|
||||||
|
compileOnly(libs.android) {
|
||||||
|
// Exclude, otherwise the org.w3c.dom API breaks.
|
||||||
|
exclude(group = "xerces", module = "xmlParserAPIs")
|
||||||
|
}
|
||||||
|
|
||||||
|
testImplementation(libs.kotlin.test)
|
||||||
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
withJavadocJar()
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(11)
|
||||||
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
repositories {
|
publications {
|
||||||
if (System.getenv("GITHUB_ACTOR") != null)
|
create<MavenPublication>("revanced-patcher-publication") {
|
||||||
maven {
|
from(components["java"])
|
||||||
name = "GitHubPackages"
|
|
||||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
version = project.version.toString()
|
||||||
credentials {
|
|
||||||
username = System.getenv("GITHUB_ACTOR")
|
pom {
|
||||||
password = System.getenv("GITHUB_TOKEN")
|
name = "ReVanced Patcher"
|
||||||
|
description = "Patcher used by ReVanced."
|
||||||
|
url = "https://revanced.app"
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = "GNU General Public License v3.0"
|
||||||
|
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = "ReVanced"
|
||||||
|
name = "ReVanced"
|
||||||
|
email = "contact@revanced.app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
connection = "scm:git:git://github.com/revanced/revanced-patcher.git"
|
||||||
|
developerConnection = "scm:git:git@github.com:revanced/revanced-patcher.git"
|
||||||
|
url = "https://github.com/revanced/revanced-patcher"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
mavenLocal()
|
|
||||||
}
|
|
||||||
publications {
|
|
||||||
register<MavenPublication>("gpr") {
|
|
||||||
from(components["java"])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
kotlin.code.style = official
|
org.gradle.parallel = true
|
||||||
version = 2.2.0-dev.2
|
org.gradle.caching = true
|
||||||
|
version = 19.3.0-dev.1
|
||||||
|
|||||||
23
gradle/libs.versions.toml
Normal file
23
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[versions]
|
||||||
|
android = "4.1.1.4"
|
||||||
|
apktool-lib = "2.9.1"
|
||||||
|
kotlin = "1.9.22"
|
||||||
|
kotlinx-coroutines-core = "1.7.3"
|
||||||
|
multidexlib2 = "3.0.3.r3"
|
||||||
|
smali = "3.0.4"
|
||||||
|
binary-compatibility-validator = "0.13.2"
|
||||||
|
xpp3 = "1.1.4c"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
android = { module = "com.google.android:android", version.ref = "android" }
|
||||||
|
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||||
|
apktool-lib = { module = "app.revanced:apktool", version.ref = "apktool-lib" }
|
||||||
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||||
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||||
|
multidexlib2 = { module = "app.revanced:multidexlib2", version.ref = "multidexlib2" }
|
||||||
|
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
|
||||||
|
xpp3 = { module = "xpp3:xpp3", version.ref = "xpp3" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
||||||
|
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
|
distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dist
|
||||||
40
gradlew
vendored
40
gradlew
vendored
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright <EFBFBD> 2015-2021 the original authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
# Busybox and similar reduced shells will NOT work, because this script
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
# requires all of these POSIX shell features:
|
# requires all of these POSIX shell features:
|
||||||
# * functions;
|
# * functions;
|
||||||
# * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>,
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
# <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>;
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
# * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>;
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
# * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>.
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
#
|
#
|
||||||
# Important for patching:
|
# Important for patching:
|
||||||
#
|
#
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +80,10 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -133,22 +130,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -193,6 +197,10 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
@@ -205,6 +213,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
|||||||
15
gradlew.bat
vendored
15
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
6895
package-lock.json
generated
Normal file
6895
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
package.json
Normal file
9
package.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
||||||
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
|
"@semantic-release/git": "^10.0.1",
|
||||||
|
"gradle-semantic-release-plugin": "^1.9.1",
|
||||||
|
"semantic-release": "^23.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,7 @@
|
|||||||
rootProject.name = "revanced-patcher"
|
rootProject.name = "revanced-patcher"
|
||||||
|
|
||||||
|
buildCache {
|
||||||
|
local {
|
||||||
|
isEnabled = "CI" !in System.getenv()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
11
src/main/kotlin/app/revanced/patcher/IntegrationsConsumer.kt
Normal file
11
src/main/kotlin/app/revanced/patcher/IntegrationsConsumer.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface IntegrationsConsumer {
|
||||||
|
fun acceptIntegrations(integrations: Set<File>)
|
||||||
|
|
||||||
|
@Deprecated("Use acceptIntegrations(Set<File>) instead.")
|
||||||
|
fun acceptIntegrations(integrations: List<File>)
|
||||||
|
}
|
||||||
7
src/main/kotlin/app/revanced/patcher/InternalApi.kt
Normal file
7
src/main/kotlin/app/revanced/patcher/InternalApi.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
@RequiresOptIn(
|
||||||
|
level = RequiresOptIn.Level.ERROR,
|
||||||
|
message = "This is an internal API, don't rely on it.",
|
||||||
|
)
|
||||||
|
annotation class InternalApi
|
||||||
16
src/main/kotlin/app/revanced/patcher/PackageMetadata.kt
Normal file
16
src/main/kotlin/app/revanced/patcher/PackageMetadata.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import brut.androlib.apk.ApkInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata about a package.
|
||||||
|
*
|
||||||
|
* @param apkInfo The [ApkInfo] of the apk file.
|
||||||
|
*/
|
||||||
|
class PackageMetadata internal constructor(internal val apkInfo: ApkInfo) {
|
||||||
|
lateinit var packageName: String
|
||||||
|
internal set
|
||||||
|
|
||||||
|
lateinit var packageVersion: String
|
||||||
|
internal set
|
||||||
|
}
|
||||||
136
src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt
Normal file
136
src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import dalvik.system.DexClassLoader
|
||||||
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
|
import lanchon.multidexlib2.MultiDexIO
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.util.jar.JarFile
|
||||||
|
import java.util.logging.Logger
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of [Patch]es.
|
||||||
|
*/
|
||||||
|
typealias PatchSet = Set<Patch<*>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Patch] class.
|
||||||
|
*/
|
||||||
|
typealias PatchClass = KClass<out Patch<*>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A loader of [Patch]es from patch bundles.
|
||||||
|
* This will load all [Patch]es from the given patch bundles that have a name.
|
||||||
|
*
|
||||||
|
* @param getBinaryClassNames A function that returns the binary names of all classes in a patch bundle.
|
||||||
|
* @param classLoader The [ClassLoader] to use for loading the classes.
|
||||||
|
* @param patchBundles A set of patches to initialize this instance with.
|
||||||
|
*/
|
||||||
|
sealed class PatchBundleLoader private constructor(
|
||||||
|
classLoader: ClassLoader,
|
||||||
|
patchBundles: Array<out File>,
|
||||||
|
getBinaryClassNames: (patchBundle: File) -> List<String>,
|
||||||
|
// This constructor parameter is unfortunately necessary,
|
||||||
|
// so that a reference to the mutable set is present in the constructor to be able to add patches to it.
|
||||||
|
// because the instance itself is a PatchSet, which is immutable, that is delegated by the parameter.
|
||||||
|
private val patchSet: MutableSet<Patch<*>> = mutableSetOf(),
|
||||||
|
) : PatchSet by patchSet {
|
||||||
|
private val logger = Logger.getLogger(PatchBundleLoader::class.java.name)
|
||||||
|
|
||||||
|
init {
|
||||||
|
patchBundles.flatMap(getBinaryClassNames).asSequence().map {
|
||||||
|
classLoader.loadClass(it)
|
||||||
|
}.filter {
|
||||||
|
Patch::class.java.isAssignableFrom(it)
|
||||||
|
}.mapNotNull { patchClass ->
|
||||||
|
patchClass.getInstance(logger, silent = true)
|
||||||
|
}.filter {
|
||||||
|
it.name != null
|
||||||
|
}.let { patches ->
|
||||||
|
patchSet.addAll(patches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal companion object Utils {
|
||||||
|
/**
|
||||||
|
* Instantiates a [Patch]. If the class is a singleton, the INSTANCE field will be used.
|
||||||
|
*
|
||||||
|
* @param logger The [Logger] to use for logging.
|
||||||
|
* @param silent Whether to suppress logging.
|
||||||
|
* @return The instantiated [Patch] or `null` if the [Patch] could not be instantiated.
|
||||||
|
*/
|
||||||
|
internal fun Class<*>.getInstance(
|
||||||
|
logger: Logger,
|
||||||
|
silent: Boolean = false,
|
||||||
|
): Patch<*>? {
|
||||||
|
return try {
|
||||||
|
getField("INSTANCE").get(null)
|
||||||
|
} catch (exception: NoSuchFieldException) {
|
||||||
|
if (!silent) {
|
||||||
|
logger.fine(
|
||||||
|
"Patch class '$name' has no INSTANCE field, therefor not a singleton. " +
|
||||||
|
"Attempting to instantiate it.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
getDeclaredConstructor().newInstance()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
if (!silent) {
|
||||||
|
logger.severe(
|
||||||
|
"Patch class '$name' is not singleton and has no suitable constructor, " +
|
||||||
|
"therefor cannot be instantiated and is ignored.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} as Patch<*>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [PatchBundleLoader] for JAR files.
|
||||||
|
*
|
||||||
|
* @param patchBundles The path to patch bundles of JAR format.
|
||||||
|
*/
|
||||||
|
class Jar(vararg patchBundles: File) : PatchBundleLoader(
|
||||||
|
URLClassLoader(patchBundles.map { it.toURI().toURL() }.toTypedArray()),
|
||||||
|
patchBundles,
|
||||||
|
{ patchBundle ->
|
||||||
|
JarFile(patchBundle).entries().toList().filter { it.name.endsWith(".class") }
|
||||||
|
.map { it.name.replace('/', '.').replace(".class", "") }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [PatchBundleLoader] for [Dex] files.
|
||||||
|
*
|
||||||
|
* @param patchBundles The path to patch bundles of DEX format.
|
||||||
|
* @param optimizedDexDirectory The directory to store optimized DEX files in.
|
||||||
|
* This parameter is deprecated and has no effect since API level 26.
|
||||||
|
*/
|
||||||
|
class Dex(vararg patchBundles: File, optimizedDexDirectory: File? = null) : PatchBundleLoader(
|
||||||
|
DexClassLoader(
|
||||||
|
patchBundles.joinToString(File.pathSeparator) { it.absolutePath },
|
||||||
|
optimizedDexDirectory?.absolutePath,
|
||||||
|
null,
|
||||||
|
PatchBundleLoader::class.java.classLoader,
|
||||||
|
),
|
||||||
|
patchBundles,
|
||||||
|
{ patchBundle ->
|
||||||
|
MultiDexIO.readDexFile(true, patchBundle, BasicDexFileNamer(), null, null).classes
|
||||||
|
.map { classDef ->
|
||||||
|
classDef.type.substring(1, classDef.length - 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
@Deprecated("This constructor is deprecated. Use the constructor with the second parameter instead.")
|
||||||
|
constructor(vararg patchBundles: File) : this(*patchBundles, optimizedDexDirectory = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.PatchResult
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>
|
||||||
@@ -1,332 +1,270 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
import app.revanced.patcher.PatchBundleLoader.Utils.getInstance
|
||||||
import app.revanced.patcher.data.PackageMetadata
|
import app.revanced.patcher.data.ResourceContext
|
||||||
import app.revanced.patcher.data.impl.findIndexed
|
import app.revanced.patcher.fingerprint.LookupMap
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
import app.revanced.patcher.fingerprint.MethodFingerprint.Companion.resolveUsingLookupMap
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patcher.extensions.nullOutputStream
|
import kotlinx.coroutines.flow.flow
|
||||||
import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils.resolve
|
import java.io.Closeable
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
import app.revanced.patcher.patch.PatchResult
|
|
||||||
import app.revanced.patcher.patch.PatchResultError
|
|
||||||
import app.revanced.patcher.patch.PatchResultSuccess
|
|
||||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
|
||||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
|
||||||
import app.revanced.patcher.util.ListBackedSet
|
|
||||||
import brut.androlib.Androlib
|
|
||||||
import brut.androlib.meta.UsesFramework
|
|
||||||
import brut.androlib.options.BuildOptions
|
|
||||||
import brut.androlib.res.AndrolibResources
|
|
||||||
import brut.androlib.res.data.ResPackage
|
|
||||||
import brut.androlib.res.decoder.AXmlResourceParser
|
|
||||||
import brut.androlib.res.decoder.ResAttrDecoder
|
|
||||||
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
|
||||||
import brut.androlib.res.xml.ResXmlPatcher
|
|
||||||
import brut.directory.ExtFile
|
|
||||||
import lanchon.multidexlib2.BasicDexFileNamer
|
|
||||||
import lanchon.multidexlib2.DexIO
|
|
||||||
import lanchon.multidexlib2.MultiDexIO
|
|
||||||
import org.jf.dexlib2.Opcodes
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
import org.jf.dexlib2.iface.DexFile
|
|
||||||
import org.jf.dexlib2.writer.io.MemoryDataStore
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Files
|
import java.util.function.Supplier
|
||||||
|
import java.util.logging.Logger
|
||||||
val NAMER = BasicDexFileNamer()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ReVanced Patcher.
|
* A Patcher.
|
||||||
* @param options The options for the patcher.
|
*
|
||||||
|
* @param config The configuration to use for the patcher.
|
||||||
*/
|
*/
|
||||||
class Patcher(private val options: PatcherOptions) {
|
class Patcher(
|
||||||
private val logger = options.logger
|
private val config: PatcherConfig,
|
||||||
private val opcodes: Opcodes
|
) : PatchExecutorFunction, PatchesConsumer, IntegrationsConsumer, Supplier<PatcherResult>, Closeable {
|
||||||
|
private val logger = Logger.getLogger(Patcher::class.java.name)
|
||||||
|
|
||||||
val data: PatcherData
|
/**
|
||||||
|
* A context for the patcher containing the current state of the patcher.
|
||||||
|
*/
|
||||||
|
val context = PatcherContext(config)
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@Deprecated("Use Patcher(PatcherConfig) instead.")
|
||||||
|
constructor(
|
||||||
|
patcherOptions: PatcherOptions,
|
||||||
|
) : this(
|
||||||
|
PatcherConfig(
|
||||||
|
patcherOptions.inputFile,
|
||||||
|
patcherOptions.resourceCachePath,
|
||||||
|
patcherOptions.aaptBinaryPath,
|
||||||
|
patcherOptions.frameworkFileDirectory,
|
||||||
|
patcherOptions.multithreadingDexFileWriter,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val extInputFile = ExtFile(options.inputFile)
|
context.resourceContext.decodeResources(ResourceContext.ResourceMode.NONE)
|
||||||
val outDir = File(options.resourceCacheDirectory)
|
|
||||||
if (outDir.exists()) {
|
|
||||||
logger.info("Deleting existing resource cache directory")
|
|
||||||
outDir.deleteRecursively()
|
|
||||||
}
|
|
||||||
outDir.mkdirs()
|
|
||||||
|
|
||||||
val androlib = Androlib(BuildOptions().also { it.setBuildOptions(options) })
|
|
||||||
val resourceTable = androlib.getResTable(extInputFile, true)
|
|
||||||
|
|
||||||
val packageMetadata = PackageMetadata()
|
|
||||||
|
|
||||||
if (options.patchResources) {
|
|
||||||
logger.info("Decoding resources")
|
|
||||||
|
|
||||||
// decode resources to cache directory
|
|
||||||
androlib.decodeManifestWithResources(extInputFile, outDir, resourceTable)
|
|
||||||
androlib.decodeResourcesFull(extInputFile, outDir, resourceTable)
|
|
||||||
|
|
||||||
// read additional metadata from the resource table
|
|
||||||
packageMetadata.metaInfo.usesFramework = UsesFramework().also { framework ->
|
|
||||||
framework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
|
|
||||||
}
|
|
||||||
|
|
||||||
packageMetadata.metaInfo.doNotCompress = buildList {
|
|
||||||
androlib.recordUncompressedFiles(extInputFile, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
logger.info("Only decoding AndroidManifest.xml because resource patching is disabled")
|
|
||||||
|
|
||||||
// create decoder for the resource table
|
|
||||||
val decoder = ResAttrDecoder()
|
|
||||||
decoder.currentPackage = ResPackage(resourceTable, 0, null)
|
|
||||||
|
|
||||||
// create xml parser with the decoder
|
|
||||||
val axmlParser = AXmlResourceParser()
|
|
||||||
axmlParser.attrDecoder = decoder
|
|
||||||
|
|
||||||
// parse package information with the decoder and parser which will set required values in the resource table
|
|
||||||
// instead of decodeManifest another more low level solution can be created to make it faster/better
|
|
||||||
XmlPullStreamDecoder(
|
|
||||||
axmlParser, AndrolibResources().resXmlSerializer
|
|
||||||
).decodeManifest(
|
|
||||||
extInputFile.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
packageMetadata.packageName = resourceTable.currentResPackage.name
|
|
||||||
packageMetadata.packageVersion = resourceTable.versionInfo.versionName
|
|
||||||
packageMetadata.metaInfo.versionInfo = resourceTable.versionInfo
|
|
||||||
packageMetadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
|
||||||
|
|
||||||
logger.info("Reading dex files")
|
|
||||||
|
|
||||||
// read dex files
|
|
||||||
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
|
|
||||||
// get the opcodes
|
|
||||||
opcodes = dexFile.opcodes
|
|
||||||
|
|
||||||
// finally create patcher data
|
|
||||||
data = PatcherData(
|
|
||||||
dexFile.classes.toMutableList(), options.resourceCacheDirectory, packageMetadata
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add additional dex file container to the patcher.
|
* Add [Patch]es to ReVanced [Patcher].
|
||||||
* @param files The dex file containers to add to the patcher.
|
*
|
||||||
* @param allowedOverwrites A list of class types that are allowed to be overwritten.
|
* @param patches The [Patch]es to add.
|
||||||
* @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found.
|
|
||||||
*/
|
*/
|
||||||
fun addFiles(
|
@Suppress("NAME_SHADOWING")
|
||||||
files: List<File>,
|
override fun acceptPatches(patches: PatchSet) {
|
||||||
allowedOverwrites: Iterable<String> = emptyList(),
|
/**
|
||||||
throwOnDuplicates: Boolean = false,
|
* Add dependencies of a [Patch] recursively to [PatcherContext.allPatches].
|
||||||
callback: (File) -> Unit
|
* If a [Patch] is already in [PatcherContext.allPatches], it will not be added again.
|
||||||
) {
|
*/
|
||||||
for (file in files) {
|
fun PatchClass.putDependenciesRecursively() {
|
||||||
var modified = false
|
if (context.allPatches.contains(this)) return
|
||||||
for (classDef in MultiDexIO.readDexFile(true, file, NAMER, null, null).classes) {
|
|
||||||
val type = classDef.type
|
|
||||||
|
|
||||||
val existingClass = data.bytecodeData.classes.internalClasses.findIndexed { it.type == type }
|
val dependency = this.java.getInstance(logger)!!
|
||||||
if (existingClass == null) {
|
context.allPatches[this] = dependency
|
||||||
if (throwOnDuplicates) throw Exception("Class $type has already been added to the patcher")
|
|
||||||
|
|
||||||
logger.trace("Merging $type")
|
dependency.dependencies?.forEach { it.putDependenciesRecursively() }
|
||||||
data.bytecodeData.classes.internalClasses.add(classDef)
|
}
|
||||||
modified = true
|
|
||||||
|
|
||||||
continue
|
// Add all patches and their dependencies to the context.
|
||||||
|
patches.forEach { patch ->
|
||||||
|
context.executablePatches.putIfAbsent(patch::class, patch) ?: run {
|
||||||
|
context.allPatches[patch::class] = patch
|
||||||
|
|
||||||
|
patch.dependencies?.forEach { it.putDependenciesRecursively() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Detect circular dependencies.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if at least one patch or its dependencies matches the given predicate.
|
||||||
|
*
|
||||||
|
* @param predicate The predicate to match.
|
||||||
|
*/
|
||||||
|
fun Patch<*>.anyRecursively(predicate: (Patch<*>) -> Boolean): Boolean =
|
||||||
|
predicate(this) || dependencies?.any { dependency ->
|
||||||
|
context.allPatches[dependency]!!.anyRecursively(predicate)
|
||||||
|
} ?: false
|
||||||
|
|
||||||
|
context.allPatches.values.let { patches ->
|
||||||
|
// Determine the resource mode.
|
||||||
|
|
||||||
|
config.resourceMode = if (patches.any { patch -> patch.anyRecursively { it is ResourcePatch } }) {
|
||||||
|
ResourceContext.ResourceMode.FULL
|
||||||
|
} else if (patches.any { patch -> patch.anyRecursively { it is RawResourcePatch } }) {
|
||||||
|
ResourceContext.ResourceMode.RAW_ONLY
|
||||||
|
} else {
|
||||||
|
ResourceContext.ResourceMode.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine, if merging integrations is required.
|
||||||
|
for (patch in patches)
|
||||||
|
if (patch.anyRecursively { it.requiresIntegrations }) {
|
||||||
|
context.bytecodeContext.integrations.merge = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allowedOverwrites.contains(type)) continue
|
|
||||||
|
|
||||||
logger.trace("Overwriting $type")
|
|
||||||
|
|
||||||
val index = existingClass.second
|
|
||||||
data.bytecodeData.classes.internalClasses[index] = classDef
|
|
||||||
modified = true
|
|
||||||
}
|
|
||||||
if (modified) callback(file)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the patched dex file.
|
* Add integrations to the [Patcher].
|
||||||
|
*
|
||||||
|
* @param integrations The integrations to add. Must be a DEX file or container of DEX files.
|
||||||
*/
|
*/
|
||||||
fun save(): PatcherResult {
|
override fun acceptIntegrations(integrations: Set<File>) {
|
||||||
val packageMetadata = data.packageMetadata
|
context.bytecodeContext.integrations.addAll(integrations)
|
||||||
val metaInfo = packageMetadata.metaInfo
|
|
||||||
var resourceFile: File? = null
|
|
||||||
|
|
||||||
if (options.patchResources) {
|
|
||||||
val cacheDirectory = ExtFile(options.resourceCacheDirectory)
|
|
||||||
|
|
||||||
val androlibResources = AndrolibResources().also { resources ->
|
|
||||||
resources.buildOptions = BuildOptions().also { buildOptions ->
|
|
||||||
buildOptions.setBuildOptions(options)
|
|
||||||
buildOptions.isFramework = metaInfo.isFrameworkApk
|
|
||||||
buildOptions.resourcesAreCompressed = metaInfo.compressionType
|
|
||||||
buildOptions.doNotCompress = metaInfo.doNotCompress
|
|
||||||
}
|
|
||||||
|
|
||||||
resources.setSdkInfo(metaInfo.sdkInfo)
|
|
||||||
resources.setVersionInfo(metaInfo.versionInfo)
|
|
||||||
resources.setSharedLibrary(metaInfo.sharedLibrary)
|
|
||||||
resources.setSparseResources(metaInfo.sparseResources)
|
|
||||||
}
|
|
||||||
|
|
||||||
val manifestFile = cacheDirectory.resolve("AndroidManifest.xml")
|
|
||||||
|
|
||||||
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifestFile)
|
|
||||||
|
|
||||||
val aaptFile = cacheDirectory.resolve("aapt_temp_file")
|
|
||||||
|
|
||||||
// delete if it exists
|
|
||||||
Files.deleteIfExists(aaptFile.toPath())
|
|
||||||
|
|
||||||
val resDirectory = cacheDirectory.resolve("res")
|
|
||||||
val includedFiles = metaInfo.usesFramework.ids.map { id ->
|
|
||||||
androlibResources.getFrameworkApk(
|
|
||||||
id, metaInfo.usesFramework.tag
|
|
||||||
)
|
|
||||||
}.toTypedArray()
|
|
||||||
|
|
||||||
logger.info("Compiling resources")
|
|
||||||
androlibResources.aaptPackage(
|
|
||||||
aaptFile, manifestFile, resDirectory, null, null, includedFiles
|
|
||||||
)
|
|
||||||
|
|
||||||
resourceFile = aaptFile
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace("Creating new dex file")
|
|
||||||
val newDexFile = object : DexFile {
|
|
||||||
override fun getClasses(): Set<ClassDef> {
|
|
||||||
data.bytecodeData.classes.applyProxies()
|
|
||||||
return ListBackedSet(data.bytecodeData.classes.internalClasses)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getOpcodes(): Opcodes {
|
|
||||||
return this@Patcher.opcodes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// write modified dex files
|
|
||||||
logger.info("Writing modified dex files")
|
|
||||||
val dexFiles = mutableMapOf<String, MemoryDataStore>()
|
|
||||||
MultiDexIO.writeDexFile(
|
|
||||||
true, -1, // core count
|
|
||||||
dexFiles, NAMER, newDexFile, DexIO.DEFAULT_MAX_DEX_POOL_SIZE, null
|
|
||||||
)
|
|
||||||
|
|
||||||
return PatcherResult(
|
|
||||||
dexFiles.map {
|
|
||||||
app.revanced.patcher.util.dex.DexFile(it.key, it.value.readAt(0))
|
|
||||||
}, metaInfo.doNotCompress.toList(), resourceFile
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Deprecated(
|
||||||
* Add [Patch]es to the patcher.
|
"Use acceptIntegrations(Set<File>) instead.",
|
||||||
* @param patches [Patch]es The patches to add.
|
ReplaceWith("acceptIntegrations(integrations.toSet())"),
|
||||||
*/
|
)
|
||||||
fun addPatches(patches: Iterable<Class<out Patch<Data>>>) {
|
override fun acceptIntegrations(integrations: List<File>) = acceptIntegrations(integrations.toSet())
|
||||||
data.patches.addAll(patches)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a [patch] and its dependencies recursively.
|
* Execute [Patch]es that were added to ReVanced [Patcher].
|
||||||
* @param patch The [patch] to apply.
|
*
|
||||||
* @param appliedPatches A map of [patch]es paired to a boolean indicating their success, to prevent infinite recursion.
|
* @param returnOnError If true, ReVanced [Patcher] will return immediately if a [Patch] fails.
|
||||||
* @return The result of executing the [patch].
|
|
||||||
*/
|
|
||||||
private fun applyPatch(
|
|
||||||
patch: Class<out Patch<Data>>,
|
|
||||||
appliedPatches: MutableMap<String, Boolean>
|
|
||||||
): PatchResult {
|
|
||||||
val patchName = patch.patchName
|
|
||||||
|
|
||||||
// if the patch has already applied silently skip it
|
|
||||||
if (appliedPatches.contains(patchName)) {
|
|
||||||
if (!appliedPatches[patchName]!!)
|
|
||||||
return PatchResultError("'$patchName' did not succeed previously")
|
|
||||||
|
|
||||||
logger.trace("Skipping '$patchName' because it has already been applied")
|
|
||||||
|
|
||||||
return PatchResultSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
// recursively apply all dependency patches
|
|
||||||
patch.dependencies?.forEach {
|
|
||||||
val patchDependency = it.java
|
|
||||||
|
|
||||||
val result = applyPatch(patchDependency, appliedPatches)
|
|
||||||
|
|
||||||
if (result.isSuccess()) return@forEach
|
|
||||||
|
|
||||||
val errorMessage = result.error()!!.message
|
|
||||||
return PatchResultError("'$patchName' depends on '${patchDependency.patchName}' but the following error was raised: $errorMessage")
|
|
||||||
}
|
|
||||||
|
|
||||||
val patchInstance = patch.getDeclaredConstructor().newInstance()
|
|
||||||
|
|
||||||
// if the current patch is a resource patch but resource patching is disabled, return an error
|
|
||||||
val isResourcePatch = patchInstance is ResourcePatch
|
|
||||||
if (!options.patchResources && isResourcePatch) {
|
|
||||||
return PatchResultError("'$patchName' is a resource patch, but resource patching is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: find a solution for this
|
|
||||||
val data = if (isResourcePatch) {
|
|
||||||
data.resourceData
|
|
||||||
} else {
|
|
||||||
val bytecodeData = data.bytecodeData
|
|
||||||
(patchInstance as BytecodePatch).fingerprints.resolve(bytecodeData, bytecodeData.classes.internalClasses)
|
|
||||||
bytecodeData
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
|
||||||
|
|
||||||
return try {
|
|
||||||
val result = patchInstance.execute(data)
|
|
||||||
appliedPatches[patchName] = result.isSuccess()
|
|
||||||
result
|
|
||||||
} catch (e: Exception) {
|
|
||||||
appliedPatches[patchName] = false
|
|
||||||
PatchResultError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply patches loaded into the patcher.
|
|
||||||
* @param stopOnError If true, the patches will stop on the first error.
|
|
||||||
* @return A pair of the name of the [Patch] and its [PatchResult].
|
* @return A pair of the name of the [Patch] and its [PatchResult].
|
||||||
*/
|
*/
|
||||||
fun applyPatches(stopOnError: Boolean = false) = sequence {
|
override fun apply(returnOnError: Boolean) =
|
||||||
logger.trace("Applying all patches")
|
flow {
|
||||||
val appliedPatches = mutableMapOf<String, Boolean>() // first is success, second is name
|
/**
|
||||||
|
* Execute a [Patch] and its dependencies recursively.
|
||||||
|
*
|
||||||
|
* @param patch The [Patch] to execute.
|
||||||
|
* @param executedPatches A map to prevent [Patch]es from being executed twice due to dependencies.
|
||||||
|
* @return The result of executing the [Patch].
|
||||||
|
*/
|
||||||
|
fun executePatch(
|
||||||
|
patch: Patch<*>,
|
||||||
|
executedPatches: LinkedHashMap<Patch<*>, PatchResult>,
|
||||||
|
): PatchResult {
|
||||||
|
val patchName = patch.toString()
|
||||||
|
|
||||||
for (patch in data.patches) {
|
executedPatches[patch]?.let { patchResult ->
|
||||||
val patchResult = applyPatch(patch, appliedPatches)
|
patchResult.exception ?: return patchResult
|
||||||
|
|
||||||
val result = if (patchResult.isSuccess()) {
|
// Return a new result with an exception indicating that the patch was not executed previously,
|
||||||
Result.success(patchResult.success()!!)
|
// because it is a dependency of another patch that failed.
|
||||||
} else {
|
return PatchResult(patch, PatchException("'$patchName' did not succeed previously"))
|
||||||
Result.failure(patchResult.error()!!)
|
}
|
||||||
|
|
||||||
|
// Recursively execute all dependency patches.
|
||||||
|
patch.dependencies?.forEach { dependencyClass ->
|
||||||
|
val dependency = context.allPatches[dependencyClass]!!
|
||||||
|
val result = executePatch(dependency, executedPatches)
|
||||||
|
|
||||||
|
result.exception?.let {
|
||||||
|
return PatchResult(
|
||||||
|
patch,
|
||||||
|
PatchException(
|
||||||
|
"'$patchName' depends on '${dependency.name ?: dependency}' " +
|
||||||
|
"that raised an exception:\n${it.stackTraceToString()}",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
// TODO: Implement this in a more polymorphic way.
|
||||||
|
when (patch) {
|
||||||
|
is BytecodePatch -> {
|
||||||
|
patch.fingerprints.resolveUsingLookupMap(context.bytecodeContext)
|
||||||
|
patch.execute(context.bytecodeContext)
|
||||||
|
}
|
||||||
|
is RawResourcePatch -> {
|
||||||
|
patch.execute(context.resourceContext)
|
||||||
|
}
|
||||||
|
is ResourcePatch -> {
|
||||||
|
patch.execute(context.resourceContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchResult(patch)
|
||||||
|
} catch (exception: PatchException) {
|
||||||
|
PatchResult(patch, exception)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
PatchResult(patch, PatchException(exception))
|
||||||
|
}.also { executedPatches[patch] = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
yield(patch.patchName to result)
|
if (context.bytecodeContext.integrations.merge) context.bytecodeContext.integrations.flush()
|
||||||
if (stopOnError && patchResult.isError()) break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BuildOptions.setBuildOptions(options: PatcherOptions) {
|
LookupMap.initializeLookupMaps(context.bytecodeContext)
|
||||||
this.aaptPath = options.aaptPath
|
|
||||||
this.useAapt2 = true
|
// Prevent from decoding the app manifest twice if it is not needed.
|
||||||
this.frameworkFolderLocation = options.frameworkFolderLocation
|
if (config.resourceMode != ResourceContext.ResourceMode.NONE) {
|
||||||
|
context.resourceContext.decodeResources(config.resourceMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Executing patches")
|
||||||
|
|
||||||
|
val executedPatches = LinkedHashMap<Patch<*>, PatchResult>() // Key is name.
|
||||||
|
|
||||||
|
context.executablePatches.values.sortedBy { it.name }.forEach { patch ->
|
||||||
|
val patchResult = executePatch(patch, executedPatches)
|
||||||
|
|
||||||
|
// If the patch failed, emit the result, even if it is closeable.
|
||||||
|
// Results of executed patches that are closeable will be emitted later.
|
||||||
|
patchResult.exception?.let {
|
||||||
|
// Propagate exception to caller instead of wrapping it in a new exception.
|
||||||
|
emit(patchResult)
|
||||||
|
|
||||||
|
if (returnOnError) return@flow
|
||||||
|
} ?: run {
|
||||||
|
if (patch is Closeable) return@run
|
||||||
|
|
||||||
|
emit(patchResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executedPatches.values
|
||||||
|
.filter { it.exception == null }
|
||||||
|
.filter { it.patch is Closeable }.asReversed().forEach { executedPatch ->
|
||||||
|
val patch = executedPatch.patch
|
||||||
|
|
||||||
|
val result =
|
||||||
|
try {
|
||||||
|
(patch as Closeable).close()
|
||||||
|
|
||||||
|
executedPatch
|
||||||
|
} catch (exception: PatchException) {
|
||||||
|
PatchResult(patch, exception)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
PatchResult(patch, PatchException(exception))
|
||||||
|
}
|
||||||
|
|
||||||
|
result.exception?.let {
|
||||||
|
emit(
|
||||||
|
PatchResult(
|
||||||
|
patch,
|
||||||
|
PatchException(
|
||||||
|
"'$patch' raised an exception while being closed: ${it.stackTraceToString()}",
|
||||||
|
result.exception,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (returnOnError) return@flow
|
||||||
|
} ?: run {
|
||||||
|
patch.name ?: return@run
|
||||||
|
|
||||||
|
emit(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() = LookupMap.clearLookupMaps()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile and save the patched APK file.
|
||||||
|
*
|
||||||
|
* @return The [PatcherResult] containing the patched input files.
|
||||||
|
*/
|
||||||
|
@OptIn(InternalApi::class)
|
||||||
|
override fun get() =
|
||||||
|
PatcherResult(
|
||||||
|
context.bytecodeContext.get(),
|
||||||
|
context.resourceContext.get(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/main/kotlin/app/revanced/patcher/PatcherConfig.kt
Normal file
72
src/main/kotlin/app/revanced/patcher/PatcherConfig.kt
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.ResourceContext
|
||||||
|
import brut.androlib.Config
|
||||||
|
import java.io.File
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration for the patcher.
|
||||||
|
*
|
||||||
|
* @param apkFile The apk file to patch.
|
||||||
|
* @param temporaryFilesPath A path to a folder to store temporary files in.
|
||||||
|
* @param aaptBinaryPath A path to a custom aapt binary.
|
||||||
|
* @param frameworkFileDirectory A path to the directory to cache the framework file in.
|
||||||
|
* @param multithreadingDexFileWriter Whether to use multiple threads for writing dex files.
|
||||||
|
* This has impact on memory usage and performance.
|
||||||
|
*/
|
||||||
|
class PatcherConfig(
|
||||||
|
internal val apkFile: File,
|
||||||
|
private val temporaryFilesPath: File = File("revanced-temporary-files"),
|
||||||
|
aaptBinaryPath: String? = null,
|
||||||
|
frameworkFileDirectory: String? = null,
|
||||||
|
internal val multithreadingDexFileWriter: Boolean = false,
|
||||||
|
) {
|
||||||
|
private val logger = Logger.getLogger(PatcherConfig::class.java.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mode to use for resource decoding and compiling.
|
||||||
|
*
|
||||||
|
* @see ResourceContext.ResourceMode
|
||||||
|
*/
|
||||||
|
internal var resourceMode = ResourceContext.ResourceMode.NONE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration for decoding and compiling resources.
|
||||||
|
*/
|
||||||
|
internal val resourceConfig =
|
||||||
|
Config.getDefaultConfig().apply {
|
||||||
|
useAapt2 = true
|
||||||
|
aaptPath = aaptBinaryPath ?: ""
|
||||||
|
frameworkDirectory = frameworkFileDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the temporary apk files directory.
|
||||||
|
*/
|
||||||
|
internal val apkFiles = temporaryFilesPath.resolve("apk")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the temporary patched files directory.
|
||||||
|
*/
|
||||||
|
internal val patchedFiles = temporaryFilesPath.resolve("patched")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the temporary files' directories.
|
||||||
|
* This will delete the existing temporary files directory if it exists.
|
||||||
|
*/
|
||||||
|
internal fun initializeTemporaryFilesDirectories() {
|
||||||
|
temporaryFilesPath.apply {
|
||||||
|
if (exists()) {
|
||||||
|
logger.info("Deleting existing temporary files directory")
|
||||||
|
|
||||||
|
if (!deleteRecursively()) {
|
||||||
|
logger.severe("Failed to delete existing temporary files directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apkFiles.mkdirs()
|
||||||
|
patchedFiles.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/main/kotlin/app/revanced/patcher/PatcherContext.kt
Normal file
40
src/main/kotlin/app/revanced/patcher/PatcherContext.kt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
|
import app.revanced.patcher.data.ResourceContext
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import brut.androlib.apk.ApkInfo
|
||||||
|
import brut.directory.ExtFile
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for the patcher containing the current state of the patcher.
|
||||||
|
*
|
||||||
|
* @param config The configuration for the patcher.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
class PatcherContext internal constructor(config: PatcherConfig) {
|
||||||
|
/**
|
||||||
|
* [PackageMetadata] of the supplied [PatcherConfig.apkFile].
|
||||||
|
*/
|
||||||
|
val packageMetadata = PackageMetadata(ApkInfo(ExtFile(config.apkFile)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map of [Patch]es associated by their [PatchClass].
|
||||||
|
*/
|
||||||
|
internal val executablePatches = mutableMapOf<PatchClass, Patch<*>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map of all [Patch]es and their dependencies associated by their [PatchClass].
|
||||||
|
*/
|
||||||
|
internal val allPatches = mutableMapOf<PatchClass, Patch<*>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for the patcher containing the current state of the resources.
|
||||||
|
*/
|
||||||
|
internal val resourceContext = ResourceContext(packageMetadata, config)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for the patcher containing the current state of the bytecode.
|
||||||
|
*/
|
||||||
|
internal val bytecodeContext = BytecodeContext(config)
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package app.revanced.patcher
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
|
||||||
import app.revanced.patcher.data.PackageMetadata
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
|
||||||
import app.revanced.patcher.data.impl.ResourceData
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
data class PatcherData(
|
|
||||||
internal val internalClasses: MutableList<ClassDef>,
|
|
||||||
internal val resourceCacheDirectory: String,
|
|
||||||
val packageMetadata: PackageMetadata
|
|
||||||
) {
|
|
||||||
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
|
||||||
internal val bytecodeData = BytecodeData(internalClasses)
|
|
||||||
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
|
||||||
}
|
|
||||||
15
src/main/kotlin/app/revanced/patcher/PatcherException.kt
Normal file
15
src/main/kotlin/app/revanced/patcher/PatcherException.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown by ReVanced [Patcher].
|
||||||
|
*
|
||||||
|
* @param errorMessage The exception message.
|
||||||
|
* @param cause The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
sealed class PatcherException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
|
||||||
|
constructor(errorMessage: String) : this(errorMessage, null)
|
||||||
|
|
||||||
|
class CircularDependencyException internal constructor(dependant: String) : PatcherException(
|
||||||
|
"Patch '$dependant' causes a circular dependency",
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.logging.Logger
|
|
||||||
import app.revanced.patcher.logging.impl.NopLogger
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
@Deprecated("Use PatcherConfig instead.")
|
||||||
* Options for the [Patcher].
|
|
||||||
* @param inputFile The input file (usually an apk file).
|
|
||||||
* @param resourceCacheDirectory Directory to cache resources.
|
|
||||||
* @param patchResources Weather to use the resource patcher. Resources will still need to be decoded.
|
|
||||||
* @param aaptPath Optional path to a custom aapt binary.
|
|
||||||
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
|
||||||
* @param logger Custom logger implementation for the [Patcher].
|
|
||||||
*/
|
|
||||||
data class PatcherOptions(
|
data class PatcherOptions(
|
||||||
internal val inputFile: File,
|
internal val inputFile: File,
|
||||||
internal val resourceCacheDirectory: String,
|
internal val resourceCachePath: File = File("revanced-resource-cache"),
|
||||||
internal val patchResources: Boolean = false,
|
internal val aaptBinaryPath: String? = null,
|
||||||
internal val aaptPath: String = "",
|
internal val frameworkFileDirectory: String? = null,
|
||||||
internal val frameworkFolderLocation: String? = null,
|
internal val multithreadingDexFileWriter: Boolean = false,
|
||||||
internal val logger: Logger = NopLogger
|
) {
|
||||||
)
|
@Deprecated("This method will be removed in the future.")
|
||||||
|
fun recreateResourceCacheDirectory(): File {
|
||||||
|
PatcherConfig(
|
||||||
|
inputFile,
|
||||||
|
resourceCachePath,
|
||||||
|
aaptBinaryPath,
|
||||||
|
frameworkFileDirectory,
|
||||||
|
multithreadingDexFileWriter,
|
||||||
|
).initializeTemporaryFilesDirectories()
|
||||||
|
|
||||||
|
return resourceCachePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,122 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.util.dex.DexFile
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import kotlin.jvm.internal.Intrinsics
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result of a patcher.
|
* The result of a patcher.
|
||||||
|
*
|
||||||
* @param dexFiles The patched dex files.
|
* @param dexFiles The patched dex files.
|
||||||
* @param doNotCompress List of relative paths to files to exclude from compressing.
|
* @param resources The patched resources.
|
||||||
* @param resourceFile File containing resources that need to be extracted into the APK.
|
|
||||||
*/
|
*/
|
||||||
data class PatcherResult(
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
val dexFiles: List<DexFile>,
|
class PatcherResult internal constructor(
|
||||||
val doNotCompress: List<String>? = null,
|
val dexFiles: Set<PatchedDexFile>,
|
||||||
val resourceFile: File?
|
val resources: PatchedResources?,
|
||||||
)
|
) {
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
constructor(
|
||||||
|
dexFiles: List<PatchedDexFile>,
|
||||||
|
resourceFile: File?,
|
||||||
|
doNotCompress: List<String>? = null,
|
||||||
|
) : this(dexFiles.toSet(), PatchedResources(resourceFile, null, doNotCompress?.toSet() ?: emptySet(), emptySet()))
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
fun component1(): List<PatchedDexFile> {
|
||||||
|
return dexFiles.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
fun component2(): File? {
|
||||||
|
return resources?.resourcesApk
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
fun component3(): List<String>? {
|
||||||
|
return resources?.doNotCompress?.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
fun copy(
|
||||||
|
dexFiles: List<PatchedDexFile>,
|
||||||
|
resourceFile: File?,
|
||||||
|
doNotCompress: List<String>? = null,
|
||||||
|
): PatcherResult {
|
||||||
|
return PatcherResult(
|
||||||
|
dexFiles.toSet(),
|
||||||
|
PatchedResources(
|
||||||
|
resourceFile,
|
||||||
|
null,
|
||||||
|
doNotCompress?.toSet() ?: emptySet(),
|
||||||
|
emptySet(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
override fun toString(): String {
|
||||||
|
return (("PatcherResult(dexFiles=" + this.dexFiles + ", resourceFile=" + this.resources?.resourcesApk) + ", doNotCompress=" + this.resources?.doNotCompress) + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
val result = dexFiles.hashCode()
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
(result * 31) +
|
||||||
|
(if (this.resources?.resourcesApk == null) 0 else this.resources?.resourcesApk.hashCode())
|
||||||
|
) * 31
|
||||||
|
) +
|
||||||
|
(if (this.resources?.doNotCompress == null) 0 else this.resources?.doNotCompress.hashCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (other is PatcherResult) {
|
||||||
|
return Intrinsics.areEqual(this.dexFiles, other.dexFiles) && Intrinsics.areEqual(
|
||||||
|
this.resources?.resourcesApk,
|
||||||
|
other.resources?.resourcesApk,
|
||||||
|
) && Intrinsics.areEqual(this.resources?.doNotCompress, other.resources?.doNotCompress)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
fun getDexFiles() = component1()
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
fun getResourceFile() = component2()
|
||||||
|
|
||||||
|
@Deprecated("This method is not used anymore")
|
||||||
|
fun getDoNotCompress() = component3()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dex file.
|
||||||
|
*
|
||||||
|
* @param name The original name of the dex file.
|
||||||
|
* @param stream The dex file as [InputStream].
|
||||||
|
*/
|
||||||
|
class PatchedDexFile
|
||||||
|
// TODO: Add internal modifier.
|
||||||
|
@Deprecated("This constructor will be removed in the future.")
|
||||||
|
constructor(val name: String, val stream: InputStream)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resources of a patched apk.
|
||||||
|
*
|
||||||
|
* @param resourcesApk The compiled resources.apk file.
|
||||||
|
* @param otherResources The directory containing other resources files.
|
||||||
|
* @param doNotCompress List of files that should not be compressed.
|
||||||
|
* @param deleteResources List of predicates about resources that should be deleted.
|
||||||
|
*/
|
||||||
|
class PatchedResources internal constructor(
|
||||||
|
val resourcesApk: File?,
|
||||||
|
val otherResources: File?,
|
||||||
|
val doNotCompress: Set<String>,
|
||||||
|
val deleteResources: Set<(String) -> Boolean>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
10
src/main/kotlin/app/revanced/patcher/PatchesConsumer.kt
Normal file
10
src/main/kotlin/app/revanced/patcher/PatchesConsumer.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface PatchesConsumer {
|
||||||
|
@Deprecated("Use acceptPatches(PatchSet) instead.", ReplaceWith("acceptPatches(patches.toSet())"))
|
||||||
|
fun acceptPatches(patches: List<Patch<*>>) = acceptPatches(patches.toSet())
|
||||||
|
fun acceptPatches(patches: PatchSet)
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package app.revanced.patcher.annotation
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to constrain a [Patch] or [MethodFingerprint] to compatible packages.
|
|
||||||
* @param compatiblePackages A list of packages a [Patch] or [MethodFingerprint] is compatible with.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Compatibility(
|
|
||||||
val compatiblePackages: Array<Package>,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to represent packages a patch can be compatible with.
|
|
||||||
* @param name The package identifier name.
|
|
||||||
* @param versions The versions of the package the [Patch] or [MethodFingerprint]is compatible with.
|
|
||||||
*/
|
|
||||||
@Target()
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Package(
|
|
||||||
val name: String,
|
|
||||||
val versions: Array<String>
|
|
||||||
)
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package app.revanced.patcher.annotation
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to name a [Patch] or [MethodFingerprint].
|
|
||||||
* @param name A suggestive name for the [Patch] or [MethodFingerprint].
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Name(
|
|
||||||
val name: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to describe a [Patch] or [MethodFingerprint].
|
|
||||||
* @param description A description for the [Patch] or [MethodFingerprint].
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Description(
|
|
||||||
val description: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to version a [Patch] or [MethodFingerprint].
|
|
||||||
* @param version The version of a [Patch] or [MethodFingerprint].
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Version(
|
|
||||||
val version: String,
|
|
||||||
)
|
|
||||||
184
src/main/kotlin/app/revanced/patcher/data/BytecodeContext.kt
Normal file
184
src/main/kotlin/app/revanced/patcher/data/BytecodeContext.kt
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
|
import app.revanced.patcher.InternalApi
|
||||||
|
import app.revanced.patcher.PatcherConfig
|
||||||
|
import app.revanced.patcher.PatcherContext
|
||||||
|
import app.revanced.patcher.PatcherResult
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import app.revanced.patcher.util.ClassMerger.merge
|
||||||
|
import app.revanced.patcher.util.ProxyClassList
|
||||||
|
import app.revanced.patcher.util.method.MethodWalker
|
||||||
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
|
import com.android.tools.smali.dexlib2.Opcodes
|
||||||
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
|
import com.android.tools.smali.dexlib2.iface.DexFile
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
|
import lanchon.multidexlib2.DexIO
|
||||||
|
import lanchon.multidexlib2.MultiDexIO
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileFilter
|
||||||
|
import java.io.Flushable
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for the patcher containing the current state of the bytecode.
|
||||||
|
*
|
||||||
|
* @param config The [PatcherConfig] used to create this context.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
class BytecodeContext internal constructor(private val config: PatcherConfig) :
|
||||||
|
Context<Set<PatcherResult.PatchedDexFile>> {
|
||||||
|
private val logger = Logger.getLogger(BytecodeContext::class.java.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Opcodes] of the supplied [PatcherConfig.apkFile].
|
||||||
|
*/
|
||||||
|
internal lateinit var opcodes: Opcodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of classes.
|
||||||
|
*/
|
||||||
|
val classes by lazy {
|
||||||
|
ProxyClassList(
|
||||||
|
MultiDexIO.readDexFile(
|
||||||
|
true,
|
||||||
|
config.apkFile,
|
||||||
|
BasicDexFileNamer(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
).also { opcodes = it.opcodes }.classes.toMutableSet(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Integrations] of this [PatcherContext].
|
||||||
|
*/
|
||||||
|
internal val integrations = Integrations()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a class by a given class name.
|
||||||
|
*
|
||||||
|
* @param className The name of the class.
|
||||||
|
* @return A proxy for the first class that matches the class name.
|
||||||
|
*/
|
||||||
|
fun findClass(className: String) = findClass { it.type.contains(className) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a class by a given predicate.
|
||||||
|
*
|
||||||
|
* @param predicate A predicate to match the class.
|
||||||
|
* @return A proxy for the first class that matches the predicate.
|
||||||
|
*/
|
||||||
|
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||||
|
// if we already proxied the class matching the predicate...
|
||||||
|
classes.proxies.firstOrNull { predicate(it.immutableClass) }
|
||||||
|
?: // else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||||
|
classes.find(predicate)?.let { proxy(it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy a class.
|
||||||
|
* This will allow the class to be modified.
|
||||||
|
*
|
||||||
|
* @param classDef The class to proxy.
|
||||||
|
* @return A proxy for the class.
|
||||||
|
*/
|
||||||
|
fun proxy(classDef: ClassDef) =
|
||||||
|
this.classes.proxies.find { it.immutableClass.type == classDef.type } ?: let {
|
||||||
|
ClassProxy(classDef).also { this.classes.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [MethodWalker] instance for the current [BytecodeContext].
|
||||||
|
*
|
||||||
|
* @param startMethod The method to start at.
|
||||||
|
* @return A [MethodWalker] instance.
|
||||||
|
*/
|
||||||
|
fun toMethodWalker(startMethod: Method) = MethodWalker(this, startMethod)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile bytecode from the [BytecodeContext].
|
||||||
|
*
|
||||||
|
* @return The compiled bytecode.
|
||||||
|
*/
|
||||||
|
@InternalApi
|
||||||
|
override fun get(): Set<PatcherResult.PatchedDexFile> {
|
||||||
|
logger.info("Compiling patched dex files")
|
||||||
|
|
||||||
|
val patchedDexFileResults =
|
||||||
|
config.patchedFiles.resolve("dex").also {
|
||||||
|
it.deleteRecursively() // Make sure the directory is empty.
|
||||||
|
it.mkdirs()
|
||||||
|
}.apply {
|
||||||
|
MultiDexIO.writeDexFile(
|
||||||
|
true,
|
||||||
|
if (config.multithreadingDexFileWriter) -1 else 1,
|
||||||
|
this,
|
||||||
|
BasicDexFileNamer(),
|
||||||
|
object : DexFile {
|
||||||
|
override fun getClasses() = this@BytecodeContext.classes.also(ProxyClassList::replaceClasses)
|
||||||
|
|
||||||
|
override fun getOpcodes() = this@BytecodeContext.opcodes
|
||||||
|
},
|
||||||
|
DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
|
||||||
|
) { _, entryName, _ -> logger.info("Compiled $entryName") }
|
||||||
|
}.listFiles(FileFilter { it.isFile })!!.map {
|
||||||
|
PatcherResult.PatchedDexFile(it.name, it.inputStream())
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
|
System.gc()
|
||||||
|
|
||||||
|
return patchedDexFileResults
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The integrations of a [PatcherContext].
|
||||||
|
*/
|
||||||
|
internal inner class Integrations : MutableList<File> by mutableListOf(), Flushable {
|
||||||
|
/**
|
||||||
|
* Whether to merge integrations.
|
||||||
|
* Set to true, if the field requiresIntegrations of any supplied [Patch] is true.
|
||||||
|
*/
|
||||||
|
var merge = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge integrations into the [BytecodeContext] and flush all [Integrations].
|
||||||
|
*/
|
||||||
|
override fun flush() {
|
||||||
|
if (!merge) return
|
||||||
|
|
||||||
|
logger.info("Merging integrations")
|
||||||
|
|
||||||
|
val classMap = classes.associateBy { it.type }
|
||||||
|
|
||||||
|
this@Integrations.forEach { integrations ->
|
||||||
|
MultiDexIO.readDexFile(
|
||||||
|
true,
|
||||||
|
integrations,
|
||||||
|
BasicDexFileNamer(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
).classes.forEach classDef@{ classDef ->
|
||||||
|
val existingClass =
|
||||||
|
classMap[classDef.type] ?: run {
|
||||||
|
logger.fine("Adding $classDef")
|
||||||
|
classes.add(classDef)
|
||||||
|
return@classDef
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.fine("$classDef exists. Adding missing methods and fields.")
|
||||||
|
|
||||||
|
existingClass.merge(classDef, this@BytecodeContext).let { mergedClass ->
|
||||||
|
// If the class was merged, replace the original class with the merged class.
|
||||||
|
if (mergedClass === existingClass) return@let
|
||||||
|
classes.apply {
|
||||||
|
remove(existingClass)
|
||||||
|
add(mergedClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/main/kotlin/app/revanced/patcher/data/Context.kt
Normal file
9
src/main/kotlin/app/revanced/patcher/data/Context.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common interface for contexts such as [ResourceContext] and [BytecodeContext].
|
||||||
|
*/
|
||||||
|
|
||||||
|
sealed interface Context<T> : Supplier<T>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package app.revanced.patcher.data
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
|
||||||
import app.revanced.patcher.data.impl.ResourceData
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constraint interface for [BytecodeData] and [ResourceData]
|
|
||||||
*/
|
|
||||||
interface Data
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package app.revanced.patcher.data
|
|
||||||
|
|
||||||
import brut.androlib.meta.MetaInfo
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metadata about a package.
|
|
||||||
*/
|
|
||||||
class PackageMetadata {
|
|
||||||
lateinit var packageName: String
|
|
||||||
lateinit var packageVersion: String
|
|
||||||
|
|
||||||
internal val metaInfo: MetaInfo = MetaInfo()
|
|
||||||
}
|
|
||||||
256
src/main/kotlin/app/revanced/patcher/data/ResourceContext.kt
Normal file
256
src/main/kotlin/app/revanced/patcher/data/ResourceContext.kt
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
|
import app.revanced.patcher.InternalApi
|
||||||
|
import app.revanced.patcher.PackageMetadata
|
||||||
|
import app.revanced.patcher.PatcherConfig
|
||||||
|
import app.revanced.patcher.PatcherResult
|
||||||
|
import app.revanced.patcher.util.Document
|
||||||
|
import app.revanced.patcher.util.DomFileEditor
|
||||||
|
import brut.androlib.AaptInvoker
|
||||||
|
import brut.androlib.ApkDecoder
|
||||||
|
import brut.androlib.apk.UsesFramework
|
||||||
|
import brut.androlib.res.Framework
|
||||||
|
import brut.androlib.res.ResourcesDecoder
|
||||||
|
import brut.androlib.res.decoder.AndroidManifestResourceParser
|
||||||
|
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
||||||
|
import brut.androlib.res.xml.ResXmlPatcher
|
||||||
|
import brut.directory.ExtFile
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for the patcher containing the current state of the resources.
|
||||||
|
*
|
||||||
|
* @param packageMetadata The [PackageMetadata] of the apk file.
|
||||||
|
* @param config The [PatcherConfig] used to create this context.
|
||||||
|
*/
|
||||||
|
class ResourceContext internal constructor(
|
||||||
|
private val packageMetadata: PackageMetadata,
|
||||||
|
private val config: PatcherConfig,
|
||||||
|
) : Context<PatcherResult.PatchedResources?>, Iterable<File> {
|
||||||
|
private val logger = Logger.getLogger(ResourceContext::class.java.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and write documents in the [PatcherConfig.apkFiles].
|
||||||
|
*/
|
||||||
|
val document = DocumentOperatable()
|
||||||
|
|
||||||
|
@Deprecated("Use document instead.")
|
||||||
|
val xmlEditor = XmlFileHolder()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Predicate to delete resources from [PatcherConfig.apkFiles].
|
||||||
|
*/
|
||||||
|
private val deleteResources = mutableSetOf<(String) -> Boolean>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode resources of [PatcherConfig.apkFile].
|
||||||
|
*
|
||||||
|
* @param mode The [ResourceMode] to use.
|
||||||
|
*/
|
||||||
|
internal fun decodeResources(mode: ResourceMode) =
|
||||||
|
with(packageMetadata.apkInfo) {
|
||||||
|
config.initializeTemporaryFilesDirectories()
|
||||||
|
|
||||||
|
// Needed to decode resources.
|
||||||
|
val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this)
|
||||||
|
|
||||||
|
if (mode == ResourceMode.FULL) {
|
||||||
|
logger.info("Decoding resources")
|
||||||
|
|
||||||
|
resourcesDecoder.decodeResources(config.apkFiles)
|
||||||
|
resourcesDecoder.decodeManifest(config.apkFiles)
|
||||||
|
|
||||||
|
// Needed to record uncompressed files.
|
||||||
|
val apkDecoder = ApkDecoder(config.resourceConfig, this)
|
||||||
|
apkDecoder.recordUncompressedFiles(resourcesDecoder.resFileMapping)
|
||||||
|
|
||||||
|
usesFramework =
|
||||||
|
UsesFramework().apply {
|
||||||
|
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Decoding app manifest")
|
||||||
|
|
||||||
|
// Decode manually instead of using resourceDecoder.decodeManifest
|
||||||
|
// because it does not support decoding to an OutputStream.
|
||||||
|
XmlPullStreamDecoder(
|
||||||
|
AndroidManifestResourceParser(resourcesDecoder.resTable),
|
||||||
|
resourcesDecoder.resXmlSerializer,
|
||||||
|
).decodeManifest(
|
||||||
|
apkFile.directory.getFileInput("AndroidManifest.xml"),
|
||||||
|
// Older Android versions do not support OutputStream.nullOutputStream()
|
||||||
|
object : OutputStream() {
|
||||||
|
override fun write(b: Int) { // Do nothing.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
|
||||||
|
// XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo.
|
||||||
|
packageMetadata.let { metadata ->
|
||||||
|
metadata.packageName = resourcesDecoder.resTable.packageRenamed
|
||||||
|
versionInfo.let {
|
||||||
|
metadata.packageVersion = it.versionName ?: it.versionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The ResTable if flagged as sparse if the main package is not loaded, which is the case here,
|
||||||
|
because ResourcesDecoder.decodeResources loads the main package
|
||||||
|
and not XmlPullStreamDecoder.decodeManifest.
|
||||||
|
See ARSCDecoder.readTableType for more info.
|
||||||
|
|
||||||
|
Set this to false again to prevent the ResTable from being flagged as sparse falsely.
|
||||||
|
*/
|
||||||
|
metadata.apkInfo.sparseResources = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile resources in [PatcherConfig.apkFiles].
|
||||||
|
*
|
||||||
|
* @return The [PatcherResult.PatchedResources].
|
||||||
|
*/
|
||||||
|
@InternalApi
|
||||||
|
override fun get(): PatcherResult.PatchedResources? {
|
||||||
|
if (config.resourceMode == ResourceMode.NONE) return null
|
||||||
|
|
||||||
|
logger.info("Compiling modified resources")
|
||||||
|
|
||||||
|
val resources = config.patchedFiles.resolve("resources").also { it.mkdirs() }
|
||||||
|
|
||||||
|
val resourcesApkFile =
|
||||||
|
if (config.resourceMode == ResourceMode.FULL) {
|
||||||
|
resources.resolve("resources.apk").apply {
|
||||||
|
// Compile the resources.apk file.
|
||||||
|
AaptInvoker(
|
||||||
|
config.resourceConfig,
|
||||||
|
packageMetadata.apkInfo,
|
||||||
|
).invokeAapt(
|
||||||
|
resources.resolve("resources.apk"),
|
||||||
|
config.apkFiles.resolve("AndroidManifest.xml").also {
|
||||||
|
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it)
|
||||||
|
},
|
||||||
|
config.apkFiles.resolve("res"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
packageMetadata.apkInfo.usesFramework.let { usesFramework ->
|
||||||
|
usesFramework.ids.map { id ->
|
||||||
|
Framework(config.resourceConfig).getFrameworkApk(id, usesFramework.tag)
|
||||||
|
}.toTypedArray()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val otherFiles =
|
||||||
|
config.apkFiles.listFiles()!!.filter {
|
||||||
|
// Excluded because present in resources.other.
|
||||||
|
// TODO: We are reusing config.apkFiles as a temporarily directory for extracting resources.
|
||||||
|
// This is not ideal as it could conflict with files such as the ones that we filter here.
|
||||||
|
// The problem is that ResourceContext#get returns a File relative to config.apkFiles,
|
||||||
|
// and we need to extract files to that directory.
|
||||||
|
// A solution would be to use config.apkFiles as the working directory for the patching process.
|
||||||
|
// Once all patches have been executed, we can move the decoded resources to a new directory.
|
||||||
|
// The filters wouldn't be needed anymore.
|
||||||
|
// For now, we assume that the files we filter here are not needed for the patching process.
|
||||||
|
it.name != "AndroidManifest.xml" &&
|
||||||
|
it.name != "res" &&
|
||||||
|
// Generated by Androlib.
|
||||||
|
it.name != "build"
|
||||||
|
}
|
||||||
|
|
||||||
|
val otherResourceFiles =
|
||||||
|
if (otherFiles.isNotEmpty()) {
|
||||||
|
// Move the other resources files.
|
||||||
|
resources.resolve("other").also { it.mkdirs() }.apply {
|
||||||
|
otherFiles.forEach { file ->
|
||||||
|
Files.move(file.toPath(), resolve(file.name).toPath())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatcherResult.PatchedResources(
|
||||||
|
resourcesApkFile,
|
||||||
|
otherResourceFiles,
|
||||||
|
packageMetadata.apkInfo.doNotCompress?.toSet() ?: emptySet(),
|
||||||
|
deleteResources,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a file from [PatcherConfig.apkFiles].
|
||||||
|
*
|
||||||
|
* @param path The path of the file.
|
||||||
|
* @param copy Whether to copy the file from [PatcherConfig.apkFile] if it does not exist yet in [PatcherConfig.apkFiles].
|
||||||
|
*/
|
||||||
|
operator fun get(
|
||||||
|
path: String,
|
||||||
|
copy: Boolean = true,
|
||||||
|
) = config.apkFiles.resolve(path).apply {
|
||||||
|
if (copy && !exists()) {
|
||||||
|
with(ExtFile(config.apkFile).directory) {
|
||||||
|
if (containsFile(path) || containsDir(path)) {
|
||||||
|
copyToDir(config.apkFiles, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stage a file to be deleted from [PatcherConfig.apkFile].
|
||||||
|
*
|
||||||
|
* @param shouldDelete The predicate to stage the file for deletion given its name.
|
||||||
|
*/
|
||||||
|
fun stageDelete(shouldDelete: (String) -> Boolean) = deleteResources.add(shouldDelete)
|
||||||
|
|
||||||
|
@Deprecated("Use get(String, Boolean) instead.", ReplaceWith("get(path, false)"))
|
||||||
|
operator fun get(path: String) = get(path, false)
|
||||||
|
|
||||||
|
@Deprecated("Use get(String, Boolean) instead.")
|
||||||
|
override fun iterator(): Iterator<File> = config.apkFiles.listFiles()!!.iterator()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How to handle resources decoding and compiling.
|
||||||
|
*/
|
||||||
|
internal enum class ResourceMode {
|
||||||
|
/**
|
||||||
|
* Decode and compile all resources.
|
||||||
|
*/
|
||||||
|
FULL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only extract resources from the APK.
|
||||||
|
* The AndroidManifest.xml and resources inside /res are not decoded or compiled.
|
||||||
|
*/
|
||||||
|
RAW_ONLY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not decode or compile any resources.
|
||||||
|
*/
|
||||||
|
NONE,
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class DocumentOperatable {
|
||||||
|
operator fun get(inputStream: InputStream) = Document(inputStream)
|
||||||
|
|
||||||
|
operator fun get(path: String) = Document(this@ResourceContext[path])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use DocumentOperatable instead.")
|
||||||
|
inner class XmlFileHolder {
|
||||||
|
operator fun get(inputStream: InputStream) = DomFileEditor(inputStream)
|
||||||
|
|
||||||
|
operator fun get(path: String): DomFileEditor {
|
||||||
|
return DomFileEditor(this@ResourceContext[path])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package app.revanced.patcher.data.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
|
||||||
import app.revanced.patcher.util.ProxyBackedClassList
|
|
||||||
import app.revanced.patcher.util.method.MethodWalker
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
|
|
||||||
class BytecodeData(
|
|
||||||
internalClasses: MutableList<ClassDef>
|
|
||||||
) : Data {
|
|
||||||
val classes = ProxyBackedClassList(internalClasses)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a class by a given class name.
|
|
||||||
* @param className The name of the class.
|
|
||||||
* @return A proxy for the first class that matches the class name.
|
|
||||||
*/
|
|
||||||
fun findClass(className: String) = findClass { it.type.contains(className) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a class by a given predicate.
|
|
||||||
* @param predicate A predicate to match the class.
|
|
||||||
* @return A proxy for the first class that matches the predicate.
|
|
||||||
*/
|
|
||||||
fun findClass(predicate: (ClassDef) -> Boolean) =
|
|
||||||
// if we already proxied the class matching the predicate...
|
|
||||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
|
||||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
|
||||||
classes.find(predicate)?.let { proxy(it) }
|
|
||||||
|
|
||||||
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
|
||||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
|
||||||
if (proxy == null) {
|
|
||||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
|
||||||
this.classes.add(proxy)
|
|
||||||
}
|
|
||||||
return proxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class MethodNotFoundException(s: String) : Exception(s)
|
|
||||||
|
|
||||||
internal inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
|
|
||||||
for (element in this) {
|
|
||||||
if (predicate(element)) {
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [MethodWalker] instance for the current [BytecodeData].
|
|
||||||
* @param startMethod The method to start at.
|
|
||||||
* @return A [MethodWalker] instance.
|
|
||||||
*/
|
|
||||||
fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
|
|
||||||
return MethodWalker(this, startMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
|
|
||||||
for ((index, element) in this.withIndex()) {
|
|
||||||
if (predicate(element)) {
|
|
||||||
return element to index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package app.revanced.patcher.data.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
|
||||||
import org.w3c.dom.Document
|
|
||||||
import java.io.Closeable
|
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
|
||||||
import javax.xml.transform.TransformerFactory
|
|
||||||
import javax.xml.transform.dom.DOMSource
|
|
||||||
import javax.xml.transform.stream.StreamResult
|
|
||||||
|
|
||||||
class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<File> {
|
|
||||||
val xmlEditor = XmlFileHolder()
|
|
||||||
|
|
||||||
operator fun get(path: String) = resourceCacheDirectory.resolve(path)
|
|
||||||
|
|
||||||
override fun iterator() = resourceCacheDirectory.walkTopDown().iterator()
|
|
||||||
|
|
||||||
inner class XmlFileHolder {
|
|
||||||
operator fun get(inputStream: InputStream, outputStream: OutputStream) =
|
|
||||||
DomFileEditor(inputStream, outputStream)
|
|
||||||
|
|
||||||
operator fun get(path: String) = DomFileEditor(this@ResourceData[path])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DomFileEditor internal constructor(inputStream: InputStream, private val outputStream: OutputStream) : Closeable {
|
|
||||||
constructor(file: File) : this(file.inputStream(), file.outputStream())
|
|
||||||
|
|
||||||
val file: Document =
|
|
||||||
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream).also(Document::normalize)
|
|
||||||
|
|
||||||
override fun close() =
|
|
||||||
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), StreamResult(outputStream))
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,57 +1,61 @@
|
|||||||
|
@file:Suppress("UNCHECKED_CAST")
|
||||||
|
|
||||||
package app.revanced.patcher.extensions
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
import app.revanced.patcher.annotation.Compatibility
|
|
||||||
import app.revanced.patcher.annotation.Description
|
|
||||||
import app.revanced.patcher.annotation.Name
|
|
||||||
import app.revanced.patcher.annotation.Version
|
|
||||||
import app.revanced.patcher.data.Data
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
internal object AnnotationExtensions {
|
||||||
* Recursively find a given annotation on a class.
|
/**
|
||||||
* @param targetAnnotation The annotation to find.
|
* Search for an annotation recursively.
|
||||||
* @return The annotation.
|
*
|
||||||
*/
|
* @param targetAnnotationClass The annotation class to search for.
|
||||||
private fun <T : Annotation> Class<*>.recursiveAnnotation(targetAnnotation: KClass<T>) =
|
* @param searchedClasses A set of annotations that have already been searched.
|
||||||
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
* @return The annotation if found, otherwise null.
|
||||||
|
*/
|
||||||
|
fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
||||||
|
targetAnnotationClass: Class<T>,
|
||||||
|
searchedClasses: HashSet<Annotation> = hashSetOf(),
|
||||||
|
): T? {
|
||||||
|
annotations.forEach { annotation ->
|
||||||
|
// Terminate if the annotation is already searched.
|
||||||
|
if (annotation in searchedClasses) return@forEach
|
||||||
|
searchedClasses.add(annotation)
|
||||||
|
|
||||||
|
// Terminate if the annotation is found.
|
||||||
|
if (targetAnnotationClass == annotation.annotationClass.java) return annotation as T
|
||||||
|
|
||||||
private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
return annotation.annotationClass.java.findAnnotationRecursively(
|
||||||
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
|
targetAnnotationClass,
|
||||||
): T? {
|
searchedClasses,
|
||||||
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
) ?: return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
|
// Search the super class.
|
||||||
|
superclass?.findAnnotationRecursively(
|
||||||
|
targetAnnotationClass,
|
||||||
|
searchedClasses,
|
||||||
|
)?.let { return it }
|
||||||
|
|
||||||
for (annotation in this.annotations) {
|
// Search the interfaces.
|
||||||
if (traversed.contains(annotation)) continue
|
interfaces.forEach { superClass ->
|
||||||
traversed.add(annotation)
|
return superClass.findAnnotationRecursively(
|
||||||
|
targetAnnotationClass,
|
||||||
|
searchedClasses,
|
||||||
|
) ?: return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed)) ?: continue
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
/**
|
||||||
|
* Search for an annotation recursively.
|
||||||
|
*
|
||||||
|
* First the annotations, then the annotated classes super class and then it's interfaces
|
||||||
|
* are searched for the annotation recursively.
|
||||||
|
*
|
||||||
|
* @param targetAnnotation The annotation to search for.
|
||||||
|
* @return The annotation if found, otherwise null.
|
||||||
|
*/
|
||||||
|
fun <T : Annotation> KClass<*>.findAnnotationRecursively(targetAnnotation: KClass<T>) =
|
||||||
|
java.findAnnotationRecursively(targetAnnotation.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
object PatchExtensions {
|
|
||||||
val Class<out Patch<Data>>.patchName: String
|
|
||||||
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
|
||||||
val Class<out Patch<Data>>.version get() = recursiveAnnotation(Version::class)?.version
|
|
||||||
val Class<out Patch<Data>>.include get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)!!.include
|
|
||||||
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
|
||||||
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Dependencies::class)?.dependencies
|
|
||||||
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
|
||||||
}
|
|
||||||
|
|
||||||
object MethodFingerprintExtensions {
|
|
||||||
val MethodFingerprint.name: String
|
|
||||||
get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
|
||||||
val MethodFingerprint.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
|
||||||
val MethodFingerprint.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
|
||||||
val MethodFingerprint.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
|
||||||
val MethodFingerprint.matchingMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.MatchingMethod::class)
|
|
||||||
val MethodFingerprint.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod::class)
|
|
||||||
val MethodFingerprint.fuzzyScanThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
|
||||||
}
|
|
||||||
@@ -1,178 +1,33 @@
|
|||||||
package app.revanced.patcher.extensions
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import app.revanced.patcher.util.smali.toInstruction
|
|
||||||
import app.revanced.patcher.util.smali.toInstructions
|
|
||||||
import org.jf.dexlib2.AccessFlags
|
|
||||||
import org.jf.dexlib2.builder.BuilderInstruction
|
|
||||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference
|
|
||||||
import org.jf.dexlib2.immutable.ImmutableMethod
|
|
||||||
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
|
||||||
import org.jf.dexlib2.util.MethodUtil
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
|
/**
|
||||||
|
* Create a label for the instruction at given index.
|
||||||
|
*
|
||||||
|
* @param index The index to create the label for the instruction at.
|
||||||
|
* @return The label.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a bitwise OR operation between an [AccessFlags] and an [Int].
|
||||||
|
*
|
||||||
|
* @param other The [Int] to perform the operation with.
|
||||||
|
*/
|
||||||
infix fun Int.or(other: AccessFlags) = this or other.value
|
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||||
|
|
||||||
fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
/**
|
||||||
for (i in instructions.lastIndex downTo 0) {
|
* Perform a bitwise OR operation between two [AccessFlags].
|
||||||
this.addInstruction(index, instructions[i])
|
*
|
||||||
}
|
* @param other The other [AccessFlags] to perform the operation with.
|
||||||
}
|
*/
|
||||||
|
infix fun AccessFlags.or(other: AccessFlags) = value or other.value
|
||||||
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
|
||||||
for (i in instructions.lastIndex downTo 0) {
|
|
||||||
this.replaceInstruction(index + i, instructions[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) {
|
|
||||||
for (i in count downTo 0) {
|
|
||||||
this.removeInstruction(index + i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare a method to another, considering constructors and parameters.
|
* Perform a bitwise OR operation between an [Int] and an [AccessFlags].
|
||||||
* @param otherMethod The method to compare against.
|
*
|
||||||
* @return True if the methods match given the conditions.
|
* @param other The [AccessFlags] to perform the operation with.
|
||||||
*/
|
*/
|
||||||
fun Method.softCompareTo(
|
infix fun AccessFlags.or(other: Int) = value or other
|
||||||
otherMethod: MethodReference
|
|
||||||
): Boolean {
|
|
||||||
if (MethodUtil.isConstructor(this) && !parametersEqual(this.parameterTypes, otherMethod.parameterTypes))
|
|
||||||
return false
|
|
||||||
return this.name == otherMethod.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clones the method.
|
|
||||||
* @param registerCount This parameter allows you to change the register count of the method.
|
|
||||||
* This may be a positive or negative number.
|
|
||||||
* @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy.
|
|
||||||
*/
|
|
||||||
internal fun Method.clone(
|
|
||||||
registerCount: Int = 0,
|
|
||||||
): ImmutableMethod {
|
|
||||||
val clonedImplementation = implementation?.let {
|
|
||||||
ImmutableMethodImplementation(
|
|
||||||
it.registerCount + registerCount,
|
|
||||||
it.instructions,
|
|
||||||
it.tryBlocks,
|
|
||||||
it.debugItems,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return ImmutableMethod(
|
|
||||||
returnType,
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
returnType,
|
|
||||||
accessFlags,
|
|
||||||
annotations,
|
|
||||||
hiddenApiRestrictions,
|
|
||||||
clonedImplementation
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a smali instruction to the method.
|
|
||||||
* @param instruction The smali instruction to add.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.addInstruction(instruction: String) =
|
|
||||||
this.implementation!!.addInstruction(instruction.toInstruction(this))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a smali instruction to the method.
|
|
||||||
* @param index The index to insert the instruction at.
|
|
||||||
* @param instruction The smali instruction to add.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.addInstruction(index: Int, instruction: String) =
|
|
||||||
this.implementation!!.addInstruction(index, instruction.toInstruction(this))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace a smali instruction within the method.
|
|
||||||
* @param index The index to replace the instruction at.
|
|
||||||
* @param instruction The smali instruction to place.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.replaceInstruction(index: Int, instruction: String) =
|
|
||||||
this.implementation!!.replaceInstruction(index, instruction.toInstruction(this))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a smali instruction within the method.
|
|
||||||
* @param index The index to delete the instruction at.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.removeInstruction(index: Int) =
|
|
||||||
this.implementation!!.removeInstruction(index)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add smali instructions to the method.
|
|
||||||
* @param index The index to insert the instructions at.
|
|
||||||
* @param instructions The smali instructions to add.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.addInstructions(index: Int, instructions: String) =
|
|
||||||
this.implementation!!.addInstructions(index, instructions.toInstructions(this))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace smali instructions within the method.
|
|
||||||
* @param index The index to replace the instructions at.
|
|
||||||
* @param instructions The smali instructions to place.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.replaceInstructions(index: Int, instructions: String) =
|
|
||||||
this.implementation!!.replaceInstructions(index, instructions.toInstructions(this))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove smali instructions from the method.
|
|
||||||
* @param index The index to remove the instructions at.
|
|
||||||
* @param count The amount of instructions to remove.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.removeInstructions(index: Int, count: Int) =
|
|
||||||
this.implementation!!.removeInstructions(index, count)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clones the method.
|
|
||||||
* @param registerCount This parameter allows you to change the register count of the method.
|
|
||||||
* This may be a positive or negative number.
|
|
||||||
* @return The **mutable** cloned method. Call [clone] to get an **immutable** copy.
|
|
||||||
*/
|
|
||||||
internal fun Method.cloneMutable(
|
|
||||||
registerCount: Int = 0,
|
|
||||||
) = clone(registerCount).toMutable()
|
|
||||||
|
|
||||||
// FIXME: also check the order of parameters as different order equals different method overload
|
|
||||||
internal fun parametersEqual(
|
|
||||||
parameters1: Iterable<CharSequence>,
|
|
||||||
parameters2: Iterable<CharSequence>
|
|
||||||
): Boolean {
|
|
||||||
return parameters1.count() == parameters2.count() && parameters1.all { parameter ->
|
|
||||||
parameters2.any {
|
|
||||||
it.startsWith(
|
|
||||||
parameter
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val nullOutputStream: OutputStream =
|
|
||||||
object : OutputStream() {
|
|
||||||
override fun write(b: Int) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should be used to parse a list of parameters represented by their first letter,
|
|
||||||
* or in the case of arrays prefixed with an unspecified amount of '[' character.
|
|
||||||
*/
|
|
||||||
internal fun String.parseParameters(): List<String> {
|
|
||||||
val parameters = mutableListOf<String>()
|
|
||||||
var parameter = ""
|
|
||||||
for (char in this.toCharArray()) {
|
|
||||||
parameter += char
|
|
||||||
if (char == '[') continue
|
|
||||||
|
|
||||||
parameters.add(parameter)
|
|
||||||
parameter = ""
|
|
||||||
}
|
|
||||||
return parameters
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,351 @@
|
|||||||
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patcher.util.smali.ExternalLabel
|
||||||
|
import app.revanced.patcher.util.smali.toInstruction
|
||||||
|
import app.revanced.patcher.util.smali.toInstructions
|
||||||
|
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.builder.Label
|
||||||
|
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||||
|
import com.android.tools.smali.dexlib2.builder.instruction.*
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||||
|
|
||||||
|
object InstructionExtensions {
|
||||||
|
/**
|
||||||
|
* Add instructions to a method at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to add the instructions at.
|
||||||
|
* @param instructions The instructions to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethodImplementation.addInstructions(
|
||||||
|
index: Int,
|
||||||
|
instructions: List<BuilderInstruction>,
|
||||||
|
) = instructions.asReversed().forEach { addInstruction(index, it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add instructions to a method.
|
||||||
|
* The instructions will be added at the end of the method.
|
||||||
|
*
|
||||||
|
* @param instructions The instructions to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) =
|
||||||
|
instructions.forEach { this.addInstruction(it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove instructions from a method at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to remove the instructions at.
|
||||||
|
* @param count The amount of instructions to remove.
|
||||||
|
*/
|
||||||
|
fun MutableMethodImplementation.removeInstructions(
|
||||||
|
index: Int,
|
||||||
|
count: Int,
|
||||||
|
) = repeat(count) {
|
||||||
|
removeInstruction(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the first instructions from a method.
|
||||||
|
*
|
||||||
|
* @param count The amount of instructions to remove.
|
||||||
|
*/
|
||||||
|
fun MutableMethodImplementation.removeInstructions(count: Int) = removeInstructions(0, count)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace instructions at the given index with the given instructions.
|
||||||
|
* The amount of instructions to replace is the amount of instructions in the given list.
|
||||||
|
*
|
||||||
|
* @param index The index to replace the instructions at.
|
||||||
|
* @param instructions The instructions to replace the instructions with.
|
||||||
|
*/
|
||||||
|
fun MutableMethodImplementation.replaceInstructions(
|
||||||
|
index: Int,
|
||||||
|
instructions: List<BuilderInstruction>,
|
||||||
|
) {
|
||||||
|
// Remove the instructions at the given index.
|
||||||
|
removeInstructions(index, instructions.size)
|
||||||
|
|
||||||
|
// Add the instructions at the given index.
|
||||||
|
addInstructions(index, instructions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to a method at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to add the instruction at.
|
||||||
|
* @param instruction The instruction to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.addInstruction(
|
||||||
|
index: Int,
|
||||||
|
instruction: BuilderInstruction,
|
||||||
|
) = implementation!!.addInstruction(index, instruction)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to a method.
|
||||||
|
*
|
||||||
|
* @param instruction The instructions to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.addInstruction(instruction: BuilderInstruction) = implementation!!.addInstruction(instruction)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to a method at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to add the instruction at.
|
||||||
|
* @param smaliInstructions The instruction to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.addInstruction(
|
||||||
|
index: Int,
|
||||||
|
smaliInstructions: String,
|
||||||
|
) = implementation!!.addInstruction(index, smaliInstructions.toInstruction(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to a method.
|
||||||
|
*
|
||||||
|
* @param smaliInstructions The instruction to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.addInstruction(smaliInstructions: String) = implementation!!.addInstruction(smaliInstructions.toInstruction(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add instructions to a method at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to add the instructions at.
|
||||||
|
* @param instructions The instructions to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.addInstructions(
|
||||||
|
index: Int,
|
||||||
|
instructions: List<BuilderInstruction>,
|
||||||
|
) = implementation!!.addInstructions(index, instructions)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add instructions to a method.
|
||||||
|
*
|
||||||
|
* @param instructions The instructions to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) = implementation!!.addInstructions(instructions)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add instructions to a method.
|
||||||
|
*
|
||||||
|
* @param smaliInstructions The instructions to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.addInstructions(
|
||||||
|
index: Int,
|
||||||
|
smaliInstructions: String,
|
||||||
|
) = implementation!!.addInstructions(index, smaliInstructions.toInstructions(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add instructions to a method.
|
||||||
|
*
|
||||||
|
* @param smaliInstructions The instructions to add.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.addInstructions(smaliInstructions: String) = implementation!!.addInstructions(smaliInstructions.toInstructions(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add instructions to a method at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to add the instructions at.
|
||||||
|
* @param smaliInstructions The instructions to add.
|
||||||
|
* @param externalLabels A list of [ExternalLabel] for instructions outside of [smaliInstructions].
|
||||||
|
*/
|
||||||
|
// Special function for adding instructions with external labels.
|
||||||
|
fun MutableMethod.addInstructionsWithLabels(
|
||||||
|
index: Int,
|
||||||
|
smaliInstructions: String,
|
||||||
|
vararg externalLabels: ExternalLabel,
|
||||||
|
) {
|
||||||
|
// Create reference dummy instructions for the instructions.
|
||||||
|
val nopSmali =
|
||||||
|
StringBuilder(smaliInstructions).also { builder ->
|
||||||
|
externalLabels.forEach { (name, _) ->
|
||||||
|
builder.append("\n:$name\nnop")
|
||||||
|
}
|
||||||
|
}.toString()
|
||||||
|
|
||||||
|
// Compile the instructions with the dummy labels
|
||||||
|
val compiledInstructions = nopSmali.toInstructions(this)
|
||||||
|
|
||||||
|
// Add the compiled list of instructions to the method.
|
||||||
|
addInstructions(
|
||||||
|
index,
|
||||||
|
compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size),
|
||||||
|
)
|
||||||
|
|
||||||
|
implementation!!.apply {
|
||||||
|
this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size)
|
||||||
|
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
|
||||||
|
// If the compiled instruction is not an offset instruction, skip it.
|
||||||
|
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new label for the instruction
|
||||||
|
* and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex].
|
||||||
|
*/
|
||||||
|
fun Instruction.makeNewLabel() {
|
||||||
|
fun replaceOffset(
|
||||||
|
i: BuilderOffsetInstruction,
|
||||||
|
label: Label,
|
||||||
|
): BuilderOffsetInstruction {
|
||||||
|
return when (i) {
|
||||||
|
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
|
||||||
|
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
|
||||||
|
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
|
||||||
|
is BuilderInstruction22t ->
|
||||||
|
BuilderInstruction22t(
|
||||||
|
i.opcode,
|
||||||
|
i.registerA,
|
||||||
|
i.registerB,
|
||||||
|
label,
|
||||||
|
)
|
||||||
|
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
|
||||||
|
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
|
||||||
|
else -> throw IllegalStateException(
|
||||||
|
"A non-offset instruction was given, this should never happen!",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the final label.
|
||||||
|
val label = newLabelForIndex(this@apply.instructions.indexOf(this))
|
||||||
|
|
||||||
|
// Create the final instruction with the new label.
|
||||||
|
val newInstruction =
|
||||||
|
replaceOffset(
|
||||||
|
compiledInstruction,
|
||||||
|
label,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Replace the instruction pointing to the dummy label
|
||||||
|
// with the new instruction pointing to the real instruction.
|
||||||
|
replaceInstruction(index + compiledInstructionIndex, newInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the compiled instruction targets its own instruction,
|
||||||
|
// which means it points to some of its own, simply an offset has to be applied.
|
||||||
|
val labelIndex = compiledInstruction.target.location.index
|
||||||
|
if (labelIndex < compiledInstructions.size - externalLabels.size) {
|
||||||
|
// Get the targets index (insertion index + the index of the dummy instruction).
|
||||||
|
this.instructions[index + labelIndex].makeNewLabel()
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the compiled instruction points to a dummy instruction,
|
||||||
|
// we can find the real instruction which it was created for by calculation.
|
||||||
|
|
||||||
|
// Get the index of the instruction in the externalLabels list
|
||||||
|
// which the dummy instruction was created for.
|
||||||
|
// This works because we created the dummy instructions in the same order as the externalLabels list.
|
||||||
|
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
|
||||||
|
instruction.makeNewLabel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an instruction at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to remove the instruction at.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.removeInstruction(index: Int) = implementation!!.removeInstruction(index)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove instructions at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to remove the instructions at.
|
||||||
|
* @param count The amount of instructions to remove.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.removeInstructions(
|
||||||
|
index: Int,
|
||||||
|
count: Int,
|
||||||
|
) = implementation!!.removeInstructions(index, count)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove instructions at the given index.
|
||||||
|
*
|
||||||
|
* @param count The amount of instructions to remove.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.removeInstructions(count: Int) = implementation!!.removeInstructions(count)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace an instruction at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to replace the instruction at.
|
||||||
|
* @param instruction The instruction to replace the instruction with.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.replaceInstruction(
|
||||||
|
index: Int,
|
||||||
|
instruction: BuilderInstruction,
|
||||||
|
) = implementation!!.replaceInstruction(index, instruction)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace an instruction at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to replace the instruction at.
|
||||||
|
* @param smaliInstruction The smali instruction to replace the instruction with.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.replaceInstruction(
|
||||||
|
index: Int,
|
||||||
|
smaliInstruction: String,
|
||||||
|
) = implementation!!.replaceInstruction(index, smaliInstruction.toInstruction(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace instructions at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to replace the instructions at.
|
||||||
|
* @param instructions The instructions to replace the instructions with.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.replaceInstructions(
|
||||||
|
index: Int,
|
||||||
|
instructions: List<BuilderInstruction>,
|
||||||
|
) = implementation!!.replaceInstructions(index, instructions)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace instructions at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to replace the instructions at.
|
||||||
|
* @param smaliInstructions The smali instructions to replace the instructions with.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.replaceInstructions(
|
||||||
|
index: Int,
|
||||||
|
smaliInstructions: String,
|
||||||
|
) = implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instruction at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to get the instruction at.
|
||||||
|
* @return The instruction.
|
||||||
|
*/
|
||||||
|
fun MutableMethodImplementation.getInstruction(index: Int): BuilderInstruction = instructions[index]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instruction at the given index.
|
||||||
|
*
|
||||||
|
* @param index The index to get the instruction at.
|
||||||
|
* @param T The type of instruction to return.
|
||||||
|
* @return The instruction.
|
||||||
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <T> MutableMethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instruction at the given index.
|
||||||
|
* @param index The index to get the instruction at.
|
||||||
|
* @return The instruction.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.getInstruction(index: Int): BuilderInstruction = implementation!!.getInstruction(index)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instruction at the given index.
|
||||||
|
* @param index The index to get the instruction at.
|
||||||
|
* @param T The type of instruction to return.
|
||||||
|
* @return The instruction.
|
||||||
|
*/
|
||||||
|
fun <T> MutableMethod.getInstruction(index: Int): T = implementation!!.getInstruction<T>(index)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instructions of a method.
|
||||||
|
* @return The instructions.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.getInstructions(): MutableList<BuilderInstruction> = implementation!!.instructions
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||||
|
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
|
||||||
|
|
||||||
|
object MethodFingerprintExtensions {
|
||||||
|
/**
|
||||||
|
* The [FuzzyPatternScanMethod] annotation of a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
@Deprecated(
|
||||||
|
message = "Use the property instead.",
|
||||||
|
replaceWith = ReplaceWith("this.fuzzyPatternScanMethod"),
|
||||||
|
)
|
||||||
|
val MethodFingerprint.fuzzyPatternScanMethod
|
||||||
|
get() = this.fuzzyPatternScanMethod
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package app.revanced.patcher.fingerprint
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A ReVanced fingerprint.
|
|
||||||
* Can be a [MethodFingerprint].
|
|
||||||
*/
|
|
||||||
interface Fingerprint
|
|
||||||
125
src/main/kotlin/app/revanced/patcher/fingerprint/LookupMap.kt
Normal file
125
src/main/kotlin/app/revanced/patcher/fingerprint/LookupMap.kt
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
internal typealias MethodClassPair = Pair<Method, ClassDef>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for methods.
|
||||||
|
*/
|
||||||
|
internal class LookupMap : MutableMap<String, LookupMap.MethodClassList> by mutableMapOf() {
|
||||||
|
/**
|
||||||
|
* Adds a [MethodClassPair] to the list associated with the given key.
|
||||||
|
* If the key does not exist, a new list is created and the [MethodClassPair] is added to it.
|
||||||
|
*/
|
||||||
|
fun add(
|
||||||
|
key: String,
|
||||||
|
methodClassPair: MethodClassPair,
|
||||||
|
) {
|
||||||
|
getOrPut(key) { MethodClassList() }.add(methodClassPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of methods and the class they are a member of.
|
||||||
|
*/
|
||||||
|
internal class MethodClassList : LinkedList<MethodClassPair>()
|
||||||
|
|
||||||
|
companion object Maps {
|
||||||
|
/**
|
||||||
|
* A list of methods and the class they are a member of.
|
||||||
|
*/
|
||||||
|
internal val methods = MethodClassList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for methods keyed to the methods access flags, return type and parameter.
|
||||||
|
*/
|
||||||
|
internal val methodSignatureLookupMap = LookupMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for methods associated by strings referenced in the method.
|
||||||
|
*/
|
||||||
|
internal val methodStringsLookupMap = LookupMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes lookup maps for [MethodFingerprint] resolution
|
||||||
|
* using attributes of methods such as the method signature or strings.
|
||||||
|
*
|
||||||
|
* @param context The [BytecodeContext] containing the classes to initialize the lookup maps with.
|
||||||
|
*/
|
||||||
|
internal fun initializeLookupMaps(context: BytecodeContext) {
|
||||||
|
if (methods.isNotEmpty()) clearLookupMaps()
|
||||||
|
|
||||||
|
context.classes.forEach { classDef ->
|
||||||
|
classDef.methods.forEach { method ->
|
||||||
|
val methodClassPair = method to classDef
|
||||||
|
|
||||||
|
// For fingerprints with no access or return type specified.
|
||||||
|
methods += methodClassPair
|
||||||
|
|
||||||
|
val accessFlagsReturnKey = method.accessFlags.toString() + method.returnType.first()
|
||||||
|
|
||||||
|
// Add <access><returnType> as the key.
|
||||||
|
methodSignatureLookupMap.add(accessFlagsReturnKey, methodClassPair)
|
||||||
|
|
||||||
|
// Add <access><returnType>[parameters] as the key.
|
||||||
|
methodSignatureLookupMap.add(
|
||||||
|
buildString {
|
||||||
|
append(accessFlagsReturnKey)
|
||||||
|
appendParameters(method.parameterTypes)
|
||||||
|
},
|
||||||
|
methodClassPair,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add strings contained in the method as the key.
|
||||||
|
method.implementation?.instructions?.forEach instructions@{ instruction ->
|
||||||
|
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) {
|
||||||
|
return@instructions
|
||||||
|
}
|
||||||
|
|
||||||
|
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||||
|
|
||||||
|
methodStringsLookupMap.add(string, methodClassPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the future, the class type could be added to the lookup map.
|
||||||
|
// This would require MethodFingerprint to be changed to include the class type.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the internal lookup maps created in [initializeLookupMaps].
|
||||||
|
*/
|
||||||
|
internal fun clearLookupMaps() {
|
||||||
|
methods.clear()
|
||||||
|
methodSignatureLookupMap.clear()
|
||||||
|
methodStringsLookupMap.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a string based on the parameter reference types of this method.
|
||||||
|
*/
|
||||||
|
internal fun StringBuilder.appendParameters(parameters: Iterable<CharSequence>) {
|
||||||
|
// Maximum parameters to use in the signature key.
|
||||||
|
// Some apps have methods with an incredible number of parameters (over 100 parameters have been seen).
|
||||||
|
// To keep the signature map from becoming needlessly bloated,
|
||||||
|
// group together in the same map entry all methods with the same access/return and 5 or more parameters.
|
||||||
|
// The value of 5 was chosen based on local performance testing and is not set in stone.
|
||||||
|
val maxSignatureParameters = 5
|
||||||
|
// Must append a unique value before the parameters to distinguish this key includes the parameters.
|
||||||
|
// If this is not appended, then methods with no parameters
|
||||||
|
// will collide with different keys that specify access/return but omit the parameters.
|
||||||
|
append("p:")
|
||||||
|
parameters.forEachIndexed { index, parameter ->
|
||||||
|
if (index >= maxSignatureParameters) return
|
||||||
|
append(parameter.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
package app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
|
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||||
|
import app.revanced.patcher.fingerprint.LookupMap.Maps.appendParameters
|
||||||
|
import app.revanced.patcher.fingerprint.LookupMap.Maps.initializeLookupMaps
|
||||||
|
import app.revanced.patcher.fingerprint.LookupMap.Maps.methodSignatureLookupMap
|
||||||
|
import app.revanced.patcher.fingerprint.LookupMap.Maps.methodStringsLookupMap
|
||||||
|
import app.revanced.patcher.fingerprint.LookupMap.Maps.methods
|
||||||
|
import app.revanced.patcher.fingerprint.MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
||||||
|
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
|
||||||
|
import app.revanced.patcher.patch.PatchException
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fingerprint to resolve methods.
|
||||||
|
*
|
||||||
|
* @param returnType The method's return type compared using [String.startsWith].
|
||||||
|
* @param accessFlags The method's exact access flags using values of [AccessFlags].
|
||||||
|
* @param parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
|
||||||
|
* @param opcodes An opcode pattern of the method's instructions. Wildcard or unknown opcodes can be specified by `null`.
|
||||||
|
* @param strings A list of the method's strings compared each using [String.contains].
|
||||||
|
* @param customFingerprint A custom condition for this fingerprint.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
abstract class MethodFingerprint(
|
||||||
|
internal val returnType: String? = null,
|
||||||
|
internal val accessFlags: Int? = null,
|
||||||
|
internal val parameters: Iterable<String>? = null,
|
||||||
|
internal val opcodes: Iterable<Opcode?>? = null,
|
||||||
|
internal val strings: Iterable<String>? = null,
|
||||||
|
internal val customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The result of the [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
var result: MethodFingerprintResult? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [FuzzyPatternScanMethod] annotation of the [MethodFingerprint].
|
||||||
|
*
|
||||||
|
* If the annotation is not present, this property is null.
|
||||||
|
*/
|
||||||
|
val fuzzyPatternScanMethod = this::class.findAnnotationRecursively(FuzzyPatternScanMethod::class)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] using the lookup map built by [initializeLookupMaps].
|
||||||
|
*
|
||||||
|
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||||
|
* amount of time because they are resolved in sequence.
|
||||||
|
*
|
||||||
|
* For apps with many fingerprints, resolving performance can be improved by:
|
||||||
|
* - Slowest: Specify [opcodes] and nothing else.
|
||||||
|
* - Fast: Specify [accessFlags], [returnType].
|
||||||
|
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||||
|
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||||
|
*/
|
||||||
|
internal fun resolveUsingLookupMap(context: BytecodeContext): Boolean {
|
||||||
|
/**
|
||||||
|
* Lookup [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||||
|
*
|
||||||
|
* @return A list of [MethodClassPair]s that match the methods strings present in a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.methodStringsLookup(): LookupMap.MethodClassList? {
|
||||||
|
strings?.forEach {
|
||||||
|
val methods = methodStringsLookupMap[it]
|
||||||
|
if (methods != null) return methods
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||||
|
*
|
||||||
|
* @return A list of [MethodClassPair]s that match the method signature present in a [MethodFingerprint].
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.methodSignatureLookup(): LookupMap.MethodClassList {
|
||||||
|
if (accessFlags == null) return methods
|
||||||
|
|
||||||
|
var returnTypeValue = returnType
|
||||||
|
if (returnTypeValue == null) {
|
||||||
|
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
||||||
|
// Constructors always have void return type
|
||||||
|
returnTypeValue = "V"
|
||||||
|
} else {
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val key =
|
||||||
|
buildString {
|
||||||
|
append(accessFlags)
|
||||||
|
append(returnTypeValue.first())
|
||||||
|
if (parameters != null) appendParameters(parameters)
|
||||||
|
}
|
||||||
|
return methodSignatureLookupMap[key] ?: return LookupMap.MethodClassList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] using a list of [MethodClassPair].
|
||||||
|
*
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun MethodFingerprint.resolveUsingMethodClassPair(methodClasses: LookupMap.MethodClassList): Boolean {
|
||||||
|
methodClasses.forEach { classAndMethod ->
|
||||||
|
if (resolve(context, classAndMethod.first, classAndMethod.second)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val methodsWithSameStrings = methodStringsLookup()
|
||||||
|
if (methodsWithSameStrings != null) {
|
||||||
|
if (resolveUsingMethodClassPair(methodsWithSameStrings)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No strings declared or none matched (partial matches are allowed).
|
||||||
|
// Use signature matching.
|
||||||
|
return resolveUsingMethodClassPair(methodSignatureLookup())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] against a [ClassDef].
|
||||||
|
*
|
||||||
|
* @param forClass The class on which to resolve the [MethodFingerprint] in.
|
||||||
|
* @param context The [BytecodeContext] to host proxies.
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun resolve(
|
||||||
|
context: BytecodeContext,
|
||||||
|
forClass: ClassDef,
|
||||||
|
): Boolean {
|
||||||
|
for (method in forClass.methods)
|
||||||
|
if (resolve(context, method, forClass)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a [MethodFingerprint] against a [Method].
|
||||||
|
*
|
||||||
|
* @param method The class on which to resolve the [MethodFingerprint] in.
|
||||||
|
* @param forClass The class on which to resolve the [MethodFingerprint].
|
||||||
|
* @param context The [BytecodeContext] to host proxies.
|
||||||
|
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
|
||||||
|
*/
|
||||||
|
fun resolve(
|
||||||
|
context: BytecodeContext,
|
||||||
|
method: Method,
|
||||||
|
forClass: ClassDef,
|
||||||
|
): Boolean {
|
||||||
|
val methodFingerprint = this
|
||||||
|
|
||||||
|
if (methodFingerprint.result != null) return true
|
||||||
|
|
||||||
|
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodFingerprint.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parametersEqual(
|
||||||
|
parameters1: Iterable<CharSequence>,
|
||||||
|
parameters2: Iterable<CharSequence>,
|
||||||
|
): Boolean {
|
||||||
|
if (parameters1.count() != parameters2.count()) return false
|
||||||
|
val iterator1 = parameters1.iterator()
|
||||||
|
parameters2.forEach {
|
||||||
|
if (!it.startsWith(iterator1.next())) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodFingerprint.parameters != null &&
|
||||||
|
!parametersEqual(
|
||||||
|
methodFingerprint.parameters, // TODO: parseParameters()
|
||||||
|
method.parameterTypes,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
||||||
|
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method, forClass)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val stringsScanResult: StringsScanResult? =
|
||||||
|
if (methodFingerprint.strings != null) {
|
||||||
|
StringsScanResult(
|
||||||
|
buildList {
|
||||||
|
val implementation = method.implementation ?: return false
|
||||||
|
|
||||||
|
val stringsList = methodFingerprint.strings.toMutableList()
|
||||||
|
|
||||||
|
implementation.instructions.forEachIndexed { instructionIndex, instruction ->
|
||||||
|
if (
|
||||||
|
instruction.opcode != Opcode.CONST_STRING &&
|
||||||
|
instruction.opcode != Opcode.CONST_STRING_JUMBO
|
||||||
|
) {
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
|
||||||
|
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||||
|
val index = stringsList.indexOfFirst(string::contains)
|
||||||
|
if (index == -1) return@forEachIndexed
|
||||||
|
|
||||||
|
add(StringsScanResult.StringMatch(string, instructionIndex))
|
||||||
|
stringsList.removeAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stringsList.isNotEmpty()) return false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val patternScanResult =
|
||||||
|
if (methodFingerprint.opcodes != null) {
|
||||||
|
method.implementation?.instructions ?: return false
|
||||||
|
|
||||||
|
fun MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.newWarnings(
|
||||||
|
pattern: Iterable<Opcode?>,
|
||||||
|
instructions: Iterable<Instruction>,
|
||||||
|
) = buildList {
|
||||||
|
for ((patternIndex, instructionIndex) in (this@newWarnings.startIndex until this@newWarnings.endIndex).withIndex()) {
|
||||||
|
val originalOpcode = instructions.elementAt(instructionIndex).opcode
|
||||||
|
val patternOpcode = pattern.elementAt(patternIndex)
|
||||||
|
|
||||||
|
if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue
|
||||||
|
|
||||||
|
this.add(
|
||||||
|
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult.Warning(
|
||||||
|
originalOpcode,
|
||||||
|
patternOpcode,
|
||||||
|
instructionIndex,
|
||||||
|
patternIndex,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Method.patternScan(
|
||||||
|
fingerprint: MethodFingerprint,
|
||||||
|
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
||||||
|
val instructions = this.implementation!!.instructions
|
||||||
|
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
|
||||||
|
|
||||||
|
val pattern = fingerprint.opcodes!!
|
||||||
|
val instructionLength = instructions.count()
|
||||||
|
val patternLength = pattern.count()
|
||||||
|
|
||||||
|
for (index in 0 until instructionLength) {
|
||||||
|
var patternIndex = 0
|
||||||
|
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||||
|
|
||||||
|
while (index + patternIndex < instructionLength) {
|
||||||
|
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||||
|
val patternOpcode = pattern.elementAt(patternIndex)
|
||||||
|
|
||||||
|
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||||
|
// reaching maximum threshold (0) means,
|
||||||
|
// the pattern does not match to the current instructions
|
||||||
|
if (threshold-- == 0) break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patternIndex < patternLength - 1) {
|
||||||
|
// if the entire pattern has not been scanned yet
|
||||||
|
// continue the scan
|
||||||
|
patternIndex++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
||||||
|
val result =
|
||||||
|
MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult(
|
||||||
|
index,
|
||||||
|
index + patternIndex,
|
||||||
|
)
|
||||||
|
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
||||||
|
result.warnings = result.newWarnings(pattern, instructions)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
method.patternScan(methodFingerprint) ?: return false
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
methodFingerprint.result =
|
||||||
|
MethodFingerprintResult(
|
||||||
|
method,
|
||||||
|
forClass,
|
||||||
|
MethodFingerprintResult.MethodFingerprintScanResult(
|
||||||
|
patternScanResult,
|
||||||
|
stringsScanResult,
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Resolve a list of [MethodFingerprint] using the lookup map built by [initializeLookupMaps].
|
||||||
|
*
|
||||||
|
* [MethodFingerprint] resolution is fast, but if many are present they can consume a noticeable
|
||||||
|
* amount of time because they are resolved in sequence.
|
||||||
|
*
|
||||||
|
* For apps with many fingerprints, resolving performance can be improved by:
|
||||||
|
* - Slowest: Specify [opcodes] and nothing else.
|
||||||
|
* - Fast: Specify [accessFlags], [returnType].
|
||||||
|
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||||
|
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||||
|
*/
|
||||||
|
internal fun Set<MethodFingerprint>.resolveUsingLookupMap(context: BytecodeContext) {
|
||||||
|
if (methods.isEmpty()) throw PatchException("lookup map not initialized")
|
||||||
|
|
||||||
|
forEach { fingerprint ->
|
||||||
|
fingerprint.resolveUsingLookupMap(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||||
|
*
|
||||||
|
* @param classes The classes on which to resolve the [MethodFingerprint] in.
|
||||||
|
* @param context The [BytecodeContext] to host proxies.
|
||||||
|
* @return True if the resolution was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun Iterable<MethodFingerprint>.resolve(
|
||||||
|
context: BytecodeContext,
|
||||||
|
classes: Iterable<ClassDef>,
|
||||||
|
) = forEach { fingerprint ->
|
||||||
|
for (classDef in classes) {
|
||||||
|
if (fingerprint.resolve(context, classDef)) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the result of a [MethodFingerprintResult].
|
||||||
|
*
|
||||||
|
* @param method The matching method.
|
||||||
|
* @param classDef The [ClassDef] that contains the matching [method].
|
||||||
|
* @param scanResult The result of scanning for the [MethodFingerprint].
|
||||||
|
* @param context The [BytecodeContext] this [MethodFingerprintResult] is attached to, to create proxies.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
class MethodFingerprintResult(
|
||||||
|
val method: Method,
|
||||||
|
val classDef: ClassDef,
|
||||||
|
val scanResult: MethodFingerprintScanResult,
|
||||||
|
internal val context: BytecodeContext,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Returns a mutable clone of [classDef]
|
||||||
|
*
|
||||||
|
* Please note, this method allocates a [ClassProxy].
|
||||||
|
* Use [classDef] where possible.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
val mutableClass by lazy { context.proxy(classDef).mutableClass }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a mutable clone of [method]
|
||||||
|
*
|
||||||
|
* Please note, this method allocates a [ClassProxy].
|
||||||
|
* Use [method] where possible.
|
||||||
|
*/
|
||||||
|
val mutableMethod by lazy {
|
||||||
|
mutableClass.methods.first {
|
||||||
|
MethodUtil.methodSignaturesMatch(it, this.method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of scanning on the [MethodFingerprint].
|
||||||
|
* @param patternScanResult The result of the pattern scan.
|
||||||
|
* @param stringsScanResult The result of the string scan.
|
||||||
|
*/
|
||||||
|
class MethodFingerprintScanResult(
|
||||||
|
val patternScanResult: PatternScanResult?,
|
||||||
|
val stringsScanResult: StringsScanResult?,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The result of scanning strings on the [MethodFingerprint].
|
||||||
|
* @param matches The list of strings that were matched.
|
||||||
|
*/
|
||||||
|
class StringsScanResult(val matches: List<StringMatch>) {
|
||||||
|
/**
|
||||||
|
* Represents a match for a string at an index.
|
||||||
|
* @param string The string that was matched.
|
||||||
|
* @param index The index of the string.
|
||||||
|
*/
|
||||||
|
class StringMatch(val string: String, val index: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a pattern scan.
|
||||||
|
* @param startIndex The start index of the instructions where to which this pattern matches.
|
||||||
|
* @param endIndex The end index of the instructions where to which this pattern matches.
|
||||||
|
* @param warnings A list of warnings considering this [PatternScanResult].
|
||||||
|
*/
|
||||||
|
class PatternScanResult(
|
||||||
|
val startIndex: Int,
|
||||||
|
val endIndex: Int,
|
||||||
|
var warnings: List<Warning>? = null,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Represents warnings of the pattern scan.
|
||||||
|
* @param correctOpcode The opcode the instruction list has.
|
||||||
|
* @param wrongOpcode The opcode the pattern list of the signature currently has.
|
||||||
|
* @param instructionIndex The index of the opcode relative to the instruction list.
|
||||||
|
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
|
||||||
|
*/
|
||||||
|
class Warning(
|
||||||
|
val correctOpcode: Opcode,
|
||||||
|
val wrongOpcode: Opcode,
|
||||||
|
val instructionIndex: Int,
|
||||||
|
val patternIndex: Int,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.patcher.fingerprint.annotation
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.
|
||||||
|
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class FuzzyPatternScanMethod(
|
||||||
|
val threshold: Int = 1,
|
||||||
|
)
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package app.revanced.patcher.fingerprint.method.annotation
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotations for a method which matches to a [MethodFingerprint].
|
|
||||||
* @param definingClass The defining class name of the method.
|
|
||||||
* @param name A suggestive name for the method which the [MethodFingerprint] was created for.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class MatchingMethod(
|
|
||||||
val definingClass: String = "L<unspecified-class>;",
|
|
||||||
val name: String = "<unspecified-method>"
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.
|
|
||||||
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class FuzzyPatternScanMethod(
|
|
||||||
val threshold: Int = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotations to scan a pattern [MethodFingerprint] directly.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class DirectPatternScanMethod
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package app.revanced.patcher.fingerprint.method.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
|
||||||
import app.revanced.patcher.data.impl.MethodNotFoundException
|
|
||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
|
|
||||||
import app.revanced.patcher.extensions.softCompareTo
|
|
||||||
import app.revanced.patcher.fingerprint.Fingerprint
|
|
||||||
import app.revanced.patcher.fingerprint.method.utils.MethodFingerprintUtils
|
|
||||||
import app.revanced.patcher.util.proxy.ClassProxy
|
|
||||||
import org.jf.dexlib2.Opcode
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the [MethodFingerprint] for a method.
|
|
||||||
* @param returnType The return type of the method.
|
|
||||||
* @param access The access flags of the method.
|
|
||||||
* @param parameters The parameters of the method.
|
|
||||||
* @param opcodes The list of opcodes of the method.
|
|
||||||
* @param strings A list of strings which a method contains.
|
|
||||||
* @param customFingerprint A custom condition for this fingerprint.
|
|
||||||
* A `null` opcode is equals to an unknown opcode.
|
|
||||||
*/
|
|
||||||
abstract class MethodFingerprint(
|
|
||||||
internal val returnType: String?,
|
|
||||||
internal val access: Int?,
|
|
||||||
internal val parameters: Iterable<String>?,
|
|
||||||
internal val opcodes: Iterable<Opcode?>?,
|
|
||||||
internal val strings: Iterable<String>? = null,
|
|
||||||
internal val customFingerprint: ((methodDef: Method) -> Boolean)? = null
|
|
||||||
) : Fingerprint {
|
|
||||||
/**
|
|
||||||
* The result of the [MethodFingerprint] the [Method].
|
|
||||||
* @throws MethodNotFoundException If the resolution of the [Method] has not happened.
|
|
||||||
*/
|
|
||||||
var result: MethodFingerprintResult? = null
|
|
||||||
get() = field ?: throw Exception("${this.name} has not been resolved yet.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the result of a [MethodFingerprintUtils].
|
|
||||||
* @param method The matching method.
|
|
||||||
* @param classDef The [ClassDef] that contains the matching [method].
|
|
||||||
* @param patternScanResult Opcodes pattern scan result.
|
|
||||||
* @param data The [BytecodeData] this [MethodFingerprintResult] is attached to, to create proxies.
|
|
||||||
*/
|
|
||||||
data class MethodFingerprintResult(
|
|
||||||
val method: Method,
|
|
||||||
val classDef: ClassDef,
|
|
||||||
val patternScanResult: PatternScanResult?,
|
|
||||||
internal val data: BytecodeData
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Returns a mutable clone of [classDef]
|
|
||||||
*
|
|
||||||
* Please note, this method allocates a [ClassProxy].
|
|
||||||
* Use [classDef] where possible.
|
|
||||||
*/
|
|
||||||
val mutableClass by lazy { data.proxy(classDef).resolve() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a mutable clone of [method]
|
|
||||||
*
|
|
||||||
* Please note, this method allocates a [ClassProxy].
|
|
||||||
* Use [method] where possible.
|
|
||||||
*/
|
|
||||||
val mutableMethod by lazy {
|
|
||||||
mutableClass.methods.first {
|
|
||||||
it.softCompareTo(this.method)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The result of a pattern scan.
|
|
||||||
* @param startIndex The start index of the instructions where to which this pattern matches.
|
|
||||||
* @param endIndex The end index of the instructions where to which this pattern matches.
|
|
||||||
* @param warnings A list of warnings considering this [PatternScanResult].
|
|
||||||
*/
|
|
||||||
data class PatternScanResult(
|
|
||||||
val startIndex: Int,
|
|
||||||
val endIndex: Int,
|
|
||||||
var warnings: List<Warning>? = null
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Represents warnings of the pattern scan.
|
|
||||||
* @param correctOpcode The opcode the instruction list has.
|
|
||||||
* @param wrongOpcode The opcode the pattern list of the signature currently has.
|
|
||||||
* @param instructionIndex The index of the opcode relative to the instruction list.
|
|
||||||
* @param patternIndex The index of the opcode relative to the pattern list from the signature.
|
|
||||||
*/
|
|
||||||
data class Warning(
|
|
||||||
val correctOpcode: Opcode,
|
|
||||||
val wrongOpcode: Opcode,
|
|
||||||
val instructionIndex: Int,
|
|
||||||
val patternIndex: Int,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package app.revanced.patcher.fingerprint.method.utils
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
|
||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
|
||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
|
||||||
import app.revanced.patcher.extensions.parametersEqual
|
|
||||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.PatternScanResult
|
|
||||||
import org.jf.dexlib2.Opcode
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
import org.jf.dexlib2.iface.instruction.Instruction
|
|
||||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
|
||||||
import org.jf.dexlib2.iface.reference.StringReference
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for [MethodFingerprint]
|
|
||||||
*/
|
|
||||||
object MethodFingerprintUtils {
|
|
||||||
/**
|
|
||||||
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
|
||||||
* @param context The classes on which to resolve the [MethodFingerprint].
|
|
||||||
* @param forData The [BytecodeData] to host proxies.
|
|
||||||
* @return True if the resolution was successful, false otherwise.
|
|
||||||
*/
|
|
||||||
fun Iterable<MethodFingerprint>.resolve(forData: BytecodeData, context: Iterable<ClassDef>) {
|
|
||||||
for (fingerprint in this) // For each fingerprint
|
|
||||||
classes@ for (classDef in context) // search through all classes for the fingerprint
|
|
||||||
if (fingerprint.resolve(forData, classDef))
|
|
||||||
break@classes // if the resolution succeeded, continue with the next fingerprint
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a [MethodFingerprint] against a [ClassDef].
|
|
||||||
* @param context The class on which to resolve the [MethodFingerprint].
|
|
||||||
* @param forData The [BytecodeData] to host proxies.
|
|
||||||
* @return True if the resolution was successful, false otherwise.
|
|
||||||
*/
|
|
||||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean {
|
|
||||||
for (method in context.methods)
|
|
||||||
if (this.resolve(forData, method, context))
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a [MethodFingerprint] against a [Method].
|
|
||||||
* @param context The context on which to resolve the [MethodFingerprint].
|
|
||||||
* @param classDef The class of the matching [Method].
|
|
||||||
* @param forData The [BytecodeData] to host proxies.
|
|
||||||
* @return True if the resolution was successful, false otherwise.
|
|
||||||
*/
|
|
||||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean {
|
|
||||||
val methodFingerprint = this
|
|
||||||
|
|
||||||
if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType))
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags)
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
if (methodFingerprint.parameters != null && !parametersEqual(
|
|
||||||
methodFingerprint.parameters, // TODO: parseParameters()
|
|
||||||
context.parameterTypes
|
|
||||||
)
|
|
||||||
) return false
|
|
||||||
|
|
||||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context))
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (methodFingerprint.strings != null) {
|
|
||||||
val implementation = context.implementation ?: return false
|
|
||||||
|
|
||||||
val stringsList = methodFingerprint.strings.toMutableList()
|
|
||||||
|
|
||||||
implementation.instructions.forEach { instruction ->
|
|
||||||
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEach
|
|
||||||
|
|
||||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
|
||||||
val index = stringsList.indexOfFirst { it == string }
|
|
||||||
if (index != -1) stringsList.removeAt(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stringsList.isNotEmpty()) return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
|
||||||
context.implementation?.instructions ?: return false
|
|
||||||
|
|
||||||
context.patternScan(methodFingerprint) ?: return false
|
|
||||||
} else null
|
|
||||||
|
|
||||||
methodFingerprint.result = MethodFingerprintResult(context, classDef, patternScanResult, forData)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Method.patternScan(
|
|
||||||
fingerprint: MethodFingerprint
|
|
||||||
): PatternScanResult? {
|
|
||||||
val instructions = this.implementation!!.instructions
|
|
||||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold
|
|
||||||
|
|
||||||
val pattern = fingerprint.opcodes!!
|
|
||||||
val instructionLength = instructions.count()
|
|
||||||
val patternLength = pattern.count()
|
|
||||||
|
|
||||||
for (index in 0 until instructionLength) {
|
|
||||||
var patternIndex = 0
|
|
||||||
var threshold = fingerprintFuzzyPatternScanThreshold
|
|
||||||
|
|
||||||
while (index + patternIndex < instructionLength) {
|
|
||||||
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
|
||||||
val patternOpcode = pattern.elementAt(patternIndex)
|
|
||||||
|
|
||||||
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
|
||||||
// reaching maximum threshold (0) means,
|
|
||||||
// the pattern does not match to the current instructions
|
|
||||||
if (threshold-- == 0) break
|
|
||||||
}
|
|
||||||
|
|
||||||
if (patternIndex < patternLength - 1) {
|
|
||||||
// if the entire pattern has not been scanned yet
|
|
||||||
// continue the scan
|
|
||||||
patternIndex++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// the pattern is valid, generate warnings if fuzzyPatternScanMethod is FuzzyPatternScanMethod
|
|
||||||
val result = PatternScanResult(index, index + patternIndex)
|
|
||||||
if (fingerprint.fuzzyPatternScanMethod !is FuzzyPatternScanMethod) return result
|
|
||||||
result.warnings = result.createWarnings(pattern, instructions)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun PatternScanResult.createWarnings(
|
|
||||||
pattern: Iterable<Opcode?>, instructions: Iterable<Instruction>
|
|
||||||
) = buildList {
|
|
||||||
for ((patternIndex, instructionIndex) in (this@createWarnings.startIndex until this@createWarnings.endIndex).withIndex()) {
|
|
||||||
val originalOpcode = instructions.elementAt(instructionIndex).opcode
|
|
||||||
val patternOpcode = pattern.elementAt(patternIndex)
|
|
||||||
|
|
||||||
if (patternOpcode == null || patternOpcode.ordinal == originalOpcode.ordinal) continue
|
|
||||||
|
|
||||||
this.add(PatternScanResult.Warning(originalOpcode, patternOpcode, instructionIndex, patternIndex))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private operator fun ClassDef.component1() = this
|
|
||||||
private operator fun ClassDef.component2() = this.methods
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package app.revanced.patcher.logging
|
|
||||||
|
|
||||||
interface Logger {
|
|
||||||
fun error(msg: String) {}
|
|
||||||
fun warn(msg: String) {}
|
|
||||||
fun info(msg: String) {}
|
|
||||||
fun trace(msg: String) {}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package app.revanced.patcher.logging.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.logging.Logger
|
|
||||||
|
|
||||||
object NopLogger : Logger
|
|
||||||
61
src/main/kotlin/app/revanced/patcher/patch/BytecodePatch.kt
Normal file
61
src/main/kotlin/app/revanced/patcher/patch/BytecodePatch.kt
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
|
import app.revanced.patcher.PatchClass
|
||||||
|
import app.revanced.patcher.Patcher
|
||||||
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
|
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Patch] that accesses a [BytecodeContext].
|
||||||
|
*
|
||||||
|
* If an implementation of [Patch] also implements [Closeable]
|
||||||
|
* it will be closed in reverse execution order of patches executed by [Patcher].
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
abstract class BytecodePatch : Patch<BytecodeContext> {
|
||||||
|
/**
|
||||||
|
* The fingerprints to resolve before executing the patch.
|
||||||
|
*/
|
||||||
|
internal val fingerprints: Set<MethodFingerprint>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [BytecodePatch].
|
||||||
|
*
|
||||||
|
* @param fingerprints The fingerprints to resolve before executing the patch.
|
||||||
|
*/
|
||||||
|
constructor(fingerprints: Set<MethodFingerprint> = emptySet()) {
|
||||||
|
this.fingerprints = fingerprints
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [BytecodePatch].
|
||||||
|
*
|
||||||
|
* @param name The name of the patch.
|
||||||
|
* @param description The description of the patch.
|
||||||
|
* @param compatiblePackages The packages the patch is compatible with.
|
||||||
|
* @param dependencies Other patches this patch depends on.
|
||||||
|
* @param use Weather or not the patch should be used.
|
||||||
|
* @param requiresIntegrations Weather or not the patch requires integrations.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
name: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
compatiblePackages: Set<CompatiblePackage>? = null,
|
||||||
|
dependencies: Set<PatchClass>? = null,
|
||||||
|
use: Boolean = true,
|
||||||
|
requiresIntegrations: Boolean = false,
|
||||||
|
fingerprints: Set<MethodFingerprint> = emptySet(),
|
||||||
|
) : super(name, description, compatiblePackages, dependencies, use, requiresIntegrations) {
|
||||||
|
this.fingerprints = fingerprints
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [BytecodePatch].
|
||||||
|
*/
|
||||||
|
@Deprecated(
|
||||||
|
"Use the constructor with fingerprints instead.",
|
||||||
|
ReplaceWith("BytecodePatch(emptySet())"),
|
||||||
|
)
|
||||||
|
constructor() : this(emptySet())
|
||||||
|
}
|
||||||
@@ -1,18 +1,124 @@
|
|||||||
|
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
package app.revanced.patcher.patch
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
import app.revanced.patcher.PatchClass
|
||||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
import app.revanced.patcher.Patcher
|
||||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
import app.revanced.patcher.data.Context
|
||||||
|
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||||
|
import app.revanced.patcher.patch.options.PatchOptions
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ReVanced patch.
|
* A patch.
|
||||||
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
*
|
||||||
|
* If an implementation of [Patch] also implements [Closeable]
|
||||||
|
* it will be closed in reverse execution order of patches executed by [Patcher].
|
||||||
|
*
|
||||||
|
* @param T The [Context] type this patch will work on.
|
||||||
*/
|
*/
|
||||||
abstract class Patch<out T : Data> {
|
sealed class Patch<out T : Context<*>> {
|
||||||
|
/**
|
||||||
|
* The name of the patch.
|
||||||
|
*/
|
||||||
|
var name: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main function of the [Patch] which the patcher will call.
|
* The description of the patch.
|
||||||
*/
|
*/
|
||||||
abstract fun execute(data: @UnsafeVariance T): PatchResult
|
var description: String? = null
|
||||||
}
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The packages the patch is compatible with.
|
||||||
|
*/
|
||||||
|
var compatiblePackages: Set<CompatiblePackage>? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other patches this patch depends on.
|
||||||
|
*/
|
||||||
|
var dependencies: Set<PatchClass>? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weather or not the patch should be used.
|
||||||
|
*/
|
||||||
|
var use = true
|
||||||
|
private set
|
||||||
|
|
||||||
|
// TODO: Remove this property, once integrations are coupled with patches.
|
||||||
|
/**
|
||||||
|
* Weather or not the patch requires integrations.
|
||||||
|
*/
|
||||||
|
var requiresIntegrations = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
name: String?,
|
||||||
|
description: String?,
|
||||||
|
compatiblePackages: Set<CompatiblePackage>?,
|
||||||
|
dependencies: Set<PatchClass>?,
|
||||||
|
use: Boolean,
|
||||||
|
requiresIntegrations: Boolean,
|
||||||
|
) {
|
||||||
|
this.name = name
|
||||||
|
this.description = description
|
||||||
|
this.compatiblePackages = compatiblePackages
|
||||||
|
this.dependencies = dependencies
|
||||||
|
this.use = use
|
||||||
|
this.requiresIntegrations = requiresIntegrations
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this::class.findAnnotationRecursively(app.revanced.patcher.patch.annotation.Patch::class)?.let { annotation ->
|
||||||
|
this.name = annotation.name.ifEmpty { null }
|
||||||
|
this.description = annotation.description.ifEmpty { null }
|
||||||
|
this.compatiblePackages =
|
||||||
|
annotation.compatiblePackages
|
||||||
|
.map { CompatiblePackage(it.name, it.versions.toSet().ifEmpty { null }) }
|
||||||
|
.toSet().ifEmpty { null }
|
||||||
|
this.dependencies = annotation.dependencies.toSet().ifEmpty { null }
|
||||||
|
this.use = annotation.use
|
||||||
|
this.requiresIntegrations = annotation.requiresIntegrations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The options of the patch associated by the options key.
|
||||||
|
*/
|
||||||
|
val options = PatchOptions()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The execution function of the patch.
|
||||||
|
*
|
||||||
|
* @param context The [Context] the patch will work on.
|
||||||
|
* @return The result of executing the patch.
|
||||||
|
*/
|
||||||
|
abstract fun execute(context: @UnsafeVariance T)
|
||||||
|
|
||||||
|
override fun hashCode() = name.hashCode()
|
||||||
|
|
||||||
|
override fun toString() = name ?: this::class.simpleName ?: "Unnamed patch"
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as Patch<*>
|
||||||
|
|
||||||
|
return name == other.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A package a [Patch] is compatible with.
|
||||||
|
*
|
||||||
|
* @param name The name of the package.
|
||||||
|
* @param versions The versions of the package.
|
||||||
|
*/
|
||||||
|
class CompatiblePackage(
|
||||||
|
val name: String,
|
||||||
|
val versions: Set<String>? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
12
src/main/kotlin/app/revanced/patcher/patch/PatchException.kt
Normal file
12
src/main/kotlin/app/revanced/patcher/patch/PatchException.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when patching.
|
||||||
|
*
|
||||||
|
* @param errorMessage The exception message.
|
||||||
|
* @param cause The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
class PatchException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
|
||||||
|
constructor(errorMessage: String) : this(errorMessage, null)
|
||||||
|
constructor(cause: Throwable) : this(cause.message, cause)
|
||||||
|
}
|
||||||
@@ -1,35 +1,9 @@
|
|||||||
package app.revanced.patcher.patch
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
interface PatchResult {
|
/**
|
||||||
fun error(): PatchResultError? {
|
* A result of executing a [Patch].
|
||||||
if (this is PatchResultError) {
|
*
|
||||||
return this
|
* @param patch The [Patch] that was executed.
|
||||||
}
|
* @param exception The [PatchException] thrown, if any.
|
||||||
return null
|
*/
|
||||||
}
|
class PatchResult internal constructor(val patch: Patch<*>, val exception: PatchException? = null)
|
||||||
|
|
||||||
fun success(): PatchResultSuccess? {
|
|
||||||
if (this is PatchResultSuccess) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isError(): Boolean {
|
|
||||||
return this is PatchResultError
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSuccess(): Boolean {
|
|
||||||
return this is PatchResultSuccess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PatchResultError(
|
|
||||||
errorMessage: String?, cause: Exception?
|
|
||||||
) : Exception(errorMessage, cause), PatchResult {
|
|
||||||
constructor(errorMessage: String) : this(errorMessage, null)
|
|
||||||
constructor(cause: Exception) : this(cause.message, cause)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class PatchResultSuccess : PatchResult
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
|
import app.revanced.patcher.PatchClass
|
||||||
|
import app.revanced.patcher.Patcher
|
||||||
|
import app.revanced.patcher.data.ResourceContext
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Patch] that accesses a [ResourceContext].
|
||||||
|
*
|
||||||
|
* If an implementation of [Patch] also implements [Closeable]
|
||||||
|
* it will be closed in reverse execution order of patches executed by [Patcher].
|
||||||
|
*
|
||||||
|
* This type of patch that does not have access to decoded resources.
|
||||||
|
* Instead, you can read and write arbitrary files in an APK file.
|
||||||
|
*
|
||||||
|
* If you want to access decoded resources, use [ResourcePatch] instead.
|
||||||
|
*/
|
||||||
|
abstract class RawResourcePatch : Patch<ResourceContext> {
|
||||||
|
/**
|
||||||
|
* Create a new [RawResourcePatch].
|
||||||
|
*/
|
||||||
|
constructor()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [RawResourcePatch].
|
||||||
|
*
|
||||||
|
* @param name The name of the patch.
|
||||||
|
* @param description The description of the patch.
|
||||||
|
* @param compatiblePackages The packages the patch is compatible with.
|
||||||
|
* @param dependencies Other patches this patch depends on.
|
||||||
|
* @param use Weather or not the patch should be used.
|
||||||
|
* @param requiresIntegrations Weather or not the patch requires integrations.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
name: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
compatiblePackages: Set<CompatiblePackage>? = null,
|
||||||
|
dependencies: Set<PatchClass>? = null,
|
||||||
|
use: Boolean = true,
|
||||||
|
requiresIntegrations: Boolean = false,
|
||||||
|
) : super(name, description, compatiblePackages, dependencies, use, requiresIntegrations)
|
||||||
|
}
|
||||||
43
src/main/kotlin/app/revanced/patcher/patch/ResourcePatch.kt
Normal file
43
src/main/kotlin/app/revanced/patcher/patch/ResourcePatch.kt
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package app.revanced.patcher.patch
|
||||||
|
|
||||||
|
import app.revanced.patcher.PatchClass
|
||||||
|
import app.revanced.patcher.Patcher
|
||||||
|
import app.revanced.patcher.data.ResourceContext
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Patch] that accesses a [ResourceContext].
|
||||||
|
*
|
||||||
|
* If an implementation of [Patch] also implements [Closeable]
|
||||||
|
* it will be closed in reverse execution order of patches executed by [Patcher].
|
||||||
|
*
|
||||||
|
* This type of patch has access to decoded resources.
|
||||||
|
* Additionally, you can read and write arbitrary files in an APK file.
|
||||||
|
*
|
||||||
|
* If you do not need access to decoded resources, use [RawResourcePatch] instead.
|
||||||
|
*/
|
||||||
|
abstract class ResourcePatch : Patch<ResourceContext> {
|
||||||
|
/**
|
||||||
|
* Create a new [ResourcePatch].
|
||||||
|
*/
|
||||||
|
constructor()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [ResourcePatch].
|
||||||
|
*
|
||||||
|
* @param name The name of the patch.
|
||||||
|
* @param description The description of the patch.
|
||||||
|
* @param compatiblePackages The packages the patch is compatible with.
|
||||||
|
* @param dependencies Other patches this patch depends on.
|
||||||
|
* @param use Weather or not the patch should be used.
|
||||||
|
* @param requiresIntegrations Weather or not the patch requires integrations.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
name: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
compatiblePackages: Set<CompatiblePackage>? = null,
|
||||||
|
dependencies: Set<PatchClass>? = null,
|
||||||
|
use: Boolean = true,
|
||||||
|
requiresIntegrations: Boolean = false,
|
||||||
|
) : super(name, description, compatiblePackages, dependencies, use, requiresIntegrations)
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package app.revanced.patcher.patch.annotation
|
||||||
|
|
||||||
|
import java.lang.annotation.Inherited
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation for [app.revanced.patcher.patch.Patch] classes.
|
||||||
|
*
|
||||||
|
* @param name The name of the patch. If empty, the patch will be unnamed.
|
||||||
|
* @param description The description of the patch. If empty, no description will be used.
|
||||||
|
* @param dependencies The patches this patch depends on.
|
||||||
|
* @param compatiblePackages The packages this patch is compatible with.
|
||||||
|
* @param use Whether this patch should be used.
|
||||||
|
* @param requiresIntegrations Whether this patch requires integrations.
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Inherited
|
||||||
|
annotation class Patch(
|
||||||
|
val name: String = "",
|
||||||
|
val description: String = "",
|
||||||
|
val dependencies: Array<KClass<out app.revanced.patcher.patch.Patch<*>>> = [],
|
||||||
|
val compatiblePackages: Array<CompatiblePackage> = [],
|
||||||
|
val use: Boolean = true,
|
||||||
|
// TODO: Remove this property, once integrations are coupled with patches.
|
||||||
|
val requiresIntegrations: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A package that a [app.revanced.patcher.patch.Patch] is compatible with.
|
||||||
|
*
|
||||||
|
* @param name The name of the package.
|
||||||
|
* @param versions The versions of the package.
|
||||||
|
*/
|
||||||
|
annotation class CompatiblePackage(
|
||||||
|
val name: String,
|
||||||
|
val versions: Array<String> = [],
|
||||||
|
)
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package app.revanced.patcher.patch.annotations
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to mark a Class as a patch.
|
|
||||||
* @param include If false, the patch should be treated as optional by default.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Patch(val include: Boolean = true)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for dependencies of [Patch]es .
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Dependencies(
|
|
||||||
val dependencies: Array<KClass<out Patch<Data>>> = []
|
|
||||||
)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package app.revanced.patcher.patch.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
|
||||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bytecode patch for the Patcher.
|
|
||||||
* @param fingerprints A list of [MethodFingerprint] this patch relies on.
|
|
||||||
*/
|
|
||||||
abstract class BytecodePatch(
|
|
||||||
internal val fingerprints: Iterable<MethodFingerprint>
|
|
||||||
) : Patch<BytecodeData>()
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package app.revanced.patcher.patch.impl
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.ResourceData
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource patch for the Patcher.
|
|
||||||
*/
|
|
||||||
abstract class ResourcePatch : Patch<ResourceData>()
|
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
package app.revanced.patcher.patch.options
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Patch] option.
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values identified by their string representation.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param valueType The type of the option value (to handle type erasure).
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
* @param T The value type of the option.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||||
|
open class PatchOption<T>(
|
||||||
|
val key: String,
|
||||||
|
val default: T?,
|
||||||
|
val values: Map<String, T?>?,
|
||||||
|
val title: String?,
|
||||||
|
val description: String?,
|
||||||
|
val required: Boolean,
|
||||||
|
val valueType: String,
|
||||||
|
val validator: PatchOption<T>.(T?) -> Boolean,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The value of the [PatchOption].
|
||||||
|
*/
|
||||||
|
var value: T?
|
||||||
|
/**
|
||||||
|
* Set the value of the [PatchOption].
|
||||||
|
*
|
||||||
|
* @param value The value to set.
|
||||||
|
*
|
||||||
|
* @throws PatchOptionException.ValueRequiredException If the value is required but null.
|
||||||
|
* @throws PatchOptionException.ValueValidationException If the value is invalid.
|
||||||
|
*/
|
||||||
|
set(value) {
|
||||||
|
assertRequiredButNotNull(value)
|
||||||
|
assertValid(value)
|
||||||
|
|
||||||
|
uncheckedValue = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the [PatchOption].
|
||||||
|
*
|
||||||
|
* @return The value.
|
||||||
|
*
|
||||||
|
* @throws PatchOptionException.ValueRequiredException If the value is required but null.
|
||||||
|
* @throws PatchOptionException.ValueValidationException If the value is invalid.
|
||||||
|
*/
|
||||||
|
get() {
|
||||||
|
assertRequiredButNotNull(uncheckedValue)
|
||||||
|
assertValid(uncheckedValue)
|
||||||
|
|
||||||
|
return uncheckedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// The unchecked value is used to allow setting the value without validation.
|
||||||
|
private var uncheckedValue = default
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the [PatchOption] to its default value.
|
||||||
|
* Override this method if you need to mutate the value instead of replacing it.
|
||||||
|
*/
|
||||||
|
open fun reset() {
|
||||||
|
uncheckedValue = default
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertRequiredButNotNull(value: T?) {
|
||||||
|
if (required && value == null) throw PatchOptionException.ValueRequiredException(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertValid(value: T?) {
|
||||||
|
if (!validator(value)) throw PatchOptionException.ValueValidationException(value, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun toString() = value.toString()
|
||||||
|
|
||||||
|
operator fun getValue(
|
||||||
|
thisRef: Any?,
|
||||||
|
property: KProperty<*>,
|
||||||
|
) = value
|
||||||
|
|
||||||
|
operator fun setValue(
|
||||||
|
thisRef: Any?,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: T?,
|
||||||
|
) {
|
||||||
|
this.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
companion object PatchExtensions {
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with a string value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.stringPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: String? = null,
|
||||||
|
values: Map<String, String?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<String>.(String?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"String",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with an integer value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.intPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: Int? = null,
|
||||||
|
values: Map<String, Int?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<Int?>.(Int?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"Int",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with a boolean value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.booleanPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: Boolean? = null,
|
||||||
|
values: Map<String, Boolean?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<Boolean?>.(Boolean?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"Boolean",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with a float value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.floatPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: Float? = null,
|
||||||
|
values: Map<String, Float?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<Float?>.(Float?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"Float",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with a long value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.longPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: Long? = null,
|
||||||
|
values: Map<String, Long?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<Long?>.(Long?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"Long",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with a string array value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.stringArrayPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: Array<String>? = null,
|
||||||
|
values: Map<String, Array<String>?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<Array<String>?>.(Array<String>?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"StringArray",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with an integer array value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.intArrayPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: Array<Int>? = null,
|
||||||
|
values: Map<String, Array<Int>?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<Array<Int>?>.(Array<Int>?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"IntArray",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with a boolean array value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.booleanArrayPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: Array<Boolean>? = null,
|
||||||
|
values: Map<String, Array<Boolean>?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<Array<Boolean>?>.(Array<Boolean>?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"BooleanArray",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with a float array value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.floatArrayPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: Array<Float>? = null,
|
||||||
|
values: Map<String, Array<Float>?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<Array<Float>?>.(Array<Float>?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"FloatArray",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with a long array value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>> P.longArrayPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: Array<Long>? = null,
|
||||||
|
values: Map<String, Array<Long>?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
validator: PatchOption<Array<Long>?>.(Array<Long>?) -> Boolean = { true },
|
||||||
|
) = registerNewPatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
"LongArray",
|
||||||
|
validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [PatchOption] with a string set value and add it to the current [Patch].
|
||||||
|
*
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param default The default value.
|
||||||
|
* @param values The set of guaranteed valid values identified by their string representation.
|
||||||
|
* @param title The title.
|
||||||
|
* @param description A description.
|
||||||
|
* @param required Whether the option is required.
|
||||||
|
* @param valueType The type of the option value (to handle type erasure).
|
||||||
|
* @param validator The function to validate the option value.
|
||||||
|
*
|
||||||
|
* @return The created [PatchOption].
|
||||||
|
*
|
||||||
|
* @see PatchOption
|
||||||
|
*/
|
||||||
|
fun <P : Patch<*>, T> P.registerNewPatchOption(
|
||||||
|
key: String,
|
||||||
|
default: T? = null,
|
||||||
|
values: Map<String, T?>? = null,
|
||||||
|
title: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
required: Boolean = false,
|
||||||
|
valueType: String,
|
||||||
|
validator: PatchOption<T>.(T?) -> Boolean = { true },
|
||||||
|
) = PatchOption(
|
||||||
|
key,
|
||||||
|
default,
|
||||||
|
values,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
required,
|
||||||
|
valueType,
|
||||||
|
validator,
|
||||||
|
).also(options::register)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package app.revanced.patcher.patch.options
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when using [PatchOption]s.
|
||||||
|
*
|
||||||
|
* @param errorMessage The exception message.
|
||||||
|
*/
|
||||||
|
sealed class PatchOptionException(errorMessage: String) : Exception(errorMessage, null) {
|
||||||
|
/**
|
||||||
|
* An exception thrown when a [PatchOption] is set to an invalid value.
|
||||||
|
*
|
||||||
|
* @param invalidType The type of the value that was passed.
|
||||||
|
* @param expectedType The type of the value that was expected.
|
||||||
|
*/
|
||||||
|
class InvalidValueTypeException(invalidType: String, expectedType: String) :
|
||||||
|
PatchOptionException("Type $expectedType was expected but received type $invalidType")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when a value did not satisfy the value conditions specified by the [PatchOption].
|
||||||
|
*
|
||||||
|
* @param value The value that failed validation.
|
||||||
|
*/
|
||||||
|
class ValueValidationException(value: Any?, option: PatchOption<*>) :
|
||||||
|
PatchOptionException("The option value \"$value\" failed validation for ${option.key}")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when a value is required but null was passed.
|
||||||
|
*
|
||||||
|
* @param option The [PatchOption] that requires a value.
|
||||||
|
*/
|
||||||
|
class ValueRequiredException(option: PatchOption<*>) :
|
||||||
|
PatchOptionException("The option ${option.key} requires a value, but null was passed")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown when a [PatchOption] is not found.
|
||||||
|
*
|
||||||
|
* @param key The key of the [PatchOption].
|
||||||
|
*/
|
||||||
|
class PatchOptionNotFoundException(key: String) :
|
||||||
|
PatchOptionException("No option with key $key")
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package app.revanced.patcher.patch.options
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of [PatchOption]s associated by their keys.
|
||||||
|
*
|
||||||
|
* @param options The [PatchOption]s to initialize with.
|
||||||
|
*/
|
||||||
|
class PatchOptions internal constructor(
|
||||||
|
private val options: MutableMap<String, PatchOption<*>> = mutableMapOf(),
|
||||||
|
) : MutableMap<String, PatchOption<*>> by options {
|
||||||
|
/**
|
||||||
|
* Register a [PatchOption]. Acts like [MutableMap.put].
|
||||||
|
* @param value The [PatchOption] to register.
|
||||||
|
*/
|
||||||
|
fun register(value: PatchOption<*>) {
|
||||||
|
options[value.key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an option's value.
|
||||||
|
* @param key The identifier.
|
||||||
|
* @param value The value.
|
||||||
|
* @throws PatchOptionException.PatchOptionNotFoundException If the option does not exist.
|
||||||
|
*/
|
||||||
|
operator fun <T : Any> set(
|
||||||
|
key: String,
|
||||||
|
value: T?,
|
||||||
|
) {
|
||||||
|
val option = this[key]
|
||||||
|
|
||||||
|
try {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(option as PatchOption<T>).value = value
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
throw PatchOptionException.InvalidValueTypeException(
|
||||||
|
value?.let { it::class.java.name } ?: "null",
|
||||||
|
option.value?.let { it::class.java.name } ?: "null",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an option.
|
||||||
|
*/
|
||||||
|
override operator fun get(key: String) = options[key] ?: throw PatchOptionException.PatchOptionNotFoundException(key)
|
||||||
|
}
|
||||||
238
src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt
Normal file
238
src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
|
import app.revanced.patcher.extensions.or
|
||||||
|
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
|
||||||
|
import app.revanced.patcher.util.ClassMerger.Utils.filterAny
|
||||||
|
import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny
|
||||||
|
import app.revanced.patcher.util.ClassMerger.Utils.isPublic
|
||||||
|
import app.revanced.patcher.util.ClassMerger.Utils.toPublic
|
||||||
|
import app.revanced.patcher.util.ClassMerger.Utils.traverseClassHierarchy
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableField
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
|
import java.util.logging.Logger
|
||||||
|
import kotlin.reflect.KFunction2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Experimental class to merge a [ClassDef] with another.
|
||||||
|
* Note: This will not consider method implementations or if the class is missing a superclass or interfaces.
|
||||||
|
*/
|
||||||
|
internal object ClassMerger {
|
||||||
|
private val logger = Logger.getLogger(ClassMerger::class.java.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge a class with [otherClass].
|
||||||
|
*
|
||||||
|
* @param otherClass The class to merge with
|
||||||
|
* @param context The context to traverse the class hierarchy in.
|
||||||
|
* @return The merged class or the original class if no merge was needed.
|
||||||
|
*/
|
||||||
|
fun ClassDef.merge(
|
||||||
|
otherClass: ClassDef,
|
||||||
|
context: BytecodeContext,
|
||||||
|
) = this
|
||||||
|
// .fixFieldAccess(otherClass)
|
||||||
|
// .fixMethodAccess(otherClass)
|
||||||
|
.addMissingFields(otherClass)
|
||||||
|
.addMissingMethods(otherClass)
|
||||||
|
.publicize(otherClass, context)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add methods which are missing but existing in [fromClass].
|
||||||
|
*
|
||||||
|
* @param fromClass The class to add missing methods from.
|
||||||
|
*/
|
||||||
|
private fun ClassDef.addMissingMethods(fromClass: ClassDef): ClassDef {
|
||||||
|
val missingMethods =
|
||||||
|
fromClass.methods.let { fromMethods ->
|
||||||
|
methods.filterNot { method ->
|
||||||
|
fromMethods.any { fromMethod ->
|
||||||
|
MethodUtil.methodSignaturesMatch(fromMethod, method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingMethods.isEmpty()) return this
|
||||||
|
|
||||||
|
logger.fine("Found ${missingMethods.size} missing methods")
|
||||||
|
|
||||||
|
return asMutableClass().apply {
|
||||||
|
methods.addAll(missingMethods.map { it.toMutable() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add fields which are missing but existing in [fromClass].
|
||||||
|
*
|
||||||
|
* @param fromClass The class to add missing fields from.
|
||||||
|
*/
|
||||||
|
private fun ClassDef.addMissingFields(fromClass: ClassDef): ClassDef {
|
||||||
|
val missingFields =
|
||||||
|
fields.filterNotAny(fromClass.fields) { field, fromField ->
|
||||||
|
fromField.name == field.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingFields.isEmpty()) return this
|
||||||
|
|
||||||
|
logger.fine("Found ${missingFields.size} missing fields")
|
||||||
|
|
||||||
|
return asMutableClass().apply {
|
||||||
|
fields.addAll(missingFields.map { it.toMutable() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a class and its super class public recursively.
|
||||||
|
* @param reference The class to check the [AccessFlags] of.
|
||||||
|
* @param context The context to traverse the class hierarchy in.
|
||||||
|
*/
|
||||||
|
private fun ClassDef.publicize(
|
||||||
|
reference: ClassDef,
|
||||||
|
context: BytecodeContext,
|
||||||
|
) = if (reference.accessFlags.isPublic() && !accessFlags.isPublic()) {
|
||||||
|
this.asMutableClass().apply {
|
||||||
|
context.traverseClassHierarchy(this) {
|
||||||
|
if (accessFlags.isPublic()) return@traverseClassHierarchy
|
||||||
|
|
||||||
|
logger.fine("Publicizing ${this.type}")
|
||||||
|
|
||||||
|
accessFlags = accessFlags.toPublic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publicize fields if they are public in [reference].
|
||||||
|
*
|
||||||
|
* @param reference The class to check the [AccessFlags] of the fields in.
|
||||||
|
*/
|
||||||
|
private fun ClassDef.fixFieldAccess(reference: ClassDef): ClassDef {
|
||||||
|
val brokenFields =
|
||||||
|
fields.filterAny(reference.fields) { field, referenceField ->
|
||||||
|
if (field.name != referenceField.name) return@filterAny false
|
||||||
|
|
||||||
|
referenceField.accessFlags.isPublic() && !field.accessFlags.isPublic()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brokenFields.isEmpty()) return this
|
||||||
|
|
||||||
|
logger.fine("Found ${brokenFields.size} broken fields")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a field public.
|
||||||
|
*/
|
||||||
|
fun MutableField.publicize() {
|
||||||
|
accessFlags = accessFlags.toPublic()
|
||||||
|
}
|
||||||
|
|
||||||
|
return asMutableClass().apply {
|
||||||
|
fields.filter { brokenFields.contains(it) }.forEach(MutableField::publicize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publicize methods if they are public in [reference].
|
||||||
|
*
|
||||||
|
* @param reference The class to check the [AccessFlags] of the methods in.
|
||||||
|
*/
|
||||||
|
private fun ClassDef.fixMethodAccess(reference: ClassDef): ClassDef {
|
||||||
|
val brokenMethods =
|
||||||
|
methods.filterAny(reference.methods) { method, referenceMethod ->
|
||||||
|
if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false
|
||||||
|
|
||||||
|
referenceMethod.accessFlags.isPublic() && !method.accessFlags.isPublic()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brokenMethods.isEmpty()) return this
|
||||||
|
|
||||||
|
logger.fine("Found ${brokenMethods.size} methods")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a method public.
|
||||||
|
*/
|
||||||
|
fun MutableMethod.publicize() {
|
||||||
|
accessFlags = accessFlags.toPublic()
|
||||||
|
}
|
||||||
|
|
||||||
|
return asMutableClass().apply {
|
||||||
|
methods.filter { brokenMethods.contains(it) }.forEach(MutableMethod::publicize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Utils {
|
||||||
|
/**
|
||||||
|
* traverse the class hierarchy starting from the given root class
|
||||||
|
*
|
||||||
|
* @param targetClass the class to start traversing the class hierarchy from
|
||||||
|
* @param callback function that is called for every class in the hierarchy
|
||||||
|
*/
|
||||||
|
fun BytecodeContext.traverseClassHierarchy(
|
||||||
|
targetClass: MutableClass,
|
||||||
|
callback: MutableClass.() -> Unit,
|
||||||
|
) {
|
||||||
|
callback(targetClass)
|
||||||
|
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
|
||||||
|
traverseClassHierarchy(it, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the [AccessFlags.PUBLIC] flag is set.
|
||||||
|
*
|
||||||
|
* @return True, if the flag is set.
|
||||||
|
*/
|
||||||
|
fun Int.isPublic() = AccessFlags.PUBLIC.isSet(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make [AccessFlags] public.
|
||||||
|
*
|
||||||
|
* @return The new [AccessFlags].
|
||||||
|
*/
|
||||||
|
fun Int.toPublic() = this.or(AccessFlags.PUBLIC).and(AccessFlags.PRIVATE.value.inv())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter [this] on [needles] matching the given [predicate].
|
||||||
|
*
|
||||||
|
* @param needles The needles to filter [this] with.
|
||||||
|
* @param predicate The filter.
|
||||||
|
* @return The [this] filtered on [needles] matching the given [predicate].
|
||||||
|
*/
|
||||||
|
fun <HayType, NeedleType> Iterable<HayType>.filterAny(
|
||||||
|
needles: Iterable<NeedleType>,
|
||||||
|
predicate: (HayType, NeedleType) -> Boolean,
|
||||||
|
) = Iterable<HayType>::filter.any(this, needles, predicate)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter [this] on [needles] not matching the given [predicate].
|
||||||
|
*
|
||||||
|
* @param needles The needles to filter [this] with.
|
||||||
|
* @param predicate The filter.
|
||||||
|
* @return The [this] filtered on [needles] not matching the given [predicate].
|
||||||
|
*/
|
||||||
|
fun <HayType, NeedleType> Iterable<HayType>.filterNotAny(
|
||||||
|
needles: Iterable<NeedleType>,
|
||||||
|
predicate: (HayType, NeedleType) -> Boolean,
|
||||||
|
) = Iterable<HayType>::filterNot.any(this, needles, predicate)
|
||||||
|
|
||||||
|
fun <HayType, NeedleType> KFunction2<Iterable<HayType>, (HayType) -> Boolean, List<HayType>>.any(
|
||||||
|
haystack: Iterable<HayType>,
|
||||||
|
needles: Iterable<NeedleType>,
|
||||||
|
predicate: (HayType, NeedleType) -> Boolean,
|
||||||
|
) = this(haystack) { hay ->
|
||||||
|
needles.any { needle ->
|
||||||
|
predicate(hay, needle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/main/kotlin/app/revanced/patcher/util/Document.kt
Normal file
48
src/main/kotlin/app/revanced/patcher/util/Document.kt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import org.w3c.dom.Document
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
import javax.xml.transform.TransformerFactory
|
||||||
|
import javax.xml.transform.dom.DOMSource
|
||||||
|
import javax.xml.transform.stream.StreamResult
|
||||||
|
|
||||||
|
class Document internal constructor(
|
||||||
|
inputStream: InputStream,
|
||||||
|
) : Document by DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream), Closeable {
|
||||||
|
private var file: File? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal constructor(file: File) : this(file.inputStream()) {
|
||||||
|
this.file = file
|
||||||
|
readerCount.merge(file, 1, Int::plus)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
file?.let {
|
||||||
|
if (readerCount[it]!! > 1) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"Two or more instances are currently reading $it." +
|
||||||
|
"To be able to close this instance, no other instances may be reading $it at the same time.",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
readerCount.remove(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.outputStream().use { stream ->
|
||||||
|
TransformerFactory.newInstance()
|
||||||
|
.newTransformer()
|
||||||
|
.transform(DOMSource(this), StreamResult(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private val readerCount = mutableMapOf<File, Int>()
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/main/kotlin/app/revanced/patcher/util/DomFileEditor.kt
Normal file
25
src/main/kotlin/app/revanced/patcher/util/DomFileEditor.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import org.w3c.dom.Document
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
@Deprecated("Use Document instead.")
|
||||||
|
class DomFileEditor : Closeable {
|
||||||
|
val file: Document
|
||||||
|
internal constructor(
|
||||||
|
inputStream: InputStream,
|
||||||
|
) {
|
||||||
|
file = Document(inputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(file: File) {
|
||||||
|
this.file = Document(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
file as app.revanced.patcher.util.Document
|
||||||
|
file.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package app.revanced.patcher.util
|
|
||||||
|
|
||||||
internal class ListBackedSet<E>(private val list: MutableList<E>) : MutableSet<E> {
|
|
||||||
override val size get() = list.size
|
|
||||||
override fun add(element: E) = list.add(element)
|
|
||||||
override fun addAll(elements: Collection<E>) = list.addAll(elements)
|
|
||||||
override fun clear() = list.clear()
|
|
||||||
override fun iterator() = list.listIterator()
|
|
||||||
override fun remove(element: E) = list.remove(element)
|
|
||||||
override fun removeAll(elements: Collection<E>) = list.removeAll(elements)
|
|
||||||
override fun retainAll(elements: Collection<E>) = list.retainAll(elements)
|
|
||||||
override fun contains(element: E) = list.contains(element)
|
|
||||||
override fun containsAll(elements: Collection<E>) = list.containsAll(elements)
|
|
||||||
override fun isEmpty() = list.isEmpty()
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package app.revanced.patcher.util
|
|
||||||
|
|
||||||
import app.revanced.patcher.util.proxy.ClassProxy
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
|
|
||||||
class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> {
|
|
||||||
private val internalProxies = mutableListOf<ClassProxy>()
|
|
||||||
internal val proxies: List<ClassProxy> = internalProxies
|
|
||||||
|
|
||||||
fun add(classDef: ClassDef) = internalClasses.add(classDef)
|
|
||||||
fun add(classProxy: ClassProxy) = internalProxies.add(classProxy)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply all resolved classes into [internalClasses] and clean the [proxies] list.
|
|
||||||
*/
|
|
||||||
internal fun applyProxies() {
|
|
||||||
// FIXME: check if this could cause issues when multiple patches use the same proxy
|
|
||||||
internalProxies.removeIf { proxy ->
|
|
||||||
// if the proxy is unused, keep it in the list
|
|
||||||
if (!proxy.proxyUsed) return@removeIf false
|
|
||||||
|
|
||||||
// if it has been used, replace the internal class which it proxied
|
|
||||||
val index = internalClasses.indexOfFirst { it.type == proxy.immutableClass.type }
|
|
||||||
internalClasses[index] = proxy.mutatedClass
|
|
||||||
|
|
||||||
// return true to remove it from the proxies list
|
|
||||||
return@removeIf true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val size get() = internalClasses.size
|
|
||||||
override fun contains(element: ClassDef) = internalClasses.contains(element)
|
|
||||||
override fun containsAll(elements: Collection<ClassDef>) = internalClasses.containsAll(elements)
|
|
||||||
override fun get(index: Int) = internalClasses[index]
|
|
||||||
override fun indexOf(element: ClassDef) = internalClasses.indexOf(element)
|
|
||||||
override fun isEmpty() = internalClasses.isEmpty()
|
|
||||||
override fun iterator() = internalClasses.iterator()
|
|
||||||
override fun lastIndexOf(element: ClassDef) = internalClasses.lastIndexOf(element)
|
|
||||||
override fun listIterator() = internalClasses.listIterator()
|
|
||||||
override fun listIterator(index: Int) = internalClasses.listIterator(index)
|
|
||||||
override fun subList(fromIndex: Int, toIndex: Int) = internalClasses.subList(fromIndex, toIndex)
|
|
||||||
}
|
|
||||||
34
src/main/kotlin/app/revanced/patcher/util/ProxyClassList.kt
Normal file
34
src/main/kotlin/app/revanced/patcher/util/ProxyClassList.kt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package app.revanced.patcher.util
|
||||||
|
|
||||||
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that represents a set of classes and proxies.
|
||||||
|
*
|
||||||
|
* @param classes The classes to be backed by proxies.
|
||||||
|
*/
|
||||||
|
class ProxyClassList internal constructor(classes: MutableSet<ClassDef>) : MutableSet<ClassDef> by classes {
|
||||||
|
internal val proxies = mutableListOf<ClassProxy>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a [ClassProxy].
|
||||||
|
*/
|
||||||
|
fun add(classProxy: ClassProxy) = proxies.add(classProxy)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace all classes with their mutated versions.
|
||||||
|
*/
|
||||||
|
internal fun replaceClasses() =
|
||||||
|
proxies.removeIf { proxy ->
|
||||||
|
// If the proxy is unused, return false to keep it in the proxies list.
|
||||||
|
if (!proxy.resolved) return@removeIf false
|
||||||
|
|
||||||
|
// If it has been used, replace the original class with the mutable class.
|
||||||
|
remove(proxy.immutableClass)
|
||||||
|
add(proxy.mutableClass)
|
||||||
|
|
||||||
|
// Return true to remove the proxy from the proxies list.
|
||||||
|
return@removeIf true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package app.revanced.patcher.util.dex
|
|
||||||
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for dex files.
|
|
||||||
* @param name The original name of the dex file.
|
|
||||||
* @param dexFileInputStream The dex file as [InputStream].
|
|
||||||
*/
|
|
||||||
data class DexFile(val name: String, val dexFileInputStream: InputStream)
|
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
package app.revanced.patcher.util.method
|
package app.revanced.patcher.util.method
|
||||||
|
|
||||||
import app.revanced.patcher.data.impl.BytecodeData
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.data.impl.MethodNotFoundException
|
|
||||||
import app.revanced.patcher.extensions.softCompareTo
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
import org.jf.dexlib2.Format
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
import org.jf.dexlib2.iface.Method
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
import org.jf.dexlib2.util.Preconditions
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a method from another method via instruction offsets.
|
* Find a method from another method via instruction offsets.
|
||||||
* @param bytecodeData The bytecodeData to use when resolving the next method reference.
|
* @param bytecodeContext The context to use when resolving the next method reference.
|
||||||
* @param currentMethod The method to start from.
|
* @param currentMethod The method to start from.
|
||||||
*/
|
*/
|
||||||
class MethodWalker internal constructor(
|
class MethodWalker internal constructor(
|
||||||
private val bytecodeData: BytecodeData,
|
private val bytecodeContext: BytecodeContext,
|
||||||
private var currentMethod: Method
|
private var currentMethod: Method,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Get the method which was walked last.
|
* Get the method which was walked last.
|
||||||
|
*
|
||||||
* It is possible to cast this method to a [MutableMethod], if the method has been walked mutably.
|
* It is possible to cast this method to a [MutableMethod], if the method has been walked mutably.
|
||||||
|
*
|
||||||
|
* @return The method which was walked last.
|
||||||
*/
|
*/
|
||||||
fun getMethod(): Method {
|
fun getMethod(): Method {
|
||||||
return currentMethod
|
return currentMethod
|
||||||
@@ -29,27 +29,32 @@ class MethodWalker internal constructor(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Walk to a method defined at the offset in the instruction list of the current method.
|
* Walk to a method defined at the offset in the instruction list of the current method.
|
||||||
|
*
|
||||||
|
* The current method will be mutable.
|
||||||
|
*
|
||||||
* @param offset The offset of the instruction. This instruction must be of format 35c.
|
* @param offset The offset of the instruction. This instruction must be of format 35c.
|
||||||
* @param walkMutable If this is true, the class of the method will be resolved mutably.
|
* @param walkMutable If this is true, the class of the method will be resolved mutably.
|
||||||
* The current method will be mutable.
|
* @return The same [MethodWalker] instance with the method at [offset].
|
||||||
*/
|
*/
|
||||||
fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
fun nextMethod(
|
||||||
|
offset: Int,
|
||||||
|
walkMutable: Boolean = false,
|
||||||
|
): MethodWalker {
|
||||||
currentMethod.implementation?.instructions?.let { instructions ->
|
currentMethod.implementation?.instructions?.let { instructions ->
|
||||||
val instruction = instructions.elementAt(offset)
|
val instruction = instructions.elementAt(offset)
|
||||||
|
|
||||||
Preconditions.checkFormat(instruction.opcode, Format.Format35c)
|
val newMethod = (instruction as ReferenceInstruction).reference as MethodReference
|
||||||
|
val proxy = bytecodeContext.findClass(newMethod.definingClass)!!
|
||||||
|
|
||||||
val newMethod = (instruction as Instruction35c).reference as MethodReference
|
val methods = if (walkMutable) proxy.mutableClass.methods else proxy.immutableClass.methods
|
||||||
val proxy = bytecodeData.findClass(newMethod.definingClass)!!
|
currentMethod =
|
||||||
|
methods.first {
|
||||||
val methods = if (walkMutable) proxy.resolve().methods else proxy.immutableClass.methods
|
return@first MethodUtil.methodSignaturesMatch(it, newMethod)
|
||||||
currentMethod = methods.first { it ->
|
}
|
||||||
return@first it.softCompareTo(newMethod)
|
|
||||||
}
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
|
throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class MethodNotFoundException(exception: String) : Exception(exception)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package app.revanced.patcher.util.patch.base
|
|
||||||
|
|
||||||
import app.revanced.patcher.data.Data
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param patchBundlePath The path to the patch bundle.
|
|
||||||
*/
|
|
||||||
abstract class PatchBundle(patchBundlePath: String) : File(patchBundlePath) {
|
|
||||||
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
|
||||||
classNames.forEach { className ->
|
|
||||||
val clazz = classLoader.loadClass(className)
|
|
||||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
|
|
||||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package app.revanced.patcher.util.patch.implementation
|
|
||||||
|
|
||||||
import app.revanced.patcher.util.patch.base.PatchBundle
|
|
||||||
import app.revanced.patcher.util.patch.util.StringIterator
|
|
||||||
import org.jf.dexlib2.DexFileFactory
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A patch bundle of the ReVanced [DexPatchBundle] format.
|
|
||||||
* @param patchBundlePath The path to a patch bundle of dex format.
|
|
||||||
* @param dexClassLoader The dex class loader.
|
|
||||||
*/
|
|
||||||
class DexPatchBundle(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
|
||||||
fun loadPatches() = loadPatches(dexClassLoader,
|
|
||||||
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
|
||||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package app.revanced.patcher.util.patch.implementation
|
|
||||||
|
|
||||||
import app.revanced.patcher.util.patch.base.PatchBundle
|
|
||||||
import app.revanced.patcher.util.patch.util.StringIterator
|
|
||||||
import java.net.URLClassLoader
|
|
||||||
import java.util.jar.JarFile
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A patch bundle of the ReVanced [JarPatchBundle] format.
|
|
||||||
* @param patchBundlePath The path to the patch bundle.
|
|
||||||
*/
|
|
||||||
class JarPatchBundle(patchBundlePath: String) : PatchBundle(patchBundlePath) {
|
|
||||||
fun loadPatches() = loadPatches(
|
|
||||||
URLClassLoader(
|
|
||||||
arrayOf(this.toURI().toURL()),
|
|
||||||
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
|
||||||
),
|
|
||||||
StringIterator(
|
|
||||||
JarFile(this)
|
|
||||||
.entries()
|
|
||||||
.toList() // TODO: find a cleaner solution than that to filter non class files
|
|
||||||
.filter {
|
|
||||||
it.name.endsWith(".class") && !it.name.contains("$")
|
|
||||||
}
|
|
||||||
.iterator()
|
|
||||||
) {
|
|
||||||
it.realName.replace('/', '.').replace(".class", "")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package app.revanced.patcher.util.patch.util
|
|
||||||
|
|
||||||
internal class StringIterator<T, I : Iterator<T>>(
|
|
||||||
private val iterator: I,
|
|
||||||
private val _next: (T) -> String
|
|
||||||
) : Iterator<String> {
|
|
||||||
override fun hasNext() = iterator.hasNext()
|
|
||||||
|
|
||||||
override fun next() = _next(iterator.next())
|
|
||||||
}
|
|
||||||
@@ -1,41 +1,34 @@
|
|||||||
package app.revanced.patcher.util.proxy
|
package app.revanced.patcher.util.proxy
|
||||||
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A proxy class for a [ClassDef].
|
* A proxy class for a [ClassDef].
|
||||||
*
|
*
|
||||||
* A class proxy simply holds a reference to the original class
|
* A class proxy simply holds a reference to the original class
|
||||||
* and allocates a mutable clone for the original class if needed.
|
* and allocates a mutable clone for the original class if needed.
|
||||||
* @param immutableClass The class to proxy
|
* @param immutableClass The class to proxy.
|
||||||
*/
|
*/
|
||||||
class ClassProxy(
|
class ClassProxy internal constructor(
|
||||||
val immutableClass: ClassDef,
|
val immutableClass: ClassDef,
|
||||||
) {
|
) {
|
||||||
internal var proxyUsed = false
|
/**
|
||||||
internal lateinit var mutatedClass: MutableClass
|
* Weather the proxy was actually used.
|
||||||
|
*/
|
||||||
init {
|
internal var resolved = false
|
||||||
// in the instance, that a [MutableClass] is being proxied,
|
|
||||||
// do not create an additional clone and reuse the [MutableClass] instance
|
|
||||||
if (immutableClass is MutableClass) {
|
|
||||||
mutatedClass = immutableClass
|
|
||||||
proxyUsed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates and returns a mutable clone of the original class.
|
* The mutable clone of the original class.
|
||||||
* A patch should always use the original immutable class reference
|
*
|
||||||
* to avoid unnecessary allocations for the mutable class.
|
* Note: This is only allocated if the proxy is actually used.
|
||||||
* @return A mutable clone of the original class.
|
|
||||||
*/
|
*/
|
||||||
fun resolve(): MutableClass {
|
val mutableClass by lazy {
|
||||||
if (!proxyUsed) {
|
resolved = true
|
||||||
proxyUsed = true
|
if (immutableClass is MutableClass) {
|
||||||
mutatedClass = MutableClass(immutableClass)
|
immutableClass
|
||||||
|
} else {
|
||||||
|
MutableClass(immutableClass)
|
||||||
}
|
}
|
||||||
return mutatedClass
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package app.revanced.patcher.util.proxy.mutableTypes
|
package app.revanced.patcher.util.proxy.mutableTypes
|
||||||
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
|
||||||
import org.jf.dexlib2.base.BaseAnnotation
|
import com.android.tools.smali.dexlib2.base.BaseAnnotation
|
||||||
import org.jf.dexlib2.iface.Annotation
|
import com.android.tools.smali.dexlib2.iface.Annotation
|
||||||
|
|
||||||
class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
|
class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
|
||||||
private val visibility = annotation.visibility
|
private val visibility = annotation.visibility
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package app.revanced.patcher.util.proxy.mutableTypes
|
|||||||
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
|
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||||
import org.jf.dexlib2.base.BaseAnnotationElement
|
import com.android.tools.smali.dexlib2.base.BaseAnnotationElement
|
||||||
import org.jf.dexlib2.iface.AnnotationElement
|
import com.android.tools.smali.dexlib2.iface.AnnotationElement
|
||||||
import org.jf.dexlib2.iface.value.EncodedValue
|
import com.android.tools.smali.dexlib2.iface.value.EncodedValue
|
||||||
|
|
||||||
class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() {
|
class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() {
|
||||||
private var name = annotationElement.name
|
private var name = annotationElement.name
|
||||||
@@ -31,4 +31,4 @@ class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnot
|
|||||||
return MutableAnnotationElement(this)
|
return MutableAnnotationElement(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package app.revanced.patcher.util.proxy.mutableTypes
|
|||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
|
import com.android.tools.smali.dexlib2.base.reference.BaseTypeReference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
|
import com.android.tools.smali.dexlib2.util.FieldUtil
|
||||||
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
import com.google.common.collect.Iterables
|
import com.google.common.collect.Iterables
|
||||||
import org.jf.dexlib2.base.reference.BaseTypeReference
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
import org.jf.dexlib2.util.FieldUtil
|
|
||||||
import org.jf.dexlib2.util.MethodUtil
|
|
||||||
|
|
||||||
class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
|
class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
|
||||||
// Class
|
// Class
|
||||||
@@ -100,4 +100,4 @@ class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
|
|||||||
return MutableClass(this)
|
return MutableClass(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user