mirror of
https://github.com/2dust/v2rayN.git
synced 2025-10-26 18:24:43 +00:00
Compare commits
1136 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22c233f0cd | ||
|
|
b2d6282755 | ||
|
|
c8d89e3dce | ||
|
|
d3b1810eab | ||
|
|
51409a3e28 | ||
|
|
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 | ||
|
|
a7f3a7b1a7 | ||
|
|
71eb5f0813 | ||
|
|
b4f50258a7 | ||
|
|
346a9c5fcc | ||
|
|
61635db5b5 | ||
|
|
4c0a59a715 | ||
|
|
aa829a66ea | ||
|
|
885f193a00 | ||
|
|
0a9bbf526d | ||
|
|
ff6716b39d | ||
|
|
8505f2db96 | ||
|
|
233d605256 | ||
|
|
c4e01d20a0 | ||
|
|
07156cb55c | ||
|
|
8361b4e4a0 | ||
|
|
bb90671979 | ||
|
|
beddc71ed8 | ||
|
|
d5f1cc99ac | ||
|
|
d03a86292e | ||
|
|
c061a24948 | ||
|
|
9c7446f820 | ||
|
|
8be1730719 | ||
|
|
7ceaf5c071 | ||
|
|
7901a59aa4 | ||
|
|
8343a1002f | ||
|
|
c83dce3cc0 | ||
|
|
14afeab2bb | ||
|
|
c22b57927c | ||
|
|
81b84d235c | ||
|
|
488e8aab00 | ||
|
|
fc43a9a726 | ||
|
|
31947fdcb3 | ||
|
|
34c7963d28 | ||
|
|
d91b0afb9a | ||
|
|
82eb3fd6bd | ||
|
|
3d3d3f83df | ||
|
|
6879c75bc8 | ||
|
|
b02ad6cdab | ||
|
|
f7b16952ea | ||
|
|
f0d05a7d4e | ||
|
|
d6ca317b20 | ||
|
|
2879fddfd9 | ||
|
|
b6c09470fc | ||
|
|
9fdf6c6c32 | ||
|
|
79085af994 | ||
|
|
d189f4b443 | ||
|
|
c9d65e5cd9 | ||
|
|
639d62588a | ||
|
|
6c9db51fd5 | ||
|
|
f0dbb6b22c | ||
|
|
f10f7b6268 | ||
|
|
48a5cbc552 | ||
|
|
6721d150e0 | ||
|
|
54c16cad7d | ||
|
|
ae3ab15245 | ||
|
|
d3c0f50fec | ||
|
|
43753b1b7a | ||
|
|
6f3e4b3682 | ||
|
|
b57cdd31bd | ||
|
|
b936c194e4 | ||
|
|
064431421a | ||
|
|
9d49c7aad0 | ||
|
|
a6f27e5071 | ||
|
|
8d1d10b783 | ||
|
|
61bea05f63 | ||
|
|
bbe7c7b884 | ||
|
|
a432852b78 | ||
|
|
770e8b8cfa | ||
|
|
7faabdc375 | ||
|
|
a9860418ba | ||
|
|
30ff9d0ea9 | ||
|
|
bbc2298939 | ||
|
|
3286e8e24d | ||
|
|
372f3991e1 | ||
|
|
9aa5c0d135 | ||
|
|
9e9808e489 | ||
|
|
3dd75b17cf | ||
|
|
2504b4737b | ||
|
|
8ff04dca0d | ||
|
|
d893ee4829 | ||
|
|
dfc5ec0705 | ||
|
|
8023eb74c9 | ||
|
|
bb4d3997ad | ||
|
|
7ede3af762 | ||
|
|
97ea1c7a9e | ||
|
|
43bb2c0fb8 | ||
|
|
6e7196bb27 | ||
|
|
87a71d58e8 | ||
|
|
32ffd43fe3 | ||
|
|
6500c8d85e | ||
|
|
9866d436da | ||
|
|
0f4884d9d8 | ||
|
|
5a81441351 | ||
|
|
e8721bfb6b | ||
|
|
1b09d95209 | ||
|
|
35f3b5a50e | ||
|
|
ff5203a561 | ||
|
|
c3c1ced309 | ||
|
|
08b1c8ec83 | ||
|
|
1dd3ee4e0a | ||
|
|
06d0c6517d | ||
|
|
4938ce6364 | ||
|
|
25f3fc354f | ||
|
|
ad1a1f8015 | ||
|
|
5c070e2ca8 | ||
|
|
70ea21fca2 | ||
|
|
bc3593871b | ||
|
|
355a424be2 | ||
|
|
0ffa9a0cc8 | ||
|
|
72fecb2b9a | ||
|
|
4e9dfe5478 | ||
|
|
c79e2e3ad4 | ||
|
|
7c33c1c322 | ||
|
|
ea32f75925 | ||
|
|
7eaed21b9a | ||
|
|
cfe8fcd28d | ||
|
|
9f4718af70 | ||
|
|
39faccfd0b | ||
|
|
7545763dae | ||
|
|
c2928be35d | ||
|
|
449bb40d48 | ||
|
|
4caf1a1e63 | ||
|
|
01b205e6f1 | ||
|
|
160d3843a6 | ||
|
|
5efb784115 | ||
|
|
acde5b8384 | ||
|
|
373ee6586a | ||
|
|
215308c329 | ||
|
|
478cadba5e | ||
|
|
f4af4da791 | ||
|
|
22d4f435de | ||
|
|
ccb8cd5c04 | ||
|
|
e963f9e349 | ||
|
|
e3c2a4b8da | ||
|
|
324f46cdad | ||
|
|
691b19b5fd | ||
|
|
63ed2311dc | ||
|
|
d59e59ca40 | ||
|
|
c9a278b4a2 | ||
|
|
8ee5304148 | ||
|
|
3beaa8145f | ||
|
|
e30c55a0af | ||
|
|
b583590a64 | ||
|
|
a28c63168a | ||
|
|
620422350f | ||
|
|
b77cc3c33b | ||
|
|
71bf9b4887 | ||
|
|
9039d401da | ||
|
|
74a0a93201 | ||
|
|
522571f0b3 | ||
|
|
123c49c22d | ||
|
|
1ff88d29be | ||
|
|
63d5a2a1be | ||
|
|
1e9a6cb06b | ||
|
|
31748aa660 | ||
|
|
d0c6ea6a63 | ||
|
|
7ffe286a56 | ||
|
|
b99b30163b | ||
|
|
83b4f1e660 | ||
|
|
b57ba6a98b | ||
|
|
d748e6eff4 | ||
|
|
315d4b75b2 | ||
|
|
31267cbc33 | ||
|
|
c23379b3b6 | ||
|
|
568144d6a2 | ||
|
|
5857042963 | ||
|
|
28e41dc621 | ||
|
|
baef3b364b | ||
|
|
4b9ddb803f | ||
|
|
c07c7ad82f | ||
|
|
e223b80b95 | ||
|
|
5fe468fa1b | ||
|
|
60068d8d16 | ||
|
|
b6c5b46afe | ||
|
|
124cbfadb4 | ||
|
|
8d21f9b900 | ||
|
|
398dbbd2e5 | ||
|
|
e9b392d1c0 | ||
|
|
9d7c7e3225 | ||
|
|
855fd4f0b7 | ||
|
|
807839929d | ||
|
|
870955fee1 |
461 changed files with 57047 additions and 25626 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]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### 报告 Bug 前请务必确认以下事项:
|
||||
> ** **
|
||||
> **✓ 已自行排除服务器端问题。**
|
||||
> **✓ 已升级到最新客户端版本。**
|
||||
> **✓ 已通过搜索确认没有人提出过相同问题。**
|
||||
> **✓ 已确认自己的电脑系统环境是受支持的。**
|
||||
|
||||
---
|
||||
|
||||
- type: input
|
||||
id: "os-version"
|
||||
attributes:
|
||||
label: "操作系统和版本"
|
||||
description: "操作系统和版本"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: "expectation"
|
||||
attributes:
|
||||
|
|
@ -10,6 +30,7 @@ body:
|
|||
description: "描述你认为应该发生什么"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: "describe-the-bug"
|
||||
attributes:
|
||||
|
|
@ -17,22 +38,34 @@ body:
|
|||
description: "描述实际发生了什么"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: "reproduction-method"
|
||||
attributes:
|
||||
label: "复现方法"
|
||||
description: "在BUG出现前执行了哪些操作"
|
||||
placeholder: 标序号
|
||||
placeholder: "标序号"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: "log"
|
||||
id: "gui-log"
|
||||
attributes:
|
||||
label: "日志信息"
|
||||
label: "软件日志"
|
||||
description: "位置在软件当前目录下的guiLogs"
|
||||
placeholder: 在日志开始和结束位置粘贴冒号后的内容:```
|
||||
placeholder: "在日志开始和结束位置粘贴冒号后的内容到这:"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: "core-log"
|
||||
attributes:
|
||||
label: "内核日志"
|
||||
description: "位置在软件主界面的信息框内"
|
||||
placeholder: "在信息框内鼠标右键复制全部信息粘贴在这:"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: "more"
|
||||
attributes:
|
||||
|
|
@ -40,6 +73,7 @@ body:
|
|||
description: "可选"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: "latest-version"
|
||||
attributes:
|
||||
|
|
@ -48,6 +82,7 @@ body:
|
|||
options:
|
||||
- label: 是
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: "issues"
|
||||
attributes:
|
||||
|
|
@ -56,3 +91,12 @@ body:
|
|||
options:
|
||||
- label: 是
|
||||
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 }}\"
|
||||
}
|
||||
}"
|
||||
120
.github/workflows/build-linux.yml
vendored
Normal file
120
.github/workflows/build-linux.yml
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
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
|
||||
|
||||
# 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 @@
|
|||
################################################################################
|
||||
# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。
|
||||
################################################################################
|
||||
## 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/main/VisualStudio.gitignore
|
||||
|
||||
/v2rayN/.vs/
|
||||
/v2rayN/v2rayN/bin/Debug/app.publish
|
||||
/v2rayN/v2rayN/bin/Debug
|
||||
/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-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.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.
|
||||
|
||||
<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
|
||||
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
|
||||
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 is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -1,23 +1,18 @@
|
|||
# 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://www.codefactor.io/repository/github/2dust/v2rayn)
|
||||
[](https://github.com/2dust/v2rayN/releases)
|
||||
[](https://t.me/v2rayn)
|
||||
|
||||
|
||||
## How to use
|
||||
- If you are new to this, please download v2rayN-With-Core.zip from [releases](https://github.com/2dust/v2rayN/releases)
|
||||
- Otherwise please download v2rayN.zip (you will also need to download cores in the bin directory)
|
||||
- Run v2rayN.exe
|
||||
|
||||
## Requirements
|
||||
- (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
|
||||
|
||||
[github_2dust](https://t.me/github_2dust)
|
||||
|
|
|
|||
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: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1)
|
||||
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
|
||||
813
package-rhel.sh
Normal file
813
package-rhel.sh
Normal file
|
|
@ -0,0 +1,813 @@
|
|||
#!/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: freetype, cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.34
|
||||
Requires: fontconfig >= 2.13.1
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 8.32
|
||||
Requires: bash >= 5.1
|
||||
|
||||
%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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
|
|
@ -60,6 +60,7 @@
|
|||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
|
|
@ -68,9 +69,10 @@
|
|||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
|
|
@ -85,9 +87,10 @@
|
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
|
|
@ -109,9 +112,45 @@
|
|||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<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>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
|
@ -117,8 +117,40 @@
|
|||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="pac" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>Resources\pac.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312</value>
|
||||
<data 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>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
|
@ -117,4 +117,40 @@
|
|||
<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="ReactiveUI.Avalonia" 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="22.2.1" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="22.2.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.26.1" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.62.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.62.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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
7
v2rayN/ServiceLib/Base/MyReactiveObject.cs
Normal file
7
v2rayN/ServiceLib/Base/MyReactiveObject.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace ServiceLib.Base;
|
||||
|
||||
public class MyReactiveObject : ReactiveObject
|
||||
{
|
||||
protected static Config? _config;
|
||||
protected Func<EViewAction, object?, Task<bool>>? _updateView;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
249
v2rayN/ServiceLib/Common/FileManager.cs
Normal file
249
v2rayN/ServiceLib/Common/FileManager.cs
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
using System.Formats.Tar;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace ServiceLib.Common;
|
||||
|
||||
public static class FileManager
|
||||
{
|
||||
private static readonly string _tag = "FileManager";
|
||||
|
||||
public static bool ByteArrayToFile(string fileName, byte[] content)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(fileName, content);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void DecompressFile(string fileName, byte[] content)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fs = File.Create(fileName);
|
||||
using GZipStream input = new(new MemoryStream(content), CompressionMode.Decompress, false);
|
||||
input.CopyTo(fs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DecompressFile(string fileName, string toPath, string? toName)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileInfo fileInfo = new(fileName);
|
||||
using var originalFileStream = fileInfo.OpenRead();
|
||||
using var 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(_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)
|
||||
{
|
||||
if (entry.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (ignoredName.IsNotEmpty() && entry.Name.Contains(ignoredName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
entry.ExtractToFile(Path.Combine(toPath, entry.Name), true);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<string>? GetFilesFromZip(string fileName)
|
||||
{
|
||||
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))
|
||||
{
|
||||
File.Delete(destinationArchiveFileName);
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
// 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())
|
||||
{
|
||||
if (ignoredName.IsNotEmpty() && file.Name.Contains(ignoredName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (file.Extension == file.Name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||
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;
|
||||
}
|
||||
}
|
||||
146
v2rayN/ServiceLib/Common/JsonUtils.cs
Normal file
146
v2rayN/ServiceLib/Common/JsonUtils.cs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
namespace ServiceLib.Common;
|
||||
|
||||
public class JsonUtils
|
||||
{
|
||||
private static readonly string _tag = "JsonUtils";
|
||||
|
||||
private static readonly JsonSerializerOptions _defaultDeserializeOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
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
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(strJson))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return JsonSerializer.Deserialize<T>(strJson, _defaultDeserializeOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// parse
|
||||
/// </summary>
|
||||
/// <param name="strJson"></param>
|
||||
/// <returns></returns>
|
||||
public static JsonNode? ParseJson(string strJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(strJson))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonNode.Parse(strJson, 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);
|
||||
}
|
||||
}
|
||||
55
v2rayN/ServiceLib/Common/Logging.cs
Normal file
55
v2rayN/ServiceLib/Common/Logging.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
|
||||
namespace ServiceLib.Common;
|
||||
|
||||
public class Logging
|
||||
{
|
||||
private static readonly Logger _logger1 = LogManager.GetLogger("Log1");
|
||||
private static readonly Logger _logger2 = LogManager.GetLogger("Log2");
|
||||
|
||||
public static void Setup()
|
||||
{
|
||||
LoggingConfiguration config = new();
|
||||
FileTarget fileTarget = new();
|
||||
config.AddTarget("file", fileTarget);
|
||||
fileTarget.Layout = "${longdate}-${level:uppercase=true} ${message}";
|
||||
fileTarget.FileName = Utils.GetLogPath("${shortdate}.txt");
|
||||
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
|
||||
LogManager.Configuration = config;
|
||||
}
|
||||
|
||||
public static void LoggingEnabled(bool enable)
|
||||
{
|
||||
if (!enable)
|
||||
{
|
||||
LogManager.SuspendLogging();
|
||||
}
|
||||
}
|
||||
|
||||
public static void 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
186
v2rayN/ServiceLib/Common/SemanticVersion.cs
Normal file
186
v2rayN/ServiceLib/Common/SemanticVersion.cs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
namespace ServiceLib.Common;
|
||||
|
||||
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)
|
||||
{
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.patch = patch;
|
||||
version = $"{major}.{minor}.{patch}";
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
1091
v2rayN/ServiceLib/Common/Utils.cs
Normal file
1091
v2rayN/ServiceLib/Common/Utils.cs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,41 +1,42 @@
|
|||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace v2rayN
|
||||
{
|
||||
namespace ServiceLib.Common;
|
||||
/*
|
||||
* See:
|
||||
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
|
||||
*/
|
||||
|
||||
public class Job : IDisposable
|
||||
public sealed class WindowsJob : IDisposable
|
||||
{
|
||||
private IntPtr handle = IntPtr.Zero;
|
||||
|
||||
public Job()
|
||||
public WindowsJob()
|
||||
{
|
||||
handle = CreateJobObject(IntPtr.Zero, null);
|
||||
IntPtr extendedInfoPtr = IntPtr.Zero;
|
||||
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new()
|
||||
var extendedInfoPtr = IntPtr.Zero;
|
||||
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||
{
|
||||
LimitFlags = 0x2000
|
||||
};
|
||||
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new()
|
||||
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
||||
{
|
||||
BasicLimitInformation = info
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
||||
var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
||||
extendedInfoPtr = Marshal.AllocHGlobal(length);
|
||||
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
|
||||
|
||||
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
|
||||
(uint)length))
|
||||
{
|
||||
throw new Exception(string.Format("Unable to set information. Error: {0}",
|
||||
Marshal.GetLastWin32Error()));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -48,7 +49,7 @@ namespace v2rayN
|
|||
|
||||
public bool AddProcess(IntPtr processHandle)
|
||||
{
|
||||
bool succ = AssignProcessToJobObject(handle, processHandle);
|
||||
var succ = AssignProcessToJobObject(handle, processHandle);
|
||||
|
||||
if (!succ)
|
||||
{
|
||||
|
|
@ -73,9 +74,12 @@ namespace v2rayN
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposed) return;
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
disposed = true;
|
||||
|
||||
if (disposing)
|
||||
|
|
@ -90,7 +94,7 @@ namespace v2rayN
|
|||
}
|
||||
}
|
||||
|
||||
~Job()
|
||||
~WindowsJob()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
|
@ -103,7 +107,7 @@ namespace v2rayN
|
|||
private static extern IntPtr CreateJobObject(IntPtr a, string? lpName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);
|
||||
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
||||
|
|
@ -120,34 +124,34 @@ namespace v2rayN
|
|||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct IO_COUNTERS
|
||||
{
|
||||
public UInt64 ReadOperationCount;
|
||||
public UInt64 WriteOperationCount;
|
||||
public UInt64 OtherOperationCount;
|
||||
public UInt64 ReadTransferCount;
|
||||
public UInt64 WriteTransferCount;
|
||||
public UInt64 OtherTransferCount;
|
||||
public ulong ReadOperationCount;
|
||||
public ulong WriteOperationCount;
|
||||
public ulong OtherOperationCount;
|
||||
public ulong ReadTransferCount;
|
||||
public ulong WriteTransferCount;
|
||||
public ulong OtherTransferCount;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||
{
|
||||
public Int64 PerProcessUserTimeLimit;
|
||||
public Int64 PerJobUserTimeLimit;
|
||||
public UInt32 LimitFlags;
|
||||
public long PerProcessUserTimeLimit;
|
||||
public long PerJobUserTimeLimit;
|
||||
public uint LimitFlags;
|
||||
public UIntPtr MinimumWorkingSetSize;
|
||||
public UIntPtr MaximumWorkingSetSize;
|
||||
public UInt32 ActiveProcessLimit;
|
||||
public uint ActiveProcessLimit;
|
||||
public UIntPtr Affinity;
|
||||
public UInt32 PriorityClass;
|
||||
public UInt32 SchedulingClass;
|
||||
public uint PriorityClass;
|
||||
public uint SchedulingClass;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SECURITY_ATTRIBUTES
|
||||
{
|
||||
public UInt32 nLength;
|
||||
public uint nLength;
|
||||
public IntPtr lpSecurityDescriptor;
|
||||
public Int32 bInheritHandle;
|
||||
public int bInheritHandle;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
|
@ -173,4 +177,4 @@ namespace v2rayN
|
|||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
79
v2rayN/ServiceLib/Common/YamlUtils.cs
Normal file
79
v2rayN/ServiceLib/Common/YamlUtils.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace ServiceLib.Common;
|
||||
|
||||
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)
|
||||
{
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
var obj = deserializer.Deserialize<T>(str);
|
||||
return obj;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
return deserializer.Deserialize<T>("");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToYaml(object? obj)
|
||||
{
|
||||
var result = string.Empty;
|
||||
if (obj == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(HyphenatedNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
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
|
||||
}
|
||||
18
v2rayN/ServiceLib/Enums/EConfigType.cs
Normal file
18
v2rayN/ServiceLib/Enums/EConfigType.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum EConfigType
|
||||
{
|
||||
VMess = 1,
|
||||
Custom = 2,
|
||||
Shadowsocks = 3,
|
||||
SOCKS = 4,
|
||||
VLESS = 5,
|
||||
Trojan = 6,
|
||||
Hysteria2 = 7,
|
||||
TUIC = 8,
|
||||
WireGuard = 9,
|
||||
HTTP = 10,
|
||||
Anytls = 11,
|
||||
PolicyGroup = 101,
|
||||
ProxyChain = 102,
|
||||
}
|
||||
20
v2rayN/ServiceLib/Enums/ECoreType.cs
Normal file
20
v2rayN/ServiceLib/Enums/ECoreType.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum ECoreType
|
||||
{
|
||||
v2fly = 1,
|
||||
Xray = 2,
|
||||
v2fly_v5 = 4,
|
||||
mihomo = 13,
|
||||
hysteria = 21,
|
||||
naiveproxy = 22,
|
||||
tuic = 23,
|
||||
sing_box = 24,
|
||||
juicity = 25,
|
||||
hysteria2 = 26,
|
||||
brook = 27,
|
||||
overtls = 28,
|
||||
shadowquic = 29,
|
||||
mieru = 30,
|
||||
v2rayN = 99
|
||||
}
|
||||
8
v2rayN/ServiceLib/Enums/EGirdOrientation.cs
Normal file
8
v2rayN/ServiceLib/Enums/EGirdOrientation.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum EGirdOrientation
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Tab,
|
||||
}
|
||||
10
v2rayN/ServiceLib/Enums/EGlobalHotkey.cs
Normal file
10
v2rayN/ServiceLib/Enums/EGlobalHotkey.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum EGlobalHotkey
|
||||
{
|
||||
ShowForm = 0,
|
||||
SystemProxyClear = 1,
|
||||
SystemProxySet = 2,
|
||||
SystemProxyUnchanged = 3,
|
||||
SystemProxyPac = 4,
|
||||
}
|
||||
13
v2rayN/ServiceLib/Enums/EInboundProtocol.cs
Normal file
13
v2rayN/ServiceLib/Enums/EInboundProtocol.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum EInboundProtocol
|
||||
{
|
||||
socks = 0,
|
||||
socks2,
|
||||
socks3,
|
||||
pac,
|
||||
api,
|
||||
api2,
|
||||
mixed,
|
||||
speedtest = 21
|
||||
}
|
||||
10
v2rayN/ServiceLib/Enums/EMove.cs
Normal file
10
v2rayN/ServiceLib/Enums/EMove.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum EMove
|
||||
{
|
||||
Top = 1,
|
||||
Up = 2,
|
||||
Down = 3,
|
||||
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,
|
||||
}
|
||||
9
v2rayN/ServiceLib/Enums/ERuleMode.cs
Normal file
9
v2rayN/ServiceLib/Enums/ERuleMode.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum ERuleMode
|
||||
{
|
||||
Rule = 0,
|
||||
Global = 1,
|
||||
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,
|
||||
}
|
||||
20
v2rayN/ServiceLib/Enums/EServerColName.cs
Normal file
20
v2rayN/ServiceLib/Enums/EServerColName.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum EServerColName
|
||||
{
|
||||
Def = 0,
|
||||
ConfigType,
|
||||
Remarks,
|
||||
Address,
|
||||
Port,
|
||||
Network,
|
||||
StreamSecurity,
|
||||
SubRemarks,
|
||||
DelayVal,
|
||||
SpeedVal,
|
||||
|
||||
TodayDown,
|
||||
TodayUp,
|
||||
TotalDown,
|
||||
TotalUp
|
||||
}
|
||||
10
v2rayN/ServiceLib/Enums/ESpeedActionType.cs
Normal file
10
v2rayN/ServiceLib/Enums/ESpeedActionType.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum ESpeedActionType
|
||||
{
|
||||
Tcping,
|
||||
Realping,
|
||||
Speedtest,
|
||||
Mixedtest,
|
||||
FastRealping
|
||||
}
|
||||
9
v2rayN/ServiceLib/Enums/ESysProxyType.cs
Normal file
9
v2rayN/ServiceLib/Enums/ESysProxyType.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum ESysProxyType
|
||||
{
|
||||
ForcedClear = 0,
|
||||
ForcedChange = 1,
|
||||
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
|
||||
}
|
||||
14
v2rayN/ServiceLib/Enums/ETransport.cs
Normal file
14
v2rayN/ServiceLib/Enums/ETransport.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum ETransport
|
||||
{
|
||||
tcp,
|
||||
kcp,
|
||||
ws,
|
||||
httpupgrade,
|
||||
xhttp,
|
||||
h2,
|
||||
http,
|
||||
quic,
|
||||
grpc
|
||||
}
|
||||
36
v2rayN/ServiceLib/Enums/EViewAction.cs
Normal file
36
v2rayN/ServiceLib/Enums/EViewAction.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum EViewAction
|
||||
{
|
||||
CloseWindow,
|
||||
ShowYesNo,
|
||||
SaveFileDialog,
|
||||
AddBatchRoutingRulesYesNo,
|
||||
SetClipboardData,
|
||||
AddServerViaClipboard,
|
||||
ImportRulesFromClipboard,
|
||||
ProfilesFocus,
|
||||
ShareSub,
|
||||
ShareServer,
|
||||
ScanScreenTask,
|
||||
ScanImageTask,
|
||||
BrowseServer,
|
||||
ImportRulesFromFile,
|
||||
InitSettingFont,
|
||||
PasswordInput,
|
||||
SubEditWindow,
|
||||
RoutingRuleSettingWindow,
|
||||
RoutingRuleDetailsWindow,
|
||||
AddServerWindow,
|
||||
AddServer2Window,
|
||||
AddGroupServerWindow,
|
||||
DNSSettingWindow,
|
||||
RoutingSettingWindow,
|
||||
OptionSettingWindow,
|
||||
FullConfigTemplateWindow,
|
||||
GlobalHotkeySettingWindow,
|
||||
SubSettingWindow,
|
||||
DispatcherRefreshServersBiz,
|
||||
DispatcherRefreshIcon,
|
||||
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);
|
||||
}
|
||||
}
|
||||
3
v2rayN/ServiceLib/FodyWeavers.xml
Normal file
3
v2rayN/ServiceLib/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ReactiveUI />
|
||||
</Weavers>
|
||||
630
v2rayN/ServiceLib/Global.cs
Normal file
630
v2rayN/ServiceLib/Global.cs
Normal file
|
|
@ -0,0 +1,630 @@
|
|||
namespace ServiceLib;
|
||||
|
||||
public class Global
|
||||
{
|
||||
#region const
|
||||
|
||||
public const string AppName = "v2rayN";
|
||||
public const string GithubUrl = "https://github.com";
|
||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||
public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat";
|
||||
public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs";
|
||||
|
||||
public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw=";
|
||||
public const string ConfigFileName = "guiNConfig.json";
|
||||
public const string CoreConfigFileName = "config.json";
|
||||
public const string CorePreConfigFileName = "configPre.json";
|
||||
public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
|
||||
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
|
||||
public const string ClashMixinConfigFileName = "Mixin.yaml";
|
||||
|
||||
public const string NamespaceSample = "ServiceLib.Sample.";
|
||||
public const string V2raySampleClient = NamespaceSample + "SampleClientConfig";
|
||||
public const string SingboxSampleClient = NamespaceSample + "SingboxSampleClientConfig";
|
||||
public const string V2raySampleHttpRequestFileName = NamespaceSample + "SampleHttpRequest";
|
||||
public const string V2raySampleHttpResponseFileName = NamespaceSample + "SampleHttpResponse";
|
||||
public const string V2raySampleInbound = NamespaceSample + "SampleInbound";
|
||||
public const string V2raySampleOutbound = NamespaceSample + "SampleOutbound";
|
||||
public const string SingboxSampleOutbound = NamespaceSample + "SingboxSampleOutbound";
|
||||
public const string CustomRoutingFileName = NamespaceSample + "custom_routing_";
|
||||
public const string TunSingboxDNSFileName = NamespaceSample + "tun_singbox_dns";
|
||||
public const string TunSingboxInboundFileName = NamespaceSample + "tun_singbox_inbound";
|
||||
public const string TunSingboxRulesFileName = NamespaceSample + "tun_singbox_rules";
|
||||
public const string DNSV2rayNormalFileName = NamespaceSample + "dns_v2ray_normal";
|
||||
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 DefaultSecurity = "auto";
|
||||
public const string DefaultNetwork = "tcp";
|
||||
public const string TcpHeaderHttp = "http";
|
||||
public const string None = "none";
|
||||
public const string ProxyTag = "proxy";
|
||||
public const string DirectTag = "direct";
|
||||
public const string BlockTag = "block";
|
||||
public const string DnsTag = "dns-module";
|
||||
public const string BalancerTagSuffix = "-round";
|
||||
public const string StreamSecurity = "tls";
|
||||
public const string StreamSecurityReality = "reality";
|
||||
public const string Loopback = "127.0.0.1";
|
||||
public const string InboundAPIProtocol = "dokodemo-door";
|
||||
public const string HttpProtocol = "http://";
|
||||
public const string HttpsProtocol = "https://";
|
||||
public const string SocksProtocol = "socks://";
|
||||
public const string Socks5Protocol = "socks5://";
|
||||
public const string AsIs = "AsIs";
|
||||
public const string IPIfNonMatch = "IPIfNonMatch";
|
||||
public const string IPOnDemand = "IPOnDemand";
|
||||
|
||||
public const string UserEMail = "t@t.tt";
|
||||
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
||||
public const string AutoRunName = "v2rayNAutoRun";
|
||||
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.*";
|
||||
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 const string SingboxDirectDNSTag = "direct_dns";
|
||||
public const string SingboxRemoteDNSTag = "remote_dns";
|
||||
public const string SingboxLocalDNSTag = "local_local";
|
||||
public const string SingboxHostsDNSTag = "hosts_dns";
|
||||
public const string SingboxFakeDNSTag = "fake_dns";
|
||||
|
||||
public static readonly List<string> IEProxyProtocols =
|
||||
[
|
||||
"{ip}:{http_port}",
|
||||
"socks={ip}:{socks_port}",
|
||||
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
|
||||
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> SubConvertUrls =
|
||||
[
|
||||
@"https://sub.xeton.dev/sub?url={0}",
|
||||
@"https://api.dler.io/sub?url={0}",
|
||||
@"http://127.0.0.1:25500/sub?url={0}",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> SubConvertConfig =
|
||||
[@"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=50000000",
|
||||
@"https://speed.cloudflare.com/__down?bytes=100000000",
|
||||
];
|
||||
|
||||
public static readonly List<string> SpeedPingTestUrls =
|
||||
[
|
||||
@"https://www.google.com/generate_204",
|
||||
@"https://www.gstatic.com/generate_204",
|
||||
@"https://www.apple.com/library/test/success.html",
|
||||
@"http://www.msftconnecttest.com/connecttest.txt"
|
||||
];
|
||||
|
||||
public static readonly 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" },
|
||||
{"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" },
|
||||
{"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" },
|
||||
{"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" },
|
||||
{"none",""}
|
||||
};
|
||||
|
||||
public const string Hysteria2ProtocolShare = "hy2://";
|
||||
|
||||
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
|
||||
{
|
||||
{ EConfigType.VMess, "vmess://" },
|
||||
{ EConfigType.Shadowsocks, "ss://" },
|
||||
{ EConfigType.SOCKS, "socks://" },
|
||||
{ EConfigType.VLESS, "vless://" },
|
||||
{ EConfigType.Trojan, "trojan://" },
|
||||
{ EConfigType.Hysteria2, "hysteria2://" },
|
||||
{ EConfigType.TUIC, "tuic://" },
|
||||
{ EConfigType.WireGuard, "wireguard://" },
|
||||
{ EConfigType.Anytls, "anytls://" }
|
||||
};
|
||||
|
||||
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
|
||||
{
|
||||
{ EConfigType.VMess, "vmess" },
|
||||
{ EConfigType.Shadowsocks, "shadowsocks" },
|
||||
{ EConfigType.SOCKS, "socks" },
|
||||
{ EConfigType.HTTP, "http" },
|
||||
{ EConfigType.VLESS, "vless" },
|
||||
{ EConfigType.Trojan, "trojan" },
|
||||
{ EConfigType.Hysteria2, "hysteria2" },
|
||||
{ EConfigType.TUIC, "tuic" },
|
||||
{ EConfigType.WireGuard, "wireguard" },
|
||||
{ EConfigType.Anytls, "anytls" }
|
||||
};
|
||||
|
||||
public static readonly List<string> VmessSecurities =
|
||||
[
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"auto",
|
||||
"none",
|
||||
"zero"
|
||||
];
|
||||
|
||||
public static readonly List<string> SsSecurities =
|
||||
[
|
||||
"aes-256-gcm",
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"chacha20-ietf-poly1305",
|
||||
"none",
|
||||
"plain"
|
||||
];
|
||||
|
||||
public static readonly List<string> SsSecuritiesInXray =
|
||||
[
|
||||
"aes-256-gcm",
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
"none",
|
||||
"plain",
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305"
|
||||
];
|
||||
|
||||
public static readonly List<string> SsSecuritiesInSingbox =
|
||||
[
|
||||
"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",
|
||||
"fr",
|
||||
"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
|
||||
}
|
||||
36
v2rayN/ServiceLib/GlobalUsings.cs
Normal file
36
v2rayN/ServiceLib/GlobalUsings.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
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.Enums;
|
||||
global using ServiceLib.Events;
|
||||
global using ServiceLib.Handler;
|
||||
global using ServiceLib.Handler.Fmt;
|
||||
global using ServiceLib.Handler.SysProxy;
|
||||
global using ServiceLib.Helper;
|
||||
global using ServiceLib.Manager;
|
||||
global using ServiceLib.Models;
|
||||
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
|
||||
}
|
||||
2457
v2rayN/ServiceLib/Handler/ConfigHandler.cs
Normal file
2457
v2rayN/ServiceLib/Handler/ConfigHandler.cs
Normal file
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
251
v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs
Normal file
251
v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
using System.Collections.Specialized;
|
||||
|
||||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class BaseFmt
|
||||
{
|
||||
protected static string GetIpv6(string 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}]";
|
||||
}
|
||||
else
|
||||
{
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
22
v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs
Normal file
22
v2rayN/ServiceLib/Handler/Fmt/ClashFmt.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class ClashFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
{
|
||||
if (Contains(strData, "rules", "-port", "proxies"))
|
||||
{
|
||||
var fileName = WriteAllText(strData, "yaml");
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
CoreType = ECoreType.mihomo,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "clash_custom"
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
96
v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs
Normal file
96
v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class FmtHandler
|
||||
{
|
||||
private static readonly string _tag = "FmtHandler";
|
||||
|
||||
public static string? GetShareUri(ProfileItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = item.ConfigType switch
|
||||
{
|
||||
EConfigType.VMess => VmessFmt.ToUri(item),
|
||||
EConfigType.Shadowsocks => ShadowsocksFmt.ToUri(item),
|
||||
EConfigType.SOCKS => SocksFmt.ToUri(item),
|
||||
EConfigType.Trojan => TrojanFmt.ToUri(item),
|
||||
EConfigType.VLESS => VLESSFmt.ToUri(item),
|
||||
EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item),
|
||||
EConfigType.TUIC => TuicFmt.ToUri(item),
|
||||
EConfigType.WireGuard => WireguardFmt.ToUri(item),
|
||||
EConfigType.Anytls => AnytlsFmt.ToUri(item),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return url;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveConfig(string config, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
try
|
||||
{
|
||||
string str = config.TrimEx();
|
||||
if (str.IsNullOrEmpty())
|
||||
{
|
||||
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 if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls]))
|
||||
{
|
||||
return AnytlsFmt.Resolve(str, out msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = ResUI.NonvmessOrssProtocol;
|
||||
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");
|
||||
}
|
||||
}
|
||||
83
v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs
Normal file
83
v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class Hysteria2Fmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem item = new()
|
||||
{
|
||||
ConfigType = EConfigType.Hysteria2
|
||||
};
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
if (url == null)
|
||||
return null;
|
||||
|
||||
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 = GetQueryDecoded(query, "obfs-password");
|
||||
item.AllowInsecure = GetQueryValue(query, "insecure") == "1" ? "true" : "false";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
177
v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs
Normal file
177
v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class ShadowsocksFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem? item;
|
||||
|
||||
item = ResolveSSLegacy(str) ?? ResolveSip002(str);
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (item.Address.Length == 0 || item.Port == 0 || item.Security.Length == 0 || item.Id.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.ConfigType = EConfigType.Shadowsocks;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var remark = string.Empty;
|
||||
if (item.Remarks.IsNotEmpty())
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
//url = string.Format("{0}:{1}@{2}:{3}",
|
||||
// item.security,
|
||||
// item.id,
|
||||
// item.address,
|
||||
// item.port);
|
||||
//url = Utile.Base64Encode(url);
|
||||
//new Sip002
|
||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", 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;
|
||||
}
|
||||
|
||||
ProfileItem item = new();
|
||||
var base64 = match.Groups["base64"].Value.TrimEnd('/');
|
||||
var tag = match.Groups["tag"].Value;
|
||||
if (tag.IsNotEmpty())
|
||||
{
|
||||
item.Remarks = Utils.UrlDecode(tag);
|
||||
}
|
||||
Match details;
|
||||
try
|
||||
{
|
||||
details = DetailsParser.Match(Utils.Base64Decode(base64));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!details.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.Security = details.Groups["method"].Value;
|
||||
item.Id = details.Groups["password"].Value;
|
||||
item.Address = details.Groups["hostname"].Value;
|
||||
item.Port = details.Groups["port"].Value.ToInt();
|
||||
return item;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveSip002(string result)
|
||||
{
|
||||
var parsedUrl = Utils.TryUri(result);
|
||||
if (parsedUrl == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
||||
Address = parsedUrl.IdnHost,
|
||||
Port = parsedUrl.Port,
|
||||
};
|
||||
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||
//2022-blake3
|
||||
if (rawUserInfo.Contains(':'))
|
||||
{
|
||||
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
|
||||
if (userInfoParts.Length != 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
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)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.Security = userInfoParts.First();
|
||||
item.Id = userInfoParts.Last();
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
obfsHost = obfsHost?.Replace("obfs-host=", "");
|
||||
item.Network = Global.DefaultNetwork;
|
||||
item.HeaderType = Global.TcpHeaderHttp;
|
||||
item.RequestHost = obfsHost ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static List<ProfileItem>? ResolveSip008(string result)
|
||||
{
|
||||
//SsSIP008
|
||||
var lstSsServer = JsonUtils.Deserialize<List<SsServer>>(result);
|
||||
if (lstSsServer?.Count <= 0)
|
||||
{
|
||||
var ssSIP008 = JsonUtils.Deserialize<SsSIP008>(result);
|
||||
if (ssSIP008?.servers?.Count > 0)
|
||||
{
|
||||
lstSsServer = ssSIP008.servers;
|
||||
}
|
||||
}
|
||||
|
||||
if (lstSsServer?.Count > 0)
|
||||
{
|
||||
List<ProfileItem> lst = [];
|
||||
foreach (var it in lstSsServer)
|
||||
{
|
||||
var ssItem = new ProfileItem()
|
||||
{
|
||||
Remarks = it.remarks,
|
||||
Security = it.method,
|
||||
Id = it.password,
|
||||
Address = it.server,
|
||||
Port = it.server_port.ToInt()
|
||||
};
|
||||
lst.Add(ssItem);
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
47
v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs
Normal file
47
v2rayN/ServiceLib/Handler/Fmt/SingboxFmt.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class SingboxFmt : BaseFmt
|
||||
{
|
||||
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
||||
{
|
||||
var configObjects = JsonUtils.Deserialize<object[]>(strData);
|
||||
if (configObjects is not { Length: > 0 })
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var configObject in configObjects)
|
||||
{
|
||||
var objectString = JsonUtils.Serialize(configObject);
|
||||
var profileIt = ResolveFull(objectString, subRemarks);
|
||||
if (profileIt != null)
|
||||
{
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
114
v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs
Normal file
114
v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class SocksFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
var item = ResolveSocksNew(str) ?? ResolveSocks(str);
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (item.Address.Length == 0 || item.Port == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.ConfigType = EConfigType.SOCKS;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var remark = string.Empty;
|
||||
if (item.Remarks.IsNotEmpty())
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
//new
|
||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
|
||||
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveSocks(string result)
|
||||
{
|
||||
ProfileItem item = new()
|
||||
{
|
||||
ConfigType = EConfigType.SOCKS
|
||||
};
|
||||
result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..];
|
||||
//remark
|
||||
var indexRemark = result.IndexOf("#");
|
||||
if (indexRemark > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1));
|
||||
}
|
||||
catch { }
|
||||
result = result[..indexRemark];
|
||||
}
|
||||
//part decode
|
||||
var indexS = result.IndexOf("@");
|
||||
if (indexS > 0)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
result = Utils.Base64Decode(result);
|
||||
}
|
||||
|
||||
var arr1 = result.Split('@');
|
||||
if (arr1.Length != 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var arr21 = arr1.First().Split(':');
|
||||
var indexPort = arr1.Last().LastIndexOf(":");
|
||||
if (arr21.Length != 2 || indexPort < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.Address = arr1[1][..indexPort];
|
||||
item.Port = arr1[1][(indexPort + 1)..].ToInt();
|
||||
item.Security = arr21.First();
|
||||
item.Id = arr21[1];
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveSocksNew(string result)
|
||||
{
|
||||
var parsedUrl = Utils.TryUri(result);
|
||||
if (parsedUrl == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
||||
Address = parsedUrl.IdnHost,
|
||||
Port = parsedUrl.Port,
|
||||
};
|
||||
|
||||
// parse base64 UserInfo
|
||||
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||
var userInfo = Utils.Base64Decode(rawUserInfo);
|
||||
var userInfoParts = userInfo.Split([':'], 2);
|
||||
if (userInfoParts.Length == 2)
|
||||
{
|
||||
item.Security = userInfoParts.First();
|
||||
item.Id = userInfoParts[1];
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
47
v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs
Normal file
47
v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class TrojanFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
ConfigType = EConfigType.Trojan
|
||||
};
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
if (url == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
_ = ResolveStdTransport(query, ref item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var remark = string.Empty;
|
||||
if (item.Remarks.IsNotEmpty())
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
_ = GetStdTransport(item, null, ref dicQuery);
|
||||
|
||||
return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark);
|
||||
}
|
||||
}
|
||||
63
v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs
Normal file
63
v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class TuicFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
ConfigType = EConfigType.TUIC
|
||||
};
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
if (url == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
var rawUserInfo = Utils.UrlDecode(url.UserInfo);
|
||||
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
|
||||
if (userInfoParts.Length == 2)
|
||||
{
|
||||
item.Id = userInfoParts.First();
|
||||
item.Security = userInfoParts.Last();
|
||||
}
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveStdTransport(query, ref item);
|
||||
item.HeaderType = GetQueryValue(query, "congestion_control");
|
||||
|
||||
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 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));
|
||||
}
|
||||
dicQuery.Add("congestion_control", item.HeaderType);
|
||||
|
||||
return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark);
|
||||
}
|
||||
}
|
||||
48
v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs
Normal file
48
v2rayN/ServiceLib/Handler/Fmt/V2rayFmt.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class V2rayFmt : BaseFmt
|
||||
{
|
||||
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
||||
{
|
||||
var configObjects = JsonUtils.Deserialize<object[]>(strData);
|
||||
if (configObjects is not { Length: > 0 })
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var configObject in configObjects)
|
||||
{
|
||||
var objectString = JsonUtils.Serialize(configObject);
|
||||
var profileIt = ResolveFull(objectString, subRemarks);
|
||||
if (profileIt != null)
|
||||
{
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
}
|
||||
|
||||
return lstResult;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
{
|
||||
var config = JsonUtils.ParseJson(strData);
|
||||
if (config?["inbounds"] == null
|
||||
|| config["outbounds"] == null
|
||||
|| config["routing"] == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = WriteAllText(strData);
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
CoreType = ECoreType.Xray,
|
||||
Address = fileName,
|
||||
Remarks = config?["remarks"]?.ToString() ?? subRemarks ?? "v2ray_custom"
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
}
|
||||
}
|
||||
59
v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs
Normal file
59
v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class VLESSFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
ConfigType = EConfigType.VLESS,
|
||||
Security = Global.None
|
||||
};
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
if (url == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
item.Security = GetQueryValue(query, "encryption", Global.None);
|
||||
item.StreamSecurity = GetQueryValue(query, "security");
|
||||
_ = 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 dicQuery = new Dictionary<string, string>();
|
||||
if (item.Security.IsNotEmpty())
|
||||
{
|
||||
dicQuery.Add("encryption", item.Security);
|
||||
}
|
||||
else
|
||||
{
|
||||
dicQuery.Add("encryption", Global.None);
|
||||
}
|
||||
_ = GetStdTransport(item, Global.None, ref dicQuery);
|
||||
|
||||
return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark);
|
||||
}
|
||||
}
|
||||
125
v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs
Normal file
125
v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class VmessFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem? item;
|
||||
if (str.IndexOf('@') > 0)
|
||||
{
|
||||
item = ResolveStdVmess(str) ?? ResolveVmess(str, out msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = ResolveVmess(str, out msg);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var vmessQRCode = new VmessQRCode
|
||||
{
|
||||
v = item.ConfigVersion,
|
||||
ps = item.Remarks.TrimEx(),
|
||||
add = item.Address,
|
||||
port = item.Port,
|
||||
id = item.Id,
|
||||
aid = item.AlterId,
|
||||
scy = item.Security,
|
||||
net = item.Network,
|
||||
type = item.HeaderType,
|
||||
host = item.RequestHost,
|
||||
path = item.Path,
|
||||
tls = item.StreamSecurity,
|
||||
sni = item.Sni,
|
||||
alpn = item.Alpn,
|
||||
fp = item.Fingerprint
|
||||
};
|
||||
|
||||
var url = JsonUtils.Serialize(vmessQRCode);
|
||||
url = Utils.Base64Encode(url);
|
||||
url = $"{Global.ProtocolShares[EConfigType.VMess]}{url}";
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveVmess(string result, out string msg)
|
||||
{
|
||||
msg = string.Empty;
|
||||
var item = new ProfileItem
|
||||
{
|
||||
ConfigType = EConfigType.VMess
|
||||
};
|
||||
|
||||
result = result[Global.ProtocolShares[EConfigType.VMess].Length..];
|
||||
result = Utils.Base64Decode(result);
|
||||
|
||||
var vmessQRCode = JsonUtils.Deserialize<VmessQRCode>(result);
|
||||
if (vmessQRCode == null)
|
||||
{
|
||||
msg = ResUI.FailedConversionConfiguration;
|
||||
return null;
|
||||
}
|
||||
|
||||
item.Network = Global.DefaultNetwork;
|
||||
item.HeaderType = Global.None;
|
||||
|
||||
item.ConfigVersion = vmessQRCode.v;
|
||||
item.Remarks = Utils.ToString(vmessQRCode.ps);
|
||||
item.Address = Utils.ToString(vmessQRCode.add);
|
||||
item.Port = vmessQRCode.port;
|
||||
item.Id = Utils.ToString(vmessQRCode.id);
|
||||
item.AlterId = vmessQRCode.aid;
|
||||
item.Security = Utils.ToString(vmessQRCode.scy);
|
||||
|
||||
item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity;
|
||||
if (vmessQRCode.net.IsNotEmpty())
|
||||
{
|
||||
item.Network = vmessQRCode.net;
|
||||
}
|
||||
if (vmessQRCode.type.IsNotEmpty())
|
||||
{
|
||||
item.HeaderType = vmessQRCode.type;
|
||||
}
|
||||
|
||||
item.RequestHost = Utils.ToString(vmessQRCode.host);
|
||||
item.Path = Utils.ToString(vmessQRCode.path);
|
||||
item.StreamSecurity = Utils.ToString(vmessQRCode.tls);
|
||||
item.Sni = Utils.ToString(vmessQRCode.sni);
|
||||
item.Alpn = Utils.ToString(vmessQRCode.alpn);
|
||||
item.Fingerprint = Utils.ToString(vmessQRCode.fp);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveStdVmess(string str)
|
||||
{
|
||||
var item = new ProfileItem
|
||||
{
|
||||
ConfigType = EConfigType.VMess,
|
||||
Security = "auto"
|
||||
};
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
if (url == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveStdTransport(query, ref item);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
67
v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs
Normal file
67
v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class WireguardFmt : BaseFmt
|
||||
{
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
ConfigType = EConfigType.WireGuard
|
||||
};
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
if (url == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
|
||||
item.PublicKey = GetQueryDecoded(query, "publickey");
|
||||
item.Path = GetQueryDecoded(query, "reserved");
|
||||
item.RequestHost = GetQueryDecoded(query, "address");
|
||||
item.ShortId = GetQueryDecoded(query, "mtu");
|
||||
|
||||
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 dicQuery = new Dictionary<string, string>();
|
||||
if (item.PublicKey.IsNotEmpty())
|
||||
{
|
||||
dicQuery.Add("publickey", Utils.UrlEncode(item.PublicKey));
|
||||
}
|
||||
if (item.Path.IsNotEmpty())
|
||||
{
|
||||
dicQuery.Add("reserved", Utils.UrlEncode(item.Path));
|
||||
}
|
||||
if (item.RequestHost.IsNotEmpty())
|
||||
{
|
||||
dicQuery.Add("address", Utils.UrlEncode(item.RequestHost));
|
||||
}
|
||||
if (item.ShortId.IsNotEmpty())
|
||||
{
|
||||
dicQuery.Add("mtu", Utils.UrlEncode(item.ShortId));
|
||||
}
|
||||
return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark);
|
||||
}
|
||||
}
|
||||
221
v2rayN/ServiceLib/Handler/SubscriptionHandler.cs
Normal file
221
v2rayN/ServiceLib/Handler/SubscriptionHandler.cs
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
namespace ServiceLib.Handler;
|
||||
|
||||
public static class SubscriptionHandler
|
||||
{
|
||||
public static async Task UpdateProcess(Config config, string subId, bool blProxy, Func<bool, string, Task> updateFunc)
|
||||
{
|
||||
await updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
|
||||
var subItem = await AppManager.Instance.SubItems();
|
||||
|
||||
if (subItem is not { Count: > 0 })
|
||||
{
|
||||
await updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription);
|
||||
return;
|
||||
}
|
||||
|
||||
var successCount = 0;
|
||||
foreach (var item in subItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsValidSubscription(item, subId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var hashCode = $"{item.Remarks}->";
|
||||
if (item.Enabled == false)
|
||||
{
|
||||
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create download handler
|
||||
var downloadHandle = CreateDownloadHandler(hashCode, updateFunc);
|
||||
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
|
||||
|
||||
// Get all subscription content (main subscription + additional subscriptions)
|
||||
var result = await DownloadAllSubscriptions(config, item, blProxy, downloadHandle);
|
||||
|
||||
// Process download result
|
||||
if (await ProcessDownloadResult(config, item.Id, result, hashCode, updateFunc))
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
|
||||
await updateFunc?.Invoke(false, "-------------------------------------------------------");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var hashCode = $"{item.Remarks}->";
|
||||
Logging.SaveLog("UpdateSubscription", ex);
|
||||
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgFailedImportSubscription}: {ex.Message}");
|
||||
await updateFunc?.Invoke(false, "-------------------------------------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
await updateFunc?.Invoke(successCount > 0, $"{ResUI.MsgUpdateSubscriptionEnd}");
|
||||
}
|
||||
|
||||
private static bool IsValidSubscription(SubItem item, string subId)
|
||||
{
|
||||
var id = item.Id.TrimEx();
|
||||
var url = item.Url.TrimEx();
|
||||
|
||||
if (id.IsNullOrEmpty() || url.IsNullOrEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (subId.IsNotEmpty() && item.Id != subId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static DownloadService CreateDownloadHandler(string hashCode, Func<bool, string, Task> updateFunc)
|
||||
{
|
||||
var downloadHandle = new DownloadService();
|
||||
downloadHandle.Error += (sender2, args) =>
|
||||
{
|
||||
updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}");
|
||||
};
|
||||
return downloadHandle;
|
||||
}
|
||||
|
||||
private static async Task<string> DownloadSubscriptionContent(DownloadService downloadHandle, string url, bool blProxy, string userAgent)
|
||||
{
|
||||
var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent);
|
||||
|
||||
// If download with proxy fails, try direct connection
|
||||
if (blProxy && result.IsNullOrEmpty())
|
||||
{
|
||||
result = await downloadHandle.TryDownloadString(url, false, userAgent);
|
||||
}
|
||||
|
||||
return result ?? string.Empty;
|
||||
}
|
||||
|
||||
private static async Task<string> DownloadAllSubscriptions(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
|
||||
{
|
||||
// Download main subscription content
|
||||
var result = await DownloadMainSubscription(config, item, blProxy, downloadHandle);
|
||||
|
||||
// Process additional subscription links (if any)
|
||||
if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty())
|
||||
{
|
||||
result = await DownloadAdditionalSubscriptions(item, result, blProxy, downloadHandle);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static async Task<string> DownloadMainSubscription(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
|
||||
{
|
||||
// Prepare subscription URL and download directly
|
||||
var url = Utils.GetPunycode(item.Url.TrimEx());
|
||||
|
||||
// If conversion is needed
|
||||
if (item.ConvertTarget.IsNotEmpty())
|
||||
{
|
||||
var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty()
|
||||
? Global.SubConvertUrls.FirstOrDefault()
|
||||
: config.ConstItem.SubConvertUrl;
|
||||
|
||||
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
|
||||
|
||||
if (!url.Contains("target="))
|
||||
{
|
||||
url += string.Format("&target={0}", item.ConvertTarget);
|
||||
}
|
||||
|
||||
if (!url.Contains("config="))
|
||||
{
|
||||
url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
// Download and return result directly
|
||||
return await DownloadSubscriptionContent(downloadHandle, url, blProxy, item.UserAgent);
|
||||
}
|
||||
|
||||
private static async Task<string> DownloadAdditionalSubscriptions(SubItem item, string mainResult, bool blProxy, DownloadService downloadHandle)
|
||||
{
|
||||
var result = mainResult;
|
||||
|
||||
// If main subscription result is Base64 encoded, decode it first
|
||||
if (result.IsNotEmpty() && Utils.IsBase64String(result))
|
||||
{
|
||||
result = Utils.Base64Decode(result);
|
||||
}
|
||||
|
||||
// Process additional URL list
|
||||
var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? [];
|
||||
foreach (var it in lstUrl)
|
||||
{
|
||||
var url2 = Utils.GetPunycode(it);
|
||||
if (url2.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var additionalResult = await DownloadSubscriptionContent(downloadHandle, url2, blProxy, item.UserAgent);
|
||||
|
||||
if (additionalResult.IsNotEmpty())
|
||||
{
|
||||
// Process additional subscription results, add to main result
|
||||
if (Utils.IsBase64String(additionalResult))
|
||||
{
|
||||
result += Environment.NewLine + Utils.Base64Decode(additionalResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
result += Environment.NewLine + additionalResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static async Task<bool> ProcessDownloadResult(Config config, string id, string result, string hashCode, Func<bool, string, Task> updateFunc)
|
||||
{
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
|
||||
return false;
|
||||
}
|
||||
|
||||
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
|
||||
|
||||
// If result is too short, display content directly
|
||||
if (result.Length < 99)
|
||||
{
|
||||
await updateFunc?.Invoke(false, $"{hashCode}{result}");
|
||||
}
|
||||
|
||||
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartParsingSubscription}");
|
||||
|
||||
// Add servers to configuration
|
||||
var ret = await ConfigHandler.AddBatchServers(config, result, id, true);
|
||||
if (ret <= 0)
|
||||
{
|
||||
Logging.SaveLog("FailedImportSubscription");
|
||||
Logging.SaveLog(result);
|
||||
}
|
||||
|
||||
// Update completion message
|
||||
await updateFunc?.Invoke(false, ret > 0
|
||||
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
|
||||
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
|
||||
|
||||
return ret > 0;
|
||||
}
|
||||
}
|
||||
25
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs
Normal file
25
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
namespace ServiceLib.Handler.SysProxy;
|
||||
|
||||
public static class ProxySettingLinux
|
||||
{
|
||||
private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh";
|
||||
|
||||
public static async Task SetProxy(string host, int port, string exceptions)
|
||||
{
|
||||
List<string> args = ["manual", host, port.ToString(), exceptions];
|
||||
await ExecCmd(args);
|
||||
}
|
||||
|
||||
public static async Task UnsetProxy()
|
||||
{
|
||||
List<string> args = ["none"];
|
||||
await ExecCmd(args);
|
||||
}
|
||||
|
||||
private static async Task ExecCmd(List<string> args)
|
||||
{
|
||||
var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false);
|
||||
|
||||
await Utils.GetCliWrapOutput(fileName, args);
|
||||
}
|
||||
}
|
||||
30
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs
Normal file
30
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
namespace ServiceLib.Handler.SysProxy;
|
||||
|
||||
public static class ProxySettingOSX
|
||||
{
|
||||
private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh";
|
||||
|
||||
public static async Task SetProxy(string host, int port, string exceptions)
|
||||
{
|
||||
List<string> args = ["set", host, port.ToString()];
|
||||
if (exceptions.IsNotEmpty())
|
||||
{
|
||||
args.AddRange(exceptions.Split(','));
|
||||
}
|
||||
|
||||
await ExecCmd(args);
|
||||
}
|
||||
|
||||
public static async Task UnsetProxy()
|
||||
{
|
||||
List<string> args = ["clear"];
|
||||
await ExecCmd(args);
|
||||
}
|
||||
|
||||
private static async Task ExecCmd(List<string> args)
|
||||
{
|
||||
var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false);
|
||||
|
||||
await Utils.GetCliWrapOutput(fileName, args);
|
||||
}
|
||||
}
|
||||
358
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingWindows.cs
Normal file
358
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingWindows.cs
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
using static ServiceLib.Handler.SysProxy.ProxySettingWindows.InternetConnectionOption;
|
||||
|
||||
namespace ServiceLib.Handler.SysProxy;
|
||||
|
||||
public static class ProxySettingWindows
|
||||
{
|
||||
private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";
|
||||
|
||||
private static bool SetProxyFallback(string? strProxy, string? exceptions, int type)
|
||||
{
|
||||
if (type == 1)
|
||||
{
|
||||
WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0);
|
||||
WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty);
|
||||
WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty);
|
||||
WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty);
|
||||
}
|
||||
if (type == 2)
|
||||
{
|
||||
WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 1);
|
||||
WindowsUtils.RegWriteValue(_regPath, "ProxyServer", strProxy ?? string.Empty);
|
||||
WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", exceptions ?? string.Empty);
|
||||
WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", string.Empty);
|
||||
}
|
||||
else if (type == 4)
|
||||
{
|
||||
WindowsUtils.RegWriteValue(_regPath, "ProxyEnable", 0);
|
||||
WindowsUtils.RegWriteValue(_regPath, "ProxyServer", string.Empty);
|
||||
WindowsUtils.RegWriteValue(_regPath, "ProxyOverride", string.Empty);
|
||||
WindowsUtils.RegWriteValue(_regPath, "AutoConfigURL", strProxy ?? string.Empty);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
// set to use no proxy
|
||||
/// </summary>
|
||||
/// <exception cref="ApplicationException">Error message with win32 error code</exception>
|
||||
public static bool UnsetProxy()
|
||||
{
|
||||
return SetProxy(null, null, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set system proxy settings
|
||||
/// </summary>
|
||||
/// <param name="strProxy"> proxy address</param>
|
||||
/// <param name="exceptions">exception addresses that do not use proxy</param>
|
||||
/// <param name="type">type of proxy defined in PerConnFlags
|
||||
/// PROXY_TYPE_DIRECT = 0x00000001, // direct connection (no proxy)
|
||||
/// PROXY_TYPE_PROXY = 0x00000002, // via named proxy
|
||||
/// PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy script URL
|
||||
/// PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection
|
||||
/// </param>
|
||||
/// <exception cref="ApplicationException">Error message with win32 error code</exception>
|
||||
/// <returns>true: one of connection is successfully updated proxy settings</returns>
|
||||
public static bool SetProxy(string? strProxy, string? exceptions, int type)
|
||||
{
|
||||
try
|
||||
{
|
||||
// set proxy for LAN
|
||||
var result = SetConnectionProxy(null, strProxy, exceptions, type);
|
||||
// set proxy for dial up connections
|
||||
var connections = EnumerateRasEntries();
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
result |= SetConnectionProxy(connection, strProxy, exceptions, type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_ = SetProxyFallback(strProxy, exceptions, type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool SetConnectionProxy(string? connectionName, string? strProxy, string? exceptions, int type)
|
||||
{
|
||||
var list = new InternetPerConnOptionList();
|
||||
|
||||
var optionCount = 1;
|
||||
if (type == 1) // No proxy
|
||||
{
|
||||
optionCount = 1;
|
||||
}
|
||||
else if (type is 2 or 4) // named proxy or autoproxy script URL
|
||||
{
|
||||
optionCount = exceptions.IsNullOrEmpty() ? 2 : 3;
|
||||
}
|
||||
|
||||
var m_Int = (int)PerConnFlags.PROXY_TYPE_DIRECT;
|
||||
var m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS;
|
||||
if (type == 2) // named proxy
|
||||
{
|
||||
m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_PROXY);
|
||||
m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_SERVER;
|
||||
}
|
||||
else if (type == 4) // autoproxy script url
|
||||
{
|
||||
m_Int = (int)(PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_AUTO_PROXY_URL);
|
||||
m_Option = PerConnOption.INTERNET_PER_CONN_AUTOCONFIG_URL;
|
||||
}
|
||||
|
||||
var options = new InternetConnectionOption[optionCount];
|
||||
// USE a proxy server ...
|
||||
options[0].m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS;
|
||||
options[0].m_Value.m_Int = m_Int;
|
||||
// use THIS proxy server
|
||||
if (optionCount > 1)
|
||||
{
|
||||
options[1].m_Option = m_Option;
|
||||
options[1].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(strProxy); // !! remember to deallocate memory 1
|
||||
// except for these addresses ...
|
||||
if (optionCount > 2)
|
||||
{
|
||||
options[2].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_BYPASS;
|
||||
options[2].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(exceptions); // !! remember to deallocate memory 2
|
||||
}
|
||||
}
|
||||
|
||||
// default stuff
|
||||
list.dwSize = Marshal.SizeOf(list);
|
||||
if (connectionName != null)
|
||||
{
|
||||
list.szConnection = Marshal.StringToHGlobalAuto(connectionName); // !! remember to deallocate memory 3
|
||||
}
|
||||
else
|
||||
{
|
||||
list.szConnection = nint.Zero;
|
||||
}
|
||||
list.dwOptionCount = options.Length;
|
||||
list.dwOptionError = 0;
|
||||
|
||||
var optSize = Marshal.SizeOf(typeof(InternetConnectionOption));
|
||||
// make a pointer out of all that ...
|
||||
var optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length); // !! remember to deallocate memory 4
|
||||
// copy the array over into that spot in memory ...
|
||||
for (var i = 0; i < options.Length; ++i)
|
||||
{
|
||||
if (Environment.Is64BitOperatingSystem)
|
||||
{
|
||||
var opt = new nint(optionsPtr.ToInt64() + (i * optSize));
|
||||
Marshal.StructureToPtr(options[i], opt, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var opt = new nint(optionsPtr.ToInt32() + (i * optSize));
|
||||
Marshal.StructureToPtr(options[i], opt, false);
|
||||
}
|
||||
}
|
||||
|
||||
list.options = optionsPtr;
|
||||
|
||||
// and then make a pointer out of the whole list
|
||||
var ipcoListPtr = Marshal.AllocCoTaskMem(list.dwSize); // !! remember to deallocate memory 5
|
||||
Marshal.StructureToPtr(list, ipcoListPtr, false);
|
||||
|
||||
// and finally, call the API method!
|
||||
var isSuccess = NativeMethods.InternetSetOption(nint.Zero,
|
||||
InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION,
|
||||
ipcoListPtr, list.dwSize);
|
||||
var returnvalue = 0; // ERROR_SUCCESS
|
||||
if (!isSuccess)
|
||||
{ // get the error codes, they might be helpful
|
||||
returnvalue = Marshal.GetLastPInvokeError();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Notify the system that the registry settings have been changed and cause them to be refreshed
|
||||
_ = NativeMethods.InternetSetOption(nint.Zero, InternetOption.INTERNET_OPTION_SETTINGS_CHANGED, nint.Zero, 0);
|
||||
_ = NativeMethods.InternetSetOption(nint.Zero, InternetOption.INTERNET_OPTION_REFRESH, nint.Zero, 0);
|
||||
}
|
||||
|
||||
// FREE the data ASAP
|
||||
if (list.szConnection != nint.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(list.szConnection); // release mem 3
|
||||
}
|
||||
if (optionCount > 1)
|
||||
{
|
||||
Marshal.FreeHGlobal(options[1].m_Value.m_StringPtr); // release mem 1
|
||||
if (optionCount > 2)
|
||||
{
|
||||
Marshal.FreeHGlobal(options[2].m_Value.m_StringPtr); // release mem 2
|
||||
}
|
||||
}
|
||||
Marshal.FreeCoTaskMem(optionsPtr); // release mem 4
|
||||
Marshal.FreeCoTaskMem(ipcoListPtr); // release mem 5
|
||||
if (returnvalue != 0)
|
||||
{
|
||||
// throw the error codes, they might be helpful
|
||||
throw new ApplicationException($"Set Internet Proxy failed with error code: {Marshal.GetLastWin32Error()}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve list of connections including LAN and WAN to support PPPoE connection
|
||||
/// </summary>
|
||||
/// <returns>A list of RAS connection names. May be empty list if no dial up connection.</returns>
|
||||
/// <exception cref="ApplicationException">Error message with win32 error code</exception>
|
||||
private static IEnumerable<string> EnumerateRasEntries()
|
||||
{
|
||||
var entries = 0;
|
||||
// attempt to query with 1 entry buffer
|
||||
var rasEntryNames = new RASENTRYNAME[1];
|
||||
var bufferSize = Marshal.SizeOf(typeof(RASENTRYNAME));
|
||||
rasEntryNames[0].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME));
|
||||
|
||||
var result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries);
|
||||
// increase buffer if the buffer is not large enough
|
||||
if (result == (uint)ErrorCode.ERROR_BUFFER_TOO_SMALL)
|
||||
{
|
||||
rasEntryNames = new RASENTRYNAME[bufferSize / Marshal.SizeOf(typeof(RASENTRYNAME))];
|
||||
for (var i = 0; i < rasEntryNames.Length; i++)
|
||||
{
|
||||
rasEntryNames[i].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME));
|
||||
}
|
||||
|
||||
result = NativeMethods.RasEnumEntries(null, null, rasEntryNames, ref bufferSize, ref entries);
|
||||
}
|
||||
if (result == 0)
|
||||
{
|
||||
var entryNames = new List<string>();
|
||||
for (var i = 0; i < entries; i++)
|
||||
{
|
||||
entryNames.Add(rasEntryNames[i].szEntryName);
|
||||
}
|
||||
|
||||
return entryNames;
|
||||
}
|
||||
throw new ApplicationException($"RasEnumEntries failed with error code: {result}");
|
||||
}
|
||||
|
||||
#region WinInet structures
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct InternetPerConnOptionList
|
||||
{
|
||||
public int dwSize; // size of the INTERNET_PER_CONN_OPTION_LIST struct
|
||||
public nint szConnection; // connection name to set/query options
|
||||
public int dwOptionCount; // number of options to set/query
|
||||
public int dwOptionError; // on error, which option failed
|
||||
|
||||
//[MarshalAs(UnmanagedType.)]
|
||||
public nint options;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct InternetConnectionOption
|
||||
{
|
||||
private static readonly int Size;
|
||||
public PerConnOption m_Option;
|
||||
public InternetConnectionOptionValue m_Value;
|
||||
|
||||
static InternetConnectionOption()
|
||||
{
|
||||
Size = Marshal.SizeOf(typeof(InternetConnectionOption));
|
||||
}
|
||||
|
||||
// Nested Types
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct InternetConnectionOptionValue
|
||||
{
|
||||
// Fields
|
||||
[FieldOffset(0)]
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME m_FileTime;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public int m_Int;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public nint m_StringPtr;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct RASENTRYNAME
|
||||
{
|
||||
public int dwSize;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS_MaxEntryName + 1)]
|
||||
public string szEntryName;
|
||||
|
||||
public int dwFlags;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)]
|
||||
public string szPhonebookPath;
|
||||
}
|
||||
|
||||
// Constants
|
||||
public const int RAS_MaxEntryName = 256;
|
||||
|
||||
public const int MAX_PATH = 260; // Standard MAX_PATH value in Windows
|
||||
}
|
||||
|
||||
#endregion WinInet structures
|
||||
|
||||
#region WinInet enums
|
||||
|
||||
//
|
||||
// options manifests for Internet{Query|Set}Option
|
||||
//
|
||||
public enum InternetOption : uint
|
||||
{
|
||||
INTERNET_OPTION_PER_CONNECTION_OPTION = 75,
|
||||
INTERNET_OPTION_REFRESH = 37,
|
||||
INTERNET_OPTION_SETTINGS_CHANGED = 39
|
||||
}
|
||||
|
||||
//
|
||||
// Options used in INTERNET_PER_CONN_OPTON struct
|
||||
//
|
||||
public enum PerConnOption
|
||||
{
|
||||
INTERNET_PER_CONN_FLAGS = 1, // Sets or retrieves the connection type. The Value member will contain one or more of the values from PerConnFlags
|
||||
INTERNET_PER_CONN_PROXY_SERVER = 2, // Sets or retrieves a string containing the proxy servers.
|
||||
INTERNET_PER_CONN_PROXY_BYPASS = 3, // Sets or retrieves a string containing the URLs that do not use the proxy server.
|
||||
INTERNET_PER_CONN_AUTOCONFIG_URL = 4//, // Sets or retrieves a string containing the URL to the automatic configuration script.
|
||||
}
|
||||
|
||||
//
|
||||
// PER_CONN_FLAGS
|
||||
//
|
||||
[Flags]
|
||||
public enum PerConnFlags
|
||||
{
|
||||
PROXY_TYPE_DIRECT = 0x00000001, // direct to net
|
||||
PROXY_TYPE_PROXY = 0x00000002, // via named proxy
|
||||
PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy URL
|
||||
PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection
|
||||
}
|
||||
|
||||
public enum ErrorCode : uint
|
||||
{
|
||||
ERROR_BUFFER_TOO_SMALL = 603,
|
||||
ERROR_INVALID_SIZE = 632
|
||||
}
|
||||
|
||||
#endregion WinInet enums
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
[DllImport("WinInet.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool InternetSetOption(nint hInternet, InternetOption dwOption, nint lpBuffer, int dwBufferLength);
|
||||
|
||||
[DllImport("Rasapi32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern uint RasEnumEntries(
|
||||
string? reserved, // Reserved, must be null
|
||||
string? lpszPhonebook, // Pointer to full path and filename of phone-book file. If this parameter is NULL, the entries are enumerated from all the remote access phone-book files
|
||||
[In, Out] RASENTRYNAME[]? lprasentryname, // Buffer to receive RAS entry names
|
||||
ref int lpcb, // Size of the buffer
|
||||
ref int lpcEntries // Number of entries written to the buffer
|
||||
);
|
||||
}
|
||||
}
|
||||
98
v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs
Normal file
98
v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
namespace ServiceLib.Handler.SysProxy;
|
||||
|
||||
public static class SysProxyHandler
|
||||
{
|
||||
private static readonly string _tag = "SysProxyHandler";
|
||||
|
||||
public static async Task<bool> UpdateSysProxy(Config config, bool forceDisable)
|
||||
{
|
||||
var type = config.SystemProxyItem.SysProxyType;
|
||||
|
||||
if (forceDisable && type != ESysProxyType.Unchanged)
|
||||
{
|
||||
type = ESysProxyType.ForcedClear;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
if (port <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
switch (type)
|
||||
{
|
||||
case ESysProxyType.ForcedChange when Utils.IsWindows():
|
||||
{
|
||||
GetWindowsProxyString(config, port, out var strProxy, out var strExceptions);
|
||||
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
|
||||
break;
|
||||
}
|
||||
case ESysProxyType.ForcedChange when Utils.IsLinux():
|
||||
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedChange when Utils.IsOSX():
|
||||
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedClear when Utils.IsWindows():
|
||||
ProxySettingWindows.UnsetProxy();
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedClear when Utils.IsLinux():
|
||||
await ProxySettingLinux.UnsetProxy();
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedClear when Utils.IsOSX():
|
||||
await ProxySettingOSX.UnsetProxy();
|
||||
break;
|
||||
|
||||
case ESysProxyType.Pac when Utils.IsWindows():
|
||||
await SetWindowsProxyPac(port);
|
||||
break;
|
||||
}
|
||||
|
||||
if (type != ESysProxyType.Pac && Utils.IsWindows())
|
||||
{
|
||||
PacManager.Instance.Stop();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void GetWindowsProxyString(Config config, int port, out string strProxy, out string strExceptions)
|
||||
{
|
||||
strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
if (config.SystemProxyItem.NotProxyLocalAddress)
|
||||
{
|
||||
strExceptions = $"<local>;{strExceptions}";
|
||||
}
|
||||
|
||||
strProxy = string.Empty;
|
||||
if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty())
|
||||
{
|
||||
strProxy = $"{Global.Loopback}:{port}";
|
||||
}
|
||||
else
|
||||
{
|
||||
strProxy = config.SystemProxyItem.SystemProxyAdvancedProtocol
|
||||
.Replace("{ip}", Global.Loopback)
|
||||
.Replace("{http_port}", port.ToString())
|
||||
.Replace("{socks_port}", port.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SetWindowsProxyPac(int port)
|
||||
{
|
||||
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
|
||||
await PacManager.Instance.StartAsync(Utils.GetConfigPath(), port, portPac);
|
||||
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
|
||||
ProxySettingWindows.SetProxy(strProxy, "", 4);
|
||||
}
|
||||
}
|
||||
179
v2rayN/ServiceLib/Helper/DownloaderHelper.cs
Normal file
179
v2rayN/ServiceLib/Helper/DownloaderHelper.cs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
using Downloader;
|
||||
|
||||
namespace ServiceLib.Helper;
|
||||
|
||||
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 (url.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri uri = new(url);
|
||||
//Authorization Header
|
||||
var headers = new WebHeaderCollection();
|
||||
if (uri.UserInfo.IsNotEmpty())
|
||||
{
|
||||
headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo));
|
||||
}
|
||||
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
Timeout = timeout * 1000,
|
||||
MaxTryAgainOnFailure = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Headers = headers,
|
||||
UserAgent = userAgent,
|
||||
Timeout = timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
};
|
||||
|
||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||
downloader.DownloadFileCompleted += (sender, value) =>
|
||||
{
|
||||
if (value.Error != null)
|
||||
{
|
||||
throw value.Error;
|
||||
}
|
||||
};
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
|
||||
using StreamReader reader = new(stream);
|
||||
|
||||
downloadOpt = null;
|
||||
|
||||
return await reader.ReadToEndAsync(cts.Token);
|
||||
}
|
||||
|
||||
public async Task DownloadDataAsync4Speed(IWebProxy webProxy, string url, IProgress<string> progress, int timeout)
|
||||
{
|
||||
if (url.IsNullOrEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
}
|
||||
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
Timeout = timeout * 1000,
|
||||
MaxTryAgainOnFailure = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Timeout= timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
};
|
||||
|
||||
var totalDatetime = DateTime.Now;
|
||||
var totalSecond = 0;
|
||||
var hasValue = false;
|
||||
double maxSpeed = 0;
|
||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||
//downloader.DownloadStarted += (sender, value) =>
|
||||
//{
|
||||
// if (progress != null)
|
||||
// {
|
||||
// progress.Report("Start download data...");
|
||||
// }
|
||||
//};
|
||||
downloader.DownloadProgressChanged += (sender, value) =>
|
||||
{
|
||||
var 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);
|
||||
await 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 (url.IsNullOrEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
}
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileName));
|
||||
}
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
Timeout = timeout * 1000,
|
||||
MaxTryAgainOnFailure = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Timeout= timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
};
|
||||
|
||||
var progressPercentage = 0;
|
||||
var hasValue = false;
|
||||
await using var downloader = new Downloader.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;
|
||||
}
|
||||
}
|
||||
235
v2rayN/ServiceLib/Helper/HttpClientHelper.cs
Normal file
235
v2rayN/ServiceLib/Helper/HttpClientHelper.cs
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
|
||||
namespace ServiceLib.Helper;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public class HttpClientHelper
|
||||
{
|
||||
private static readonly Lazy<HttpClientHelper> _instance = new(() =>
|
||||
{
|
||||
SocketsHttpHandler 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 (url.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await httpClient.GetAsync(url);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> GetAsync(string url)
|
||||
{
|
||||
if (url.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await httpClient.GetStringAsync(url);
|
||||
}
|
||||
|
||||
public async Task<string?> GetAsync(HttpClient client, string url, CancellationToken token = default)
|
||||
{
|
||||
if (url.IsNullOrEmpty())
|
||||
{
|
||||
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);
|
||||
|
||||
await httpClient.PutAsync(url, content);
|
||||
}
|
||||
|
||||
public async Task PatchAsync(string url, Dictionary<string, string> headers)
|
||||
{
|
||||
var myContent = JsonUtils.Serialize(headers);
|
||||
var buffer = 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;
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(token);
|
||||
await 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;
|
||||
}
|
||||
await file.WriteAsync(buffer.AsMemory(0, read), token);
|
||||
|
||||
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 (url.IsNullOrEmpty())
|
||||
{
|
||||
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;
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(token);
|
||||
var totalRead = 0L;
|
||||
var buffer = new byte[1024 * 64];
|
||||
var isMoreToRead = true;
|
||||
var progressSpeed = string.Empty;
|
||||
var totalDatetime = DateTime.Now;
|
||||
var 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;
|
||||
|
||||
var 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);
|
||||
}
|
||||
|
||||
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
|
||||
{
|
||||
var 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 = new();
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
var timer = Stopwatch.StartNew();
|
||||
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
|
||||
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;
|
||||
}
|
||||
}
|
||||
89
v2rayN/ServiceLib/Helper/SqliteHelper.cs
Normal file
89
v2rayN/ServiceLib/Helper/SqliteHelper.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace ServiceLib.Helper;
|
||||
|
||||
public sealed class SQLiteHelper
|
||||
{
|
||||
private static readonly Lazy<SQLiteHelper> _instance = new(() => new());
|
||||
public static SQLiteHelper Instance => _instance.Value;
|
||||
private readonly string _connstr;
|
||||
private SQLiteConnection _db;
|
||||
private SQLiteAsyncConnection _dbAsync;
|
||||
private 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 async Task<int> InsertAllAsync(IEnumerable models)
|
||||
{
|
||||
return await _dbAsync.InsertAllAsync(models, runInTransaction: true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<int> InsertAsync(object model)
|
||||
{
|
||||
return await _dbAsync.InsertAsync(model);
|
||||
}
|
||||
|
||||
public async Task<int> ReplaceAsync(object model)
|
||||
{
|
||||
return await _dbAsync.InsertOrReplaceAsync(model);
|
||||
}
|
||||
|
||||
public async Task<int> UpdateAsync(object model)
|
||||
{
|
||||
return await _dbAsync.UpdateAsync(model);
|
||||
}
|
||||
|
||||
public async Task<int> UpdateAllAsync(IEnumerable models)
|
||||
{
|
||||
return await _dbAsync.UpdateAllAsync(models, runInTransaction: true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<int> DeleteAsync(object model)
|
||||
{
|
||||
return await _dbAsync.DeleteAsync(model);
|
||||
}
|
||||
|
||||
public async Task<int> DeleteAllAsync<T>()
|
||||
{
|
||||
return await _dbAsync.DeleteAllAsync<T>();
|
||||
}
|
||||
|
||||
public async Task<int> ExecuteAsync(string sql)
|
||||
{
|
||||
return await _dbAsync.ExecuteAsync(sql);
|
||||
}
|
||||
|
||||
public async Task<List<T>> QueryAsync<T>(string sql) where T : new()
|
||||
{
|
||||
return await _dbAsync.QueryAsync<T>(sql);
|
||||
}
|
||||
|
||||
public AsyncTableQuery<T> TableAsync<T>() where T : new()
|
||||
{
|
||||
return _dbAsync.Table<T>();
|
||||
}
|
||||
|
||||
public async Task DisposeDbConnectionAsync()
|
||||
{
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
_db?.Close();
|
||||
_db?.Dispose();
|
||||
_db = null;
|
||||
|
||||
_dbAsync?.GetConnection()?.Close();
|
||||
_dbAsync?.GetConnection()?.Dispose();
|
||||
_dbAsync = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
282
v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs
Normal file
282
v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
namespace ServiceLib.Manager;
|
||||
|
||||
/// <summary>
|
||||
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
|
||||
/// </summary>
|
||||
public class ActionPrecheckManager(Config config)
|
||||
{
|
||||
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
|
||||
public static ActionPrecheckManager Instance => _instance.Value;
|
||||
|
||||
private readonly Config _config = config;
|
||||
|
||||
public async Task<List<string>> Check(string? indexId)
|
||||
{
|
||||
if (indexId.IsNullOrEmpty())
|
||||
{
|
||||
return [ResUI.PleaseSelectServer];
|
||||
}
|
||||
|
||||
var item = await AppManager.Instance.GetProfileItem(indexId);
|
||||
if (item is null)
|
||||
{
|
||||
return [ResUI.PleaseSelectServer];
|
||||
}
|
||||
|
||||
return await Check(item);
|
||||
}
|
||||
|
||||
public async Task<List<string>> Check(ProfileItem? item)
|
||||
{
|
||||
if (item is null)
|
||||
{
|
||||
return [ResUI.PleaseSelectServer];
|
||||
}
|
||||
|
||||
var errors = new List<string>();
|
||||
|
||||
errors.AddRange(await ValidateCurrentNodeAndCoreSupport(item));
|
||||
errors.AddRange(await ValidateRelatedNodesExistAndValid(item));
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private async Task<List<string>> ValidateCurrentNodeAndCoreSupport(ProfileItem item)
|
||||
{
|
||||
if (item.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||
return await ValidateNodeAndCoreSupport(item, coreType);
|
||||
}
|
||||
|
||||
private async Task<List<string>> ValidateNodeAndCoreSupport(ProfileItem item, ECoreType? coreType = null)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
coreType ??= AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||
|
||||
if (item.ConfigType is EConfigType.Custom)
|
||||
{
|
||||
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (!item.IsComplex())
|
||||
{
|
||||
if (item.Address.IsNullOrEmpty())
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (item.Port is <= 0 or >= 65536)
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
|
||||
return errors;
|
||||
}
|
||||
|
||||
switch (item.ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id) && item.Id.Length > 30)
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
||||
if (!Global.Flows.Contains(item.Flow))
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
if (item.Id.IsNullOrEmpty())
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
||||
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
|
||||
break;
|
||||
}
|
||||
|
||||
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
|
||||
&& item.StreamSecurity == Global.StreamSecurityReality
|
||||
&& item.PublicKey.IsNullOrEmpty())
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
|
||||
}
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.ConfigType.IsGroupType())
|
||||
{
|
||||
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
|
||||
if (group is null || group.ChildItems.IsNullOrEmpty())
|
||||
{
|
||||
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
|
||||
return errors;
|
||||
}
|
||||
|
||||
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
|
||||
if (hasCycle)
|
||||
{
|
||||
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
|
||||
return errors;
|
||||
}
|
||||
|
||||
foreach (var child in Utils.String2List(group.ChildItems))
|
||||
{
|
||||
var childErrors = new List<string>();
|
||||
if (child.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var childItem = await AppManager.Instance.GetProfileItem(child);
|
||||
if (childItem is null)
|
||||
{
|
||||
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
|
||||
{
|
||||
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
|
||||
continue;
|
||||
}
|
||||
|
||||
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
|
||||
errors.AddRange(childErrors);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
var net = item.GetNetwork() ?? item.Network;
|
||||
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
// sing-box does not support xhttp / kcp
|
||||
// sing-box does not support transports like ws/http/httpupgrade/etc. when the node is not vmess/trojan/vless
|
||||
if (net is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
||||
{
|
||||
errors.Add(string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net));
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan))
|
||||
{
|
||||
if (net is nameof(ETransport.ws) or nameof(ETransport.http) or nameof(ETransport.h2) or nameof(ETransport.quic) or nameof(ETransport.httpupgrade))
|
||||
{
|
||||
errors.Add(string.Format(ResUI.CoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), item.ConfigType.ToString(), net));
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (coreType is ECoreType.Xray)
|
||||
{
|
||||
// Xray core does not support these protocols
|
||||
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
|
||||
&& !item.IsComplex())
|
||||
{
|
||||
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private async Task<List<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
errors.AddRange(await ValidateProxyChainedNodeExistAndValid(item));
|
||||
errors.AddRange(await ValidateRoutingNodeExistAndValid(item));
|
||||
return errors;
|
||||
}
|
||||
|
||||
private async Task<List<string>> ValidateProxyChainedNodeExistAndValid(ProfileItem? item)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
if (item is null)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
// prev node and next node
|
||||
var subItem = await AppManager.Instance.GetSubItem(item.Subid);
|
||||
if (subItem is null)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||
|
||||
await CollectProxyChainedNodeValidation(prevNode, subItem.PrevProfile, coreType, errors);
|
||||
await CollectProxyChainedNodeValidation(nextNode, subItem.NextProfile, coreType, errors);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private async Task CollectProxyChainedNodeValidation(ProfileItem? node, string tag, ECoreType coreType, List<string> errors)
|
||||
{
|
||||
if (node is not null)
|
||||
{
|
||||
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
|
||||
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
|
||||
}
|
||||
else if (tag.IsNotEmpty())
|
||||
{
|
||||
errors.Add(ResUI.ProxyChainedPrefix + string.Format(ResUI.NodeTagNotExist, tag));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<string>> ValidateRoutingNodeExistAndValid(ProfileItem? item)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
if (item is null)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
if (routing == null)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||
foreach (var ruleItem in rules ?? [])
|
||||
{
|
||||
if (!ruleItem.Enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var outboundTag = ruleItem.OutboundTag;
|
||||
if (outboundTag.IsNullOrEmpty() || Global.OutboundTags.Contains(outboundTag))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var tagItem = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||
if (tagItem is null)
|
||||
{
|
||||
errors.Add(ResUI.RoutingRuleOutboundPrefix + string.Format(ResUI.NodeTagNotExist, outboundTag));
|
||||
continue;
|
||||
}
|
||||
|
||||
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
|
||||
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue