mirror of
https://github.com/2dust/v2rayN.git
synced 2025-10-24 09:14:41 +00:00
Compare commits
949 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a0f50a41e | ||
|
|
83d4a9c18e | ||
|
|
b4c20e7b81 | ||
|
|
7c76308c93 | ||
|
|
f28fa31c14 | ||
|
|
bbedc4dbb1 | ||
|
|
ecf42cb85d | ||
|
|
e4701d6703 | ||
|
|
54a47d00a3 | ||
|
|
964572817b | ||
|
|
10358064dc | ||
|
|
6a19896915 | ||
|
|
07e173eab1 | ||
|
|
91bca3a7ae | ||
|
|
20260412a7 | ||
|
|
bca030002f | ||
|
|
479bf8e037 | ||
|
|
cb5069bcfc | ||
|
|
eb1339f2f5 | ||
|
|
b66bfabd21 | ||
|
|
3555d861ae | ||
|
|
f39bc6d3b0 | ||
|
|
0c0ecc359b | ||
|
|
68713e7b77 | ||
|
|
899b3fc97b | ||
|
|
a1490d0ac1 | ||
|
|
b23f49ffce | ||
|
|
9a9e28e494 | ||
|
|
65ee5eb510 | ||
|
|
1f42d32e1a | ||
|
|
f2ed8c1d6b | ||
|
|
308b216d1b | ||
|
|
c713f5c8f5 | ||
|
|
6771eb25d1 | ||
|
|
91af50f99a | ||
|
|
a559586e71 | ||
|
|
929520775d | ||
|
|
4eaf31bbf8 | ||
|
|
1607525539 | ||
|
|
31b5b4ca0c | ||
|
|
64c7fea2bc | ||
|
|
f76fd364a2 | ||
|
|
0a1d6db9d1 | ||
|
|
7a750a127e | ||
|
|
fce4a7b74c | ||
|
|
fec7353703 | ||
|
|
40c90d5b3b | ||
|
|
9c58fec8d4 | ||
|
|
11343a30fd | ||
|
|
3693a7fee6 | ||
|
|
a452bbe140 | ||
|
|
185c5e4bfb | ||
|
|
bbe64aa970 | ||
|
|
513662d89a | ||
|
|
22f0d04f01 | ||
|
|
d7c5161431 | ||
|
|
12cc09d0c9 | ||
|
|
5b12c36da5 | ||
|
|
e970372a9f | ||
|
|
5d6c5da9d9 | ||
|
|
ade2db3903 | ||
|
|
7f07279a4c | ||
|
|
b25d4d57bd | ||
|
|
46edd8f9a4 | ||
|
|
ebb95b5ee8 | ||
|
|
dc4611a258 | ||
|
|
03d5b7a05b | ||
|
|
a652fd879b | ||
|
|
326bf334e7 | ||
|
|
21a773f400 | ||
|
|
d86003df55 | ||
|
|
faff8e4ea2 | ||
|
|
6b85aa0b03 | ||
|
|
671678724b | ||
|
|
e96a4818c4 | ||
|
|
0377e7ce19 | ||
|
|
6929886b3e | ||
|
|
721d70c8c7 | ||
|
|
27b45aee83 | ||
|
|
18ac76e683 | ||
|
|
3e1e23a524 | ||
|
|
534c7ab444 | ||
|
|
c2c13ad318 | ||
|
|
3a21596d95 | ||
|
|
ef30d389dc | ||
|
|
bf8783fed7 | ||
|
|
4e042295d2 | ||
|
|
33d9c5db6c | ||
|
|
cb182125f6 | ||
|
|
ec627bdb82 | ||
|
|
4606e78570 | ||
|
|
f00e968b8f | ||
|
|
a87a015c03 | ||
|
|
c559914ff7 | ||
|
|
436d95576e | ||
|
|
54e83391d0 | ||
|
|
3e0578f775 | ||
|
|
29a5abf4d6 | ||
|
|
b54c67d6f1 | ||
|
|
b49486cc23 | ||
|
|
b95830b3d5 | ||
|
|
8e0c5cb9aa | ||
|
|
6ffb3bd30c | ||
|
|
2826444ffc | ||
|
|
56c3e9c46d | ||
|
|
0770e30034 | ||
|
|
04195c2957 | ||
|
|
d18d74ac1c | ||
|
|
6391667c15 | ||
|
|
7f26445327 | ||
|
|
291d4bd8e5 | ||
|
|
f2f3a7eb5f | ||
|
|
e7609619d4 | ||
|
|
84bf9ecfaf | ||
|
|
a2917b3ce8 | ||
|
|
d094370209 | ||
|
|
1a6fbf782d | ||
|
|
3f67a23f8b | ||
|
|
b8eb7e7b29 | ||
|
|
1d69916410 | ||
|
|
49fa103077 | ||
|
|
e3a63db966 | ||
|
|
ef4a1903ec | ||
|
|
5a3286dad1 | ||
|
|
058c6e4a85 | ||
|
|
ea1d438e40 | ||
|
|
a108eaf34b | ||
|
|
da28c639b3 | ||
|
|
8ef68127d4 | ||
|
|
f39d966a33 | ||
|
|
f83e83de13 | ||
|
|
abdafc9b3b | ||
|
|
8f93c50151 | ||
|
|
fe7c505cc9 | ||
|
|
0d5afa4ff5 | ||
|
|
2ad716a4ad | ||
|
|
cddf88730f | ||
|
|
3eb49aa24c | ||
|
|
45c987fd86 | ||
|
|
7bec05ec23 | ||
|
|
606b216cd0 | ||
|
|
bb4f33559f | ||
|
|
c7f3e53f28 | ||
|
|
0035e836d7 | ||
|
|
e6da14f4a8 | ||
|
|
f748f1849c | ||
|
|
f8995b78f6 | ||
|
|
a861020828 | ||
|
|
dc94962900 | ||
|
|
4a40b87bba | ||
|
|
4853e2348d | ||
|
|
e104f9f9b2 | ||
|
|
876381a7fb | ||
|
|
4f711b1bd3 | ||
|
|
89893c0945 | ||
|
|
7b7fe0ef46 | ||
|
|
f66226c103 | ||
|
|
d5c50ef27c | ||
|
|
2060ac18fd | ||
|
|
c9c1cd8cbb | ||
|
|
5201dd5ad0 | ||
|
|
4c3c1e0b5f | ||
|
|
c27651b7b7 | ||
|
|
06636d04ac | ||
|
|
6979e21628 | ||
|
|
310d266745 | ||
|
|
120e8d0686 | ||
|
|
186b56aed9 | ||
|
|
c560fe13fe | ||
|
|
95e3ebd815 | ||
|
|
dc2877d817 | ||
|
|
89d6af8fc9 | ||
|
|
dcc9c9fa14 | ||
|
|
a73906505c | ||
|
|
f45290eb3a | ||
|
|
0fb6b2e54b | ||
|
|
9cc99c5c63 | ||
|
|
46801ce339 | ||
|
|
4345c58b45 | ||
|
|
a2028623e7 | ||
|
|
8314ff3271 | ||
|
|
6a9408fe9b | ||
|
|
b5e0a77401 | ||
|
|
dffc6d9a9b | ||
|
|
c9989108bd | ||
|
|
386c86bfa6 | ||
|
|
925cf16c50 | ||
|
|
c561916b67 | ||
|
|
d41a73b44b | ||
|
|
7995bdd4df | ||
|
|
df95cc6af7 | ||
|
|
c669e72189 | ||
|
|
46db5efef3 | ||
|
|
610418b42b | ||
|
|
508eb24fc3 | ||
|
|
6973272dd0 | ||
|
|
d820c4367e | ||
|
|
96e1f85d6f | ||
|
|
6fa5ca5aa9 | ||
|
|
1d1f5641eb | ||
|
|
3f79df21d9 | ||
|
|
ac1231ad54 | ||
|
|
8662d94ab6 | ||
|
|
3d23f3e3a2 | ||
|
|
6715d7dce6 | ||
|
|
dad35f57d0 | ||
|
|
f779e311ed | ||
|
|
ce7c41e3ff | ||
|
|
74bb01d044 | ||
|
|
82f9698c0d | ||
|
|
6911883995 | ||
|
|
47c509faf6 | ||
|
|
8704942209 | ||
|
|
e8cdc29bb5 | ||
|
|
191a7a6574 | ||
|
|
ad5d21db5a | ||
|
|
569e939492 | ||
|
|
6a17c539d1 | ||
|
|
f8a4f946e4 | ||
|
|
0715fa85ce | ||
|
|
1360051f0c | ||
|
|
42c4f9a6c6 | ||
|
|
11691d0128 | ||
|
|
26fe9c63a3 | ||
|
|
30cd033b42 | ||
|
|
e21c0b4d62 | ||
|
|
916055d8bd | ||
|
|
683ca8af14 | ||
|
|
70151db91b | ||
|
|
da3d4c36a9 | ||
|
|
1d01476523 | ||
|
|
75ceba1b08 | ||
|
|
493c37e7d5 | ||
|
|
6d686b284d | ||
|
|
60fcf6174e | ||
|
|
4141f451b7 | ||
|
|
7a9ee6e9e2 | ||
|
|
cb28c31519 | ||
|
|
84f93f2ae6 | ||
|
|
30c09a7b54 | ||
|
|
b3874a78b9 | ||
|
|
3e71965cd4 | ||
|
|
3df57f74ba | ||
|
|
7972cb8e1f | ||
|
|
0d74452c6c | ||
|
|
f947f63e6d | ||
|
|
fefa7ded5a | ||
|
|
a46a4ad7c1 | ||
|
|
e46f680651 | ||
|
|
93a20852f5 | ||
|
|
298bb64e66 | ||
|
|
0e5ac65f55 | ||
|
|
cb6122f872 | ||
|
|
06500e0218 | ||
|
|
9ddf0b42e7 | ||
|
|
2056377f55 | ||
|
|
7065dabc94 | ||
|
|
9e2336a71e | ||
|
|
d239c627f3 | ||
|
|
984b36d34e | ||
|
|
c81bc2f536 | ||
|
|
87f7e65076 | ||
|
|
9985b68b6b | ||
|
|
fb4b8b2789 | ||
|
|
3812ccc780 | ||
|
|
608a6c387a | ||
|
|
4875b37f70 | ||
|
|
2f3fba73de | ||
|
|
2ab1b9068f | ||
|
|
b9613875ce | ||
|
|
5d2aea6b4f | ||
|
|
5824e18ed6 | ||
|
|
4f8648cbc9 | ||
|
|
01b021b2c3 | ||
|
|
331e8ce960 | ||
|
|
a2cfe6fa51 | ||
|
|
8381fefb78 | ||
|
|
d3b95d781a | ||
|
|
3a4a96f87a | ||
|
|
3d462c4be3 | ||
|
|
82b366cd9b | ||
|
|
897a4e5635 | ||
|
|
8ea76fd318 | ||
|
|
693a96fff2 | ||
|
|
8b4e2f8f23 | ||
|
|
13b164acac | ||
|
|
e590547b30 | ||
|
|
5a0fdd971a | ||
|
|
514dce960a | ||
|
|
6ee6fb1706 | ||
|
|
6985328653 | ||
|
|
41c406b84d | ||
|
|
30f7f2c563 | ||
|
|
be3dbfb8e3 | ||
|
|
9b92259e80 | ||
|
|
1c04144573 | ||
|
|
adf3b955d6 | ||
|
|
0032a3d27a | ||
|
|
c6d347d49a | ||
|
|
ea42246d1b | ||
|
|
3f19958c75 | ||
|
|
35788158bc | ||
|
|
4fd494ded4 | ||
|
|
23eeb8ff55 | ||
|
|
456ffb200a | ||
|
|
18e0bb194e | ||
|
|
392f6111dd | ||
|
|
ce6572af3d | ||
|
|
cf59137481 | ||
|
|
519e588124 | ||
|
|
666c874998 | ||
|
|
5f9f677467 | ||
|
|
b06b5779dd | ||
|
|
e3a3b9c201 | ||
|
|
321ec30f39 | ||
|
|
5adae2dd2a | ||
|
|
be5e15dfb6 | ||
|
|
15d3418c79 | ||
|
|
0efb0228c6 | ||
|
|
75b399b48b | ||
|
|
24ccfb8077 | ||
|
|
204451db6c | ||
|
|
f553bbc41e | ||
|
|
8cb4f2f961 | ||
|
|
4d3db56065 | ||
|
|
d92540121f | ||
|
|
17d586ea26 | ||
|
|
9a096d31fc | ||
|
|
bf83dbdfea | ||
|
|
e31cd0e199 | ||
|
|
1e11477e27 | ||
|
|
e0750df96c | ||
|
|
e3580b05f7 | ||
|
|
6ad0762731 | ||
|
|
70b05d7812 | ||
|
|
5403fc9e21 | ||
|
|
5bffca9584 | ||
|
|
2060539c34 | ||
|
|
de3cdb4f7e | ||
|
|
2a4ba2a751 | ||
|
|
48747aabe0 | ||
|
|
7182be921d | ||
|
|
9d7dcd2c4f | ||
|
|
c3e56e84f1 | ||
|
|
f1ef5a1f51 | ||
|
|
d1e6898290 | ||
|
|
8597332b21 | ||
|
|
7cc42ae249 | ||
|
|
e054d4487d | ||
|
|
6ef36f521d | ||
|
|
a02a122dd1 | ||
|
|
701138617c | ||
|
|
d0e2cc9442 | ||
|
|
d561f10edc | ||
|
|
2df412476a | ||
|
|
e6011cfede | ||
|
|
bcf43e2928 | ||
|
|
3f0f895424 | ||
|
|
07a3bdc618 | ||
|
|
7e348c196e | ||
|
|
51e5885e76 | ||
|
|
50d7912f62 | ||
|
|
3869148fc8 | ||
|
|
a0af4fb30c | ||
|
|
c374b8565b | ||
|
|
7e8b405555 | ||
|
|
c3439c5abe | ||
|
|
d4a8787356 | ||
|
|
23b27575a0 | ||
|
|
8d8a887c42 | ||
|
|
1229c967ba | ||
|
|
d35f65f86d | ||
|
|
0a8ce0f961 | ||
|
|
8092481d26 | ||
|
|
764014e49a | ||
|
|
71cc6d7a88 | ||
|
|
f3af831cf2 | ||
|
|
78fde575d7 | ||
|
|
6e5af34877 | ||
|
|
8d1853e991 | ||
|
|
859299c712 | ||
|
|
7fbb0013b0 | ||
|
|
837cfbd03b | ||
|
|
cdc5d72cfa | ||
|
|
8dcf5c5b90 | ||
|
|
67fe6ac3d8 | ||
|
|
438eaba4d5 | ||
|
|
3c8baa99d5 | ||
|
|
e70658f311 | ||
|
|
2dd10cf5a1 | ||
|
|
96781a784b | ||
|
|
9748fbb076 | ||
|
|
aa5e4378ab | ||
|
|
a7de149fd7 | ||
|
|
ae38be36f5 | ||
|
|
a20e989211 | ||
|
|
579f47ba0d | ||
|
|
24cad87954 | ||
|
|
84d72cd110 | ||
|
|
c0cd46a5aa | ||
|
|
98613c43ca | ||
|
|
555960e210 | ||
|
|
a18ae5582b | ||
|
|
6d6894591c | ||
|
|
984b97fc14 | ||
|
|
a7f35d4495 | ||
|
|
add92cfa7c | ||
|
|
6079e76be5 | ||
|
|
166c7cb2f5 | ||
|
|
dbd3ca44c2 | ||
|
|
ae495dde54 | ||
|
|
4ae25b2f34 | ||
|
|
cdfb621c59 | ||
|
|
29e8df7d2e | ||
|
|
72ff947d95 | ||
|
|
3edaac5739 | ||
|
|
5777a97119 | ||
|
|
aeddbc1dcc | ||
|
|
2f3e409487 | ||
|
|
aa133bb50b | ||
|
|
3bc79a4ba1 | ||
|
|
064a04fbad | ||
|
|
39ed13cf8a | ||
|
|
a1edacb196 | ||
|
|
c9f79e4b47 | ||
|
|
40c1498226 | ||
|
|
390061f9bd | ||
|
|
42324a2c9e | ||
|
|
b9b4ca6360 | ||
|
|
565697bc0b | ||
|
|
2e6c82c851 | ||
|
|
ee75dd37af | ||
|
|
4859dcda08 | ||
|
|
2e962e555d | ||
|
|
ab73e5acb2 | ||
|
|
4e3e5ce130 | ||
|
|
bb8eef3bf5 | ||
|
|
eee87ded29 | ||
|
|
e0f005bd96 | ||
|
|
2cacc372ad | ||
|
|
0b1b681655 | ||
|
|
deafd73306 | ||
|
|
317a5da120 | ||
|
|
071cefc511 | ||
|
|
32a5cc8aa3 | ||
|
|
c41378a085 | ||
|
|
6910e03ef4 | ||
|
|
5060a358db | ||
|
|
b3e9a957c4 | ||
|
|
f6dbfc2dac | ||
|
|
2966a34e63 | ||
|
|
50959951ae | ||
|
|
c2e1cf7bdb | ||
|
|
51ac7cc8be | ||
|
|
3144f1d1c2 | ||
|
|
a176e7b912 | ||
|
|
1198ec0f74 | ||
|
|
4104964e38 | ||
|
|
3bbd1edf06 | ||
|
|
41cc260b5c | ||
|
|
6cd5063c9b | ||
|
|
e544df6d01 | ||
|
|
f952d2383c | ||
|
|
8a29e147d3 | ||
|
|
8a19128e7f | ||
|
|
7dc9fbd8ff | ||
|
|
31a179e647 | ||
|
|
c3fdfcc4bd | ||
|
|
7ea8fae2da | ||
|
|
67ffa810d3 | ||
|
|
ba2a636dd2 | ||
|
|
d471336994 | ||
|
|
7e6482fdff | ||
|
|
a8eba93ffd | ||
|
|
4ffe595db6 | ||
|
|
885587e551 | ||
|
|
e986dc189e | ||
|
|
bccab41c8f | ||
|
|
79a0538ca0 | ||
|
|
e6b27d17e4 | ||
|
|
2a0012824a | ||
|
|
9d92be99ee | ||
|
|
6914831d30 | ||
|
|
19d83be6de | ||
|
|
ce7303bd0d | ||
|
|
4c3318ac86 | ||
|
|
db25fdeee1 | ||
|
|
732c3eee8b | ||
|
|
2b0d805b2f | ||
|
|
4faa94b2a3 | ||
|
|
331d11aee2 | ||
|
|
4d95d7d8c0 | ||
|
|
fd82623c74 | ||
|
|
4a32b2c814 | ||
|
|
45264005a4 | ||
|
|
253219dd16 | ||
|
|
b2feaf3ba9 | ||
|
|
6c5011ad68 | ||
|
|
c0f8b6b84c | ||
|
|
f71125d8f3 | ||
|
|
e674a025d8 | ||
|
|
dadc24876f | ||
|
|
98d4801b7e | ||
|
|
0e50bfb6eb | ||
|
|
a5aa286535 | ||
|
|
3caf025c3c | ||
|
|
c943c6c60a | ||
|
|
8c55c629cd | ||
|
|
0c38ebb63f | ||
|
|
75df85f598 | ||
|
|
59e99b2316 | ||
|
|
02f4dcbaf7 | ||
|
|
470dec5588 | ||
|
|
be1e37ddd0 | ||
|
|
fdc32601a9 | ||
|
|
d3b86858e1 | ||
|
|
9c20d9cb1f | ||
|
|
d2cda16378 | ||
|
|
2ab00d5b6f | ||
|
|
1b8a58cd07 | ||
|
|
0c3293045f | ||
|
|
4c4ce7e8d1 | ||
|
|
a0d4a3f2e8 | ||
|
|
26abe7e7d7 | ||
|
|
b9c86ed3a1 | ||
|
|
b7cff66a80 | ||
|
|
61a40d2860 | ||
|
|
80eb569366 | ||
|
|
54fe669d89 | ||
|
|
0953237e9e | ||
|
|
cf1a8599eb | ||
|
|
171132be12 | ||
|
|
43c95422b7 | ||
|
|
2662243641 | ||
|
|
060a35e091 | ||
|
|
de1132c2df | ||
|
|
f19edc9370 | ||
|
|
f1601c463b | ||
|
|
77b15cd530 | ||
|
|
a6479fe0d0 | ||
|
|
4ad4e27dc1 | ||
|
|
7370684985 | ||
|
|
5ae58e6a98 | ||
|
|
780ccb1932 | ||
|
|
6b4076be10 | ||
|
|
d7a04a15ae | ||
|
|
2ae43f8bdb | ||
|
|
d3ebc17a10 | ||
|
|
7a8680711e | ||
|
|
649e89e7af | ||
|
|
cb94d64395 | ||
|
|
2440dc2440 | ||
|
|
364a24c580 | ||
|
|
8f3e0dbd82 | ||
|
|
596234df26 | ||
|
|
c38aec50dc | ||
|
|
40915b5f98 | ||
|
|
a176613119 | ||
|
|
6c6de1ae7f | ||
|
|
edbd168dcf | ||
|
|
127858d582 | ||
|
|
baf90cfbdd | ||
|
|
1a5eeb9401 | ||
|
|
e0ae101ff4 | ||
|
|
8b4a07dfe8 | ||
|
|
db934e70cb | ||
|
|
b770048f05 | ||
|
|
27c53be209 | ||
|
|
d93c12b354 | ||
|
|
98d4a47efb | ||
|
|
8ab04c65b5 | ||
|
|
1b2dab1388 | ||
|
|
4f8dae7fa0 | ||
|
|
cdc23e32c4 | ||
|
|
20457e9e63 | ||
|
|
e63042af84 | ||
|
|
c35b4d3c1b | ||
|
|
64a83a5d64 | ||
|
|
01039d0b47 | ||
|
|
7c1e5a3cba | ||
|
|
038161527f | ||
|
|
b12b7a17e6 | ||
|
|
37cf23d5fe | ||
|
|
ed7fb4f6e3 | ||
|
|
5d4bd2fee6 | ||
|
|
aae5906311 | ||
|
|
4a7bafd011 | ||
|
|
626ebfe65d | ||
|
|
3cc75cd46d | ||
|
|
f809ee7b20 | ||
|
|
1295d8191c | ||
|
|
bc2adbfa77 | ||
|
|
9583dff176 | ||
|
|
5732b84a7b | ||
|
|
c0d27504ac | ||
|
|
3db1dd7bbb | ||
|
|
5392766c5e | ||
|
|
730b7dea37 | ||
|
|
02a13ce028 | ||
|
|
7884853098 | ||
|
|
e122ea8146 | ||
|
|
f500d2b9f4 | ||
|
|
79e53bf1f5 | ||
|
|
31de7ec094 | ||
|
|
2e4501187c | ||
|
|
4430c9bd74 | ||
|
|
cf8be85ff7 | ||
|
|
0759be1223 | ||
|
|
21f8ddcf9f | ||
|
|
37cba5ee34 | ||
|
|
ff642fd1ac | ||
|
|
647f1d9c8b | ||
|
|
d886195ee3 | ||
|
|
e2b8f4f89a | ||
|
|
1dcfe661e9 | ||
|
|
d6dd110781 | ||
|
|
a0f956c885 | ||
|
|
88bcafed97 | ||
|
|
6e4f15ab52 | ||
|
|
20cae1ff4d | ||
|
|
dcf621b822 | ||
|
|
eafa20032b | ||
|
|
7375e1a490 | ||
|
|
199d87ba84 | ||
|
|
b43975ebfc | ||
|
|
fe7314c978 | ||
|
|
d4eb8e59a6 | ||
|
|
8ee00907b7 | ||
|
|
25fddc3c71 | ||
|
|
c78b733850 | ||
|
|
a57c83125e | ||
|
|
247b59985f | ||
|
|
7470b8b6d3 | ||
|
|
776d37b7aa | ||
|
|
8f532f8468 | ||
|
|
472963fe2d | ||
|
|
6a9b62ab9a | ||
|
|
9ac8aa2969 | ||
|
|
838bd2c794 | ||
|
|
b2bbe432e0 | ||
|
|
b3d8042452 | ||
|
|
0c758a7fdc | ||
|
|
85cb1d1c92 | ||
|
|
2cd2e8894d | ||
|
|
abb58379b3 | ||
|
|
4f48e8b190 | ||
|
|
2b5cbb5e74 | ||
|
|
23dd140921 | ||
|
|
8c8d7bda64 | ||
|
|
a6e246948a | ||
|
|
c49ba735a0 | ||
|
|
1a33c598e8 | ||
|
|
a4bbdb49de | ||
|
|
cf3846fbfd | ||
|
|
d46943eedf | ||
|
|
3bc17bd50a | ||
|
|
48a159f4c8 | ||
|
|
3d1bcffdc5 | ||
|
|
45fa0f94d2 | ||
|
|
ed16b7de4a | ||
|
|
f2ec03c7ec | ||
|
|
11db87f1e6 | ||
|
|
a1feaf33e0 | ||
|
|
53a99e3f79 | ||
|
|
e7f04f55c2 | ||
|
|
6653ea12b7 | ||
|
|
a28cef5b98 | ||
|
|
78a28fbdb3 | ||
|
|
bf8bbbdcb0 | ||
|
|
83ad83b135 | ||
|
|
dbd4f55981 | ||
|
|
5f5d7172ee | ||
|
|
fbd4557b44 | ||
|
|
5d55a55754 | ||
|
|
84032aec33 | ||
|
|
a372d8902e | ||
|
|
c38c62e4c3 | ||
|
|
8aceff7480 | ||
|
|
736c450161 | ||
|
|
3b63a3d308 | ||
|
|
4f56174c8f | ||
|
|
4d2eb324f1 | ||
|
|
3f2ab8ddcb | ||
|
|
fd8f863c5b | ||
|
|
246f1d7df0 | ||
|
|
ccacda9bf5 | ||
|
|
5494d63878 | ||
|
|
c32b9812a7 | ||
|
|
9eb9898b61 | ||
|
|
834e05999f | ||
|
|
b4c37d9906 | ||
|
|
9326b450d7 | ||
|
|
019ee8b1ba | ||
|
|
f043645397 | ||
|
|
3b173f0b3e | ||
|
|
f36c06389d | ||
|
|
d3a0b44247 | ||
|
|
e4d3a98aa8 | ||
|
|
f685682214 | ||
|
|
1c6323315b | ||
|
|
1ff4839be1 | ||
|
|
558e5bb340 | ||
|
|
48a9d208e6 | ||
|
|
f29e1f8c45 | ||
|
|
5d2bc88bb9 | ||
|
|
298fdb1191 | ||
|
|
59b34688ea | ||
|
|
5ce0bb6e4a | ||
|
|
487b1ab182 | ||
|
|
5c144a8ba3 | ||
|
|
ad344356df | ||
|
|
a55c65374d | ||
|
|
28447a9d43 | ||
|
|
0c03550c62 | ||
|
|
6d9a84803f | ||
|
|
03b0e4e2bb | ||
|
|
a00e9a6f5e | ||
|
|
ba17f8fde9 | ||
|
|
01d35456bd | ||
|
|
672b8c48ac | ||
|
|
ac1a357740 | ||
|
|
504f8d09a6 | ||
|
|
89ce7c23c9 | ||
|
|
a5d99b1eb5 | ||
|
|
800d193acb | ||
|
|
7a1d12be76 | ||
|
|
1b9c95e801 | ||
|
|
9f44815470 | ||
|
|
ca38239bce | ||
|
|
d9c22de6b8 | ||
|
|
49a3c84fc5 | ||
|
|
5e0c28438b | ||
|
|
a6dc801bc4 | ||
|
|
22009d1b71 | ||
|
|
50f39dc40e | ||
|
|
05d446ed37 | ||
|
|
7a0b068642 | ||
|
|
c0ef8c09af | ||
|
|
a2db6dd468 | ||
|
|
471bc0f65d | ||
|
|
80f840a7c2 | ||
|
|
a2413fdf23 | ||
|
|
9cbe2b938c | ||
|
|
e915726c52 | ||
|
|
b11e68cfd8 | ||
|
|
bee66d06dd | ||
|
|
945a0add96 | ||
|
|
294cabcf05 | ||
|
|
22eb993ebf | ||
|
|
b5e1a297ae | ||
|
|
499a16feae | ||
|
|
1866a59d12 | ||
|
|
10513e0f3b | ||
|
|
2e32de2fbb | ||
|
|
30a838df77 | ||
|
|
7cf9b9f57e | ||
|
|
736d995d4c | ||
|
|
e335b2c0d6 | ||
|
|
96ae5517f6 | ||
|
|
fb4b8d923a | ||
|
|
2ade705e51 | ||
|
|
e0086b4b79 | ||
|
|
8f6d443104 | ||
|
|
5dbce16895 | ||
|
|
1016dcb3d1 | ||
|
|
ba5ad12e13 | ||
|
|
57f9c8158e | ||
|
|
b8a0293b52 | ||
|
|
59b7daaef5 | ||
|
|
d781c768e9 | ||
|
|
04efed2079 | ||
|
|
2de9e14c7d | ||
|
|
a849dec6c2 | ||
|
|
28019dc511 | ||
|
|
9d638968a9 | ||
|
|
f921ff7d77 | ||
|
|
e3ea329795 | ||
|
|
87f1c129b6 | ||
|
|
a1c8bc0e61 | ||
|
|
61e9101851 | ||
|
|
a6289347cc | ||
|
|
9e84de8e76 | ||
|
|
d04cc53152 | ||
|
|
1ff3c5fc07 | ||
|
|
3db047a12f | ||
|
|
bf4615f2f2 | ||
|
|
798c7fad9a | ||
|
|
5797b37262 | ||
|
|
ce22f34cd6 | ||
|
|
4c49e52e26 | ||
|
|
47318b5d70 | ||
|
|
d12297909c | ||
|
|
5fb4edae2d | ||
|
|
c4b490e46d | ||
|
|
45febe3fff | ||
|
|
689a81a985 | ||
|
|
28620b385a | ||
|
|
be13446e69 | ||
|
|
ee57d5b8e6 | ||
|
|
4eda3dd8fa | ||
|
|
911dc7f90e | ||
|
|
18005b96e8 | ||
|
|
a3e45d206e | ||
|
|
0accf262dc | ||
|
|
e0eb73bb0a | ||
|
|
258e822c13 | ||
|
|
4f05b93d63 | ||
|
|
bb661d4f50 | ||
|
|
201cfaa922 | ||
|
|
c339aa349c | ||
|
|
046421f345 | ||
|
|
3a2c9e7aaa | ||
|
|
0bb6e6bd86 | ||
|
|
693e8ab157 | ||
|
|
033c16c992 | ||
|
|
6ec61433fb | ||
|
|
072f773245 | ||
|
|
0086f65a96 | ||
|
|
281f14f47e | ||
|
|
9fd20ff001 | ||
|
|
7df90a6034 | ||
|
|
019869ec28 | ||
|
|
b7f4fd7469 | ||
|
|
1273d2aee1 | ||
|
|
88990b4828 | ||
|
|
2f02c2970c | ||
|
|
ade789c6d4 | ||
|
|
9738f90970 | ||
|
|
6ed0741339 | ||
|
|
e6b1e22245 | ||
|
|
6b922be0c6 | ||
|
|
6e35a260e8 | ||
|
|
1106fd8cf1 | ||
|
|
fb92b90d5c | ||
|
|
5effbee50b | ||
|
|
9bc50a9f34 | ||
|
|
2a5a339c27 | ||
|
|
78d182fff3 | ||
|
|
0efb0b5e3e | ||
|
|
a2e8755730 | ||
|
|
06ddedbc4c | ||
|
|
fa148cdf42 | ||
|
|
c1f71dee15 | ||
|
|
871cb81323 | ||
|
|
34f5c0f910 | ||
|
|
3fafc6de93 | ||
|
|
b013213745 | ||
|
|
40ea454a7c | ||
|
|
f0bac4b4c8 | ||
|
|
ffb38129e2 | ||
|
|
3c550c094a | ||
|
|
2d143687b8 | ||
|
|
1e7284f141 | ||
|
|
50bdf0927c | ||
|
|
10a60b26fa | ||
|
|
b3c2084b76 | ||
|
|
3bf2dc711d | ||
|
|
5a7836115e | ||
|
|
0da9cb45bd | ||
|
|
28aa954f8c | ||
|
|
4fc71fb77e | ||
|
|
5ea6b1e08a | ||
|
|
0cdfc97460 | ||
|
|
3dd54312e7 | ||
|
|
a866017b4c | ||
|
|
394b657fc9 | ||
|
|
50449df08d | ||
|
|
fe3836be14 | ||
|
|
055cd62dd8 | ||
|
|
4d1f7fa60c | ||
|
|
5c0fba8744 | ||
|
|
b74ddc0b43 | ||
|
|
d004c6860e | ||
|
|
fc3ba6c030 | ||
|
|
9c4dc185be | ||
|
|
2edbbc523a | ||
|
|
a5122b656d | ||
|
|
74f980aab1 | ||
|
|
35e5475255 | ||
|
|
fb649c04cf | ||
|
|
4ce7ca3c6f | ||
|
|
eebb6735aa | ||
|
|
e96c9abd69 | ||
|
|
fd7cf164ff | ||
|
|
24afe8bde4 | ||
|
|
9540669f56 | ||
|
|
c6caf29b5b | ||
|
|
2698137dea | ||
|
|
bdae08b13b | ||
|
|
019c69ecff | ||
|
|
6b99b7eec5 | ||
|
|
4eb443e547 | ||
|
|
e530789739 | ||
|
|
7618f9f7d4 | ||
|
|
d7bde77977 | ||
|
|
a0286ff810 | ||
|
|
b172b03927 | ||
|
|
a556bf9487 | ||
|
|
4f5362fdc4 | ||
|
|
176a91e7c5 | ||
|
|
3c45ef624a | ||
|
|
fea7c9fbd7 | ||
|
|
aa6b6e3849 | ||
|
|
f5956e7bf0 | ||
|
|
b2669103dc | ||
|
|
53e19ecb1d | ||
|
|
3e74bb65bd | ||
|
|
f60575b77c | ||
|
|
7a839063d0 | ||
|
|
4ccc7aa92d | ||
|
|
d5c6a42a9a | ||
|
|
3bdef4d6d8 | ||
|
|
f40eb724d7 | ||
|
|
4d84eede56 | ||
|
|
53a2fbd0ff | ||
|
|
409fe5290e | ||
|
|
75d86cf883 | ||
|
|
b33f536dbd | ||
|
|
1f77c56eaf | ||
|
|
fe895b1b27 | ||
|
|
20bb263b06 | ||
|
|
3ecbd3bc10 | ||
|
|
fe81b51dcb | ||
|
|
90ba80436d | ||
|
|
2bb5f6afc4 | ||
|
|
3971318ffb | ||
|
|
5fbeb4b0fb | ||
|
|
1b2b838bc0 | ||
|
|
8e28cc01b8 | ||
|
|
77a59e9107 | ||
|
|
1411643192 | ||
|
|
fde2a768cf | ||
|
|
b008a58cf8 | ||
|
|
8e02394ff4 | ||
|
|
60a75d9a31 | ||
|
|
24566901fb | ||
|
|
cc428618e5 | ||
|
|
4d330cedac | ||
|
|
2503583498 | ||
|
|
1b11916e1e | ||
|
|
3dfd557265 | ||
|
|
cd6bea28b6 | ||
|
|
054f0f2bc7 | ||
|
|
97347674e7 | ||
|
|
36d15852aa | ||
|
|
4813610492 | ||
|
|
243dbab06a | ||
|
|
073eaa9a26 | ||
|
|
cb1f936d05 |
404 changed files with 54532 additions and 55810 deletions
170
.editorconfig
Normal file
170
.editorconfig
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
tab_width = 4
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = crlf
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.cs]
|
||||||
|
dotnet_hide_advanced_members = true
|
||||||
|
dotnet_member_insertion_location = with_other_members_of_the_same_kind
|
||||||
|
dotnet_property_generation_behavior = prefer_throwing_properties
|
||||||
|
dotnet_search_reference_assemblies = true
|
||||||
|
dotnet_separate_import_directive_groups = false:warning
|
||||||
|
dotnet_sort_system_directives_first = true:warning
|
||||||
|
file_header_template = unset
|
||||||
|
|
||||||
|
dotnet_style_qualification_for_event = false:warning
|
||||||
|
dotnet_style_qualification_for_field = false:warning
|
||||||
|
dotnet_style_qualification_for_method = false:warning
|
||||||
|
dotnet_style_qualification_for_property = false:warning
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
|
||||||
|
dotnet_style_predefined_type_for_member_access = true:warning
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
|
||||||
|
dotnet_style_parentheses_in_other_operators = always_for_clarity:warning
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
|
||||||
|
dotnet_style_require_accessibility_modifiers = always:warning
|
||||||
|
dotnet_prefer_system_hash_code = true:warning
|
||||||
|
dotnet_style_coalesce_expression = true:warning
|
||||||
|
dotnet_style_collection_initializer = false:warning
|
||||||
|
dotnet_style_explicit_tuple_names = true:warning
|
||||||
|
dotnet_style_namespace_match_folder = true:warning
|
||||||
|
dotnet_style_null_propagation = true:warning
|
||||||
|
dotnet_style_object_initializer = true:warning
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
dotnet_style_prefer_auto_properties = true:warning
|
||||||
|
dotnet_style_prefer_collection_expression = false:warning
|
||||||
|
dotnet_style_prefer_compound_assignment = true:warning
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = false:warning
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = false:warning
|
||||||
|
dotnet_style_prefer_foreach_explicit_cast_in_source = always:warning
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true:warning
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true:warning
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true:warning
|
||||||
|
dotnet_style_readonly_field = true:warning
|
||||||
|
dotnet_code_quality_unused_parameters = all:warning
|
||||||
|
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||||
|
dotnet_style_allow_multiple_blank_lines_experimental = false:warning
|
||||||
|
dotnet_style_allow_statement_immediately_after_block_experimental = true:warning
|
||||||
|
|
||||||
|
csharp_style_var_elsewhere = true:warning
|
||||||
|
csharp_style_var_for_built_in_types = true:warning
|
||||||
|
csharp_style_var_when_type_is_apparent = true:warning
|
||||||
|
csharp_style_expression_bodied_accessors = when_on_single_line:warning
|
||||||
|
csharp_style_expression_bodied_constructors = false:warning
|
||||||
|
csharp_style_expression_bodied_indexers = when_on_single_line:warning
|
||||||
|
csharp_style_expression_bodied_lambdas = when_on_single_line:warning
|
||||||
|
csharp_style_expression_bodied_local_functions = false:warning
|
||||||
|
csharp_style_expression_bodied_methods = false:warning
|
||||||
|
csharp_style_expression_bodied_operators = false:warning
|
||||||
|
csharp_style_expression_bodied_properties = when_on_single_line:warning
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:warning
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
|
||||||
|
csharp_style_prefer_extended_property_pattern = true:warning
|
||||||
|
csharp_style_prefer_not_pattern = true:warning
|
||||||
|
csharp_style_prefer_pattern_matching = true:warning
|
||||||
|
csharp_style_prefer_switch_expression = false:warning
|
||||||
|
csharp_style_conditional_delegate_call = true:warning
|
||||||
|
csharp_prefer_static_anonymous_function = true:warning
|
||||||
|
csharp_prefer_static_local_function = true:warning
|
||||||
|
csharp_preferred_modifier_order = public,internal,private,protected,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:warning
|
||||||
|
csharp_style_prefer_readonly_struct = true:warning
|
||||||
|
csharp_style_prefer_readonly_struct_member = true:warning
|
||||||
|
csharp_prefer_braces = true:warning
|
||||||
|
csharp_prefer_simple_using_statement = true:warning
|
||||||
|
csharp_prefer_system_threading_lock = true:warning
|
||||||
|
csharp_style_namespace_declarations = file_scoped:warning
|
||||||
|
csharp_style_prefer_method_group_conversion = true:warning
|
||||||
|
csharp_style_prefer_primary_constructors = true:warning
|
||||||
|
csharp_style_prefer_top_level_statements = false:warning
|
||||||
|
csharp_prefer_simple_default_expression = true:warning
|
||||||
|
csharp_style_deconstructed_variable_declaration = true:warning
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
|
||||||
|
csharp_style_inlined_variable_declaration = true:warning
|
||||||
|
csharp_style_prefer_index_operator = false:warning
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true:warning
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true:warning
|
||||||
|
csharp_style_prefer_range_operator = false:warning
|
||||||
|
csharp_style_prefer_tuple_swap = true:warning
|
||||||
|
csharp_style_prefer_utf8_string_literals = true:warning
|
||||||
|
csharp_style_throw_expression = true:warning
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:warning
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable:warning
|
||||||
|
csharp_using_directive_placement = outside_namespace:warning
|
||||||
|
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:warning
|
||||||
|
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:warning
|
||||||
|
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning
|
||||||
|
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning
|
||||||
|
csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning
|
||||||
|
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = all
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = true
|
||||||
|
csharp_indent_labels = no_change
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false:warning
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_parentheses = false
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = false
|
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_pascal.severity = warning
|
||||||
|
dotnet_naming_rule.interface_should_be_pascal.symbols = interface
|
||||||
|
dotnet_naming_rule.interface_should_be_pascal.style = pascal
|
||||||
|
dotnet_naming_rule.types_should_be_pascal.severity = warning
|
||||||
|
dotnet_naming_rule.types_should_be_pascal.symbols = types
|
||||||
|
dotnet_naming_rule.types_should_be_pascal.style = pascal
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal.severity = warning
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal.symbols = non_field_members
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal.style = pascal
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.interface.required_modifiers =
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.types.required_modifiers =
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
dotnet_naming_style.pascal.required_prefix =
|
||||||
|
dotnet_naming_style.pascal.required_suffix =
|
||||||
|
dotnet_naming_style.pascal.word_separator =
|
||||||
|
dotnet_naming_style.pascal.capitalization = pascal_case
|
||||||
0
v2rayN/.gitattributes → .gitattributes
vendored
0
v2rayN/.gitattributes → .gitattributes
vendored
52
.github/ISSUE_TEMPLATE/01_bug_report.yml
vendored
52
.github/ISSUE_TEMPLATE/01_bug_report.yml
vendored
|
|
@ -3,6 +3,26 @@ description: 在提出问题前请先自行排除服务器端问题和升级到
|
||||||
title: "[Bug]: "
|
title: "[Bug]: "
|
||||||
labels: ["bug"]
|
labels: ["bug"]
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
### 报告 Bug 前请务必确认以下事项:
|
||||||
|
> ** **
|
||||||
|
> **✓ 已自行排除服务器端问题。**
|
||||||
|
> **✓ 已升级到最新客户端版本。**
|
||||||
|
> **✓ 已通过搜索确认没有人提出过相同问题。**
|
||||||
|
> **✓ 已确认自己的电脑系统环境是受支持的。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: "os-version"
|
||||||
|
attributes:
|
||||||
|
label: "操作系统和版本"
|
||||||
|
description: "操作系统和版本"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: "expectation"
|
id: "expectation"
|
||||||
attributes:
|
attributes:
|
||||||
|
|
@ -10,6 +30,7 @@ body:
|
||||||
description: "描述你认为应该发生什么"
|
description: "描述你认为应该发生什么"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: "describe-the-bug"
|
id: "describe-the-bug"
|
||||||
attributes:
|
attributes:
|
||||||
|
|
@ -17,22 +38,34 @@ body:
|
||||||
description: "描述实际发生了什么"
|
description: "描述实际发生了什么"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: "reproduction-method"
|
id: "reproduction-method"
|
||||||
attributes:
|
attributes:
|
||||||
label: "复现方法"
|
label: "复现方法"
|
||||||
description: "在BUG出现前执行了哪些操作"
|
description: "在BUG出现前执行了哪些操作"
|
||||||
placeholder: 标序号
|
placeholder: "标序号"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: "log"
|
id: "gui-log"
|
||||||
attributes:
|
attributes:
|
||||||
label: "日志信息"
|
label: "软件日志"
|
||||||
description: "位置在软件当前目录下的guiLogs"
|
description: "位置在软件当前目录下的guiLogs"
|
||||||
placeholder: 在日志开始和结束位置粘贴冒号后的内容:```
|
placeholder: "在日志开始和结束位置粘贴冒号后的内容到这:"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: "core-log"
|
||||||
|
attributes:
|
||||||
|
label: "内核日志"
|
||||||
|
description: "位置在软件主界面的信息框内"
|
||||||
|
placeholder: "在信息框内鼠标右键复制全部信息粘贴在这:"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: "more"
|
id: "more"
|
||||||
attributes:
|
attributes:
|
||||||
|
|
@ -40,6 +73,7 @@ body:
|
||||||
description: "可选"
|
description: "可选"
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: "latest-version"
|
id: "latest-version"
|
||||||
attributes:
|
attributes:
|
||||||
|
|
@ -48,6 +82,7 @@ body:
|
||||||
options:
|
options:
|
||||||
- label: 是
|
- label: 是
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: "issues"
|
id: "issues"
|
||||||
attributes:
|
attributes:
|
||||||
|
|
@ -56,3 +91,12 @@ body:
|
||||||
options:
|
options:
|
||||||
- label: 是
|
- label: 是
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: "system-version"
|
||||||
|
attributes:
|
||||||
|
label: "我确认系统版本是受支持的"
|
||||||
|
description: "否则请切换后尝试"
|
||||||
|
options:
|
||||||
|
- label: 是
|
||||||
|
required: true
|
||||||
|
|
|
||||||
9
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
9
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
|
||||||
|
contact_links:
|
||||||
|
- name: Discussions / 讨论区
|
||||||
|
url: https://github.com/2dust/v2rayN/discussions
|
||||||
|
about: 使用问题或需要帮助请前往 Discussions。
|
||||||
|
- name: Wiki / 使用说明
|
||||||
|
url: https://github.com/2dust/v2rayN/wiki
|
||||||
|
about: 查看常见问题和使用文档。
|
||||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
|
||||||
|
- package-ecosystem: "nuget"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
69
.github/workflows/build-all.yml
vendored
Normal file
69
.github/workflows/build-all.yml
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
name: release all platforms
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Trigger build windows
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
curl -X POST \
|
||||||
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
|
https://api.github.com/repos/${{ github.repository }}/actions/workflows/build-windows.yml/dispatches \
|
||||||
|
-d "{
|
||||||
|
\"ref\": \"master\",
|
||||||
|
\"inputs\": {
|
||||||
|
\"release_tag\": \"${{ github.event.inputs.release_tag }}\"
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
|
||||||
|
- name: Trigger build linux
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
curl -X POST \
|
||||||
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
|
https://api.github.com/repos/${{ github.repository }}/actions/workflows/build-linux.yml/dispatches \
|
||||||
|
-d "{
|
||||||
|
\"ref\": \"master\",
|
||||||
|
\"inputs\": {
|
||||||
|
\"release_tag\": \"${{ github.event.inputs.release_tag }}\"
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
|
||||||
|
- name: Trigger build osx
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
curl -X POST \
|
||||||
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
|
https://api.github.com/repos/${{ github.repository }}/actions/workflows/build-osx.yml/dispatches \
|
||||||
|
-d "{
|
||||||
|
\"ref\": \"master\",
|
||||||
|
\"inputs\": {
|
||||||
|
\"release_tag\": \"${{ github.event.inputs.release_tag }}\"
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
|
||||||
|
- name: Trigger build windows desktop
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
curl -X POST \
|
||||||
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
|
https://api.github.com/repos/${{ github.repository }}/actions/workflows/build-windows-desktop.yml/dispatches \
|
||||||
|
-d "{
|
||||||
|
\"ref\": \"master\",
|
||||||
|
\"inputs\": {
|
||||||
|
\"release_tag\": \"${{ github.event.inputs.release_tag }}\"
|
||||||
|
}
|
||||||
|
}"
|
||||||
135
.github/workflows/build-linux.yml
vendored
Normal file
135
.github/workflows/build-linux.yml
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
name: release Linux
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
OutputArch: "linux-64"
|
||||||
|
OutputArchArm: "linux-arm64"
|
||||||
|
OutputPath64: "${{ github.workspace }}/v2rayN/Release/linux-64"
|
||||||
|
OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/linux-arm64"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
configuration: [Release]
|
||||||
|
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5.0.0
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
fetch-depth: '0'
|
||||||
|
|
||||||
|
- name: Setup
|
||||||
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
|
with:
|
||||||
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cd v2rayN
|
||||||
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64 --self-contained=true -o $OutputPath64
|
||||||
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 --self-contained=true -o $OutputPathArm64
|
||||||
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-x64 --self-contained=true -p:PublishTrimmed=true -o $OutputPath64
|
||||||
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: v2rayN-linux
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}/v2rayN/Release/linux*
|
||||||
|
|
||||||
|
# release debian package
|
||||||
|
- name: Package debian
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
chmod 755 package-debian.sh
|
||||||
|
./package-debian.sh $OutputArch $OutputPath64 ${{ github.event.inputs.release_tag }}
|
||||||
|
./package-debian.sh $OutputArchArm $OutputPathArm64 ${{ github.event.inputs.release_tag }}
|
||||||
|
|
||||||
|
- name: Upload deb to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
with:
|
||||||
|
file: ${{ github.workspace }}/v2rayN*.deb
|
||||||
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
|
|
||||||
|
- name: Package AppImage
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
chmod a+x package-appimage.sh
|
||||||
|
./package-appimage.sh
|
||||||
|
|
||||||
|
- name: Upload AppImage to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
with:
|
||||||
|
file: ${{ github.workspace }}/v2rayN*.AppImage
|
||||||
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
|
|
||||||
|
# release zip archive
|
||||||
|
- name: Package release zip archive
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
chmod 755 package-release-zip.sh
|
||||||
|
./package-release-zip.sh $OutputArch $OutputPath64
|
||||||
|
./package-release-zip.sh $OutputArchArm $OutputPathArm64
|
||||||
|
|
||||||
|
- name: Upload zip archive to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
with:
|
||||||
|
file: ${{ github.workspace }}/v2rayN*.zip
|
||||||
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
|
|
||||||
|
# release RHEL package
|
||||||
|
- name: Package RPM (RHEL-family)
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
chmod 755 package-rhel.sh
|
||||||
|
# Build for both x86_64 and aarch64 in one go (explicit version passed; no --buildfrom)
|
||||||
|
./package-rhel.sh "${{ github.event.inputs.release_tag }}" --arch all
|
||||||
|
|
||||||
|
- name: Collect RPMs into workspace
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
mkdir -p "${{ github.workspace }}/dist/rpm"
|
||||||
|
rsync -av "$HOME/rpmbuild/RPMS/" "${{ github.workspace }}/dist/rpm/"
|
||||||
|
# Rename to requested filenames
|
||||||
|
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.x86_64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-x64.rpm" \; || true
|
||||||
|
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.aarch64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
|
||||||
|
|
||||||
|
- name: Upload RPM artifacts
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: v2rayN-rpm
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}/dist/rpm/**/*.rpm
|
||||||
|
|
||||||
|
- name: Upload RPMs to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
with:
|
||||||
|
file: ${{ github.workspace }}/dist/rpm/**/*.rpm
|
||||||
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
87
.github/workflows/build-osx.yml
vendored
Normal file
87
.github/workflows/build-osx.yml
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
name: release macOS
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
OutputArch: "macos-64"
|
||||||
|
OutputArchArm: "macos-arm64"
|
||||||
|
OutputPath64: "${{ github.workspace }}/v2rayN/Release/macos-64"
|
||||||
|
OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/macos-arm64"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
configuration: [Release]
|
||||||
|
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5.0.0
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
fetch-depth: '0'
|
||||||
|
|
||||||
|
- name: Setup
|
||||||
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
|
with:
|
||||||
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cd v2rayN
|
||||||
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64 --self-contained=true -o $OutputPath64
|
||||||
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 --self-contained=true -o $OutputPathArm64
|
||||||
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-x64 --self-contained=true -p:PublishTrimmed=true -o $OutputPath64
|
||||||
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: v2rayN-macos
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}/v2rayN/Release/macos*
|
||||||
|
|
||||||
|
# release osx package
|
||||||
|
- name: Package osx
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
brew install create-dmg
|
||||||
|
chmod 755 package-osx.sh
|
||||||
|
./package-osx.sh $OutputArch $OutputPath64 ${{ github.event.inputs.release_tag }}
|
||||||
|
./package-osx.sh $OutputArchArm $OutputPathArm64 ${{ github.event.inputs.release_tag }}
|
||||||
|
|
||||||
|
- name: Upload dmg to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
with:
|
||||||
|
file: ${{ github.workspace }}/v2rayN*.dmg
|
||||||
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
|
|
||||||
|
# release zip archive
|
||||||
|
- name: Package release zip archive
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
chmod 755 package-release-zip.sh
|
||||||
|
./package-release-zip.sh $OutputArch $OutputPath64
|
||||||
|
./package-release-zip.sh $OutputArchArm $OutputPathArm64
|
||||||
|
|
||||||
|
- name: Upload zip archive to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
with:
|
||||||
|
file: ${{ github.workspace }}/v2rayN*.zip
|
||||||
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
71
.github/workflows/build-windows-desktop.yml
vendored
Normal file
71
.github/workflows/build-windows-desktop.yml
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
name: release Windows desktop (Avalonia UI)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
OutputArch: "windows-64"
|
||||||
|
OutputArchArm: "windows-arm64"
|
||||||
|
OutputPath64: "${{ github.workspace }}/v2rayN/Release/windows-64"
|
||||||
|
OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/windows-arm64"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
configuration: [Release]
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5.0.0
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
fetch-depth: '0'
|
||||||
|
|
||||||
|
- name: Setup
|
||||||
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
|
with:
|
||||||
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cd v2rayN
|
||||||
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||||
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||||
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64
|
||||||
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: v2rayN-windows-desktop
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}/v2rayN/Release/windows*
|
||||||
|
|
||||||
|
# release zip archive
|
||||||
|
- name: Package release zip archive
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
chmod 755 package-release-zip.sh
|
||||||
|
./package-release-zip.sh $OutputArch $OutputPath64
|
||||||
|
mv "v2rayN-${OutputArch}.zip" "v2rayN-${OutputArch}-desktop.zip"
|
||||||
|
./package-release-zip.sh $OutputArchArm $OutputPathArm64
|
||||||
|
mv "v2rayN-${OutputArchArm}.zip" "v2rayN-${OutputArchArm}-desktop.zip"
|
||||||
|
|
||||||
|
- name: Upload zip archive to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
with:
|
||||||
|
file: ${{ github.workspace }}/v2rayN*.zip
|
||||||
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
71
.github/workflows/build-windows.yml
vendored
Normal file
71
.github/workflows/build-windows.yml
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
name: release Windows
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
OutputArch: "windows-64"
|
||||||
|
OutputArchArm: "windows-arm64"
|
||||||
|
OutputPath64: "${{ github.workspace }}/v2rayN/Release/windows-64"
|
||||||
|
OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/windows-arm64"
|
||||||
|
OutputPath64Sc: "${{ github.workspace }}/v2rayN/Release/windows-64-SelfContained"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
configuration: [Release]
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5.0.0
|
||||||
|
|
||||||
|
- name: Setup
|
||||||
|
uses: actions/setup-dotnet@v5.0.0
|
||||||
|
with:
|
||||||
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cd v2rayN
|
||||||
|
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||||
|
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||||
|
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
|
||||||
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||||
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||||
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc
|
||||||
|
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: v2rayN-windows
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}/v2rayN/Release/windows*
|
||||||
|
|
||||||
|
# release zip archive
|
||||||
|
- name: Package release zip archive
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
run: |
|
||||||
|
chmod 755 package-release-zip.sh
|
||||||
|
./package-release-zip.sh $OutputArch $OutputPath64
|
||||||
|
./package-release-zip.sh $OutputArchArm $OutputPathArm64
|
||||||
|
./package-release-zip.sh "windows-64-SelfContained" $OutputPath64Sc
|
||||||
|
|
||||||
|
- name: Upload zip archive to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event.inputs.release_tag != ''
|
||||||
|
with:
|
||||||
|
file: ${{ github.workspace }}/v2rayN*.zip
|
||||||
|
tag: ${{ github.event.inputs.release_tag }}
|
||||||
|
file_glob: true
|
||||||
|
prerelease: true
|
||||||
60
.github/workflows/build.yml
vendored
60
.github/workflows/build.yml
vendored
|
|
@ -1,60 +0,0 @@
|
||||||
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 自动化部署,更新日志应该很快会手动更新
|
|
||||||
39
.github/workflows/winget-publish.yml
vendored
Normal file
39
.github/workflows/winget-publish.yml
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
name: WinGet submission on release
|
||||||
|
# based off of https://github.com/nushell/nushell/blob/main/.github/workflows/winget-submission.yml
|
||||||
|
# inspired by https://github.com/microsoft/PowerToys/blob/main/.github/workflows/package-submissions.yml
|
||||||
|
# Modified by @MerrickZ https://github.com/anpho
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
winget:
|
||||||
|
name: Publish winget package
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Submit v2ray package to Windows Package Manager Community Repository
|
||||||
|
run: |
|
||||||
|
|
||||||
|
$wingetPackage = "2dust.v2rayN"
|
||||||
|
$gitToken = "${{ secrets.PT_WINGET }}"
|
||||||
|
|
||||||
|
$github = Invoke-RestMethod -uri "https://api.github.com/repos/2dust/v2rayN/releases"
|
||||||
|
|
||||||
|
$targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1
|
||||||
|
|
||||||
|
$x64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip' | Select -ExpandProperty browser_download_url
|
||||||
|
$arm64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-arm64\.zip' | Select -ExpandProperty browser_download_url
|
||||||
|
|
||||||
|
$ver = $targetRelease.tag_name
|
||||||
|
|
||||||
|
# getting latest wingetcreate file
|
||||||
|
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||||
|
|
||||||
|
Write-Host "Updating with both x64 and arm64 installers"
|
||||||
|
Write-Host "Version: $ver"
|
||||||
|
Write-Host "x64 URL: $x64InstallerUrl"
|
||||||
|
Write-Host "arm64 URL: $arm64InstallerUrl"
|
||||||
|
|
||||||
|
.\wingetcreate.exe update $wingetPackage -s -v $ver -u "$x64InstallerUrl|x64" "$arm64InstallerUrl|arm64" -t $gitToken
|
||||||
416
.gitignore
vendored
416
.gitignore
vendored
|
|
@ -1,19 +1,401 @@
|
||||||
################################################################################
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。
|
## files generated by popular Visual Studio add-ons.
|
||||||
################################################################################
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
/v2rayN/.vs/
|
# User-specific files
|
||||||
/v2rayN/v2rayN/bin/Debug/app.publish
|
*.rsuser
|
||||||
/v2rayN/v2rayN/bin/Debug
|
*.suo
|
||||||
/v2rayN/v2rayN/bin/Release
|
|
||||||
/v2rayN/v2rayN/obj/
|
|
||||||
/v2rayN/.vs/v2rayN/DesignTimeBuild
|
|
||||||
/v2rayN/packages
|
|
||||||
.vs/ProjectSettings.json
|
|
||||||
.vs/slnx.sqlite
|
|
||||||
.vs/VSWorkspaceState.json
|
|
||||||
/v2rayN/v2rayUpgrade/bin/Debug
|
|
||||||
/v2rayN/v2rayUpgrade/bin/Release
|
|
||||||
/v2rayN/v2rayUpgrade/obj/
|
|
||||||
*.user
|
*.user
|
||||||
/.vs/v2rayN
|
*.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/
|
||||||
|
[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
|
||||||
|
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||||
|
!Directory.Build.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.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 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
*.ncb
|
||||||
|
*.aps
|
||||||
|
|
||||||
|
# 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/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "v2rayN/GlobalHotKeys"]
|
||||||
|
path = v2rayN/GlobalHotKeys
|
||||||
|
url = https://github.com/2dust/GlobalHotKeys
|
||||||
4
LICENSE
4
LICENSE
|
|
@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
Copyright (C) <year> <name of author>
|
Copyright (C) 2019-Present 2dust
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||||
If the program does terminal interaction, make it output a short
|
If the program does terminal interaction, make it output a short
|
||||||
notice like this when it starts in an interactive mode:
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
<program> Copyright (C) <year> <name of author>
|
v2rayN Copyright (C) 2019-Present 2dust
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
This is free software, and you are welcome to redistribute it
|
This is free software, and you are welcome to redistribute it
|
||||||
under certain conditions; type `show c' for details.
|
under certain conditions; type `show c' for details.
|
||||||
|
|
|
||||||
15
README.md
15
README.md
|
|
@ -1,23 +1,18 @@
|
||||||
# v2rayN
|
# 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)
|
|
||||||
|
|
||||||
|
A GUI client for Windows, Linux and macOS, support [Xray](https://github.com/XTLS/Xray-core)
|
||||||
|
and [sing-box](https://github.com/SagerNet/sing-box)
|
||||||
|
and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||||
|
|
||||||
[](https://github.com/2dust/v2rayN/commits/master)
|
[](https://github.com/2dust/v2rayN/commits/master)
|
||||||
[](https://www.codefactor.io/repository/github/2dust/v2rayn)
|
[](https://www.codefactor.io/repository/github/2dust/v2rayn)
|
||||||
[](https://github.com/2dust/v2rayN/releases)
|
[](https://github.com/2dust/v2rayN/releases)
|
||||||
[](https://t.me/v2rayn)
|
[](https://t.me/v2rayn)
|
||||||
|
|
||||||
|
|
||||||
## How to use
|
## 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
|
|
||||||
- (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)
|
|
||||||
|
|
||||||
|
Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details.
|
||||||
|
|
||||||
## Telegram Channel
|
## Telegram Channel
|
||||||
|
|
||||||
[github_2dust](https://t.me/github_2dust)
|
[github_2dust](https://t.me/github_2dust)
|
||||||
|
|
|
||||||
67
package-appimage.sh
Normal file
67
package-appimage.sh
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Install deps
|
||||||
|
sudo apt update -y
|
||||||
|
sudo apt install -y libfuse2 wget file
|
||||||
|
|
||||||
|
# Get tools
|
||||||
|
wget -qO appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||||
|
chmod +x appimagetool
|
||||||
|
|
||||||
|
# x86_64 AppDir
|
||||||
|
APPDIR_X64="AppDir-x86_64"
|
||||||
|
rm -rf "$APPDIR_X64"
|
||||||
|
mkdir -p "$APPDIR_X64/usr/lib/v2rayN" "$APPDIR_X64/usr/bin" "$APPDIR_X64/usr/share/applications" "$APPDIR_X64/usr/share/pixmaps"
|
||||||
|
cp -rf "$OutputPath64"/* "$APPDIR_X64/usr/lib/v2rayN" || true
|
||||||
|
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/usr/share/pixmaps/v2rayN.png" || true
|
||||||
|
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/v2rayN.png" || true
|
||||||
|
|
||||||
|
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_X64/AppRun"
|
||||||
|
chmod +x "$APPDIR_X64/AppRun"
|
||||||
|
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_X64/usr/bin/v2rayN"
|
||||||
|
cat > "$APPDIR_X64/v2rayN.desktop" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
|
Exec=v2rayN
|
||||||
|
Icon=v2rayN
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Network;
|
||||||
|
EOF
|
||||||
|
install -Dm644 "$APPDIR_X64/v2rayN.desktop" "$APPDIR_X64/usr/share/applications/v2rayN.desktop"
|
||||||
|
|
||||||
|
ARCH=x86_64 ./appimagetool "$APPDIR_X64" "v2rayN-${OutputArch}.AppImage"
|
||||||
|
file "v2rayN-${OutputArch}.AppImage" | grep -q 'x86-64'
|
||||||
|
|
||||||
|
# aarch64 AppDir
|
||||||
|
APPDIR_ARM64="AppDir-aarch64"
|
||||||
|
rm -rf "$APPDIR_ARM64"
|
||||||
|
mkdir -p "$APPDIR_ARM64/usr/lib/v2rayN" "$APPDIR_ARM64/usr/bin" "$APPDIR_ARM64/usr/share/applications" "$APPDIR_ARM64/usr/share/pixmaps"
|
||||||
|
cp -rf "$OutputPathArm64"/* "$APPDIR_ARM64/usr/lib/v2rayN" || true
|
||||||
|
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/usr/share/pixmaps/v2rayN.png" || true
|
||||||
|
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/v2rayN.png" || true
|
||||||
|
|
||||||
|
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_ARM64/AppRun"
|
||||||
|
chmod +x "$APPDIR_ARM64/AppRun"
|
||||||
|
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_ARM64/usr/bin/v2rayN"
|
||||||
|
cat > "$APPDIR_ARM64/v2rayN.desktop" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
|
Exec=v2rayN
|
||||||
|
Icon=v2rayN
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Network;
|
||||||
|
EOF
|
||||||
|
install -Dm644 "$APPDIR_ARM64/v2rayN.desktop" "$APPDIR_ARM64/usr/share/applications/v2rayN.desktop"
|
||||||
|
|
||||||
|
# aarch64 runtime
|
||||||
|
wget -qO runtime-aarch64 https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-aarch64
|
||||||
|
chmod +x runtime-aarch64
|
||||||
|
|
||||||
|
# build aarch64 AppImage
|
||||||
|
ARCH=aarch64 ./appimagetool --runtime-file ./runtime-aarch64 "$APPDIR_ARM64" "v2rayN-${OutputArchArm}.AppImage"
|
||||||
|
file "v2rayN-${OutputArchArm}.AppImage" | grep -q 'ARM aarch64'
|
||||||
69
package-debian.sh
Normal file
69
package-debian.sh
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
Arch="$1"
|
||||||
|
OutputPath="$2"
|
||||||
|
Version="$3"
|
||||||
|
|
||||||
|
FileName="v2rayN-${Arch}.zip"
|
||||||
|
wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/$FileName"
|
||||||
|
7z x $FileName
|
||||||
|
cp -rf v2rayN-${Arch}/* $OutputPath
|
||||||
|
|
||||||
|
PackagePath="v2rayN-Package-${Arch}"
|
||||||
|
mkdir -p "${PackagePath}/DEBIAN"
|
||||||
|
mkdir -p "${PackagePath}/opt"
|
||||||
|
cp -rf $OutputPath "${PackagePath}/opt/v2rayN"
|
||||||
|
echo "When this file exists, app will not store configs under this folder" > "${PackagePath}/opt/v2rayN/NotStoreConfigHere.txt"
|
||||||
|
|
||||||
|
if [ $Arch = "linux-64" ]; then
|
||||||
|
Arch2="amd64"
|
||||||
|
else
|
||||||
|
Arch2="arm64"
|
||||||
|
fi
|
||||||
|
echo $Arch2
|
||||||
|
|
||||||
|
# basic
|
||||||
|
cat >"${PackagePath}/DEBIAN/control" <<-EOF
|
||||||
|
Package: v2rayN
|
||||||
|
Version: $Version
|
||||||
|
Architecture: $Arch2
|
||||||
|
Maintainer: https://github.com/2dust/v2rayN
|
||||||
|
Depends: desktop-file-utils, xdg-utils
|
||||||
|
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >"${PackagePath}/DEBIAN/postinst" <<-EOF
|
||||||
|
if [ ! -s /usr/share/applications/v2rayN.desktop ]; then
|
||||||
|
cat >/usr/share/applications/v2rayN.desktop<<-END
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
|
Exec=/opt/v2rayN/v2rayN
|
||||||
|
Icon=/opt/v2rayN/v2rayN.png
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Network;Application;
|
||||||
|
END
|
||||||
|
fi
|
||||||
|
|
||||||
|
update-desktop-database
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo chmod 0755 "${PackagePath}/DEBIAN/postinst"
|
||||||
|
sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN"
|
||||||
|
sudo chmod 0755 "${PackagePath}/opt/v2rayN/AmazTool"
|
||||||
|
|
||||||
|
# Patch
|
||||||
|
# set owner to root:root
|
||||||
|
sudo chown -R root:root "${PackagePath}"
|
||||||
|
# set all directories to 755 (readable & traversable by all users)
|
||||||
|
sudo find "${PackagePath}/opt/v2rayN" -type d -exec chmod 755 {} +
|
||||||
|
# set all regular files to 644 (readable by all users)
|
||||||
|
sudo find "${PackagePath}/opt/v2rayN" -type f -exec chmod 644 {} +
|
||||||
|
# ensure main binaries are 755 (executable by all users)
|
||||||
|
sudo chmod 755 "${PackagePath}/opt/v2rayN/v2rayN" 2>/dev/null || true
|
||||||
|
sudo chmod 755 "${PackagePath}/opt/v2rayN/AmazTool" 2>/dev/null || true
|
||||||
|
|
||||||
|
# build deb package
|
||||||
|
sudo dpkg-deb -Zxz --build $PackagePath
|
||||||
|
sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb"
|
||||||
58
package-osx.sh
Executable file
58
package-osx.sh
Executable file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
Arch="$1"
|
||||||
|
OutputPath="$2"
|
||||||
|
Version="$3"
|
||||||
|
|
||||||
|
FileName="v2rayN-${Arch}.zip"
|
||||||
|
wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/$FileName"
|
||||||
|
7z x $FileName
|
||||||
|
cp -rf v2rayN-${Arch}/* $OutputPath
|
||||||
|
|
||||||
|
PackagePath="v2rayN-Package-${Arch}"
|
||||||
|
mkdir -p "$PackagePath/v2rayN.app/Contents/Resources"
|
||||||
|
cp -rf "$OutputPath" "$PackagePath/v2rayN.app/Contents/MacOS"
|
||||||
|
cp -f "$PackagePath/v2rayN.app/Contents/MacOS/v2rayN.icns" "$PackagePath/v2rayN.app/Contents/Resources/AppIcon.icns"
|
||||||
|
echo "When this file exists, app will not store configs under this folder" > "$PackagePath/v2rayN.app/Contents/MacOS/NotStoreConfigHere.txt"
|
||||||
|
chmod +x "$PackagePath/v2rayN.app/Contents/MacOS/v2rayN"
|
||||||
|
|
||||||
|
cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>v2rayN</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>v2rayN</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>AppIcon</string>
|
||||||
|
<key>CFBundleIconName</key>
|
||||||
|
<string>AppIcon</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>2dust.v2rayN</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>v2rayN</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>${Version}</string>
|
||||||
|
<key>CSResourcesFileMapped</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
create-dmg \
|
||||||
|
--volname "v2rayN Installer" \
|
||||||
|
--window-size 700 420 \
|
||||||
|
--icon-size 100 \
|
||||||
|
--icon "v2rayN.app" 160 185 \
|
||||||
|
--hide-extension "v2rayN.app" \
|
||||||
|
--app-drop-link 500 185 \
|
||||||
|
"v2rayN-${Arch}.dmg" \
|
||||||
|
"$PackagePath/v2rayN.app"
|
||||||
15
package-release-zip.sh
Normal file
15
package-release-zip.sh
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
Arch="$1"
|
||||||
|
OutputPath="$2"
|
||||||
|
|
||||||
|
OutputArch="v2rayN-${Arch}"
|
||||||
|
FileName="v2rayN-${Arch}.zip"
|
||||||
|
|
||||||
|
wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/$FileName"
|
||||||
|
|
||||||
|
ZipPath64="./$OutputArch"
|
||||||
|
mkdir $ZipPath64
|
||||||
|
|
||||||
|
cp -rf $OutputPath "$ZipPath64/$OutputArch"
|
||||||
|
7z a -tZip $FileName "$ZipPath64/$OutputArch" -mx1
|
||||||
808
package-rhel.sh
Normal file
808
package-rhel.sh
Normal file
|
|
@ -0,0 +1,808 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
|
||||||
|
if [[ -r /etc/os-release ]]; then
|
||||||
|
. /etc/os-release
|
||||||
|
case "$ID" in
|
||||||
|
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
|
||||||
|
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unsupported system: $NAME ($ID)."
|
||||||
|
echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS or Ubuntu/Debian."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
echo "[ERROR] Cannot detect system (missing /etc/os-release)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== Config & Parse arguments =========================================================
|
||||||
|
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
|
||||||
|
WITH_CORE="both" # Default: bundle both xray+sing-box
|
||||||
|
AUTOSTART=0 # 1 = enable system-wide autostart (/etc/xdg/autostart)
|
||||||
|
FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads
|
||||||
|
ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target)
|
||||||
|
BUILD_FROM="" # --buildfrom 1|2|3 to select channel non-interactively
|
||||||
|
|
||||||
|
# If the first argument starts with --, do not treat it as a version number
|
||||||
|
if [[ "${VERSION_ARG:-}" == --* ]]; then
|
||||||
|
VERSION_ARG=""
|
||||||
|
fi
|
||||||
|
# Take the first non --* argument as version, discard it
|
||||||
|
if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi
|
||||||
|
|
||||||
|
# Parse remaining optional arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--with-core) WITH_CORE="${2:-both}"; shift 2;;
|
||||||
|
--autostart) AUTOSTART=1; shift;;
|
||||||
|
--xray-ver) XRAY_VER="${2:-}"; shift 2;;
|
||||||
|
--singbox-ver) SING_VER="${2:-}"; shift 2;;
|
||||||
|
--netcore) FORCE_NETCORE=1; shift;;
|
||||||
|
--arch) ARCH_OVERRIDE="${2:-}"; shift 2;;
|
||||||
|
--buildfrom) BUILD_FROM="${2:-}"; shift 2;;
|
||||||
|
*)
|
||||||
|
if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi
|
||||||
|
shift;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Conflict: version number AND --buildfrom cannot be used together
|
||||||
|
if [[ -n "${VERSION_ARG:-}" && -n "${BUILD_FROM:-}" ]]; then
|
||||||
|
echo "[ERROR] You cannot specify both an explicit version and --buildfrom at the same time."
|
||||||
|
echo " Provide either a version (e.g. 7.14.0) OR --buildfrom 1|2|3."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== Environment check + Dependencies ========================================
|
||||||
|
host_arch="$(uname -m)"
|
||||||
|
[[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; }
|
||||||
|
|
||||||
|
install_ok=0
|
||||||
|
case "$ID" in
|
||||||
|
# ------------------------------ RHEL family (UNCHANGED) ------------------------------
|
||||||
|
rhel|rocky|almalinux|centos)
|
||||||
|
if command -v dnf >/dev/null 2>&1; then
|
||||||
|
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
||||||
|
sudo dnf -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
|
||||||
|
install_ok=1
|
||||||
|
elif command -v yum >/dev/null 2>&1; then
|
||||||
|
sudo yum -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
||||||
|
sudo yum -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
|
||||||
|
install_ok=1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
# ------------------------------ Ubuntu ----------------------------------------------
|
||||||
|
ubuntu)
|
||||||
|
sudo apt-get update
|
||||||
|
# Ensure 'universe' (Ubuntu) to get 'rpm'
|
||||||
|
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
|
||||||
|
sudo apt-get -y install software-properties-common || true
|
||||||
|
sudo add-apt-repository -y universe || true
|
||||||
|
sudo apt-get update
|
||||||
|
fi
|
||||||
|
# Base tools + rpm (provides rpmbuild)
|
||||||
|
sudo apt-get -y install curl unzip tar rsync rpm || true
|
||||||
|
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
|
||||||
|
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
|
||||||
|
# rpmbuild presence check
|
||||||
|
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||||
|
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
||||||
|
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# .NET SDK 8 (best effort via apt)
|
||||||
|
if ! command -v dotnet >/dev/null 2>&1; then
|
||||||
|
sudo apt-get -y install dotnet-sdk-8.0 || true
|
||||||
|
sudo apt-get -y install dotnet-sdk-8 || true
|
||||||
|
sudo apt-get -y install dotnet-sdk || true
|
||||||
|
fi
|
||||||
|
install_ok=1
|
||||||
|
;;
|
||||||
|
# ------------------------------ Debian (KEEP, with local dotnet install) ------------
|
||||||
|
debian)
|
||||||
|
sudo apt-get update
|
||||||
|
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip
|
||||||
|
sudo apt-get -y install curl unzip tar rsync rpm binutils || true
|
||||||
|
# rpmbuild presence check
|
||||||
|
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||||
|
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
||||||
|
echo " Please ensure 'rpm' is available from Debian repos."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet
|
||||||
|
if ! command -v dotnet >/dev/null 2>&1; then
|
||||||
|
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..."
|
||||||
|
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
|
||||||
|
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh"
|
||||||
|
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet"
|
||||||
|
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
|
||||||
|
export DOTNET_ROOT="$HOME/.dotnet"
|
||||||
|
if ! command -v dotnet >/dev/null 2>&1; then
|
||||||
|
echo "[ERROR] dotnet installation failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
install_ok=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$install_ok" -ne 1 ]]; then
|
||||||
|
echo "[WARN] Could not auto-install dependencies for '$ID'. Make sure these are available:"
|
||||||
|
echo " dotnet-sdk 8.x, curl, unzip, tar, rsync, rpm, rpmdevtools, rpm-build (on RPM-based distros)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
command -v curl >/dev/null
|
||||||
|
|
||||||
|
# Root directory = the script's location
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Git submodules (best effort)
|
||||||
|
if [[ -f .gitmodules ]]; then
|
||||||
|
git submodule sync --recursive || true
|
||||||
|
git submodule update --init --recursive || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== Locate project ================================================================
|
||||||
|
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
|
if [[ ! -f "$PROJECT" ]]; then
|
||||||
|
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
|
||||||
|
fi
|
||||||
|
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
|
||||||
|
|
||||||
|
# ===== Resolve GUI version & auto checkout ============================================
|
||||||
|
VERSION=""
|
||||||
|
|
||||||
|
choose_channel() {
|
||||||
|
# If --buildfrom provided, map it directly and skip interaction.
|
||||||
|
if [[ -n "${BUILD_FROM:-}" ]]; then
|
||||||
|
case "$BUILD_FROM" in
|
||||||
|
1) echo "latest"; return 0;;
|
||||||
|
2) echo "prerelease"; return 0;;
|
||||||
|
3) echo "keep"; return 0;;
|
||||||
|
*) echo "[ERROR] Invalid --buildfrom value: ${BUILD_FROM}. Use 1|2|3." >&2; exit 1;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Print menu to stderr and read from /dev/tty so stdout only carries the token.
|
||||||
|
local ch="latest" sel=""
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
echo "[?] Choose v2rayN release channel:" >&2
|
||||||
|
echo " 1) Latest (stable) [default]" >&2
|
||||||
|
echo " 2) Pre-release (preview)" >&2
|
||||||
|
echo " 3) Keep current (do nothing)" >&2
|
||||||
|
printf "Enter 1, 2 or 3 [default 1]: " >&2
|
||||||
|
if read -r sel </dev/tty; then
|
||||||
|
case "${sel:-}" in
|
||||||
|
2) ch="prerelease" ;;
|
||||||
|
3) ch="keep" ;;
|
||||||
|
*) ch="latest" ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
ch="latest"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ch="latest"
|
||||||
|
fi
|
||||||
|
echo "$ch"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_latest_tag_latest() {
|
||||||
|
# Resolve /releases/latest → tag_name
|
||||||
|
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \
|
||||||
|
| grep -Eo '"tag_name":\s*"v?[^"]+"' \
|
||||||
|
| head -n1 \
|
||||||
|
| sed -E 's/.*"tag_name":\s*"v?([^"]+)".*/\1/'
|
||||||
|
}
|
||||||
|
|
||||||
|
get_latest_tag_prerelease() {
|
||||||
|
# Resolve newest prerelease=true tag; prefer jq, fallback to sed/grep (no awk)
|
||||||
|
local json tag
|
||||||
|
json="$(curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20")" || return 1
|
||||||
|
|
||||||
|
# 1) Use jq if present
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
tag="$(printf '%s' "$json" \
|
||||||
|
| jq -r '[.[] | select(.prerelease==true)][0].tag_name' 2>/dev/null \
|
||||||
|
| sed 's/^v//')" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2) Fallback to sed/grep only
|
||||||
|
if [[ -z "${tag:-}" || "${tag:-}" == "null" ]]; then
|
||||||
|
tag="$(printf '%s' "$json" \
|
||||||
|
| tr '\n' ' ' \
|
||||||
|
| sed 's/},[[:space:]]*{/\n/g' \
|
||||||
|
| grep -m1 -E '"prerelease"[[:space:]]*:[[:space:]]*true' \
|
||||||
|
| grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"v?[^"]+"' \
|
||||||
|
| head -n1 \
|
||||||
|
| sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/')" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "${tag:-}" && "${tag:-}" != "null" ]] || return 1
|
||||||
|
printf '%s\n' "$tag"
|
||||||
|
}
|
||||||
|
|
||||||
|
git_try_checkout() {
|
||||||
|
# Try a series of refs and checkout when found.
|
||||||
|
local want="$1" ref=""
|
||||||
|
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
git fetch --tags --force --prune --depth=1 || true
|
||||||
|
if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then
|
||||||
|
ref="v${want}"
|
||||||
|
elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then
|
||||||
|
ref="${want}"
|
||||||
|
elif git rev-parse --verify "${want}" >/dev/null 2>&1; then
|
||||||
|
ref="${want}"
|
||||||
|
fi
|
||||||
|
if [[ -n "$ref" ]]; then
|
||||||
|
echo "[OK] Found ref '${ref}', checking out..."
|
||||||
|
git checkout -f "${ref}"
|
||||||
|
if [[ -f .gitmodules ]]; then
|
||||||
|
git submodule sync --recursive || true
|
||||||
|
git submodule update --init --recursive || true
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
if [[ -n "${VERSION_ARG:-}" ]]; then
|
||||||
|
echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}"
|
||||||
|
if git_try_checkout "${VERSION_ARG#v}"; then
|
||||||
|
VERSION="${VERSION_ARG#v}"
|
||||||
|
else
|
||||||
|
echo "[WARN] Tag '${VERSION_ARG}' not found."
|
||||||
|
ch="$(choose_channel)"
|
||||||
|
if [[ "$ch" == "keep" ]]; then
|
||||||
|
echo "[*] Keep current repository state (no checkout)."
|
||||||
|
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||||
|
VERSION="$(git describe --tags --abbrev=0)"
|
||||||
|
else
|
||||||
|
VERSION="0.0.0+git"
|
||||||
|
fi
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
else
|
||||||
|
echo "[*] Resolving ${ch} tag from GitHub releases..."
|
||||||
|
tag=""
|
||||||
|
if [[ "$ch" == "prerelease" ]]; then
|
||||||
|
tag="$(get_latest_tag_prerelease || true)"
|
||||||
|
if [[ -z "$tag" ]]; then
|
||||||
|
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
|
||||||
|
echo "[*] Latest tag for '${ch}': ${tag}"
|
||||||
|
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
|
||||||
|
VERSION="${tag#v}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ch="$(choose_channel)"
|
||||||
|
if [[ "$ch" == "keep" ]]; then
|
||||||
|
echo "[*] Keep current repository state (no checkout)."
|
||||||
|
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||||
|
VERSION="$(git describe --tags --abbrev=0)"
|
||||||
|
else
|
||||||
|
VERSION="0.0.0+git"
|
||||||
|
fi
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
else
|
||||||
|
echo "[*] Resolving ${ch} tag from GitHub releases..."
|
||||||
|
tag=""
|
||||||
|
if [[ "$ch" == "prerelease" ]]; then
|
||||||
|
tag="$(get_latest_tag_prerelease || true)"
|
||||||
|
if [[ -z "$tag" ]]; then
|
||||||
|
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
|
||||||
|
echo "[*] Latest tag for '${ch}': ${tag}"
|
||||||
|
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
|
||||||
|
VERSION="${tag#v}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree."
|
||||||
|
VERSION="${VERSION_ARG:-}"
|
||||||
|
if [[ -z "$VERSION" ]]; then
|
||||||
|
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||||
|
VERSION="$(git describe --tags --abbrev=0)"
|
||||||
|
else
|
||||||
|
VERSION="0.0.0+git"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
fi
|
||||||
|
echo "[*] GUI version resolved as: ${VERSION}"
|
||||||
|
|
||||||
|
# ===== Helpers for core/rules download (use RID_DIR for arch sync) =====================
|
||||||
|
download_xray() {
|
||||||
|
# Download Xray core and install to outdir/xray
|
||||||
|
local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip"
|
||||||
|
mkdir -p "$outdir"
|
||||||
|
if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi
|
||||||
|
if [[ -z "$ver" ]]; then
|
||||||
|
ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \
|
||||||
|
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
|
||||||
|
fi
|
||||||
|
[[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; }
|
||||||
|
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
||||||
|
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip"
|
||||||
|
else
|
||||||
|
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip"
|
||||||
|
fi
|
||||||
|
echo "[+] Download xray: $url"
|
||||||
|
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
|
||||||
|
curl -fL "$url" -o "$tmp/$zipname"
|
||||||
|
unzip -q "$tmp/$zipname" -d "$tmp"
|
||||||
|
install -Dm755 "$tmp/xray" "$outdir/xray"
|
||||||
|
}
|
||||||
|
|
||||||
|
download_singbox() {
|
||||||
|
# Download sing-box core and install to outdir/sing-box
|
||||||
|
local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin
|
||||||
|
mkdir -p "$outdir"
|
||||||
|
if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi
|
||||||
|
if [[ -z "$ver" ]]; then
|
||||||
|
ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \
|
||||||
|
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
|
||||||
|
fi
|
||||||
|
[[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; }
|
||||||
|
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
||||||
|
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz"
|
||||||
|
else
|
||||||
|
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz"
|
||||||
|
fi
|
||||||
|
echo "[+] Download sing-box: $url"
|
||||||
|
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
|
||||||
|
curl -fL "$url" -o "$tmp/$tarname"
|
||||||
|
tar -C "$tmp" -xzf "$tmp/$tarname"
|
||||||
|
bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)"
|
||||||
|
[[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; return 1; }
|
||||||
|
install -Dm755 "$bin" "$outdir/sing-box"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- NEW: download_mihomo (REQUIRED in --netcore mode) ----
|
||||||
|
download_mihomo() {
|
||||||
|
# Download mihomo into outroot/bin/mihomo/mihomo
|
||||||
|
local outroot="$1"
|
||||||
|
local url=""
|
||||||
|
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64/bin/mihomo/mihomo"
|
||||||
|
else
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo"
|
||||||
|
fi
|
||||||
|
echo "[+] Download mihomo: $url"
|
||||||
|
mkdir -p "$outroot/bin/mihomo"
|
||||||
|
curl -fL "$url" -o "$outroot/bin/mihomo/mihomo"
|
||||||
|
chmod +x "$outroot/bin/mihomo/mihomo" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Move geo files to a unified path: outroot/bin
|
||||||
|
unify_geo_layout() {
|
||||||
|
local outroot="$1"
|
||||||
|
mkdir -p "$outroot/bin"
|
||||||
|
local names=( \
|
||||||
|
"geosite.dat" \
|
||||||
|
"geoip.dat" \
|
||||||
|
"geoip-only-cn-private.dat" \
|
||||||
|
"Country.mmdb" \
|
||||||
|
"geoip.metadb" \
|
||||||
|
)
|
||||||
|
for n in "${names[@]}"; do
|
||||||
|
# If file exists under bin/xray/, move it up to bin/
|
||||||
|
if [[ -f "$outroot/bin/xray/$n" ]]; then
|
||||||
|
mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n"
|
||||||
|
fi
|
||||||
|
# If file already in bin/, leave it as-is
|
||||||
|
if [[ -f "$outroot/bin/$n" ]]; then
|
||||||
|
:
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download geo/rule assets; then unify to bin/
|
||||||
|
download_geo_assets() {
|
||||||
|
local outroot="$1"
|
||||||
|
local bin_dir="$outroot/bin"
|
||||||
|
local srss_dir="$bin_dir/srss"
|
||||||
|
mkdir -p "$bin_dir" "$srss_dir"
|
||||||
|
|
||||||
|
echo "[+] Download Xray Geo to ${bin_dir}"
|
||||||
|
curl -fsSL -o "$bin_dir/geosite.dat" \
|
||||||
|
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||||
|
curl -fsSL -o "$bin_dir/geoip.dat" \
|
||||||
|
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||||
|
curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \
|
||||||
|
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat"
|
||||||
|
curl -fsSL -o "$bin_dir/Country.mmdb" \
|
||||||
|
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
|
||||||
|
|
||||||
|
echo "[+] Download sing-box rule DB & rule-sets"
|
||||||
|
curl -fsSL -o "$bin_dir/geoip.metadb" \
|
||||||
|
"https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true
|
||||||
|
|
||||||
|
for f in \
|
||||||
|
geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \
|
||||||
|
geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do
|
||||||
|
curl -fsSL -o "$srss_dir/$f" \
|
||||||
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
|
||||||
|
done
|
||||||
|
for f in \
|
||||||
|
geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \
|
||||||
|
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
|
||||||
|
curl -fsSL -o "$srss_dir/$f" \
|
||||||
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# Unify to bin/
|
||||||
|
unify_geo_layout "$outroot"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prefer the prebuilt v2rayN core bundle; then unify geo layout
|
||||||
|
download_v2rayn_bundle() {
|
||||||
|
local outroot="$1"
|
||||||
|
local url=""
|
||||||
|
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64.zip"
|
||||||
|
else
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip"
|
||||||
|
fi
|
||||||
|
echo "[+] Try v2rayN bundle archive: $url"
|
||||||
|
local tmp zipname
|
||||||
|
tmp="$(mktemp -d)"; zipname="$tmp/v2rayn.zip"
|
||||||
|
curl -fL "$url" -o "$zipname" || { echo "[!] Bundle download failed"; return 1; }
|
||||||
|
unzip -q "$zipname" -d "$tmp" || { echo "[!] Bundle unzip failed"; return 1; }
|
||||||
|
|
||||||
|
if [[ -d "$tmp/bin" ]]; then
|
||||||
|
mkdir -p "$outroot/bin"
|
||||||
|
rsync -a "$tmp/bin/" "$outroot/bin/"
|
||||||
|
else
|
||||||
|
rsync -a "$tmp/" "$outroot/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
|
||||||
|
# keep mihomo
|
||||||
|
# find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
|
||||||
|
local nested_dir
|
||||||
|
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
|
||||||
|
if [[ -n "${nested_dir:-}" && -d "$nested_dir/bin" ]]; then
|
||||||
|
mkdir -p "$outroot/bin"
|
||||||
|
rsync -a "$nested_dir/bin/" "$outroot/bin/"
|
||||||
|
rm -rf "$nested_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unify to bin/
|
||||||
|
unify_geo_layout "$outroot"
|
||||||
|
|
||||||
|
echo "[+] Bundle extracted to $outroot"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== Build results collection for --arch all ========================================
|
||||||
|
BUILT_RPMS=() # Will collect absolute paths of built RPMs
|
||||||
|
BUILT_ALL=0 # Flag to know if we should print the final summary
|
||||||
|
|
||||||
|
# ===== Build (single-arch) function ====================================================
|
||||||
|
build_for_arch() {
|
||||||
|
# $1: target short arch: x64 | arm64
|
||||||
|
local short="$1"
|
||||||
|
local rid rpm_target archdir
|
||||||
|
case "$short" in
|
||||||
|
x64) rid="linux-x64"; rpm_target="x86_64"; archdir="x86_64" ;;
|
||||||
|
arm64) rid="linux-arm64"; rpm_target="aarch64"; archdir="aarch64" ;;
|
||||||
|
*) echo "[ERROR] Unknown arch '$short' (use x64|arm64)"; return 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "[*] Building for target: $short (RID=$rid, RPM --target $rpm_target)"
|
||||||
|
|
||||||
|
# .NET publish (self-contained) for this RID
|
||||||
|
dotnet clean "$PROJECT" -c Release
|
||||||
|
rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true
|
||||||
|
|
||||||
|
dotnet restore "$PROJECT"
|
||||||
|
dotnet publish "$PROJECT" \
|
||||||
|
-c Release -r "$rid" \
|
||||||
|
-p:PublishSingleFile=false \
|
||||||
|
-p:SelfContained=true \
|
||||||
|
-p:IncludeNativeLibrariesForSelfExtract=true
|
||||||
|
|
||||||
|
# Per-arch variables (scoped)
|
||||||
|
local RID_DIR="$rid"
|
||||||
|
local PUBDIR
|
||||||
|
PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish"
|
||||||
|
[[ -d "$PUBDIR" ]]
|
||||||
|
|
||||||
|
# Make RID_DIR visible to download helpers (they read this var)
|
||||||
|
export RID_DIR
|
||||||
|
|
||||||
|
# Per-arch working area
|
||||||
|
local PKGROOT="v2rayN-publish"
|
||||||
|
local WORKDIR
|
||||||
|
WORKDIR="$(mktemp -d)"
|
||||||
|
trap '[[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"' RETURN
|
||||||
|
|
||||||
|
# rpmbuild topdir selection
|
||||||
|
local TOPDIR SPECDIR SOURCEDIR USE_TOPDIR_DEFINE
|
||||||
|
if [[ "$ID" =~ ^(rhel|rocky|almalinux|centos)$ ]]; then
|
||||||
|
rpmdev-setuptree
|
||||||
|
TOPDIR="${HOME}/rpmbuild"
|
||||||
|
SPECDIR="${TOPDIR}/SPECS"
|
||||||
|
SOURCEDIR="${TOPDIR}/SOURCES"
|
||||||
|
USE_TOPDIR_DEFINE=0
|
||||||
|
else
|
||||||
|
TOPDIR="${WORKDIR}/rpmbuild"
|
||||||
|
SPECDIR="${TOPDIR}/SPECS}"
|
||||||
|
SOURCEDIR="${TOPDIR}/SOURCES"
|
||||||
|
mkdir -p "${SPECDIR}" "${SOURCEDIR}" "${TOPDIR}/BUILD" "${TOPDIR}/RPMS" "${TOPDIR}/SRPMS"
|
||||||
|
USE_TOPDIR_DEFINE=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stage publish content
|
||||||
|
mkdir -p "$WORKDIR/$PKGROOT"
|
||||||
|
cp -a "$PUBDIR/." "$WORKDIR/$PKGROOT/"
|
||||||
|
|
||||||
|
# Optional icon
|
||||||
|
local ICON_CANDIDATE
|
||||||
|
ICON_CANDIDATE="$(dirname "$PROJECT")/../v2rayN.Desktop/v2rayN.png"
|
||||||
|
[[ -f "$ICON_CANDIDATE" ]] && cp "$ICON_CANDIDATE" "$WORKDIR/$PKGROOT/v2rayn.png" || true
|
||||||
|
|
||||||
|
# Prepare bin structure
|
||||||
|
mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box"
|
||||||
|
|
||||||
|
# Bundle / cores per-arch
|
||||||
|
if [[ "$FORCE_NETCORE" -eq 0 ]]; then
|
||||||
|
if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then
|
||||||
|
echo "[*] Using v2rayN bundle archive."
|
||||||
|
else
|
||||||
|
echo "[*] Bundle failed, fallback to separate core + rules."
|
||||||
|
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
|
||||||
|
fi
|
||||||
|
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||||
|
fi
|
||||||
|
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[*] --netcore specified: use separate core + rules."
|
||||||
|
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
|
||||||
|
fi
|
||||||
|
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||||
|
fi
|
||||||
|
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||||
|
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
|
||||||
|
download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Tarball
|
||||||
|
mkdir -p "$SOURCEDIR"
|
||||||
|
tar -C "$WORKDIR" -czf "$SOURCEDIR/$PKGROOT.tar.gz" "$PKGROOT"
|
||||||
|
|
||||||
|
# SPEC
|
||||||
|
local SPECFILE="$SPECDIR/v2rayN.spec"
|
||||||
|
mkdir -p "$SPECDIR"
|
||||||
|
cat > "$SPECFILE" <<'SPEC'
|
||||||
|
%global debug_package %{nil}
|
||||||
|
%undefine _debuginfo_subpackages
|
||||||
|
%undefine _debugsource_packages
|
||||||
|
# Ignore outdated LTTng dependencies incorrectly reported by the .NET runtime (to avoid installation failures)
|
||||||
|
%global __requires_exclude ^liblttng-ust\.so\..*$
|
||||||
|
|
||||||
|
Name: v2rayN
|
||||||
|
Version: __VERSION__
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64)
|
||||||
|
License: GPL-3.0-only
|
||||||
|
URL: https://github.com/2dust/v2rayN
|
||||||
|
BugURL: https://github.com/2dust/v2rayN/issues
|
||||||
|
ExclusiveArch: aarch64 x86_64
|
||||||
|
Source0: __PKGROOT__.tar.gz
|
||||||
|
|
||||||
|
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
|
||||||
|
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
|
||||||
|
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL, xdg-utils
|
||||||
|
|
||||||
|
%description
|
||||||
|
v2rayN Linux for Red Hat Enterprise Linux
|
||||||
|
Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / Shadowsocks / tuic / WireGuard
|
||||||
|
Support Red Hat Enterprise Linux / Fedora Linux / Rocky Linux / AlmaLinux / CentOS
|
||||||
|
For more information, Please visit our website
|
||||||
|
https://github.com/2dust/v2rayN
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%setup -q -n __PKGROOT__
|
||||||
|
|
||||||
|
%build
|
||||||
|
# no build
|
||||||
|
|
||||||
|
%install
|
||||||
|
install -dm0755 %{buildroot}/opt/v2rayN
|
||||||
|
cp -a * %{buildroot}/opt/v2rayN/
|
||||||
|
|
||||||
|
# Launcher (prefer native ELF first, then DLL fallback)
|
||||||
|
install -dm0755 %{buildroot}%{_bindir}
|
||||||
|
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
|
||||||
|
#!/usr/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
DIR="/opt/v2rayN"
|
||||||
|
|
||||||
|
# Prefer native apphost
|
||||||
|
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi
|
||||||
|
|
||||||
|
# DLL fallback
|
||||||
|
for dll in v2rayN.Desktop.dll v2rayN.dll; do
|
||||||
|
if [[ -f "$DIR/$dll" ]]; then exec /usr/bin/dotnet "$DIR/$dll" "$@"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "v2rayN launcher: no executable found in $DIR" >&2
|
||||||
|
ls -l "$DIR" >&2 || true
|
||||||
|
exit 1
|
||||||
|
EOF
|
||||||
|
chmod 0755 %{buildroot}%{_bindir}/v2rayn
|
||||||
|
|
||||||
|
# Desktop file
|
||||||
|
install -dm0755 %{buildroot}%{_datadir}/applications
|
||||||
|
cat > %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF'
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=v2rayN for Red Hat Enterprise Linux
|
||||||
|
Exec=v2rayn
|
||||||
|
Icon=v2rayn
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Icon
|
||||||
|
if [ -f "%{_builddir}/__PKGROOT__/v2rayn.png" ]; then
|
||||||
|
install -dm0755 %{buildroot}%{_datadir}/icons/hicolor/256x256/apps
|
||||||
|
install -m0644 %{_builddir}/__PKGROOT__/v2rayn.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
|
||||||
|
fi
|
||||||
|
|
||||||
|
%post
|
||||||
|
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
|
||||||
|
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
%postun
|
||||||
|
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
|
||||||
|
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
%files
|
||||||
|
%{_bindir}/v2rayn
|
||||||
|
/opt/v2rayN
|
||||||
|
%{_datadir}/applications/v2rayn.desktop
|
||||||
|
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
|
||||||
|
SPEC
|
||||||
|
|
||||||
|
# Autostart injection (inside %install) and %files entry
|
||||||
|
if [[ "$AUTOSTART" -eq 1 ]]; then
|
||||||
|
awk '
|
||||||
|
BEGIN{ins=0}
|
||||||
|
/^%post$/ && !ins {
|
||||||
|
print "# --- Autostart (.desktop) ---"
|
||||||
|
print "install -dm0755 %{buildroot}/etc/xdg/autostart"
|
||||||
|
print "cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << '\''EOF'\''"
|
||||||
|
print "[Desktop Entry]"
|
||||||
|
print "Type=Application"
|
||||||
|
print "Name=v2rayN (Autostart)"
|
||||||
|
print "Exec=v2rayn"
|
||||||
|
print "X-GNOME-Autostart-enabled=true"
|
||||||
|
print "NoDisplay=false"
|
||||||
|
print "EOF"
|
||||||
|
ins=1
|
||||||
|
}
|
||||||
|
{print}
|
||||||
|
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
|
||||||
|
|
||||||
|
awk '
|
||||||
|
BEGIN{infiles=0; done=0}
|
||||||
|
/^%files$/ {infiles=1}
|
||||||
|
infiles && done==0 && $0 ~ /%{_datadir}\/icons\/hicolor\/256x256\/apps\/v2rayn\.png/ {
|
||||||
|
print
|
||||||
|
print "%config(noreplace) /etc/xdg/autostart/v2rayn.desktop"
|
||||||
|
done=1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{print}
|
||||||
|
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Replace placeholders
|
||||||
|
sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE"
|
||||||
|
sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE"
|
||||||
|
|
||||||
|
# ----- Select proper 'strip' per target arch on Ubuntu only (cross-binutils) -----
|
||||||
|
# NOTE: We define only __strip to point to the target-arch strip.
|
||||||
|
# DO NOT override __brp_strip (it must stay the brp script path).
|
||||||
|
local STRIP_ARGS=()
|
||||||
|
if [[ "$ID" == "ubuntu" ]]; then
|
||||||
|
local STRIP_BIN=""
|
||||||
|
if [[ "$short" == "x64" ]]; then
|
||||||
|
STRIP_BIN="/usr/bin/x86_64-linux-gnu-strip"
|
||||||
|
else
|
||||||
|
STRIP_BIN="/usr/bin/aarch64-linux-gnu-strip"
|
||||||
|
fi
|
||||||
|
if [[ -x "$STRIP_BIN" ]]; then
|
||||||
|
STRIP_ARGS=( --define "__strip $STRIP_BIN" )
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build RPM for this arch (force rpm --target to match compile arch)
|
||||||
|
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
|
||||||
|
rpmbuild -ba "$SPECFILE" --define "_topdir $TOPDIR" --target "$rpm_target" "${STRIP_ARGS[@]}"
|
||||||
|
else
|
||||||
|
rpmbuild -ba "$SPECFILE" --target "$rpm_target" "${STRIP_ARGS[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy temporary rpmbuild to ~/rpmbuild on Debian/Ubuntu path
|
||||||
|
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
|
||||||
|
mkdir -p "$HOME/rpmbuild"
|
||||||
|
rsync -a "$TOPDIR"/ "$HOME/rpmbuild"/
|
||||||
|
TOPDIR="$HOME/rpmbuild"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Build done for $short. RPM at:"
|
||||||
|
local f
|
||||||
|
for f in "${TOPDIR}/RPMS/${archdir}/v2rayN-${VERSION}-1"*.rpm; do
|
||||||
|
[[ -e "$f" ]] || continue
|
||||||
|
echo " $f"
|
||||||
|
BUILT_RPMS+=("$f")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== Arch selection and build orchestration =========================================
|
||||||
|
case "${ARCH_OVERRIDE:-}" in
|
||||||
|
"")
|
||||||
|
# No --arch: use host architecture
|
||||||
|
if [[ "$host_arch" == "aarch64" ]]; then
|
||||||
|
build_for_arch arm64
|
||||||
|
else
|
||||||
|
build_for_arch x64
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
x64|amd64)
|
||||||
|
build_for_arch x64
|
||||||
|
;;
|
||||||
|
arm64|aarch64)
|
||||||
|
build_for_arch arm64
|
||||||
|
;;
|
||||||
|
all)
|
||||||
|
BUILT_ALL=1
|
||||||
|
# Build x64 and arm64 separately; each package contains its own arch-only binaries.
|
||||||
|
build_for_arch x64
|
||||||
|
build_for_arch arm64
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ===== Final summary if building both arches ==========================================
|
||||||
|
if [[ "$BUILT_ALL" -eq 1 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "================ Build Summary (both architectures) ================"
|
||||||
|
if [[ "${#BUILT_RPMS[@]}" -gt 0 ]]; then
|
||||||
|
for rp in "${BUILT_RPMS[@]}"; do
|
||||||
|
echo "$rp"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "[WARN] No RPMs detected in summary (check build logs above)."
|
||||||
|
fi
|
||||||
|
echo "==================================================================="
|
||||||
|
fi
|
||||||
363
v2rayN/.gitignore
vendored
363
v2rayN/.gitignore
vendored
|
|
@ -1,363 +0,0 @@
|
||||||
## 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
|
|
||||||
20
v2rayN/AmazTool/AmazTool.csproj
Normal file
20
v2rayN/AmazTool/AmazTool.csproj
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Resx\Resource.resx">
|
||||||
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
|
||||||
|
</EmbeddedResource>
|
||||||
|
|
||||||
|
<Compile Update="Resx\Resource.Designer.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Resource.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
87
v2rayN/AmazTool/Program.cs
Normal file
87
v2rayN/AmazTool/Program.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
namespace AmazTool;
|
||||||
|
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
private static void Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// If no arguments are provided, display usage guidelines and exit
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
ShowHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log all arguments for debugging purposes
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
Console.WriteLine(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command based on first argument
|
||||||
|
switch (args[0].ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "rebootas":
|
||||||
|
// Handle application restart
|
||||||
|
HandleRebootAsync();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "help":
|
||||||
|
case "--help":
|
||||||
|
case "-h":
|
||||||
|
case "/?":
|
||||||
|
// Display help information
|
||||||
|
ShowHelp();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Default behavior: handle as upgrade data
|
||||||
|
// Maintain backward compatibility with existing usage pattern
|
||||||
|
var argData = Uri.UnescapeDataString(string.Join(" ", args));
|
||||||
|
HandleUpgrade(argData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Global exception handling
|
||||||
|
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||||
|
Console.WriteLine("Press any key to exit...");
|
||||||
|
Console.ReadKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display help information and usage guidelines
|
||||||
|
/// </summary>
|
||||||
|
private static void ShowHelp()
|
||||||
|
{
|
||||||
|
Console.WriteLine(Resx.Resource.Guidelines);
|
||||||
|
Console.WriteLine("Available commands:");
|
||||||
|
Console.WriteLine(" rebootas - Restart the application");
|
||||||
|
Console.WriteLine(" help - Display this help information");
|
||||||
|
Thread.Sleep(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle application restart
|
||||||
|
/// </summary>
|
||||||
|
private static void HandleRebootAsync()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Restarting application...");
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
Utils.StartV2RayN();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle application upgrade with the provided data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="upgradeData">Data for the upgrade process</param>
|
||||||
|
private static void HandleUpgrade(string upgradeData)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Upgrading application...");
|
||||||
|
UpgradeApp.Upgrade(upgradeData);
|
||||||
|
}
|
||||||
|
}
|
||||||
171
v2rayN/AmazTool/Resx/Resource.Designer.cs
generated
Normal file
171
v2rayN/AmazTool/Resx/Resource.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// 此代码由工具生成。
|
||||||
|
// 运行时版本:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// 对此文件的更改可能会导致不正确的行为,并且如果
|
||||||
|
// 重新生成代码,这些更改将会丢失。
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace AmazTool.Resx {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 一个强类型的资源类,用于查找本地化的字符串等。
|
||||||
|
/// </summary>
|
||||||
|
// 此类是由 StronglyTypedResourceBuilder
|
||||||
|
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||||
|
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||||
|
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
internal class Resource {
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resource() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 返回此类使用的缓存的 ResourceManager 实例。
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AmazTool.Resx.Resource", typeof(Resource).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重写当前线程的 CurrentUICulture 属性,对
|
||||||
|
/// 使用此强类型资源类的所有资源查找执行重写。
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Failed to terminate the v2rayN. Close it manually, or the upgrade may fail. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string FailedTerminateProcess {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FailedTerminateProcess", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Failed to extract the update package. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string FailedUnzipping {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FailedUnzipping", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Upgrade failed. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string FailedUpgrade {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FailedUpgrade", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Please run it from the main application. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string Guidelines {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Guidelines", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Information 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string Information {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Information", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 In progress, please wait... 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string InProgress {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InProgress", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Start v2rayN, please wait... 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string Restartv2rayN {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Restartv2rayN", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Start extracting the update package... 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string StartUnzipping {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("StartUnzipping", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Successfully extracted the update package. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string SuccessUnzipping {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SuccessUnzipping", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Upgrade success. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string SuccessUpgrade {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SuccessUpgrade", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Try to terminate the v2rayN process... 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string TryTerminateProcess {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TryTerminateProcess", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Upgrade failed, file not found. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
internal static string UpgradeFileNotFound {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("UpgradeFileNotFound", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
156
v2rayN/AmazTool/Resx/Resource.resx
Normal file
156
v2rayN/AmazTool/Resx/Resource.resx
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: 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">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<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">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<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" 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">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Restartv2rayN" xml:space="preserve">
|
||||||
|
<value>Start v2rayN, please wait...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Guidelines" xml:space="preserve">
|
||||||
|
<value>Please run it from the main application.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UpgradeFileNotFound" xml:space="preserve">
|
||||||
|
<value>Upgrade failed, file not found.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InProgress" xml:space="preserve">
|
||||||
|
<value>In progress, please wait...</value>
|
||||||
|
</data>
|
||||||
|
<data name="TryTerminateProcess" xml:space="preserve">
|
||||||
|
<value>Try to terminate the v2rayN process...</value>
|
||||||
|
</data>
|
||||||
|
<data name="FailedTerminateProcess" xml:space="preserve">
|
||||||
|
<value>Failed to terminate the v2rayN. Close it manually, or the upgrade may fail.</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartUnzipping" xml:space="preserve">
|
||||||
|
<value>Start extracting the update package...</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessUnzipping" xml:space="preserve">
|
||||||
|
<value>Successfully extracted the update package.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FailedUnzipping" xml:space="preserve">
|
||||||
|
<value>Failed to extract the update package.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FailedUpgrade" xml:space="preserve">
|
||||||
|
<value>Upgrade failed.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessUpgrade" xml:space="preserve">
|
||||||
|
<value>Upgrade success.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Information" xml:space="preserve">
|
||||||
|
<value>Information</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
@ -117,8 +117,40 @@
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.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>
|
</resheader>
|
||||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
<data name="Restartv2rayN" xml:space="preserve">
|
||||||
<data name="pac" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<value>正在重启,请等待...</value>
|
||||||
<value>Resources\pac.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312</value>
|
</data>
|
||||||
|
<data name="Guidelines" xml:space="preserve">
|
||||||
|
<value>请从主应用运行。</value>
|
||||||
|
</data>
|
||||||
|
<data name="UpgradeFileNotFound" xml:space="preserve">
|
||||||
|
<value>升级失败,文件不存在。</value>
|
||||||
|
</data>
|
||||||
|
<data name="InProgress" xml:space="preserve">
|
||||||
|
<value>正在进行中,请等待...</value>
|
||||||
|
</data>
|
||||||
|
<data name="TryTerminateProcess" xml:space="preserve">
|
||||||
|
<value>尝试结束 v2rayN 进程...</value>
|
||||||
|
</data>
|
||||||
|
<data name="FailedTerminateProcess" xml:space="preserve">
|
||||||
|
<value>请手动关闭正在运行的 v2rayN,否则可能升级失败。</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartUnzipping" xml:space="preserve">
|
||||||
|
<value>开始解压缩更新包...</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessUnzipping" xml:space="preserve">
|
||||||
|
<value>解压缩更新包成功。</value>
|
||||||
|
</data>
|
||||||
|
<data name="FailedUnzipping" xml:space="preserve">
|
||||||
|
<value>解压缩更新包失败。</value>
|
||||||
|
</data>
|
||||||
|
<data name="FailedUpgrade" xml:space="preserve">
|
||||||
|
<value>升级失败。</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessUpgrade" xml:space="preserve">
|
||||||
|
<value>升级成功。</value>
|
||||||
|
</data>
|
||||||
|
<data name="Information" xml:space="preserve">
|
||||||
|
<value>提示</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
156
v2rayN/AmazTool/Resx/Resource.zh-Hant.resx
Normal file
156
v2rayN/AmazTool/Resx/Resource.zh-Hant.resx
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: 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">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<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">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<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" 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">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Restartv2rayN" xml:space="preserve">
|
||||||
|
<value>正在重啟,請等待...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Guidelines" xml:space="preserve">
|
||||||
|
<value>請從主應用程式運行。</value>
|
||||||
|
</data>
|
||||||
|
<data name="UpgradeFileNotFound" xml:space="preserve">
|
||||||
|
<value>升級失敗,檔案不存在。</value>
|
||||||
|
</data>
|
||||||
|
<data name="InProgress" xml:space="preserve">
|
||||||
|
<value>正在進行中,請等待...</value>
|
||||||
|
</data>
|
||||||
|
<data name="TryTerminateProcess" xml:space="preserve">
|
||||||
|
<value>嘗試結束 v2rayN 進程...</value>
|
||||||
|
</data>
|
||||||
|
<data name="FailedTerminateProcess" xml:space="preserve">
|
||||||
|
<value>請手動關閉正在執行的 v2rayN,否則可能會升級失敗。</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartUnzipping" xml:space="preserve">
|
||||||
|
<value>開始解壓縮更新包...</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessUnzipping" xml:space="preserve">
|
||||||
|
<value>解壓縮更新包成功。</value>
|
||||||
|
</data>
|
||||||
|
<data name="FailedUnzipping" xml:space="preserve">
|
||||||
|
<value>解壓縮更新包失敗。</value>
|
||||||
|
</data>
|
||||||
|
<data name="FailedUpgrade" xml:space="preserve">
|
||||||
|
<value>升級失敗。</value>
|
||||||
|
</data>
|
||||||
|
<data name="SuccessUpgrade" xml:space="preserve">
|
||||||
|
<value>升級成功。</value>
|
||||||
|
</data>
|
||||||
|
<data name="Information" xml:space="preserve">
|
||||||
|
<value>提示</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
128
v2rayN/AmazTool/UpgradeApp.cs
Normal file
128
v2rayN/AmazTool/UpgradeApp.cs
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AmazTool;
|
||||||
|
|
||||||
|
internal class UpgradeApp
|
||||||
|
{
|
||||||
|
public static void Upgrade(string fileName)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{Resx.Resource.StartUnzipping}\n{fileName}");
|
||||||
|
|
||||||
|
Utils.Waiting(5);
|
||||||
|
|
||||||
|
if (!File.Exists(fileName))
|
||||||
|
{
|
||||||
|
Console.WriteLine(Resx.Resource.UpgradeFileNotFound);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(Resx.Resource.TryTerminateProcess);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var existing = Process.GetProcessesByName(Utils.V2rayN);
|
||||||
|
foreach (var pp in existing)
|
||||||
|
{
|
||||||
|
var path = pp.MainModule?.FileName ?? "";
|
||||||
|
if (path.StartsWith(Utils.GetPath(Utils.V2rayN)))
|
||||||
|
{
|
||||||
|
pp?.Kill();
|
||||||
|
pp?.WaitForExit(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Access may be denied without admin right. The user may not be an administrator.
|
||||||
|
Console.WriteLine(Resx.Resource.FailedTerminateProcess + ex.StackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(Resx.Resource.StartUnzipping);
|
||||||
|
StringBuilder sb = new();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var thisAppOldFile = $"{Utils.GetExePath()}.tmp";
|
||||||
|
File.Delete(thisAppOldFile);
|
||||||
|
var splitKey = "/";
|
||||||
|
|
||||||
|
using var archive = ZipFile.OpenRead(fileName);
|
||||||
|
foreach (var entry in archive.Entries)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (entry.Length == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(entry.FullName);
|
||||||
|
|
||||||
|
var lst = entry.FullName.Split(splitKey);
|
||||||
|
if (lst.Length == 1)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullName = string.Join(splitKey, lst[1..lst.Length]);
|
||||||
|
|
||||||
|
if (string.Equals(Utils.GetExePath(), Utils.GetPath(fullName), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
File.Move(Utils.GetExePath(), thisAppOldFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryOutputPath = Utils.GetPath(fullName);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
|
||||||
|
//In the bin folder, if the file already exists, it will be skipped
|
||||||
|
if (fullName.StartsWith("bin") && File.Exists(entryOutputPath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryExtractToFile(entry, entryOutputPath);
|
||||||
|
|
||||||
|
Console.WriteLine(entryOutputPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
sb.Append(ex.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(Resx.Resource.FailedUpgrade + ex.StackTrace);
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
if (sb.Length > 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine(Resx.Resource.FailedUpgrade + sb.ToString());
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(Resx.Resource.Restartv2rayN);
|
||||||
|
Utils.Waiting(2);
|
||||||
|
|
||||||
|
Utils.StartV2RayN();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryExtractToFile(ZipArchiveEntry entry, string outputPath)
|
||||||
|
{
|
||||||
|
var retryCount = 5;
|
||||||
|
var delayMs = 1000;
|
||||||
|
|
||||||
|
for (var i = 1; i <= retryCount; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
entry.ExtractToFile(outputPath, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Thread.Sleep(delayMs * i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
v2rayN/AmazTool/Utils.cs
Normal file
51
v2rayN/AmazTool/Utils.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace AmazTool;
|
||||||
|
|
||||||
|
internal class Utils
|
||||||
|
{
|
||||||
|
public static string GetExePath()
|
||||||
|
{
|
||||||
|
return Environment.ProcessPath ?? Process.GetCurrentProcess().MainModule?.FileName ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string StartupPath()
|
||||||
|
{
|
||||||
|
return AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetPath(string fileName)
|
||||||
|
{
|
||||||
|
var startupPath = StartupPath();
|
||||||
|
if (string.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
return startupPath;
|
||||||
|
}
|
||||||
|
return Path.Combine(startupPath, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string V2rayN => "v2rayN";
|
||||||
|
|
||||||
|
public static void StartV2RayN()
|
||||||
|
{
|
||||||
|
Process process = new()
|
||||||
|
{
|
||||||
|
StartInfo = new()
|
||||||
|
{
|
||||||
|
UseShellExecute = true,
|
||||||
|
FileName = V2rayN,
|
||||||
|
WorkingDirectory = StartupPath()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Waiting(int second)
|
||||||
|
{
|
||||||
|
for (var i = second; i > 0; i--)
|
||||||
|
{
|
||||||
|
Console.WriteLine(i);
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
v2rayN/Directory.Build.props
Normal file
33
v2rayN/Directory.Build.props
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<Project>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Version>7.15.6</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||||
|
<NoWarn>CA1031;CS1591;NU1507;CA1416;IDE0058</NoWarn>
|
||||||
|
<Nullable>annotations</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Authors>2dust</Authors>
|
||||||
|
<PackageLicenseExpression>GPL-3.0</PackageLicenseExpression>
|
||||||
|
<Copyright>Copyright © 2017-$([System.DateTime]::UtcNow.Year) $(Authors)</Copyright>
|
||||||
|
<InvariantGlobalization>false</InvariantGlobalization>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
<EventSourceSupport>false</EventSourceSupport>
|
||||||
|
<StackTraceSupport>false</StackTraceSupport>
|
||||||
|
<MetricsSupport>false</MetricsSupport>
|
||||||
|
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
|
||||||
|
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
|
||||||
|
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
|
||||||
|
|
||||||
|
<UseSystemResourceKeys>true</UseSystemResourceKeys>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<PublishReadyToRun>false</PublishReadyToRun>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
32
v2rayN/Directory.Packages.props
Normal file
32
v2rayN/Directory.Packages.props
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
|
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||||
|
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
||||||
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.8" />
|
||||||
|
<PackageVersion Include="Avalonia.Desktop" Version="11.3.8" />
|
||||||
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.8" />
|
||||||
|
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.8" />
|
||||||
|
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
||||||
|
<PackageVersion Include="Downloader" Version="4.0.3" />
|
||||||
|
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" />
|
||||||
|
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
|
||||||
|
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
|
||||||
|
<PackageVersion Include="QRCoder" Version="1.7.0" />
|
||||||
|
<PackageVersion Include="ReactiveUI" Version="20.4.1" />
|
||||||
|
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||||
|
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
|
||||||
|
<PackageVersion Include="Semi.Avalonia" Version="11.3.7" />
|
||||||
|
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
|
||||||
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7" />
|
||||||
|
<PackageVersion Include="NLog" Version="6.0.5" />
|
||||||
|
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
|
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||||
|
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||||
|
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
|
||||||
|
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
1
v2rayN/GlobalHotKeys
Submodule
1
v2rayN/GlobalHotKeys
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ffb2850df0991495d0918e13cc5701737f26175a
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<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>
|
|
||||||
95
v2rayN/PacLib/Resources.Designer.cs
generated
95
v2rayN/PacLib/Resources.Designer.cs
generated
|
|
@ -1,95 +0,0 @@
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// <auto-generated>
|
|
||||||
// 此代码由工具生成。
|
|
||||||
// 运行时版本:4.0.30319.42000
|
|
||||||
//
|
|
||||||
// 对此文件的更改可能会导致不正确的行为,并且如果
|
|
||||||
// 重新生成代码,这些更改将会丢失。
|
|
||||||
// </auto-generated>
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
namespace PacLib {
|
|
||||||
using System;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 一个强类型的资源类,用于查找本地化的字符串等。
|
|
||||||
/// </summary>
|
|
||||||
// 此类是由 StronglyTypedResourceBuilder
|
|
||||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
|
||||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
|
||||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
|
||||||
[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 {
|
|
||||||
|
|
||||||
private static global::System.Resources.ResourceManager resourceMan;
|
|
||||||
|
|
||||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
|
||||||
|
|
||||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
|
||||||
internal Resources() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 返回此类使用的缓存的 ResourceManager 实例。
|
|
||||||
/// </summary>
|
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
|
||||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
|
||||||
get {
|
|
||||||
if (object.ReferenceEquals(resourceMan, null)) {
|
|
||||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PacLib.Resources", typeof(Resources).Assembly);
|
|
||||||
resourceMan = temp;
|
|
||||||
}
|
|
||||||
return resourceMan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重写当前线程的 CurrentUICulture 属性,对
|
|
||||||
/// 使用此强类型资源类的所有资源查找执行重写。
|
|
||||||
/// </summary>
|
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
|
||||||
internal static global::System.Globalization.CultureInfo Culture {
|
|
||||||
get {
|
|
||||||
return resourceCulture;
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,20 +0,0 @@
|
||||||
<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.1" />
|
|
||||||
<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,53 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package v2ray.core.app.stats.command;
|
|
||||||
option csharp_namespace = "ProtosLib.Statistics";
|
|
||||||
|
|
||||||
message GetStatsRequest {
|
|
||||||
// Name of the stat counter.
|
|
||||||
string name = 1;
|
|
||||||
// Whether or not to reset the counter to fetching its value.
|
|
||||||
bool reset = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Stat {
|
|
||||||
string name = 1;
|
|
||||||
int64 value = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetStatsResponse {
|
|
||||||
Stat stat = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message QueryStatsRequest {
|
|
||||||
string pattern = 1;
|
|
||||||
bool reset = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message QueryStatsResponse {
|
|
||||||
repeated Stat stat = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SysStatsRequest {
|
|
||||||
}
|
|
||||||
|
|
||||||
message SysStatsResponse {
|
|
||||||
uint32 NumGoroutine = 1;
|
|
||||||
uint32 NumGC = 2;
|
|
||||||
uint64 Alloc = 3;
|
|
||||||
uint64 TotalAlloc = 4;
|
|
||||||
uint64 Sys = 5;
|
|
||||||
uint64 Mallocs = 6;
|
|
||||||
uint64 Frees = 7;
|
|
||||||
uint64 LiveObjects = 8;
|
|
||||||
uint64 PauseTotalNs = 9;
|
|
||||||
uint32 Uptime = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
service StatsService {
|
|
||||||
rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {}
|
|
||||||
rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {}
|
|
||||||
rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Config {}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
using ProtosLib.Statistics;
|
|
||||||
|
|
||||||
namespace ProtosLib
|
|
||||||
{
|
|
||||||
public class Tests
|
|
||||||
{
|
|
||||||
private StatsService.StatsServiceClient client_;
|
|
||||||
|
|
||||||
public Tests()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
using ReactiveUI;
|
namespace ServiceLib.Base;
|
||||||
|
|
||||||
namespace ServiceLib.Base
|
public class MyReactiveObject : ReactiveObject
|
||||||
{
|
{
|
||||||
public class MyReactiveObject : ReactiveObject
|
protected static Config? _config;
|
||||||
{
|
protected Func<EViewAction, object?, Task<bool>>? _updateView;
|
||||||
protected static Config? _config;
|
}
|
||||||
protected Func<EViewAction, object?, Task<bool>>? _updateView;
|
|
||||||
protected NoticeHandler? _noticeHandler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
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.IsNotEmpty(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);
|
|
||||||
|
|
||||||
downloadOpt = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
58
v2rayN/ServiceLib/Common/EmbedUtils.cs
Normal file
58
v2rayN/ServiceLib/Common/EmbedUtils.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
public static class EmbedUtils
|
||||||
|
{
|
||||||
|
private static readonly string _tag = "EmbedUtils";
|
||||||
|
private static readonly ConcurrentDictionary<string, string> _dicEmbedCache = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get embedded text resources
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="res"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetEmbedText(string res)
|
||||||
|
{
|
||||||
|
if (_dicEmbedCache.TryGetValue(res, out var value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
var result = string.Empty;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
|
using var stream = assembly.GetManifestResourceStream(res);
|
||||||
|
ArgumentNullException.ThrowIfNull(stream);
|
||||||
|
using StreamReader reader = new(stream);
|
||||||
|
result = reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
_dicEmbedCache.TryAdd(res, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get local storage resources
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string? LoadResource(string? res)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(res))
|
||||||
|
{
|
||||||
|
return File.ReadAllText(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
v2rayN/ServiceLib/Common/Extension.cs
Normal file
97
v2rayN/ServiceLib/Common/Extension.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
public static class Extension
|
||||||
|
{
|
||||||
|
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
|
||||||
|
{
|
||||||
|
if (s.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return chars.Contains(s.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsWhiteSpace(this string value)
|
||||||
|
{
|
||||||
|
return value.All(char.IsWhiteSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> NonWhiteSpaceLines(this TextReader reader)
|
||||||
|
{
|
||||||
|
while (reader.ReadLine() is { } line)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return value.StartsWith(prefix) ? value[1..] : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string RemovePrefix(this string value, string prefix)
|
||||||
|
{
|
||||||
|
return value.StartsWith(prefix) ? value[prefix.Length..] : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string UpperFirstChar(this string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return char.ToUpper(value.First()) + value[1..];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AppendQuotes(this string value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(value) ? string.Empty : $"\"{value}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ToInt(this string? value, int defaultValue = 0)
|
||||||
|
{
|
||||||
|
return int.TryParse(value, out var result) ? result : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> AppendEmpty(this IEnumerable<string> source)
|
||||||
|
{
|
||||||
|
return source.Concat(new[] { string.Empty }).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsGroupType(this EConfigType configType)
|
||||||
|
{
|
||||||
|
return configType is EConfigType.PolicyGroup or EConfigType.ProxyChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsComplexType(this EConfigType configType)
|
||||||
|
{
|
||||||
|
return configType is EConfigType.Custom or EConfigType.PolicyGroup or EConfigType.ProxyChain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,183 +1,249 @@
|
||||||
using System.IO.Compression;
|
using System.Formats.Tar;
|
||||||
using System.Text;
|
using System.IO.Compression;
|
||||||
|
|
||||||
namespace ServiceLib.Common
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
public static class FileManager
|
||||||
{
|
{
|
||||||
public static class FileManager
|
private static readonly string _tag = "FileManager";
|
||||||
|
|
||||||
|
public static bool ByteArrayToFile(string fileName, byte[] content)
|
||||||
{
|
{
|
||||||
public static bool ByteArrayToFile(string fileName, byte[] content)
|
try
|
||||||
{
|
{
|
||||||
try
|
File.WriteAllBytes(fileName, content);
|
||||||
{
|
return true;
|
||||||
File.WriteAllBytes(fileName, content);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(ex.Message, ex);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public static void UncompressedFile(string fileName, byte[] content)
|
|
||||||
{
|
{
|
||||||
try
|
Logging.SaveLog(_tag, ex);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static void UncompressedFile(string fileName, string toPath, string? toName)
|
public static void DecompressFile(string fileName, byte[] content)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
using var fs = File.Create(fileName);
|
||||||
{
|
using GZipStream input = new(new MemoryStream(content), CompressionMode.Decompress, false);
|
||||||
FileInfo fileInfo = new(fileName);
|
input.CopyTo(fs);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public static string NonExclusiveReadAllText(string path)
|
|
||||||
{
|
{
|
||||||
return NonExclusiveReadAllText(path, Encoding.Default);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string NonExclusiveReadAllText(string path, Encoding encoding)
|
public static void DecompressFile(string fileName, string toPath, string? toName)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
FileInfo fileInfo = new(fileName);
|
||||||
{
|
using var originalFileStream = fileInfo.OpenRead();
|
||||||
using FileStream fs = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
using var decompressedFileStream = File.Create(toName != null ? Path.Combine(toPath, toName) : toPath);
|
||||||
using StreamReader sr = new(fs, encoding);
|
using GZipStream decompressionStream = new(originalFileStream, CompressionMode.Decompress);
|
||||||
return sr.ReadToEnd();
|
decompressionStream.CopyTo(decompressedFileStream);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(ex.Message, ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public static bool ZipExtractToFile(string fileName, string toPath, string ignoredName)
|
|
||||||
{
|
{
|
||||||
try
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DecompressTarFile(string fileName, string toPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
|
||||||
|
using var gz = new GZipStream(fs, CompressionMode.Decompress, leaveOpen: true);
|
||||||
|
TarFile.ExtractToDirectory(gz, toPath, overwriteFiles: true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string NonExclusiveReadAllText(string path)
|
||||||
|
{
|
||||||
|
return NonExclusiveReadAllText(path, Encoding.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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(_tag, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ZipExtractToFile(string fileName, string toPath, string ignoredName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var archive = ZipFile.OpenRead(fileName);
|
||||||
|
foreach (var entry in archive.Entries)
|
||||||
{
|
{
|
||||||
using ZipArchive archive = ZipFile.OpenRead(fileName);
|
if (entry.Length == 0)
|
||||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
|
||||||
{
|
{
|
||||||
if (entry.Length == 0)
|
continue;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ignoredName.IsNotEmpty() && entry.Name.Contains(ignoredName))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try
|
entry.ExtractToFile(Path.Combine(toPath, entry.Name), true);
|
||||||
{
|
}
|
||||||
if (Utils.IsNotEmpty(ignoredName) && entry.Name.Contains(ignoredName))
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
continue;
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
|
||||||
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 List<string>? GetFilesFromZip(string fileName)
|
|
||||||
{
|
|
||||||
if (!File.Exists(fileName))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using ZipArchive archive = ZipFile.OpenRead(fileName);
|
|
||||||
return archive.Entries.Select(entry => entry.FullName).ToList();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(ex.Message, ex);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public static bool CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName)
|
|
||||||
{
|
{
|
||||||
try
|
Logging.SaveLog(_tag, ex);
|
||||||
{
|
return false;
|
||||||
if (File.Exists(destinationArchiveFileName))
|
}
|
||||||
{
|
return true;
|
||||||
File.Delete(destinationArchiveFileName);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, CompressionLevel.SmallestSize, true);
|
public static List<string>? GetFilesFromZip(string fileName)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
if (!File.Exists(fileName))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var archive = ZipFile.OpenRead(fileName);
|
||||||
|
return archive.Entries.Select(entry => entry.FullName).ToList();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(destinationArchiveFileName))
|
||||||
{
|
{
|
||||||
Logging.SaveLog(ex.Message, ex);
|
File.Delete(destinationArchiveFileName);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, CompressionLevel.SmallestSize, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive, bool overwrite, string? ignoredName = null)
|
||||||
|
{
|
||||||
|
// Get information about the source directory
|
||||||
|
var dir = new DirectoryInfo(sourceDir);
|
||||||
|
|
||||||
|
// Check if the source directory exists
|
||||||
|
if (!dir.Exists)
|
||||||
|
{
|
||||||
|
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive, string ignoredName)
|
// Cache directories before we start copying
|
||||||
|
var dirs = dir.GetDirectories();
|
||||||
|
|
||||||
|
// Create the destination directory
|
||||||
|
_ = Directory.CreateDirectory(destinationDir);
|
||||||
|
|
||||||
|
// Get the files in the source directory and copy to the destination directory
|
||||||
|
foreach (var file in dir.GetFiles())
|
||||||
{
|
{
|
||||||
// Get information about the source directory
|
if (ignoredName.IsNotEmpty() && file.Name.Contains(ignoredName))
|
||||||
var dir = new DirectoryInfo(sourceDir);
|
|
||||||
|
|
||||||
// Check if the source directory exists
|
|
||||||
if (!dir.Exists)
|
|
||||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
|
||||||
|
|
||||||
// Cache directories before we start copying
|
|
||||||
DirectoryInfo[] dirs = dir.GetDirectories();
|
|
||||||
|
|
||||||
// Create the destination directory
|
|
||||||
Directory.CreateDirectory(destinationDir);
|
|
||||||
|
|
||||||
// Get the files in the source directory and copy to the destination directory
|
|
||||||
foreach (FileInfo file in dir.GetFiles())
|
|
||||||
{
|
{
|
||||||
if (Utils.IsNotEmpty(ignoredName) && file.Name.Contains(ignoredName))
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (file.Extension == file.Name)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
|
||||||
file.CopyTo(targetFilePath);
|
|
||||||
}
|
}
|
||||||
|
if (file.Extension == file.Name)
|
||||||
// If recursive and copying subdirectories, recursively call this method
|
|
||||||
if (recursive)
|
|
||||||
{
|
{
|
||||||
foreach (DirectoryInfo subDir in dirs)
|
continue;
|
||||||
{
|
}
|
||||||
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
var targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||||
CopyDirectory(subDir.FullName, newDestinationDir, true, ignoredName);
|
if (!overwrite && File.Exists(targetFilePath))
|
||||||
}
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ = file.CopyTo(targetFilePath, overwrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If recursive and copying subdirectories, recursively call this method
|
||||||
|
if (recursive)
|
||||||
|
{
|
||||||
|
foreach (var subDir in dirs)
|
||||||
|
{
|
||||||
|
var newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
||||||
|
CopyDirectory(subDir.FullName, newDestinationDir, true, overwrite, ignoredName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static void DeleteExpiredFiles(string sourceDir, DateTime dtLine)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var files = Directory.GetFiles(sourceDir, "*.*");
|
||||||
|
foreach (var filePath in files)
|
||||||
|
{
|
||||||
|
var file = new FileInfo(filePath);
|
||||||
|
if (file.CreationTime >= dtLine)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
file.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a Linux shell file with the specified contents.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <param name="contents"></param>
|
||||||
|
/// <param name="overwrite"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task<string> CreateLinuxShellFile(string fileName, string contents, bool overwrite)
|
||||||
|
{
|
||||||
|
var shFilePath = Utils.GetBinConfigPath(fileName);
|
||||||
|
|
||||||
|
// Check if the file already exists and if we should overwrite it
|
||||||
|
if (!overwrite && File.Exists(shFilePath))
|
||||||
|
{
|
||||||
|
return shFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Delete(shFilePath);
|
||||||
|
await File.WriteAllTextAsync(shFilePath, contents);
|
||||||
|
await Utils.SetLinuxChmod(shFilePath);
|
||||||
|
|
||||||
|
return shFilePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
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 (Utils.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,133 +1,146 @@
|
||||||
using System.Text.Json;
|
namespace ServiceLib.Common;
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace ServiceLib.Common
|
public class JsonUtils
|
||||||
{
|
{
|
||||||
public class JsonUtils
|
private static readonly string _tag = "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>
|
private static readonly JsonSerializerOptions _defaultDeserializeOptions = new()
|
||||||
/// Deserialize to object
|
{
|
||||||
/// </summary>
|
PropertyNameCaseInsensitive = true,
|
||||||
/// <typeparam name="T"></typeparam>
|
ReadCommentHandling = JsonCommentHandling.Skip
|
||||||
/// <param name="strJson"></param>
|
};
|
||||||
/// <returns></returns>
|
|
||||||
public static T? Deserialize<T>(string? strJson)
|
private static readonly JsonSerializerOptions _defaultSerializeOptions = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions _nullValueSerializeOptions = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly JsonDocumentOptions _defaultDocumentOptions = new()
|
||||||
|
{
|
||||||
|
CommentHandling = JsonCommentHandling.Skip
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <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
|
||||||
{
|
{
|
||||||
try
|
if (string.IsNullOrWhiteSpace(strJson))
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(strJson))
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
return JsonSerializer.Deserialize<T>(strJson);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
return JsonSerializer.Deserialize<T>(strJson, _defaultDeserializeOptions);
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
/// <summary>
|
|
||||||
/// parse
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="strJson"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static JsonNode? ParseJson(string strJson)
|
|
||||||
{
|
{
|
||||||
try
|
return default;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <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, nodeOptions: null, _defaultDocumentOptions);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//SaveLog(ex.Message, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize Object to Json string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="indented"></param>
|
||||||
|
/// <param name="nullValue"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string Serialize(object? obj, bool indented = true, bool nullValue = false)
|
||||||
|
{
|
||||||
|
var result = string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions;
|
||||||
|
result = JsonSerializer.Serialize(obj, options);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize Object to Json string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string Serialize(object? obj, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var result = string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = JsonSerializer.Serialize(obj, options);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SerializeToNode
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static JsonNode? SerializeToNode(object? obj, JsonSerializerOptions? options = null)
|
||||||
|
{
|
||||||
|
return JsonSerializer.SerializeToNode(obj, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,55 @@
|
||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Config;
|
using NLog.Config;
|
||||||
using NLog.Targets;
|
using NLog.Targets;
|
||||||
|
|
||||||
namespace ServiceLib.Common
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
public class Logging
|
||||||
{
|
{
|
||||||
public class Logging
|
private static readonly Logger _logger1 = LogManager.GetLogger("Log1");
|
||||||
|
private static readonly Logger _logger2 = LogManager.GetLogger("Log2");
|
||||||
|
|
||||||
|
public static void Setup()
|
||||||
{
|
{
|
||||||
public static void Setup()
|
LoggingConfiguration config = new();
|
||||||
{
|
FileTarget fileTarget = new();
|
||||||
LoggingConfiguration config = new();
|
config.AddTarget("file", fileTarget);
|
||||||
FileTarget fileTarget = new();
|
fileTarget.Layout = "${longdate}-${level:uppercase=true} ${message}";
|
||||||
config.AddTarget("file", fileTarget);
|
fileTarget.FileName = Utils.GetLogPath("${shortdate}.txt");
|
||||||
fileTarget.Layout = "${longdate}-${level:uppercase=true} ${message}";
|
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
|
||||||
fileTarget.FileName = Utils.GetLogPath("${shortdate}.txt");
|
LogManager.Configuration = config;
|
||||||
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
|
}
|
||||||
LogManager.Configuration = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LoggingEnabled(bool enable)
|
public static void LoggingEnabled(bool enable)
|
||||||
|
{
|
||||||
|
if (!enable)
|
||||||
{
|
{
|
||||||
if (!enable)
|
LogManager.SuspendLogging();
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static void SaveLog(string strContent)
|
||||||
|
{
|
||||||
|
if (!LogManager.IsLoggingEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger1.Info(strContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveLog(string strTitle, Exception ex)
|
||||||
|
{
|
||||||
|
if (!LogManager.IsLoggingEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger2.Debug($"{strTitle},{ex.Message}");
|
||||||
|
_logger2.Debug(ex.StackTrace);
|
||||||
|
if (ex?.InnerException != null)
|
||||||
|
{
|
||||||
|
_logger2.Error(ex.InnerException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
68
v2rayN/ServiceLib/Common/ProcUtils.cs
Normal file
68
v2rayN/ServiceLib/Common/ProcUtils.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
public static class ProcUtils
|
||||||
|
{
|
||||||
|
private static readonly string _tag = "ProcUtils";
|
||||||
|
|
||||||
|
public static void ProcessStart(string? fileName, string arguments = "")
|
||||||
|
{
|
||||||
|
_ = ProcessStart(fileName, arguments, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int? ProcessStart(string? fileName, string arguments, string? dir)
|
||||||
|
{
|
||||||
|
if (fileName.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (fileName.Contains(' '))
|
||||||
|
{
|
||||||
|
fileName = fileName.AppendQuotes();
|
||||||
|
}
|
||||||
|
if (arguments.Contains(' '))
|
||||||
|
{
|
||||||
|
arguments = arguments.AppendQuotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
Process proc = new()
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
UseShellExecute = true,
|
||||||
|
FileName = fileName,
|
||||||
|
Arguments = arguments,
|
||||||
|
WorkingDirectory = dir ?? string.Empty
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_ = proc.Start();
|
||||||
|
return dir is null ? null : proc.Id;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RebootAsAdmin(bool blAdmin = true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProcessStartInfo startInfo = new()
|
||||||
|
{
|
||||||
|
UseShellExecute = true,
|
||||||
|
Arguments = Global.RebootAs,
|
||||||
|
WorkingDirectory = Utils.StartupPath(),
|
||||||
|
FileName = Utils.GetExePath().AppendQuotes(),
|
||||||
|
Verb = blAdmin ? "runas" : null,
|
||||||
|
};
|
||||||
|
_ = Process.Start(startInfo);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
125
v2rayN/ServiceLib/Common/QRCodeUtils.cs
Normal file
125
v2rayN/ServiceLib/Common/QRCodeUtils.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
using QRCoder;
|
||||||
|
using QRCoder.Exceptions;
|
||||||
|
using SkiaSharp;
|
||||||
|
using ZXing.SkiaSharp;
|
||||||
|
|
||||||
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
public class QRCodeUtils
|
||||||
|
{
|
||||||
|
public static byte[]? GenQRCode(string? url)
|
||||||
|
{
|
||||||
|
if (url.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
using QRCodeGenerator qrGenerator = new();
|
||||||
|
DataTooLongException? lastDtle = null;
|
||||||
|
|
||||||
|
var levels = new[]
|
||||||
|
{
|
||||||
|
QRCodeGenerator.ECCLevel.H,
|
||||||
|
QRCodeGenerator.ECCLevel.Q,
|
||||||
|
QRCodeGenerator.ECCLevel.M,
|
||||||
|
QRCodeGenerator.ECCLevel.L
|
||||||
|
};
|
||||||
|
foreach (var level in levels)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var qrCodeData = qrGenerator.CreateQrCode(url, level);
|
||||||
|
using PngByteQRCode qrCode = new(qrCodeData);
|
||||||
|
return qrCode.GetGraphic(20);
|
||||||
|
}
|
||||||
|
catch (DataTooLongException ex)
|
||||||
|
{
|
||||||
|
lastDtle = ex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDtle != null)
|
||||||
|
{
|
||||||
|
throw lastDtle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? ParseBarcode(string? fileName)
|
||||||
|
{
|
||||||
|
if (fileName == null || !File.Exists(fileName))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var image = SKImage.FromEncodedData(fileName);
|
||||||
|
var bitmap = SKBitmap.FromImage(image);
|
||||||
|
|
||||||
|
return ReaderBarcode(bitmap);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? ParseBarcode(byte[]? bytes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bitmap = SKBitmap.Decode(bytes);
|
||||||
|
//using var stream = new FileStream("test2.png", FileMode.Create, FileAccess.Write);
|
||||||
|
//using var image = SKImage.FromBitmap(bitmap);
|
||||||
|
//using var encodedImage = image.Encode();
|
||||||
|
//encodedImage.SaveTo(stream);
|
||||||
|
return ReaderBarcode(bitmap);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ReaderBarcode(SKBitmap? bitmap)
|
||||||
|
{
|
||||||
|
var reader = new BarcodeReader();
|
||||||
|
var result = reader.Decode(bitmap);
|
||||||
|
|
||||||
|
if (result != null && result.Text.IsNotEmpty())
|
||||||
|
{
|
||||||
|
return result.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
//FlipBitmap
|
||||||
|
var result2 = reader.Decode(FlipBitmap(bitmap));
|
||||||
|
return result2?.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SKBitmap FlipBitmap(SKBitmap bmp)
|
||||||
|
{
|
||||||
|
// Create a bitmap (to return)
|
||||||
|
var flipped = new SKBitmap(bmp.Width, bmp.Height, bmp.Info.ColorType, bmp.Info.AlphaType);
|
||||||
|
|
||||||
|
// Create a canvas to draw into the bitmap
|
||||||
|
using var canvas = new SKCanvas(flipped);
|
||||||
|
|
||||||
|
// Set a transform matrix which moves the bitmap to the right,
|
||||||
|
// and then "scales" it by -1, which just flips the pixels
|
||||||
|
// horizontally
|
||||||
|
canvas.Translate(bmp.Width, 0);
|
||||||
|
canvas.Scale(-1, 1);
|
||||||
|
canvas.DrawBitmap(bmp, 0, 0);
|
||||||
|
return flipped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace ServiceLib.Common
|
|
||||||
{
|
|
||||||
public static class QueryableExtension
|
|
||||||
{
|
|
||||||
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName)
|
|
||||||
{
|
|
||||||
return _OrderBy<T>(query, propertyName, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName)
|
|
||||||
{
|
|
||||||
return _OrderBy<T>(query, propertyName, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IOrderedQueryable<T> _OrderBy<T>(IQueryable<T> query, string propertyName, bool isDesc)
|
|
||||||
{
|
|
||||||
string methodname = (isDesc) ? "OrderByDescendingInternal" : "OrderByInternal";
|
|
||||||
|
|
||||||
var memberProp = typeof(T).GetProperty(propertyName);
|
|
||||||
|
|
||||||
var method = typeof(QueryableExtension).GetMethod(methodname)
|
|
||||||
.MakeGenericMethod(typeof(T), memberProp.PropertyType);
|
|
||||||
|
|
||||||
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(_GetLambda<T, TProp>(memberProperty));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IOrderedQueryable<T> OrderByDescendingInternal<T, TProp>(IQueryable<T> query, PropertyInfo memberProperty)
|
|
||||||
{//public
|
|
||||||
return query.OrderByDescending(_GetLambda<T, TProp>(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 lambda = Expression.Lambda<Func<T, TProp>>(Expression.Property(thisArg, memberProperty), thisArg);
|
|
||||||
|
|
||||||
return lambda;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,180 +1,186 @@
|
||||||
namespace ServiceLib.Common
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
public class SemanticVersion
|
||||||
{
|
{
|
||||||
public class SemanticVersion
|
private readonly int major;
|
||||||
|
private readonly int minor;
|
||||||
|
private readonly int patch;
|
||||||
|
private readonly string version;
|
||||||
|
|
||||||
|
public SemanticVersion(int major, int minor, int patch)
|
||||||
{
|
{
|
||||||
private int major;
|
this.major = major;
|
||||||
private int minor;
|
this.minor = minor;
|
||||||
private int patch;
|
this.patch = patch;
|
||||||
private string version;
|
version = $"{major}.{minor}.{patch}";
|
||||||
|
|
||||||
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 || parts.Length == 4)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public SemanticVersion(string? version)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(version))
|
||||||
|
{
|
||||||
|
major = 0;
|
||||||
|
minor = 0;
|
||||||
|
patch = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.version = version.RemovePrefix('v');
|
||||||
|
|
||||||
|
var parts = this.version.Split('.');
|
||||||
|
if (parts.Length == 2)
|
||||||
|
{
|
||||||
|
major = int.Parse(parts.First());
|
||||||
|
minor = int.Parse(parts.Last());
|
||||||
|
patch = 0;
|
||||||
|
}
|
||||||
|
else if (parts.Length is 3 or 4)
|
||||||
|
{
|
||||||
|
major = int.Parse(parts[0]);
|
||||||
|
minor = int.Parse(parts[1]);
|
||||||
|
patch = int.Parse(parts[2]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid version string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
major = 0;
|
||||||
|
minor = 0;
|
||||||
|
patch = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is SemanticVersion other)
|
||||||
|
{
|
||||||
|
return major == other.major && minor == other.minor && patch == other.patch;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return major.GetHashCode() ^ minor.GetHashCode() ^ patch.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use ToVersionString(string? prefix) instead if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>major.minor.patch</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToVersionString(string? prefix = null)
|
||||||
|
{
|
||||||
|
if (prefix == null)
|
||||||
|
{
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{prefix}{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 (major < other.major)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (major > other.major)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (minor < other.minor)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (minor > other.minor)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (patch < other.patch)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (patch > other.patch)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool LessEquals(SemanticVersion other)
|
||||||
|
{
|
||||||
|
if (major < other.major)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (major > other.major)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (minor < other.minor)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (minor > other.minor)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (patch < other.patch)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (patch > other.patch)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Private
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
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>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
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 IsNotEmpty([NotNullWhen(false)] this string? value)
|
|
||||||
{
|
|
||||||
return !string.IsNullOrEmpty(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}\"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,41 +1,42 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace ServiceLib.Common
|
namespace ServiceLib.Common;
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
* See:
|
* See:
|
||||||
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
|
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class Job : IDisposable
|
public sealed class WindowsJob : IDisposable
|
||||||
{
|
{
|
||||||
private IntPtr handle = IntPtr.Zero;
|
private IntPtr handle = IntPtr.Zero;
|
||||||
|
|
||||||
public Job()
|
public WindowsJob()
|
||||||
{
|
{
|
||||||
handle = CreateJobObject(IntPtr.Zero, null);
|
handle = CreateJobObject(IntPtr.Zero, null);
|
||||||
IntPtr extendedInfoPtr = IntPtr.Zero;
|
var extendedInfoPtr = IntPtr.Zero;
|
||||||
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new()
|
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||||
{
|
{
|
||||||
LimitFlags = 0x2000
|
LimitFlags = 0x2000
|
||||||
};
|
};
|
||||||
|
|
||||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new()
|
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
||||||
{
|
{
|
||||||
BasicLimitInformation = info
|
BasicLimitInformation = info
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
||||||
extendedInfoPtr = Marshal.AllocHGlobal(length);
|
extendedInfoPtr = Marshal.AllocHGlobal(length);
|
||||||
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
|
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
|
||||||
|
|
||||||
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
|
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
|
||||||
(uint)length))
|
(uint)length))
|
||||||
|
{
|
||||||
throw new Exception(string.Format("Unable to set information. Error: {0}",
|
throw new Exception(string.Format("Unable to set information. Error: {0}",
|
||||||
Marshal.GetLastWin32Error()));
|
Marshal.GetLastWin32Error()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -48,7 +49,7 @@ namespace ServiceLib.Common
|
||||||
|
|
||||||
public bool AddProcess(IntPtr processHandle)
|
public bool AddProcess(IntPtr processHandle)
|
||||||
{
|
{
|
||||||
bool succ = AssignProcessToJobObject(handle, processHandle);
|
var succ = AssignProcessToJobObject(handle, processHandle);
|
||||||
|
|
||||||
if (!succ)
|
if (!succ)
|
||||||
{
|
{
|
||||||
|
|
@ -73,9 +74,12 @@ namespace ServiceLib.Common
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
private void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposed) return;
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
disposed = true;
|
disposed = true;
|
||||||
|
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
|
@ -90,7 +94,7 @@ namespace ServiceLib.Common
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~Job()
|
~WindowsJob()
|
||||||
{
|
{
|
||||||
Dispose(false);
|
Dispose(false);
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +107,7 @@ namespace ServiceLib.Common
|
||||||
private static extern IntPtr CreateJobObject(IntPtr a, string? lpName);
|
private static extern IntPtr CreateJobObject(IntPtr a, string? lpName);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);
|
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
||||||
|
|
@ -120,34 +124,34 @@ namespace ServiceLib.Common
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
internal struct IO_COUNTERS
|
internal struct IO_COUNTERS
|
||||||
{
|
{
|
||||||
public UInt64 ReadOperationCount;
|
public ulong ReadOperationCount;
|
||||||
public UInt64 WriteOperationCount;
|
public ulong WriteOperationCount;
|
||||||
public UInt64 OtherOperationCount;
|
public ulong OtherOperationCount;
|
||||||
public UInt64 ReadTransferCount;
|
public ulong ReadTransferCount;
|
||||||
public UInt64 WriteTransferCount;
|
public ulong WriteTransferCount;
|
||||||
public UInt64 OtherTransferCount;
|
public ulong OtherTransferCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||||
{
|
{
|
||||||
public Int64 PerProcessUserTimeLimit;
|
public long PerProcessUserTimeLimit;
|
||||||
public Int64 PerJobUserTimeLimit;
|
public long PerJobUserTimeLimit;
|
||||||
public UInt32 LimitFlags;
|
public uint LimitFlags;
|
||||||
public UIntPtr MinimumWorkingSetSize;
|
public UIntPtr MinimumWorkingSetSize;
|
||||||
public UIntPtr MaximumWorkingSetSize;
|
public UIntPtr MaximumWorkingSetSize;
|
||||||
public UInt32 ActiveProcessLimit;
|
public uint ActiveProcessLimit;
|
||||||
public UIntPtr Affinity;
|
public UIntPtr Affinity;
|
||||||
public UInt32 PriorityClass;
|
public uint PriorityClass;
|
||||||
public UInt32 SchedulingClass;
|
public uint SchedulingClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct SECURITY_ATTRIBUTES
|
public struct SECURITY_ATTRIBUTES
|
||||||
{
|
{
|
||||||
public UInt32 nLength;
|
public uint nLength;
|
||||||
public IntPtr lpSecurityDescriptor;
|
public IntPtr lpSecurityDescriptor;
|
||||||
public Int32 bInheritHandle;
|
public int bInheritHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
|
@ -173,4 +177,4 @@ namespace ServiceLib.Common
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Helper classes
|
#endregion Helper classes
|
||||||
}
|
|
||||||
71
v2rayN/ServiceLib/Common/WindowsUtils.cs
Normal file
71
v2rayN/ServiceLib/Common/WindowsUtils.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
internal static class WindowsUtils
|
||||||
|
{
|
||||||
|
private static readonly string _tag = "WindowsUtils";
|
||||||
|
|
||||||
|
public static string? RegReadValue(string path, string name, string def)
|
||||||
|
{
|
||||||
|
RegistryKey? regKey = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
regKey = Registry.CurrentUser.OpenSubKey(path, false);
|
||||||
|
var value = regKey?.GetValue(name) as string;
|
||||||
|
return value.IsNullOrEmpty() ? def : value;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
regKey?.Close();
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RegWriteValue(string path, string name, object value)
|
||||||
|
{
|
||||||
|
RegistryKey? regKey = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
regKey = Registry.CurrentUser.CreateSubKey(path);
|
||||||
|
if (value.ToString().IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
regKey?.DeleteValue(name, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
regKey?.SetValue(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
regKey?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task RemoveTunDevice()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sum = MD5.HashData(Encoding.UTF8.GetBytes("wintunsingbox_tun"));
|
||||||
|
var guid = new Guid(sum);
|
||||||
|
var pnpUtilPath = @"C:\Windows\System32\pnputil.exe";
|
||||||
|
var arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """;
|
||||||
|
|
||||||
|
// Try to remove the device
|
||||||
|
_ = await Utils.GetCliWrapOutput(pnpUtilPath, arg);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,81 +1,79 @@
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.NamingConventions;
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
namespace ServiceLib.Common
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
|
public class YamlUtils
|
||||||
{
|
{
|
||||||
public class YamlUtils
|
private static readonly string _tag = "YamlUtils";
|
||||||
|
|
||||||
|
#region YAML
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserialize
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T FromYaml<T>(string str)
|
||||||
{
|
{
|
||||||
#region YAML
|
var deserializer = new DeserializerBuilder()
|
||||||
|
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||||
/// <summary>
|
.Build();
|
||||||
/// 反序列化成对象
|
try
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="str"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static T FromYaml<T>(string str)
|
|
||||||
{
|
{
|
||||||
var deserializer = new DeserializerBuilder()
|
var obj = deserializer.Deserialize<T>(str);
|
||||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
return obj;
|
||||||
.Build();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
T obj = deserializer.Deserialize<T>(str);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog("FromYaml", ex);
|
|
||||||
return deserializer.Deserialize<T>("");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
/// <summary>
|
|
||||||
/// 序列化
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string ToYaml(Object? obj)
|
|
||||||
{
|
{
|
||||||
string result = string.Empty;
|
Logging.SaveLog(_tag, ex);
|
||||||
if (obj == null)
|
return deserializer.Deserialize<T>("");
|
||||||
{
|
}
|
||||||
return result;
|
}
|
||||||
}
|
|
||||||
var serializer = new SerializerBuilder()
|
|
||||||
.WithNamingConvention(HyphenatedNamingConvention.Instance)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
try
|
/// <summary>
|
||||||
{
|
/// Serialize
|
||||||
result = serializer.Serialize(obj);
|
/// </summary>
|
||||||
}
|
/// <param name="obj"></param>
|
||||||
catch (Exception ex)
|
/// <returns></returns>
|
||||||
{
|
public static string ToYaml(object? obj)
|
||||||
Logging.SaveLog(ex.Message, ex);
|
{
|
||||||
}
|
var result = string.Empty;
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
var serializer = new SerializerBuilder()
|
||||||
public static string? PreprocessYaml(string str)
|
.WithNamingConvention(HyphenatedNamingConvention.Instance)
|
||||||
{
|
|
||||||
var deserializer = new DeserializerBuilder()
|
|
||||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
|
||||||
.Build();
|
.Build();
|
||||||
try
|
|
||||||
{
|
|
||||||
var mergingParser = new MergingParser(new Parser(new StringReader(str)));
|
|
||||||
var obj = new DeserializerBuilder().Build().Deserialize(mergingParser);
|
|
||||||
return ToYaml(obj);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog("PreprocessYaml", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion YAML
|
try
|
||||||
|
{
|
||||||
|
result = serializer.Serialize(obj);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static string? PreprocessYaml(string str)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mergingParser = new MergingParser(new Parser(new StringReader(str)));
|
||||||
|
var obj = new DeserializerBuilder().Build().Deserialize(mergingParser);
|
||||||
|
return ToYaml(obj);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion YAML
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum EConfigType
|
||||||
{
|
{
|
||||||
public enum EConfigType
|
VMess = 1,
|
||||||
{
|
Custom = 2,
|
||||||
VMess = 1,
|
Shadowsocks = 3,
|
||||||
Custom = 2,
|
SOCKS = 4,
|
||||||
Shadowsocks = 3,
|
VLESS = 5,
|
||||||
Socks = 4,
|
Trojan = 6,
|
||||||
VLESS = 5,
|
Hysteria2 = 7,
|
||||||
Trojan = 6,
|
TUIC = 8,
|
||||||
Hysteria2 = 7,
|
WireGuard = 9,
|
||||||
Tuic = 8,
|
HTTP = 10,
|
||||||
Wireguard = 9,
|
Anytls = 11,
|
||||||
Http = 10
|
PolicyGroup = 101,
|
||||||
}
|
ProxyChain = 102,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum ECoreType
|
||||||
{
|
{
|
||||||
public enum ECoreType
|
v2fly = 1,
|
||||||
{
|
Xray = 2,
|
||||||
v2fly = 1,
|
v2fly_v5 = 4,
|
||||||
Xray = 2,
|
mihomo = 13,
|
||||||
SagerNet = 3,
|
hysteria = 21,
|
||||||
v2fly_v5 = 4,
|
naiveproxy = 22,
|
||||||
clash = 11,
|
tuic = 23,
|
||||||
clash_meta = 12,
|
sing_box = 24,
|
||||||
mihomo = 13,
|
juicity = 25,
|
||||||
hysteria = 21,
|
hysteria2 = 26,
|
||||||
naiveproxy = 22,
|
brook = 27,
|
||||||
tuic = 23,
|
overtls = 28,
|
||||||
sing_box = 24,
|
shadowquic = 29,
|
||||||
juicity = 25,
|
mieru = 30,
|
||||||
hysteria2 = 26,
|
v2rayN = 99
|
||||||
v2rayN = 99
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum EGirdOrientation
|
||||||
{
|
{
|
||||||
public enum EGirdOrientation
|
Horizontal,
|
||||||
{
|
Vertical,
|
||||||
Horizontal,
|
Tab,
|
||||||
Vertical,
|
}
|
||||||
Tab,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum EGlobalHotkey
|
||||||
{
|
{
|
||||||
public enum EGlobalHotkey
|
ShowForm = 0,
|
||||||
{
|
SystemProxyClear = 1,
|
||||||
ShowForm = 0,
|
SystemProxySet = 2,
|
||||||
SystemProxyClear = 1,
|
SystemProxyUnchanged = 3,
|
||||||
SystemProxySet = 2,
|
SystemProxyPac = 4,
|
||||||
SystemProxyUnchanged = 3,
|
}
|
||||||
SystemProxyPac = 4,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum EInboundProtocol
|
||||||
{
|
{
|
||||||
public enum EInboundProtocol
|
socks = 0,
|
||||||
{
|
socks2,
|
||||||
socks = 0,
|
socks3,
|
||||||
http,
|
pac,
|
||||||
socks2,
|
api,
|
||||||
http2,
|
api2,
|
||||||
pac,
|
mixed,
|
||||||
api,
|
speedtest = 21
|
||||||
api2,
|
}
|
||||||
speedtest = 21
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum EMove
|
||||||
{
|
{
|
||||||
public enum EMove
|
Top = 1,
|
||||||
{
|
Up = 2,
|
||||||
Top = 1,
|
Down = 3,
|
||||||
Up = 2,
|
Bottom = 4,
|
||||||
Down = 3,
|
Position = 5
|
||||||
Bottom = 4,
|
}
|
||||||
Position = 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
10
v2rayN/ServiceLib/Enums/EMultipleLoad.cs
Normal file
10
v2rayN/ServiceLib/Enums/EMultipleLoad.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum EMultipleLoad
|
||||||
|
{
|
||||||
|
LeastPing,
|
||||||
|
Fallback,
|
||||||
|
Random,
|
||||||
|
RoundRobin,
|
||||||
|
LeastLoad
|
||||||
|
}
|
||||||
8
v2rayN/ServiceLib/Enums/EPresetType.cs
Normal file
8
v2rayN/ServiceLib/Enums/EPresetType.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum EPresetType
|
||||||
|
{
|
||||||
|
Default = 0,
|
||||||
|
Russia = 1,
|
||||||
|
Iran = 2,
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum ERuleMode
|
||||||
{
|
{
|
||||||
public enum ERuleMode
|
Rule = 0,
|
||||||
{
|
Global = 1,
|
||||||
Rule = 0,
|
Direct = 2,
|
||||||
Global = 1,
|
Unchanged = 3
|
||||||
Direct = 2,
|
}
|
||||||
Unchanged = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
8
v2rayN/ServiceLib/Enums/ERuleType.cs
Normal file
8
v2rayN/ServiceLib/Enums/ERuleType.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum ERuleType
|
||||||
|
{
|
||||||
|
ALL = 0,
|
||||||
|
Routing = 1,
|
||||||
|
DNS = 2,
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
{
|
|
||||||
public enum EServerColName
|
|
||||||
{
|
|
||||||
def = 0,
|
|
||||||
configType,
|
|
||||||
remarks,
|
|
||||||
address,
|
|
||||||
port,
|
|
||||||
network,
|
|
||||||
streamSecurity,
|
|
||||||
subRemarks,
|
|
||||||
delayVal,
|
|
||||||
speedVal,
|
|
||||||
|
|
||||||
todayDown,
|
public enum EServerColName
|
||||||
todayUp,
|
{
|
||||||
totalDown,
|
Def = 0,
|
||||||
totalUp
|
ConfigType,
|
||||||
}
|
Remarks,
|
||||||
}
|
Address,
|
||||||
|
Port,
|
||||||
|
Network,
|
||||||
|
StreamSecurity,
|
||||||
|
SubRemarks,
|
||||||
|
DelayVal,
|
||||||
|
SpeedVal,
|
||||||
|
|
||||||
|
TodayDown,
|
||||||
|
TodayUp,
|
||||||
|
TotalDown,
|
||||||
|
TotalUp
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum ESpeedActionType
|
||||||
{
|
{
|
||||||
public enum ESpeedActionType
|
Tcping,
|
||||||
{
|
Realping,
|
||||||
Tcping,
|
Speedtest,
|
||||||
Realping,
|
Mixedtest,
|
||||||
Speedtest,
|
FastRealping
|
||||||
Mixedtest
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum ESysProxyType
|
||||||
{
|
{
|
||||||
public enum ESysProxyType
|
ForcedClear = 0,
|
||||||
{
|
ForcedChange = 1,
|
||||||
ForcedClear = 0,
|
Unchanged = 2,
|
||||||
ForcedChange = 1,
|
Pac = 3
|
||||||
Unchanged = 2,
|
}
|
||||||
Pac = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
12
v2rayN/ServiceLib/Enums/ETheme.cs
Normal file
12
v2rayN/ServiceLib/Enums/ETheme.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum ETheme
|
||||||
|
{
|
||||||
|
FollowSystem,
|
||||||
|
Dark,
|
||||||
|
Light,
|
||||||
|
Aquatic,
|
||||||
|
Desert,
|
||||||
|
Dusk,
|
||||||
|
NightSky
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum ETransport
|
||||||
{
|
{
|
||||||
public enum ETransport
|
tcp,
|
||||||
{
|
kcp,
|
||||||
tcp,
|
ws,
|
||||||
kcp,
|
httpupgrade,
|
||||||
ws,
|
xhttp,
|
||||||
httpupgrade,
|
h2,
|
||||||
splithttp,
|
http,
|
||||||
h2,
|
quic,
|
||||||
http,
|
grpc
|
||||||
quic,
|
}
|
||||||
grpc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,36 @@
|
||||||
namespace ServiceLib.Enums
|
namespace ServiceLib.Enums;
|
||||||
|
|
||||||
|
public enum EViewAction
|
||||||
{
|
{
|
||||||
public enum EViewAction
|
CloseWindow,
|
||||||
{
|
ShowYesNo,
|
||||||
CloseWindow,
|
SaveFileDialog,
|
||||||
ShowYesNo,
|
AddBatchRoutingRulesYesNo,
|
||||||
SaveFileDialog,
|
SetClipboardData,
|
||||||
AddBatchRoutingRulesYesNo,
|
AddServerViaClipboard,
|
||||||
AdjustMainLvColWidth,
|
ImportRulesFromClipboard,
|
||||||
UpdateSysProxy,
|
ProfilesFocus,
|
||||||
SetClipboardData,
|
ShareSub,
|
||||||
AddServerViaClipboard,
|
ShareServer,
|
||||||
ImportRulesFromClipboard,
|
ScanScreenTask,
|
||||||
ProfilesFocus,
|
ScanImageTask,
|
||||||
ShareSub,
|
BrowseServer,
|
||||||
ShareServer,
|
ImportRulesFromFile,
|
||||||
ShowHideWindow,
|
InitSettingFont,
|
||||||
ScanScreenTask,
|
PasswordInput,
|
||||||
Shutdown,
|
SubEditWindow,
|
||||||
BrowseServer,
|
RoutingRuleSettingWindow,
|
||||||
ImportRulesFromFile,
|
RoutingRuleDetailsWindow,
|
||||||
SubEditWindow,
|
AddServerWindow,
|
||||||
RoutingRuleSettingWindow,
|
AddServer2Window,
|
||||||
RoutingRuleDetailsWindow,
|
AddGroupServerWindow,
|
||||||
AddServerWindow,
|
DNSSettingWindow,
|
||||||
AddServer2Window,
|
RoutingSettingWindow,
|
||||||
DNSSettingWindow,
|
OptionSettingWindow,
|
||||||
RoutingSettingWindow,
|
FullConfigTemplateWindow,
|
||||||
OptionSettingWindow,
|
GlobalHotkeySettingWindow,
|
||||||
GlobalHotkeySettingWindow,
|
SubSettingWindow,
|
||||||
SubSettingWindow,
|
DispatcherRefreshServersBiz,
|
||||||
DispatcherSpeedTest,
|
DispatcherRefreshIcon,
|
||||||
DispatcherRefreshConnections,
|
DispatcherShowMsg,
|
||||||
DispatcherRefreshProxyGroups,
|
}
|
||||||
DispatcherProxiesDelayTest,
|
|
||||||
DispatcherStatistics,
|
|
||||||
DispatcherServerAvailability,
|
|
||||||
DispatcherReload,
|
|
||||||
DispatcherRefreshServersBiz,
|
|
||||||
DispatcherRefreshIcon,
|
|
||||||
DispatcherCheckUpdate,
|
|
||||||
DispatcherCheckUpdateFinished,
|
|
||||||
DispatcherShowMsg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
30
v2rayN/ServiceLib/Events/AppEvents.cs
Normal file
30
v2rayN/ServiceLib/Events/AppEvents.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
namespace ServiceLib.Events;
|
||||||
|
|
||||||
|
public static class AppEvents
|
||||||
|
{
|
||||||
|
public static readonly EventChannel<Unit> ReloadRequested = new();
|
||||||
|
public static readonly EventChannel<bool?> ShowHideWindowRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
|
||||||
|
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> ProxiesReloadRequested = new();
|
||||||
|
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<string> SendSnackMsgRequested = new();
|
||||||
|
public static readonly EventChannel<string> SendMsgViewRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<Unit> AppExitRequested = new();
|
||||||
|
public static readonly EventChannel<bool> ShutdownRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<Unit> AdjustMainLvColWidthRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<string> SetDefaultServerRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<Unit> RoutingsMenuRefreshRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> TestServerRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> InboundDisplayRequested = new();
|
||||||
|
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
|
||||||
|
}
|
||||||
27
v2rayN/ServiceLib/Events/EventChannel.cs
Normal file
27
v2rayN/ServiceLib/Events/EventChannel.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System.Reactive.Subjects;
|
||||||
|
|
||||||
|
namespace ServiceLib.Events;
|
||||||
|
|
||||||
|
public sealed class EventChannel<T>
|
||||||
|
{
|
||||||
|
private readonly ISubject<T> _subject = Subject.Synchronize(new Subject<T>());
|
||||||
|
|
||||||
|
public IObservable<T> AsObservable()
|
||||||
|
{
|
||||||
|
return _subject.AsObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Publish(T value)
|
||||||
|
{
|
||||||
|
_subject.OnNext(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Publish()
|
||||||
|
{
|
||||||
|
if (typeof(T) != typeof(Unit))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Publish() without value is only valid for EventChannel<Unit>.");
|
||||||
|
}
|
||||||
|
_subject.OnNext((T)(object)Unit.Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,128 +1,167 @@
|
||||||
namespace ServiceLib
|
namespace ServiceLib;
|
||||||
|
|
||||||
|
public class Global
|
||||||
{
|
{
|
||||||
public class Global
|
#region const
|
||||||
{
|
|
||||||
#region const
|
|
||||||
|
|
||||||
public const string AppName = "v2rayN";
|
public const string AppName = "v2rayN";
|
||||||
public const string GithubUrl = "https://github.com";
|
public const string GithubUrl = "https://github.com";
|
||||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||||
public const string V2flyCoreUrl = "https://github.com/v2fly/v2ray-core/releases";
|
public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat";
|
||||||
public const string XrayCoreUrl = "https://github.com/XTLS/Xray-core/releases";
|
public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs";
|
||||||
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 PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw=";
|
||||||
public const string ConfigFileName = "guiNConfig.json";
|
public const string ConfigFileName = "guiNConfig.json";
|
||||||
public const string CoreConfigFileName = "config.json";
|
public const string CoreConfigFileName = "config.json";
|
||||||
public const string CorePreConfigFileName = "configPre.json";
|
public const string CorePreConfigFileName = "configPre.json";
|
||||||
public const string CoreSpeedtestConfigFileName = "configSpeedtest.json";
|
public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
|
||||||
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
|
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
|
||||||
public const string ClashMixinConfigFileName = "Mixin.yaml";
|
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 NamespaceSample = "ServiceLib.Sample.";
|
||||||
public const string DefaultNetwork = "tcp";
|
public const string V2raySampleClient = NamespaceSample + "SampleClientConfig";
|
||||||
public const string TcpHeaderHttp = "http";
|
public const string SingboxSampleClient = NamespaceSample + "SingboxSampleClientConfig";
|
||||||
public const string None = "none";
|
public const string V2raySampleHttpRequestFileName = NamespaceSample + "SampleHttpRequest";
|
||||||
public const string ProxyTag = "proxy";
|
public const string V2raySampleHttpResponseFileName = NamespaceSample + "SampleHttpResponse";
|
||||||
public const string DirectTag = "direct";
|
public const string V2raySampleInbound = NamespaceSample + "SampleInbound";
|
||||||
public const string BlockTag = "block";
|
public const string V2raySampleOutbound = NamespaceSample + "SampleOutbound";
|
||||||
public const string StreamSecurity = "tls";
|
public const string SingboxSampleOutbound = NamespaceSample + "SingboxSampleOutbound";
|
||||||
public const string StreamSecurityReality = "reality";
|
public const string CustomRoutingFileName = NamespaceSample + "custom_routing_";
|
||||||
public const string Loopback = "127.0.0.1";
|
public const string TunSingboxDNSFileName = NamespaceSample + "tun_singbox_dns";
|
||||||
public const string InboundAPIProtocol = "dokodemo-door";
|
public const string TunSingboxInboundFileName = NamespaceSample + "tun_singbox_inbound";
|
||||||
public const string HttpProtocol = "http://";
|
public const string TunSingboxRulesFileName = NamespaceSample + "tun_singbox_rules";
|
||||||
public const string HttpsProtocol = "https://";
|
public const string DNSV2rayNormalFileName = NamespaceSample + "dns_v2ray_normal";
|
||||||
public const string SocksProtocol = "socks://";
|
public const string DNSSingboxNormalFileName = NamespaceSample + "dns_singbox_normal";
|
||||||
|
public const string ClashMixinYaml = NamespaceSample + "clash_mixin_yaml";
|
||||||
|
public const string ClashTunYaml = NamespaceSample + "clash_tun_yaml";
|
||||||
|
public const string LinuxAutostartConfig = NamespaceSample + "linux_autostart_config";
|
||||||
|
public const string PacFileName = NamespaceSample + "pac";
|
||||||
|
public const string ProxySetOSXShellFileName = NamespaceSample + "proxy_set_osx_sh";
|
||||||
|
public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh";
|
||||||
|
public const string KillAsSudoOSXShellFileName = NamespaceSample + "kill_as_sudo_osx_sh";
|
||||||
|
public const string KillAsSudoLinuxShellFileName = NamespaceSample + "kill_as_sudo_linux_sh";
|
||||||
|
public const string SingboxFakeIPFilterFileName = NamespaceSample + "singbox_fakeip_filter";
|
||||||
|
|
||||||
public const string UserEMail = "t@t.tt";
|
public const string DefaultSecurity = "auto";
|
||||||
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
public const string DefaultNetwork = "tcp";
|
||||||
public const string AutoRunName = "v2rayNAutoRun";
|
public const string TcpHeaderHttp = "http";
|
||||||
public const string CustomIconName = "v2rayN.ico";
|
public const string None = "none";
|
||||||
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 ProxyTag = "proxy";
|
||||||
public const string RoutingRuleComma = "<COMMA>";
|
public const string DirectTag = "direct";
|
||||||
public const string GrpcGunMode = "gun";
|
public const string BlockTag = "block";
|
||||||
public const string GrpcMultiMode = "multi";
|
public const string DnsTag = "dns-module";
|
||||||
public const int MaxPort = 65536;
|
public const string BalancerTagSuffix = "-round";
|
||||||
public const string CommandClearMsg = "CommandClearMsg";
|
public const string StreamSecurity = "tls";
|
||||||
public const string CommandSendMsgView = "CommandSendMsgView";
|
public const string StreamSecurityReality = "reality";
|
||||||
public const string CommandSendSnackMsg = "CommandSendSnackMsg";
|
public const string Loopback = "127.0.0.1";
|
||||||
public const string CommandStopSpeedTest = "CommandStopSpeedTest";
|
public const string InboundAPIProtocol = "dokodemo-door";
|
||||||
public const string CommandRefreshProfiles = "CommandRefreshProfiles";
|
public const string HttpProtocol = "http://";
|
||||||
public const string DelayUnit = "";
|
public const string HttpsProtocol = "https://";
|
||||||
public const string SpeedUnit = "";
|
public const string SocksProtocol = "socks://";
|
||||||
public const int MinFontSize = 10;
|
public const string Socks5Protocol = "socks5://";
|
||||||
public const string RebootAs = "rebootas";
|
public const string AsIs = "AsIs";
|
||||||
|
public const string IPIfNonMatch = "IPIfNonMatch";
|
||||||
|
public const string IPOnDemand = "IPOnDemand";
|
||||||
|
|
||||||
public static readonly List<string> IEProxyProtocols = new() {
|
public const string UserEMail = "t@t.tt";
|
||||||
"{ip}:{http_port}",
|
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
||||||
"socks={ip}:{socks_port}",
|
public const string AutoRunName = "v2rayNAutoRun";
|
||||||
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
|
public const string SystemProxyExceptionsWindows = "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.*";
|
||||||
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
|
public const string SystemProxyExceptionsLinux = "localhost,127.0.0.0/8,::1";
|
||||||
""
|
public const string RoutingRuleComma = "<COMMA>";
|
||||||
};
|
public const string GrpcGunMode = "gun";
|
||||||
|
public const string GrpcMultiMode = "multi";
|
||||||
|
public const int MaxPort = 65536;
|
||||||
|
public const int MinFontSize = 8;
|
||||||
|
public const string RebootAs = "rebootas";
|
||||||
|
public const string AvaAssets = "avares://v2rayN/Assets/";
|
||||||
|
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2";
|
||||||
|
public const string V2RayLocalAsset = "V2RAY_LOCATION_ASSET";
|
||||||
|
public const string XrayLocalAsset = "XRAY_LOCATION_ASSET";
|
||||||
|
public const string XrayLocalCert = "XRAY_LOCATION_CERT";
|
||||||
|
public const int SpeedTestPageSize = 1000;
|
||||||
|
public const string LinuxBash = "/bin/bash";
|
||||||
|
|
||||||
public static readonly List<string> SubConvertUrls = new List<string> {
|
public const string SingboxDirectDNSTag = "direct_dns";
|
||||||
@"https://sub.xeton.dev/sub?url={0}",
|
public const string SingboxRemoteDNSTag = "remote_dns";
|
||||||
@"https://api.dler.io/sub?url={0}",
|
public const string SingboxLocalDNSTag = "local_local";
|
||||||
@"http://127.0.0.1:25500/sub?url={0}",
|
public const string SingboxHostsDNSTag = "hosts_dns";
|
||||||
""
|
public const string SingboxFakeDNSTag = "fake_dns";
|
||||||
};
|
|
||||||
|
|
||||||
public static readonly List<string> SubConvertConfig = new List<string> {
|
public static readonly List<string> IEProxyProtocols =
|
||||||
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
|
[
|
||||||
};
|
"{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> SubConvertTargets = new List<string> {
|
public static readonly List<string> SubConvertUrls =
|
||||||
"",
|
[
|
||||||
"mixed",
|
@"https://sub.xeton.dev/sub?url={0}",
|
||||||
"v2ray",
|
@"https://api.dler.io/sub?url={0}",
|
||||||
"clash",
|
@"http://127.0.0.1:25500/sub?url={0}",
|
||||||
"ss",
|
""
|
||||||
};
|
];
|
||||||
|
|
||||||
public static readonly List<string> SpeedTestUrls = new() {
|
public static readonly List<string> SubConvertConfig =
|
||||||
@"https://speed.cloudflare.com/__down?bytes=100000000",
|
[@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"];
|
||||||
|
|
||||||
|
public static readonly List<string> SubConvertTargets =
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"mixed",
|
||||||
|
"v2ray",
|
||||||
|
"clash",
|
||||||
|
"ss"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> SpeedTestUrls =
|
||||||
|
[
|
||||||
|
@"https://cachefly.cachefly.net/50mb.test",
|
||||||
@"https://speed.cloudflare.com/__down?bytes=10000000",
|
@"https://speed.cloudflare.com/__down?bytes=10000000",
|
||||||
@"http://cachefly.cachefly.net/50mb.test",
|
@"https://speed.cloudflare.com/__down?bytes=50000000",
|
||||||
@"http://cachefly.cachefly.net/10mb.test"
|
@"https://speed.cloudflare.com/__down?bytes=100000000",
|
||||||
};
|
];
|
||||||
|
|
||||||
public static readonly List<string> SpeedPingTestUrls = new() {
|
public static readonly List<string> SpeedPingTestUrls =
|
||||||
@"https://www.google.com/generate_204",
|
[
|
||||||
|
@"https://www.google.com/generate_204",
|
||||||
@"https://www.gstatic.com/generate_204",
|
@"https://www.gstatic.com/generate_204",
|
||||||
@"https://www.apple.com/library/test/success.html",
|
@"https://www.apple.com/library/test/success.html",
|
||||||
@"http://www.msftconnecttest.com/connecttest.txt",
|
@"http://www.msftconnecttest.com/connecttest.txt"
|
||||||
};
|
];
|
||||||
|
|
||||||
public static readonly Dictionary<string, string> UserAgentTexts = new()
|
public static readonly List<string> GeoFilesSources =
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
@"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat",
|
||||||
|
@"https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/{0}.dat"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> SingboxRulesetSources =
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
|
||||||
|
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> RoutingRulesSources =
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/template.json",
|
||||||
|
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/template.json"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> DNSTemplateSources =
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/",
|
||||||
|
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/"
|
||||||
|
];
|
||||||
|
|
||||||
|
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" },
|
{"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" },
|
{"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" },
|
||||||
|
|
@ -131,71 +170,460 @@
|
||||||
{"none",""}
|
{"none",""}
|
||||||
};
|
};
|
||||||
|
|
||||||
public const string Hysteria2ProtocolShare = "hy2://";
|
public const string Hysteria2ProtocolShare = "hy2://";
|
||||||
|
|
||||||
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
|
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
|
||||||
{
|
{
|
||||||
{EConfigType.VMess,"vmess://"},
|
{ EConfigType.VMess, "vmess://" },
|
||||||
{EConfigType.Shadowsocks,"ss://"},
|
{ EConfigType.Shadowsocks, "ss://" },
|
||||||
{EConfigType.Socks,"socks://"},
|
{ EConfigType.SOCKS, "socks://" },
|
||||||
{EConfigType.VLESS,"vless://"},
|
{ EConfigType.VLESS, "vless://" },
|
||||||
{EConfigType.Trojan,"trojan://"},
|
{ EConfigType.Trojan, "trojan://" },
|
||||||
{EConfigType.Hysteria2,"hysteria2://"},
|
{ EConfigType.Hysteria2, "hysteria2://" },
|
||||||
{EConfigType.Tuic,"tuic://"},
|
{ EConfigType.TUIC, "tuic://" },
|
||||||
{EConfigType.Wireguard,"wireguard://"}
|
{ EConfigType.WireGuard, "wireguard://" },
|
||||||
|
{ EConfigType.Anytls, "anytls://" }
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
|
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
|
||||||
{
|
{
|
||||||
{EConfigType.VMess,"vmess"},
|
{ EConfigType.VMess, "vmess" },
|
||||||
{EConfigType.Shadowsocks,"shadowsocks"},
|
{ EConfigType.Shadowsocks, "shadowsocks" },
|
||||||
{EConfigType.Socks,"socks"},
|
{ EConfigType.SOCKS, "socks" },
|
||||||
{EConfigType.Http,"http"},
|
{ EConfigType.HTTP, "http" },
|
||||||
{EConfigType.VLESS,"vless"},
|
{ EConfigType.VLESS, "vless" },
|
||||||
{EConfigType.Trojan,"trojan"},
|
{ EConfigType.Trojan, "trojan" },
|
||||||
{EConfigType.Hysteria2,"hysteria2"},
|
{ EConfigType.Hysteria2, "hysteria2" },
|
||||||
{EConfigType.Tuic,"tuic"},
|
{ EConfigType.TUIC, "tuic" },
|
||||||
{EConfigType.Wireguard,"wireguard"}
|
{ EConfigType.WireGuard, "wireguard" },
|
||||||
|
{ EConfigType.Anytls, "anytls" }
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly List<string> VmessSecurities = new() { "aes-128-gcm", "chacha20-poly1305", "auto", "none", "zero" };
|
public static readonly List<string> VmessSecurities =
|
||||||
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" };
|
"aes-128-gcm",
|
||||||
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" };
|
"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" };
|
"auto",
|
||||||
public static readonly List<string> Flows = new() { "", "xtls-rprx-vision", "xtls-rprx-vision-udp443" };
|
"none",
|
||||||
public static readonly List<string> Networks = new() { "tcp", "kcp", "ws", "httpupgrade", "splithttp", "h2", "quic", "grpc" };
|
"zero"
|
||||||
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> SsSecurities =
|
||||||
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", "" };
|
"aes-256-gcm",
|
||||||
public static readonly List<string> DomainDNSAddress = ["223.5.5.5", "223.6.6.6", "localhost"];
|
"aes-128-gcm",
|
||||||
public static readonly List<string> SingboxDomainDNSAddress = ["223.5.5.5", "223.6.6.6", "dhcp://auto"];
|
"chacha20-poly1305",
|
||||||
public static readonly List<string> Languages = new() { "zh-Hans", "zh-Hant", "en", "fa-Ir", "ru" };
|
"chacha20-ietf-poly1305",
|
||||||
public static readonly List<string> Alpns = new() { "h3", "h2", "http/1.1", "h3,h2", "h2,http/1.1", "h3,h2,http/1.1", "" };
|
"none",
|
||||||
public static readonly List<string> LogLevels = new() { "debug", "info", "warning", "error", "none" };
|
"plain"
|
||||||
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> SsSecuritiesInXray =
|
||||||
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" };
|
"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"
|
||||||
|
];
|
||||||
|
|
||||||
#endregion const
|
public static readonly List<string> SsSecuritiesInSingbox =
|
||||||
}
|
[
|
||||||
}
|
"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 =
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"xtls-rprx-vision",
|
||||||
|
"xtls-rprx-vision-udp443"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> Networks =
|
||||||
|
[
|
||||||
|
"tcp",
|
||||||
|
"kcp",
|
||||||
|
"ws",
|
||||||
|
"httpupgrade",
|
||||||
|
"xhttp",
|
||||||
|
"h2",
|
||||||
|
"quic",
|
||||||
|
"grpc"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> KcpHeaderTypes =
|
||||||
|
[
|
||||||
|
"srtp",
|
||||||
|
"utp",
|
||||||
|
"wechat-video",
|
||||||
|
"dtls",
|
||||||
|
"wireguard",
|
||||||
|
"dns"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> CoreTypes =
|
||||||
|
[
|
||||||
|
"Xray",
|
||||||
|
"sing_box"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly HashSet<EConfigType> XraySupportConfigType =
|
||||||
|
[
|
||||||
|
EConfigType.VMess,
|
||||||
|
EConfigType.VLESS,
|
||||||
|
EConfigType.Shadowsocks,
|
||||||
|
EConfigType.Trojan,
|
||||||
|
EConfigType.WireGuard,
|
||||||
|
EConfigType.SOCKS,
|
||||||
|
EConfigType.HTTP,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly HashSet<EConfigType> SingboxSupportConfigType =
|
||||||
|
[
|
||||||
|
EConfigType.VMess,
|
||||||
|
EConfigType.VLESS,
|
||||||
|
EConfigType.Shadowsocks,
|
||||||
|
EConfigType.Trojan,
|
||||||
|
EConfigType.Hysteria2,
|
||||||
|
EConfigType.TUIC,
|
||||||
|
EConfigType.Anytls,
|
||||||
|
EConfigType.WireGuard,
|
||||||
|
EConfigType.SOCKS,
|
||||||
|
EConfigType.HTTP,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly HashSet<EConfigType> SingboxOnlyConfigType = SingboxSupportConfigType.Except(XraySupportConfigType).ToHashSet();
|
||||||
|
|
||||||
|
public static readonly List<string> DomainStrategies =
|
||||||
|
[
|
||||||
|
AsIs,
|
||||||
|
IPIfNonMatch,
|
||||||
|
IPOnDemand
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> DomainStrategies4Singbox =
|
||||||
|
[
|
||||||
|
"ipv4_only",
|
||||||
|
"ipv6_only",
|
||||||
|
"prefer_ipv4",
|
||||||
|
"prefer_ipv6",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> Fingerprints =
|
||||||
|
[
|
||||||
|
"chrome",
|
||||||
|
"firefox",
|
||||||
|
"safari",
|
||||||
|
"ios",
|
||||||
|
"android",
|
||||||
|
"edge",
|
||||||
|
"360",
|
||||||
|
"qq",
|
||||||
|
"random",
|
||||||
|
"randomized",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> UserAgent =
|
||||||
|
[
|
||||||
|
"chrome",
|
||||||
|
"firefox",
|
||||||
|
"safari",
|
||||||
|
"edge",
|
||||||
|
"none"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> XhttpMode =
|
||||||
|
[
|
||||||
|
"auto",
|
||||||
|
"packet-up",
|
||||||
|
"stream-up",
|
||||||
|
"stream-one"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> AllowInsecure =
|
||||||
|
[
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> DomainStrategy4Freedoms =
|
||||||
|
[
|
||||||
|
"AsIs",
|
||||||
|
"UseIP",
|
||||||
|
"UseIPv4",
|
||||||
|
"UseIPv6",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> SingboxDomainStrategy4Out =
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"ipv4_only",
|
||||||
|
"prefer_ipv4",
|
||||||
|
"prefer_ipv6",
|
||||||
|
"ipv6_only"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> DomainDirectDNSAddress =
|
||||||
|
[
|
||||||
|
"https://dns.alidns.com/dns-query",
|
||||||
|
"https://doh.pub/dns-query",
|
||||||
|
"223.5.5.5",
|
||||||
|
"119.29.29.29",
|
||||||
|
"localhost"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> DomainRemoteDNSAddress =
|
||||||
|
[
|
||||||
|
"https://cloudflare-dns.com/dns-query",
|
||||||
|
"https://dns.cloudflare.com/dns-query",
|
||||||
|
"https://dns.google/dns-query",
|
||||||
|
"https://doh.dns.sb/dns-query",
|
||||||
|
"https://doh.opendns.com/dns-query",
|
||||||
|
"https://common.dot.dns.yandex.net",
|
||||||
|
"8.8.8.8",
|
||||||
|
"1.1.1.1",
|
||||||
|
"185.222.222.222",
|
||||||
|
"208.67.222.222",
|
||||||
|
"77.88.8.8"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> DomainPureIPDNSAddress =
|
||||||
|
[
|
||||||
|
"223.5.5.5",
|
||||||
|
"119.29.29.29",
|
||||||
|
"localhost"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> Languages =
|
||||||
|
[
|
||||||
|
"zh-Hans",
|
||||||
|
"zh-Hant",
|
||||||
|
"en",
|
||||||
|
"fa-Ir",
|
||||||
|
"ru",
|
||||||
|
"hu"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> Alpns =
|
||||||
|
[
|
||||||
|
"h3",
|
||||||
|
"h2",
|
||||||
|
"http/1.1",
|
||||||
|
"h3,h2",
|
||||||
|
"h2,http/1.1",
|
||||||
|
"h3,h2,http/1.1",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> LogLevels =
|
||||||
|
[
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"warning",
|
||||||
|
"error",
|
||||||
|
"none"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly Dictionary<string, string> LogLevelColors = new()
|
||||||
|
{
|
||||||
|
{ "debug", "#6C757D" },
|
||||||
|
{ "info", "#2ECC71" },
|
||||||
|
{ "warning", "#FFA500" },
|
||||||
|
{ "error", "#E74C3C" },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly List<string> InboundTags =
|
||||||
|
[
|
||||||
|
"socks",
|
||||||
|
"socks2",
|
||||||
|
"socks3"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> RuleProtocols =
|
||||||
|
[
|
||||||
|
"http",
|
||||||
|
"tls",
|
||||||
|
"bittorrent"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> RuleNetworks =
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"tcp",
|
||||||
|
"udp",
|
||||||
|
"tcp,udp"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> destOverrideProtocols =
|
||||||
|
[
|
||||||
|
"http",
|
||||||
|
"tls",
|
||||||
|
"quic",
|
||||||
|
"fakedns",
|
||||||
|
"fakedns+others"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<int> TunMtus =
|
||||||
|
[
|
||||||
|
1280,
|
||||||
|
1408,
|
||||||
|
1500,
|
||||||
|
4064,
|
||||||
|
9000,
|
||||||
|
65535
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> TunStacks =
|
||||||
|
[
|
||||||
|
"gvisor",
|
||||||
|
"system",
|
||||||
|
"mixed"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> PresetMsgFilters =
|
||||||
|
[
|
||||||
|
"proxy",
|
||||||
|
"direct",
|
||||||
|
"block",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> SingboxMuxs =
|
||||||
|
[
|
||||||
|
"h2mux",
|
||||||
|
"smux",
|
||||||
|
"yamux",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> TuicCongestionControls =
|
||||||
|
[
|
||||||
|
"cubic",
|
||||||
|
"new_reno",
|
||||||
|
"bbr"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> allowSelectType =
|
||||||
|
[
|
||||||
|
"selector",
|
||||||
|
"urltest",
|
||||||
|
"loadbalance",
|
||||||
|
"fallback"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> notAllowTestType =
|
||||||
|
[
|
||||||
|
"selector",
|
||||||
|
"urltest",
|
||||||
|
"direct",
|
||||||
|
"reject",
|
||||||
|
"compatible",
|
||||||
|
"pass",
|
||||||
|
"loadbalance",
|
||||||
|
"fallback"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> proxyVehicleType =
|
||||||
|
[
|
||||||
|
"file",
|
||||||
|
"http"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly Dictionary<ECoreType, string> CoreUrls = new()
|
||||||
|
{
|
||||||
|
{ ECoreType.v2fly, "v2fly/v2ray-core" },
|
||||||
|
{ ECoreType.v2fly_v5, "v2fly/v2ray-core" },
|
||||||
|
{ ECoreType.Xray, "XTLS/Xray-core" },
|
||||||
|
{ ECoreType.sing_box, "SagerNet/sing-box" },
|
||||||
|
{ ECoreType.mihomo, "MetaCubeX/mihomo" },
|
||||||
|
{ ECoreType.hysteria, "apernet/hysteria" },
|
||||||
|
{ ECoreType.hysteria2, "apernet/hysteria" },
|
||||||
|
{ ECoreType.naiveproxy, "klzgrad/naiveproxy" },
|
||||||
|
{ ECoreType.tuic, "EAimTY/tuic" },
|
||||||
|
{ ECoreType.juicity, "juicity/juicity" },
|
||||||
|
{ ECoreType.brook, "txthinking/brook" },
|
||||||
|
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
|
||||||
|
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
|
||||||
|
{ ECoreType.mieru, "enfein/mieru" },
|
||||||
|
{ ECoreType.v2rayN, "2dust/v2rayN" },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly List<string> OtherGeoUrls =
|
||||||
|
[
|
||||||
|
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat",
|
||||||
|
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
|
||||||
|
@"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> IPAPIUrls =
|
||||||
|
[
|
||||||
|
@"https://speed.cloudflare.com/meta",
|
||||||
|
@"https://api.ip.sb/geoip",
|
||||||
|
@"https://api-ipv4.ip.sb/geoip",
|
||||||
|
@"https://api-ipv6.ip.sb/geoip",
|
||||||
|
@"https://api.ipapi.is",
|
||||||
|
@""
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> OutboundTags =
|
||||||
|
[
|
||||||
|
ProxyTag,
|
||||||
|
DirectTag,
|
||||||
|
BlockTag
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly Dictionary<string, List<string>> PredefinedHosts = new()
|
||||||
|
{
|
||||||
|
{ "dns.google", new List<string> { "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844" } },
|
||||||
|
{ "dns.alidns.com", new List<string> { "223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1" } },
|
||||||
|
{ "one.one.one.one", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
|
||||||
|
{ "1dot1dot1dot1.cloudflare-dns.com", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
|
||||||
|
{ "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } },
|
||||||
|
{ "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } },
|
||||||
|
{ "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
|
||||||
|
{ "doh.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
|
||||||
|
{ "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } },
|
||||||
|
{ "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } },
|
||||||
|
{ "dns.sb", new List<string> { "185.222.222.222", "2a09::" } },
|
||||||
|
{ "dns.umbrella.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
|
||||||
|
{ "dns.sse.cisco.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
|
||||||
|
{ "engage.cloudflareclient.com", new List<string> { "162.159.192.1", "2606:4700:d0::a29f:c001" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly List<string> ExpectedIPs =
|
||||||
|
[
|
||||||
|
"geoip:cn",
|
||||||
|
"geoip:ir",
|
||||||
|
"geoip:ru",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
|
#endregion const
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,36 @@
|
||||||
global using ServiceLib.Base;
|
global using System.Collections.Concurrent;
|
||||||
|
global using System.Diagnostics;
|
||||||
|
global using System.Net;
|
||||||
|
global using System.Net.NetworkInformation;
|
||||||
|
global using System.Net.Sockets;
|
||||||
|
global using System.Reactive;
|
||||||
|
global using System.Reactive.Disposables;
|
||||||
|
global using System.Reactive.Linq;
|
||||||
|
global using System.Reflection;
|
||||||
|
global using System.Runtime.InteropServices;
|
||||||
|
global using System.Security.Cryptography;
|
||||||
|
global using System.Text;
|
||||||
|
global using System.Text.Encodings.Web;
|
||||||
|
global using System.Text.Json;
|
||||||
|
global using System.Text.Json.Nodes;
|
||||||
|
global using System.Text.Json.Serialization;
|
||||||
|
global using System.Text.RegularExpressions;
|
||||||
|
global using DynamicData;
|
||||||
|
global using DynamicData.Binding;
|
||||||
|
global using ReactiveUI;
|
||||||
|
global using ReactiveUI.Fody.Helpers;
|
||||||
|
global using ServiceLib.Base;
|
||||||
global using ServiceLib.Common;
|
global using ServiceLib.Common;
|
||||||
global using ServiceLib.Enums;
|
global using ServiceLib.Enums;
|
||||||
|
global using ServiceLib.Events;
|
||||||
global using ServiceLib.Handler;
|
global using ServiceLib.Handler;
|
||||||
global using ServiceLib.Handler.CoreConfig;
|
|
||||||
global using ServiceLib.Handler.Fmt;
|
global using ServiceLib.Handler.Fmt;
|
||||||
global using ServiceLib.Handler.Statistics;
|
global using ServiceLib.Handler.SysProxy;
|
||||||
|
global using ServiceLib.Helper;
|
||||||
|
global using ServiceLib.Manager;
|
||||||
global using ServiceLib.Models;
|
global using ServiceLib.Models;
|
||||||
global using ServiceLib.Resx;
|
global using ServiceLib.Resx;
|
||||||
|
global using ServiceLib.Services;
|
||||||
|
global using ServiceLib.Services.CoreConfig;
|
||||||
|
global using ServiceLib.Services.Statistics;
|
||||||
|
global using SQLite;
|
||||||
|
|
|
||||||
240
v2rayN/ServiceLib/Handler/AutoStartupHandler.cs
Normal file
240
v2rayN/ServiceLib/Handler/AutoStartupHandler.cs
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
using System.Security.Principal;
|
||||||
|
|
||||||
|
namespace ServiceLib.Handler;
|
||||||
|
|
||||||
|
public static class AutoStartupHandler
|
||||||
|
{
|
||||||
|
private static readonly string _tag = "AutoStartupHandler";
|
||||||
|
|
||||||
|
public static async Task<bool> UpdateTask(Config config)
|
||||||
|
{
|
||||||
|
if (Utils.IsWindows())
|
||||||
|
{
|
||||||
|
await ClearTaskWindows();
|
||||||
|
|
||||||
|
if (config.GuiItem.AutoRun)
|
||||||
|
{
|
||||||
|
await SetTaskWindows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Utils.IsLinux())
|
||||||
|
{
|
||||||
|
await ClearTaskLinux();
|
||||||
|
|
||||||
|
if (config.GuiItem.AutoRun)
|
||||||
|
{
|
||||||
|
await SetTaskLinux();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Utils.IsOSX())
|
||||||
|
{
|
||||||
|
await ClearTaskOSX();
|
||||||
|
|
||||||
|
if (config.GuiItem.AutoRun)
|
||||||
|
{
|
||||||
|
await SetTaskOSX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows
|
||||||
|
|
||||||
|
private static async Task ClearTaskWindows()
|
||||||
|
{
|
||||||
|
var autoRunName = GetAutoRunNameWindows();
|
||||||
|
WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, "");
|
||||||
|
if (Utils.IsAdministrator())
|
||||||
|
{
|
||||||
|
AutoStartTaskService(autoRunName, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SetTaskWindows()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var autoRunName = GetAutoRunNameWindows();
|
||||||
|
var exePath = Utils.GetExePath();
|
||||||
|
if (Utils.IsAdministrator())
|
||||||
|
{
|
||||||
|
AutoStartTaskService(autoRunName, exePath, "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WindowsUtils.RegWriteValue(Global.AutoRunRegPath, autoRunName, exePath.AppendQuotes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto Start via TaskService
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskName"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
/// <param name="description"></param>
|
||||||
|
/// <exception cref="ArgumentNullException"></exception>
|
||||||
|
public static void AutoStartTaskService(string taskName, string fileName, string description)
|
||||||
|
{
|
||||||
|
if (taskName.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var logonUser = WindowsIdentity.GetCurrent().Name;
|
||||||
|
using var taskService = new Microsoft.Win32.TaskScheduler.TaskService();
|
||||||
|
var tasks = taskService.RootFolder.GetTasks(new Regex(taskName));
|
||||||
|
if (fileName.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
foreach (var t in tasks)
|
||||||
|
{
|
||||||
|
taskService.RootFolder.DeleteTask(t.Name);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var task = taskService.NewTask();
|
||||||
|
task.RegistrationInfo.Description = description;
|
||||||
|
task.Settings.DisallowStartIfOnBatteries = false;
|
||||||
|
task.Settings.StopIfGoingOnBatteries = false;
|
||||||
|
task.Settings.RunOnlyIfIdle = false;
|
||||||
|
task.Settings.IdleSettings.StopOnIdleEnd = false;
|
||||||
|
task.Settings.ExecutionTimeLimit = TimeSpan.Zero;
|
||||||
|
task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(30) });
|
||||||
|
task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest;
|
||||||
|
task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName)));
|
||||||
|
|
||||||
|
taskService.RootFolder.RegisterTaskDefinition(taskName, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAutoRunNameWindows()
|
||||||
|
{
|
||||||
|
return $"{Global.AutoRunName}_{Utils.GetMd5(Utils.StartupPath())}";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Windows
|
||||||
|
|
||||||
|
#region Linux
|
||||||
|
|
||||||
|
private static async Task ClearTaskLinux()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(GetHomePathLinux());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SetTaskLinux()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var linuxConfig = EmbedUtils.GetEmbedText(Global.LinuxAutostartConfig);
|
||||||
|
if (linuxConfig.IsNotEmpty())
|
||||||
|
{
|
||||||
|
linuxConfig = linuxConfig.Replace("$ExecPath$", Utils.GetExePath());
|
||||||
|
Logging.SaveLog(linuxConfig);
|
||||||
|
|
||||||
|
var homePath = GetHomePathLinux();
|
||||||
|
await File.WriteAllTextAsync(homePath, linuxConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetHomePathLinux()
|
||||||
|
{
|
||||||
|
var homePath = Path.Combine(Utils.GetHomePath(), ".config", "autostart", $"{Global.AppName}.desktop");
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(homePath));
|
||||||
|
return homePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Linux
|
||||||
|
|
||||||
|
#region macOS
|
||||||
|
|
||||||
|
private static async Task ClearTaskOSX()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var launchAgentPath = GetLaunchAgentPathMacOS();
|
||||||
|
if (File.Exists(launchAgentPath))
|
||||||
|
{
|
||||||
|
var args = new[] { "-c", $"launchctl unload -w \"{launchAgentPath}\"" };
|
||||||
|
await Utils.GetCliWrapOutput(Global.LinuxBash, args);
|
||||||
|
|
||||||
|
File.Delete(launchAgentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SetTaskOSX()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var plistContent = GenerateLaunchAgentPlist();
|
||||||
|
var launchAgentPath = GetLaunchAgentPathMacOS();
|
||||||
|
await File.WriteAllTextAsync(launchAgentPath, plistContent);
|
||||||
|
|
||||||
|
var args = new[] { "-c", $"launchctl load -w \"{launchAgentPath}\"" };
|
||||||
|
await Utils.GetCliWrapOutput(Global.LinuxBash, args);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetLaunchAgentPathMacOS()
|
||||||
|
{
|
||||||
|
var homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
var launchAgentPath = Path.Combine(homePath, "Library", "LaunchAgents", $"{Global.AppName}-LaunchAgent.plist");
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(launchAgentPath));
|
||||||
|
return launchAgentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateLaunchAgentPlist()
|
||||||
|
{
|
||||||
|
var exePath = Utils.GetExePath();
|
||||||
|
var appName = Path.GetFileNameWithoutExtension(exePath);
|
||||||
|
return $@"<?xml version=""1.0"" encoding=""UTF-8""?>
|
||||||
|
<!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd"">
|
||||||
|
<plist version=""1.0"">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>{Global.AppName}-LaunchAgent</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>/bin/sh</string>
|
||||||
|
<string>-c</string>
|
||||||
|
<string>if ! pgrep -x ""{appName}"" > /dev/null; then ""{exePath}""; fi</string>
|
||||||
|
</array>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion macOS
|
||||||
|
}
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
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}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
68
v2rayN/ServiceLib/Handler/ConnectionHandler.cs
Normal file
68
v2rayN/ServiceLib/Handler/ConnectionHandler.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
namespace ServiceLib.Handler;
|
||||||
|
|
||||||
|
public static class ConnectionHandler
|
||||||
|
{
|
||||||
|
private static readonly string _tag = "ConnectionHandler";
|
||||||
|
|
||||||
|
public static async Task<string> RunAvailabilityCheck()
|
||||||
|
{
|
||||||
|
var time = await GetRealPingTime();
|
||||||
|
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
|
||||||
|
|
||||||
|
return string.Format(ResUI.TestMeOutput, time, ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string?> GetIPInfo()
|
||||||
|
{
|
||||||
|
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
|
||||||
|
if (url.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadHandle = new DownloadService();
|
||||||
|
var result = await downloadHandle.TryDownloadString(url, true, "");
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipInfo = JsonUtils.Deserialize<IPAPIInfo>(result);
|
||||||
|
if (ipInfo == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query;
|
||||||
|
var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code;
|
||||||
|
|
||||||
|
return $"({country ?? "unknown"}) {ip}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<int> GetRealPingTime()
|
||||||
|
{
|
||||||
|
var responseTime = -1;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
|
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}");
|
||||||
|
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
||||||
|
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
responseTime = await HttpClientHelper.Instance.GetRealPingTime(url, webProxy, 10);
|
||||||
|
if (responseTime > 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,271 +0,0 @@
|
||||||
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 (Utils.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);
|
|
||||||
|
|
||||||
//YAML anchors
|
|
||||||
if (txtFile.Contains("<<:") && txtFile.Contains("*") && txtFile.Contains("&"))
|
|
||||||
{
|
|
||||||
txtFile = YamlUtils.PreprocessYaml(txtFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (Utils.IsNotEmpty(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
135
v2rayN/ServiceLib/Handler/CoreConfigHandler.cs
Normal file
135
v2rayN/ServiceLib/Handler/CoreConfigHandler.cs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
namespace ServiceLib.Handler;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Core configuration file processing class
|
||||||
|
/// </summary>
|
||||||
|
public static class CoreConfigHandler
|
||||||
|
{
|
||||||
|
private static readonly string _tag = "CoreConfigHandler";
|
||||||
|
|
||||||
|
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
|
||||||
|
{
|
||||||
|
var config = AppManager.Instance.Config;
|
||||||
|
var result = new RetResult();
|
||||||
|
|
||||||
|
if (node.ConfigType == EConfigType.Custom)
|
||||||
|
{
|
||||||
|
result = node.CoreType switch
|
||||||
|
{
|
||||||
|
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
|
||||||
|
ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName),
|
||||||
|
_ => await GenerateClientCustomConfig(node, fileName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||||
|
{
|
||||||
|
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node);
|
||||||
|
}
|
||||||
|
if (result.Success != true)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (fileName.IsNotEmpty() && result.Data != null)
|
||||||
|
{
|
||||||
|
await File.WriteAllTextAsync(fileName, result.Data.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node == null || fileName is null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
|
ret.Success = true;
|
||||||
|
return await Task.FromResult(ret);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType)
|
||||||
|
{
|
||||||
|
var result = new RetResult();
|
||||||
|
if (coreType == ECoreType.sing_box)
|
||||||
|
{
|
||||||
|
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds);
|
||||||
|
}
|
||||||
|
else if (coreType == ECoreType.Xray)
|
||||||
|
{
|
||||||
|
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds);
|
||||||
|
}
|
||||||
|
if (result.Success != true)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
await File.WriteAllTextAsync(fileName, result.Data.ToString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
|
||||||
|
{
|
||||||
|
var result = new RetResult();
|
||||||
|
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||||
|
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
|
||||||
|
testItem.Port = port;
|
||||||
|
|
||||||
|
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||||
|
{
|
||||||
|
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port);
|
||||||
|
}
|
||||||
|
if (result.Success != true)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(fileName, result.Data.ToString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,378 +0,0 @@
|
||||||
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.IsNotEmpty(e.Data))
|
|
||||||
{
|
|
||||||
string msg = e.Data + Environment.NewLine;
|
|
||||||
ShowMsg(false, msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
proc.ErrorDataReceived += (sender, e) =>
|
|
||||||
{
|
|
||||||
if (Utils.IsNotEmpty(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,217 +0,0 @@
|
||||||
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("")}\"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,341 +0,0 @@
|
||||||
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, string fileName, 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,
|
|
||||||
fileName,
|
|
||||||
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.IsNotEmpty(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.IsNotEmpty(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.IsNotEmpty(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.IsNotEmpty(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
48
v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs
Normal file
48
v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
|
public class AnytlsFmt : BaseFmt
|
||||||
|
{
|
||||||
|
public static ProfileItem? Resolve(string str, out string msg)
|
||||||
|
{
|
||||||
|
msg = ResUI.ConfigurationFormatIncorrect;
|
||||||
|
|
||||||
|
var parsedUrl = Utils.TryUri(str);
|
||||||
|
if (parsedUrl == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileItem item = new()
|
||||||
|
{
|
||||||
|
ConfigType = EConfigType.Anytls,
|
||||||
|
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
||||||
|
Address = parsedUrl.IdnHost,
|
||||||
|
Port = parsedUrl.Port,
|
||||||
|
};
|
||||||
|
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||||
|
item.Id = rawUserInfo;
|
||||||
|
|
||||||
|
var query = Utils.ParseQueryString(parsedUrl.Query);
|
||||||
|
_ = ResolveStdTransport(query, ref item);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? ToUri(ProfileItem? item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var remark = string.Empty;
|
||||||
|
if (item.Remarks.IsNotEmpty())
|
||||||
|
{
|
||||||
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
|
}
|
||||||
|
var pw = item.Id;
|
||||||
|
var dicQuery = new Dictionary<string, string>();
|
||||||
|
_ = GetStdTransport(item, Global.None, ref dicQuery);
|
||||||
|
|
||||||
|
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,205 +1,251 @@
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
namespace ServiceLib.Handler.Fmt
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
|
public class BaseFmt
|
||||||
{
|
{
|
||||||
public class BaseFmt
|
protected static string GetIpv6(string address)
|
||||||
{
|
{
|
||||||
protected static string GetIpv6(string address)
|
if (Utils.IsIpv6(address))
|
||||||
{
|
{
|
||||||
if (Utils.IsIpv6(address))
|
// Check if the address is already surrounded by square brackets, if not, add square brackets
|
||||||
{
|
return address.StartsWith('[') && address.EndsWith(']') ? address : $"[{address}]";
|
||||||
// 检查地址是否已经被方括号包围,如果没有,则添加方括号
|
|
||||||
return address.StartsWith('[') && address.EndsWith(']') ? address : $"[{address}]";
|
|
||||||
}
|
|
||||||
return address; // 如果不是IPv6地址,直接返回原地址
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
|
|
||||||
{
|
{
|
||||||
if (Utils.IsNotEmpty(item.flow))
|
return address;
|
||||||
{
|
|
||||||
dicQuery.Add("flow", item.flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Utils.IsNotEmpty(item.streamSecurity))
|
|
||||||
{
|
|
||||||
dicQuery.Add("security", item.streamSecurity);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (securityDef != null)
|
|
||||||
{
|
|
||||||
dicQuery.Add("security", securityDef);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(item.sni))
|
|
||||||
{
|
|
||||||
dicQuery.Add("sni", item.sni);
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(item.alpn))
|
|
||||||
{
|
|
||||||
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(item.fingerprint))
|
|
||||||
{
|
|
||||||
dicQuery.Add("fp", Utils.UrlEncode(item.fingerprint));
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(item.publicKey))
|
|
||||||
{
|
|
||||||
dicQuery.Add("pbk", Utils.UrlEncode(item.publicKey));
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(item.shortId))
|
|
||||||
{
|
|
||||||
dicQuery.Add("sid", Utils.UrlEncode(item.shortId));
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(item.spiderX))
|
|
||||||
{
|
|
||||||
dicQuery.Add("spx", Utils.UrlEncode(item.spiderX));
|
|
||||||
}
|
|
||||||
if (item.allowInsecure.Equals("true"))
|
|
||||||
{
|
|
||||||
dicQuery.Add("allowInsecure", "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
dicQuery.Add("type", Utils.IsNotEmpty(item.network) ? item.network : nameof(ETransport.tcp));
|
|
||||||
|
|
||||||
switch (item.network)
|
|
||||||
{
|
|
||||||
case nameof(ETransport.tcp):
|
|
||||||
dicQuery.Add("headerType", Utils.IsNotEmpty(item.headerType) ? item.headerType : Global.None);
|
|
||||||
if (Utils.IsNotEmpty(item.requestHost))
|
|
||||||
{
|
|
||||||
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case nameof(ETransport.kcp):
|
|
||||||
dicQuery.Add("headerType", Utils.IsNotEmpty(item.headerType) ? item.headerType : Global.None);
|
|
||||||
if (Utils.IsNotEmpty(item.path))
|
|
||||||
{
|
|
||||||
dicQuery.Add("seed", Utils.UrlEncode(item.path));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case nameof(ETransport.ws):
|
|
||||||
case nameof(ETransport.httpupgrade):
|
|
||||||
case nameof(ETransport.splithttp):
|
|
||||||
if (Utils.IsNotEmpty(item.requestHost))
|
|
||||||
{
|
|
||||||
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(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.IsNotEmpty(item.requestHost))
|
|
||||||
{
|
|
||||||
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(item.path))
|
|
||||||
{
|
|
||||||
dicQuery.Add("path", Utils.UrlEncode(item.path));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case nameof(ETransport.quic):
|
|
||||||
dicQuery.Add("headerType", Utils.IsNotEmpty(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.IsNotEmpty(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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
|
||||||
|
{
|
||||||
|
if (item.Flow.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("flow", item.Flow);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.StreamSecurity.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("security", item.StreamSecurity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (securityDef != null)
|
||||||
|
{
|
||||||
|
dicQuery.Add("security", securityDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.Sni.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("sni", item.Sni);
|
||||||
|
}
|
||||||
|
if (item.Alpn.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||||
|
}
|
||||||
|
if (item.Fingerprint.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("fp", Utils.UrlEncode(item.Fingerprint));
|
||||||
|
}
|
||||||
|
if (item.PublicKey.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("pbk", Utils.UrlEncode(item.PublicKey));
|
||||||
|
}
|
||||||
|
if (item.ShortId.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("sid", Utils.UrlEncode(item.ShortId));
|
||||||
|
}
|
||||||
|
if (item.SpiderX.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("spx", Utils.UrlEncode(item.SpiderX));
|
||||||
|
}
|
||||||
|
if (item.Mldsa65Verify.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify));
|
||||||
|
}
|
||||||
|
if (item.AllowInsecure.Equals("true"))
|
||||||
|
{
|
||||||
|
dicQuery.Add("allowInsecure", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
|
||||||
|
|
||||||
|
switch (item.Network)
|
||||||
|
{
|
||||||
|
case nameof(ETransport.tcp):
|
||||||
|
dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None);
|
||||||
|
if (item.RequestHost.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("host", Utils.UrlEncode(item.RequestHost));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.kcp):
|
||||||
|
dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None);
|
||||||
|
if (item.Path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("seed", Utils.UrlEncode(item.Path));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.ws):
|
||||||
|
case nameof(ETransport.httpupgrade):
|
||||||
|
if (item.RequestHost.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("host", Utils.UrlEncode(item.RequestHost));
|
||||||
|
}
|
||||||
|
if (item.Path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("path", Utils.UrlEncode(item.Path));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.xhttp):
|
||||||
|
if (item.RequestHost.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("host", Utils.UrlEncode(item.RequestHost));
|
||||||
|
}
|
||||||
|
if (item.Path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("path", Utils.UrlEncode(item.Path));
|
||||||
|
}
|
||||||
|
if (item.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(item.HeaderType))
|
||||||
|
{
|
||||||
|
dicQuery.Add("mode", Utils.UrlEncode(item.HeaderType));
|
||||||
|
}
|
||||||
|
if (item.Extra.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("extra", Utils.UrlEncode(item.Extra));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.http):
|
||||||
|
case nameof(ETransport.h2):
|
||||||
|
dicQuery["type"] = nameof(ETransport.http);
|
||||||
|
if (item.RequestHost.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("host", Utils.UrlEncode(item.RequestHost));
|
||||||
|
}
|
||||||
|
if (item.Path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("path", Utils.UrlEncode(item.Path));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.quic):
|
||||||
|
dicQuery.Add("headerType", item.HeaderType.IsNotEmpty() ? item.HeaderType : Global.None);
|
||||||
|
dicQuery.Add("quicSecurity", Utils.UrlEncode(item.RequestHost));
|
||||||
|
dicQuery.Add("key", Utils.UrlEncode(item.Path));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.grpc):
|
||||||
|
if (item.Path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
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 = GetQueryValue(query, "flow");
|
||||||
|
item.StreamSecurity = GetQueryValue(query, "security");
|
||||||
|
item.Sni = GetQueryValue(query, "sni");
|
||||||
|
item.Alpn = GetQueryDecoded(query, "alpn");
|
||||||
|
item.Fingerprint = GetQueryDecoded(query, "fp");
|
||||||
|
item.PublicKey = GetQueryDecoded(query, "pbk");
|
||||||
|
item.ShortId = GetQueryDecoded(query, "sid");
|
||||||
|
item.SpiderX = GetQueryDecoded(query, "spx");
|
||||||
|
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
|
||||||
|
item.AllowInsecure = new[] { "allowInsecure", "allow_insecure", "insecure" }.Any(k => (query[k] ?? "") == "1") ? "true" : "";
|
||||||
|
|
||||||
|
item.Network = GetQueryValue(query, "type", nameof(ETransport.tcp));
|
||||||
|
switch (item.Network)
|
||||||
|
{
|
||||||
|
case nameof(ETransport.tcp):
|
||||||
|
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
|
||||||
|
item.RequestHost = GetQueryDecoded(query, "host");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.kcp):
|
||||||
|
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
|
||||||
|
item.Path = GetQueryDecoded(query, "seed");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.ws):
|
||||||
|
case nameof(ETransport.httpupgrade):
|
||||||
|
item.RequestHost = GetQueryDecoded(query, "host");
|
||||||
|
item.Path = GetQueryDecoded(query, "path", "/");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.xhttp):
|
||||||
|
item.RequestHost = GetQueryDecoded(query, "host");
|
||||||
|
item.Path = GetQueryDecoded(query, "path", "/");
|
||||||
|
item.HeaderType = GetQueryDecoded(query, "mode");
|
||||||
|
item.Extra = GetQueryDecoded(query, "extra");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.http):
|
||||||
|
case nameof(ETransport.h2):
|
||||||
|
item.Network = nameof(ETransport.h2);
|
||||||
|
item.RequestHost = GetQueryDecoded(query, "host");
|
||||||
|
item.Path = GetQueryDecoded(query, "path", "/");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.quic):
|
||||||
|
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
|
||||||
|
item.RequestHost = GetQueryValue(query, "quicSecurity", Global.None);
|
||||||
|
item.Path = GetQueryDecoded(query, "key");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nameof(ETransport.grpc):
|
||||||
|
item.RequestHost = GetQueryDecoded(query, "authority");
|
||||||
|
item.Path = GetQueryDecoded(query, "serviceName");
|
||||||
|
item.HeaderType = GetQueryDecoded(query, "mode", Global.GrpcGunMode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static bool Contains(string str, params string[] s)
|
||||||
|
{
|
||||||
|
return s.All(item => str.Contains(item, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static string WriteAllText(string strData, string ext = "json")
|
||||||
|
{
|
||||||
|
var fileName = Utils.GetTempPath($"{Utils.GetGuid(false)}.{ext}");
|
||||||
|
File.WriteAllText(fileName, strData);
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static string ToUri(EConfigType eConfigType, string address, object port, string userInfo, Dictionary<string, string>? dicQuery, string? remark)
|
||||||
|
{
|
||||||
|
var query = dicQuery != null
|
||||||
|
? ("?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray()))
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
var url = $"{Utils.UrlEncode(userInfo)}@{GetIpv6(address)}:{port}";
|
||||||
|
return $"{Global.ProtocolShares[eConfigType]}{url}{query}{remark}";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static string GetQueryValue(NameValueCollection query, string key, string defaultValue = "")
|
||||||
|
{
|
||||||
|
return query[key] ?? defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static string GetQueryDecoded(NameValueCollection query, string key, string defaultValue = "")
|
||||||
|
{
|
||||||
|
return Utils.UrlDecode(GetQueryValue(query, key, defaultValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,22 @@
|
||||||
namespace ServiceLib.Handler.Fmt
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
|
public class ClashFmt : BaseFmt
|
||||||
{
|
{
|
||||||
public class ClashFmt : BaseFmt
|
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||||
{
|
{
|
||||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
if (Contains(strData, "rules", "-port", "proxies"))
|
||||||
{
|
{
|
||||||
if (Contains(strData, "port", "socks-port", "proxies"))
|
var fileName = WriteAllText(strData, "yaml");
|
||||||
|
|
||||||
|
var profileItem = new ProfileItem
|
||||||
{
|
{
|
||||||
var fileName = WriteAllText(strData, "yaml");
|
CoreType = ECoreType.mihomo,
|
||||||
|
Address = fileName,
|
||||||
var profileItem = new ProfileItem
|
Remarks = subRemarks ?? "clash_custom"
|
||||||
{
|
};
|
||||||
coreType = ECoreType.mihomo,
|
return profileItem;
|
||||||
address = fileName,
|
|
||||||
remarks = subRemarks ?? "clash_custom"
|
|
||||||
};
|
|
||||||
return profileItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,90 +1,96 @@
|
||||||
namespace ServiceLib.Handler.Fmt
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
|
public class FmtHandler
|
||||||
{
|
{
|
||||||
public class FmtHandler
|
private static readonly string _tag = "FmtHandler";
|
||||||
|
|
||||||
|
public static string? GetShareUri(ProfileItem item)
|
||||||
{
|
{
|
||||||
public static string? GetShareUri(ProfileItem item)
|
try
|
||||||
{
|
{
|
||||||
try
|
var url = item.ConfigType switch
|
||||||
{
|
{
|
||||||
var url = item.configType switch
|
EConfigType.VMess => VmessFmt.ToUri(item),
|
||||||
{
|
EConfigType.Shadowsocks => ShadowsocksFmt.ToUri(item),
|
||||||
EConfigType.VMess => VmessFmt.ToUri(item),
|
EConfigType.SOCKS => SocksFmt.ToUri(item),
|
||||||
EConfigType.Shadowsocks => ShadowsocksFmt.ToUri(item),
|
EConfigType.Trojan => TrojanFmt.ToUri(item),
|
||||||
EConfigType.Socks => SocksFmt.ToUri(item),
|
EConfigType.VLESS => VLESSFmt.ToUri(item),
|
||||||
EConfigType.Trojan => TrojanFmt.ToUri(item),
|
EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item),
|
||||||
EConfigType.VLESS => VLESSFmt.ToUri(item),
|
EConfigType.TUIC => TuicFmt.ToUri(item),
|
||||||
EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item),
|
EConfigType.WireGuard => WireguardFmt.ToUri(item),
|
||||||
EConfigType.Tuic => TuicFmt.ToUri(item),
|
EConfigType.Anytls => AnytlsFmt.ToUri(item),
|
||||||
EConfigType.Wireguard => WireguardFmt.ToUri(item),
|
_ => null,
|
||||||
_ => null,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(ex.Message, ex);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public static ProfileItem? ResolveConfig(string config, out string msg)
|
|
||||||
{
|
{
|
||||||
msg = ResUI.ConfigurationFormatIncorrect;
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
public static ProfileItem? ResolveConfig(string config, out string msg)
|
||||||
|
{
|
||||||
|
msg = ResUI.ConfigurationFormatIncorrect;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string str = config.TrimEx();
|
||||||
|
if (str.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
string str = config.TrimEx();
|
msg = ResUI.FailedReadConfiguration;
|
||||||
if (Utils.IsNullOrEmpty(str))
|
return null;
|
||||||
{
|
|
||||||
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)
|
|
||||||
|
if (str.StartsWith(Global.ProtocolShares[EConfigType.VMess]))
|
||||||
{
|
{
|
||||||
Logging.SaveLog(ex.Message, ex);
|
return VmessFmt.Resolve(str, out msg);
|
||||||
msg = ResUI.Incorrectconfiguration;
|
}
|
||||||
|
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 if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls]))
|
||||||
|
{
|
||||||
|
return AnytlsFmt.Resolve(str, out msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msg = ResUI.NonvmessOrssProtocol;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
msg = ResUI.Incorrectconfiguration;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
v2rayN/ServiceLib/Handler/Fmt/HtmlPageFmt.cs
Normal file
9
v2rayN/ServiceLib/Handler/Fmt/HtmlPageFmt.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
|
public class HtmlPageFmt : BaseFmt
|
||||||
|
{
|
||||||
|
public static bool IsHtmlPage(string strData)
|
||||||
|
{
|
||||||
|
return Contains(strData, "<html", "<!doctype html", "<head");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,100 +1,83 @@
|
||||||
namespace ServiceLib.Handler.Fmt
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
|
public class Hysteria2Fmt : BaseFmt
|
||||||
{
|
{
|
||||||
public class Hysteria2Fmt : BaseFmt
|
public static ProfileItem? Resolve(string str, out string msg)
|
||||||
{
|
{
|
||||||
public static ProfileItem? Resolve(string str, out string msg)
|
msg = ResUI.ConfigurationFormatIncorrect;
|
||||||
|
ProfileItem item = new()
|
||||||
{
|
{
|
||||||
msg = ResUI.ConfigurationFormatIncorrect;
|
ConfigType = EConfigType.Hysteria2
|
||||||
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.IsNotEmpty(item.remarks))
|
|
||||||
{
|
|
||||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
|
||||||
}
|
|
||||||
var dicQuery = new Dictionary<string, string>();
|
|
||||||
if (Utils.IsNotEmpty(item.sni))
|
|
||||||
{
|
|
||||||
dicQuery.Add("sni", item.sni);
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(item.alpn))
|
|
||||||
{
|
|
||||||
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
|
|
||||||
}
|
|
||||||
if (Utils.IsNotEmpty(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var url = Utils.TryUri(str);
|
||||||
|
if (url == null)
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
public static ProfileItem? ResolveFull2(string strData, string? subRemarks)
|
item.Address = url.IdnHost;
|
||||||
{
|
item.Port = url.Port;
|
||||||
if (Contains(strData, "server", "auth", "up", "down", "listen"))
|
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||||
{
|
item.Id = Utils.UrlDecode(url.UserInfo);
|
||||||
var fileName = WriteAllText(strData);
|
|
||||||
|
|
||||||
var profileItem = new ProfileItem
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
{
|
ResolveStdTransport(query, ref item);
|
||||||
coreType = ECoreType.hysteria2,
|
item.Path = GetQueryDecoded(query, "obfs-password");
|
||||||
address = fileName,
|
item.AllowInsecure = GetQueryValue(query, "insecure") == "1" ? "true" : "false";
|
||||||
remarks = subRemarks ?? "hysteria2_custom"
|
|
||||||
};
|
|
||||||
return profileItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
item.Ports = GetQueryDecoded(query, "mport");
|
||||||
}
|
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static string? ToUri(ProfileItem? item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
return null;
|
||||||
|
string url = string.Empty;
|
||||||
|
|
||||||
|
string remark = string.Empty;
|
||||||
|
if (item.Remarks.IsNotEmpty())
|
||||||
|
{
|
||||||
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
|
}
|
||||||
|
var dicQuery = new Dictionary<string, string>();
|
||||||
|
if (item.Sni.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("sni", item.Sni);
|
||||||
|
}
|
||||||
|
if (item.Alpn.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||||
|
}
|
||||||
|
if (item.Path.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("obfs", "salamander");
|
||||||
|
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path));
|
||||||
|
}
|
||||||
|
dicQuery.Add("insecure", item.AllowInsecure.ToLower() == "true" ? "1" : "0");
|
||||||
|
if (item.Ports.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,180 +1,177 @@
|
||||||
using System.Text.RegularExpressions;
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
namespace ServiceLib.Handler.Fmt
|
public class ShadowsocksFmt : BaseFmt
|
||||||
{
|
{
|
||||||
public class ShadowsocksFmt : BaseFmt
|
public static ProfileItem? Resolve(string str, out string msg)
|
||||||
{
|
{
|
||||||
public static ProfileItem? Resolve(string str, out string msg)
|
msg = ResUI.ConfigurationFormatIncorrect;
|
||||||
|
ProfileItem? item;
|
||||||
|
|
||||||
|
item = ResolveSSLegacy(str) ?? ResolveSip002(str);
|
||||||
|
if (item == null)
|
||||||
{
|
{
|
||||||
msg = ResUI.ConfigurationFormatIncorrect;
|
return null;
|
||||||
ProfileItem? item;
|
}
|
||||||
|
if (item.Address.Length == 0 || item.Port == 0 || item.Security.Length == 0 || item.Id.Length == 0)
|
||||||
item = ResolveSSLegacy(str) ?? ResolveSip002(str);
|
{
|
||||||
if (item == null)
|
return 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)
|
item.ConfigType = EConfigType.Shadowsocks;
|
||||||
{
|
|
||||||
if (item == null) return null;
|
|
||||||
string url = string.Empty;
|
|
||||||
|
|
||||||
string remark = string.Empty;
|
return item;
|
||||||
if (Utils.IsNotEmpty(item.remarks))
|
}
|
||||||
{
|
|
||||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
public static string? ToUri(ProfileItem? item)
|
||||||
}
|
{
|
||||||
//url = string.Format("{0}:{1}@{2}:{3}",
|
if (item == null)
|
||||||
// item.security,
|
{
|
||||||
// item.id,
|
return null;
|
||||||
// item.address,
|
}
|
||||||
// item.port);
|
var remark = string.Empty;
|
||||||
//url = Utile.Base64Encode(url);
|
if (item.Remarks.IsNotEmpty())
|
||||||
//new Sip002
|
{
|
||||||
var pw = Utils.Base64Encode($"{item.security}:{item.id}");
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
url = $"{pw}@{GetIpv6(item.address)}:{item.port}";
|
}
|
||||||
url = $"{Global.ProtocolShares[EConfigType.Shadowsocks]}{url}{remark}";
|
//url = string.Format("{0}:{1}@{2}:{3}",
|
||||||
return url;
|
// item.security,
|
||||||
|
// item.id,
|
||||||
|
// item.address,
|
||||||
|
// item.port);
|
||||||
|
//url = Utile.Base64Encode(url);
|
||||||
|
//new Sip002
|
||||||
|
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
|
||||||
|
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
ProfileItem item = new();
|
||||||
private static readonly Regex DetailsParser = new(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
var base64 = match.Groups["base64"].Value.TrimEnd('/');
|
||||||
|
var tag = match.Groups["tag"].Value;
|
||||||
private static ProfileItem? ResolveSSLegacy(string result)
|
if (tag.IsNotEmpty())
|
||||||
{
|
{
|
||||||
var match = UrlFinder.Match(result);
|
item.Remarks = Utils.UrlDecode(tag);
|
||||||
if (!match.Success)
|
}
|
||||||
return null;
|
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 = details.Groups["port"].Value.ToInt();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
ProfileItem item = new();
|
private static ProfileItem? ResolveSip002(string result)
|
||||||
var base64 = match.Groups["base64"].Value.TrimEnd('/');
|
{
|
||||||
var tag = match.Groups["tag"].Value;
|
var parsedUrl = Utils.TryUri(result);
|
||||||
if (Utils.IsNotEmpty(tag))
|
if (parsedUrl == null)
|
||||||
{
|
{
|
||||||
item.remarks = Utils.UrlDecode(tag);
|
return null;
|
||||||
}
|
|
||||||
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)
|
ProfileItem item = new()
|
||||||
{
|
{
|
||||||
Uri parsedUrl;
|
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
||||||
try
|
Address = parsedUrl.IdnHost,
|
||||||
{
|
Port = parsedUrl.Port,
|
||||||
parsedUrl = new Uri(result);
|
};
|
||||||
}
|
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||||
catch (UriFormatException)
|
//2022-blake3
|
||||||
|
if (rawUserInfo.Contains(':'))
|
||||||
|
{
|
||||||
|
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
|
||||||
|
if (userInfoParts.Length != 2)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
ProfileItem item = new()
|
item.Security = userInfoParts.First();
|
||||||
|
item.Id = Utils.UrlDecode(userInfoParts.Last());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// parse base64 UserInfo
|
||||||
|
var userInfo = Utils.Base64Decode(rawUserInfo);
|
||||||
|
var userInfoParts = userInfo.Split(new[] { ':' }, 2);
|
||||||
|
if (userInfoParts.Length != 2)
|
||||||
{
|
{
|
||||||
remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
return null;
|
||||||
address = parsedUrl.IdnHost,
|
}
|
||||||
port = parsedUrl.Port,
|
item.Security = userInfoParts.First();
|
||||||
};
|
item.Id = userInfoParts.Last();
|
||||||
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.UriEscaped);
|
}
|
||||||
//2022-blake3
|
|
||||||
if (rawUserInfo.Contains(':'))
|
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") && obfsHost.IsNotEmpty())
|
||||||
{
|
{
|
||||||
string[] userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
|
obfsHost = obfsHost?.Replace("obfs-host=", "");
|
||||||
if (userInfoParts.Length != 2)
|
item.Network = Global.DefaultNetwork;
|
||||||
{
|
item.HeaderType = Global.TcpHeaderHttp;
|
||||||
return null;
|
item.RequestHost = obfsHost ?? "";
|
||||||
}
|
|
||||||
item.security = userInfoParts[0];
|
|
||||||
item.id = Utils.UrlDecode(userInfoParts[1]);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// parse base64 UserInfo
|
return null;
|
||||||
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.IsNotEmpty(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)
|
return item;
|
||||||
{
|
|
||||||
//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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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 = it.server_port.ToInt()
|
||||||
|
};
|
||||||
|
lst.Add(ssItem);
|
||||||
|
}
|
||||||
|
return lst;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,47 @@
|
||||||
namespace ServiceLib.Handler.Fmt
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
|
public class SingboxFmt : BaseFmt
|
||||||
{
|
{
|
||||||
public class SingboxFmt : BaseFmt
|
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
||||||
{
|
{
|
||||||
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
var configObjects = JsonUtils.Deserialize<object[]>(strData);
|
||||||
|
if (configObjects is not { Length: > 0 })
|
||||||
{
|
{
|
||||||
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
List<ProfileItem> lstResult = [];
|
||||||
|
foreach (var configObject in configObjects)
|
||||||
{
|
{
|
||||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(strData);
|
var objectString = JsonUtils.Serialize(configObject);
|
||||||
if (singboxConfig?.inbounds?.Count > 0
|
var profileIt = ResolveFull(objectString, subRemarks);
|
||||||
&& singboxConfig.outbounds?.Count > 0
|
if (profileIt != null)
|
||||||
&& singboxConfig.route != null)
|
|
||||||
{
|
{
|
||||||
var fileName = WriteAllText(strData);
|
lstResult.Add(profileIt);
|
||||||
var profileItem = new ProfileItem
|
|
||||||
{
|
|
||||||
coreType = ECoreType.sing_box,
|
|
||||||
address = fileName,
|
|
||||||
remarks = subRemarks ?? "singbox_custom"
|
|
||||||
};
|
|
||||||
|
|
||||||
return profileItem;
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return lstResult;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||||
|
{
|
||||||
|
var config = JsonUtils.ParseJson(strData);
|
||||||
|
if (config?["inbounds"] == null
|
||||||
|
|| config["outbounds"] == null
|
||||||
|
|| config["route"] == null
|
||||||
|
|| config["dns"] == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = WriteAllText(strData);
|
||||||
|
var profileItem = new ProfileItem
|
||||||
|
{
|
||||||
|
CoreType = ECoreType.sing_box,
|
||||||
|
Address = fileName,
|
||||||
|
Remarks = subRemarks ?? "singbox_custom"
|
||||||
|
};
|
||||||
|
|
||||||
|
return profileItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue