Compare commits
1103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b02ad6cdab | ||
|
|
f7b16952ea | ||
|
|
f0d05a7d4e | ||
|
|
d6ca317b20 | ||
|
|
2879fddfd9 | ||
|
|
b6c09470fc | ||
|
|
9fdf6c6c32 | ||
|
|
79085af994 | ||
|
|
d189f4b443 | ||
|
|
c9d65e5cd9 | ||
|
|
639d62588a | ||
|
|
6c9db51fd5 | ||
|
|
f0dbb6b22c | ||
|
|
f10f7b6268 | ||
|
|
48a5cbc552 | ||
|
|
6721d150e0 | ||
|
|
54c16cad7d | ||
|
|
ae3ab15245 | ||
|
|
d3c0f50fec | ||
|
|
43753b1b7a | ||
|
|
6f3e4b3682 | ||
|
|
b57cdd31bd | ||
|
|
b936c194e4 | ||
|
|
064431421a | ||
|
|
9d49c7aad0 | ||
|
|
a6f27e5071 | ||
|
|
8d1d10b783 | ||
|
|
61bea05f63 | ||
|
|
bbe7c7b884 | ||
|
|
a432852b78 | ||
|
|
770e8b8cfa | ||
|
|
7faabdc375 | ||
|
|
a9860418ba | ||
|
|
30ff9d0ea9 | ||
|
|
bbc2298939 | ||
|
|
3286e8e24d | ||
|
|
372f3991e1 | ||
|
|
9aa5c0d135 | ||
|
|
9e9808e489 | ||
|
|
3dd75b17cf | ||
|
|
2504b4737b | ||
|
|
8ff04dca0d | ||
|
|
d893ee4829 | ||
|
|
dfc5ec0705 | ||
|
|
8023eb74c9 | ||
|
|
bb4d3997ad | ||
|
|
7ede3af762 | ||
|
|
97ea1c7a9e | ||
|
|
43bb2c0fb8 | ||
|
|
6e7196bb27 | ||
|
|
87a71d58e8 | ||
|
|
32ffd43fe3 | ||
|
|
6500c8d85e | ||
|
|
9866d436da | ||
|
|
0f4884d9d8 | ||
|
|
5a81441351 | ||
|
|
e8721bfb6b | ||
|
|
1b09d95209 | ||
|
|
35f3b5a50e | ||
|
|
ff5203a561 | ||
|
|
c3c1ced309 | ||
|
|
08b1c8ec83 | ||
|
|
1dd3ee4e0a | ||
|
|
06d0c6517d | ||
|
|
4938ce6364 | ||
|
|
25f3fc354f | ||
|
|
ad1a1f8015 | ||
|
|
5c070e2ca8 | ||
|
|
70ea21fca2 | ||
|
|
bc3593871b | ||
|
|
355a424be2 | ||
|
|
0ffa9a0cc8 | ||
|
|
72fecb2b9a | ||
|
|
4e9dfe5478 | ||
|
|
c79e2e3ad4 | ||
|
|
7c33c1c322 | ||
|
|
ea32f75925 | ||
|
|
7eaed21b9a | ||
|
|
cfe8fcd28d | ||
|
|
9f4718af70 | ||
|
|
39faccfd0b | ||
|
|
7545763dae | ||
|
|
c2928be35d | ||
|
|
449bb40d48 | ||
|
|
4caf1a1e63 | ||
|
|
01b205e6f1 | ||
|
|
160d3843a6 | ||
|
|
5efb784115 | ||
|
|
acde5b8384 | ||
|
|
373ee6586a | ||
|
|
215308c329 | ||
|
|
478cadba5e | ||
|
|
f4af4da791 | ||
|
|
22d4f435de | ||
|
|
ccb8cd5c04 | ||
|
|
e963f9e349 | ||
|
|
e3c2a4b8da | ||
|
|
324f46cdad | ||
|
|
691b19b5fd | ||
|
|
63ed2311dc | ||
|
|
d59e59ca40 | ||
|
|
c9a278b4a2 | ||
|
|
8ee5304148 | ||
|
|
3beaa8145f | ||
|
|
e30c55a0af | ||
|
|
b583590a64 | ||
|
|
a28c63168a | ||
|
|
620422350f | ||
|
|
b77cc3c33b | ||
|
|
71bf9b4887 | ||
|
|
9039d401da | ||
|
|
74a0a93201 | ||
|
|
522571f0b3 | ||
|
|
123c49c22d | ||
|
|
1ff88d29be | ||
|
|
63d5a2a1be | ||
|
|
1e9a6cb06b | ||
|
|
31748aa660 | ||
|
|
d0c6ea6a63 | ||
|
|
7ffe286a56 | ||
|
|
b99b30163b | ||
|
|
83b4f1e660 | ||
|
|
b57ba6a98b | ||
|
|
d748e6eff4 | ||
|
|
315d4b75b2 | ||
|
|
31267cbc33 | ||
|
|
c23379b3b6 | ||
|
|
568144d6a2 | ||
|
|
5857042963 | ||
|
|
28e41dc621 | ||
|
|
baef3b364b | ||
|
|
4b9ddb803f | ||
|
|
c07c7ad82f | ||
|
|
e223b80b95 | ||
|
|
5fe468fa1b | ||
|
|
60068d8d16 | ||
|
|
b6c5b46afe | ||
|
|
124cbfadb4 | ||
|
|
8d21f9b900 | ||
|
|
398dbbd2e5 | ||
|
|
e9b392d1c0 | ||
|
|
9d7c7e3225 | ||
|
|
855fd4f0b7 | ||
|
|
807839929d | ||
|
|
870955fee1 | ||
|
|
e0cea929ea | ||
|
|
315f4c35f0 | ||
|
|
d961ea22a6 | ||
|
|
5c0c07c78f | ||
|
|
bba93a0fb7 | ||
|
|
5683df2fc0 | ||
|
|
b5cb9ce67e | ||
|
|
06a32dc895 | ||
|
|
dff12a8a3a | ||
|
|
dc3f07ee84 | ||
|
|
1aef49ee11 | ||
|
|
ee3159b00e | ||
|
|
86d2c307f1 | ||
|
|
7823a217b4 | ||
|
|
2004df8337 | ||
|
|
13bc2d0340 | ||
|
|
edbc850346 | ||
|
|
901d3c85ca | ||
|
|
abbe83f3c2 | ||
|
|
bb3a04a9c8 | ||
|
|
bac13e8b71 | ||
|
|
3871681de3 | ||
|
|
ae6f2b6df6 | ||
|
|
f59bbc9981 | ||
|
|
eac0c84e11 | ||
|
|
f814cc443d | ||
|
|
262466303b | ||
|
|
3b4cc2c5f2 | ||
|
|
023d3fbf6f | ||
|
|
fe01f290bc | ||
|
|
5805ac9f7f | ||
|
|
4e2f38a343 | ||
|
|
572c924e5c | ||
|
|
e90353e550 | ||
|
|
f8ae1b8c49 | ||
|
|
186d995919 | ||
|
|
70584aff9d | ||
|
|
dcdc63785a | ||
|
|
0e7bfa65de | ||
|
|
9427340ab7 | ||
|
|
63af5bae8a | ||
|
|
9232f1fa40 | ||
|
|
15bdb551f4 | ||
|
|
2cda2b53ed | ||
|
|
028c9ea0b5 | ||
|
|
315b51e7ca | ||
|
|
0185b3b145 | ||
|
|
afaad49879 | ||
|
|
4b2b45979b | ||
|
|
66e40edd0e | ||
|
|
3d5168885f | ||
|
|
6eee2c2342 | ||
|
|
11ec27147a | ||
|
|
a522a02ba2 | ||
|
|
5ff4d35a30 | ||
|
|
4c5546bf52 | ||
|
|
ab37a46e83 | ||
|
|
0b3635b5c5 | ||
|
|
2a338d9a83 | ||
|
|
7031a4c26c | ||
|
|
2c5ca97476 | ||
|
|
fc0c8f6bb1 | ||
|
|
643547704e | ||
|
|
4717b63775 | ||
|
|
4af148f480 | ||
|
|
a9c59693ee | ||
|
|
9b3ac159c1 | ||
|
|
c03c98157f | ||
|
|
74149b761f | ||
|
|
44cfa2d8dc | ||
|
|
b0fb00d597 | ||
|
|
af3c1dc039 | ||
|
|
1c94b5d5f0 | ||
|
|
fbb00c0f85 | ||
|
|
4a72e63d1c | ||
|
|
ce94e0ba4d | ||
|
|
e2c836794b | ||
|
|
05e424f805 | ||
|
|
bf30819a4a | ||
|
|
52e9d30b46 | ||
|
|
1fa2d55e1d | ||
|
|
4d0ee652d8 | ||
|
|
ff215bc9aa | ||
|
|
e1cadc878a | ||
|
|
57d9d8ddb9 | ||
|
|
f76b1a5363 | ||
|
|
9b579be2be | ||
|
|
9d43465eab | ||
|
|
fc52870431 | ||
|
|
c466ea100a | ||
|
|
4054369611 | ||
|
|
11106937a3 | ||
|
|
fdddf901f3 | ||
|
|
2e30c04238 | ||
|
|
bd838094cd | ||
|
|
6f22b74154 | ||
|
|
e71525db0e | ||
|
|
d7c1516ec2 | ||
|
|
ff91a5dad9 | ||
|
|
d76679c2b6 | ||
|
|
d7cb88f0a4 | ||
|
|
8a3eb41314 | ||
|
|
e2f9aac1c9 | ||
|
|
08c7396404 | ||
|
|
69cbdbd20e | ||
|
|
005f26ba7c | ||
|
|
a1dc0c8104 | ||
|
|
75435b8fe0 | ||
|
|
6d7385788e | ||
|
|
726d9d410f | ||
|
|
ee2a61bb61 | ||
|
|
40748d6e42 | ||
|
|
02225ad0b8 | ||
|
|
3c4703ad85 | ||
|
|
6762f35ade | ||
|
|
5135afcf8f | ||
|
|
4aae82e0cc | ||
|
|
ea95c6de79 | ||
|
|
4a67e963ed | ||
|
|
720f177d65 | ||
|
|
2b4a043a92 | ||
|
|
77b20bc562 | ||
|
|
063449448a | ||
|
|
559ffcbab5 | ||
|
|
80ed8346be | ||
|
|
a49455f330 | ||
|
|
c3de6f6d02 | ||
|
|
ee416b1bf2 | ||
|
|
784928ffc8 | ||
|
|
609360a1d0 | ||
|
|
18ce2f7b1c | ||
|
|
78625d82ae | ||
|
|
2ca34fb9ad | ||
|
|
f5f944aa50 | ||
|
|
83b4ab83d1 | ||
|
|
34f63f9006 | ||
|
|
b74188d904 | ||
|
|
ba246e99e8 | ||
|
|
51576b54c3 | ||
|
|
0d5a9fcf4a | ||
|
|
b0c5f74edb | ||
|
|
1ac7661593 | ||
|
|
caa2c523f4 | ||
|
|
7b9f1e6788 | ||
|
|
86f3ed29f9 | ||
|
|
655d411afe | ||
|
|
f5deb8e168 | ||
|
|
fcdb61bfbf | ||
|
|
bd9a4ca094 | ||
|
|
e2657e746d | ||
|
|
6f5a174231 | ||
|
|
91e9c0b02b | ||
|
|
658ea26ab7 | ||
|
|
77b736541e | ||
|
|
843b881da6 | ||
|
|
3ca8acc805 | ||
|
|
63809d4f16 | ||
|
|
557660b16b | ||
|
|
12be8bda52 | ||
|
|
81efd25e0a | ||
|
|
7037009ce4 | ||
|
|
4a0902d4c8 | ||
|
|
ed25ac1748 | ||
|
|
55e09ed062 | ||
|
|
babb548c22 | ||
|
|
1d0e8434ad | ||
|
|
e7b2deb941 | ||
|
|
2a37a6a28e | ||
|
|
735ebbe92a | ||
|
|
2031729656 | ||
|
|
f39fdc38cb | ||
|
|
6b62623615 | ||
|
|
f3f3dc1951 | ||
|
|
f4dd970f5e | ||
|
|
74332c58c5 | ||
|
|
f7ce49020f | ||
|
|
aaba1f7209 | ||
|
|
a9c7916b3d | ||
|
|
e3c8ccfafe | ||
|
|
2a22b65b2d | ||
|
|
ce5728e2df | ||
|
|
cba82e0e8e | ||
|
|
be30064f9a | ||
|
|
1798981380 | ||
|
|
06586d3405 | ||
|
|
31bcbd0e13 | ||
|
|
2652beed88 | ||
|
|
a2b87b862f | ||
|
|
85e8d9479e | ||
|
|
119508a4e3 | ||
|
|
3c2da0a225 | ||
|
|
ef79e6adf9 | ||
|
|
528400579a | ||
|
|
521ce1a487 | ||
|
|
014cc9bc16 | ||
|
|
34c6c953ac | ||
|
|
40fbbc7c58 | ||
|
|
534c329970 | ||
|
|
328d728257 | ||
|
|
fae6d42758 | ||
|
|
27693f6d23 | ||
|
|
89c4bab5b9 | ||
|
|
9d2ff04838 | ||
|
|
cf00243107 | ||
|
|
e8019ba7a5 | ||
|
|
4f30e3f0e3 | ||
|
|
047d08470f | ||
|
|
9643695389 | ||
|
|
5cabec86e7 | ||
|
|
f2d0e37255 | ||
|
|
9560851a3f | ||
|
|
4aeec1caa1 | ||
|
|
ae45b1ef44 | ||
|
|
aad1b087c9 | ||
|
|
b69c581fa1 | ||
|
|
9eb4d90cd6 | ||
|
|
958567c2c5 | ||
|
|
f1fc4583ed | ||
|
|
f7bfd5f766 | ||
|
|
aadc3c216a | ||
|
|
6fd2499daf | ||
|
|
f0405f1c5d | ||
|
|
1914cb3994 | ||
|
|
ab38fb572d | ||
|
|
46d7128a6e | ||
|
|
0821884ff3 | ||
|
|
62233281ef | ||
|
|
39504502af | ||
|
|
f97530a671 | ||
|
|
0fe7d5e598 | ||
|
|
527362dc9f | ||
|
|
1023426c8d | ||
|
|
4458fd7541 | ||
|
|
f0b03f59ff | ||
|
|
d5b17b3cfb | ||
|
|
9664d1d77f | ||
|
|
d329646a52 | ||
|
|
8eafe72bb0 | ||
|
|
c38a0bde65 | ||
|
|
eef5d3cc16 | ||
|
|
59ada594a5 | ||
|
|
5ec1d18143 | ||
|
|
fd1e1bb6bf | ||
|
|
5d545d8a85 | ||
|
|
e38bc45527 | ||
|
|
1b11425acd | ||
|
|
1e67850a80 | ||
|
|
975f89456f | ||
|
|
e267b4b379 | ||
|
|
2fd21690a5 | ||
|
|
b176ad03aa | ||
|
|
152c4802d0 | ||
|
|
8adbc57f23 | ||
|
|
d263a78db8 | ||
|
|
b94a065c06 | ||
|
|
bc957fea71 | ||
|
|
566f056149 | ||
|
|
abe484b0df | ||
|
|
982c865245 | ||
|
|
07d2a27b5f | ||
|
|
e4c65deda8 | ||
|
|
4930646e05 | ||
|
|
01dd1ff56f | ||
|
|
9433213fe5 | ||
|
|
82dea829f1 | ||
|
|
dafc83aa53 | ||
|
|
7ab1cd6612 | ||
|
|
d0e3b3ffbd | ||
|
|
50bcc88700 | ||
|
|
8fb8575f4b | ||
|
|
e63c113b8a | ||
|
|
43d892dbf7 | ||
|
|
d9bf31d4f0 | ||
|
|
0bde282448 | ||
|
|
6098132fdf | ||
|
|
a6bb8947f3 | ||
|
|
48880328b0 | ||
|
|
25776a788e | ||
|
|
f5e8f2831e | ||
|
|
b3fe13c97a | ||
|
|
7845569319 | ||
|
|
69b3178c13 | ||
|
|
1dce0f0366 | ||
|
|
db1b3fdaad | ||
|
|
032d12a592 | ||
|
|
94cc36a08f | ||
|
|
4fcae44fb7 | ||
|
|
83719dfe17 | ||
|
|
c8b01a5530 | ||
|
|
b469d73385 | ||
|
|
69468b1770 | ||
|
|
7e4f66d533 | ||
|
|
758d0d82d4 | ||
|
|
a01b934fdb | ||
|
|
e9b0372174 | ||
|
|
288c02e092 | ||
|
|
33322e8795 | ||
|
|
ec8f6478df | ||
|
|
59d397a3a0 | ||
|
|
b06540f2ec | ||
|
|
419f5458b4 | ||
|
|
ca4e8960d2 | ||
|
|
7b3bed015a | ||
|
|
c97fa3a767 | ||
|
|
42f7c7cb43 | ||
|
|
c8ae834f50 | ||
|
|
b8644268b3 | ||
|
|
75742d8a9e | ||
|
|
6b6169e248 | ||
|
|
3c4ae902fd | ||
|
|
bfc8bf91ee | ||
|
|
768d5cce27 | ||
|
|
d54a1c2754 | ||
|
|
282ad3ab2d | ||
|
|
c692dd3a66 | ||
|
|
e461fce2cb | ||
|
|
4d92caabf8 | ||
|
|
07c07ec60a | ||
|
|
4020213729 | ||
|
|
b9beb4be9d | ||
|
|
f747416d3b | ||
|
|
41241b694f | ||
|
|
26c5c1ac01 | ||
|
|
dedd5b197c | ||
|
|
6340764a6c | ||
|
|
a262c1e98d | ||
|
|
7b9c64c5a5 | ||
|
|
4283404f35 | ||
|
|
f612ce5db8 | ||
|
|
831a20ff91 | ||
|
|
38ae669070 | ||
|
|
fed7e7764e | ||
|
|
f3804fce40 | ||
|
|
88e7cd09d7 | ||
|
|
1ce5571ac6 | ||
|
|
355bf84af4 | ||
|
|
6e8a3a4867 | ||
|
|
bf6dcdf2bd | ||
|
|
165454dc57 | ||
|
|
34b4b9d099 | ||
|
|
0808ec1612 | ||
|
|
6b12549cd9 | ||
|
|
92baf9025a | ||
|
|
d58861ab88 | ||
|
|
142230e56a | ||
|
|
b8959c8f12 | ||
|
|
3fa22fdc55 | ||
|
|
a1c743e59c | ||
|
|
ebd0f45c10 | ||
|
|
fce6c46250 | ||
|
|
6114e4ff55 | ||
|
|
cc3d1ff7de | ||
|
|
2f721c8535 | ||
|
|
ddb790104f | ||
|
|
181dfd2f51 | ||
|
|
fca062f75a | ||
|
|
52b84c3201 | ||
|
|
e1021a6832 | ||
|
|
aa338d2da5 | ||
|
|
44471c4926 | ||
|
|
930c7e4ce5 | ||
|
|
2e64f23ef9 | ||
|
|
801d1fd320 | ||
|
|
cfffb5756e | ||
|
|
b30daf8e56 | ||
|
|
84eba671f0 | ||
|
|
f28b93daa0 | ||
|
|
dc16136c59 | ||
|
|
e47caf8c4f | ||
|
|
8ad80bb75d | ||
|
|
22475e459d | ||
|
|
73ad33dfc9 | ||
|
|
f1782c78f4 | ||
|
|
b544dde2c6 | ||
|
|
1a7dca8e58 | ||
|
|
23c7d7fb46 | ||
|
|
65fda407dc | ||
|
|
12ebad436a | ||
|
|
c7297dba7e | ||
|
|
e61368a26e | ||
|
|
b98da3a5dc | ||
|
|
12f3400894 | ||
|
|
4b5508fc3c | ||
|
|
5f40e6e0b7 | ||
|
|
080d660cfa | ||
|
|
790209efbc | ||
|
|
f1679e444c | ||
|
|
9dc6ba182a | ||
|
|
785a30e623 | ||
|
|
f09efdad66 | ||
|
|
4e73b3ae28 | ||
|
|
6213f86f81 | ||
|
|
0a6955cd59 | ||
|
|
6ee8f03ec0 | ||
|
|
b1a82d95c2 | ||
|
|
30bc9ded29 | ||
|
|
5a32892e94 | ||
|
|
e33de896b6 | ||
|
|
3ba92444a5 | ||
|
|
4f120e8eb4 | ||
|
|
08aebf5736 | ||
|
|
5a4966ba8d | ||
|
|
9ba963fc45 | ||
|
|
ccc10dbae4 | ||
|
|
d858342269 | ||
|
|
63f251d1fd | ||
|
|
83efe66f3e | ||
|
|
3f5729044f | ||
|
|
045af7e8df | ||
|
|
6f181053b2 | ||
|
|
4ff1dc2982 | ||
|
|
a883ba8808 | ||
|
|
961bd6140c | ||
|
|
5f364b48c9 | ||
|
|
5a04911c7c | ||
|
|
8ff248aa62 | ||
|
|
046ac95dc3 | ||
|
|
6fb17a4b74 | ||
|
|
f84397393d | ||
|
|
800c93e2aa | ||
|
|
df6179a1a8 | ||
|
|
3e3a079ba1 | ||
|
|
0bf4a43663 | ||
|
|
5e9f4ad926 | ||
|
|
69050bfe41 | ||
|
|
eebc16bcdd | ||
|
|
d4921535f2 | ||
|
|
b1eeb648a7 | ||
|
|
ba702ba041 | ||
|
|
c40d88d0b6 | ||
|
|
285f91e9e8 | ||
|
|
947a7aa7df | ||
|
|
0b37e36036 | ||
|
|
92320f5086 | ||
|
|
ae17e0c264 | ||
|
|
ec756ee943 | ||
|
|
b0ff814753 | ||
|
|
1f7eb2d48a | ||
|
|
eeab8e4a90 | ||
|
|
77fbddf488 | ||
|
|
bf396f8802 | ||
|
|
13cb8b84bf | ||
|
|
3ab992f5fb | ||
|
|
a386ecfc9c | ||
|
|
a71399c42c | ||
|
|
5a5d7e0981 | ||
|
|
b663a7f52a | ||
|
|
7390039086 | ||
|
|
12af02435f | ||
|
|
6f357828b7 | ||
|
|
09e1a4386d | ||
|
|
dcc8e6dc34 | ||
|
|
1b19ef54e4 | ||
|
|
8ce476caf1 | ||
|
|
dd2d9133eb | ||
|
|
2147d12ac8 | ||
|
|
22641a1da0 | ||
|
|
2bd4088d40 | ||
|
|
ee61363c31 | ||
|
|
8285688ec9 | ||
|
|
4eb076540e | ||
|
|
527bfec24c | ||
|
|
2fff7d7160 | ||
|
|
aac2b4aaf0 | ||
|
|
fe070b306f | ||
|
|
ecfadf8a23 | ||
|
|
653af71596 | ||
|
|
6a89be2e89 | ||
|
|
b84bad4e1a | ||
|
|
0f8d86f081 | ||
|
|
d44f311ba1 | ||
|
|
6f5428ca61 | ||
|
|
b0bee3ca1a | ||
|
|
7908194d27 | ||
|
|
b27c7fb2dd | ||
|
|
a23cb95a10 | ||
|
|
fc137f9b1c | ||
|
|
84780bf9ef | ||
|
|
1321037c52 | ||
|
|
de1da12d45 | ||
|
|
6d4cbacd50 | ||
|
|
bf9f7ca990 | ||
|
|
ca334104d7 | ||
|
|
c3e00ba31b | ||
|
|
ea3a9cc70e | ||
|
|
7ad22e0a73 | ||
|
|
ca1abb58eb | ||
|
|
3e353944b2 | ||
|
|
007a250f55 | ||
|
|
e9bb6a9951 | ||
|
|
82f236e07b | ||
|
|
17bfe74ecf | ||
|
|
b77827df90 | ||
|
|
a359a508ae | ||
|
|
3a740118f0 | ||
|
|
58d9bcbd14 | ||
|
|
24be7b2180 | ||
|
|
f4c9ca8dff | ||
|
|
9b8181b72b | ||
|
|
1ff1962425 | ||
|
|
9b05736746 | ||
|
|
dec722e693 | ||
|
|
49fa0a4c67 | ||
|
|
0cdc69e1e8 | ||
|
|
0494cc4ce7 | ||
|
|
30d82947d6 | ||
|
|
ec59249d79 | ||
|
|
7eb869ab1d | ||
|
|
d014724a2d | ||
|
|
dfb6cef364 | ||
|
|
0ebf8c9349 | ||
|
|
9dfd89c90d | ||
|
|
f6972125cd | ||
|
|
6e366bf55a | ||
|
|
5e2e45c673 | ||
|
|
f879235564 | ||
|
|
b00aee2ae7 | ||
|
|
28c2159ec3 | ||
|
|
33dcef2285 | ||
|
|
b9acd0ec28 | ||
|
|
7989d5180b | ||
|
|
238086942e | ||
|
|
a704a30242 | ||
|
|
26dd0d0ea5 | ||
|
|
e31a4bcaa9 | ||
|
|
1722dc570b | ||
|
|
3575e69b43 | ||
|
|
373d89874c | ||
|
|
976087ce97 | ||
|
|
69a45788ee | ||
|
|
92e4de12fb | ||
|
|
ea7fdb9b3d | ||
|
|
fe1c043b8e | ||
|
|
146f597a0b | ||
|
|
d2bef312ce | ||
|
|
61f297215d | ||
|
|
639a9fd540 | ||
|
|
3d0428c518 | ||
|
|
f40f926ce1 | ||
|
|
d6db4f0e4c | ||
|
|
fcbc9471aa | ||
|
|
b147e05794 | ||
|
|
47e6eb546d | ||
|
|
8ba05cb4ed | ||
|
|
84b91e9649 | ||
|
|
703e17478d | ||
|
|
8584e15c32 | ||
|
|
46be7aadab | ||
|
|
a5871f6cba | ||
|
|
917dc1803c | ||
|
|
c0c0961b2b | ||
|
|
0995ac6f9a | ||
|
|
b7f40e4cbf | ||
|
|
754cbb9eaa | ||
|
|
7052b56069 | ||
|
|
decdee825b | ||
|
|
5f66afc399 | ||
|
|
5ceb638edf | ||
|
|
688b9ef5ee | ||
|
|
4909a557d5 | ||
|
|
75f63afadc | ||
|
|
e90a624c9a | ||
|
|
dd85ccd3f8 | ||
|
|
fc54e19ce2 | ||
|
|
118a920e57 | ||
|
|
975a335538 | ||
|
|
c6ec4f38b0 | ||
|
|
5a5d686be1 | ||
|
|
8fc430d124 | ||
|
|
721eb40a8a | ||
|
|
f18103751f | ||
|
|
9d30bb669e | ||
|
|
ab6f5c21c8 | ||
|
|
4fc2ed32d2 | ||
|
|
00ab4f2a7d | ||
|
|
cb5d8b405b | ||
|
|
7b28aa8500 | ||
|
|
97a369df0a | ||
|
|
73a817c1cb | ||
|
|
c16053f0e5 | ||
|
|
3dfd108fc4 | ||
|
|
576696b25e | ||
|
|
781fab5aab | ||
|
|
0f099a19cd | ||
|
|
43b96ea4e5 | ||
|
|
8cbf6bfffa | ||
|
|
52debcdcb3 | ||
|
|
9d38d89c25 | ||
|
|
837f0ca393 | ||
|
|
42fd5b6eb2 | ||
|
|
7bdf76a665 | ||
|
|
784ef278ea | ||
|
|
9b4bf455b4 | ||
|
|
ddd3739ed9 | ||
|
|
1c95c5861c | ||
|
|
fd6fa1c622 | ||
|
|
6809a77a41 | ||
|
|
7a913ee38f | ||
|
|
cca330829a | ||
|
|
e2f5c76d91 | ||
|
|
26bf7c149c | ||
|
|
020df117c4 | ||
|
|
b48037b757 | ||
|
|
529b6613e9 | ||
|
|
8a5d8cdf8a | ||
|
|
cdf35740d9 | ||
|
|
76c40585fd | ||
|
|
2bf58288d3 | ||
|
|
ba2eac5290 | ||
|
|
23a4e4cc19 | ||
|
|
841a20cbd4 | ||
|
|
f70ca77913 | ||
|
|
c97df99c5c | ||
|
|
809e170018 | ||
|
|
d0c8f5eb45 | ||
|
|
d0f953f1d8 | ||
|
|
6d1636c540 | ||
|
|
b3292729b0 | ||
|
|
11840ffc8a | ||
|
|
9fd20d1dc8 | ||
|
|
ea6799d749 | ||
|
|
e23fe00a37 | ||
|
|
ccda957e8f | ||
|
|
cdaaf00509 | ||
|
|
4dfb0fa213 | ||
|
|
dcda8fc773 | ||
|
|
9c3fff8510 | ||
|
|
c3526ebfa3 | ||
|
|
28ba47a2f5 | ||
|
|
55a8b9085e | ||
|
|
2f250c5569 | ||
|
|
e9d0565e6a | ||
|
|
272de5f34d | ||
|
|
60a319a482 | ||
|
|
9e440fd5e6 | ||
|
|
235f20813d | ||
|
|
55ce8a3e97 | ||
|
|
b7ac15ad55 | ||
|
|
038b64a416 | ||
|
|
ec2fbca979 | ||
|
|
7f28b1d2c9 | ||
|
|
6a0ec7d294 | ||
|
|
217215b035 | ||
|
|
a6fefcacc9 | ||
|
|
03023c04fc | ||
|
|
8dadfe6579 | ||
|
|
6c06de63e4 | ||
|
|
54a122ae34 | ||
|
|
7a0daee443 | ||
|
|
88b5e29092 | ||
|
|
b3ea08dd99 | ||
|
|
95794bc5b2 | ||
|
|
3275454bd6 | ||
|
|
7f5bb86127 | ||
|
|
cf1ae8f240 | ||
|
|
9b2261918f | ||
|
|
f74071691f | ||
|
|
6d5e02860e | ||
|
|
ffa2acb2f5 | ||
|
|
25b4ff771e | ||
|
|
7a48617fea | ||
|
|
800807b47a | ||
|
|
eaf11c6998 | ||
|
|
e2053d5a77 | ||
|
|
ae6b3ef7fb | ||
|
|
359c6e0f02 | ||
|
|
9a8b4593e1 | ||
|
|
02fc631e3c | ||
|
|
19e03a1fdd | ||
|
|
5ea6279711 | ||
|
|
c44578c341 | ||
|
|
ab34d3fe30 | ||
|
|
5aa27c14c6 | ||
|
|
a5e9edb2e5 | ||
|
|
bbd8ef1f26 | ||
|
|
ad8afa0d3d | ||
|
|
5198f30363 | ||
|
|
50e55761ab | ||
|
|
7cb67aa49c | ||
|
|
1d4993180a | ||
|
|
73a36d5556 | ||
|
|
368e489190 | ||
|
|
d582d4846e | ||
|
|
87ffad398e | ||
|
|
439e7eaca0 | ||
|
|
90d703ae91 | ||
|
|
2d80c90f78 | ||
|
|
b54c8e97bd | ||
|
|
58e69a6644 | ||
|
|
f09a13259f | ||
|
|
8775c5e1f2 | ||
|
|
61b7997ae9 | ||
|
|
99fa98844c | ||
|
|
6e5781c633 | ||
|
|
b58e524d78 | ||
|
|
0d669af44e | ||
|
|
a331c70233 | ||
|
|
fd7d688e6b | ||
|
|
069d47f01b | ||
|
|
3079f1c651 | ||
|
|
f5ecda9255 | ||
|
|
d83ae315ed | ||
|
|
2b40e87eb9 | ||
|
|
f7e3eb180b | ||
|
|
209786cdb6 | ||
|
|
d418f79d8f | ||
|
|
eddd38ff03 | ||
|
|
e4fa729071 | ||
|
|
b3e7eac895 | ||
|
|
c43675d987 | ||
|
|
cdaff66126 | ||
|
|
cf45e89b16 | ||
|
|
aec8459761 | ||
|
|
de746375a9 | ||
|
|
25c2871888 | ||
|
|
28b2e1a405 | ||
|
|
2603132dae | ||
|
|
6f08cb0e88 | ||
|
|
dd65c3fcaa | ||
|
|
fec8641efe | ||
|
|
d5eaa56b60 | ||
|
|
db7e8dd6dc | ||
|
|
9415055e6f | ||
|
|
f597f09914 | ||
|
|
7903228c13 | ||
|
|
4c2388749d | ||
|
|
89da3823a4 | ||
|
|
3a9a49b163 | ||
|
|
277b21dc86 | ||
|
|
bc0e8c17ba | ||
|
|
51d3df69bb | ||
|
|
4913f6f37d | ||
|
|
ff622e2ef6 | ||
|
|
50ad643abe | ||
|
|
944849a381 | ||
|
|
6f485141f0 | ||
|
|
3575291119 | ||
|
|
e256ec5401 | ||
|
|
3defba6290 | ||
|
|
c201d986c5 | ||
|
|
36aad4424e | ||
|
|
b29fb1e2a3 | ||
|
|
d5aa307efb | ||
|
|
48928613bf | ||
|
|
52b8f480f6 | ||
|
|
bcda8bd602 | ||
|
|
2a11fe11e8 | ||
|
|
7ec0607fec | ||
|
|
aa41a8675e | ||
|
|
19f9bff6fe | ||
|
|
4e65732a4e | ||
|
|
47c843bf09 | ||
|
|
a7741a0b7d | ||
|
|
6f3fbdfe17 | ||
|
|
3be93df63f | ||
|
|
6259539c87 | ||
|
|
9654009650 | ||
|
|
62e796cf5a | ||
|
|
af820bb0f2 | ||
|
|
8f5bb3591b | ||
|
|
7eafae98d4 | ||
|
|
1d4e5baafb | ||
|
|
9894aa053f | ||
|
|
4ada8fea27 | ||
|
|
4d013a206b | ||
|
|
2d8a707bed | ||
|
|
0e55a859ea | ||
|
|
0b1ec520f4 | ||
|
|
fcc5f29882 | ||
|
|
d431c63245 | ||
|
|
15f8db64e7 | ||
|
|
c2ea14151e | ||
|
|
977f0428e2 | ||
|
|
f110446738 | ||
|
|
90289120d3 | ||
|
|
688f206555 | ||
|
|
38c739c45d | ||
|
|
73c79ca7bf | ||
|
|
978975ee2d | ||
|
|
d7dc0dff50 | ||
|
|
a3aa6c045f | ||
|
|
785ebc49fc | ||
|
|
fde1c98ddd | ||
|
|
024040163a | ||
|
|
e95b5e04c0 | ||
|
|
815826c856 | ||
|
|
1c5cc190c5 | ||
|
|
50b109ca71 | ||
|
|
3aa48a9a3e | ||
|
|
09702b9bde | ||
|
|
35f6cd3d9c | ||
|
|
b62f1786a9 | ||
|
|
b21ad28d5b | ||
|
|
8d66c0d180 | ||
|
|
ec8aa0df18 | ||
|
|
e824d8e91d | ||
|
|
b919422ec4 | ||
|
|
a8bc9ce605 | ||
|
|
e2f399eeb1 | ||
|
|
82924278b5 | ||
|
|
bb828a4e06 | ||
|
|
74933ac146 | ||
|
|
0d315f0dc3 | ||
|
|
372ee346dc | ||
|
|
cb462ab944 | ||
|
|
606da79372 | ||
|
|
0801c3db64 | ||
|
|
35deb0c915 | ||
|
|
fd4d712688 | ||
|
|
354fc618b0 | ||
|
|
82b6ee5ad2 | ||
|
|
645e6c4ec0 | ||
|
|
cdc83bc9d7 | ||
|
|
70feacd276 | ||
|
|
ece4572058 | ||
|
|
4d16a5e801 | ||
|
|
1493a8b03f | ||
|
|
d20791bf73 | ||
|
|
7bb91f57ac | ||
|
|
b7a6004830 | ||
|
|
dee5613f2f | ||
|
|
e58e0d6ac7 | ||
|
|
a2679e009d | ||
|
|
78d6bcd57a | ||
|
|
026936c92f | ||
|
|
5e5893362c | ||
|
|
5b8ce1836b | ||
|
|
d71f3fafcf | ||
|
|
828f93bc5c | ||
|
|
f947c541c9 | ||
|
|
539b6aafd9 | ||
|
|
c3971bda05 | ||
|
|
8f17331db2 | ||
|
|
583e824337 | ||
|
|
ac79466041 | ||
|
|
291fd491ff | ||
|
|
6408d51c85 | ||
|
|
b693873619 | ||
|
|
c2af982272 | ||
|
|
1f0a77bde2 | ||
|
|
0463c1a6e7 | ||
|
|
6517f66876 | ||
|
|
82682d4474 | ||
|
|
e030fae1c4 | ||
|
|
4d7cf140cd | ||
|
|
f6ccee1976 | ||
|
|
d42346d500 | ||
|
|
887dd8da3b | ||
|
|
731db2541d | ||
|
|
bfdc0a2e77 | ||
|
|
03ea24f29b | ||
|
|
61bd48fd1e | ||
|
|
f05b5d7fff | ||
|
|
0b8d66a87e | ||
|
|
c0b7a8c33e | ||
|
|
32a07bceb2 | ||
|
|
ba8f24d398 | ||
|
|
31b7857238 | ||
|
|
fada5f99bb | ||
|
|
734df5def5 | ||
|
|
03ad07733f | ||
|
|
b2d538d02a | ||
|
|
b9435a4b01 | ||
|
|
d27566686c | ||
|
|
ca883c164d | ||
|
|
0f6cd76f28 | ||
|
|
7778af65aa | ||
|
|
828246130b | ||
|
|
4eb1801f50 | ||
|
|
80b68dcf84 | ||
|
|
685baa576b | ||
|
|
7cdead8cb1 | ||
|
|
f220825b80 | ||
|
|
2b00490dd6 | ||
|
|
d361afa3f3 | ||
|
|
8d5bd5a80c | ||
|
|
002283899f | ||
|
|
206b440966 | ||
|
|
b5b5eb25a1 | ||
|
|
f84e538b6a | ||
|
|
5b9cdec156 | ||
|
|
cf0bd7f1e6 | ||
|
|
f3a86a2173 | ||
|
|
0a1d4d5193 | ||
|
|
88e2288298 | ||
|
|
fec47d4b72 | ||
|
|
f7f23e7980 | ||
|
|
32100db3fc | ||
|
|
dd2bed49f1 | ||
|
|
6a427a2158 | ||
|
|
716029def8 | ||
|
|
610a610ee0 | ||
|
|
4fc0738dad | ||
|
|
fe29771870 | ||
|
|
19be43d2d9 | ||
|
|
cb01809093 | ||
|
|
1138d660b2 | ||
|
|
c29ccf76d5 | ||
|
|
5907bf388c | ||
|
|
54adaffb92 | ||
|
|
3bc7812e1a | ||
|
|
16cd9706f9 | ||
|
|
839d07870c | ||
|
|
933d3c2678 | ||
|
|
a29b744526 | ||
|
|
c4070b26ff | ||
|
|
6d0b4e8c75 | ||
|
|
058e1aafd1 | ||
|
|
eac7361a0b | ||
|
|
64e6d6d038 | ||
|
|
d613fe79af | ||
|
|
565a7b1680 | ||
|
|
5076f45674 | ||
|
|
dc72c949c3 | ||
|
|
c85e418afa | ||
|
|
2fc3e846df | ||
|
|
a665dc9db7 | ||
|
|
672eb4b365 | ||
|
|
c9a150565d | ||
|
|
5b271108c0 | ||
|
|
8b63a19161 | ||
|
|
0d17a112a6 | ||
|
|
d997633810 | ||
|
|
446e981d0e | ||
|
|
dabca02b18 | ||
|
|
790bd1217c | ||
|
|
43d8515e23 | ||
|
|
bcd051e5a3 | ||
|
|
1a1e08753f | ||
|
|
d46838eb55 | ||
|
|
a70b5eeef5 | ||
|
|
7513ff860b | ||
|
|
4d872e9143 | ||
|
|
4408d67c49 | ||
|
|
545e1c5402 | ||
|
|
03dec5b604 | ||
|
|
777427787a | ||
|
|
f0c59e38cc | ||
|
|
35cbc54f99 | ||
|
|
bb78af8489 | ||
|
|
af000a27a6 | ||
|
|
b30191feb2 | ||
|
|
83804bd914 | ||
|
|
23b0f154f3 | ||
|
|
6084173257 | ||
|
|
7885893e9b | ||
|
|
53f9fee834 | ||
|
|
ae79d8c608 | ||
|
|
9c96feb4b5 | ||
|
|
597531b92b | ||
|
|
108ab7069b | ||
|
|
63411c5978 | ||
|
|
e6fc9e4783 | ||
|
|
57d5e02d6a | ||
|
|
e578c75545 | ||
|
|
77c5f6c583 | ||
|
|
d8ee3c3bba | ||
|
|
656451f604 |
58
.github/ISSUE_TEMPLATE/01_bug_report.yml
vendored
Normal file
58
.github/ISSUE_TEMPLATE/01_bug_report.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Bug 报告
|
||||
description: 在提出问题前请先自行排除服务器端问题和升级到最新客户端,同时也请通过搜索确认是否有人提出过相同问题。
|
||||
title: "[Bug]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: input
|
||||
id: "expectation"
|
||||
attributes:
|
||||
label: "预期情况"
|
||||
description: "描述你认为应该发生什么"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "describe-the-bug"
|
||||
attributes:
|
||||
label: "实际情况"
|
||||
description: "描述实际发生了什么"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "reproduction-method"
|
||||
attributes:
|
||||
label: "复现方法"
|
||||
description: "在BUG出现前执行了哪些操作"
|
||||
placeholder: 标序号
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "log"
|
||||
attributes:
|
||||
label: "日志信息"
|
||||
description: "位置在软件当前目录下的guiLogs"
|
||||
placeholder: 在日志开始和结束位置粘贴冒号后的内容:```
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "more"
|
||||
attributes:
|
||||
label: "额外信息"
|
||||
description: "可选"
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: "latest-version"
|
||||
attributes:
|
||||
label: "我确认已更新至最新版本"
|
||||
description: "否则请更新后尝试"
|
||||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: "issues"
|
||||
attributes:
|
||||
label: "我确认已查询历史issues"
|
||||
description: "否则请查询后提出"
|
||||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
34
.github/ISSUE_TEMPLATE/02_feature_request.yml
vendored
Normal file
34
.github/ISSUE_TEMPLATE/02_feature_request.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Feature 请求
|
||||
description: "为这个项目提出一个建议"
|
||||
title: "[Feature request]: "
|
||||
labels: ['enhancement']
|
||||
body:
|
||||
- type: input
|
||||
id: problem
|
||||
attributes:
|
||||
label: 相关问题
|
||||
description: "清楚而简洁地描述问题是什么。"
|
||||
placeholder: "当我想要……时,软件不能……"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: way-to-solve
|
||||
attributes:
|
||||
label: 描述你希望的解决方案
|
||||
description: "你希望发生什么"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: instead
|
||||
attributes:
|
||||
label: 描述你所考虑的替代方案
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: "issues"
|
||||
attributes:
|
||||
label: "我确认已查询历史issues"
|
||||
description: "否则请查询后提出"
|
||||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
25
.github/issue_template.md
vendored
25
.github/issue_template.md
vendored
@@ -1,25 +0,0 @@
|
||||
在提出问题前请先自行排除服务器端问题和升级到最新客户端,同时也请通过搜索确认是否有人提出过相同问题。
|
||||
|
||||
### 预期行为
|
||||
描述你认为应该发生什么
|
||||
|
||||
### 实际行为
|
||||
描述实际发生了什么
|
||||
|
||||
### 复现方法
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### 日志信息,位置在当前目录下的guiLogs
|
||||
<details>
|
||||
|
||||
```
|
||||
在这里粘贴日志
|
||||
```
|
||||
</details>
|
||||
|
||||
### 环境信息(客户端请升级至最新正式版)
|
||||
|
||||
### 额外信息(可选)
|
||||
|
||||
60
.github/workflows/build.yml
vendored
Normal file
60
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Release]
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# - name: 删除工作流运行
|
||||
# uses: Mattraks/delete-workflow-runs@v2
|
||||
# with:
|
||||
# token: ${{ github.token }}
|
||||
# repository: ${{ github.repository }}
|
||||
# retain_days: 0
|
||||
# keep_minimum_runs: 1
|
||||
|
||||
- name: Build
|
||||
run: cd v2rayN &&
|
||||
.\build.ps1
|
||||
|
||||
# - name: Package
|
||||
# shell: pwsh
|
||||
# run: |
|
||||
# 7z a -mx9 ..\v2rayN.7z $env:Wap_Project_Directory
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: v2rayN
|
||||
path: |
|
||||
.\v2rayN\v2rayN.zip
|
||||
|
||||
# - name: Release
|
||||
# uses: softprops/action-gh-release@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
# with:
|
||||
# prerelease: ${{ contains(github.ref, '-') }}
|
||||
# draft: false
|
||||
# files: |
|
||||
# .\v2rayN\v2rayN.zip
|
||||
# body: |
|
||||
# [](https://t.me/netch_channel) [](https://t.me/netch_group)
|
||||
# ## Changelogs
|
||||
# * This is an automated deployment of GitHub Actions, the change log should be updated manually soon
|
||||
|
||||
# ## 更新日志
|
||||
# * 这是 GitHub Actions 自动化部署,更新日志应该很快会手动更新
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -13,6 +13,7 @@
|
||||
.vs/slnx.sqlite
|
||||
.vs/VSWorkspaceState.json
|
||||
/v2rayN/v2rayUpgrade/bin/Debug
|
||||
/v2rayN/v2rayUpgrade/obj/Debug
|
||||
/v2rayN/v2rayUpgrade/bin/Release
|
||||
/v2rayN/v2rayUpgrade/obj/Release
|
||||
/v2rayN/v2rayUpgrade/obj/
|
||||
*.user
|
||||
/.vs/v2rayN
|
||||
|
||||
25
README.md
25
README.md
@@ -1,10 +1,23 @@
|
||||
# v2rayN
|
||||
A GUI client for Windows, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core) and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
|
||||
### How to use
|
||||
- If you are newbie please download v2rayN-Core.zip from releases
|
||||
- Otherwise please download v2rayN.zip (Also need to download v2ray core in the same folder)
|
||||
|
||||
[](https://github.com/2dust/v2rayN/commits/master)
|
||||
[](https://www.codefactor.io/repository/github/2dust/v2rayn)
|
||||
[](https://github.com/2dust/v2rayN/releases)
|
||||
[](https://t.me/v2rayn)
|
||||
|
||||
|
||||
## How to use
|
||||
- If you are new to this, please download v2rayN-With-Core.zip from [releases](https://github.com/2dust/v2rayN/releases)
|
||||
- Otherwise please download v2rayN.zip (you will also need to download cores in the bin directory)
|
||||
- Run v2rayN.exe
|
||||
|
||||
### Requirements
|
||||
- Microsoft [.NET Framework 4.6](https://docs.microsoft.com/zh-cn/dotnet/framework/install/guide-for-developers) or higher
|
||||
- Project V core [https://github.com/v2fly/v2ray-core/releases](https://github.com/v2fly/v2ray-core/releases)
|
||||
## Requirements
|
||||
- (6.35 and above)[Microsoft .NET 8.0 Desktop Runtime ](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
- (6.33 and below)[Microsoft .NET 6.0 Desktop Runtime ](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
|
||||
- [Supported cores](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
|
||||
|
||||
## Telegram Channel
|
||||
[github_2dust](https://t.me/github_2dust)
|
||||
|
||||
63
v2rayN/.gitattributes
vendored
Normal file
63
v2rayN/.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
363
v2rayN/.gitignore
vendored
Normal file
363
v2rayN/.gitignore
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
102
v2rayN/PacLib/PacHandler.cs
Normal file
102
v2rayN/PacLib/PacHandler.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PacLib;
|
||||
|
||||
public class PacHandler
|
||||
{
|
||||
private static string _configPath;
|
||||
private static int _httpPort;
|
||||
private static int _pacPort;
|
||||
private static TcpListener? _tcpListener;
|
||||
private static string _pacText;
|
||||
private static bool _isRunning;
|
||||
private static bool _needRestart = true;
|
||||
|
||||
public static void Start(string configPath, int httpPort, int pacPort)
|
||||
{
|
||||
_needRestart = (configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning);
|
||||
|
||||
_configPath = configPath;
|
||||
_httpPort = httpPort;
|
||||
_pacPort = pacPort;
|
||||
|
||||
InitText();
|
||||
|
||||
if (_needRestart)
|
||||
{
|
||||
Stop();
|
||||
RunListener();
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitText()
|
||||
{
|
||||
var path = Path.Combine(_configPath, "pac.txt");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
File.AppendAllText(path, Resources.ResourceManager.GetString("pac"));
|
||||
}
|
||||
|
||||
_pacText = File.ReadAllText(path).Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;");
|
||||
}
|
||||
|
||||
private static void RunListener()
|
||||
{
|
||||
_tcpListener = TcpListener.Create(_pacPort);
|
||||
_isRunning = true;
|
||||
_tcpListener.Start();
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
while (_isRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_tcpListener.Pending())
|
||||
{
|
||||
await Task.Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
var client = _tcpListener.AcceptTcpClient();
|
||||
await Task.Run(() =>
|
||||
{
|
||||
var stream = client.GetStream();
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("HTTP/1.0 200 OK");
|
||||
sb.AppendLine("Content-type:application/x-ns-proxy-autoconfig");
|
||||
sb.AppendLine("Connection:close");
|
||||
sb.AppendLine("Content-Length:" + Encoding.UTF8.GetByteCount(_pacText));
|
||||
sb.AppendLine();
|
||||
sb.Append(_pacText);
|
||||
var content = Encoding.UTF8.GetBytes(sb.ToString());
|
||||
stream.Write(content, 0, content.Length);
|
||||
stream.Flush();
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
if (_tcpListener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_isRunning = false;
|
||||
_tcpListener.Stop();
|
||||
_tcpListener = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
v2rayN/PacLib/PacLib.csproj
Normal file
20
v2rayN/PacLib/PacLib.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<EmbeddedResource Update="Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -8,7 +8,7 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace v2rayUpgrade.Properties {
|
||||
namespace PacLib {
|
||||
using System;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace v2rayUpgrade.Properties {
|
||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
@@ -39,7 +39,7 @@ namespace v2rayUpgrade.Properties {
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("v2rayUpgrade.Properties.Resources", typeof(Resources).Assembly);
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PacLib.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
@@ -47,8 +47,8 @@ namespace v2rayUpgrade.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写当前线程的 CurrentUICulture 属性
|
||||
/// 重写当前线程的 CurrentUICulture 属性。
|
||||
/// 重写当前线程的 CurrentUICulture 属性,对
|
||||
/// 使用此强类型资源类的所有资源查找执行重写。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
@@ -59,5 +59,37 @@ namespace v2rayUpgrade.Properties {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 var proxy = '__PROXY__';
|
||||
///var rules = [
|
||||
/// [
|
||||
/// [],
|
||||
/// []
|
||||
/// ],
|
||||
/// [
|
||||
/// [
|
||||
/// "aftygh.gov.tw",
|
||||
/// "aide.gov.tw",
|
||||
/// "aliyun.com",
|
||||
/// "arte.gov.tw",
|
||||
/// "baidu.com",
|
||||
/// "chinaso.com",
|
||||
/// "chinaz.com",
|
||||
/// "chukuang.gov.tw",
|
||||
/// "cycab.gov.tw",
|
||||
/// "dbnsa.gov.tw",
|
||||
/// "df.gov.tw",
|
||||
/// "eastcoast-nsa.gov.tw",
|
||||
/// "erv-nsa.gov.tw",
|
||||
/// "grb.gov.tw",
|
||||
/// "haosou.com",
|
||||
/// [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string pac {
|
||||
get {
|
||||
return ResourceManager.GetString("pac", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
@@ -60,6 +60,7 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
@@ -68,9 +69,10 @@
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
@@ -85,9 +87,10 @@
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
@@ -109,9 +112,13 @@
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="pac" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>Resources\pac.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312</value>
|
||||
</data>
|
||||
</root>
|
||||
6024
v2rayN/PacLib/Resources/pac.txt
Normal file
6024
v2rayN/PacLib/Resources/pac.txt
Normal file
File diff suppressed because it is too large
Load Diff
20
v2rayN/ProtosLib/ProtosLib.csproj
Normal file
20
v2rayN/ProtosLib/ProtosLib.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Statistics.proto" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.28.0" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.65.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.66.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package v2ray.core.app.stats.command;
|
||||
option csharp_namespace = "v2rayN.Protos.Statistics";
|
||||
option csharp_namespace = "ProtosLib.Statistics";
|
||||
|
||||
message GetStatsRequest {
|
||||
// Name of the stat counter.
|
||||
13
v2rayN/ProtosLib/Tests.cs
Normal file
13
v2rayN/ProtosLib/Tests.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using ProtosLib.Statistics;
|
||||
|
||||
namespace ProtosLib
|
||||
{
|
||||
public class Tests
|
||||
{
|
||||
private StatsService.StatsServiceClient client_;
|
||||
|
||||
public Tests()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
v2rayN/ServiceLib/Base/MyReactiveObject.cs
Normal file
11
v2rayN/ServiceLib/Base/MyReactiveObject.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using ReactiveUI;
|
||||
|
||||
namespace ServiceLib.Base
|
||||
{
|
||||
public class MyReactiveObject : ReactiveObject
|
||||
{
|
||||
protected static Config? _config;
|
||||
protected Func<EViewAction, object?, Task<bool>>? _updateView;
|
||||
protected NoticeHandler? _noticeHandler;
|
||||
}
|
||||
}
|
||||
184
v2rayN/ServiceLib/Common/DownloaderHelper.cs
Normal file
184
v2rayN/ServiceLib/Common/DownloaderHelper.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using Downloader;
|
||||
using System.Net;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public class DownloaderHelper
|
||||
{
|
||||
private static readonly Lazy<DownloaderHelper> _instance = new(() => new());
|
||||
public static DownloaderHelper Instance => _instance.Value;
|
||||
|
||||
public async Task<string?> DownloadStringAsync(IWebProxy? webProxy, string url, string? userAgent, int timeout)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(url))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri uri = new(url);
|
||||
//Authorization Header
|
||||
var headers = new WebHeaderCollection();
|
||||
if (!Utils.IsNullOrEmpty(uri.UserInfo))
|
||||
{
|
||||
headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo));
|
||||
}
|
||||
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
Timeout = timeout * 1000,
|
||||
MaxTryAgainOnFailover = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Headers = headers,
|
||||
UserAgent = userAgent,
|
||||
Timeout = timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
};
|
||||
|
||||
using var downloader = new DownloadService(downloadOpt);
|
||||
downloader.DownloadFileCompleted += (sender, value) =>
|
||||
{
|
||||
if (value.Error != null)
|
||||
{
|
||||
throw value.Error;
|
||||
}
|
||||
};
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
|
||||
using StreamReader reader = new(stream);
|
||||
|
||||
downloadOpt = null;
|
||||
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
public async Task DownloadDataAsync4Speed(IWebProxy webProxy, string url, IProgress<string> progress, int timeout)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(url))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
}
|
||||
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
Timeout = timeout * 1000,
|
||||
MaxTryAgainOnFailover = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Timeout= timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
};
|
||||
|
||||
DateTime totalDatetime = DateTime.Now;
|
||||
int totalSecond = 0;
|
||||
var hasValue = false;
|
||||
double maxSpeed = 0;
|
||||
using var downloader = new DownloadService(downloadOpt);
|
||||
//downloader.DownloadStarted += (sender, value) =>
|
||||
//{
|
||||
// if (progress != null)
|
||||
// {
|
||||
// progress.Report("Start download data...");
|
||||
// }
|
||||
//};
|
||||
downloader.DownloadProgressChanged += (sender, value) =>
|
||||
{
|
||||
TimeSpan ts = (DateTime.Now - totalDatetime);
|
||||
if (progress != null && ts.Seconds > totalSecond)
|
||||
{
|
||||
hasValue = true;
|
||||
totalSecond = ts.Seconds;
|
||||
if (value.BytesPerSecondSpeed > maxSpeed)
|
||||
{
|
||||
maxSpeed = value.BytesPerSecondSpeed;
|
||||
var speed = (maxSpeed / 1000 / 1000).ToString("#0.0");
|
||||
progress.Report(speed);
|
||||
}
|
||||
}
|
||||
};
|
||||
downloader.DownloadFileCompleted += (sender, value) =>
|
||||
{
|
||||
if (progress != null)
|
||||
{
|
||||
if (!hasValue && value.Error != null)
|
||||
{
|
||||
progress.Report(value.Error?.Message);
|
||||
}
|
||||
}
|
||||
};
|
||||
//progress.Report("......");
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(timeout * 1000);
|
||||
using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
||||
|
||||
downloadOpt = null;
|
||||
}
|
||||
|
||||
public async Task DownloadFileAsync(IWebProxy? webProxy, string url, string fileName, IProgress<double> progress, int timeout)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(url))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileName));
|
||||
}
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
Timeout = timeout * 1000,
|
||||
MaxTryAgainOnFailover = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Timeout= timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
};
|
||||
|
||||
var progressPercentage = 0;
|
||||
var hasValue = false;
|
||||
using var downloader = new DownloadService(downloadOpt);
|
||||
downloader.DownloadStarted += (sender, value) =>
|
||||
{
|
||||
progress?.Report(0);
|
||||
};
|
||||
downloader.DownloadProgressChanged += (sender, value) =>
|
||||
{
|
||||
hasValue = true;
|
||||
var percent = (int)value.ProgressPercentage;// Convert.ToInt32((totalRead * 1d) / (total * 1d) * 100);
|
||||
if (progressPercentage != percent && percent % 10 == 0)
|
||||
{
|
||||
progressPercentage = percent;
|
||||
progress.Report(percent);
|
||||
}
|
||||
};
|
||||
downloader.DownloadFileCompleted += (sender, value) =>
|
||||
{
|
||||
if (progress != null)
|
||||
{
|
||||
if (hasValue && value.Error == null)
|
||||
{
|
||||
progress.Report(101);
|
||||
}
|
||||
else if (value.Error != null)
|
||||
{
|
||||
throw value.Error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
await downloader.DownloadFileTaskAsync(url, fileName, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
|
||||
|
||||
downloadOpt = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
120
v2rayN/ServiceLib/Common/FileManager.cs
Normal file
120
v2rayN/ServiceLib/Common/FileManager.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public static class FileManager
|
||||
{
|
||||
public static bool ByteArrayToFile(string fileName, byte[] content)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(fileName, content);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void UncompressedFile(string fileName, byte[] content)
|
||||
{
|
||||
try
|
||||
{
|
||||
using FileStream fs = File.Create(fileName);
|
||||
using GZipStream input = new(new MemoryStream(content), CompressionMode.Decompress, false);
|
||||
input.CopyTo(fs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UncompressedFile(string fileName, string toPath, string? toName)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileInfo fileInfo = new(fileName);
|
||||
using FileStream originalFileStream = fileInfo.OpenRead();
|
||||
using FileStream decompressedFileStream = File.Create(toName != null ? Path.Combine(toPath, toName) : toPath);
|
||||
using GZipStream decompressionStream = new(originalFileStream, CompressionMode.Decompress);
|
||||
decompressionStream.CopyTo(decompressedFileStream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static string NonExclusiveReadAllText(string path)
|
||||
{
|
||||
return NonExclusiveReadAllText(path, Encoding.Default);
|
||||
}
|
||||
|
||||
public static string NonExclusiveReadAllText(string path, Encoding encoding)
|
||||
{
|
||||
try
|
||||
{
|
||||
using FileStream fs = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using StreamReader sr = new(fs, encoding);
|
||||
return sr.ReadToEnd();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ZipExtractToFile(string fileName, string toPath, string ignoredName)
|
||||
{
|
||||
try
|
||||
{
|
||||
using ZipArchive archive = ZipFile.OpenRead(fileName);
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
if (entry.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(ignoredName) && entry.Name.Contains(ignoredName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
entry.ExtractToFile(Path.Combine(toPath, entry.Name), true);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
186
v2rayN/ServiceLib/Common/HttpClientHelper.cs
Normal file
186
v2rayN/ServiceLib/Common/HttpClientHelper.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public class HttpClientHelper
|
||||
{
|
||||
private static readonly Lazy<HttpClientHelper> _instance = new(() =>
|
||||
{
|
||||
HttpClientHandler handler = new() { UseCookies = false };
|
||||
HttpClientHelper helper = new(new HttpClient(handler));
|
||||
return helper;
|
||||
});
|
||||
|
||||
public static HttpClientHelper Instance => _instance.Value;
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
private HttpClientHelper(HttpClient httpClient) => this.httpClient = httpClient;
|
||||
|
||||
public async Task<string?> TryGetAsync(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await httpClient.GetAsync(url);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> GetAsync(string url)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(url)) return null;
|
||||
return await httpClient.GetStringAsync(url);
|
||||
}
|
||||
|
||||
public async Task<string?> GetAsync(HttpClient client, string url, CancellationToken token = default)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(url)) return null;
|
||||
return await client.GetStringAsync(url, token);
|
||||
}
|
||||
|
||||
public async Task PutAsync(string url, Dictionary<string, string> headers)
|
||||
{
|
||||
var jsonContent = JsonUtils.Serialize(headers);
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var result = await httpClient.PutAsync(url, content);
|
||||
}
|
||||
|
||||
public async Task PatchAsync(string url, Dictionary<string, string> headers)
|
||||
{
|
||||
var myContent = JsonUtils.Serialize(headers);
|
||||
var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
|
||||
var byteContent = new ByteArrayContent(buffer);
|
||||
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
|
||||
await httpClient.PatchAsync(url, byteContent);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string url)
|
||||
{
|
||||
await httpClient.DeleteAsync(url);
|
||||
}
|
||||
|
||||
public static async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress<double>? progress, CancellationToken token = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(url);
|
||||
ArgumentNullException.ThrowIfNull(fileName);
|
||||
if (File.Exists(fileName)) File.Delete(fileName);
|
||||
|
||||
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
||||
|
||||
if (!response.IsSuccessStatusCode) throw new Exception(response.StatusCode.ToString());
|
||||
|
||||
var total = response.Content.Headers.ContentLength ?? -1L;
|
||||
var canReportProgress = total != -1 && progress != null;
|
||||
|
||||
using var stream = await response.Content.ReadAsStreamAsync(token);
|
||||
using var file = File.Create(fileName);
|
||||
var totalRead = 0L;
|
||||
var buffer = new byte[1024 * 1024];
|
||||
var progressPercentage = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var read = await stream.ReadAsync(buffer, token);
|
||||
totalRead += read;
|
||||
|
||||
if (read == 0) break;
|
||||
file.Write(buffer, 0, read);
|
||||
|
||||
if (canReportProgress)
|
||||
{
|
||||
var percent = (int)(100.0 * totalRead / total);
|
||||
//if (progressPercentage != percent && percent % 10 == 0)
|
||||
{
|
||||
progressPercentage = percent;
|
||||
progress?.Report(percent);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canReportProgress)
|
||||
{
|
||||
progress?.Report(101);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DownloadDataAsync4Speed(HttpClient client, string url, IProgress<string> progress, CancellationToken token = default)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(url))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
}
|
||||
|
||||
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new Exception(response.StatusCode.ToString());
|
||||
}
|
||||
|
||||
//var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
|
||||
//var canReportProgress = total != -1 && progress != null;
|
||||
|
||||
using var stream = await response.Content.ReadAsStreamAsync(token);
|
||||
var totalRead = 0L;
|
||||
var buffer = new byte[1024 * 64];
|
||||
var isMoreToRead = true;
|
||||
string progressSpeed = string.Empty;
|
||||
DateTime totalDatetime = DateTime.Now;
|
||||
int totalSecond = 0;
|
||||
|
||||
do
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
if (totalRead > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
|
||||
var read = await stream.ReadAsync(buffer, token);
|
||||
|
||||
if (read == 0)
|
||||
{
|
||||
isMoreToRead = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = new byte[read];
|
||||
buffer.ToList().CopyTo(0, data, 0, read);
|
||||
|
||||
totalRead += read;
|
||||
|
||||
TimeSpan ts = (DateTime.Now - totalDatetime);
|
||||
if (progress != null && ts.Seconds > totalSecond)
|
||||
{
|
||||
totalSecond = ts.Seconds;
|
||||
var speed = (totalRead * 1d / ts.TotalMilliseconds / 1000).ToString("#0.0");
|
||||
if (progressSpeed != speed)
|
||||
{
|
||||
progressSpeed = speed;
|
||||
progress.Report(speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (isMoreToRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
namespace v2rayN
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
/*
|
||||
* See:
|
||||
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
|
||||
*/
|
||||
|
||||
public class Job : IDisposable
|
||||
{
|
||||
private IntPtr handle = IntPtr.Zero;
|
||||
@@ -17,12 +16,12 @@ namespace v2rayN
|
||||
{
|
||||
handle = CreateJobObject(IntPtr.Zero, null);
|
||||
IntPtr extendedInfoPtr = IntPtr.Zero;
|
||||
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new()
|
||||
{
|
||||
LimitFlags = 0x2000
|
||||
};
|
||||
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new()
|
||||
{
|
||||
BasicLimitInformation = info
|
||||
};
|
||||
@@ -34,7 +33,7 @@ namespace v2rayN
|
||||
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
|
||||
|
||||
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
|
||||
(uint) length))
|
||||
(uint)length))
|
||||
throw new Exception(string.Format("Unable to set information. Error: {0}",
|
||||
Marshal.GetLastWin32Error()));
|
||||
}
|
||||
@@ -53,7 +52,7 @@ namespace v2rayN
|
||||
|
||||
if (!succ)
|
||||
{
|
||||
Utils.SaveLog("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error());
|
||||
Logging.SaveLog("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
return succ;
|
||||
@@ -96,12 +95,12 @@ namespace v2rayN
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion IDisposable
|
||||
|
||||
#region Interop
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr CreateJobObject(IntPtr a, string lpName);
|
||||
private static extern IntPtr CreateJobObject(IntPtr a, string? lpName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);
|
||||
@@ -113,13 +112,13 @@ namespace v2rayN
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
#endregion
|
||||
#endregion Interop
|
||||
}
|
||||
|
||||
#region Helper classes
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct IO_COUNTERS
|
||||
internal struct IO_COUNTERS
|
||||
{
|
||||
public UInt64 ReadOperationCount;
|
||||
public UInt64 WriteOperationCount;
|
||||
@@ -129,9 +128,8 @@ namespace v2rayN
|
||||
public UInt64 OtherTransferCount;
|
||||
}
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||
{
|
||||
public Int64 PerProcessUserTimeLimit;
|
||||
public Int64 PerJobUserTimeLimit;
|
||||
@@ -153,7 +151,7 @@ namespace v2rayN
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
||||
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
||||
{
|
||||
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
||||
public IO_COUNTERS IoInfo;
|
||||
@@ -174,5 +172,5 @@ namespace v2rayN
|
||||
GroupInformation = 11
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion Helper classes
|
||||
}
|
||||
133
v2rayN/ServiceLib/Common/JsonUtils.cs
Normal file
133
v2rayN/ServiceLib/Common/JsonUtils.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public class JsonUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// DeepCopy
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static T DeepCopy<T>(T obj)
|
||||
{
|
||||
return Deserialize<T>(Serialize(obj, false))!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize to object
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="strJson"></param>
|
||||
/// <returns></returns>
|
||||
public static T? Deserialize<T>(string? strJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(strJson))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return JsonSerializer.Deserialize<T>(strJson);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// parse
|
||||
/// </summary>
|
||||
/// <param name="strJson"></param>
|
||||
/// <returns></returns>
|
||||
public static JsonNode? ParseJson(string strJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(strJson))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonNode.Parse(strJson);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//SaveLog(ex.Message, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize Object to Json string
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="indented"></param>
|
||||
/// <returns></returns>
|
||||
public static string Serialize(object? obj, bool indented = true)
|
||||
{
|
||||
string result = string.Empty;
|
||||
try
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = indented ? true : false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
result = JsonSerializer.Serialize(obj, options);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SerializeToNode
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static JsonNode? SerializeToNode(object? obj) => JsonSerializer.SerializeToNode(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Save as json file
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="nullValue"></param>
|
||||
/// <returns></returns>
|
||||
public static int ToFile(object? obj, string? filePath, bool nullValue = true)
|
||||
{
|
||||
if (filePath is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
try
|
||||
{
|
||||
using FileStream file = File.Create(filePath);
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
JsonSerializer.Serialize(file, obj, options);
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
v2rayN/ServiceLib/Common/Logging.cs
Normal file
77
v2rayN/ServiceLib/Common/Logging.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public class Logging
|
||||
{
|
||||
public static void Setup()
|
||||
{
|
||||
LoggingConfiguration config = new();
|
||||
FileTarget fileTarget = new();
|
||||
config.AddTarget("file", fileTarget);
|
||||
fileTarget.Layout = "${longdate}-${level:uppercase=true} ${message}";
|
||||
fileTarget.FileName = Utils.GetLogPath("${shortdate}.txt");
|
||||
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
|
||||
LogManager.Configuration = config;
|
||||
}
|
||||
|
||||
public static void LoggingEnabled(bool enable)
|
||||
{
|
||||
if (!enable)
|
||||
{
|
||||
LogManager.SuspendLogging();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearLogs()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.Now.AddMonths(-1);
|
||||
var dir = Utils.GetLogPath();
|
||||
var files = Directory.GetFiles(dir, "*.txt");
|
||||
foreach (var filePath in files)
|
||||
{
|
||||
var file = new FileInfo(filePath);
|
||||
if (file.CreationTime < now)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
}
|
||||
|
||||
public static void SaveLog(string strContent)
|
||||
{
|
||||
if (LogManager.IsLoggingEnabled())
|
||||
{
|
||||
var logger = LogManager.GetLogger("Log1");
|
||||
logger.Info(strContent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveLog(string strTitle, Exception ex)
|
||||
{
|
||||
if (LogManager.IsLoggingEnabled())
|
||||
{
|
||||
var logger = LogManager.GetLogger("Log2");
|
||||
logger.Debug($"{strTitle},{ex.Message}");
|
||||
logger.Debug(ex.StackTrace);
|
||||
if (ex?.InnerException != null)
|
||||
{
|
||||
logger.Error(ex.InnerException);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
v2rayN/ServiceLib/Common/QRCodeHelper.cs
Normal file
15
v2rayN/ServiceLib/Common/QRCodeHelper.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using QRCoder;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public class QRCodeHelper
|
||||
{
|
||||
public static byte[]? GenQRCode(string? url)
|
||||
{
|
||||
using QRCodeGenerator qrGenerator = new();
|
||||
using QRCodeData qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q);
|
||||
using PngByteQRCode qrCode = new(qrCodeData);
|
||||
return qrCode.GetGraphic(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace v2rayN.Tool
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public static class QueryableExtension
|
||||
{
|
||||
@@ -11,12 +9,13 @@ namespace v2rayN.Tool
|
||||
{
|
||||
return _OrderBy<T>(query, propertyName, false);
|
||||
}
|
||||
|
||||
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName)
|
||||
{
|
||||
return _OrderBy<T>(query, propertyName, true);
|
||||
}
|
||||
|
||||
static IOrderedQueryable<T> _OrderBy<T>(IQueryable<T> query, string propertyName, bool isDesc)
|
||||
private static IOrderedQueryable<T> _OrderBy<T>(IQueryable<T> query, string propertyName, bool isDesc)
|
||||
{
|
||||
string methodname = (isDesc) ? "OrderByDescendingInternal" : "OrderByInternal";
|
||||
|
||||
@@ -27,22 +26,25 @@ namespace v2rayN.Tool
|
||||
|
||||
return (IOrderedQueryable<T>)method.Invoke(null, new object[] { query, memberProp });
|
||||
}
|
||||
|
||||
public static IOrderedQueryable<T> OrderByInternal<T, TProp>(IQueryable<T> query, PropertyInfo memberProperty)
|
||||
{//public
|
||||
return query.OrderBy(_GetLamba<T, TProp>(memberProperty));
|
||||
return query.OrderBy(_GetLambda<T, TProp>(memberProperty));
|
||||
}
|
||||
|
||||
public static IOrderedQueryable<T> OrderByDescendingInternal<T, TProp>(IQueryable<T> query, PropertyInfo memberProperty)
|
||||
{//public
|
||||
return query.OrderByDescending(_GetLamba<T, TProp>(memberProperty));
|
||||
return query.OrderByDescending(_GetLambda<T, TProp>(memberProperty));
|
||||
}
|
||||
static Expression<Func<T, TProp>> _GetLamba<T, TProp>(PropertyInfo memberProperty)
|
||||
|
||||
private static Expression<Func<T, TProp>> _GetLambda<T, TProp>(PropertyInfo memberProperty)
|
||||
{
|
||||
if (memberProperty.PropertyType != typeof(TProp)) throw new Exception();
|
||||
|
||||
var thisArg = Expression.Parameter(typeof(T));
|
||||
var lamba = Expression.Lambda<Func<T, TProp>>(Expression.Property(thisArg, memberProperty), thisArg);
|
||||
var lambda = Expression.Lambda<Func<T, TProp>>(Expression.Property(thisArg, memberProperty), thisArg);
|
||||
|
||||
return lamba;
|
||||
return lambda;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
180
v2rayN/ServiceLib/Common/SemanticVersion.cs
Normal file
180
v2rayN/ServiceLib/Common/SemanticVersion.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public class SemanticVersion
|
||||
{
|
||||
private int major;
|
||||
private int minor;
|
||||
private int patch;
|
||||
private string version;
|
||||
|
||||
public SemanticVersion(int major, int minor, int patch)
|
||||
{
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.patch = patch;
|
||||
this.version = $"{major}.{minor}.{patch}";
|
||||
}
|
||||
|
||||
public SemanticVersion(string version)
|
||||
{
|
||||
this.version = version.RemovePrefix('v');
|
||||
try
|
||||
{
|
||||
string[] parts = this.version.Split('.');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
this.major = int.Parse(parts[0]);
|
||||
this.minor = int.Parse(parts[1]);
|
||||
this.patch = 0;
|
||||
}
|
||||
else if (parts.Length == 3)
|
||||
{
|
||||
this.major = int.Parse(parts[0]);
|
||||
this.minor = int.Parse(parts[1]);
|
||||
this.patch = int.Parse(parts[2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid version string");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.major = 0;
|
||||
this.minor = 0;
|
||||
this.patch = 0;
|
||||
//this.version = "0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is SemanticVersion other)
|
||||
{
|
||||
return this.major == other.major && this.minor == other.minor && this.patch == other.patch;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.major.GetHashCode() ^ this.minor.GetHashCode() ^ this.patch.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use ToVersionString(string? prefix) instead if possible.
|
||||
/// </summary>
|
||||
/// <returns>major.minor.patch</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public string ToVersionString(string? prefix = null)
|
||||
{
|
||||
if (prefix == null)
|
||||
{
|
||||
return this.version;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{prefix}{this.version}";
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(SemanticVersion v1, SemanticVersion v2)
|
||||
{ return v1.Equals(v2); }
|
||||
|
||||
public static bool operator !=(SemanticVersion v1, SemanticVersion v2)
|
||||
{ return !v1.Equals(v2); }
|
||||
|
||||
public static bool operator >=(SemanticVersion v1, SemanticVersion v2)
|
||||
{ return v1.GreaterEquals(v2); }
|
||||
|
||||
public static bool operator <=(SemanticVersion v1, SemanticVersion v2)
|
||||
{ return v1.LessEquals(v2); }
|
||||
|
||||
#region Private
|
||||
|
||||
private bool GreaterEquals(SemanticVersion other)
|
||||
{
|
||||
if (this.major < other.major)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (this.major > other.major)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.minor < other.minor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (this.minor > other.minor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.patch < other.patch)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (this.patch > other.patch)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool LessEquals(SemanticVersion other)
|
||||
{
|
||||
if (this.major < other.major)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (this.major > other.major)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.minor < other.minor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (this.minor > other.minor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.patch < other.patch)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (this.patch > other.patch)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private
|
||||
}
|
||||
}
|
||||
123
v2rayN/ServiceLib/Common/SqliteHelper.cs
Normal file
123
v2rayN/ServiceLib/Common/SqliteHelper.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using SQLite;
|
||||
using System.Collections;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public sealed class SQLiteHelper
|
||||
{
|
||||
private static readonly Lazy<SQLiteHelper> _instance = new(() => new());
|
||||
public static SQLiteHelper Instance => _instance.Value;
|
||||
private string _connstr;
|
||||
private SQLiteConnection _db;
|
||||
private SQLiteAsyncConnection _dbAsync;
|
||||
private static readonly object objLock = new();
|
||||
public readonly string _configDB = "guiNDB.db";
|
||||
|
||||
public SQLiteHelper()
|
||||
{
|
||||
_connstr = Utils.GetConfigPath(_configDB);
|
||||
_db = new SQLiteConnection(_connstr, false);
|
||||
_dbAsync = new SQLiteAsyncConnection(_connstr, false);
|
||||
}
|
||||
|
||||
public CreateTableResult CreateTable<T>()
|
||||
{
|
||||
return _db.CreateTable<T>();
|
||||
}
|
||||
|
||||
public int Insert(object model)
|
||||
{
|
||||
return _db.Insert(model);
|
||||
}
|
||||
|
||||
public int InsertAll(IEnumerable models)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.InsertAll(models);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> InsertAsync(object model)
|
||||
{
|
||||
return await _dbAsync.InsertAsync(model);
|
||||
}
|
||||
|
||||
public int Replace(object model)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.InsertOrReplace(model);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> ReplaceAsync(object model)
|
||||
{
|
||||
return await _dbAsync.InsertOrReplaceAsync(model);
|
||||
}
|
||||
|
||||
public int Update(object model)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.Update(model);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> UpdateAsync(object model)
|
||||
{
|
||||
return await _dbAsync.UpdateAsync(model);
|
||||
}
|
||||
|
||||
public int UpdateAll(IEnumerable models)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.UpdateAll(models);
|
||||
}
|
||||
}
|
||||
|
||||
public int Delete(object model)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.Delete(model);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> DeleteAsync(object model)
|
||||
{
|
||||
return await _dbAsync.DeleteAsync(model);
|
||||
}
|
||||
|
||||
public List<T> Query<T>(string sql) where T : new()
|
||||
{
|
||||
return _db.Query<T>(sql);
|
||||
}
|
||||
|
||||
public async Task<List<T>> QueryAsync<T>(string sql) where T : new()
|
||||
{
|
||||
return await _dbAsync.QueryAsync<T>(sql);
|
||||
}
|
||||
|
||||
public int Execute(string sql)
|
||||
{
|
||||
return _db.Execute(sql);
|
||||
}
|
||||
|
||||
public async Task<int> ExecuteAsync(string sql)
|
||||
{
|
||||
return await _dbAsync.ExecuteAsync(sql);
|
||||
}
|
||||
|
||||
public TableQuery<T> Table<T>() where T : new()
|
||||
{
|
||||
return _db.Table<T>();
|
||||
}
|
||||
|
||||
public AsyncTableQuery<T> TableAsync<T>() where T : new()
|
||||
{
|
||||
return _dbAsync.Table<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
93
v2rayN/ServiceLib/Common/StringEx.cs
Normal file
93
v2rayN/ServiceLib/Common/StringEx.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public static class StringEx
|
||||
{
|
||||
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
|
||||
{
|
||||
return string.IsNullOrEmpty(value);
|
||||
}
|
||||
|
||||
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
|
||||
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
|
||||
{
|
||||
if (s.IsNullOrEmpty()) return false;
|
||||
return chars.Contains(s[0]);
|
||||
}
|
||||
|
||||
public static bool IsWhiteSpace(this string value)
|
||||
{
|
||||
foreach (char c in value)
|
||||
{
|
||||
if (char.IsWhiteSpace(c)) continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> NonWhiteSpaceLines(this TextReader reader)
|
||||
{
|
||||
string? line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
if (line.IsWhiteSpace()) continue;
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
|
||||
public static string TrimEx(this string? value)
|
||||
{
|
||||
return value == null ? string.Empty : value.Trim();
|
||||
}
|
||||
|
||||
public static string RemovePrefix(this string value, char prefix)
|
||||
{
|
||||
if (value.StartsWith(prefix))
|
||||
{
|
||||
return value.Substring(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static string RemovePrefix(this string value, string prefix)
|
||||
{
|
||||
if (value.StartsWith(prefix))
|
||||
{
|
||||
return value.Substring(prefix.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static string UpperFirstChar(this string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return char.ToUpper(value[0]) + value.Substring(1);
|
||||
}
|
||||
|
||||
public static string AppendQuotes(this string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return $"\"{value}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
864
v2rayN/ServiceLib/Common/Utils.cs
Normal file
864
v2rayN/ServiceLib/Common/Utils.cs
Normal file
@@ -0,0 +1,864 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public class Utils
|
||||
{
|
||||
#region 资源Json操作
|
||||
|
||||
/// <summary>
|
||||
/// 获取嵌入文本资源
|
||||
/// </summary>
|
||||
/// <param name="res"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetEmbedText(string res)
|
||||
{
|
||||
string result = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
using Stream? stream = assembly.GetManifestResourceStream(res);
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
using StreamReader reader = new(stream);
|
||||
result = reader.ReadToEnd();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得存储资源
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string? LoadResource(string? res)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(res))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return File.ReadAllText(res);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion 资源Json操作
|
||||
|
||||
#region 转换函数
|
||||
|
||||
/// <summary>
|
||||
/// List<string>转逗号分隔的字符串
|
||||
/// </summary>
|
||||
/// <param name="lst"></param>
|
||||
/// <returns></returns>
|
||||
public static string List2String(List<string>? lst, bool wrap = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lst == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (wrap)
|
||||
{
|
||||
return string.Join("," + Environment.NewLine, lst);
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Join(",", lst);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逗号分隔的字符串,转List<string>
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static List<string> String2List(string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
str = str.Replace(Environment.NewLine, "");
|
||||
return new List<string>(str.Split(',', StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逗号分隔的字符串,先排序后转List<string>
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static List<string> String2ListSorted(string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
str = str.Replace(Environment.NewLine, "");
|
||||
List<string> list = new(str.Split(',', StringSplitOptions.RemoveEmptyEntries));
|
||||
list.Sort();
|
||||
return list;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64编码
|
||||
/// </summary>
|
||||
/// <param name="plainText"></param>
|
||||
/// <returns></returns>
|
||||
public static string Base64Encode(string plainText)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
return Convert.ToBase64String(plainTextBytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("Base64Encode", ex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64解码
|
||||
/// </summary>
|
||||
/// <param name="plainText"></param>
|
||||
/// <returns></returns>
|
||||
public static string Base64Decode(string plainText)
|
||||
{
|
||||
try
|
||||
{
|
||||
plainText = plainText.Trim()
|
||||
.Replace(Environment.NewLine, "")
|
||||
.Replace("\n", "")
|
||||
.Replace("\r", "")
|
||||
.Replace('_', '/')
|
||||
.Replace('-', '+')
|
||||
.Replace(" ", "");
|
||||
|
||||
if (plainText.Length % 4 > 0)
|
||||
{
|
||||
plainText = plainText.PadRight(plainText.Length + 4 - plainText.Length % 4, '=');
|
||||
}
|
||||
|
||||
byte[] data = Convert.FromBase64String(plainText);
|
||||
return Encoding.UTF8.GetString(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("Base64Decode", ex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转Int
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static int ToInt(object? obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(obj ?? string.Empty);
|
||||
}
|
||||
catch //(Exception ex)
|
||||
{
|
||||
//SaveLog(ex.Message, ex);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ToBool(object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToBoolean(obj);
|
||||
}
|
||||
catch //(Exception ex)
|
||||
{
|
||||
//SaveLog(ex.Message, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToString(object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
return obj?.ToString() ?? string.Empty;
|
||||
}
|
||||
catch// (Exception ex)
|
||||
{
|
||||
//SaveLog(ex.Message, ex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// byte 转成 有两位小数点的 方便阅读的数据
|
||||
/// 比如 2.50 MB
|
||||
/// </summary>
|
||||
/// <param name="amount">bytes</param>
|
||||
/// <param name="result">转换之后的数据</param>
|
||||
/// <param name="unit">单位</param>
|
||||
public static void ToHumanReadable(long amount, out double result, out string unit)
|
||||
{
|
||||
uint factor = 1024u;
|
||||
//long KBs = amount / factor;
|
||||
long KBs = amount;
|
||||
if (KBs > 0)
|
||||
{
|
||||
// multi KB
|
||||
long MBs = KBs / factor;
|
||||
if (MBs > 0)
|
||||
{
|
||||
// multi MB
|
||||
long GBs = MBs / factor;
|
||||
if (GBs > 0)
|
||||
{
|
||||
// multi GB
|
||||
long TBs = GBs / factor;
|
||||
if (TBs > 0)
|
||||
{
|
||||
result = TBs + ((GBs % factor) / (factor + 0.0));
|
||||
unit = "TB";
|
||||
return;
|
||||
}
|
||||
result = GBs + ((MBs % factor) / (factor + 0.0));
|
||||
unit = "GB";
|
||||
return;
|
||||
}
|
||||
result = MBs + ((KBs % factor) / (factor + 0.0));
|
||||
unit = "MB";
|
||||
return;
|
||||
}
|
||||
result = KBs + ((amount % factor) / (factor + 0.0));
|
||||
unit = "KB";
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = amount;
|
||||
unit = "B";
|
||||
}
|
||||
}
|
||||
|
||||
public static string HumanFy(long amount)
|
||||
{
|
||||
ToHumanReadable(amount, out double result, out string unit);
|
||||
return $"{string.Format("{0:f1}", result)} {unit}";
|
||||
}
|
||||
|
||||
public static string UrlEncode(string url)
|
||||
{
|
||||
return Uri.EscapeDataString(url);
|
||||
//return HttpUtility.UrlEncode(url);
|
||||
}
|
||||
|
||||
public static string UrlDecode(string url)
|
||||
{
|
||||
return Uri.UnescapeDataString(url);
|
||||
//return HttpUtility.UrlDecode(url);
|
||||
}
|
||||
|
||||
public static NameValueCollection ParseQueryString(string query)
|
||||
{
|
||||
var result = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
|
||||
if (IsNullOrEmpty(query))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var parts = query[1..].Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var keyValue = part.Split(['=']);
|
||||
if (keyValue.Length != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var key = Uri.UnescapeDataString(keyValue[0]);
|
||||
var val = Uri.UnescapeDataString(keyValue[1]);
|
||||
|
||||
if (result[key] is null)
|
||||
{
|
||||
result.Add(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetMD5(string str)
|
||||
{
|
||||
byte[] byteOld = Encoding.UTF8.GetBytes(str);
|
||||
byte[] byteNew = MD5.HashData(byteOld);
|
||||
StringBuilder sb = new(32);
|
||||
foreach (byte b in byteNew)
|
||||
{
|
||||
sb.Append(b.ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// idn to idc
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetPunycode(string url)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(url))
|
||||
{
|
||||
return url;
|
||||
}
|
||||
try
|
||||
{
|
||||
Uri uri = new(url);
|
||||
if (uri.Host == uri.IdnHost || uri.Host == $"[{uri.IdnHost}]")
|
||||
{
|
||||
return url;
|
||||
}
|
||||
else
|
||||
{
|
||||
return url.Replace(uri.Host, uri.IdnHost);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsBase64String(string plainText)
|
||||
{
|
||||
if (plainText.IsNullOrEmpty()) return false;
|
||||
var buffer = new Span<byte>(new byte[plainText.Length]);
|
||||
return Convert.TryFromBase64String(plainText, buffer, out int _);
|
||||
}
|
||||
|
||||
public static string Convert2Comma(string text)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(text))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
return text.Replace(",", ",").Replace(Environment.NewLine, ",");
|
||||
}
|
||||
|
||||
#endregion 转换函数
|
||||
|
||||
#region 数据检查
|
||||
|
||||
/// <summary>
|
||||
/// 判断输入的是否是数字
|
||||
/// </summary>
|
||||
/// <param name="oText"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsNumeric(string oText)
|
||||
{
|
||||
try
|
||||
{
|
||||
int var1 = ToInt(oText);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsNullOrEmpty(string? text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (text == "null")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证IP地址是否合法
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
public static bool IsIP(string ip)
|
||||
{
|
||||
//如果为空
|
||||
if (IsNullOrEmpty(ip))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//清除要验证字符串中的空格
|
||||
//ip = ip.TrimEx();
|
||||
//可能是CIDR
|
||||
if (ip.IndexOf(@"/") > 0)
|
||||
{
|
||||
string[] cidr = ip.Split('/');
|
||||
if (cidr.Length == 2)
|
||||
{
|
||||
if (!IsNumeric(cidr[0]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ip = cidr[0];
|
||||
}
|
||||
}
|
||||
|
||||
//模式字符串
|
||||
string pattern = @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$";
|
||||
|
||||
//验证
|
||||
return IsMatch(ip, pattern);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证Domain地址是否合法
|
||||
/// </summary>
|
||||
/// <param name="domain"></param>
|
||||
public static bool IsDomain(string? domain)
|
||||
{
|
||||
//如果为空
|
||||
if (IsNullOrEmpty(domain))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Uri.CheckHostName(domain) == UriHostNameType.Dns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证输入字符串是否与模式字符串匹配,匹配返回true
|
||||
/// </summary>
|
||||
/// <param name="input">输入字符串</param>
|
||||
/// <param name="pattern">模式字符串</param>
|
||||
public static bool IsMatch(string input, string pattern)
|
||||
{
|
||||
return Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
public static bool IsIpv6(string ip)
|
||||
{
|
||||
if (IPAddress.TryParse(ip, out IPAddress? address))
|
||||
{
|
||||
return address.AddressFamily switch
|
||||
{
|
||||
AddressFamily.InterNetwork => false,
|
||||
AddressFamily.InterNetworkV6 => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion 数据检查
|
||||
|
||||
#region 测速
|
||||
|
||||
public static void SetSecurityProtocol(bool enableSecurityProtocolTls13)
|
||||
{
|
||||
if (enableSecurityProtocolTls13)
|
||||
{
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
|
||||
}
|
||||
else
|
||||
{
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||
}
|
||||
ServicePointManager.DefaultConnectionLimit = 256;
|
||||
}
|
||||
|
||||
public static bool PortInUse(int port)
|
||||
{
|
||||
bool inUse = false;
|
||||
try
|
||||
{
|
||||
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||
IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();
|
||||
|
||||
var lstIpEndPoints = new List<IPEndPoint>(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
|
||||
|
||||
foreach (IPEndPoint endPoint in ipEndPoints)
|
||||
{
|
||||
if (endPoint.Port == port)
|
||||
{
|
||||
inUse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return inUse;
|
||||
}
|
||||
|
||||
public static int GetFreePort(int defaultPort = 9090)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Utils.PortInUse(defaultPort))
|
||||
{
|
||||
return defaultPort;
|
||||
}
|
||||
|
||||
TcpListener l = new(IPAddress.Loopback, 0);
|
||||
l.Start();
|
||||
int port = ((IPEndPoint)l.LocalEndpoint).Port;
|
||||
l.Stop();
|
||||
return port;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return 59090;
|
||||
}
|
||||
|
||||
#endregion 测速
|
||||
|
||||
#region 杂项
|
||||
|
||||
/// <summary>
|
||||
/// 取得版本
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetVersion(bool blFull = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
string location = GetExePath();
|
||||
if (blFull)
|
||||
{
|
||||
return string.Format("{0} - V{1} - {2}",
|
||||
Global.AppName,
|
||||
GetVersionInfo(),
|
||||
File.GetLastWriteTime(location).ToString("yyyy/MM/dd"));
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Format("{0}/{1}",
|
||||
Global.AppName,
|
||||
GetVersionInfo());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return Global.AppName;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetVersionInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
string location = GetExePath();
|
||||
return FileVersionInfo.GetVersionInfo(location)?.FileVersion ?? "0.0";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return "0.0";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得GUID
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetGUID(bool full = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (full)
|
||||
{
|
||||
return Guid.NewGuid().ToString("D");
|
||||
}
|
||||
else
|
||||
{
|
||||
return BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0).ToString();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string GetDownloadFileName(string url)
|
||||
{
|
||||
var fileName = Path.GetFileName(url);
|
||||
fileName += "_temp";
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public static IPAddress? GetDefaultGateway()
|
||||
{
|
||||
return NetworkInterface
|
||||
.GetAllNetworkInterfaces()
|
||||
.Where(n => n.OperationalStatus == OperationalStatus.Up)
|
||||
.Where(n => n.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
||||
.SelectMany(n => n.GetIPProperties()?.GatewayAddresses)
|
||||
.Select(g => g?.Address)
|
||||
.Where(a => a != null)
|
||||
// .Where(a => a.AddressFamily == AddressFamily.InterNetwork)
|
||||
// .Where(a => Array.FindIndex(a.GetAddressBytes(), b => b != 0) >= 0)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public static bool IsGuidByParse(string strSrc)
|
||||
{
|
||||
return Guid.TryParse(strSrc, out Guid g);
|
||||
}
|
||||
|
||||
public static void ProcessStart(string fileName, string arguments = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(fileName, arguments) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统hosts
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, string> GetSystemHosts()
|
||||
{
|
||||
var systemHosts = new Dictionary<string, string>();
|
||||
var hostfile = @"C:\Windows\System32\drivers\etc\hosts";
|
||||
try
|
||||
{
|
||||
if (File.Exists(hostfile))
|
||||
{
|
||||
var hosts = File.ReadAllText(hostfile).Replace("\r", "");
|
||||
var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var host in hostsList)
|
||||
{
|
||||
if (host.StartsWith("#")) continue;
|
||||
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (hostItem.Length < 2) continue;
|
||||
systemHosts.Add(hostItem[1], hostItem[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return systemHosts;
|
||||
}
|
||||
|
||||
public static string GetExeName(string name)
|
||||
{
|
||||
if (IsWindows())
|
||||
{
|
||||
return $"{name}.exe";
|
||||
}
|
||||
else
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
public static bool IsOSX() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
#endregion 杂项
|
||||
|
||||
#region TempPath
|
||||
|
||||
/// <summary>
|
||||
/// 获取启动了应用程序的可执行文件的路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetPath(string fileName)
|
||||
{
|
||||
string startupPath = StartupPath();
|
||||
if (IsNullOrEmpty(fileName))
|
||||
{
|
||||
return startupPath;
|
||||
}
|
||||
return Path.Combine(startupPath, fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取启动了应用程序的可执行文件的路径及文件名
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetExePath()
|
||||
{
|
||||
return Environment.ProcessPath ?? string.Empty;
|
||||
}
|
||||
|
||||
public static string StartupPath()
|
||||
{
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
}
|
||||
|
||||
public static string GetTempPath(string filename = "")
|
||||
{
|
||||
string _tempPath = Path.Combine(StartupPath(), "guiTemps");
|
||||
if (!Directory.Exists(_tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(_tempPath);
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(filename))
|
||||
{
|
||||
return _tempPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(_tempPath, filename);
|
||||
}
|
||||
}
|
||||
|
||||
public static string UnGzip(byte[] buf)
|
||||
{
|
||||
using MemoryStream sb = new();
|
||||
using GZipStream input = new(new MemoryStream(buf), CompressionMode.Decompress, false);
|
||||
input.CopyTo(sb);
|
||||
sb.Position = 0;
|
||||
return new StreamReader(sb, Encoding.UTF8).ReadToEnd();
|
||||
}
|
||||
|
||||
public static string GetBackupPath(string filename)
|
||||
{
|
||||
string _tempPath = Path.Combine(StartupPath(), "guiBackups");
|
||||
if (!Directory.Exists(_tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(_tempPath);
|
||||
}
|
||||
return Path.Combine(_tempPath, filename);
|
||||
}
|
||||
|
||||
public static string GetConfigPath(string filename = "")
|
||||
{
|
||||
string _tempPath = Path.Combine(StartupPath(), "guiConfigs");
|
||||
if (!Directory.Exists(_tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(_tempPath);
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(filename))
|
||||
{
|
||||
return _tempPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(_tempPath, filename);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetBinPath(string filename, string? coreType = null)
|
||||
{
|
||||
string _tempPath = Path.Combine(StartupPath(), "bin");
|
||||
if (!Directory.Exists(_tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(_tempPath);
|
||||
}
|
||||
if (coreType != null)
|
||||
{
|
||||
_tempPath = Path.Combine(_tempPath, coreType.ToString()!);
|
||||
if (!Directory.Exists(_tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(_tempPath);
|
||||
}
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(filename))
|
||||
{
|
||||
return _tempPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(_tempPath, filename);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLogPath(string filename = "")
|
||||
{
|
||||
string _tempPath = Path.Combine(StartupPath(), "guiLogs");
|
||||
if (!Directory.Exists(_tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(_tempPath);
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(filename))
|
||||
{
|
||||
return _tempPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(_tempPath, filename);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFontsPath(string filename = "")
|
||||
{
|
||||
string _tempPath = Path.Combine(StartupPath(), "guiFonts");
|
||||
if (!Directory.Exists(_tempPath))
|
||||
{
|
||||
Directory.CreateDirectory(_tempPath);
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(filename))
|
||||
{
|
||||
return _tempPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(_tempPath, filename);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion TempPath
|
||||
}
|
||||
}
|
||||
58
v2rayN/ServiceLib/Common/YamlUtils.cs
Normal file
58
v2rayN/ServiceLib/Common/YamlUtils.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public class YamlUtils
|
||||
{
|
||||
#region YAML
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化成对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static T FromYaml<T>(string str)
|
||||
{
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
T obj = deserializer.Deserialize<T>(str);
|
||||
return obj;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("FromYaml", ex);
|
||||
return deserializer.Deserialize<T>("");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 序列化
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToYaml(Object obj)
|
||||
{
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(HyphenatedNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
string result = string.Empty;
|
||||
try
|
||||
{
|
||||
result = serializer.Serialize(obj);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion YAML
|
||||
}
|
||||
}
|
||||
16
v2rayN/ServiceLib/Enums/EConfigType.cs
Normal file
16
v2rayN/ServiceLib/Enums/EConfigType.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum EConfigType
|
||||
{
|
||||
VMess = 1,
|
||||
Custom = 2,
|
||||
Shadowsocks = 3,
|
||||
Socks = 4,
|
||||
VLESS = 5,
|
||||
Trojan = 6,
|
||||
Hysteria2 = 7,
|
||||
Tuic = 8,
|
||||
Wireguard = 9,
|
||||
Http = 10
|
||||
}
|
||||
}
|
||||
20
v2rayN/ServiceLib/Enums/ECoreType.cs
Normal file
20
v2rayN/ServiceLib/Enums/ECoreType.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum ECoreType
|
||||
{
|
||||
v2fly = 1,
|
||||
Xray = 2,
|
||||
SagerNet = 3,
|
||||
v2fly_v5 = 4,
|
||||
clash = 11,
|
||||
clash_meta = 12,
|
||||
mihomo = 13,
|
||||
hysteria = 21,
|
||||
naiveproxy = 22,
|
||||
tuic = 23,
|
||||
sing_box = 24,
|
||||
juicity = 25,
|
||||
hysteria2 = 26,
|
||||
v2rayN = 99
|
||||
}
|
||||
}
|
||||
9
v2rayN/ServiceLib/Enums/EGirdOrientation.cs
Normal file
9
v2rayN/ServiceLib/Enums/EGirdOrientation.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum EGirdOrientation
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Tab,
|
||||
}
|
||||
}
|
||||
11
v2rayN/ServiceLib/Enums/EGlobalHotkey.cs
Normal file
11
v2rayN/ServiceLib/Enums/EGlobalHotkey.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum EGlobalHotkey
|
||||
{
|
||||
ShowForm = 0,
|
||||
SystemProxyClear = 1,
|
||||
SystemProxySet = 2,
|
||||
SystemProxyUnchanged = 3,
|
||||
SystemProxyPac = 4,
|
||||
}
|
||||
}
|
||||
14
v2rayN/ServiceLib/Enums/EInboundProtocol.cs
Normal file
14
v2rayN/ServiceLib/Enums/EInboundProtocol.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum EInboundProtocol
|
||||
{
|
||||
socks = 0,
|
||||
http,
|
||||
socks2,
|
||||
http2,
|
||||
pac,
|
||||
api,
|
||||
api2,
|
||||
speedtest = 21
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
namespace v2rayN.Mode
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum EMove
|
||||
{
|
||||
Top = 1,
|
||||
Up = 2,
|
||||
Down = 3,
|
||||
Bottom = 4
|
||||
Bottom = 4,
|
||||
Position = 5
|
||||
}
|
||||
}
|
||||
}
|
||||
10
v2rayN/ServiceLib/Enums/ERuleMode.cs
Normal file
10
v2rayN/ServiceLib/Enums/ERuleMode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum ERuleMode
|
||||
{
|
||||
Rule = 0,
|
||||
Global = 1,
|
||||
Direct = 2,
|
||||
Unchanged = 3
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace v2rayN.Mode
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum EServerColName
|
||||
{
|
||||
@@ -8,10 +7,11 @@ namespace v2rayN.Mode
|
||||
remarks,
|
||||
address,
|
||||
port,
|
||||
security,
|
||||
network,
|
||||
streamSecurity,
|
||||
subRemarks,
|
||||
testResult,
|
||||
delayVal,
|
||||
speedVal,
|
||||
|
||||
todayDown,
|
||||
todayUp,
|
||||
10
v2rayN/ServiceLib/Enums/ESpeedActionType.cs
Normal file
10
v2rayN/ServiceLib/Enums/ESpeedActionType.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum ESpeedActionType
|
||||
{
|
||||
Tcping,
|
||||
Realping,
|
||||
Speedtest,
|
||||
Mixedtest
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
namespace v2rayN.Mode
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum ESysProxyType
|
||||
{
|
||||
ForcedClear = 0,
|
||||
ForcedChange = 1,
|
||||
Unchanged = 2
|
||||
Unchanged = 2,
|
||||
Pac = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
15
v2rayN/ServiceLib/Enums/ETransport.cs
Normal file
15
v2rayN/ServiceLib/Enums/ETransport.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum ETransport
|
||||
{
|
||||
tcp,
|
||||
kcp,
|
||||
ws,
|
||||
httpupgrade,
|
||||
splithttp,
|
||||
h2,
|
||||
http,
|
||||
quic,
|
||||
grpc
|
||||
}
|
||||
}
|
||||
44
v2rayN/ServiceLib/Enums/EViewAction.cs
Normal file
44
v2rayN/ServiceLib/Enums/EViewAction.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum EViewAction
|
||||
{
|
||||
CloseWindow,
|
||||
ShowYesNo,
|
||||
SaveFileDialog,
|
||||
AddBatchRoutingRulesYesNo,
|
||||
AdjustMainLvColWidth,
|
||||
UpdateSysProxy,
|
||||
SetClipboardData,
|
||||
AddServerViaClipboard,
|
||||
ImportRulesFromClipboard,
|
||||
ProfilesFocus,
|
||||
ShareSub,
|
||||
ShareServer,
|
||||
ShowHideWindow,
|
||||
ScanScreenTask,
|
||||
Shutdown,
|
||||
BrowseServer,
|
||||
ImportRulesFromFile,
|
||||
SubEditWindow,
|
||||
RoutingRuleSettingWindow,
|
||||
RoutingRuleDetailsWindow,
|
||||
AddServerWindow,
|
||||
AddServer2Window,
|
||||
DNSSettingWindow,
|
||||
RoutingSettingWindow,
|
||||
OptionSettingWindow,
|
||||
GlobalHotkeySettingWindow,
|
||||
SubSettingWindow,
|
||||
DispatcherSpeedTest,
|
||||
DispatcherRefreshConnections,
|
||||
DispatcherRefreshProxyGroups,
|
||||
DispatcherProxiesDelayTest,
|
||||
DispatcherStatistics,
|
||||
DispatcherServerAvailability,
|
||||
DispatcherReload,
|
||||
DispatcherRefreshServersBiz,
|
||||
DispatcherRefreshIcon,
|
||||
DispatcherCheckUpdate,
|
||||
DispatcherCheckUpdateFinished,
|
||||
}
|
||||
}
|
||||
3
v2rayN/ServiceLib/FodyWeavers.xml
Normal file
3
v2rayN/ServiceLib/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ReactiveUI />
|
||||
</Weavers>
|
||||
201
v2rayN/ServiceLib/Global.cs
Normal file
201
v2rayN/ServiceLib/Global.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
namespace ServiceLib
|
||||
{
|
||||
public class Global
|
||||
{
|
||||
#region const
|
||||
|
||||
public const string AppName = "v2rayN";
|
||||
public const string GithubUrl = "https://github.com";
|
||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||
public const string V2flyCoreUrl = "https://github.com/v2fly/v2ray-core/releases";
|
||||
public const string XrayCoreUrl = "https://github.com/XTLS/Xray-core/releases";
|
||||
public const string SagerNetCoreUrl = "https://github.com/SagerNet/v2ray-core/releases";
|
||||
public const string NUrl = @"https://github.com/2dust/v2rayN/releases";
|
||||
public const string ClashCoreUrl = "https://github.com/Dreamacro/clash/releases";
|
||||
public const string ClashMetaCoreUrl = "https://github.com/MetaCubeX/Clash.Meta/releases";
|
||||
public const string MihomoCoreUrl = "https://github.com/MetaCubeX/mihomo/releases";
|
||||
public const string HysteriaCoreUrl = "https://github.com/apernet/hysteria/releases";
|
||||
public const string NaiveproxyCoreUrl = "https://github.com/klzgrad/naiveproxy/releases";
|
||||
public const string TuicCoreUrl = "https://github.com/EAimTY/tuic/releases";
|
||||
public const string SingboxCoreUrl = "https://github.com/SagerNet/sing-box/releases";
|
||||
public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat";
|
||||
public const string SpeedPingTestUrl = @"https://www.google.com/generate_204";
|
||||
public const string JuicityCoreUrl = "https://github.com/juicity/juicity/releases";
|
||||
public const string CustomRoutingListUrl = @"https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/";
|
||||
public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs";
|
||||
|
||||
public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw=";
|
||||
public const string ConfigFileName = "guiNConfig.json";
|
||||
public const string CoreConfigFileName = "config.json";
|
||||
public const string CorePreConfigFileName = "configPre.json";
|
||||
public const string CoreSpeedtestConfigFileName = "configSpeedtest.json";
|
||||
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
|
||||
public const string ClashMixinConfigFileName = "Mixin.yaml";
|
||||
public const string V2raySampleClient = "ServiceLib.Sample.SampleClientConfig";
|
||||
public const string SingboxSampleClient = "ServiceLib.Sample.SingboxSampleClientConfig";
|
||||
public const string V2raySampleHttpRequestFileName = "ServiceLib.Sample.SampleHttpRequest";
|
||||
public const string V2raySampleHttpResponseFileName = "ServiceLib.Sample.SampleHttpResponse";
|
||||
public const string V2raySampleInbound = "ServiceLib.Sample.SampleInbound";
|
||||
public const string V2raySampleOutbound = "ServiceLib.Sample.SampleOutbound";
|
||||
public const string SingboxSampleOutbound = "ServiceLib.Sample.SingboxSampleOutbound";
|
||||
public const string CustomRoutingFileName = "ServiceLib.Sample.custom_routing_";
|
||||
public const string TunSingboxDNSFileName = "ServiceLib.Sample.tun_singbox_dns";
|
||||
public const string TunSingboxInboundFileName = "ServiceLib.Sample.tun_singbox_inbound";
|
||||
public const string TunSingboxRulesFileName = "ServiceLib.Sample.tun_singbox_rules";
|
||||
public const string DNSV2rayNormalFileName = "ServiceLib.Sample.dns_v2ray_normal";
|
||||
public const string DNSSingboxNormalFileName = "ServiceLib.Sample.dns_singbox_normal";
|
||||
public const string ClashMixinYaml = "ServiceLib.Sample.clash_mixin_yaml";
|
||||
public const string ClashTunYaml = "ServiceLib.Sample.clash_tun_yaml";
|
||||
|
||||
public const string DefaultSecurity = "auto";
|
||||
public const string DefaultNetwork = "tcp";
|
||||
public const string TcpHeaderHttp = "http";
|
||||
public const string None = "none";
|
||||
public const string ProxyTag = "proxy";
|
||||
public const string DirectTag = "direct";
|
||||
public const string BlockTag = "block";
|
||||
public const string StreamSecurity = "tls";
|
||||
public const string StreamSecurityReality = "reality";
|
||||
public const string Loopback = "127.0.0.1";
|
||||
public const string InboundAPIProtocol = "dokodemo-door";
|
||||
public const string HttpProtocol = "http://";
|
||||
public const string HttpsProtocol = "https://";
|
||||
public const string SocksProtocol = "socks://";
|
||||
|
||||
public const string UserEMail = "t@t.tt";
|
||||
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
||||
public const string AutoRunName = "v2rayNAutoRun";
|
||||
public const string CustomIconName = "v2rayN.ico";
|
||||
public const string IEProxyExceptions = "localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*";
|
||||
public const string RoutingRuleComma = "<COMMA>";
|
||||
public const string GrpcGunMode = "gun";
|
||||
public const string GrpcMultiMode = "multi";
|
||||
public const int MaxPort = 65536;
|
||||
public const string CommandClearMsg = "CommandClearMsg";
|
||||
public const string CommandSendMsgView = "CommandSendMsgView";
|
||||
public const string CommandSendSnackMsg = "CommandSendSnackMsg";
|
||||
public const string CommandStopSpeedTest = "CommandStopSpeedTest";
|
||||
public const string CommandRefreshProfiles = "CommandRefreshProfiles";
|
||||
public const string DelayUnit = "";
|
||||
public const string SpeedUnit = "";
|
||||
public const int MinFontSize = 10;
|
||||
public const string RebootAs = "rebootas";
|
||||
|
||||
public static readonly List<string> IEProxyProtocols = new() {
|
||||
"{ip}:{http_port}",
|
||||
"socks={ip}:{socks_port}",
|
||||
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
|
||||
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
|
||||
""
|
||||
};
|
||||
|
||||
public static readonly List<string> SubConvertUrls = new List<string> {
|
||||
@"https://sub.xeton.dev/sub?url={0}",
|
||||
@"https://api.dler.io/sub?url={0}",
|
||||
@"http://127.0.0.1:25500/sub?url={0}",
|
||||
""
|
||||
};
|
||||
|
||||
public static readonly List<string> SubConvertConfig = new List<string> {
|
||||
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
|
||||
};
|
||||
|
||||
public static readonly List<string> SubConvertTargets = new List<string> {
|
||||
"",
|
||||
"mixed",
|
||||
"v2ray",
|
||||
"clash",
|
||||
"ss",
|
||||
};
|
||||
|
||||
public static readonly List<string> SpeedTestUrls = new() {
|
||||
@"https://speed.cloudflare.com/__down?bytes=100000000",
|
||||
@"https://speed.cloudflare.com/__down?bytes=10000000",
|
||||
@"http://cachefly.cachefly.net/50mb.test",
|
||||
@"http://cachefly.cachefly.net/10mb.test"
|
||||
};
|
||||
|
||||
public static readonly List<string> SpeedPingTestUrls = new() {
|
||||
@"https://www.google.com/generate_204",
|
||||
@"https://www.gstatic.com/generate_204",
|
||||
@"https://www.apple.com/library/test/success.html",
|
||||
@"http://www.msftconnecttest.com/connecttest.txt",
|
||||
};
|
||||
|
||||
public static readonly Dictionary<string, string> UserAgentTexts = new()
|
||||
{
|
||||
{"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" },
|
||||
{"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" },
|
||||
{"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" },
|
||||
{"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" },
|
||||
{"none",""}
|
||||
};
|
||||
|
||||
public const string Hysteria2ProtocolShare = "hy2://";
|
||||
|
||||
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
|
||||
{
|
||||
{EConfigType.VMess,"vmess://"},
|
||||
{EConfigType.Shadowsocks,"ss://"},
|
||||
{EConfigType.Socks,"socks://"},
|
||||
{EConfigType.VLESS,"vless://"},
|
||||
{EConfigType.Trojan,"trojan://"},
|
||||
{EConfigType.Hysteria2,"hysteria2://"},
|
||||
{EConfigType.Tuic,"tuic://"},
|
||||
{EConfigType.Wireguard,"wireguard://"}
|
||||
};
|
||||
|
||||
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
|
||||
{
|
||||
{EConfigType.VMess,"vmess"},
|
||||
{EConfigType.Shadowsocks,"shadowsocks"},
|
||||
{EConfigType.Socks,"socks"},
|
||||
{EConfigType.Http,"http"},
|
||||
{EConfigType.VLESS,"vless"},
|
||||
{EConfigType.Trojan,"trojan"},
|
||||
{EConfigType.Hysteria2,"hysteria2"},
|
||||
{EConfigType.Tuic,"tuic"},
|
||||
{EConfigType.Wireguard,"wireguard"}
|
||||
};
|
||||
|
||||
public static readonly List<string> VmessSecurities = new() { "aes-128-gcm", "chacha20-poly1305", "auto", "none", "zero" };
|
||||
public static readonly List<string> SsSecurities = new() { "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "none", "plain" };
|
||||
public static readonly List<string> SsSecuritiesInSagerNet = new() { "none", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "rc4", "rc4-md5", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-cfb8", "aes-192-cfb8", "aes-256-cfb8", "aes-128-ofb", "aes-192-ofb", "aes-256-ofb", "bf-cfb", "cast5-cfb", "des-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "camellia-128-cfb8", "camellia-192-cfb8", "camellia-256-cfb8", "salsa20", "chacha20", "chacha20-ietf", "xchacha20" };
|
||||
public static readonly List<string> SsSecuritiesInXray = new() { "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "none", "plain", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" };
|
||||
public static readonly List<string> SsSecuritiesInSingbox = new() { "aes-256-gcm", "aes-192-gcm", "aes-128-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "none", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20" };
|
||||
public static readonly List<string> Flows = new() { "", "xtls-rprx-vision", "xtls-rprx-vision-udp443" };
|
||||
public static readonly List<string> Networks = new() { "tcp", "kcp", "ws", "httpupgrade", "splithttp", "h2", "quic", "grpc" };
|
||||
public static readonly List<string> KcpHeaderTypes = new() { "srtp", "utp", "wechat-video", "dtls", "wireguard" };
|
||||
public static readonly List<string> CoreTypes = new() { "v2fly", "SagerNet", "Xray", "sing_box" };
|
||||
public static readonly List<string> CoreTypes4VLESS = new() { "Xray", "sing_box" };
|
||||
public static readonly List<string> DomainStrategies = new() { "AsIs", "IPIfNonMatch", "IPOnDemand" };
|
||||
public static readonly List<string> DomainStrategies4Singbox = new() { "ipv4_only", "ipv6_only", "prefer_ipv4", "prefer_ipv6", "" };
|
||||
public static readonly List<string> DomainMatchers = new() { "linear", "mph", "" };
|
||||
public static readonly List<string> Fingerprints = new() { "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random", "randomized", "" };
|
||||
public static readonly List<string> UserAgent = new() { "chrome", "firefox", "safari", "edge", "none" };
|
||||
|
||||
public static readonly List<string> AllowInsecure = new() { "true", "false", "" };
|
||||
public static readonly List<string> DomainStrategy4Freedoms = new() { "AsIs", "UseIP", "UseIPv4", "UseIPv6", "" };
|
||||
public static readonly List<string> SingboxDomainStrategy4Out = new() { "ipv4_only", "prefer_ipv4", "prefer_ipv6", "ipv6_only", "" };
|
||||
public static readonly List<string> DomainDNSAddress = ["223.5.5.5", "223.6.6.6", "localhost"];
|
||||
public static readonly List<string> SingboxDomainDNSAddress = ["223.5.5.5", "223.6.6.6", "dhcp://auto"];
|
||||
public static readonly List<string> Languages = new() { "zh-Hans", "zh-Hant", "en", "fa-Ir", "ru" };
|
||||
public static readonly List<string> Alpns = new() { "h3", "h2", "http/1.1", "h3,h2", "h2,http/1.1", "h3,h2,http/1.1", "" };
|
||||
public static readonly List<string> LogLevels = new() { "debug", "info", "warning", "error", "none" };
|
||||
public static readonly List<string> InboundTags = new() { "socks", "http", "socks2", "http2" };
|
||||
public static readonly List<string> RuleProtocols = new() { "http", "tls", "bittorrent" };
|
||||
public static readonly List<string> RuleNetworks = new() { "", "tcp", "udp", "tcp,udp" };
|
||||
public static readonly List<string> destOverrideProtocols = ["http", "tls", "quic", "fakedns", "fakedns+others"];
|
||||
public static readonly List<string> TunMtus = new() { "1280", "1408", "1500", "9000" };
|
||||
public static readonly List<string> TunStacks = new() { "gvisor", "system" };
|
||||
public static readonly List<string> PresetMsgFilters = new() { "proxy", "direct", "block", "" };
|
||||
public static readonly List<string> SingboxMuxs = new() { "h2mux", "smux", "yamux", "" };
|
||||
public static readonly List<string> TuicCongestionControls = new() { "cubic", "new_reno", "bbr" };
|
||||
|
||||
public static readonly List<string> allowSelectType = new List<string> { "selector", "urltest", "loadbalance", "fallback" };
|
||||
public static readonly List<string> notAllowTestType = new List<string> { "selector", "urltest", "direct", "reject", "compatible", "pass", "loadbalance", "fallback" };
|
||||
public static readonly List<string> proxyVehicleType = new List<string> { "file", "http" };
|
||||
|
||||
#endregion const
|
||||
}
|
||||
}
|
||||
9
v2rayN/ServiceLib/GlobalUsings.cs
Normal file
9
v2rayN/ServiceLib/GlobalUsings.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
global using ServiceLib.Base;
|
||||
global using ServiceLib.Common;
|
||||
global using ServiceLib.Enums;
|
||||
global using ServiceLib.Handler;
|
||||
global using ServiceLib.Handler.CoreConfig;
|
||||
global using ServiceLib.Handler.Fmt;
|
||||
global using ServiceLib.Handler.Statistics;
|
||||
global using ServiceLib.Models;
|
||||
global using ServiceLib.Resx;
|
||||
206
v2rayN/ServiceLib/Handler/ClashApiHandler.cs
Normal file
206
v2rayN/ServiceLib/Handler/ClashApiHandler.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using static ServiceLib.Models.ClashProxies;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public sealed class ClashApiHandler
|
||||
{
|
||||
private static readonly Lazy<ClashApiHandler> instance = new(() => new());
|
||||
public static ClashApiHandler Instance => instance.Value;
|
||||
|
||||
private Dictionary<String, ProxiesItem>? _proxies;
|
||||
public Dictionary<string, object> ProfileContent { get; set; }
|
||||
|
||||
public void GetClashProxies(Config config, Action<ClashProxies, ClashProviders> update)
|
||||
{
|
||||
Task.Run(() => GetClashProxiesAsync(config, update));
|
||||
}
|
||||
|
||||
private async Task GetClashProxiesAsync(Config config, Action<ClashProxies, ClashProviders> update)
|
||||
{
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var url = $"{GetApiUrl()}/proxies";
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||
var clashProxies = JsonUtils.Deserialize<ClashProxies>(result);
|
||||
|
||||
var url2 = $"{GetApiUrl()}/providers/proxies";
|
||||
var result2 = await HttpClientHelper.Instance.TryGetAsync(url2);
|
||||
var clashProviders = JsonUtils.Deserialize<ClashProviders>(result2);
|
||||
|
||||
if (clashProxies != null || clashProviders != null)
|
||||
{
|
||||
_proxies = clashProxies?.proxies;
|
||||
update(clashProxies, clashProviders);
|
||||
return;
|
||||
}
|
||||
Task.Delay(5000).Wait();
|
||||
}
|
||||
update(null, null);
|
||||
}
|
||||
|
||||
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Action<ClashProxyModel?, string> update)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (blAll)
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (_proxies != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Task.Delay(5000).Wait();
|
||||
}
|
||||
if (_proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lstProxy = new List<ClashProxyModel>();
|
||||
foreach (KeyValuePair<string, ProxiesItem> kv in _proxies)
|
||||
{
|
||||
if (Global.notAllowTestType.Contains(kv.Value.type.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
lstProxy.Add(new ClashProxyModel()
|
||||
{
|
||||
name = kv.Value.name,
|
||||
type = kv.Value.type.ToLower(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (lstProxy == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var urlBase = $"{GetApiUrl()}/proxies";
|
||||
urlBase += @"/{0}/delay?timeout=10000&url=" + LazyConfig.Instance.Config.speedTestItem.speedPingTestUrl;
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
foreach (var it in lstProxy)
|
||||
{
|
||||
if (Global.notAllowTestType.Contains(it.type.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var name = it.name;
|
||||
var url = string.Format(urlBase, name);
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||
update(it, result);
|
||||
}));
|
||||
}
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
Task.Delay(1000).Wait();
|
||||
update(null, "");
|
||||
});
|
||||
}
|
||||
|
||||
public List<ProxiesItem>? GetClashProxyGroups()
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileContent = ProfileContent;
|
||||
if (fileContent is null || fileContent?.ContainsKey("proxy-groups") == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonUtils.Deserialize<List<ProxiesItem>>(JsonUtils.Serialize(fileContent["proxy-groups"]));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GetClashProxyGroups", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async void ClashSetActiveProxy(string name, string nameNode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{GetApiUrl()}/proxies/{name}";
|
||||
Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
headers.Add("name", nameNode);
|
||||
await HttpClientHelper.Instance.PutAsync(url, headers);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClashConfigUpdate(Dictionary<string, string> headers)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if (_proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var urlBase = $"{GetApiUrl()}/configs";
|
||||
|
||||
await HttpClientHelper.Instance.PatchAsync(urlBase, headers);
|
||||
});
|
||||
}
|
||||
|
||||
public async void ClashConfigReload(string filePath)
|
||||
{
|
||||
ClashConnectionClose("");
|
||||
try
|
||||
{
|
||||
var url = $"{GetApiUrl()}/configs?force=true";
|
||||
Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
headers.Add("path", filePath);
|
||||
await HttpClientHelper.Instance.PutAsync(url, headers);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetClashConnections(Config config, Action<ClashConnections> update)
|
||||
{
|
||||
Task.Run(() => GetClashConnectionsAsync(config, update));
|
||||
}
|
||||
|
||||
private async Task GetClashConnectionsAsync(Config config, Action<ClashConnections> update)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{GetApiUrl()}/connections";
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||
var clashConnections = JsonUtils.Deserialize<ClashConnections>(result);
|
||||
|
||||
update(clashConnections);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async void ClashConnectionClose(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"{GetApiUrl()}/connections/{id}";
|
||||
await HttpClientHelper.Instance.DeleteAsync(url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetApiUrl()
|
||||
{
|
||||
return $"{Global.HttpProtocol}{Global.Loopback}:{LazyConfig.Instance.StatePort2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
1869
v2rayN/ServiceLib/Handler/ConfigHandler.cs
Normal file
1869
v2rayN/ServiceLib/Handler/ConfigHandler.cs
Normal file
File diff suppressed because it is too large
Load Diff
265
v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigClash.cs
Normal file
265
v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigClash.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
namespace ServiceLib.Handler.CoreConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Core configuration file processing class
|
||||
/// </summary>
|
||||
public class CoreConfigClash
|
||||
{
|
||||
private Config _config;
|
||||
|
||||
public CoreConfigClash(Config config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成配置文件
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
public int GenerateClientCustomConfig(ProfileItem node, string? fileName, out string msg)
|
||||
{
|
||||
if (node == null || fileName is null)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg = ResUI.InitialConfiguration;
|
||||
|
||||
try
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
string addressFileName = node.address;
|
||||
if (string.IsNullOrEmpty(addressFileName))
|
||||
{
|
||||
msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
msg = ResUI.FailedReadConfiguration + "1";
|
||||
return -1;
|
||||
}
|
||||
|
||||
string tagYamlStr1 = "!<str>";
|
||||
string tagYamlStr2 = "__strn__";
|
||||
string tagYamlStr3 = "!!str";
|
||||
var txtFile = File.ReadAllText(addressFileName);
|
||||
txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2);
|
||||
|
||||
var fileContent = YamlUtils.FromYaml<Dictionary<string, object>>(txtFile);
|
||||
if (fileContent == null)
|
||||
{
|
||||
msg = ResUI.FailedConversionConfiguration;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//port
|
||||
fileContent["port"] = LazyConfig.Instance.GetLocalPort(EInboundProtocol.http);
|
||||
//socks-port
|
||||
fileContent["socks-port"] = LazyConfig.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
//log-level
|
||||
fileContent["log-level"] = GetLogLevel(_config.coreBasicItem.loglevel);
|
||||
|
||||
//external-controller
|
||||
fileContent["external-controller"] = $"{Global.Loopback}:{LazyConfig.Instance.StatePort2}";
|
||||
//allow-lan
|
||||
if (_config.inbound[0].allowLANConn)
|
||||
{
|
||||
fileContent["allow-lan"] = "true";
|
||||
fileContent["bind-address"] = "*";
|
||||
}
|
||||
else
|
||||
{
|
||||
fileContent["allow-lan"] = "false";
|
||||
}
|
||||
|
||||
//ipv6
|
||||
fileContent["ipv6"] = _config.clashUIItem.enableIPv6;
|
||||
|
||||
//mode
|
||||
if (!fileContent.ContainsKey("mode"))
|
||||
{
|
||||
fileContent["mode"] = ERuleMode.Rule.ToString().ToLower();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_config.clashUIItem.ruleMode != ERuleMode.Unchanged)
|
||||
{
|
||||
fileContent["mode"] = _config.clashUIItem.ruleMode.ToString().ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
//enable tun mode
|
||||
if (_config.tunModeItem.enableTun)
|
||||
{
|
||||
string tun = Utils.GetEmbedText(Global.ClashTunYaml);
|
||||
if (!string.IsNullOrEmpty(tun))
|
||||
{
|
||||
var tunContent = YamlUtils.FromYaml<Dictionary<string, object>>(tun);
|
||||
if (tunContent != null)
|
||||
fileContent["tun"] = tunContent["tun"];
|
||||
}
|
||||
}
|
||||
|
||||
//Mixin
|
||||
try
|
||||
{
|
||||
MixinContent(fileContent, node);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GenerateClientConfigClash-Mixin", ex);
|
||||
}
|
||||
|
||||
var txtFileNew = YamlUtils.ToYaml(fileContent).Replace(tagYamlStr2, tagYamlStr3);
|
||||
File.WriteAllText(fileName, txtFileNew);
|
||||
//check again
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
msg = ResUI.FailedReadConfiguration + "2";
|
||||
return -1;
|
||||
}
|
||||
|
||||
ClashApiHandler.Instance.ProfileContent = fileContent;
|
||||
|
||||
msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GenerateClientConfigClash", ex);
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void MixinContent(Dictionary<string, object> fileContent, ProfileItem node)
|
||||
{
|
||||
//if (!_config.clashUIItem.enableMixinContent)
|
||||
//{
|
||||
// return;
|
||||
//}
|
||||
|
||||
var path = Utils.GetConfigPath(Global.ClashMixinConfigFileName);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var txtFile = File.ReadAllText(Utils.GetConfigPath(Global.ClashMixinConfigFileName));
|
||||
|
||||
var mixinContent = YamlUtils.FromYaml<Dictionary<string, object>>(txtFile);
|
||||
if (mixinContent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var item in mixinContent)
|
||||
{
|
||||
if (!_config.tunModeItem.enableTun && item.Key == "tun")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.Key.StartsWith("prepend-")
|
||||
|| item.Key.StartsWith("append-")
|
||||
|| item.Key.StartsWith("removed-"))
|
||||
{
|
||||
ModifyContentMerge(fileContent, item.Key, item.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileContent[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private void ModifyContentMerge(Dictionary<string, object> fileContent, string key, object value)
|
||||
{
|
||||
bool blPrepend = false;
|
||||
bool blRemoved = false;
|
||||
if (key.StartsWith("prepend-"))
|
||||
{
|
||||
blPrepend = true;
|
||||
key = key.Replace("prepend-", "");
|
||||
}
|
||||
else if (key.StartsWith("append-"))
|
||||
{
|
||||
blPrepend = false;
|
||||
key = key.Replace("append-", "");
|
||||
}
|
||||
else if (key.StartsWith("removed-"))
|
||||
{
|
||||
blRemoved = true;
|
||||
key = key.Replace("removed-", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!blRemoved && !fileContent.ContainsKey(key))
|
||||
{
|
||||
fileContent.Add(key, value);
|
||||
return;
|
||||
}
|
||||
var lstOri = (List<object>)fileContent[key];
|
||||
var lstValue = (List<object>)value;
|
||||
|
||||
if (blRemoved)
|
||||
{
|
||||
foreach (var item in lstValue)
|
||||
{
|
||||
lstOri.RemoveAll(t => t.ToString().StartsWith(item.ToString()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (blPrepend)
|
||||
{
|
||||
lstValue.Reverse();
|
||||
foreach (var item in lstValue)
|
||||
{
|
||||
lstOri.Insert(0, item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in lstValue)
|
||||
{
|
||||
lstOri.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLogLevel(string level)
|
||||
{
|
||||
if (level == "none")
|
||||
{
|
||||
return "silent";
|
||||
}
|
||||
else
|
||||
{
|
||||
return level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
171
v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigHandler.cs
Normal file
171
v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigHandler.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
namespace ServiceLib.Handler.CoreConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Core configuration file processing class
|
||||
/// </summary>
|
||||
public class CoreConfigHandler
|
||||
{
|
||||
public static int GenerateClientConfig(ProfileItem node, string? fileName, out string msg, out string content)
|
||||
{
|
||||
content = string.Empty;
|
||||
try
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
return -1;
|
||||
}
|
||||
var config = LazyConfig.Instance.Config;
|
||||
|
||||
msg = ResUI.InitialConfiguration;
|
||||
if (node.configType == EConfigType.Custom)
|
||||
{
|
||||
if (node.coreType is ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo)
|
||||
{
|
||||
var configGenClash = new CoreConfigClash(config);
|
||||
return configGenClash.GenerateClientCustomConfig(node, fileName, out msg);
|
||||
}
|
||||
if (node.coreType is ECoreType.sing_box)
|
||||
{
|
||||
var configGenSingbox = new CoreConfigSingbox(config);
|
||||
return configGenSingbox.GenerateClientCustomConfig(node, fileName, out msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GenerateClientCustomConfig(node, fileName, out msg);
|
||||
}
|
||||
}
|
||||
else if (LazyConfig.Instance.GetCoreType(node, node.configType) == ECoreType.sing_box)
|
||||
{
|
||||
var configGenSingbox = new CoreConfigSingbox(config);
|
||||
if (configGenSingbox.GenerateClientConfigContent(node, out SingboxConfig? singboxConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
content = JsonUtils.Serialize(singboxConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils.ToFile(singboxConfig, fileName, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var coreConfigV2ray = new CoreConfigV2ray(config);
|
||||
if (coreConfigV2ray.GenerateClientConfigContent(node, out V2rayConfig? v2rayConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
content = JsonUtils.Serialize(v2rayConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils.ToFile(v2rayConfig, fileName, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GenerateClientConfig", ex);
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int GenerateClientCustomConfig(ProfileItem node, string? fileName, out string msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (node == null || fileName is null)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
string addressFileName = node.address;
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
addressFileName = Utils.GetConfigPath(addressFileName);
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
File.Copy(addressFileName, fileName);
|
||||
File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file.
|
||||
|
||||
//check again
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GenerateClientCustomConfig", ex);
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType, out string msg)
|
||||
{
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
if (new CoreConfigSingbox(config).GenerateClientSpeedtestConfig(selecteds, out SingboxConfig? singboxConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
JsonUtils.ToFile(singboxConfig, fileName, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (new CoreConfigV2ray(config).GenerateClientSpeedtestConfig(selecteds, out V2rayConfig? v2rayConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
JsonUtils.ToFile(v2rayConfig, fileName, false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int GenerateClientMultipleLoadConfig(Config config, string fileName, List<ProfileItem> selecteds, ECoreType coreType, out string msg)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
if (new CoreConfigSingbox(config).GenerateClientMultipleLoadConfig(selecteds, out SingboxConfig? singboxConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
JsonUtils.ToFile(singboxConfig, fileName, false);
|
||||
}
|
||||
else if (coreType == ECoreType.Xray)
|
||||
{
|
||||
if (new CoreConfigV2ray(config).GenerateClientMultipleLoadConfig(selecteds, out V2rayConfig? v2rayConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
JsonUtils.ToFile(v2rayConfig, fileName, false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
1383
v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigSingbox.cs
Normal file
1383
v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigSingbox.cs
Normal file
File diff suppressed because it is too large
Load Diff
1250
v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigV2ray.cs
Normal file
1250
v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigV2ray.cs
Normal file
File diff suppressed because it is too large
Load Diff
378
v2rayN/ServiceLib/Handler/CoreHandler.cs
Normal file
378
v2rayN/ServiceLib/Handler/CoreHandler.cs
Normal file
@@ -0,0 +1,378 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
/// <summary>
|
||||
/// Core process processing class
|
||||
/// </summary>
|
||||
public class CoreHandler
|
||||
{
|
||||
private Config _config;
|
||||
private Process? _process;
|
||||
private Process? _processPre;
|
||||
private Action<bool, string> _updateFunc;
|
||||
|
||||
public CoreHandler(Config config, Action<bool, string> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
|
||||
Environment.SetEnvironmentVariable("v2ray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("xray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
||||
}
|
||||
|
||||
public void LoadCore(ProfileItem? node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
ShowMsg(false, ResUI.CheckServerSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
string fileName = Utils.GetConfigPath(Global.CoreConfigFileName);
|
||||
if (CoreConfigHandler.GenerateClientConfig(node, fileName, out string msg, out string content) != 0)
|
||||
{
|
||||
ShowMsg(false, msg);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowMsg(false, msg);
|
||||
ShowMsg(true, $"{node.GetSummary()}");
|
||||
CoreStop();
|
||||
CoreStart(node);
|
||||
|
||||
//In tun mode, do a delay check and restart the core
|
||||
//if (_config.tunModeItem.enableTun)
|
||||
//{
|
||||
// Observable.Range(1, 1)
|
||||
// .Delay(TimeSpan.FromSeconds(15))
|
||||
// .Subscribe(x =>
|
||||
// {
|
||||
// {
|
||||
// if (_process == null || _process.HasExited)
|
||||
// {
|
||||
// CoreStart(node);
|
||||
// ShowMsg(false, "Tun mode restart the core once");
|
||||
// Logging.SaveLog("Tun mode restart the core once");
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
public int LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
|
||||
{
|
||||
int pid = -1;
|
||||
var coreType = selecteds.Exists(t => t.configType == EConfigType.Hysteria2 || t.configType == EConfigType.Tuic || t.configType == EConfigType.Wireguard) ? ECoreType.sing_box : ECoreType.Xray;
|
||||
string configPath = Utils.GetConfigPath(Global.CoreSpeedtestConfigFileName);
|
||||
if (CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType, out string msg) != 0)
|
||||
{
|
||||
ShowMsg(false, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowMsg(false, msg);
|
||||
pid = CoreStartSpeedtest(configPath, coreType);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
public void CoreStop()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool hasProc = false;
|
||||
if (_process != null)
|
||||
{
|
||||
KillProcess(_process);
|
||||
_process.Dispose();
|
||||
_process = null;
|
||||
hasProc = true;
|
||||
}
|
||||
|
||||
if (_processPre != null)
|
||||
{
|
||||
KillProcess(_processPre);
|
||||
_processPre.Dispose();
|
||||
_processPre = null;
|
||||
hasProc = true;
|
||||
}
|
||||
|
||||
if (!hasProc)
|
||||
{
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
|
||||
foreach (var it in coreInfo)
|
||||
{
|
||||
if (it.coreType == ECoreType.v2rayN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (string vName in it.coreExes)
|
||||
{
|
||||
var existing = Process.GetProcessesByName(vName);
|
||||
foreach (Process p in existing)
|
||||
{
|
||||
string? path = p.MainModule?.FileName;
|
||||
if (path == Utils.GetExeName(Utils.GetBinPath(vName, it.coreType.ToString())))
|
||||
{
|
||||
KillProcess(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void CoreStopPid(int pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
var _p = Process.GetProcessById(pid);
|
||||
KillProcess(_p);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
#region Private
|
||||
|
||||
private string CoreFindExe(CoreInfo coreInfo)
|
||||
{
|
||||
string fileName = string.Empty;
|
||||
foreach (string name in coreInfo.coreExes)
|
||||
{
|
||||
string vName = Utils.GetExeName(name);
|
||||
vName = Utils.GetBinPath(vName, coreInfo.coreType.ToString());
|
||||
if (File.Exists(vName))
|
||||
{
|
||||
fileName = vName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
string msg = string.Format(ResUI.NotFoundCore, Utils.GetBinPath("", coreInfo.coreType.ToString()), string.Join(", ", coreInfo.coreExes.ToArray()), coreInfo.coreUrl);
|
||||
Logging.SaveLog(msg);
|
||||
ShowMsg(false, msg);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private void CoreStart(ProfileItem node)
|
||||
{
|
||||
ShowMsg(false, $"{Environment.OSVersion} - {(Environment.Is64BitOperatingSystem ? 64 : 32)}");
|
||||
ShowMsg(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||
|
||||
//ECoreType coreType;
|
||||
//if (node.configType != EConfigType.Custom && _config.tunModeItem.enableTun)
|
||||
//{
|
||||
// coreType = ECoreType.sing_box;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// coreType = LazyConfig.Instance.GetCoreType(node, node.configType);
|
||||
//}
|
||||
var coreType = LazyConfig.Instance.GetCoreType(node, node.configType);
|
||||
_config.runningCoreType = coreType;
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
||||
|
||||
var displayLog = node.configType != EConfigType.Custom || node.displayLog;
|
||||
var proc = RunProcess(node, coreInfo, "", displayLog);
|
||||
if (proc is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_process = proc;
|
||||
|
||||
//start a pre service
|
||||
if (_process != null && !_process.HasExited)
|
||||
{
|
||||
ProfileItem? itemSocks = null;
|
||||
var preCoreType = ECoreType.sing_box;
|
||||
if (node.configType != EConfigType.Custom && coreType != ECoreType.sing_box && _config.tunModeItem.enableTun)
|
||||
{
|
||||
itemSocks = new ProfileItem()
|
||||
{
|
||||
coreType = preCoreType,
|
||||
configType = EConfigType.Socks,
|
||||
address = Global.Loopback,
|
||||
sni = node.address, //Tun2SocksAddress
|
||||
port = LazyConfig.Instance.GetLocalPort(EInboundProtocol.socks)
|
||||
};
|
||||
}
|
||||
else if ((node.configType == EConfigType.Custom && node.preSocksPort > 0))
|
||||
{
|
||||
preCoreType = _config.tunModeItem.enableTun ? ECoreType.sing_box : ECoreType.Xray;
|
||||
itemSocks = new ProfileItem()
|
||||
{
|
||||
coreType = preCoreType,
|
||||
configType = EConfigType.Socks,
|
||||
address = Global.Loopback,
|
||||
port = node.preSocksPort.Value,
|
||||
};
|
||||
_config.runningCoreType = preCoreType;
|
||||
}
|
||||
if (itemSocks != null)
|
||||
{
|
||||
string fileName2 = Utils.GetConfigPath(Global.CorePreConfigFileName);
|
||||
if (CoreConfigHandler.GenerateClientConfig(itemSocks, fileName2, out string msg2, out string configStr) == 0)
|
||||
{
|
||||
var coreInfo2 = CoreInfoHandler.Instance.GetCoreInfo(preCoreType);
|
||||
var proc2 = RunProcess(node, coreInfo2, $" -c {Global.CorePreConfigFileName}", true);
|
||||
if (proc2 is not null)
|
||||
{
|
||||
_processPre = proc2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int CoreStartSpeedtest(string configPath, ECoreType coreType)
|
||||
{
|
||||
ShowMsg(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||
|
||||
ShowMsg(false, configPath);
|
||||
try
|
||||
{
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
||||
var proc = RunProcess(new(), coreInfo, $" -c {Global.CoreSpeedtestConfigFileName}", true);
|
||||
if (proc is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return proc.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
string msg = ex.Message;
|
||||
ShowMsg(false, msg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowMsg(bool notify, string msg)
|
||||
{
|
||||
_updateFunc(notify, msg);
|
||||
}
|
||||
|
||||
#endregion Private
|
||||
|
||||
#region Process
|
||||
|
||||
private Process? RunProcess(ProfileItem node, CoreInfo coreInfo, string configPath, bool displayLog)
|
||||
{
|
||||
try
|
||||
{
|
||||
string fileName = CoreFindExe(coreInfo);
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Process proc = new()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = string.Format(coreInfo.arguments, configPath),
|
||||
WorkingDirectory = Utils.GetConfigPath(),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = displayLog,
|
||||
RedirectStandardError = displayLog,
|
||||
CreateNoWindow = true,
|
||||
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
|
||||
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
||||
}
|
||||
};
|
||||
var startUpErrorMessage = new StringBuilder();
|
||||
var startUpSuccessful = false;
|
||||
if (displayLog)
|
||||
{
|
||||
proc.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
string msg = e.Data + Environment.NewLine;
|
||||
ShowMsg(false, msg);
|
||||
}
|
||||
};
|
||||
proc.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
string msg = e.Data + Environment.NewLine;
|
||||
ShowMsg(false, msg);
|
||||
|
||||
if (!startUpSuccessful)
|
||||
{
|
||||
startUpErrorMessage.Append(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
proc.Start();
|
||||
if (displayLog)
|
||||
{
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
}
|
||||
|
||||
if (proc.WaitForExit(1000))
|
||||
{
|
||||
proc.CancelErrorRead();
|
||||
throw new Exception(displayLog ? startUpErrorMessage.ToString() : "启动进程失败并退出 (Failed to start the process and exited)");
|
||||
}
|
||||
else
|
||||
{
|
||||
startUpSuccessful = true;
|
||||
}
|
||||
|
||||
LazyConfig.Instance.AddProcess(proc.Handle);
|
||||
return proc;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
string msg = ex.Message;
|
||||
ShowMsg(true, msg);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void KillProcess(Process? proc)
|
||||
{
|
||||
if (proc is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
proc.Kill();
|
||||
proc.WaitForExit(100);
|
||||
if (!proc.HasExited)
|
||||
{
|
||||
proc.Kill();
|
||||
proc.WaitForExit(100);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Process
|
||||
}
|
||||
}
|
||||
217
v2rayN/ServiceLib/Handler/CoreInfoHandler.cs
Normal file
217
v2rayN/ServiceLib/Handler/CoreInfoHandler.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public sealed class CoreInfoHandler
|
||||
{
|
||||
private static readonly Lazy<CoreInfoHandler> _instance = new(() => new());
|
||||
private List<CoreInfo>? _coreInfo;
|
||||
public static CoreInfoHandler Instance => _instance.Value;
|
||||
|
||||
public CoreInfoHandler()
|
||||
{
|
||||
InitCoreInfo();
|
||||
}
|
||||
|
||||
public CoreInfo? GetCoreInfo(ECoreType coreType)
|
||||
{
|
||||
if (_coreInfo == null)
|
||||
{
|
||||
InitCoreInfo();
|
||||
}
|
||||
return _coreInfo?.FirstOrDefault(t => t.coreType == coreType);
|
||||
}
|
||||
|
||||
public List<CoreInfo> GetCoreInfo()
|
||||
{
|
||||
if (_coreInfo == null)
|
||||
{
|
||||
InitCoreInfo();
|
||||
}
|
||||
return _coreInfo ?? [];
|
||||
}
|
||||
|
||||
private void InitCoreInfo()
|
||||
{
|
||||
_coreInfo = [];
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.v2rayN,
|
||||
coreUrl = Global.NUrl,
|
||||
coreReleaseApiUrl = Global.NUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
coreDownloadUrl32 = Global.NUrl + "/download/{0}/v2rayN-32.zip",
|
||||
coreDownloadUrl64 = Global.NUrl + "/download/{0}/v2rayN.zip",
|
||||
coreDownloadUrlArm64 = Global.NUrl + "/download/{0}/v2rayN-arm64.zip",
|
||||
coreDownloadUrlLinux32 = Global.NUrl + "/download/{0}/v2rayN-linux-32.zip",
|
||||
coreDownloadUrlLinux64 = Global.NUrl + "/download/{0}/v2rayN-linux-64.zip",
|
||||
coreDownloadUrlLinuxArm64 = Global.NUrl + "/download/{0}/v2rayN-linux-arm64.zip",
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.v2fly,
|
||||
coreExes = new List<string> { "wv2ray", "v2ray" },
|
||||
arguments = "",
|
||||
coreUrl = Global.V2flyCoreUrl,
|
||||
coreReleaseApiUrl = Global.V2flyCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "V2Ray",
|
||||
versionArg = "-version",
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.SagerNet,
|
||||
coreExes = new List<string> { "SagerNet", "v2ray" },
|
||||
arguments = "run",
|
||||
coreUrl = Global.SagerNetCoreUrl,
|
||||
coreReleaseApiUrl = Global.SagerNetCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "V2Ray",
|
||||
versionArg = "version",
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.v2fly_v5,
|
||||
coreExes = new List<string> { "v2ray" },
|
||||
arguments = "run -c config.json -format jsonv5",
|
||||
coreUrl = Global.V2flyCoreUrl,
|
||||
coreReleaseApiUrl = Global.V2flyCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "V2Ray",
|
||||
versionArg = "version",
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.Xray,
|
||||
coreExes = new List<string> { "xray", "wxray" },
|
||||
arguments = "run {0}",
|
||||
coreUrl = Global.XrayCoreUrl,
|
||||
coreReleaseApiUrl = Global.XrayCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
coreDownloadUrl32 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-32.zip",
|
||||
coreDownloadUrl64 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-64.zip",
|
||||
coreDownloadUrlArm64 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-arm64-v8a.zip",
|
||||
coreDownloadUrlLinux32 = Global.XrayCoreUrl + "/download/{0}/Xray-linux-32.zip",
|
||||
coreDownloadUrlLinux64 = Global.XrayCoreUrl + "/download/{0}/Xray-linux-64.zip",
|
||||
coreDownloadUrlLinuxArm64 = Global.XrayCoreUrl + "/download/{0}/Xray-linux-arm64-v8a.zip",
|
||||
match = "Xray",
|
||||
versionArg = "-version",
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.clash,
|
||||
coreExes = new List<string> { "clash-windows-amd64-v3", "clash-windows-amd64", "clash-windows-386", "clash" },
|
||||
arguments = "-f config.json" + PortableMode(),
|
||||
coreUrl = Global.ClashCoreUrl,
|
||||
coreReleaseApiUrl = Global.ClashCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "v",
|
||||
versionArg = "-v",
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.clash_meta,
|
||||
coreExes = new List<string> { "Clash.Meta-windows-amd64-compatible", "Clash.Meta-windows-amd64", "Clash.Meta-windows-386", "Clash.Meta", "clash" },
|
||||
arguments = "-f config.json" + PortableMode(),
|
||||
coreUrl = Global.ClashMetaCoreUrl,
|
||||
coreReleaseApiUrl = Global.ClashMetaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "v",
|
||||
versionArg = "-v",
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.mihomo,
|
||||
coreExes = new List<string> { $"mihomo-windows-amd64{(Avx2.X64.IsSupported ? "" : "-compatible")}", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-windows-386", "mihomo", "clash" },
|
||||
arguments = "-f config.json" + PortableMode(),
|
||||
coreUrl = Global.MihomoCoreUrl,
|
||||
coreReleaseApiUrl = Global.MihomoCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
coreDownloadUrl32 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-windows-386-{0}.zip",
|
||||
coreDownloadUrl64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-windows-amd64-compatible-{0}.zip",
|
||||
coreDownloadUrlArm64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-windows-arm64-{0}.zip",
|
||||
coreDownloadUrlLinux32 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-linux-386-{0}.gz",
|
||||
coreDownloadUrlLinux64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-linux-amd64-compatible-{0}.gz",
|
||||
coreDownloadUrlLinuxArm64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-linux-arm64-{0}.gz",
|
||||
match = "Mihomo",
|
||||
versionArg = "-v",
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.hysteria,
|
||||
coreExes = new List<string> { "hysteria-windows-amd64", "hysteria-windows-386", "hysteria" },
|
||||
arguments = "",
|
||||
coreUrl = Global.HysteriaCoreUrl,
|
||||
coreReleaseApiUrl = Global.HysteriaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.naiveproxy,
|
||||
coreExes = new List<string> { "naiveproxy", "naive" },
|
||||
arguments = "config.json",
|
||||
coreUrl = Global.NaiveproxyCoreUrl,
|
||||
redirectInfo = false,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.tuic,
|
||||
coreExes = new List<string> { "tuic-client", "tuic" },
|
||||
arguments = "-c config.json",
|
||||
coreUrl = Global.TuicCoreUrl,
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.sing_box,
|
||||
coreExes = new List<string> { "sing-box-client", "sing-box" },
|
||||
arguments = "run {0} --disable-color",
|
||||
coreUrl = Global.SingboxCoreUrl,
|
||||
redirectInfo = true,
|
||||
coreReleaseApiUrl = Global.SingboxCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
coreDownloadUrl32 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-386.zip",
|
||||
coreDownloadUrl64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-amd64.zip",
|
||||
coreDownloadUrlArm64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-arm64.zip",
|
||||
coreDownloadUrlLinux32 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-linux-386.tar.gz",
|
||||
coreDownloadUrlLinux64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-linux-amd64.tar.gz",
|
||||
coreDownloadUrlLinuxArm64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-linux-arm64.tar.gz",
|
||||
match = "sing-box",
|
||||
versionArg = "version",
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.juicity,
|
||||
coreExes = new List<string> { "juicity-client", "juicity" },
|
||||
arguments = "run -c config.json",
|
||||
coreUrl = Global.JuicityCoreUrl
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.hysteria2,
|
||||
coreExes = new List<string> { "hysteria-windows-amd64", "hysteria-windows-386", "hysteria" },
|
||||
arguments = "",
|
||||
coreUrl = Global.HysteriaCoreUrl,
|
||||
coreReleaseApiUrl = Global.HysteriaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
redirectInfo = true,
|
||||
});
|
||||
}
|
||||
|
||||
private string PortableMode()
|
||||
{
|
||||
return $" -d \"{Utils.GetBinPath("")}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
341
v2rayN/ServiceLib/Handler/DownloadHandler.cs
Normal file
341
v2rayN/ServiceLib/Handler/DownloadHandler.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
/// <summary>
|
||||
///Download
|
||||
/// </summary>
|
||||
public class DownloadHandler
|
||||
{
|
||||
public event EventHandler<ResultEventArgs>? UpdateCompleted;
|
||||
|
||||
public event ErrorEventHandler? Error;
|
||||
|
||||
public class ResultEventArgs : EventArgs
|
||||
{
|
||||
public bool Success;
|
||||
public string Msg;
|
||||
|
||||
public ResultEventArgs(bool success, string msg)
|
||||
{
|
||||
Success = success;
|
||||
Msg = msg;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Action<bool, string> update)
|
||||
{
|
||||
try
|
||||
{
|
||||
Utils.SetSecurityProtocol(LazyConfig.Instance.Config.guiItem.enableSecurityProtocolTls13);
|
||||
|
||||
var progress = new Progress<string>();
|
||||
progress.ProgressChanged += (sender, value) =>
|
||||
{
|
||||
if (update != null)
|
||||
{
|
||||
string msg = $"{value}";
|
||||
update(false, msg);
|
||||
}
|
||||
};
|
||||
|
||||
await DownloaderHelper.Instance.DownloadDataAsync4Speed(webProxy,
|
||||
url,
|
||||
progress,
|
||||
downloadTimeout);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
update(false, ex.Message);
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
update(false, ex.InnerException.Message);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public async Task DownloadFileAsync(string url, bool blProxy, int downloadTimeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
Utils.SetSecurityProtocol(LazyConfig.Instance.Config.guiItem.enableSecurityProtocolTls13);
|
||||
UpdateCompleted?.Invoke(this, new ResultEventArgs(false, $"{ResUI.Downloading} {url}"));
|
||||
|
||||
var progress = new Progress<double>();
|
||||
progress.ProgressChanged += (sender, value) =>
|
||||
{
|
||||
UpdateCompleted?.Invoke(this, new ResultEventArgs(value > 100, $"...{value}%"));
|
||||
};
|
||||
|
||||
var webProxy = GetWebProxy(blProxy);
|
||||
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,
|
||||
url,
|
||||
Utils.GetTempPath(Utils.GetDownloadFileName(url)),
|
||||
progress,
|
||||
downloadTimeout);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex));
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
|
||||
{
|
||||
Utils.SetSecurityProtocol(LazyConfig.Instance.Config.guiItem.enableSecurityProtocolTls13);
|
||||
var webRequestHandler = new SocketsHttpHandler
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
Proxy = GetWebProxy(blProxy)
|
||||
};
|
||||
HttpClient client = new(webRequestHandler);
|
||||
|
||||
HttpResponseMessage response = await client.GetAsync(url);
|
||||
if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null)
|
||||
{
|
||||
return response.Headers.Location.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.SaveLog("StatusCode error: " + url);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> TryDownloadString(string url, bool blProxy, string userAgent)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result1 = await DownloadStringAsync(url, blProxy, userAgent);
|
||||
if (!Utils.IsNullOrEmpty(result1))
|
||||
{
|
||||
return result1;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex));
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent);
|
||||
if (!Utils.IsNullOrEmpty(result2))
|
||||
{
|
||||
return result2;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex));
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var wc = new WebClient();
|
||||
wc.Proxy = GetWebProxy(blProxy);
|
||||
var result3 = await wc.DownloadStringTaskAsync(url);
|
||||
if (!Utils.IsNullOrEmpty(result3))
|
||||
{
|
||||
return result3;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex));
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DownloadString
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
public async Task<string?> DownloadStringAsync(string url, bool blProxy, string userAgent)
|
||||
{
|
||||
try
|
||||
{
|
||||
Utils.SetSecurityProtocol(LazyConfig.Instance.Config.guiItem.enableSecurityProtocolTls13);
|
||||
var webProxy = GetWebProxy(blProxy);
|
||||
var client = new HttpClient(new SocketsHttpHandler()
|
||||
{
|
||||
Proxy = webProxy,
|
||||
UseProxy = webProxy != null
|
||||
});
|
||||
|
||||
if (Utils.IsNullOrEmpty(userAgent))
|
||||
{
|
||||
userAgent = Utils.GetVersion(false);
|
||||
}
|
||||
client.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent);
|
||||
|
||||
Uri uri = new(url);
|
||||
//Authorization Header
|
||||
if (!Utils.IsNullOrEmpty(uri.UserInfo))
|
||||
{
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Utils.Base64Encode(uri.UserInfo));
|
||||
}
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
var result = await HttpClientHelper.Instance.GetAsync(client, url, cts.Token).WaitAsync(TimeSpan.FromSeconds(30), cts.Token);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex));
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DownloadString
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
public async Task<string?> DownloadStringViaDownloader(string url, bool blProxy, string userAgent)
|
||||
{
|
||||
try
|
||||
{
|
||||
Utils.SetSecurityProtocol(LazyConfig.Instance.Config.guiItem.enableSecurityProtocolTls13);
|
||||
|
||||
var webProxy = GetWebProxy(blProxy);
|
||||
|
||||
if (Utils.IsNullOrEmpty(userAgent))
|
||||
{
|
||||
userAgent = Utils.GetVersion(false);
|
||||
}
|
||||
var result = await DownloaderHelper.Instance.DownloadStringAsync(webProxy, url, userAgent, 30);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex));
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<int> RunAvailabilityCheck(IWebProxy? webProxy)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (webProxy == null)
|
||||
{
|
||||
webProxy = GetWebProxy(true);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var config = LazyConfig.Instance.Config;
|
||||
int responseTime = await GetRealPingTime(config.speedTestItem.speedPingTestUrl, webProxy, 10);
|
||||
return responseTime;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
|
||||
{
|
||||
int responseTime = -1;
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
|
||||
using var client = new HttpClient(new SocketsHttpHandler()
|
||||
{
|
||||
Proxy = webProxy,
|
||||
UseProxy = webProxy != null
|
||||
});
|
||||
|
||||
List<int> oneTime = [];
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
var timer = Stopwatch.StartNew();
|
||||
await client.GetAsync(url, cts.Token);
|
||||
timer.Stop();
|
||||
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
|
||||
await Task.Delay(100);
|
||||
}
|
||||
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
|
||||
}
|
||||
catch //(Exception ex)
|
||||
{
|
||||
//Utile.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return responseTime;
|
||||
}
|
||||
|
||||
private WebProxy? GetWebProxy(bool blProxy)
|
||||
{
|
||||
if (!blProxy)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var httpPort = LazyConfig.Instance.GetLocalPort(EInboundProtocol.http);
|
||||
if (!SocketCheck(Global.Loopback, httpPort))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new WebProxy(Global.Loopback, httpPort);
|
||||
}
|
||||
|
||||
private bool SocketCheck(string ip, int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPEndPoint point = new(IPAddress.Parse(ip), port);
|
||||
using Socket? sock = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
sock.Connect(point);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
205
v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs
Normal file
205
v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class BaseFmt
|
||||
{
|
||||
protected static string GetIpv6(string address)
|
||||
{
|
||||
if (Utils.IsIpv6(address))
|
||||
{
|
||||
// 检查地址是否已经被方括号包围,如果没有,则添加方括号
|
||||
return address.StartsWith('[') && address.EndsWith(']') ? address : $"[{address}]";
|
||||
}
|
||||
return address; // 如果不是IPv6地址,直接返回原地址
|
||||
}
|
||||
|
||||
protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(item.flow))
|
||||
{
|
||||
dicQuery.Add("flow", item.flow);
|
||||
}
|
||||
|
||||
if (!Utils.IsNullOrEmpty(item.streamSecurity))
|
||||
{
|
||||
dicQuery.Add("security", item.streamSecurity);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (securityDef != null)
|
||||
{
|
||||
dicQuery.Add("security", securityDef);
|
||||
}
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.sni))
|
||||
{
|
||||
dicQuery.Add("sni", item.sni);
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.alpn))
|
||||
{
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.fingerprint))
|
||||
{
|
||||
dicQuery.Add("fp", Utils.UrlEncode(item.fingerprint));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.publicKey))
|
||||
{
|
||||
dicQuery.Add("pbk", Utils.UrlEncode(item.publicKey));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.shortId))
|
||||
{
|
||||
dicQuery.Add("sid", Utils.UrlEncode(item.shortId));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.spiderX))
|
||||
{
|
||||
dicQuery.Add("spx", Utils.UrlEncode(item.spiderX));
|
||||
}
|
||||
if (item.allowInsecure.Equals("true"))
|
||||
{
|
||||
dicQuery.Add("allowInsecure", "1");
|
||||
}
|
||||
|
||||
dicQuery.Add("type", !Utils.IsNullOrEmpty(item.network) ? item.network : nameof(ETransport.tcp));
|
||||
|
||||
switch (item.network)
|
||||
{
|
||||
case nameof(ETransport.tcp):
|
||||
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
|
||||
if (!Utils.IsNullOrEmpty(item.requestHost))
|
||||
{
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.kcp):
|
||||
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
{
|
||||
dicQuery.Add("seed", Utils.UrlEncode(item.path));
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.ws):
|
||||
case nameof(ETransport.httpupgrade):
|
||||
case nameof(ETransport.splithttp):
|
||||
if (!Utils.IsNullOrEmpty(item.requestHost))
|
||||
{
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
{
|
||||
dicQuery.Add("path", Utils.UrlEncode(item.path));
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.http):
|
||||
case nameof(ETransport.h2):
|
||||
dicQuery["type"] = nameof(ETransport.http);
|
||||
if (!Utils.IsNullOrEmpty(item.requestHost))
|
||||
{
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
{
|
||||
dicQuery.Add("path", Utils.UrlEncode(item.path));
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.quic):
|
||||
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
|
||||
dicQuery.Add("quicSecurity", Utils.UrlEncode(item.requestHost));
|
||||
dicQuery.Add("key", Utils.UrlEncode(item.path));
|
||||
break;
|
||||
|
||||
case nameof(ETransport.grpc):
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
{
|
||||
dicQuery.Add("authority", Utils.UrlEncode(item.requestHost));
|
||||
dicQuery.Add("serviceName", Utils.UrlEncode(item.path));
|
||||
if (item.headerType is Global.GrpcGunMode or Global.GrpcMultiMode)
|
||||
{
|
||||
dicQuery.Add("mode", Utils.UrlEncode(item.headerType));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item)
|
||||
{
|
||||
item.flow = query["flow"] ?? "";
|
||||
item.streamSecurity = query["security"] ?? "";
|
||||
item.sni = query["sni"] ?? "";
|
||||
item.alpn = Utils.UrlDecode(query["alpn"] ?? "");
|
||||
item.fingerprint = Utils.UrlDecode(query["fp"] ?? "");
|
||||
item.publicKey = Utils.UrlDecode(query["pbk"] ?? "");
|
||||
item.shortId = Utils.UrlDecode(query["sid"] ?? "");
|
||||
item.spiderX = Utils.UrlDecode(query["spx"] ?? "");
|
||||
item.allowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : "";
|
||||
|
||||
item.network = query["type"] ?? nameof(ETransport.tcp);
|
||||
switch (item.network)
|
||||
{
|
||||
case nameof(ETransport.tcp):
|
||||
item.headerType = query["headerType"] ?? Global.None;
|
||||
item.requestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
|
||||
break;
|
||||
|
||||
case nameof(ETransport.kcp):
|
||||
item.headerType = query["headerType"] ?? Global.None;
|
||||
item.path = Utils.UrlDecode(query["seed"] ?? "");
|
||||
break;
|
||||
|
||||
case nameof(ETransport.ws):
|
||||
case nameof(ETransport.httpupgrade):
|
||||
case nameof(ETransport.splithttp):
|
||||
item.requestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
item.path = Utils.UrlDecode(query["path"] ?? "/");
|
||||
break;
|
||||
|
||||
case nameof(ETransport.http):
|
||||
case nameof(ETransport.h2):
|
||||
item.network = nameof(ETransport.h2);
|
||||
item.requestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
item.path = Utils.UrlDecode(query["path"] ?? "/");
|
||||
break;
|
||||
|
||||
case nameof(ETransport.quic):
|
||||
item.headerType = query["headerType"] ?? Global.None;
|
||||
item.requestHost = query["quicSecurity"] ?? Global.None;
|
||||
item.path = Utils.UrlDecode(query["key"] ?? "");
|
||||
break;
|
||||
|
||||
case nameof(ETransport.grpc):
|
||||
item.requestHost = Utils.UrlDecode(query["authority"] ?? "");
|
||||
item.path = Utils.UrlDecode(query["serviceName"] ?? "");
|
||||
item.headerType = Utils.UrlDecode(query["mode"] ?? Global.GrpcGunMode);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected static bool Contains(string str, params string[] s)
|
||||
{
|
||||
foreach (var item in s)
|
||||
{
|
||||
if (str.Contains(item, StringComparison.OrdinalIgnoreCase)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static string WriteAllText(string strData, string ext = "json")
|
||||
{
|
||||
var fileName = Utils.GetTempPath($"{Utils.GetGUID(false)}.{ext}");
|
||||
File.WriteAllText(fileName, strData);
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs
Normal file
23
v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class ClashFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
{
|
||||
if (Contains(strData, "port", "socks-port", "proxies"))
|
||||
{
|
||||
var fileName = WriteAllText(strData, "yaml");
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.mihomo,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "clash_custom"
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
90
v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs
Normal file
90
v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class FmtHandler
|
||||
{
|
||||
public static string? GetShareUri(ProfileItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = item.configType switch
|
||||
{
|
||||
EConfigType.VMess => VmessFmt.ToUri(item),
|
||||
EConfigType.Shadowsocks => ShadowsocksFmt.ToUri(item),
|
||||
EConfigType.Socks => SocksFmt.ToUri(item),
|
||||
EConfigType.Trojan => TrojanFmt.ToUri(item),
|
||||
EConfigType.VLESS => VLESSFmt.ToUri(item),
|
||||
EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item),
|
||||
EConfigType.Tuic => TuicFmt.ToUri(item),
|
||||
EConfigType.Wireguard => WireguardFmt.ToUri(item),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return url;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveConfig(string config, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
try
|
||||
{
|
||||
string str = config.TrimEx();
|
||||
if (Utils.IsNullOrEmpty(str))
|
||||
{
|
||||
msg = ResUI.FailedReadConfiguration;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str.StartsWith(Global.ProtocolShares[EConfigType.VMess]))
|
||||
{
|
||||
return VmessFmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Shadowsocks]))
|
||||
{
|
||||
return ShadowsocksFmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Socks]))
|
||||
{
|
||||
return SocksFmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Trojan]))
|
||||
{
|
||||
return TrojanFmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.VLESS]))
|
||||
{
|
||||
return VLESSFmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2]) || str.StartsWith(Global.Hysteria2ProtocolShare))
|
||||
{
|
||||
return Hysteria2Fmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Tuic]))
|
||||
{
|
||||
return TuicFmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Wireguard]))
|
||||
{
|
||||
return WireguardFmt.Resolve(str, out msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = ResUI.NonvmessOrssProtocol;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
msg = ResUI.Incorrectconfiguration;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
100
v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs
Normal file
100
v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class Hysteria2Fmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Hysteria2
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
|
||||
item.address = url.IdnHost;
|
||||
item.port = url.Port;
|
||||
item.remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveStdTransport(query, ref item);
|
||||
item.path = Utils.UrlDecode(query["obfs-password"] ?? "");
|
||||
item.allowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false";
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (!Utils.IsNullOrEmpty(item.sni))
|
||||
{
|
||||
dicQuery.Add("sni", item.sni);
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.alpn))
|
||||
{
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
{
|
||||
dicQuery.Add("obfs", "salamander");
|
||||
dicQuery.Add("obfs-password", Utils.UrlEncode(item.path));
|
||||
}
|
||||
dicQuery.Add("insecure", item.allowInsecure.ToLower() == "true" ? "1" : "0");
|
||||
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
item.id,
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.Hysteria2]}{url}/{query}{remark}";
|
||||
return url;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
{
|
||||
if (Contains(strData, "server", "up", "down", "listen", "<html>", "<body>"))
|
||||
{
|
||||
var fileName = WriteAllText(strData);
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.hysteria,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "hysteria_custom"
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull2(string strData, string? subRemarks)
|
||||
{
|
||||
if (Contains(strData, "server", "auth", "up", "down", "listen"))
|
||||
{
|
||||
var fileName = WriteAllText(strData);
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.hysteria2,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "hysteria2_custom"
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
v2rayN/ServiceLib/Handler/Fmt/NaiveproxyFmt.cs
Normal file
23
v2rayN/ServiceLib/Handler/Fmt/NaiveproxyFmt.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class NaiveproxyFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
{
|
||||
if (Contains(strData, "listen", "proxy", "<html>", "<body>"))
|
||||
{
|
||||
var fileName = WriteAllText(strData);
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.naiveproxy,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "naiveproxy_custom"
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
180
v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs
Normal file
180
v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class ShadowsocksFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem? item;
|
||||
|
||||
item = ResolveSSLegacy(str) ?? ResolveSip002(str);
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (item.address.Length == 0 || item.port == 0 || item.security.Length == 0 || item.id.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.configType = EConfigType.Shadowsocks;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
}
|
||||
//url = string.Format("{0}:{1}@{2}:{3}",
|
||||
// item.security,
|
||||
// item.id,
|
||||
// item.address,
|
||||
// item.port);
|
||||
//url = Utile.Base64Encode(url);
|
||||
//new Sip002
|
||||
var pw = Utils.Base64Encode($"{item.security}:{item.id}");
|
||||
url = $"{pw}@{GetIpv6(item.address)}:{item.port}";
|
||||
url = $"{Global.ProtocolShares[EConfigType.Shadowsocks]}{url}{remark}";
|
||||
return url;
|
||||
}
|
||||
|
||||
private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex DetailsParser = new(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static ProfileItem? ResolveSSLegacy(string result)
|
||||
{
|
||||
var match = UrlFinder.Match(result);
|
||||
if (!match.Success)
|
||||
return null;
|
||||
|
||||
ProfileItem item = new();
|
||||
var base64 = match.Groups["base64"].Value.TrimEnd('/');
|
||||
var tag = match.Groups["tag"].Value;
|
||||
if (!Utils.IsNullOrEmpty(tag))
|
||||
{
|
||||
item.remarks = Utils.UrlDecode(tag);
|
||||
}
|
||||
Match details;
|
||||
try
|
||||
{
|
||||
details = DetailsParser.Match(Utils.Base64Decode(base64));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!details.Success)
|
||||
return null;
|
||||
item.security = details.Groups["method"].Value;
|
||||
item.id = details.Groups["password"].Value;
|
||||
item.address = details.Groups["hostname"].Value;
|
||||
item.port = Utils.ToInt(details.Groups["port"].Value);
|
||||
return item;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveSip002(string result)
|
||||
{
|
||||
Uri parsedUrl;
|
||||
try
|
||||
{
|
||||
parsedUrl = new Uri(result);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ProfileItem item = new()
|
||||
{
|
||||
remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
||||
address = parsedUrl.IdnHost,
|
||||
port = parsedUrl.Port,
|
||||
};
|
||||
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.UriEscaped);
|
||||
//2022-blake3
|
||||
if (rawUserInfo.Contains(':'))
|
||||
{
|
||||
string[] userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
|
||||
if (userInfoParts.Length != 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.security = userInfoParts[0];
|
||||
item.id = Utils.UrlDecode(userInfoParts[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// parse base64 UserInfo
|
||||
string userInfo = Utils.Base64Decode(rawUserInfo);
|
||||
string[] userInfoParts = userInfo.Split(new[] { ':' }, 2);
|
||||
if (userInfoParts.Length != 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.security = userInfoParts[0];
|
||||
item.id = userInfoParts[1];
|
||||
}
|
||||
|
||||
var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
|
||||
if (queryParameters["plugin"] != null)
|
||||
{
|
||||
//obfs-host exists
|
||||
var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host"));
|
||||
if (queryParameters["plugin"].Contains("obfs=http") && !Utils.IsNullOrEmpty(obfsHost))
|
||||
{
|
||||
obfsHost = obfsHost?.Replace("obfs-host=", "");
|
||||
item.network = Global.DefaultNetwork;
|
||||
item.headerType = Global.TcpHeaderHttp;
|
||||
item.requestHost = obfsHost ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static List<ProfileItem>? ResolveSip008(string result)
|
||||
{
|
||||
//SsSIP008
|
||||
var lstSsServer = JsonUtils.Deserialize<List<SsServer>>(result);
|
||||
if (lstSsServer?.Count <= 0)
|
||||
{
|
||||
var ssSIP008 = JsonUtils.Deserialize<SsSIP008>(result);
|
||||
if (ssSIP008?.servers?.Count > 0)
|
||||
{
|
||||
lstSsServer = ssSIP008.servers;
|
||||
}
|
||||
}
|
||||
|
||||
if (lstSsServer?.Count > 0)
|
||||
{
|
||||
List<ProfileItem> lst = [];
|
||||
foreach (var it in lstSsServer)
|
||||
{
|
||||
var ssItem = new ProfileItem()
|
||||
{
|
||||
remarks = it.remarks,
|
||||
security = it.method,
|
||||
id = it.password,
|
||||
address = it.server,
|
||||
port = Utils.ToInt(it.server_port)
|
||||
};
|
||||
lst.Add(ssItem);
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs
Normal file
55
v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class SingboxFmt : BaseFmt
|
||||
{
|
||||
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
||||
{
|
||||
var configObjects = JsonUtils.Deserialize<Object[]>(strData);
|
||||
if (configObjects != null && configObjects.Length > 0)
|
||||
{
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var configObject in configObjects)
|
||||
{
|
||||
var objectString = JsonUtils.Serialize(configObject);
|
||||
var singboxCon = JsonUtils.Deserialize<SingboxConfig>(objectString);
|
||||
if (singboxCon?.inbounds?.Count > 0
|
||||
&& singboxCon.outbounds?.Count > 0
|
||||
&& singboxCon.route != null)
|
||||
{
|
||||
var fileName = WriteAllText(objectString);
|
||||
|
||||
var profileIt = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.sing_box,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "singbox_custom",
|
||||
};
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
}
|
||||
return lstResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
{
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(strData);
|
||||
if (singboxConfig?.inbounds?.Count > 0
|
||||
&& singboxConfig.outbounds?.Count > 0
|
||||
&& singboxConfig.route != null)
|
||||
{
|
||||
var fileName = WriteAllText(strData);
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.sing_box,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "singbox_custom"
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs
Normal file
127
v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class SocksFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem? item;
|
||||
|
||||
item = ResolveSocksNew(str) ?? ResolveSocks(str);
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (item.address.Length == 0 || item.port == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.configType = EConfigType.Socks;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
}
|
||||
//url = string.Format("{0}:{1}@{2}:{3}",
|
||||
// item.security,
|
||||
// item.id,
|
||||
// item.address,
|
||||
// item.port);
|
||||
//url = Utile.Base64Encode(url);
|
||||
//new
|
||||
var pw = Utils.Base64Encode($"{item.security}:{item.id}");
|
||||
url = $"{pw}@{GetIpv6(item.address)}:{item.port}";
|
||||
url = $"{Global.ProtocolShares[EConfigType.Socks]}{url}{remark}";
|
||||
return url;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveSocks(string result)
|
||||
{
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Socks
|
||||
};
|
||||
result = result[Global.ProtocolShares[EConfigType.Socks].Length..];
|
||||
//remark
|
||||
int indexRemark = result.IndexOf("#");
|
||||
if (indexRemark > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
item.remarks = Utils.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1));
|
||||
}
|
||||
catch { }
|
||||
result = result[..indexRemark];
|
||||
}
|
||||
//part decode
|
||||
int indexS = result.IndexOf("@");
|
||||
if (indexS > 0)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
result = Utils.Base64Decode(result);
|
||||
}
|
||||
|
||||
string[] arr1 = result.Split('@');
|
||||
if (arr1.Length != 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string[] arr21 = arr1[0].Split(':');
|
||||
//string[] arr22 = arr1[1].Split(':');
|
||||
int indexPort = arr1[1].LastIndexOf(":");
|
||||
if (arr21.Length != 2 || indexPort < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.address = arr1[1][..indexPort];
|
||||
item.port = Utils.ToInt(arr1[1][(indexPort + 1)..]);
|
||||
item.security = arr21[0];
|
||||
item.id = arr21[1];
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveSocksNew(string result)
|
||||
{
|
||||
Uri parsedUrl;
|
||||
try
|
||||
{
|
||||
parsedUrl = new Uri(result);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ProfileItem item = new()
|
||||
{
|
||||
remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
||||
address = parsedUrl.IdnHost,
|
||||
port = parsedUrl.Port,
|
||||
};
|
||||
|
||||
// parse base64 UserInfo
|
||||
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped);
|
||||
string userInfo = Utils.Base64Decode(rawUserInfo);
|
||||
string[] userInfoParts = userInfo.Split(new[] { ':' }, 2);
|
||||
if (userInfoParts.Length == 2)
|
||||
{
|
||||
item.security = userInfoParts[0];
|
||||
item.id = userInfoParts[1];
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs
Normal file
49
v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class TrojanFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Trojan
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
|
||||
item.address = url.IdnHost;
|
||||
item.port = url.Port;
|
||||
item.remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveStdTransport(query, ref item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
GetStdTransport(item, null, ref dicQuery);
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
item.id,
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.Trojan]}{url}{query}{remark}";
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs
Normal file
64
v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class TuicFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Tuic
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
|
||||
item.address = url.IdnHost;
|
||||
item.port = url.Port;
|
||||
item.remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
var userInfoParts = url.UserInfo.Split(new[] { ':' }, 2);
|
||||
if (userInfoParts.Length == 2)
|
||||
{
|
||||
item.id = userInfoParts[0];
|
||||
item.security = userInfoParts[1];
|
||||
}
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveStdTransport(query, ref item);
|
||||
item.headerType = query["congestion_control"] ?? "";
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (!Utils.IsNullOrEmpty(item.sni))
|
||||
{
|
||||
dicQuery.Add("sni", item.sni);
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.alpn))
|
||||
{
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
|
||||
}
|
||||
dicQuery.Add("congestion_control", item.headerType);
|
||||
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
$"{item.id}:{item.security}",
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.Tuic]}{url}{query}{remark}";
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs
Normal file
56
v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class V2rayFmt : BaseFmt
|
||||
{
|
||||
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
||||
{
|
||||
var configObjects = JsonUtils.Deserialize<Object[]>(strData);
|
||||
if (configObjects != null && configObjects.Length > 0)
|
||||
{
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var configObject in configObjects)
|
||||
{
|
||||
var objectString = JsonUtils.Serialize(configObject);
|
||||
var v2rayCon = JsonUtils.Deserialize<V2rayConfig>(objectString);
|
||||
if (v2rayCon?.inbounds?.Count > 0
|
||||
&& v2rayCon.outbounds?.Count > 0
|
||||
&& v2rayCon.routing != null)
|
||||
{
|
||||
var fileName = WriteAllText(objectString);
|
||||
|
||||
var profileIt = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.Xray,
|
||||
address = fileName,
|
||||
remarks = v2rayCon.remarks ?? subRemarks ?? "v2ray_custom",
|
||||
};
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
}
|
||||
return lstResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
{
|
||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(strData);
|
||||
if (v2rayConfig?.inbounds?.Count > 0
|
||||
&& v2rayConfig.outbounds?.Count > 0
|
||||
&& v2rayConfig.routing != null)
|
||||
{
|
||||
var fileName = WriteAllText(strData);
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.Xray,
|
||||
address = fileName,
|
||||
remarks = v2rayConfig.remarks ?? subRemarks ?? "v2ray_custom"
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs
Normal file
60
v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class VLESSFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.VLESS,
|
||||
security = Global.None
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
|
||||
item.address = url.IdnHost;
|
||||
item.port = url.Port;
|
||||
item.remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
item.security = query["encryption"] ?? Global.None;
|
||||
item.streamSecurity = query["security"] ?? "";
|
||||
ResolveStdTransport(query, ref item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (!Utils.IsNullOrEmpty(item.security))
|
||||
{
|
||||
dicQuery.Add("encryption", item.security);
|
||||
}
|
||||
else
|
||||
{
|
||||
dicQuery.Add("encryption", Global.None);
|
||||
}
|
||||
GetStdTransport(item, Global.None, ref dicQuery);
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
item.id,
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.VLESS]}{url}{query}{remark}";
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
122
v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs
Normal file
122
v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class VmessFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem? item;
|
||||
if (str.IndexOf('?') > 0 && str.IndexOf('&') > 0)
|
||||
{
|
||||
item = ResolveStdVmess(str);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = ResolveVmess(str, out msg);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
string url = string.Empty;
|
||||
|
||||
VmessQRCode vmessQRCode = new()
|
||||
{
|
||||
v = item.configVersion,
|
||||
ps = item.remarks.TrimEx(),
|
||||
add = item.address,
|
||||
port = item.port,
|
||||
id = item.id,
|
||||
aid = item.alterId,
|
||||
scy = item.security,
|
||||
net = item.network,
|
||||
type = item.headerType,
|
||||
host = item.requestHost,
|
||||
path = item.path,
|
||||
tls = item.streamSecurity,
|
||||
sni = item.sni,
|
||||
alpn = item.alpn,
|
||||
fp = item.fingerprint
|
||||
};
|
||||
|
||||
url = JsonUtils.Serialize(vmessQRCode);
|
||||
url = Utils.Base64Encode(url);
|
||||
url = $"{Global.ProtocolShares[EConfigType.VMess]}{url}";
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveVmess(string result, out string msg)
|
||||
{
|
||||
msg = string.Empty;
|
||||
var item = new ProfileItem
|
||||
{
|
||||
configType = EConfigType.VMess
|
||||
};
|
||||
|
||||
result = result[Global.ProtocolShares[EConfigType.VMess].Length..];
|
||||
result = Utils.Base64Decode(result);
|
||||
|
||||
//转成Json
|
||||
VmessQRCode? vmessQRCode = JsonUtils.Deserialize<VmessQRCode>(result);
|
||||
if (vmessQRCode == null)
|
||||
{
|
||||
msg = ResUI.FailedConversionConfiguration;
|
||||
return null;
|
||||
}
|
||||
|
||||
item.network = Global.DefaultNetwork;
|
||||
item.headerType = Global.None;
|
||||
|
||||
item.configVersion = Utils.ToInt(vmessQRCode.v);
|
||||
item.remarks = Utils.ToString(vmessQRCode.ps);
|
||||
item.address = Utils.ToString(vmessQRCode.add);
|
||||
item.port = Utils.ToInt(vmessQRCode.port);
|
||||
item.id = Utils.ToString(vmessQRCode.id);
|
||||
item.alterId = Utils.ToInt(vmessQRCode.aid);
|
||||
item.security = Utils.ToString(vmessQRCode.scy);
|
||||
|
||||
item.security = !Utils.IsNullOrEmpty(vmessQRCode.scy) ? vmessQRCode.scy : Global.DefaultSecurity;
|
||||
if (!Utils.IsNullOrEmpty(vmessQRCode.net))
|
||||
{
|
||||
item.network = vmessQRCode.net;
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(vmessQRCode.type))
|
||||
{
|
||||
item.headerType = vmessQRCode.type;
|
||||
}
|
||||
|
||||
item.requestHost = Utils.ToString(vmessQRCode.host);
|
||||
item.path = Utils.ToString(vmessQRCode.path);
|
||||
item.streamSecurity = Utils.ToString(vmessQRCode.tls);
|
||||
item.sni = Utils.ToString(vmessQRCode.sni);
|
||||
item.alpn = Utils.ToString(vmessQRCode.alpn);
|
||||
item.fingerprint = Utils.ToString(vmessQRCode.fp);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveStdVmess(string str)
|
||||
{
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.VMess,
|
||||
security = "auto"
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
|
||||
item.address = url.IdnHost;
|
||||
item.port = url.Port;
|
||||
item.remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveStdTransport(query, ref item);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs
Normal file
69
v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
public class WireguardFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Wireguard
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
|
||||
item.address = url.IdnHost;
|
||||
item.port = url.Port;
|
||||
item.remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
|
||||
item.publicKey = Utils.UrlDecode(query["publickey"] ?? "");
|
||||
item.path = Utils.UrlDecode(query["reserved"] ?? "");
|
||||
item.requestHost = Utils.UrlDecode(query["address"] ?? "");
|
||||
item.shortId = Utils.UrlDecode(query["mtu"] ?? "");
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
}
|
||||
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (!Utils.IsNullOrEmpty(item.publicKey))
|
||||
{
|
||||
dicQuery.Add("publickey", Utils.UrlEncode(item.publicKey));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
{
|
||||
dicQuery.Add("reserved", Utils.UrlEncode(item.path));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.requestHost))
|
||||
{
|
||||
dicQuery.Add("address", Utils.UrlEncode(item.requestHost));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.shortId))
|
||||
{
|
||||
dicQuery.Add("mtu", Utils.UrlEncode(item.shortId));
|
||||
}
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
Utils.UrlEncode(item.id),
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.Wireguard]}{url}/{query}{remark}";
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
244
v2rayN/ServiceLib/Handler/LazyConfig.cs
Normal file
244
v2rayN/ServiceLib/Handler/LazyConfig.cs
Normal file
@@ -0,0 +1,244 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public sealed class LazyConfig
|
||||
{
|
||||
private static readonly Lazy<LazyConfig> _instance = new(() => new());
|
||||
private Config _config;
|
||||
private int? _statePort;
|
||||
private int? _statePort2;
|
||||
|
||||
public static LazyConfig Instance => _instance.Value;
|
||||
public Config Config => _config;
|
||||
|
||||
public int StatePort
|
||||
{
|
||||
get
|
||||
{
|
||||
_statePort ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api));
|
||||
return _statePort.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public int StatePort2
|
||||
{
|
||||
get
|
||||
{
|
||||
_statePort2 ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api2));
|
||||
return _statePort2.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private Job? _processJob;
|
||||
|
||||
public LazyConfig()
|
||||
{
|
||||
SQLiteHelper.Instance.CreateTable<SubItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ProfileItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ServerStatItem>();
|
||||
SQLiteHelper.Instance.CreateTable<RoutingItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
||||
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
||||
}
|
||||
|
||||
#region Config
|
||||
|
||||
public void SetConfig(Config config) => _config = config;
|
||||
|
||||
public int GetLocalPort(EInboundProtocol protocol)
|
||||
{
|
||||
var localPort = _config.inbound.FirstOrDefault(t => t.protocol == nameof(EInboundProtocol.socks))?.localPort ?? 10808;
|
||||
return localPort + (int)protocol;
|
||||
}
|
||||
|
||||
public void AddProcess(IntPtr processHandle)
|
||||
{
|
||||
if (Utils.IsWindows())
|
||||
{
|
||||
_processJob ??= new();
|
||||
_processJob?.AddProcess(processHandle);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Config
|
||||
|
||||
#region SqliteHelper
|
||||
|
||||
public List<SubItem> SubItems()
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<SubItem>().ToList();
|
||||
}
|
||||
|
||||
public SubItem GetSubItem(string subid)
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<SubItem>().FirstOrDefault(t => t.id == subid);
|
||||
}
|
||||
|
||||
public List<ProfileItem> ProfileItems(string subid)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(subid))
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().Where(t => t.subid == subid).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> ProfileItemIndexes(string subid)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(subid))
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().Select(t => t.indexId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().Where(t => t.subid == subid).Select(t => t.indexId).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public List<ProfileItemModel> ProfileItems(string subid, string filter)
|
||||
{
|
||||
var sql = @$"select a.*
|
||||
,b.remarks subRemarks
|
||||
from ProfileItem a
|
||||
left join SubItem b on a.subid = b.id
|
||||
where 1=1 ";
|
||||
if (!Utils.IsNullOrEmpty(subid))
|
||||
{
|
||||
sql += $" and a.subid = '{subid}'";
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(filter))
|
||||
{
|
||||
if (filter.Contains('\''))
|
||||
{
|
||||
filter = filter.Replace("'", "");
|
||||
}
|
||||
sql += String.Format(" and (a.remarks like '%{0}%' or a.address like '%{0}%') ", filter);
|
||||
}
|
||||
|
||||
return SQLiteHelper.Instance.Query<ProfileItemModel>(sql).ToList();
|
||||
}
|
||||
|
||||
public List<ProfileItemModel> ProfileItemsEx(string subid, string filter)
|
||||
{
|
||||
var lstModel = ProfileItems(_config.subIndexId, filter);
|
||||
|
||||
ConfigHandler.SetDefaultServer(_config, lstModel);
|
||||
|
||||
var lstServerStat = (_config.guiItem.enableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? [];
|
||||
var lstProfileExs = ProfileExHandler.Instance.ProfileExs;
|
||||
lstModel = (from t in lstModel
|
||||
join t2 in lstServerStat on t.indexId equals t2.indexId into t2b
|
||||
from t22 in t2b.DefaultIfEmpty()
|
||||
join t3 in lstProfileExs on t.indexId equals t3.indexId into t3b
|
||||
from t33 in t3b.DefaultIfEmpty()
|
||||
select new ProfileItemModel
|
||||
{
|
||||
indexId = t.indexId,
|
||||
configType = t.configType,
|
||||
remarks = t.remarks,
|
||||
address = t.address,
|
||||
port = t.port,
|
||||
security = t.security,
|
||||
network = t.network,
|
||||
streamSecurity = t.streamSecurity,
|
||||
subid = t.subid,
|
||||
subRemarks = t.subRemarks,
|
||||
isActive = t.indexId == _config.indexId,
|
||||
sort = t33 == null ? 0 : t33.sort,
|
||||
delay = t33 == null ? 0 : t33.delay,
|
||||
delayVal = t33?.delay != 0 ? $"{t33?.delay} {Global.DelayUnit}" : string.Empty,
|
||||
speedVal = t33?.speed != 0 ? $"{t33?.speed} {Global.SpeedUnit}" : string.Empty,
|
||||
todayDown = t22 == null ? "" : Utils.HumanFy(t22.todayDown),
|
||||
todayUp = t22 == null ? "" : Utils.HumanFy(t22.todayUp),
|
||||
totalDown = t22 == null ? "" : Utils.HumanFy(t22.totalDown),
|
||||
totalUp = t22 == null ? "" : Utils.HumanFy(t22.totalUp)
|
||||
}).OrderBy(t => t.sort).ToList();
|
||||
|
||||
return lstModel;
|
||||
}
|
||||
|
||||
public ProfileItem? GetProfileItem(string indexId)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(indexId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().FirstOrDefault(it => it.indexId == indexId);
|
||||
}
|
||||
|
||||
public ProfileItem? GetProfileItemViaRemarks(string remarks)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(remarks))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().FirstOrDefault(it => it.remarks == remarks);
|
||||
}
|
||||
|
||||
public List<RoutingItem> RoutingItems()
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<RoutingItem>().Where(it => it.locked == false).OrderBy(t => t.sort).ToList();
|
||||
}
|
||||
|
||||
public RoutingItem GetRoutingItem(string id)
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<RoutingItem>().FirstOrDefault(it => it.locked == false && it.id == id);
|
||||
}
|
||||
|
||||
public List<DNSItem> DNSItems()
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<DNSItem>().ToList();
|
||||
}
|
||||
|
||||
public DNSItem GetDNSItem(ECoreType eCoreType)
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<DNSItem>().FirstOrDefault(it => it.coreType == eCoreType);
|
||||
}
|
||||
|
||||
#endregion SqliteHelper
|
||||
|
||||
#region Core Type
|
||||
|
||||
public List<string> GetShadowsocksSecurities(ProfileItem profileItem)
|
||||
{
|
||||
var coreType = GetCoreType(profileItem, EConfigType.Shadowsocks);
|
||||
switch (coreType)
|
||||
{
|
||||
case ECoreType.v2fly:
|
||||
return Global.SsSecurities;
|
||||
|
||||
case ECoreType.Xray:
|
||||
return Global.SsSecuritiesInXray;
|
||||
|
||||
case ECoreType.sing_box:
|
||||
return Global.SsSecuritiesInSingbox;
|
||||
}
|
||||
return Global.SsSecuritiesInSagerNet;
|
||||
}
|
||||
|
||||
public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType)
|
||||
{
|
||||
if (profileItem?.coreType != null)
|
||||
{
|
||||
return (ECoreType)profileItem.coreType;
|
||||
}
|
||||
|
||||
if (_config.coreTypeItem == null)
|
||||
{
|
||||
return ECoreType.Xray;
|
||||
}
|
||||
var item = _config.coreTypeItem.FirstOrDefault(it => it.configType == eConfigType);
|
||||
if (item == null)
|
||||
{
|
||||
return ECoreType.Xray;
|
||||
}
|
||||
return item.coreType;
|
||||
}
|
||||
|
||||
#endregion Core Type
|
||||
}
|
||||
}
|
||||
41
v2rayN/ServiceLib/Handler/NoticeHandler.cs
Normal file
41
v2rayN/ServiceLib/Handler/NoticeHandler.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using ReactiveUI;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public class NoticeHandler
|
||||
{
|
||||
public void Enqueue(string? content)
|
||||
{
|
||||
if (content.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
MessageBus.Current.SendMessage(content, Global.CommandSendSnackMsg);
|
||||
}
|
||||
|
||||
public void SendMessage(string? content)
|
||||
{
|
||||
if (content.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
MessageBus.Current.SendMessage(content, Global.CommandSendMsgView);
|
||||
}
|
||||
|
||||
public void SendMessage(string? content, bool time)
|
||||
{
|
||||
if (content.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
content = $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {content}";
|
||||
SendMessage(content);
|
||||
}
|
||||
|
||||
public void SendMessageAndEnqueue(string? msg)
|
||||
{
|
||||
Enqueue(msg);
|
||||
SendMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
174
v2rayN/ServiceLib/Handler/ProfileExHandler.cs
Normal file
174
v2rayN/ServiceLib/Handler/ProfileExHandler.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
//using System.Reactive.Linq;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public class ProfileExHandler
|
||||
{
|
||||
private static readonly Lazy<ProfileExHandler> _instance = new(() => new());
|
||||
private ConcurrentBag<ProfileExItem> _lstProfileEx = [];
|
||||
private Queue<string> _queIndexIds = new();
|
||||
public ConcurrentBag<ProfileExItem> ProfileExs => _lstProfileEx;
|
||||
public static ProfileExHandler Instance => _instance.Value;
|
||||
|
||||
public ProfileExHandler()
|
||||
{
|
||||
Init();
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
SaveQueueIndexIds();
|
||||
await Task.Delay(1000 * 600);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
SQLiteHelper.Instance.Execute($"delete from ProfileExItem where indexId not in ( select indexId from ProfileItem )");
|
||||
|
||||
_lstProfileEx = new(SQLiteHelper.Instance.Table<ProfileExItem>());
|
||||
}
|
||||
|
||||
private void IndexIdEnqueue(string indexId)
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(indexId) && !_queIndexIds.Contains(indexId))
|
||||
{
|
||||
_queIndexIds.Enqueue(indexId);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveQueueIndexIds()
|
||||
{
|
||||
var cnt = _queIndexIds.Count;
|
||||
if (cnt > 0)
|
||||
{
|
||||
var lstExists = SQLiteHelper.Instance.Table<ProfileExItem>();
|
||||
List<ProfileExItem> lstInserts = [];
|
||||
List<ProfileExItem> lstUpdates = [];
|
||||
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
var id = _queIndexIds.Dequeue();
|
||||
var item = lstExists.FirstOrDefault(t => t.indexId == id);
|
||||
var itemNew = _lstProfileEx?.FirstOrDefault(t => t.indexId == id);
|
||||
if (itemNew is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item is not null)
|
||||
{
|
||||
lstUpdates.Add(itemNew);
|
||||
}
|
||||
else
|
||||
{
|
||||
lstInserts.Add(itemNew);
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
if (lstInserts.Count() > 0)
|
||||
SQLiteHelper.Instance.InsertAll(lstInserts);
|
||||
|
||||
if (lstUpdates.Count() > 0)
|
||||
SQLiteHelper.Instance.UpdateAll(lstUpdates);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("ProfileExHandler", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddProfileEx(string indexId, ref ProfileExItem? profileEx)
|
||||
{
|
||||
profileEx = new()
|
||||
{
|
||||
indexId = indexId,
|
||||
delay = 0,
|
||||
speed = 0,
|
||||
sort = 0
|
||||
};
|
||||
_lstProfileEx.Add(profileEx);
|
||||
IndexIdEnqueue(indexId);
|
||||
}
|
||||
|
||||
public void ClearAll()
|
||||
{
|
||||
SQLiteHelper.Instance.Execute($"delete from ProfileExItem ");
|
||||
_lstProfileEx = new();
|
||||
}
|
||||
|
||||
public void SaveTo()
|
||||
{
|
||||
try
|
||||
{
|
||||
SaveQueueIndexIds();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTestDelay(string indexId, string delayVal)
|
||||
{
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
|
||||
if (profileEx == null)
|
||||
{
|
||||
AddProfileEx(indexId, ref profileEx);
|
||||
}
|
||||
|
||||
int.TryParse(delayVal, out int delay);
|
||||
profileEx.delay = delay;
|
||||
IndexIdEnqueue(indexId);
|
||||
}
|
||||
|
||||
public void SetTestSpeed(string indexId, string speedVal)
|
||||
{
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
|
||||
if (profileEx == null)
|
||||
{
|
||||
AddProfileEx(indexId, ref profileEx);
|
||||
}
|
||||
|
||||
decimal.TryParse(speedVal, out decimal speed);
|
||||
profileEx.speed = speed;
|
||||
IndexIdEnqueue(indexId);
|
||||
}
|
||||
|
||||
public void SetSort(string indexId, int sort)
|
||||
{
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
|
||||
if (profileEx == null)
|
||||
{
|
||||
AddProfileEx(indexId, ref profileEx);
|
||||
}
|
||||
profileEx.sort = sort;
|
||||
IndexIdEnqueue(indexId);
|
||||
}
|
||||
|
||||
public int GetSort(string indexId)
|
||||
{
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
|
||||
if (profileEx == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return profileEx.sort;
|
||||
}
|
||||
|
||||
public int GetMaxSort()
|
||||
{
|
||||
if (_lstProfileEx.Count <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return _lstProfileEx.Max(t => t == null ? 0 : t.sort);
|
||||
}
|
||||
}
|
||||
}
|
||||
390
v2rayN/ServiceLib/Handler/SpeedtestHandler.cs
Normal file
390
v2rayN/ServiceLib/Handler/SpeedtestHandler.cs
Normal file
@@ -0,0 +1,390 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public class SpeedtestHandler
|
||||
{
|
||||
private Config? _config;
|
||||
private CoreHandler _coreHandler;
|
||||
private List<ServerTestItem> _selecteds;
|
||||
private ESpeedActionType _actionType;
|
||||
private Action<SpeedTestResult> _updateFunc;
|
||||
private bool _exitLoop = false;
|
||||
|
||||
public SpeedtestHandler(Config config, CoreHandler coreHandler, List<ProfileItem> selecteds, ESpeedActionType actionType, Action<SpeedTestResult> update)
|
||||
{
|
||||
_config = config;
|
||||
_coreHandler = coreHandler;
|
||||
_actionType = actionType;
|
||||
_updateFunc = update;
|
||||
|
||||
_selecteds = new List<ServerTestItem>();
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_selecteds.Add(new ServerTestItem()
|
||||
{
|
||||
indexId = it.indexId,
|
||||
address = it.address,
|
||||
port = it.port,
|
||||
configType = it.configType
|
||||
});
|
||||
}
|
||||
//clear test result
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
switch (actionType)
|
||||
{
|
||||
case ESpeedActionType.Tcping:
|
||||
case ESpeedActionType.Realping:
|
||||
UpdateFunc(it.indexId, ResUI.Speedtesting, "");
|
||||
ProfileExHandler.Instance.SetTestDelay(it.indexId, "0");
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Speedtest:
|
||||
UpdateFunc(it.indexId, "", ResUI.SpeedtestingWait);
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "0");
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Mixedtest:
|
||||
UpdateFunc(it.indexId, ResUI.Speedtesting, ResUI.SpeedtestingWait);
|
||||
ProfileExHandler.Instance.SetTestDelay(it.indexId, "0");
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "0");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (actionType)
|
||||
{
|
||||
case ESpeedActionType.Tcping:
|
||||
Task.Run(RunTcping);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Realping:
|
||||
Task.Run(RunRealPing);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Speedtest:
|
||||
Task.Run(RunSpeedTestAsync);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Mixedtest:
|
||||
Task.Run(RunMixedtestAsync);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ExitLoop()
|
||||
{
|
||||
_exitLoop = true;
|
||||
UpdateFunc("", ResUI.SpeedtestingStop);
|
||||
}
|
||||
|
||||
private Task RunTcping()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Task> tasks = [];
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
tasks.Add(Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
int time = GetTcpingTime(it.address, it.port);
|
||||
var output = FormatOut(time, Global.DelayUnit);
|
||||
|
||||
ProfileExHandler.Instance.SetTestDelay(it.indexId, output);
|
||||
UpdateFunc(it.indexId, output);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}));
|
||||
}
|
||||
Task.WaitAll([.. tasks]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ProfileExHandler.Instance.SaveTo();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task RunRealPing()
|
||||
{
|
||||
int pid = -1;
|
||||
try
|
||||
{
|
||||
string msg = string.Empty;
|
||||
|
||||
pid = _coreHandler.LoadCoreConfigSpeedtest(_selecteds);
|
||||
if (pid < 0)
|
||||
{
|
||||
UpdateFunc("", ResUI.FailedToRunCore);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
DownloadHandler downloadHandle = new DownloadHandler();
|
||||
|
||||
List<Task> tasks = new();
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
if (!it.allowTest)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
WebProxy webProxy = new(Global.Loopback, it.port);
|
||||
string output = await GetRealPingTime(downloadHandle, webProxy);
|
||||
|
||||
ProfileExHandler.Instance.SetTestDelay(it.indexId, output);
|
||||
UpdateFunc(it.indexId, output);
|
||||
int.TryParse(output, out int delay);
|
||||
it.delay = delay;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}));
|
||||
}
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pid > 0)
|
||||
{
|
||||
_coreHandler.CoreStopPid(pid);
|
||||
}
|
||||
ProfileExHandler.Instance.SaveTo();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RunSpeedTestAsync()
|
||||
{
|
||||
int pid = -1;
|
||||
//if (_actionType == ESpeedActionType.Mixedtest)
|
||||
//{
|
||||
// _selecteds = _selecteds.OrderBy(t => t.delay).ToList();
|
||||
//}
|
||||
|
||||
pid = _coreHandler.LoadCoreConfigSpeedtest(_selecteds);
|
||||
if (pid < 0)
|
||||
{
|
||||
UpdateFunc("", ResUI.FailedToRunCore);
|
||||
return;
|
||||
}
|
||||
|
||||
string url = _config.speedTestItem.speedTestUrl;
|
||||
var timeout = _config.speedTestItem.speedTestTimeout;
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
if (_exitLoop)
|
||||
{
|
||||
UpdateFunc(it.indexId, "", ResUI.SpeedtestingSkip);
|
||||
continue;
|
||||
}
|
||||
if (!it.allowTest)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//if (it.delay < 0)
|
||||
//{
|
||||
// UpdateFunc(it.indexId, "", ResUI.SpeedtestingSkip);
|
||||
// continue;
|
||||
//}
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "-1");
|
||||
UpdateFunc(it.indexId, "", ResUI.Speedtesting);
|
||||
|
||||
var item = LazyConfig.Instance.GetProfileItem(it.indexId);
|
||||
if (item is null) continue;
|
||||
|
||||
WebProxy webProxy = new(Global.Loopback, it.port);
|
||||
|
||||
await downloadHandle.DownloadDataAsync(url, webProxy, timeout, (bool success, string msg) =>
|
||||
{
|
||||
decimal.TryParse(msg, out decimal dec);
|
||||
if (dec > 0)
|
||||
{
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, msg);
|
||||
}
|
||||
UpdateFunc(it.indexId, "", msg);
|
||||
});
|
||||
}
|
||||
|
||||
if (pid > 0)
|
||||
{
|
||||
_coreHandler.CoreStopPid(pid);
|
||||
}
|
||||
UpdateFunc("", ResUI.SpeedtestingCompleted);
|
||||
ProfileExHandler.Instance.SaveTo();
|
||||
}
|
||||
|
||||
private async Task RunSpeedTestMulti()
|
||||
{
|
||||
int pid = -1;
|
||||
pid = _coreHandler.LoadCoreConfigSpeedtest(_selecteds);
|
||||
if (pid < 0)
|
||||
{
|
||||
UpdateFunc("", ResUI.FailedToRunCore);
|
||||
return;
|
||||
}
|
||||
|
||||
string url = _config.speedTestItem.speedTestUrl;
|
||||
var timeout = _config.speedTestItem.speedTestTimeout;
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
if (_exitLoop)
|
||||
{
|
||||
UpdateFunc(it.indexId, "", ResUI.SpeedtestingSkip);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!it.allowTest)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.delay < 0)
|
||||
{
|
||||
UpdateFunc(it.indexId, "", ResUI.SpeedtestingSkip);
|
||||
continue;
|
||||
}
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "-1");
|
||||
UpdateFunc(it.indexId, "", ResUI.Speedtesting);
|
||||
|
||||
var item = LazyConfig.Instance.GetProfileItem(it.indexId);
|
||||
if (item is null) continue;
|
||||
|
||||
WebProxy webProxy = new(Global.Loopback, it.port);
|
||||
_ = downloadHandle.DownloadDataAsync(url, webProxy, timeout, (bool success, string msg) =>
|
||||
{
|
||||
decimal.TryParse(msg, out decimal dec);
|
||||
if (dec > 0)
|
||||
{
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, msg);
|
||||
}
|
||||
UpdateFunc(it.indexId, "", msg);
|
||||
});
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
|
||||
await Task.Delay((timeout + 2) * 1000);
|
||||
|
||||
if (pid > 0)
|
||||
{
|
||||
_coreHandler.CoreStopPid(pid);
|
||||
}
|
||||
UpdateFunc("", ResUI.SpeedtestingCompleted);
|
||||
ProfileExHandler.Instance.SaveTo();
|
||||
}
|
||||
|
||||
private async Task RunMixedtestAsync()
|
||||
{
|
||||
await RunRealPing();
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
await RunSpeedTestMulti();
|
||||
}
|
||||
|
||||
private async Task<string> GetRealPingTime(DownloadHandler downloadHandle, IWebProxy webProxy)
|
||||
{
|
||||
int responseTime = await downloadHandle.GetRealPingTime(_config.speedTestItem.speedPingTestUrl, webProxy, 10);
|
||||
//string output = Utile.IsNullOrEmpty(status) ? FormatOut(responseTime, "ms") : status;
|
||||
return FormatOut(responseTime, Global.DelayUnit);
|
||||
}
|
||||
|
||||
private int GetTcpingTime(string url, int port)
|
||||
{
|
||||
int responseTime = -1;
|
||||
|
||||
try
|
||||
{
|
||||
if (!IPAddress.TryParse(url, out IPAddress? ipAddress))
|
||||
{
|
||||
IPHostEntry ipHostInfo = System.Net.Dns.GetHostEntry(url);
|
||||
ipAddress = ipHostInfo.AddressList[0];
|
||||
}
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
IPEndPoint endPoint = new(ipAddress, port);
|
||||
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
var result = clientSocket.BeginConnect(endPoint, null, null);
|
||||
if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)))
|
||||
throw new TimeoutException("connect timeout (5s): " + url);
|
||||
clientSocket.EndConnect(result);
|
||||
|
||||
timer.Stop();
|
||||
responseTime = (int)timer.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return responseTime;
|
||||
}
|
||||
|
||||
private string FormatOut(object time, string unit)
|
||||
{
|
||||
//if (time.ToString().Equals("-1"))
|
||||
//{
|
||||
// return "Timeout";
|
||||
//}
|
||||
return $"{time}";
|
||||
}
|
||||
|
||||
private void UpdateFunc(string indexId, string delay, string speed = "")
|
||||
{
|
||||
_updateFunc(new() { IndexId = indexId, Delay = delay, Speed = speed });
|
||||
}
|
||||
}
|
||||
}
|
||||
136
v2rayN/ServiceLib/Handler/Statistics/StatisticsHandler.cs
Normal file
136
v2rayN/ServiceLib/Handler/Statistics/StatisticsHandler.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
namespace ServiceLib.Handler.Statistics
|
||||
{
|
||||
public class StatisticsHandler
|
||||
{
|
||||
private static readonly Lazy<StatisticsHandler> instance = new(() => new());
|
||||
public static StatisticsHandler Instance => instance.Value;
|
||||
|
||||
private Config _config;
|
||||
private ServerStatItem? _serverStatItem;
|
||||
private List<ServerStatItem> _lstServerStat;
|
||||
private Action<ServerSpeedItem> _updateFunc;
|
||||
private StatisticsV2ray? _statisticsV2Ray;
|
||||
private StatisticsSingbox? _statisticsSingbox;
|
||||
|
||||
public List<ServerStatItem> ServerStat => _lstServerStat;
|
||||
|
||||
public void Init(Config config, Action<ServerSpeedItem> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
if (!config.guiItem.enableStatistics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InitData();
|
||||
|
||||
_statisticsV2Ray = new StatisticsV2ray(config, UpdateServerStat);
|
||||
_statisticsSingbox = new StatisticsSingbox(config, UpdateServerStat);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
try
|
||||
{
|
||||
_statisticsV2Ray?.Close();
|
||||
_statisticsSingbox?.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAllServerStatistics()
|
||||
{
|
||||
SQLiteHelper.Instance.Execute($"delete from ServerStatItem ");
|
||||
_serverStatItem = null;
|
||||
_lstServerStat = new();
|
||||
}
|
||||
|
||||
public void SaveTo()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_lstServerStat != null)
|
||||
{
|
||||
SQLiteHelper.Instance.UpdateAll(_lstServerStat);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitData()
|
||||
{
|
||||
SQLiteHelper.Instance.Execute($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )");
|
||||
|
||||
long ticks = DateTime.Now.Date.Ticks;
|
||||
SQLiteHelper.Instance.Execute($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
|
||||
|
||||
_lstServerStat = SQLiteHelper.Instance.Table<ServerStatItem>().ToList();
|
||||
}
|
||||
|
||||
private void UpdateServerStat(ServerSpeedItem server)
|
||||
{
|
||||
GetServerStatItem(_config.indexId);
|
||||
|
||||
if (_serverStatItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (server.proxyUp != 0 || server.proxyDown != 0)
|
||||
{
|
||||
_serverStatItem.todayUp += server.proxyUp;
|
||||
_serverStatItem.todayDown += server.proxyDown;
|
||||
_serverStatItem.totalUp += server.proxyUp;
|
||||
_serverStatItem.totalDown += server.proxyDown;
|
||||
}
|
||||
|
||||
server.indexId = _config.indexId;
|
||||
server.todayUp = _serverStatItem.todayUp;
|
||||
server.todayDown = _serverStatItem.todayDown;
|
||||
server.totalUp = _serverStatItem.totalUp;
|
||||
server.totalDown = _serverStatItem.totalDown;
|
||||
_updateFunc(server);
|
||||
}
|
||||
|
||||
private void GetServerStatItem(string indexId)
|
||||
{
|
||||
long ticks = DateTime.Now.Date.Ticks;
|
||||
if (_serverStatItem != null && _serverStatItem.indexId != indexId)
|
||||
{
|
||||
_serverStatItem = null;
|
||||
}
|
||||
|
||||
if (_serverStatItem == null)
|
||||
{
|
||||
_serverStatItem = _lstServerStat.FirstOrDefault(t => t.indexId == indexId);
|
||||
if (_serverStatItem == null)
|
||||
{
|
||||
_serverStatItem = new ServerStatItem
|
||||
{
|
||||
indexId = indexId,
|
||||
totalUp = 0,
|
||||
totalDown = 0,
|
||||
todayUp = 0,
|
||||
todayDown = 0,
|
||||
dateNow = ticks
|
||||
};
|
||||
SQLiteHelper.Instance.Replace(_serverStatItem);
|
||||
_lstServerStat.Add(_serverStatItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (_serverStatItem.dateNow != ticks)
|
||||
{
|
||||
_serverStatItem.todayUp = 0;
|
||||
_serverStatItem.todayDown = 0;
|
||||
_serverStatItem.dateNow = ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
v2rayN/ServiceLib/Handler/Statistics/StatisticsSingbox.cs
Normal file
128
v2rayN/ServiceLib/Handler/Statistics/StatisticsSingbox.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Handler.Statistics
|
||||
{
|
||||
public class StatisticsSingbox
|
||||
{
|
||||
private Config _config;
|
||||
private bool _exitFlag;
|
||||
private ClientWebSocket? webSocket;
|
||||
private string url = string.Empty;
|
||||
private Action<ServerSpeedItem> _updateFunc;
|
||||
|
||||
public StatisticsSingbox(Config config, Action<ServerSpeedItem> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
_exitFlag = false;
|
||||
|
||||
Task.Run(() => Run());
|
||||
}
|
||||
|
||||
private async void Init()
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
|
||||
try
|
||||
{
|
||||
url = $"ws://{Global.Loopback}:{LazyConfig.Instance.StatePort2}/traffic";
|
||||
|
||||
if (webSocket == null)
|
||||
{
|
||||
webSocket = new ClientWebSocket();
|
||||
await webSocket.ConnectAsync(new Uri(url), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
try
|
||||
{
|
||||
_exitFlag = true;
|
||||
if (webSocket != null)
|
||||
{
|
||||
webSocket.Abort();
|
||||
webSocket = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Run()
|
||||
{
|
||||
Init();
|
||||
|
||||
while (!_exitFlag)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
if (!(_config.IsRunningCore(ECoreType.clash)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (webSocket != null)
|
||||
{
|
||||
if (webSocket.State == WebSocketState.Aborted
|
||||
|| webSocket.State == WebSocketState.Closed)
|
||||
{
|
||||
webSocket.Abort();
|
||||
webSocket = null;
|
||||
Init();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (webSocket.State != WebSocketState.Open)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var buffer = new byte[1024];
|
||||
var res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
while (!res.CloseStatus.HasValue)
|
||||
{
|
||||
var result = Encoding.UTF8.GetString(buffer, 0, res.Count);
|
||||
if (!Utils.IsNullOrEmpty(result))
|
||||
{
|
||||
ParseOutput(result, out ulong up, out ulong down);
|
||||
|
||||
_updateFunc(new ServerSpeedItem()
|
||||
{
|
||||
proxyUp = (long)(up / 1000),
|
||||
proxyDown = (long)(down / 1000)
|
||||
});
|
||||
}
|
||||
res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseOutput(string source, out ulong up, out ulong down)
|
||||
{
|
||||
up = 0; down = 0;
|
||||
try
|
||||
{
|
||||
var trafficItem = JsonUtils.Deserialize<TrafficItem>(source);
|
||||
if (trafficItem != null)
|
||||
{
|
||||
up = trafficItem.up;
|
||||
down = trafficItem.down;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
135
v2rayN/ServiceLib/Handler/Statistics/StatisticsV2ray.cs
Normal file
135
v2rayN/ServiceLib/Handler/Statistics/StatisticsV2ray.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using Grpc.Core;
|
||||
using Grpc.Net.Client;
|
||||
using ProtosLib.Statistics;
|
||||
|
||||
namespace ServiceLib.Handler.Statistics
|
||||
{
|
||||
public class StatisticsV2ray
|
||||
{
|
||||
private Models.Config _config;
|
||||
private GrpcChannel? _channel;
|
||||
private StatsService.StatsServiceClient? _client;
|
||||
private bool _exitFlag;
|
||||
private Action<ServerSpeedItem> _updateFunc;
|
||||
|
||||
public StatisticsV2ray(Models.Config config, Action<ServerSpeedItem> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
_exitFlag = false;
|
||||
|
||||
GrpcInit();
|
||||
|
||||
Task.Run(Run);
|
||||
}
|
||||
|
||||
private void GrpcInit()
|
||||
{
|
||||
if (_channel is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel = GrpcChannel.ForAddress($"{Global.HttpProtocol}{Global.Loopback}:{LazyConfig.Instance.StatePort}");
|
||||
_client = new StatsService.StatsServiceClient(_channel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_exitFlag = true;
|
||||
}
|
||||
|
||||
private async void Run()
|
||||
{
|
||||
while (!_exitFlag)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
if (!(_config.IsRunningCore(ECoreType.Xray)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_channel?.State == ConnectivityState.Ready)
|
||||
{
|
||||
QueryStatsResponse? res = null;
|
||||
try
|
||||
{
|
||||
if (_client != null)
|
||||
res = await _client.QueryStatsAsync(new QueryStatsRequest() { Pattern = "", Reset = true });
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (res != null)
|
||||
{
|
||||
ParseOutput(res.Stat, out ServerSpeedItem server);
|
||||
_updateFunc(server);
|
||||
}
|
||||
}
|
||||
if (_channel != null)
|
||||
await _channel.ConnectAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseOutput(Google.Protobuf.Collections.RepeatedField<Stat> source, out ServerSpeedItem server)
|
||||
{
|
||||
server = new();
|
||||
long aggregateProxyUp = 0;
|
||||
long aggregateProxyDown = 0;
|
||||
try
|
||||
{
|
||||
foreach (Stat stat in source)
|
||||
{
|
||||
string name = stat.Name;
|
||||
long value = stat.Value / 1024; //KByte
|
||||
string[] nStr = name.Split(">>>".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
string type = "";
|
||||
|
||||
name = name.Trim();
|
||||
|
||||
name = nStr[1];
|
||||
type = nStr[3];
|
||||
|
||||
if (name.StartsWith(Global.ProxyTag))
|
||||
{
|
||||
if (type == "uplink")
|
||||
{
|
||||
aggregateProxyUp += value;
|
||||
}
|
||||
else if (type == "downlink")
|
||||
{
|
||||
aggregateProxyDown += value;
|
||||
}
|
||||
}
|
||||
else if (name == Global.DirectTag)
|
||||
{
|
||||
if (type == "uplink")
|
||||
{
|
||||
server.directUp = value;
|
||||
}
|
||||
else if (type == "downlink")
|
||||
{
|
||||
server.directDown = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
server.proxyUp = aggregateProxyUp;
|
||||
server.proxyDown = aggregateProxyDown;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
76
v2rayN/ServiceLib/Handler/TaskHandler.cs
Normal file
76
v2rayN/ServiceLib/Handler/TaskHandler.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public class TaskHandler
|
||||
{
|
||||
private static readonly Lazy<TaskHandler> _instance = new(() => new());
|
||||
public static TaskHandler Instance => _instance.Value;
|
||||
|
||||
public TaskHandler()
|
||||
{
|
||||
}
|
||||
|
||||
public void RegUpdateTask(Config config, Action<bool, string> update)
|
||||
{
|
||||
Task.Run(() => UpdateTaskRunSubscription(config, update));
|
||||
Task.Run(() => UpdateTaskRunGeo(config, update));
|
||||
}
|
||||
|
||||
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> update)
|
||||
{
|
||||
await Task.Delay(60000);
|
||||
Logging.SaveLog("UpdateTaskRunSubscription");
|
||||
|
||||
var updateHandle = new UpdateHandler();
|
||||
while (true)
|
||||
{
|
||||
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
|
||||
var lstSubs = LazyConfig.Instance.SubItems()
|
||||
.Where(t => t.autoUpdateInterval > 0)
|
||||
.Where(t => updateTime - t.updateTime >= t.autoUpdateInterval * 60)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in lstSubs)
|
||||
{
|
||||
updateHandle.UpdateSubscriptionProcess(config, item.id, true, (bool success, string msg) =>
|
||||
{
|
||||
update(success, msg);
|
||||
if (success)
|
||||
Logging.SaveLog("subscription" + msg);
|
||||
});
|
||||
item.updateTime = updateTime;
|
||||
ConfigHandler.AddSubItem(config, item);
|
||||
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
await Task.Delay(60000);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateTaskRunGeo(Config config, Action<bool, string> update)
|
||||
{
|
||||
var autoUpdateGeoTime = DateTime.Now;
|
||||
|
||||
await Task.Delay(1000 * 120);
|
||||
Logging.SaveLog("UpdateTaskRunGeo");
|
||||
|
||||
var updateHandle = new UpdateHandler();
|
||||
while (true)
|
||||
{
|
||||
var dtNow = DateTime.Now;
|
||||
if (config.guiItem.autoUpdateInterval > 0)
|
||||
{
|
||||
if ((dtNow - autoUpdateGeoTime).Hours % config.guiItem.autoUpdateInterval == 0)
|
||||
{
|
||||
updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
|
||||
{
|
||||
update(false, msg);
|
||||
});
|
||||
autoUpdateGeoTime = dtNow;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(1000 * 3600);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
539
v2rayN/ServiceLib/Handler/UpdateHandler.cs
Normal file
539
v2rayN/ServiceLib/Handler/UpdateHandler.cs
Normal file
@@ -0,0 +1,539 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public class UpdateHandler
|
||||
{
|
||||
private Action<bool, string> _updateFunc;
|
||||
private Config _config;
|
||||
|
||||
public event EventHandler<ResultEventArgs> AbsoluteCompleted;
|
||||
|
||||
public class ResultEventArgs : EventArgs
|
||||
{
|
||||
public bool Success;
|
||||
public string Msg;
|
||||
public string Url;
|
||||
|
||||
public ResultEventArgs(bool success, string msg, string url = "")
|
||||
{
|
||||
Success = success;
|
||||
Msg = msg;
|
||||
Url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckUpdateGuiN(Config config, Action<bool, string> update, bool preRelease)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
var url = string.Empty;
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
downloadHandle.UpdateCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
||||
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(url));
|
||||
fileName = Utils.UrlEncode(fileName);
|
||||
_updateFunc(true, fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
}
|
||||
};
|
||||
downloadHandle.Error += (sender2, args) =>
|
||||
{
|
||||
_updateFunc(false, args.GetException().Message);
|
||||
_updateFunc(false, "");
|
||||
};
|
||||
AbsoluteCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
|
||||
_updateFunc(false, args.Msg);
|
||||
|
||||
url = args.Url;
|
||||
AskToDownload(downloadHandle, url, true).ContinueWith(task =>
|
||||
{
|
||||
_updateFunc(false, "");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
_updateFunc(false, "");
|
||||
}
|
||||
};
|
||||
_updateFunc(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN));
|
||||
CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease);
|
||||
}
|
||||
|
||||
public async void CheckUpdateCore(ECoreType type, Config config, Action<bool, string> update, bool preRelease)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
var url = string.Empty;
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
downloadHandle.UpdateCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
||||
_updateFunc(false, ResUI.MsgUnpacking);
|
||||
|
||||
try
|
||||
{
|
||||
_updateFunc(true, url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_updateFunc(false, ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
}
|
||||
};
|
||||
downloadHandle.Error += (sender2, args) =>
|
||||
{
|
||||
_updateFunc(false, args.GetException().Message);
|
||||
_updateFunc(false, "");
|
||||
};
|
||||
|
||||
AbsoluteCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
|
||||
_updateFunc(false, args.Msg);
|
||||
|
||||
url = args.Url;
|
||||
AskToDownload(downloadHandle, url, true).ContinueWith(task =>
|
||||
{
|
||||
_updateFunc(false, "");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
_updateFunc(false, "");
|
||||
}
|
||||
};
|
||||
_updateFunc(false, string.Format(ResUI.MsgStartUpdating, type));
|
||||
CheckUpdateAsync(downloadHandle, type, preRelease);
|
||||
}
|
||||
|
||||
public void UpdateSubscriptionProcess(Config config, string subId, bool blProxy, Action<bool, string> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
|
||||
_updateFunc(false, ResUI.MsgUpdateSubscriptionStart);
|
||||
var subItem = LazyConfig.Instance.SubItems().OrderBy(t => t.sort).ToList();
|
||||
|
||||
if (subItem == null || subItem.Count <= 0)
|
||||
{
|
||||
_updateFunc(false, ResUI.MsgNoValidSubscription);
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
foreach (var item in subItem)
|
||||
{
|
||||
string id = item.id.TrimEx();
|
||||
string url = item.url.TrimEx();
|
||||
string userAgent = item.userAgent.TrimEx();
|
||||
string hashCode = $"{item.remarks}->";
|
||||
if (Utils.IsNullOrEmpty(id) || Utils.IsNullOrEmpty(url) || (!Utils.IsNullOrEmpty(subId) && item.id != subId))
|
||||
{
|
||||
//_updateFunc(false, $"{hashCode}{ResUI.MsgNoValidSubscription}");
|
||||
continue;
|
||||
}
|
||||
if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.enabled == false)
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var downloadHandle = new DownloadHandler();
|
||||
downloadHandle.Error += (sender2, args) =>
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{args.GetException().Message}");
|
||||
};
|
||||
|
||||
_updateFunc(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
|
||||
|
||||
//one url
|
||||
url = Utils.GetPunycode(url);
|
||||
//convert
|
||||
if (!Utils.IsNullOrEmpty(item.convertTarget))
|
||||
{
|
||||
var subConvertUrl = Utils.IsNullOrEmpty(config.constItem.subConvertUrl) ? Global.SubConvertUrls.FirstOrDefault() : config.constItem.subConvertUrl;
|
||||
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
|
||||
if (!url.Contains("target="))
|
||||
{
|
||||
url += string.Format("&target={0}", item.convertTarget);
|
||||
}
|
||||
if (!url.Contains("config="))
|
||||
{
|
||||
url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault());
|
||||
}
|
||||
}
|
||||
var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent);
|
||||
if (blProxy && Utils.IsNullOrEmpty(result))
|
||||
{
|
||||
result = await downloadHandle.TryDownloadString(url, false, userAgent);
|
||||
}
|
||||
|
||||
//more url
|
||||
if (Utils.IsNullOrEmpty(item.convertTarget) && !Utils.IsNullOrEmpty(item.moreUrl.TrimEx()))
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(result) && Utils.IsBase64String(result!))
|
||||
{
|
||||
result = Utils.Base64Decode(result);
|
||||
}
|
||||
|
||||
var lstUrl = item.moreUrl.TrimEx().Split(",") ?? [];
|
||||
foreach (var it in lstUrl)
|
||||
{
|
||||
var url2 = Utils.GetPunycode(it);
|
||||
if (Utils.IsNullOrEmpty(url2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result2 = await downloadHandle.TryDownloadString(url2, blProxy, userAgent);
|
||||
if (blProxy && Utils.IsNullOrEmpty(result2))
|
||||
{
|
||||
result2 = await downloadHandle.TryDownloadString(url2, false, userAgent);
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(result2))
|
||||
{
|
||||
if (Utils.IsBase64String(result2!))
|
||||
{
|
||||
result += Utils.Base64Decode(result2);
|
||||
}
|
||||
else
|
||||
{
|
||||
result += result2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Utils.IsNullOrEmpty(result))
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
|
||||
if (result?.Length < 99)
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{result}");
|
||||
}
|
||||
|
||||
int ret = ConfigHandler.AddBatchServers(config, result, id, true);
|
||||
if (ret <= 0)
|
||||
{
|
||||
Logging.SaveLog("FailedImportSubscription");
|
||||
Logging.SaveLog(result);
|
||||
}
|
||||
_updateFunc(false,
|
||||
ret > 0
|
||||
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
|
||||
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
|
||||
}
|
||||
_updateFunc(false, "-------------------------------------------------------");
|
||||
}
|
||||
|
||||
_updateFunc(true, $"{ResUI.MsgUpdateSubscriptionEnd}");
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateGeoFileAll(Config config, Action<bool, string> update)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await UpdateGeoFile("geosite", _config, update);
|
||||
await UpdateGeoFile("geoip", _config, update);
|
||||
_updateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
|
||||
});
|
||||
}
|
||||
|
||||
public void RunAvailabilityCheck(Action<bool, string> update)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var time = await (new DownloadHandler()).RunAvailabilityCheck(null);
|
||||
|
||||
update(false, string.Format(ResUI.TestMeOutput, time));
|
||||
});
|
||||
}
|
||||
|
||||
#region private
|
||||
|
||||
private async void CheckUpdateAsync(DownloadHandler downloadHandle, ECoreType type, bool preRelease)
|
||||
{
|
||||
try
|
||||
{
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
||||
string url = coreInfo.coreReleaseApiUrl;
|
||||
|
||||
var result = await downloadHandle.DownloadStringAsync(url, true, Global.AppName);
|
||||
if (!Utils.IsNullOrEmpty(result))
|
||||
{
|
||||
ResponseHandler(type, result, preRelease);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.SaveLog("StatusCode error: " + url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
_updateFunc(false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Core版本
|
||||
/// </summary>
|
||||
private SemanticVersion GetCoreVersion(ECoreType type)
|
||||
{
|
||||
try
|
||||
{
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
||||
string filePath = string.Empty;
|
||||
foreach (string name in coreInfo.coreExes)
|
||||
{
|
||||
string vName = Utils.GetExeName(name);
|
||||
vName = Utils.GetBinPath(vName, coreInfo.coreType.ToString());
|
||||
if (File.Exists(vName))
|
||||
{
|
||||
filePath = vName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
string msg = string.Format(ResUI.NotFoundCore, @"", "", "");
|
||||
//ShowMsg(true, msg);
|
||||
return new SemanticVersion("");
|
||||
}
|
||||
|
||||
using Process p = new();
|
||||
p.StartInfo.FileName = filePath.AppendQuotes();
|
||||
p.StartInfo.Arguments = coreInfo.versionArg;
|
||||
p.StartInfo.WorkingDirectory = Utils.StartupPath();
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.StartInfo.CreateNoWindow = true;
|
||||
p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
|
||||
p.Start();
|
||||
p.WaitForExit(5000);
|
||||
string echo = p.StandardOutput.ReadToEnd();
|
||||
string version = string.Empty;
|
||||
switch (type)
|
||||
{
|
||||
case ECoreType.v2fly:
|
||||
case ECoreType.SagerNet:
|
||||
case ECoreType.Xray:
|
||||
case ECoreType.v2fly_v5:
|
||||
version = Regex.Match(echo, $"{coreInfo.match} ([0-9.]+) \\(").Groups[1].Value;
|
||||
break;
|
||||
|
||||
case ECoreType.clash:
|
||||
case ECoreType.clash_meta:
|
||||
case ECoreType.mihomo:
|
||||
version = Regex.Match(echo, $"v[0-9.]+").Groups[0].Value;
|
||||
break;
|
||||
|
||||
case ECoreType.sing_box:
|
||||
version = Regex.Match(echo, $"([0-9.]+)").Groups[1].Value;
|
||||
break;
|
||||
}
|
||||
return new SemanticVersion(version);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
_updateFunc(false, ex.Message);
|
||||
return new SemanticVersion("");
|
||||
}
|
||||
}
|
||||
|
||||
private void ResponseHandler(ECoreType type, string gitHubReleaseApi, bool preRelease)
|
||||
{
|
||||
try
|
||||
{
|
||||
var gitHubReleases = JsonUtils.Deserialize<List<GitHubRelease>>(gitHubReleaseApi);
|
||||
var gitHubRelease = preRelease ? gitHubReleases?.First() : gitHubReleases?.First(r => r.Prerelease == false);
|
||||
var version = new SemanticVersion(gitHubRelease?.TagName!);
|
||||
var body = gitHubRelease?.Body;
|
||||
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
||||
SemanticVersion curVersion;
|
||||
string message;
|
||||
string? url;
|
||||
switch (type)
|
||||
{
|
||||
case ECoreType.v2fly:
|
||||
case ECoreType.SagerNet:
|
||||
case ECoreType.Xray:
|
||||
case ECoreType.v2fly_v5:
|
||||
{
|
||||
curVersion = GetCoreVersion(type);
|
||||
message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v"));
|
||||
url = string.Format(GetUrlFromCore(coreInfo), version.ToVersionString("v"));
|
||||
break;
|
||||
}
|
||||
case ECoreType.clash:
|
||||
case ECoreType.clash_meta:
|
||||
case ECoreType.mihomo:
|
||||
{
|
||||
curVersion = GetCoreVersion(type);
|
||||
message = string.Format(ResUI.IsLatestCore, type, curVersion);
|
||||
url = string.Format(GetUrlFromCore(coreInfo), version.ToVersionString("v"));
|
||||
break;
|
||||
}
|
||||
case ECoreType.sing_box:
|
||||
{
|
||||
curVersion = GetCoreVersion(type);
|
||||
message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v"));
|
||||
url = string.Format(GetUrlFromCore(coreInfo), version.ToVersionString("v"), version);
|
||||
break;
|
||||
}
|
||||
case ECoreType.v2rayN:
|
||||
{
|
||||
curVersion = new SemanticVersion(Utils.GetVersionInfo());
|
||||
message = string.Format(ResUI.IsLatestN, type, curVersion);
|
||||
url = string.Format(GetUrlFromCore(coreInfo), version);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException("Type");
|
||||
}
|
||||
|
||||
if (curVersion >= version && version != new SemanticVersion(0, 0, 0))
|
||||
{
|
||||
AbsoluteCompleted?.Invoke(this, new ResultEventArgs(false, message));
|
||||
return;
|
||||
}
|
||||
|
||||
AbsoluteCompleted?.Invoke(this, new ResultEventArgs(true, body, url));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
_updateFunc(false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetUrlFromCore(CoreInfo? coreInfo)
|
||||
{
|
||||
if (Utils.IsWindows())
|
||||
{
|
||||
return RuntimeInformation.ProcessArchitecture switch
|
||||
{
|
||||
Architecture.Arm64 => coreInfo?.coreDownloadUrlArm64,
|
||||
Architecture.X86 => coreInfo?.coreDownloadUrl32,
|
||||
Architecture.X64 => coreInfo?.coreDownloadUrl64,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
else if (Utils.IsLinux())
|
||||
{
|
||||
return RuntimeInformation.ProcessArchitecture switch
|
||||
{
|
||||
Architecture.Arm64 => coreInfo?.coreDownloadUrlLinuxArm64,
|
||||
Architecture.X86 => coreInfo?.coreDownloadUrlLinux32,
|
||||
Architecture.X64 => coreInfo?.coreDownloadUrlLinux64,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task AskToDownload(DownloadHandler downloadHandle, string url, bool blAsk)
|
||||
{
|
||||
//bool blDownload = false;
|
||||
//if (blAsk)
|
||||
//{
|
||||
// if (UI.ShowYesNo(string.Format(ResUI.DownloadYesNo, url)) == MessageBoxResult.Yes)
|
||||
// {
|
||||
// blDownload = true;
|
||||
// }
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// blDownload = true;
|
||||
//}
|
||||
//if (blDownload)
|
||||
//{
|
||||
await downloadHandle.DownloadFileAsync(url, true, 60);
|
||||
//}
|
||||
}
|
||||
|
||||
private async Task UpdateGeoFile(string geoName, Config config, Action<bool, string> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
var url = string.Format(Global.GeoUrl, geoName);
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
downloadHandle.UpdateCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, geoName));
|
||||
|
||||
try
|
||||
{
|
||||
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(url));
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
string targetPath = Utils.GetBinPath($"{geoName}.dat");
|
||||
File.Copy(fileName, targetPath, true);
|
||||
|
||||
File.Delete(fileName);
|
||||
//_updateFunc(true, "");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_updateFunc(false, ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
}
|
||||
};
|
||||
downloadHandle.Error += (sender2, args) =>
|
||||
{
|
||||
_updateFunc(false, args.GetException().Message);
|
||||
};
|
||||
await AskToDownload(downloadHandle, url, false);
|
||||
}
|
||||
|
||||
#endregion private
|
||||
}
|
||||
}
|
||||
17
v2rayN/ServiceLib/Models/CheckUpdateItem.cs
Normal file
17
v2rayN/ServiceLib/Models/CheckUpdateItem.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class CheckUpdateItem
|
||||
{
|
||||
public bool? isSelected { get; set; }
|
||||
public string coreType { get; set; }
|
||||
public string? remarks { get; set; }
|
||||
public string? fileName { get; set; }
|
||||
public bool? isFinished { get; set; }
|
||||
}
|
||||
}
|
||||
17
v2rayN/ServiceLib/Models/ClashConnectionModel.cs
Normal file
17
v2rayN/ServiceLib/Models/ClashConnectionModel.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class ClashConnectionModel
|
||||
{
|
||||
public string id { get; set; }
|
||||
public string network { get; set; }
|
||||
public string type { get; set; }
|
||||
public string host { get; set; }
|
||||
public ulong upload { get; set; }
|
||||
public ulong download { get; set; }
|
||||
public string uploadTraffic { get; set; }
|
||||
public string downloadTraffic { get; set; }
|
||||
public double time { get; set; }
|
||||
public string elapsed { get; set; }
|
||||
public string chain { get; set; }
|
||||
}
|
||||
}
|
||||
37
v2rayN/ServiceLib/Models/ClashConnections.cs
Normal file
37
v2rayN/ServiceLib/Models/ClashConnections.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class ClashConnections
|
||||
{
|
||||
public ulong downloadTotal { get; set; }
|
||||
public ulong uploadTotal { get; set; }
|
||||
public List<ConnectionItem>? connections { get; set; }
|
||||
}
|
||||
|
||||
public class ConnectionItem
|
||||
{
|
||||
public string id { get; set; } = string.Empty;
|
||||
public MetadataItem metadata { get; set; }
|
||||
public ulong upload { get; set; }
|
||||
public ulong download { get; set; }
|
||||
public DateTime start { get; set; }
|
||||
public List<string>? chains { get; set; }
|
||||
public string rule { get; set; }
|
||||
public string rulePayload { get; set; }
|
||||
}
|
||||
|
||||
public class MetadataItem
|
||||
{
|
||||
public string network { get; set; }
|
||||
public string type { get; set; }
|
||||
public string sourceIP { get; set; }
|
||||
public string destinationIP { get; set; }
|
||||
public string sourcePort { get; set; }
|
||||
public string destinationPort { get; set; }
|
||||
public string host { get; set; }
|
||||
public string nsMode { get; set; }
|
||||
public object uid { get; set; }
|
||||
public string process { get; set; }
|
||||
public string processPath { get; set; }
|
||||
public string remoteDestination { get; set; }
|
||||
}
|
||||
}
|
||||
17
v2rayN/ServiceLib/Models/ClashProviders.cs
Normal file
17
v2rayN/ServiceLib/Models/ClashProviders.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using static ServiceLib.Models.ClashProxies;
|
||||
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class ClashProviders
|
||||
{
|
||||
public Dictionary<String, ProvidersItem> providers { get; set; }
|
||||
|
||||
public class ProvidersItem
|
||||
{
|
||||
public string name { get; set; }
|
||||
public ProxiesItem[] proxies { get; set; }
|
||||
public string type { get; set; }
|
||||
public string vehicleType { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
24
v2rayN/ServiceLib/Models/ClashProxies.cs
Normal file
24
v2rayN/ServiceLib/Models/ClashProxies.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class ClashProxies
|
||||
{
|
||||
public Dictionary<String, ProxiesItem> proxies { get; set; }
|
||||
|
||||
public class ProxiesItem
|
||||
{
|
||||
public string[] all { get; set; }
|
||||
public List<HistoryItem> history { get; set; }
|
||||
public string name { get; set; }
|
||||
public string type { get; set; }
|
||||
public bool udp { get; set; }
|
||||
public string now { get; set; }
|
||||
public int delay { get; set; }
|
||||
}
|
||||
|
||||
public class HistoryItem
|
||||
{
|
||||
public string time { get; set; }
|
||||
public int delay { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
18
v2rayN/ServiceLib/Models/ClashProxyModel.cs
Normal file
18
v2rayN/ServiceLib/Models/ClashProxyModel.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class ClashProxyModel
|
||||
{
|
||||
public string name { get; set; }
|
||||
|
||||
public string type { get; set; }
|
||||
|
||||
public string now { get; set; }
|
||||
|
||||
public int delay { get; set; }
|
||||
|
||||
public string delayName { get; set; }
|
||||
|
||||
public bool isActive { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
namespace v2rayN.Mode
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
class ComboItem
|
||||
public class ComboItem
|
||||
{
|
||||
public int ID
|
||||
public string ID
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
v2rayN/ServiceLib/Models/Config.cs
Normal file
55
v2rayN/ServiceLib/Models/Config.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 本软件配置文件实体类
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class Config
|
||||
{
|
||||
#region property
|
||||
|
||||
public string indexId { get; set; }
|
||||
public string subIndexId { get; set; }
|
||||
public string systemProxyExceptions { get; set; }
|
||||
public string systemProxyAdvancedProtocol { get; set; }
|
||||
|
||||
public ECoreType runningCoreType { get; set; }
|
||||
|
||||
public bool IsRunningCore(ECoreType type)
|
||||
{
|
||||
if (type == ECoreType.Xray && runningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5 or ECoreType.SagerNet)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (type == ECoreType.clash && runningCoreType is ECoreType.sing_box or ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion property
|
||||
|
||||
#region other entities
|
||||
|
||||
public CoreBasicItem coreBasicItem { get; set; }
|
||||
public TunModeItem tunModeItem { get; set; }
|
||||
public KcpItem kcpItem { get; set; }
|
||||
public GrpcItem grpcItem { get; set; }
|
||||
public RoutingBasicItem routingBasicItem { get; set; }
|
||||
public GUIItem guiItem { get; set; }
|
||||
public UIItem uiItem { get; set; }
|
||||
public ConstItem constItem { get; set; }
|
||||
public SpeedTestItem speedTestItem { get; set; }
|
||||
public Mux4RayItem mux4RayItem { get; set; }
|
||||
public Mux4SboxItem mux4SboxItem { get; set; }
|
||||
public HysteriaItem hysteriaItem { get; set; }
|
||||
public ClashUIItem clashUIItem { get; set; }
|
||||
public SystemProxyItem systemProxyItem { get; set; }
|
||||
public List<InItem> inbound { get; set; }
|
||||
public List<KeyEventItem> globalHotkeys { get; set; }
|
||||
public List<CoreTypeItem> coreTypeItem { get; set; }
|
||||
|
||||
#endregion other entities
|
||||
}
|
||||
}
|
||||
245
v2rayN/ServiceLib/Models/ConfigItems.cs
Normal file
245
v2rayN/ServiceLib/Models/ConfigItems.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class CoreBasicItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 允许日志
|
||||
/// </summary>
|
||||
public bool logEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志等级
|
||||
/// </summary>
|
||||
public string loglevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 允许Mux多路复用
|
||||
/// </summary>
|
||||
public bool muxEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许不安全连接
|
||||
/// </summary>
|
||||
public bool defAllowInsecure { get; set; }
|
||||
|
||||
public string defFingerprint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认用户代理
|
||||
/// </summary>
|
||||
public string defUserAgent { get; set; }
|
||||
|
||||
public bool enableFragment { get; set; }
|
||||
|
||||
public bool enableCacheFile4Sbox { get; set; } = true;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class InItem
|
||||
{
|
||||
public int localPort { get; set; }
|
||||
|
||||
public string protocol { get; set; }
|
||||
|
||||
public bool udpEnabled { get; set; }
|
||||
|
||||
public bool sniffingEnabled { get; set; } = true;
|
||||
public List<string>? destOverride { get; set; } = ["http", "tls"];
|
||||
public bool routeOnly { get; set; }
|
||||
public bool allowLANConn { get; set; }
|
||||
|
||||
public bool newPort4LAN { get; set; }
|
||||
|
||||
public string user { get; set; }
|
||||
|
||||
public string pass { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class KcpItem
|
||||
{
|
||||
public int mtu { get; set; }
|
||||
|
||||
public int tti { get; set; }
|
||||
|
||||
public int uplinkCapacity { get; set; }
|
||||
|
||||
public int downlinkCapacity { get; set; }
|
||||
|
||||
public bool congestion { get; set; }
|
||||
|
||||
public int readBufferSize { get; set; }
|
||||
|
||||
public int writeBufferSize { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GrpcItem
|
||||
{
|
||||
public int idle_timeout { get; set; }
|
||||
public int health_check_timeout { get; set; }
|
||||
public bool permit_without_stream { get; set; }
|
||||
public int initial_windows_size { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GUIItem
|
||||
{
|
||||
public bool autoRun { get; set; }
|
||||
|
||||
public bool enableStatistics { get; set; }
|
||||
|
||||
public bool keepOlderDedupl { get; set; }
|
||||
|
||||
public bool ignoreGeoUpdateCore { get; set; } = true;
|
||||
|
||||
public int autoUpdateInterval { get; set; } = 10;
|
||||
|
||||
public bool checkPreReleaseUpdate { get; set; } = false;
|
||||
|
||||
public bool enableSecurityProtocolTls13 { get; set; }
|
||||
|
||||
public int trayMenuServersLimit { get; set; } = 20;
|
||||
|
||||
public bool enableHWA { get; set; } = false;
|
||||
|
||||
public bool enableLog { get; set; } = true;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class UIItem
|
||||
{
|
||||
public bool enableAutoAdjustMainLvColWidth { get; set; }
|
||||
public bool enableUpdateSubOnlyRemarksExist { get; set; }
|
||||
public double mainWidth { get; set; }
|
||||
public double mainHeight { get; set; }
|
||||
public double mainGirdHeight1 { get; set; }
|
||||
public double mainGirdHeight2 { get; set; }
|
||||
public EGirdOrientation mainGirdOrientation { get; set; } = EGirdOrientation.Vertical;
|
||||
public bool colorModeDark { get; set; }
|
||||
public bool followSystemTheme { get; set; }
|
||||
public string? colorPrimaryName { get; set; }
|
||||
public string currentLanguage { get; set; }
|
||||
public string currentFontFamily { get; set; }
|
||||
public int currentFontSize { get; set; }
|
||||
public bool enableDragDropSort { get; set; }
|
||||
public bool doubleClick2Activate { get; set; }
|
||||
public bool autoHideStartup { get; set; }
|
||||
public string mainMsgFilter { get; set; }
|
||||
public List<ColumnItem> mainColumnItem { get; set; }
|
||||
public bool showInTaskbar { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ConstItem
|
||||
{
|
||||
public string defIEProxyExceptions { get; set; }
|
||||
public string subConvertUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class KeyEventItem
|
||||
{
|
||||
public EGlobalHotkey eGlobalHotkey { get; set; }
|
||||
|
||||
public bool Alt { get; set; }
|
||||
|
||||
public bool Control { get; set; }
|
||||
|
||||
public bool Shift { get; set; }
|
||||
|
||||
public int? KeyCode { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class CoreTypeItem
|
||||
{
|
||||
public EConfigType configType { get; set; }
|
||||
|
||||
public ECoreType coreType { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TunModeItem
|
||||
{
|
||||
public bool enableTun { get; set; }
|
||||
public bool strictRoute { get; set; } = true;
|
||||
public string stack { get; set; }
|
||||
public int mtu { get; set; }
|
||||
public bool enableExInbound { get; set; }
|
||||
public bool enableIPv6Address { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class SpeedTestItem
|
||||
{
|
||||
public int speedTestTimeout { get; set; }
|
||||
public string speedTestUrl { get; set; }
|
||||
public string speedPingTestUrl { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RoutingBasicItem
|
||||
{
|
||||
public string domainStrategy { get; set; }
|
||||
public string domainStrategy4Singbox { get; set; }
|
||||
public string domainMatcher { get; set; }
|
||||
public string routingIndexId { get; set; }
|
||||
public bool enableRoutingAdvanced { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ColumnItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Width { get; set; }
|
||||
public int Index { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Mux4RayItem
|
||||
{
|
||||
public int? concurrency { get; set; }
|
||||
public int? xudpConcurrency { get; set; }
|
||||
public string? xudpProxyUDP443 { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Mux4SboxItem
|
||||
{
|
||||
public string protocol { get; set; }
|
||||
public int max_connections { get; set; }
|
||||
public bool? padding { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class HysteriaItem
|
||||
{
|
||||
public int up_mbps { get; set; }
|
||||
public int down_mbps { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ClashUIItem
|
||||
{
|
||||
public ERuleMode ruleMode { get; set; }
|
||||
public bool enableIPv6 { get; set; }
|
||||
public bool enableMixinContent { get; set; }
|
||||
public int proxiesSorting { get; set; }
|
||||
public bool proxiesAutoRefresh { get; set; }
|
||||
public int proxiesAutoDelayTestInterval { get; set; } = 10;
|
||||
public int connectionsSorting { get; set; }
|
||||
public bool connectionsAutoRefresh { get; set; }
|
||||
public int connectionsRefreshInterval { get; set; } = 2;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class SystemProxyItem
|
||||
{
|
||||
public ESysProxyType sysProxyType { get; set; }
|
||||
public string systemProxyExceptions { get; set; }
|
||||
public bool notProxyLocalAddress { get; set; } = true;
|
||||
public string systemProxyAdvancedProtocol { get; set; }
|
||||
}
|
||||
}
|
||||
30
v2rayN/ServiceLib/Models/CoreInfo.cs
Normal file
30
v2rayN/ServiceLib/Models/CoreInfo.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class CoreInfo
|
||||
{
|
||||
public ECoreType coreType { get; set; }
|
||||
|
||||
public List<string> coreExes { get; set; }
|
||||
|
||||
public string arguments { get; set; }
|
||||
|
||||
public string coreUrl { get; set; }
|
||||
|
||||
public string coreReleaseApiUrl { get; set; }
|
||||
|
||||
public string coreDownloadUrl32 { get; set; }
|
||||
|
||||
public string coreDownloadUrl64 { get; set; }
|
||||
|
||||
public string coreDownloadUrlArm64 { get; set; }
|
||||
public string? coreDownloadUrlLinux32 { get; set; }
|
||||
public string? coreDownloadUrlLinux64 { get; set; }
|
||||
public string? coreDownloadUrlLinuxArm64 { get; set; }
|
||||
|
||||
public string match { get; set; }
|
||||
public string versionArg { get; set; }
|
||||
|
||||
public bool redirectInfo { get; set; }
|
||||
}
|
||||
}
|
||||
20
v2rayN/ServiceLib/Models/DNSItem.cs
Normal file
20
v2rayN/ServiceLib/Models/DNSItem.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using SQLite;
|
||||
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class DNSItem
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string id { get; set; }
|
||||
|
||||
public string remarks { get; set; }
|
||||
public bool enabled { get; set; } = true;
|
||||
public ECoreType coreType { get; set; }
|
||||
public bool useSystemHosts { get; set; }
|
||||
public string? normalDNS { get; set; }
|
||||
public string? tunDNS { get; set; }
|
||||
public string? domainStrategy4Freedom { get; set; }
|
||||
public string? domainDNSAddress { get; set; }
|
||||
}
|
||||
}
|
||||
68
v2rayN/ServiceLib/Models/GitHubRelease.cs
Normal file
68
v2rayN/ServiceLib/Models/GitHubRelease.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class GitHubReleaseAsset
|
||||
{
|
||||
[JsonPropertyName("url")] public string Url { get; set; }
|
||||
|
||||
[JsonPropertyName("id")] public int Id { get; set; }
|
||||
|
||||
[JsonPropertyName("node_id")] public string NodeId { get; set; }
|
||||
|
||||
[JsonPropertyName("name")] public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("label")] public object Label { get; set; }
|
||||
|
||||
[JsonPropertyName("content_type")] public string ContentType { get; set; }
|
||||
|
||||
[JsonPropertyName("state")] public string State { get; set; }
|
||||
|
||||
[JsonPropertyName("size")] public int Size { get; set; }
|
||||
|
||||
[JsonPropertyName("download_count")] public int DownloadCount { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("updated_at")] public DateTime UpdatedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("browser_download_url")] public string BrowserDownloadUrl { get; set; }
|
||||
}
|
||||
|
||||
public class GitHubRelease
|
||||
{
|
||||
[JsonPropertyName("url")] public string Url { get; set; }
|
||||
|
||||
[JsonPropertyName("assets_url")] public string AssetsUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("upload_url")] public string UploadUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("html_url")] public string HtmlUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("id")] public int Id { get; set; }
|
||||
|
||||
[JsonPropertyName("node_id")] public string NodeId { get; set; }
|
||||
|
||||
[JsonPropertyName("tag_name")] public string TagName { get; set; }
|
||||
|
||||
[JsonPropertyName("target_commitish")] public string TargetCommitish { get; set; }
|
||||
|
||||
[JsonPropertyName("name")] public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("draft")] public bool Draft { get; set; }
|
||||
|
||||
[JsonPropertyName("prerelease")] public bool Prerelease { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("published_at")] public DateTime PublishedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("assets")] public List<GitHubReleaseAsset> Assets { get; set; }
|
||||
|
||||
[JsonPropertyName("tarball_url")] public string TarballUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("zipball_url")] public string ZipballUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("body")] public string Body { get; set; }
|
||||
}
|
||||
}
|
||||
15
v2rayN/ServiceLib/Models/ProfileExItem.cs
Normal file
15
v2rayN/ServiceLib/Models/ProfileExItem.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using SQLite;
|
||||
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class ProfileExItem
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string indexId { get; set; }
|
||||
|
||||
public int delay { get; set; }
|
||||
public decimal speed { get; set; }
|
||||
public int sort { get; set; }
|
||||
}
|
||||
}
|
||||
190
v2rayN/ServiceLib/Models/ProfileItem.cs
Normal file
190
v2rayN/ServiceLib/Models/ProfileItem.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using SQLite;
|
||||
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class ProfileItem
|
||||
{
|
||||
public ProfileItem()
|
||||
{
|
||||
indexId = string.Empty;
|
||||
configType = EConfigType.VMess;
|
||||
configVersion = 2;
|
||||
address = string.Empty;
|
||||
port = 0;
|
||||
id = string.Empty;
|
||||
alterId = 0;
|
||||
security = string.Empty;
|
||||
network = string.Empty;
|
||||
remarks = string.Empty;
|
||||
headerType = string.Empty;
|
||||
requestHost = string.Empty;
|
||||
path = string.Empty;
|
||||
streamSecurity = string.Empty;
|
||||
allowInsecure = string.Empty;
|
||||
subid = string.Empty;
|
||||
flow = string.Empty;
|
||||
}
|
||||
|
||||
#region function
|
||||
|
||||
public string GetSummary()
|
||||
{
|
||||
string summary = string.Format("[{0}] ", (configType).ToString());
|
||||
string[] arrAddr = address.Split('.');
|
||||
string addr;
|
||||
if (arrAddr.Length > 2)
|
||||
{
|
||||
addr = $"{arrAddr[0]}***{arrAddr[arrAddr.Length - 1]}";
|
||||
}
|
||||
else if (arrAddr.Length > 1)
|
||||
{
|
||||
addr = $"***{arrAddr[arrAddr.Length - 1]}";
|
||||
}
|
||||
else
|
||||
{
|
||||
addr = address;
|
||||
}
|
||||
switch (configType)
|
||||
{
|
||||
case EConfigType.Custom:
|
||||
summary += string.Format("[{1}]{0}", remarks, coreType.ToString());
|
||||
break;
|
||||
|
||||
default:
|
||||
summary += string.Format("{0}({1}:{2})", remarks, addr, port);
|
||||
break;
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
public List<string> GetAlpn()
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(alpn))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Utils.String2List(alpn);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetNetwork()
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(network) || !Global.Networks.Contains(network))
|
||||
{
|
||||
return Global.DefaultNetwork;
|
||||
}
|
||||
return network.TrimEx();
|
||||
}
|
||||
|
||||
#endregion function
|
||||
|
||||
[PrimaryKey]
|
||||
public string indexId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// config type(1=normal,2=custom)
|
||||
/// </summary>
|
||||
public EConfigType configType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 版本(现在=2)
|
||||
/// </summary>
|
||||
public int configVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 远程服务器地址
|
||||
/// </summary>
|
||||
public string address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 远程服务器端口
|
||||
/// </summary>
|
||||
public int port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 远程服务器ID
|
||||
/// </summary>
|
||||
public string id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 远程服务器额外ID
|
||||
/// </summary>
|
||||
public int alterId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 本地安全策略
|
||||
/// </summary>
|
||||
public string security { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// tcp,kcp,ws,h2,quic
|
||||
/// </summary>
|
||||
public string network { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string remarks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 伪装类型
|
||||
/// </summary>
|
||||
public string headerType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 伪装的域名
|
||||
/// </summary>
|
||||
public string requestHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ws h2 path
|
||||
/// </summary>
|
||||
public string path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 传输层安全
|
||||
/// </summary>
|
||||
public string streamSecurity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许不安全连接(用于客户端)
|
||||
/// </summary>
|
||||
public string allowInsecure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SubItem id
|
||||
/// </summary>
|
||||
public string subid { get; set; }
|
||||
|
||||
public bool isSub { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// VLESS flow
|
||||
/// </summary>
|
||||
public string flow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// tls sni
|
||||
/// </summary>
|
||||
public string sni { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// tls alpn
|
||||
/// </summary>
|
||||
public string alpn { get; set; } = string.Empty;
|
||||
|
||||
public ECoreType? coreType { get; set; }
|
||||
|
||||
public int? preSocksPort { get; set; }
|
||||
|
||||
public string fingerprint { get; set; }
|
||||
|
||||
public bool displayLog { get; set; } = true;
|
||||
public string publicKey { get; set; }
|
||||
public string shortId { get; set; }
|
||||
public string spiderX { get; set; }
|
||||
}
|
||||
}
|
||||
18
v2rayN/ServiceLib/Models/ProfileItemModel.cs
Normal file
18
v2rayN/ServiceLib/Models/ProfileItemModel.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class ProfileItemModel : ProfileItem
|
||||
{
|
||||
public bool isActive { get; set; }
|
||||
public string subRemarks { get; set; }
|
||||
public int delay { get; set; }
|
||||
public decimal speed { get; set; }
|
||||
public int sort { get; set; }
|
||||
public string delayVal { get; set; }
|
||||
public string speedVal { get; set; }
|
||||
public string todayUp { get; set; }
|
||||
public string todayDown { get; set; }
|
||||
public string totalUp { get; set; }
|
||||
public string totalDown { get; set; }
|
||||
}
|
||||
}
|
||||
23
v2rayN/ServiceLib/Models/RoutingItem.cs
Normal file
23
v2rayN/ServiceLib/Models/RoutingItem.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using SQLite;
|
||||
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class RoutingItem
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string id { get; set; }
|
||||
|
||||
public string remarks { get; set; }
|
||||
public string url { get; set; }
|
||||
public string ruleSet { get; set; }
|
||||
public int ruleNum { get; set; }
|
||||
public bool enabled { get; set; } = true;
|
||||
public bool locked { get; set; }
|
||||
public string customIcon { get; set; }
|
||||
public string customRulesetPath4Singbox { get; set; }
|
||||
public string domainStrategy { get; set; }
|
||||
public string domainStrategy4Singbox { get; set; }
|
||||
public int sort { get; set; }
|
||||
}
|
||||
}
|
||||
8
v2rayN/ServiceLib/Models/RoutingItemModel.cs
Normal file
8
v2rayN/ServiceLib/Models/RoutingItemModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class RoutingItemModel : RoutingItem
|
||||
{
|
||||
public bool isActive { get; set; }
|
||||
}
|
||||
}
|
||||
26
v2rayN/ServiceLib/Models/RulesItem.cs
Normal file
26
v2rayN/ServiceLib/Models/RulesItem.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class RulesItem
|
||||
{
|
||||
public string id { get; set; }
|
||||
public string? type { get; set; }
|
||||
|
||||
public string? port { get; set; }
|
||||
public string? network { get; set; }
|
||||
|
||||
public List<string>? inboundTag { get; set; }
|
||||
|
||||
public string? outboundTag { get; set; }
|
||||
|
||||
public List<string>? ip { get; set; }
|
||||
|
||||
public List<string>? domain { get; set; }
|
||||
|
||||
public List<string>? protocol { get; set; }
|
||||
|
||||
public List<string>? process { get; set; }
|
||||
|
||||
public bool enabled { get; set; } = true;
|
||||
}
|
||||
}
|
||||
14
v2rayN/ServiceLib/Models/RulesItemModel.cs
Normal file
14
v2rayN/ServiceLib/Models/RulesItemModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class RulesItemModel : RulesItem
|
||||
{
|
||||
public string inboundTags { get; set; }
|
||||
|
||||
public string ips { get; set; }
|
||||
|
||||
public string domains { get; set; }
|
||||
|
||||
public string protocols { get; set; }
|
||||
}
|
||||
}
|
||||
40
v2rayN/ServiceLib/Models/ServerSpeedItem.cs
Normal file
40
v2rayN/ServiceLib/Models/ServerSpeedItem.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class ServerSpeedItem : ServerStatItem
|
||||
{
|
||||
public long proxyUp
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long proxyDown
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long directUp
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long directDown
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TrafficItem
|
||||
{
|
||||
public ulong up
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong down
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
v2rayN/ServiceLib/Models/ServerStatItem.cs
Normal file
39
v2rayN/ServiceLib/Models/ServerStatItem.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using SQLite;
|
||||
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class ServerStatItem
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string indexId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long totalUp
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long totalDown
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long todayUp
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long todayDown
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long dateNow
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
v2rayN/ServiceLib/Models/ServerTestItem.cs
Normal file
13
v2rayN/ServiceLib/Models/ServerTestItem.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
[Serializable]
|
||||
public class ServerTestItem
|
||||
{
|
||||
public string indexId { get; set; }
|
||||
public string address { get; set; }
|
||||
public int port { get; set; }
|
||||
public EConfigType configType { get; set; }
|
||||
public bool allowTest { get; set; }
|
||||
public int delay { get; set; }
|
||||
}
|
||||
}
|
||||
256
v2rayN/ServiceLib/Models/SingboxConfig.cs
Normal file
256
v2rayN/ServiceLib/Models/SingboxConfig.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class SingboxConfig
|
||||
{
|
||||
public Log4Sbox log { get; set; }
|
||||
public Dns4Sbox? dns { get; set; }
|
||||
public List<Inbound4Sbox> inbounds { get; set; }
|
||||
public List<Outbound4Sbox> outbounds { get; set; }
|
||||
public Route4Sbox route { get; set; }
|
||||
public Experimental4Sbox? experimental { get; set; }
|
||||
}
|
||||
|
||||
public class Log4Sbox
|
||||
{
|
||||
public bool? disabled { get; set; }
|
||||
public string level { get; set; }
|
||||
public string output { get; set; }
|
||||
public bool? timestamp { get; set; }
|
||||
}
|
||||
|
||||
public class Dns4Sbox
|
||||
{
|
||||
public List<Server4Sbox> servers { get; set; }
|
||||
public List<Rule4Sbox> rules { get; set; }
|
||||
public string? final { get; set; }
|
||||
public string? strategy { get; set; }
|
||||
public bool? disable_cache { get; set; }
|
||||
public bool? disable_expire { get; set; }
|
||||
public bool? independent_cache { get; set; }
|
||||
public bool? reverse_mapping { get; set; }
|
||||
public string? client_subnet { get; set; }
|
||||
public Fakeip4Sbox? fakeip { get; set; }
|
||||
}
|
||||
|
||||
public class Route4Sbox
|
||||
{
|
||||
public bool? auto_detect_interface { get; set; }
|
||||
public List<Rule4Sbox> rules { get; set; }
|
||||
public List<Ruleset4Sbox>? rule_set { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Rule4Sbox
|
||||
{
|
||||
public string? outbound { get; set; }
|
||||
public string? server { get; set; }
|
||||
public bool? disable_cache { get; set; }
|
||||
public string? type { get; set; }
|
||||
public string? mode { get; set; }
|
||||
public bool? ip_is_private { get; set; }
|
||||
public string? client_subnet { get; set; }
|
||||
public bool? invert { get; set; }
|
||||
public string? clash_mode { get; set; }
|
||||
public List<string>? inbound { get; set; }
|
||||
public List<string>? protocol { get; set; }
|
||||
public List<string>? network { get; set; }
|
||||
public List<int>? port { get; set; }
|
||||
public List<string>? port_range { get; set; }
|
||||
public List<string>? geosite { get; set; }
|
||||
public List<string>? domain { get; set; }
|
||||
public List<string>? domain_suffix { get; set; }
|
||||
public List<string>? domain_keyword { get; set; }
|
||||
public List<string>? domain_regex { get; set; }
|
||||
public List<string>? geoip { get; set; }
|
||||
public List<string>? ip_cidr { get; set; }
|
||||
public List<string>? source_ip_cidr { get; set; }
|
||||
public List<string>? process_name { get; set; }
|
||||
public List<string>? rule_set { get; set; }
|
||||
public List<Rule4Sbox>? rules { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Inbound4Sbox
|
||||
{
|
||||
public string type { get; set; }
|
||||
public string tag { get; set; }
|
||||
public string listen { get; set; }
|
||||
public int? listen_port { get; set; }
|
||||
public string? domain_strategy { get; set; }
|
||||
public string interface_name { get; set; }
|
||||
public string inet4_address { get; set; }
|
||||
public string? inet6_address { get; set; }
|
||||
public int? mtu { get; set; }
|
||||
public bool? auto_route { get; set; }
|
||||
public bool? strict_route { get; set; }
|
||||
public bool? endpoint_independent_nat { get; set; }
|
||||
public string? stack { get; set; }
|
||||
public bool? sniff { get; set; }
|
||||
public bool? sniff_override_destination { get; set; }
|
||||
public List<User4Sbox> users { get; set; }
|
||||
}
|
||||
|
||||
public class User4Sbox
|
||||
{
|
||||
public string username { get; set; }
|
||||
public string password { get; set; }
|
||||
}
|
||||
|
||||
public class Outbound4Sbox
|
||||
{
|
||||
public string type { get; set; }
|
||||
public string tag { get; set; }
|
||||
public string? server { get; set; }
|
||||
public int? server_port { get; set; }
|
||||
public string uuid { get; set; }
|
||||
public string security { get; set; }
|
||||
public int? alter_id { get; set; }
|
||||
public string flow { get; set; }
|
||||
public int? up_mbps { get; set; }
|
||||
public int? down_mbps { get; set; }
|
||||
public string auth_str { get; set; }
|
||||
public int? recv_window_conn { get; set; }
|
||||
public int? recv_window { get; set; }
|
||||
public bool? disable_mtu_discovery { get; set; }
|
||||
public string? detour { get; set; }
|
||||
public string method { get; set; }
|
||||
public string username { get; set; }
|
||||
public string password { get; set; }
|
||||
public string congestion_control { get; set; }
|
||||
public string? version { get; set; }
|
||||
public string? network { get; set; }
|
||||
public string? packet_encoding { get; set; }
|
||||
public string[]? local_address { get; set; }
|
||||
public string? private_key { get; set; }
|
||||
public string? peer_public_key { get; set; }
|
||||
public int[]? reserved { get; set; }
|
||||
public int? mtu { get; set; }
|
||||
public string? plugin { get; set; }
|
||||
public string? plugin_opts { get; set; }
|
||||
public Tls4Sbox? tls { get; set; }
|
||||
public Multiplex4Sbox? multiplex { get; set; }
|
||||
public Transport4Sbox? transport { get; set; }
|
||||
public HyObfs4Sbox? obfs { get; set; }
|
||||
public List<string>? outbounds { get; set; }
|
||||
public bool? interrupt_exist_connections { get; set; }
|
||||
}
|
||||
|
||||
public class Tls4Sbox
|
||||
{
|
||||
public bool enabled { get; set; }
|
||||
public string server_name { get; set; }
|
||||
public bool? insecure { get; set; }
|
||||
public List<string> alpn { get; set; }
|
||||
public Utls4Sbox utls { get; set; }
|
||||
public Reality4Sbox reality { get; set; }
|
||||
}
|
||||
|
||||
public class Multiplex4Sbox
|
||||
{
|
||||
public bool enabled { get; set; }
|
||||
public string protocol { get; set; }
|
||||
public int max_connections { get; set; }
|
||||
public bool? padding { get; set; }
|
||||
}
|
||||
|
||||
public class Utls4Sbox
|
||||
{
|
||||
public bool enabled { get; set; }
|
||||
public string fingerprint { get; set; }
|
||||
}
|
||||
|
||||
public class Reality4Sbox
|
||||
{
|
||||
public bool enabled { get; set; }
|
||||
public string public_key { get; set; }
|
||||
public string short_id { get; set; }
|
||||
}
|
||||
|
||||
public class Transport4Sbox
|
||||
{
|
||||
public string? type { get; set; }
|
||||
public object? host { get; set; }
|
||||
public string? path { get; set; }
|
||||
public Headers4Sbox? headers { get; set; }
|
||||
|
||||
public string? service_name { get; set; }
|
||||
public string? idle_timeout { get; set; }
|
||||
public string? ping_timeout { get; set; }
|
||||
public bool? permit_without_stream { get; set; }
|
||||
}
|
||||
|
||||
public class Headers4Sbox
|
||||
{
|
||||
public string? Host { get; set; }
|
||||
}
|
||||
|
||||
public class HyObfs4Sbox
|
||||
{
|
||||
public string? type { get; set; }
|
||||
public string? password { get; set; }
|
||||
}
|
||||
|
||||
public class Server4Sbox
|
||||
{
|
||||
public string? tag { get; set; }
|
||||
public string? address { get; set; }
|
||||
public string? address_resolver { get; set; }
|
||||
public string? address_strategy { get; set; }
|
||||
public string? strategy { get; set; }
|
||||
public string? detour { get; set; }
|
||||
public string? client_subnet { get; set; }
|
||||
}
|
||||
|
||||
public class Experimental4Sbox
|
||||
{
|
||||
public CacheFile4Sbox? cache_file { get; set; }
|
||||
public V2ray_Api4Sbox? v2ray_api { get; set; }
|
||||
public Clash_Api4Sbox? clash_api { get; set; }
|
||||
}
|
||||
|
||||
public class V2ray_Api4Sbox
|
||||
{
|
||||
public string listen { get; set; }
|
||||
public Stats4Sbox stats { get; set; }
|
||||
}
|
||||
|
||||
public class Clash_Api4Sbox
|
||||
{
|
||||
public string? external_controller { get; set; }
|
||||
public bool? store_selected { get; set; }
|
||||
}
|
||||
|
||||
public class Stats4Sbox
|
||||
{
|
||||
public bool enabled { get; set; }
|
||||
public List<string>? inbounds { get; set; }
|
||||
public List<string>? outbounds { get; set; }
|
||||
public List<string>? users { get; set; }
|
||||
}
|
||||
|
||||
public class Fakeip4Sbox
|
||||
{
|
||||
public bool enabled { get; set; }
|
||||
public string inet4_range { get; set; }
|
||||
public string inet6_range { get; set; }
|
||||
}
|
||||
|
||||
public class CacheFile4Sbox
|
||||
{
|
||||
public bool enabled { get; set; }
|
||||
public string? path { get; set; }
|
||||
public string? cache_id { get; set; }
|
||||
public bool? store_fakeip { get; set; }
|
||||
}
|
||||
|
||||
public class Ruleset4Sbox
|
||||
{
|
||||
public string? tag { get; set; }
|
||||
public string? type { get; set; }
|
||||
public string? format { get; set; }
|
||||
public string? path { get; set; }
|
||||
public string? url { get; set; }
|
||||
public string? download_detour { get; set; }
|
||||
public string? update_interval { get; set; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user