Compare commits
463 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Set update schedule for GitHub Actions
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every daily
|
||||
interval: "daily"
|
||||
77
.github/workflows/build-linux.yml
vendored
Normal file
77
.github/workflows/build-linux.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
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-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cd v2rayN
|
||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -o $OutputPath64
|
||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -o $OutputPathArm64
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-x64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:PublishTrimmed=true -o $OutputPath64
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
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
|
||||
|
||||
# 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
|
||||
78
.github/workflows/build-osx.yml
vendored
Normal file
78
.github/workflows/build-osx.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
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@v4
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cd v2rayN
|
||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -o $OutputPath64
|
||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -o $OutputPathArm64
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-x64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:PublishTrimmed=true -o $OutputPath64
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
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
|
||||
|
||||
# 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.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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: actions/setup-dotnet@v4
|
||||
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:PublishReadyToRun=false -p:PublishSingleFile=true -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 --self-contained false -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained false -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained false -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained true -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:PublishTrimmed=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
|
||||
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
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 自动化部署,更新日志应该很快会手动更新
|
||||
31
.github/workflows/winget-publish.yml
vendored
Normal file
31
.github/workflows/winget-publish.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
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
|
||||
$installerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip*' | Select -ExpandProperty browser_download_url
|
||||
|
||||
$ver = $targetRelease.tag_name
|
||||
|
||||
# getting latest wingetcreate file
|
||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||
.\wingetcreate.exe update $wingetPackage -s -v $ver -u "$installerUrl|x64" -t $gitToken
|
||||
23
README.md
23
README.md
@@ -1,5 +1,5 @@
|
||||
# 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 core](https://github.com/XTLS/Xray-core) and [sing-box-core](https://github.com/SagerNet/sing-box/releases) and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
|
||||
|
||||
[](https://github.com/2dust/v2rayN/commits/master)
|
||||
@@ -9,13 +9,24 @@ A GUI client for Windows, support [Xray core](https://github.com/XTLS/Xray-core)
|
||||
|
||||
|
||||
## 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
|
||||
Check [Release files introduction](https://github.com/2dust/v2rayN/wiki/Release-files-introduction) and select the version you need to download
|
||||
### Windows
|
||||
- Run `v2rayN.exe`
|
||||
### Linux
|
||||
- `chmod +x v2rayN` Run `./v2rayN` under user permissions
|
||||
```
|
||||
Debian 9+
|
||||
Ubuntu 16.04+
|
||||
Fedora 30+
|
||||
```
|
||||
### macOS
|
||||
- `chmod +x v2rayN` Run `./v2rayN` under user permissions
|
||||
```
|
||||
macOS 11+
|
||||
```
|
||||
|
||||
## 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)
|
||||
- [Microsoft .NET 8.0 Desktop Runtime ](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
- [Supported cores](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
|
||||
|
||||
|
||||
58
package-debian.sh
Normal file
58
package-debian.sh
Normal 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}/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
|
||||
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"
|
||||
|
||||
# desktop && PATH
|
||||
|
||||
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
|
||||
27
v2rayN/AmazTool/AmazTool.csproj
Normal file
27
v2rayN/AmazTool/AmazTool.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Copyright>Copyright © 2017-2025 (GPLv3)</Copyright>
|
||||
<FileVersion>1.3.1</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resx\Resource.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resource.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resx\Resource.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
29
v2rayN/AmazTool/Program.cs
Normal file
29
v2rayN/AmazTool/Program.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace AmazTool
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用程序的主入口点。
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine(Resx.Resource.Guidelines);
|
||||
Thread.Sleep(5000);
|
||||
return;
|
||||
}
|
||||
|
||||
var argData = Uri.UnescapeDataString(string.Join(" ", args));
|
||||
if (argData.Equals("rebootas"))
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
Utils.StartV2RayN();
|
||||
return;
|
||||
}
|
||||
|
||||
UpgradeApp.Upgrade(argData);
|
||||
}
|
||||
}
|
||||
}
|
||||
171
v2rayN/AmazTool/Resx/Resource.Designer.cs
generated
Normal file
171
v2rayN/AmazTool/Resx/Resource.Designer.cs
generated
Normal file
@@ -0,0 +1,171 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 此代码由工具生成。
|
||||
// 运行时版本:4.0.30319.42000
|
||||
//
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果
|
||||
// 重新生成代码,这些更改将会丢失。
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace AmazTool.Resx {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 一个强类型的资源类,用于查找本地化的字符串等。
|
||||
/// </summary>
|
||||
// 此类是由 StronglyTypedResourceBuilder
|
||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resource {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resource() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回此类使用的缓存的 ResourceManager 实例。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AmazTool.Resx.Resource", typeof(Resource).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写当前线程的 CurrentUICulture 属性,对
|
||||
/// 使用此强类型资源类的所有资源查找执行重写。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Failed to terminate the v2rayN.Close it manually,or the upgrade may fail. 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string FailedTerminateProcess {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedTerminateProcess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Failed to extract the update package. 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string FailedUnzipping {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedUnzipping", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Upgrade failed. 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string FailedUpgrade {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedUpgrade", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please run it from the main application. 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string Guidelines {
|
||||
get {
|
||||
return ResourceManager.GetString("Guidelines", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Information 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string Information {
|
||||
get {
|
||||
return ResourceManager.GetString("Information", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 In progress, please wait... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string InProgress {
|
||||
get {
|
||||
return ResourceManager.GetString("InProgress", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Start v2rayN, please wait... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string Restartv2rayN {
|
||||
get {
|
||||
return ResourceManager.GetString("Restartv2rayN", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Start extracting the update package... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string StartUnzipping {
|
||||
get {
|
||||
return ResourceManager.GetString("StartUnzipping", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Successfully extracted the update package. 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string SuccessUnzipping {
|
||||
get {
|
||||
return ResourceManager.GetString("SuccessUnzipping", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Upgrade success. 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string SuccessUpgrade {
|
||||
get {
|
||||
return ResourceManager.GetString("SuccessUpgrade", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Try to terminate the v2rayN process... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string TryTerminateProcess {
|
||||
get {
|
||||
return ResourceManager.GetString("TryTerminateProcess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Upgrade failed, file not found. 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string UpgradeFileNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("UpgradeFileNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
156
v2rayN/AmazTool/Resx/Resource.resx
Normal file
156
v2rayN/AmazTool/Resx/Resource.resx
Normal file
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Restartv2rayN" xml:space="preserve">
|
||||
<value>Start v2rayN, please wait...</value>
|
||||
</data>
|
||||
<data name="Guidelines" xml:space="preserve">
|
||||
<value>Please run it from the main application.</value>
|
||||
</data>
|
||||
<data name="UpgradeFileNotFound" xml:space="preserve">
|
||||
<value>Upgrade failed, file not found.</value>
|
||||
</data>
|
||||
<data name="InProgress" xml:space="preserve">
|
||||
<value>In progress, please wait...</value>
|
||||
</data>
|
||||
<data name="TryTerminateProcess" xml:space="preserve">
|
||||
<value>Try to terminate the v2rayN process...</value>
|
||||
</data>
|
||||
<data name="FailedTerminateProcess" xml:space="preserve">
|
||||
<value>Failed to terminate the v2rayN.Close it manually,or the upgrade may fail.</value>
|
||||
</data>
|
||||
<data name="StartUnzipping" xml:space="preserve">
|
||||
<value>Start extracting the update package...</value>
|
||||
</data>
|
||||
<data name="SuccessUnzipping" xml:space="preserve">
|
||||
<value>Successfully extracted the update package.</value>
|
||||
</data>
|
||||
<data name="FailedUnzipping" xml:space="preserve">
|
||||
<value>Failed to extract the update package.</value>
|
||||
</data>
|
||||
<data name="FailedUpgrade" xml:space="preserve">
|
||||
<value>Upgrade failed.</value>
|
||||
</data>
|
||||
<data name="SuccessUpgrade" xml:space="preserve">
|
||||
<value>Upgrade success.</value>
|
||||
</data>
|
||||
<data name="Information" xml:space="preserve">
|
||||
<value>Information</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
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>
|
||||
104
v2rayN/AmazTool/UpgradeApp.cs
Normal file
104
v2rayN/AmazTool/UpgradeApp.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
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;
|
||||
}
|
||||
entry.ExtractToFile(entryOutputPath, true);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
52
v2rayN/AmazTool/Utils.cs
Normal file
52
v2rayN/AmazTool/Utils.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
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)
|
||||
{
|
||||
string 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PacLib;
|
||||
|
||||
public class PacHandler
|
||||
{
|
||||
private static string _configPath;
|
||||
private static int _httpPort;
|
||||
private static int _pacPort;
|
||||
private static TcpListener? _tcpListener;
|
||||
private static string _pacText;
|
||||
private static bool _isRunning;
|
||||
private static bool _needRestart = true;
|
||||
|
||||
public static void Start(string configPath, int httpPort, int pacPort)
|
||||
{
|
||||
_needRestart = (configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning);
|
||||
|
||||
_configPath = configPath;
|
||||
_httpPort = httpPort;
|
||||
_pacPort = pacPort;
|
||||
|
||||
InitText();
|
||||
|
||||
if (_needRestart)
|
||||
{
|
||||
Stop();
|
||||
RunListener();
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitText()
|
||||
{
|
||||
var path = Path.Combine(_configPath, "pac.txt");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
File.AppendAllText(path, Resources.ResourceManager.GetString("pac"));
|
||||
}
|
||||
|
||||
_pacText = File.ReadAllText(path).Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;");
|
||||
}
|
||||
|
||||
private static void RunListener()
|
||||
{
|
||||
_tcpListener = TcpListener.Create(_pacPort);
|
||||
_isRunning = true;
|
||||
_tcpListener.Start();
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
while (_isRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_tcpListener.Pending())
|
||||
{
|
||||
await Task.Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
var client = _tcpListener.AcceptTcpClient();
|
||||
await Task.Run(() =>
|
||||
{
|
||||
var stream = client.GetStream();
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("HTTP/1.0 200 OK");
|
||||
sb.AppendLine("Content-type:application/x-ns-proxy-autoconfig");
|
||||
sb.AppendLine("Connection:close");
|
||||
sb.AppendLine("Content-Length:" + Encoding.UTF8.GetByteCount(_pacText));
|
||||
sb.AppendLine();
|
||||
sb.Append(_pacText);
|
||||
var content = Encoding.UTF8.GetBytes(sb.ToString());
|
||||
stream.Write(content, 0, content.Length);
|
||||
stream.Flush();
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
if (_tcpListener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_isRunning = false;
|
||||
_tcpListener.Stop();
|
||||
_tcpListener = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<EmbeddedResource Update="Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
95
v2rayN/PacLib/Resources.Designer.cs
generated
95
v2rayN/PacLib/Resources.Designer.cs
generated
@@ -1,95 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 此代码由工具生成。
|
||||
// 运行时版本:4.0.30319.42000
|
||||
//
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果
|
||||
// 重新生成代码,这些更改将会丢失。
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace PacLib {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 一个强类型的资源类,用于查找本地化的字符串等。
|
||||
/// </summary>
|
||||
// 此类是由 StronglyTypedResourceBuilder
|
||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回此类使用的缓存的 ResourceManager 实例。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PacLib.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写当前线程的 CurrentUICulture 属性,对
|
||||
/// 使用此强类型资源类的所有资源查找执行重写。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 var proxy = '__PROXY__';
|
||||
///var rules = [
|
||||
/// [
|
||||
/// [],
|
||||
/// []
|
||||
/// ],
|
||||
/// [
|
||||
/// [
|
||||
/// "aftygh.gov.tw",
|
||||
/// "aide.gov.tw",
|
||||
/// "aliyun.com",
|
||||
/// "arte.gov.tw",
|
||||
/// "baidu.com",
|
||||
/// "chinaso.com",
|
||||
/// "chinaz.com",
|
||||
/// "chukuang.gov.tw",
|
||||
/// "cycab.gov.tw",
|
||||
/// "dbnsa.gov.tw",
|
||||
/// "df.gov.tw",
|
||||
/// "eastcoast-nsa.gov.tw",
|
||||
/// "erv-nsa.gov.tw",
|
||||
/// "grb.gov.tw",
|
||||
/// "haosou.com",
|
||||
/// [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string pac {
|
||||
get {
|
||||
return ResourceManager.GetString("pac", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Statistics.proto" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.28.0" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.65.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.66.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,5 @@ namespace ServiceLib.Base
|
||||
{
|
||||
protected static Config? _config;
|
||||
protected Func<EViewAction, object?, Task<bool>>? _updateView;
|
||||
protected NoticeHandler? _noticeHandler;
|
||||
}
|
||||
}
|
||||
101
v2rayN/ServiceLib/Common/AesUtils.cs
Normal file
101
v2rayN/ServiceLib/Common/AesUtils.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public class AesUtils
|
||||
{
|
||||
private const int KeySize = 256; // AES-256
|
||||
private const int IvSize = 16; // AES block size
|
||||
private const int Iterations = 10000;
|
||||
private static readonly byte[] Salt = Encoding.ASCII.GetBytes("saltysalt".PadRight(16, ' ')); // google浏览器默认盐值
|
||||
private static readonly string DefaultPassword = Utils.GetMd5(Utils.GetHomePath() + "AesUtils");
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt
|
||||
/// </summary>
|
||||
/// <param name="text">Plain text</param>
|
||||
/// <param name="password">Password for key derivation or direct key in ASCII bytes</param>
|
||||
/// <returns>Base64 encoded cipher text with IV</returns>
|
||||
public static string Encrypt(string text, string? password = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return string.Empty;
|
||||
|
||||
var plaintext = Encoding.UTF8.GetBytes(text);
|
||||
var key = GetKey(password);
|
||||
var iv = GenerateIv();
|
||||
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
ms.Write(iv, 0, iv.Length);
|
||||
|
||||
using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(plaintext, 0, plaintext.Length);
|
||||
cs.FlushFinalBlock();
|
||||
}
|
||||
|
||||
var cipherTextWithIv = ms.ToArray();
|
||||
return Convert.ToBase64String(cipherTextWithIv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt
|
||||
/// </summary>
|
||||
/// <param name="cipherTextWithIv">Base64 encoded cipher text with IV</param>
|
||||
/// <param name="password">Password for key derivation or direct key in ASCII bytes</param>
|
||||
/// <returns>Plain text</returns>
|
||||
public static string Decrypt(string cipherTextWithIv, string? password = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cipherTextWithIv))
|
||||
return string.Empty;
|
||||
|
||||
var cipherTextWithIvBytes = Convert.FromBase64String(cipherTextWithIv);
|
||||
var key = GetKey(password);
|
||||
|
||||
var iv = new byte[IvSize];
|
||||
Buffer.BlockCopy(cipherTextWithIvBytes, 0, iv, 0, IvSize);
|
||||
|
||||
var cipherText = new byte[cipherTextWithIvBytes.Length - IvSize];
|
||||
Buffer.BlockCopy(cipherTextWithIvBytes, IvSize, cipherText, 0, cipherText.Length);
|
||||
|
||||
using var aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(cipherText, 0, cipherText.Length);
|
||||
cs.FlushFinalBlock();
|
||||
}
|
||||
|
||||
var plainText = ms.ToArray();
|
||||
return Encoding.UTF8.GetString(plainText);
|
||||
}
|
||||
|
||||
private static byte[] GetKey(string? password)
|
||||
{
|
||||
if (password.IsNullOrEmpty())
|
||||
{
|
||||
password = DefaultPassword;
|
||||
}
|
||||
|
||||
using var pbkdf2 = new Rfc2898DeriveBytes(password, Salt, Iterations, HashAlgorithmName.SHA256);
|
||||
return pbkdf2.GetBytes(KeySize / 8);
|
||||
}
|
||||
|
||||
private static byte[] GenerateIv()
|
||||
{
|
||||
var randomNumber = new byte[IvSize];
|
||||
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(randomNumber);
|
||||
return randomNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
v2rayN/ServiceLib/Common/DesUtils.cs
Normal file
75
v2rayN/ServiceLib/Common/DesUtils.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public class DesUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Encrypt
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// /// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public static string Encrypt(string? text, string? key = null)
|
||||
{
|
||||
if (text.IsNullOrEmpty())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv);
|
||||
var dsp = DES.Create();
|
||||
using var memStream = new MemoryStream();
|
||||
using var cryStream = new CryptoStream(memStream, dsp.CreateEncryptor(rgbKey, rgbIv), CryptoStreamMode.Write);
|
||||
using var sWriter = new StreamWriter(cryStream);
|
||||
sWriter.Write(text);
|
||||
sWriter.Flush();
|
||||
cryStream.FlushFinalBlock();
|
||||
memStream.Flush();
|
||||
return Convert.ToBase64String(memStream.GetBuffer(), 0, (int)memStream.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt
|
||||
/// </summary>
|
||||
/// <param name="encryptText"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public static string Decrypt(string? encryptText, string? key = null)
|
||||
{
|
||||
if (encryptText.IsNullOrEmpty())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv);
|
||||
var dsp = DES.Create();
|
||||
var buffer = Convert.FromBase64String(encryptText);
|
||||
|
||||
using var memStream = new MemoryStream();
|
||||
using var cryStream = new CryptoStream(memStream, dsp.CreateDecryptor(rgbKey, rgbIv), CryptoStreamMode.Write);
|
||||
cryStream.Write(buffer, 0, buffer.Length);
|
||||
cryStream.FlushFinalBlock();
|
||||
return Encoding.UTF8.GetString(memStream.ToArray());
|
||||
}
|
||||
|
||||
private static void GetKeyIv(string key, out byte[] rgbKey, out byte[] rgbIv)
|
||||
{
|
||||
if (key.IsNullOrEmpty())
|
||||
{
|
||||
throw new ArgumentNullException("The key cannot be null");
|
||||
}
|
||||
if (key.Length <= 8)
|
||||
{
|
||||
throw new ArgumentNullException("The key length cannot be less than 8 characters.");
|
||||
}
|
||||
|
||||
rgbKey = Encoding.ASCII.GetBytes(key.Substring(0, 8));
|
||||
rgbIv = Encoding.ASCII.GetBytes(key.Insert(0, "w").Substring(0, 8));
|
||||
}
|
||||
|
||||
private static string GetDefaultKey()
|
||||
{
|
||||
return Utils.GetMd5(Utils.GetHomePath() + "DesUtils");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace ServiceLib.Common
|
||||
Uri uri = new(url);
|
||||
//Authorization Header
|
||||
var headers = new WebHeaderCollection();
|
||||
if (!Utils.IsNullOrEmpty(uri.UserInfo))
|
||||
if (Utils.IsNotEmpty(uri.UserInfo))
|
||||
{
|
||||
headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo));
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace ServiceLib.Common
|
||||
}
|
||||
};
|
||||
|
||||
using var downloader = new DownloadService(downloadOpt);
|
||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||
downloader.DownloadFileCompleted += (sender, value) =>
|
||||
{
|
||||
if (value.Error != null)
|
||||
@@ -46,12 +46,12 @@ namespace ServiceLib.Common
|
||||
};
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
|
||||
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 reader.ReadToEnd();
|
||||
return await reader.ReadToEndAsync(cts.Token);
|
||||
}
|
||||
|
||||
public async Task DownloadDataAsync4Speed(IWebProxy webProxy, string url, IProgress<string> progress, int timeout)
|
||||
@@ -72,11 +72,11 @@ namespace ServiceLib.Common
|
||||
}
|
||||
};
|
||||
|
||||
DateTime totalDatetime = DateTime.Now;
|
||||
int totalSecond = 0;
|
||||
var totalDatetime = DateTime.Now;
|
||||
var totalSecond = 0;
|
||||
var hasValue = false;
|
||||
double maxSpeed = 0;
|
||||
using var downloader = new DownloadService(downloadOpt);
|
||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||
//downloader.DownloadStarted += (sender, value) =>
|
||||
//{
|
||||
// if (progress != null)
|
||||
@@ -86,7 +86,7 @@ namespace ServiceLib.Common
|
||||
//};
|
||||
downloader.DownloadProgressChanged += (sender, value) =>
|
||||
{
|
||||
TimeSpan ts = (DateTime.Now - totalDatetime);
|
||||
var ts = (DateTime.Now - totalDatetime);
|
||||
if (progress != null && ts.Seconds > totalSecond)
|
||||
{
|
||||
hasValue = true;
|
||||
@@ -112,7 +112,7 @@ namespace ServiceLib.Common
|
||||
//progress.Report("......");
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(timeout * 1000);
|
||||
using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
||||
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
||||
|
||||
downloadOpt = null;
|
||||
}
|
||||
@@ -145,7 +145,7 @@ namespace ServiceLib.Common
|
||||
|
||||
var progressPercentage = 0;
|
||||
var hasValue = false;
|
||||
using var downloader = new DownloadService(downloadOpt);
|
||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||
downloader.DownloadStarted += (sender, value) =>
|
||||
{
|
||||
progress?.Report(0);
|
||||
@@ -176,7 +176,7 @@ namespace ServiceLib.Common
|
||||
};
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
await downloader.DownloadFileTaskAsync(url, fileName, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
|
||||
await downloader.DownloadFileTaskAsync(url, fileName, cts.Token);
|
||||
|
||||
downloadOpt = null;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using System.IO.Compression;
|
||||
using System.Formats.Tar;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public static class FileManager
|
||||
{
|
||||
private static readonly string _tag = "FileManager";
|
||||
|
||||
public static bool ByteArrayToFile(string fileName, byte[] content)
|
||||
{
|
||||
try
|
||||
@@ -14,38 +17,52 @@ namespace ServiceLib.Common
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void UncompressedFile(string fileName, byte[] content)
|
||||
public static void DecompressFile(string fileName, byte[] content)
|
||||
{
|
||||
try
|
||||
{
|
||||
using FileStream fs = File.Create(fileName);
|
||||
using var fs = File.Create(fileName);
|
||||
using GZipStream input = new(new MemoryStream(content), CompressionMode.Decompress, false);
|
||||
input.CopyTo(fs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UncompressedFile(string fileName, string toPath, string? toName)
|
||||
public static void DecompressFile(string fileName, string toPath, string? toName)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileInfo fileInfo = new(fileName);
|
||||
using FileStream originalFileStream = fileInfo.OpenRead();
|
||||
using FileStream decompressedFileStream = File.Create(toName != null ? Path.Combine(toPath, toName) : toPath);
|
||||
using 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(ex.Message, 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +71,7 @@ namespace ServiceLib.Common
|
||||
return NonExclusiveReadAllText(path, Encoding.Default);
|
||||
}
|
||||
|
||||
public static string NonExclusiveReadAllText(string path, Encoding encoding)
|
||||
private static string NonExclusiveReadAllText(string path, Encoding encoding)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -64,7 +81,7 @@ namespace ServiceLib.Common
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -73,8 +90,8 @@ namespace ServiceLib.Common
|
||||
{
|
||||
try
|
||||
{
|
||||
using ZipArchive archive = ZipFile.OpenRead(fileName);
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
using var archive = ZipFile.OpenRead(fileName);
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
if (entry.Length == 0)
|
||||
{
|
||||
@@ -82,7 +99,7 @@ namespace ServiceLib.Common
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(ignoredName) && entry.Name.Contains(ignoredName))
|
||||
if (Utils.IsNotEmpty(ignoredName) && entry.Name.Contains(ignoredName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -90,31 +107,119 @@ namespace ServiceLib.Common
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, 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
|
||||
{
|
||||
ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName);
|
||||
if (File.Exists(destinationArchiveFileName))
|
||||
{
|
||||
File.Delete(destinationArchiveFileName);
|
||||
}
|
||||
|
||||
ZipFile.CreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, CompressionLevel.SmallestSize, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, 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 (Utils.IsNotEmpty(ignoredName) && 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace ServiceLib.Common
|
||||
{
|
||||
private static readonly Lazy<HttpClientHelper> _instance = new(() =>
|
||||
{
|
||||
HttpClientHandler handler = new() { UseCookies = false };
|
||||
SocketsHttpHandler handler = new() { UseCookies = false };
|
||||
HttpClientHelper helper = new(new HttpClient(handler));
|
||||
return helper;
|
||||
});
|
||||
@@ -22,12 +22,12 @@ namespace ServiceLib.Common
|
||||
|
||||
public async Task<string?> TryGetAsync(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
if (Utils.IsNullOrEmpty(url))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await httpClient.GetAsync(url);
|
||||
var response = await httpClient.GetAsync(url);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
@@ -84,8 +84,8 @@ namespace ServiceLib.Common
|
||||
var total = response.Content.Headers.ContentLength ?? -1L;
|
||||
var canReportProgress = total != -1 && progress != null;
|
||||
|
||||
using var stream = await response.Content.ReadAsStreamAsync(token);
|
||||
using var file = File.Create(fileName);
|
||||
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;
|
||||
@@ -98,7 +98,7 @@ namespace ServiceLib.Common
|
||||
totalRead += read;
|
||||
|
||||
if (read == 0) break;
|
||||
file.Write(buffer, 0, read);
|
||||
await file.WriteAsync(buffer.AsMemory(0, read), token);
|
||||
|
||||
if (canReportProgress)
|
||||
{
|
||||
@@ -133,13 +133,13 @@ namespace ServiceLib.Common
|
||||
//var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
|
||||
//var canReportProgress = total != -1 && progress != null;
|
||||
|
||||
using var stream = await response.Content.ReadAsStreamAsync(token);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(token);
|
||||
var totalRead = 0L;
|
||||
var buffer = new byte[1024 * 64];
|
||||
var isMoreToRead = true;
|
||||
string progressSpeed = string.Empty;
|
||||
DateTime totalDatetime = DateTime.Now;
|
||||
int totalSecond = 0;
|
||||
var progressSpeed = string.Empty;
|
||||
var totalDatetime = DateTime.Now;
|
||||
var totalSecond = 0;
|
||||
|
||||
do
|
||||
{
|
||||
@@ -168,7 +168,7 @@ namespace ServiceLib.Common
|
||||
|
||||
totalRead += read;
|
||||
|
||||
TimeSpan ts = (DateTime.Now - totalDatetime);
|
||||
var ts = (DateTime.Now - totalDatetime);
|
||||
if (progress != null && ts.Seconds > totalSecond)
|
||||
{
|
||||
totalSecond = ts.Seconds;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace ServiceLib.Common
|
||||
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
|
||||
*/
|
||||
|
||||
public class Job : IDisposable
|
||||
public sealed class Job : IDisposable
|
||||
{
|
||||
private IntPtr handle = IntPtr.Zero;
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace ServiceLib.Common
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace ServiceLib.Common
|
||||
{
|
||||
public class JsonUtils
|
||||
{
|
||||
private static readonly string _tag = "JsonUtils";
|
||||
|
||||
/// <summary>
|
||||
/// DeepCopy
|
||||
/// </summary>
|
||||
@@ -31,7 +33,11 @@ namespace ServiceLib.Common
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return JsonSerializer.Deserialize<T>(strJson);
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
return JsonSerializer.Deserialize<T>(strJson, options);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -66,10 +72,11 @@ namespace ServiceLib.Common
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="indented"></param>
|
||||
/// <param name="nullValue"></param>
|
||||
/// <returns></returns>
|
||||
public static string Serialize(object? obj, bool indented = true)
|
||||
public static string Serialize(object? obj, bool indented = true, bool nullValue = false)
|
||||
{
|
||||
string result = string.Empty;
|
||||
var result = string.Empty;
|
||||
try
|
||||
{
|
||||
if (obj == null)
|
||||
@@ -78,14 +85,38 @@ namespace ServiceLib.Common
|
||||
}
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = indented ? true : false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
WriteIndented = indented,
|
||||
DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
result = JsonSerializer.Serialize(obj, options);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, 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;
|
||||
}
|
||||
@@ -96,38 +127,5 @@ namespace ServiceLib.Common
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static JsonNode? SerializeToNode(object? obj) => JsonSerializer.SerializeToNode(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Save as json file
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="nullValue"></param>
|
||||
/// <returns></returns>
|
||||
public static int ToFile(object? obj, string? filePath, bool nullValue = true)
|
||||
{
|
||||
if (filePath is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
try
|
||||
{
|
||||
using FileStream file = File.Create(filePath);
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
JsonSerializer.Serialize(file, obj, options);
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,52 +25,23 @@ namespace ServiceLib.Common
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearLogs()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.Now.AddMonths(-1);
|
||||
var dir = Utils.GetLogPath();
|
||||
var files = Directory.GetFiles(dir, "*.txt");
|
||||
foreach (var filePath in files)
|
||||
{
|
||||
var file = new FileInfo(filePath);
|
||||
if (file.CreationTime < now)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
}
|
||||
|
||||
public static void SaveLog(string strContent)
|
||||
{
|
||||
if (LogManager.IsLoggingEnabled())
|
||||
{
|
||||
var logger = LogManager.GetLogger("Log1");
|
||||
logger.Info(strContent);
|
||||
}
|
||||
if (!LogManager.IsLoggingEnabled()) return;
|
||||
|
||||
LogManager.GetLogger("Log1").Info(strContent);
|
||||
}
|
||||
|
||||
public static void SaveLog(string strTitle, Exception ex)
|
||||
{
|
||||
if (LogManager.IsLoggingEnabled())
|
||||
if (!LogManager.IsLoggingEnabled()) return;
|
||||
|
||||
var logger = LogManager.GetLogger("Log2");
|
||||
logger.Debug($"{strTitle},{ex.Message}");
|
||||
logger.Debug(ex.StackTrace);
|
||||
if (ex?.InnerException != null)
|
||||
{
|
||||
var logger = LogManager.GetLogger("Log2");
|
||||
logger.Debug($"{strTitle},{ex.Message}");
|
||||
logger.Debug(ex.StackTrace);
|
||||
if (ex?.InnerException != null)
|
||||
{
|
||||
logger.Error(ex.InnerException);
|
||||
}
|
||||
logger.Error(ex.InnerException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
139
v2rayN/ServiceLib/Common/ProcUtils.cs
Normal file
139
v2rayN/ServiceLib/Common/ProcUtils.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
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 process = new()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
UseShellExecute = true,
|
||||
FileName = fileName,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = dir
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
return process.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);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ProcessKill(int pid)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessKill(Process.GetProcessById(pid), false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ProcessKill(Process? proc, bool review)
|
||||
{
|
||||
if (proc is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetProcessKeyInfo(proc, review, out var procId, out var fileName, out var processName);
|
||||
|
||||
try { proc?.Kill(true); } catch (Exception ex) { Logging.SaveLog(_tag, ex); }
|
||||
try { proc?.Kill(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); }
|
||||
try { proc?.Close(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); }
|
||||
try { proc?.Dispose(); } catch (Exception ex) { Logging.SaveLog(_tag, ex); }
|
||||
|
||||
await Task.Delay(300);
|
||||
await ProcessKillByKeyInfo(review, procId, fileName, processName);
|
||||
}
|
||||
|
||||
private static void GetProcessKeyInfo(Process? proc, bool review, out int? procId, out string? fileName, out string? processName)
|
||||
{
|
||||
procId = null;
|
||||
fileName = null;
|
||||
processName = null;
|
||||
if (!review) return;
|
||||
try
|
||||
{
|
||||
procId = proc?.Id;
|
||||
fileName = proc?.MainModule?.FileName;
|
||||
processName = proc?.ProcessName;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ProcessKillByKeyInfo(bool review, int? procId, string? fileName, string? processName)
|
||||
{
|
||||
if (review && procId != null && fileName != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lstProc = Process.GetProcessesByName(processName);
|
||||
foreach (var proc2 in lstProc)
|
||||
{
|
||||
if (proc2.Id == procId)
|
||||
{
|
||||
Logging.SaveLog($"{_tag}, KillProcess not completing the job, procId");
|
||||
await ProcessKill(proc2, false);
|
||||
}
|
||||
if (proc2.MainModule != null && proc2.MainModule?.FileName == fileName)
|
||||
{
|
||||
Logging.SaveLog($"{_tag}, KillProcess not completing the job, fileName");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using QRCoder;
|
||||
using SkiaSharp;
|
||||
using ZXing.SkiaSharp;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
@@ -7,9 +9,82 @@ namespace ServiceLib.Common
|
||||
public static byte[]? GenQRCode(string? url)
|
||||
{
|
||||
using QRCodeGenerator qrGenerator = new();
|
||||
using QRCodeData qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q);
|
||||
using var qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q);
|
||||
using PngByteQRCode qrCode = new(qrCodeData);
|
||||
return qrCode.GetGraphic(20);
|
||||
}
|
||||
|
||||
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 && Utils.IsNotEmpty(result.Text))
|
||||
{
|
||||
return result.Text;
|
||||
}
|
||||
|
||||
//FlipBitmap
|
||||
var result2 = reader.Decode(FlipBitmap(bitmap));
|
||||
return result2?.Text;
|
||||
}
|
||||
|
||||
private static SKBitmap FlipBitmap(SKBitmap bmp)
|
||||
{
|
||||
// Create a bitmap (to return)
|
||||
var flipped = new SKBitmap(bmp.Width, bmp.Height, bmp.Info.ColorType, bmp.Info.AlphaType);
|
||||
|
||||
// Create a canvas to draw into the bitmap
|
||||
using var canvas = new SKCanvas(flipped);
|
||||
|
||||
// Set a transform matrix which moves the bitmap to the right,
|
||||
// and then "scales" it by -1, which just flips the pixels
|
||||
// horizontally
|
||||
canvas.Translate(bmp.Width, 0);
|
||||
canvas.Scale(-1, 1);
|
||||
canvas.DrawBitmap(bmp, 0, 0);
|
||||
return flipped;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ServiceLib.Common
|
||||
{
|
||||
public static class QueryableExtension
|
||||
{
|
||||
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName)
|
||||
{
|
||||
return _OrderBy<T>(query, propertyName, false);
|
||||
}
|
||||
|
||||
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName)
|
||||
{
|
||||
return _OrderBy<T>(query, propertyName, true);
|
||||
}
|
||||
|
||||
private static IOrderedQueryable<T> _OrderBy<T>(IQueryable<T> query, string propertyName, bool isDesc)
|
||||
{
|
||||
string methodname = (isDesc) ? "OrderByDescendingInternal" : "OrderByInternal";
|
||||
|
||||
var memberProp = typeof(T).GetProperty(propertyName);
|
||||
|
||||
var method = typeof(QueryableExtension).GetMethod(methodname)
|
||||
.MakeGenericMethod(typeof(T), memberProp.PropertyType);
|
||||
|
||||
return (IOrderedQueryable<T>)method.Invoke(null, new object[] { query, memberProp });
|
||||
}
|
||||
|
||||
public static IOrderedQueryable<T> OrderByInternal<T, TProp>(IQueryable<T> query, PropertyInfo memberProperty)
|
||||
{//public
|
||||
return query.OrderBy(_GetLambda<T, TProp>(memberProperty));
|
||||
}
|
||||
|
||||
public static IOrderedQueryable<T> OrderByDescendingInternal<T, TProp>(IQueryable<T> query, PropertyInfo memberProperty)
|
||||
{//public
|
||||
return query.OrderByDescending(_GetLambda<T, TProp>(memberProperty));
|
||||
}
|
||||
|
||||
private static Expression<Func<T, TProp>> _GetLambda<T, TProp>(PropertyInfo memberProperty)
|
||||
{
|
||||
if (memberProperty.PropertyType != typeof(TProp)) throw new Exception();
|
||||
|
||||
var thisArg = Expression.Parameter(typeof(T));
|
||||
var lambda = Expression.Lambda<Func<T, TProp>>(Expression.Property(thisArg, memberProperty), thisArg);
|
||||
|
||||
return lambda;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,19 +15,27 @@
|
||||
this.version = $"{major}.{minor}.{patch}";
|
||||
}
|
||||
|
||||
public SemanticVersion(string version)
|
||||
public SemanticVersion(string? version)
|
||||
{
|
||||
this.version = version.RemovePrefix('v');
|
||||
try
|
||||
{
|
||||
string[] parts = this.version.Split('.');
|
||||
if (version.IsNullOrEmpty())
|
||||
{
|
||||
this.major = 0;
|
||||
this.minor = 0;
|
||||
this.patch = 0;
|
||||
return;
|
||||
}
|
||||
this.version = version.RemovePrefix('v');
|
||||
|
||||
var parts = this.version.Split('.');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
this.major = int.Parse(parts[0]);
|
||||
this.minor = int.Parse(parts[1]);
|
||||
this.major = int.Parse(parts.First());
|
||||
this.minor = int.Parse(parts.Last());
|
||||
this.patch = 0;
|
||||
}
|
||||
else if (parts.Length == 3)
|
||||
else if (parts.Length is 3 or 4)
|
||||
{
|
||||
this.major = int.Parse(parts[0]);
|
||||
this.minor = int.Parse(parts[1]);
|
||||
@@ -43,7 +51,6 @@
|
||||
this.major = 0;
|
||||
this.minor = 0;
|
||||
this.patch = 0;
|
||||
//this.version = "0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ namespace ServiceLib.Common
|
||||
private string _connstr;
|
||||
private SQLiteConnection _db;
|
||||
private SQLiteAsyncConnection _dbAsync;
|
||||
private static readonly object objLock = new();
|
||||
public readonly string _configDB = "guiNDB.db";
|
||||
private readonly string _configDB = "guiNDB.db";
|
||||
|
||||
public SQLiteHelper()
|
||||
{
|
||||
@@ -25,17 +24,9 @@ namespace ServiceLib.Common
|
||||
return _db.CreateTable<T>();
|
||||
}
|
||||
|
||||
public int Insert(object model)
|
||||
public async Task<int> InsertAllAsync(IEnumerable models)
|
||||
{
|
||||
return _db.Insert(model);
|
||||
}
|
||||
|
||||
public int InsertAll(IEnumerable models)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.InsertAll(models);
|
||||
}
|
||||
return await _dbAsync.InsertAllAsync(models);
|
||||
}
|
||||
|
||||
public async Task<int> InsertAsync(object model)
|
||||
@@ -43,46 +34,19 @@ namespace ServiceLib.Common
|
||||
return await _dbAsync.InsertAsync(model);
|
||||
}
|
||||
|
||||
public int Replace(object model)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.InsertOrReplace(model);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> ReplaceAsync(object model)
|
||||
{
|
||||
return await _dbAsync.InsertOrReplaceAsync(model);
|
||||
}
|
||||
|
||||
public int Update(object model)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.Update(model);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> UpdateAsync(object model)
|
||||
{
|
||||
return await _dbAsync.UpdateAsync(model);
|
||||
}
|
||||
|
||||
public int UpdateAll(IEnumerable models)
|
||||
public async Task<int> UpdateAllAsync(IEnumerable models)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.UpdateAll(models);
|
||||
}
|
||||
}
|
||||
|
||||
public int Delete(object model)
|
||||
{
|
||||
lock (objLock)
|
||||
{
|
||||
return _db.Delete(model);
|
||||
}
|
||||
return await _dbAsync.UpdateAllAsync(models);
|
||||
}
|
||||
|
||||
public async Task<int> DeleteAsync(object model)
|
||||
@@ -90,19 +54,9 @@ namespace ServiceLib.Common
|
||||
return await _dbAsync.DeleteAsync(model);
|
||||
}
|
||||
|
||||
public List<T> Query<T>(string sql) where T : new()
|
||||
public async Task<int> DeleteAllAsync<T>()
|
||||
{
|
||||
return _db.Query<T>(sql);
|
||||
}
|
||||
|
||||
public async Task<List<T>> QueryAsync<T>(string sql) where T : new()
|
||||
{
|
||||
return await _dbAsync.QueryAsync<T>(sql);
|
||||
}
|
||||
|
||||
public int Execute(string sql)
|
||||
{
|
||||
return _db.Execute(sql);
|
||||
return await _dbAsync.DeleteAllAsync<T>();
|
||||
}
|
||||
|
||||
public async Task<int> ExecuteAsync(string sql)
|
||||
@@ -110,14 +64,28 @@ namespace ServiceLib.Common
|
||||
return await _dbAsync.ExecuteAsync(sql);
|
||||
}
|
||||
|
||||
public TableQuery<T> Table<T>() where T : new()
|
||||
public async Task<List<T>> QueryAsync<T>(string sql) where T : new()
|
||||
{
|
||||
return _db.Table<T>();
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,27 +14,25 @@ namespace ServiceLib.Common
|
||||
return string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
|
||||
public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
|
||||
{
|
||||
return !string.IsNullOrEmpty(value);
|
||||
}
|
||||
|
||||
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
|
||||
{
|
||||
if (s.IsNullOrEmpty()) return false;
|
||||
return chars.Contains(s[0]);
|
||||
return chars.Contains(s.First());
|
||||
}
|
||||
|
||||
public static bool IsWhiteSpace(this string value)
|
||||
private static bool IsWhiteSpace(this string value)
|
||||
{
|
||||
foreach (char c in value)
|
||||
{
|
||||
if (char.IsWhiteSpace(c)) continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return value.All(char.IsWhiteSpace);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> NonWhiteSpaceLines(this TextReader reader)
|
||||
{
|
||||
string? line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
while (reader.ReadLine() is { } line)
|
||||
{
|
||||
if (line.IsWhiteSpace()) continue;
|
||||
yield return line;
|
||||
@@ -48,26 +46,12 @@ namespace ServiceLib.Common
|
||||
|
||||
public static string RemovePrefix(this string value, char prefix)
|
||||
{
|
||||
if (value.StartsWith(prefix))
|
||||
{
|
||||
return value.Substring(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return value.StartsWith(prefix) ? value[1..] : value;
|
||||
}
|
||||
|
||||
public static string RemovePrefix(this string value, string prefix)
|
||||
{
|
||||
if (value.StartsWith(prefix))
|
||||
{
|
||||
return value.Substring(prefix.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return value.StartsWith(prefix) ? value[prefix.Length..] : value;
|
||||
}
|
||||
|
||||
public static string UpperFirstChar(this string value)
|
||||
@@ -77,17 +61,12 @@ namespace ServiceLib.Common
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return char.ToUpper(value[0]) + value.Substring(1);
|
||||
return char.ToUpper(value.First()) + value[1..];
|
||||
}
|
||||
|
||||
public static string AppendQuotes(this string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return $"\"{value}\"";
|
||||
return string.IsNullOrEmpty(value) ? string.Empty : $"\"{value}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
54
v2rayN/ServiceLib/Common/WindowsUtils.cs
Normal file
54
v2rayN/ServiceLib/Common/WindowsUtils.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
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 Utils.IsNullOrEmpty(value) ? 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 (Utils.IsNullOrEmpty(value.ToString()))
|
||||
{
|
||||
regKey?.DeleteValue(name, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
regKey?.SetValue(name, value);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
regKey?.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
using YamlDotNet.Serialization;
|
||||
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>
|
||||
@@ -20,12 +23,12 @@ namespace ServiceLib.Common
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
T obj = deserializer.Deserialize<T>(str);
|
||||
var obj = deserializer.Deserialize<T>(str);
|
||||
return obj;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("FromYaml", ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
return deserializer.Deserialize<T>("");
|
||||
}
|
||||
}
|
||||
@@ -35,24 +38,46 @@ namespace ServiceLib.Common
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToYaml(Object obj)
|
||||
public static string ToYaml(object? obj)
|
||||
{
|
||||
var result = string.Empty;
|
||||
if (obj == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(HyphenatedNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
string result = string.Empty;
|
||||
try
|
||||
{
|
||||
result = serializer.Serialize(obj);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string? PreprocessYaml(string str)
|
||||
{
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
var mergingParser = new MergingParser(new Parser(new StringReader(str)));
|
||||
var obj = new DeserializerBuilder().Build().Deserialize(mergingParser);
|
||||
return ToYaml(obj);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion YAML
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,12 @@
|
||||
VMess = 1,
|
||||
Custom = 2,
|
||||
Shadowsocks = 3,
|
||||
Socks = 4,
|
||||
SOCKS = 4,
|
||||
VLESS = 5,
|
||||
Trojan = 6,
|
||||
Hysteria2 = 7,
|
||||
Tuic = 8,
|
||||
Wireguard = 9,
|
||||
Http = 10
|
||||
TUIC = 8,
|
||||
WireGuard = 9,
|
||||
HTTP = 10
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,7 @@
|
||||
{
|
||||
v2fly = 1,
|
||||
Xray = 2,
|
||||
SagerNet = 3,
|
||||
v2fly_v5 = 4,
|
||||
clash = 11,
|
||||
clash_meta = 12,
|
||||
mihomo = 13,
|
||||
hysteria = 21,
|
||||
naiveproxy = 22,
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
public enum EInboundProtocol
|
||||
{
|
||||
socks = 0,
|
||||
http,
|
||||
socks2,
|
||||
http2,
|
||||
socks3,
|
||||
pac,
|
||||
api,
|
||||
api2,
|
||||
mixed,
|
||||
speedtest = 21
|
||||
}
|
||||
}
|
||||
12
v2rayN/ServiceLib/Enums/EMsgCommand.cs
Normal file
12
v2rayN/ServiceLib/Enums/EMsgCommand.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum EMsgCommand
|
||||
{
|
||||
ClearMsg,
|
||||
SendMsgView,
|
||||
SendSnackMsg,
|
||||
RefreshProfiles,
|
||||
StopSpeedtest,
|
||||
AppExit
|
||||
}
|
||||
}
|
||||
9
v2rayN/ServiceLib/Enums/EPresetType.cs
Normal file
9
v2rayN/ServiceLib/Enums/EPresetType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum EPresetType
|
||||
{
|
||||
Default = 0,
|
||||
Russia = 1,
|
||||
Iran = 2,
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,20 @@
|
||||
{
|
||||
public enum EServerColName
|
||||
{
|
||||
def = 0,
|
||||
configType,
|
||||
remarks,
|
||||
address,
|
||||
port,
|
||||
network,
|
||||
streamSecurity,
|
||||
subRemarks,
|
||||
delayVal,
|
||||
speedVal,
|
||||
Def = 0,
|
||||
ConfigType,
|
||||
Remarks,
|
||||
Address,
|
||||
Port,
|
||||
Network,
|
||||
StreamSecurity,
|
||||
SubRemarks,
|
||||
DelayVal,
|
||||
SpeedVal,
|
||||
|
||||
todayDown,
|
||||
todayUp,
|
||||
totalDown,
|
||||
totalUp
|
||||
TodayDown,
|
||||
TodayUp,
|
||||
TotalDown,
|
||||
TotalUp
|
||||
}
|
||||
}
|
||||
13
v2rayN/ServiceLib/Enums/ETheme.cs
Normal file
13
v2rayN/ServiceLib/Enums/ETheme.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace ServiceLib.Enums
|
||||
{
|
||||
public enum ETheme
|
||||
{
|
||||
FollowSystem,
|
||||
Dark,
|
||||
Light,
|
||||
Aquatic,
|
||||
Desert,
|
||||
Dusk,
|
||||
NightSky
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
kcp,
|
||||
ws,
|
||||
httpupgrade,
|
||||
splithttp,
|
||||
xhttp,
|
||||
h2,
|
||||
http,
|
||||
quic,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
SaveFileDialog,
|
||||
AddBatchRoutingRulesYesNo,
|
||||
AdjustMainLvColWidth,
|
||||
UpdateSysProxy,
|
||||
SetClipboardData,
|
||||
AddServerViaClipboard,
|
||||
ImportRulesFromClipboard,
|
||||
@@ -16,9 +15,11 @@
|
||||
ShareServer,
|
||||
ShowHideWindow,
|
||||
ScanScreenTask,
|
||||
ScanImageTask,
|
||||
Shutdown,
|
||||
BrowseServer,
|
||||
ImportRulesFromFile,
|
||||
InitSettingFont,
|
||||
SubEditWindow,
|
||||
RoutingRuleSettingWindow,
|
||||
RoutingRuleDetailsWindow,
|
||||
@@ -39,6 +40,7 @@
|
||||
DispatcherRefreshServersBiz,
|
||||
DispatcherRefreshIcon,
|
||||
DispatcherCheckUpdate,
|
||||
DispatcherCheckUpdateFinished,
|
||||
DispatcherCheckUpdateFinished,
|
||||
DispatcherShowMsg,
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,7 @@
|
||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||
public const string V2flyCoreUrl = "https://github.com/v2fly/v2ray-core/releases";
|
||||
public const string XrayCoreUrl = "https://github.com/XTLS/Xray-core/releases";
|
||||
public const string SagerNetCoreUrl = "https://github.com/SagerNet/v2ray-core/releases";
|
||||
public const string NUrl = @"https://github.com/2dust/v2rayN/releases";
|
||||
public const string ClashCoreUrl = "https://github.com/Dreamacro/clash/releases";
|
||||
public const string ClashMetaCoreUrl = "https://github.com/MetaCubeX/Clash.Meta/releases";
|
||||
public const string MihomoCoreUrl = "https://github.com/MetaCubeX/mihomo/releases";
|
||||
public const string HysteriaCoreUrl = "https://github.com/apernet/hysteria/releases";
|
||||
public const string NaiveproxyCoreUrl = "https://github.com/klzgrad/naiveproxy/releases";
|
||||
@@ -21,8 +18,8 @@
|
||||
public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat";
|
||||
public const string SpeedPingTestUrl = @"https://www.google.com/generate_204";
|
||||
public const string JuicityCoreUrl = "https://github.com/juicity/juicity/releases";
|
||||
public const string CustomRoutingListUrl = @"https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/";
|
||||
public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs";
|
||||
public const string IPAPIUrl = "https://api.ip.sb/geoip";
|
||||
|
||||
public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw=";
|
||||
public const string ConfigFileName = "guiNConfig.json";
|
||||
@@ -31,21 +28,25 @@
|
||||
public const string CoreSpeedtestConfigFileName = "configSpeedtest.json";
|
||||
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
|
||||
public const string ClashMixinConfigFileName = "Mixin.yaml";
|
||||
public const string V2raySampleClient = "ServiceLib.Sample.SampleClientConfig";
|
||||
public const string SingboxSampleClient = "ServiceLib.Sample.SingboxSampleClientConfig";
|
||||
public const string V2raySampleHttpRequestFileName = "ServiceLib.Sample.SampleHttpRequest";
|
||||
public const string V2raySampleHttpResponseFileName = "ServiceLib.Sample.SampleHttpResponse";
|
||||
public const string V2raySampleInbound = "ServiceLib.Sample.SampleInbound";
|
||||
public const string V2raySampleOutbound = "ServiceLib.Sample.SampleOutbound";
|
||||
public const string SingboxSampleOutbound = "ServiceLib.Sample.SingboxSampleOutbound";
|
||||
public const string CustomRoutingFileName = "ServiceLib.Sample.custom_routing_";
|
||||
public const string TunSingboxDNSFileName = "ServiceLib.Sample.tun_singbox_dns";
|
||||
public const string TunSingboxInboundFileName = "ServiceLib.Sample.tun_singbox_inbound";
|
||||
public const string TunSingboxRulesFileName = "ServiceLib.Sample.tun_singbox_rules";
|
||||
public const string DNSV2rayNormalFileName = "ServiceLib.Sample.dns_v2ray_normal";
|
||||
public const string DNSSingboxNormalFileName = "ServiceLib.Sample.dns_singbox_normal";
|
||||
public const string ClashMixinYaml = "ServiceLib.Sample.clash_mixin_yaml";
|
||||
public const string ClashTunYaml = "ServiceLib.Sample.clash_tun_yaml";
|
||||
|
||||
public const string 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 DefaultSecurity = "auto";
|
||||
public const string DefaultNetwork = "tcp";
|
||||
@@ -61,66 +62,98 @@
|
||||
public const string HttpProtocol = "http://";
|
||||
public const string HttpsProtocol = "https://";
|
||||
public const string SocksProtocol = "socks://";
|
||||
public const string Socks5Protocol = "socks5://";
|
||||
|
||||
public const string UserEMail = "t@t.tt";
|
||||
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
||||
public const string AutoRunName = "v2rayNAutoRun";
|
||||
public const string CustomIconName = "v2rayN.ico";
|
||||
public const string IEProxyExceptions = "localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*";
|
||||
public const string 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 string CommandClearMsg = "CommandClearMsg";
|
||||
public const string CommandSendMsgView = "CommandSendMsgView";
|
||||
public const string CommandSendSnackMsg = "CommandSendSnackMsg";
|
||||
public const string CommandStopSpeedTest = "CommandStopSpeedTest";
|
||||
public const string CommandRefreshProfiles = "CommandRefreshProfiles";
|
||||
public const string DelayUnit = "";
|
||||
public const string SpeedUnit = "";
|
||||
public const int MinFontSize = 10;
|
||||
public const int MinFontSize = 8;
|
||||
public const string RebootAs = "rebootas";
|
||||
public const string AvaAssets = "avares://v2rayN/Assets/";
|
||||
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA";
|
||||
public const string V2RayLocalAsset = "V2RAY_LOCATION_ASSET";
|
||||
public const string XrayLocalAsset = "XRAY_LOCATION_ASSET";
|
||||
|
||||
public static readonly List<string> IEProxyProtocols = new() {
|
||||
"{ip}:{http_port}",
|
||||
"socks={ip}:{socks_port}",
|
||||
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
|
||||
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
|
||||
""
|
||||
};
|
||||
public static readonly List<string> 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 = new List<string> {
|
||||
@"https://sub.xeton.dev/sub?url={0}",
|
||||
@"https://api.dler.io/sub?url={0}",
|
||||
@"http://127.0.0.1:25500/sub?url={0}",
|
||||
""
|
||||
};
|
||||
public static readonly List<string> 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 = new List<string> {
|
||||
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
|
||||
};
|
||||
public static readonly List<string> SubConvertConfig =
|
||||
[@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"];
|
||||
|
||||
public static readonly List<string> SubConvertTargets = new List<string> {
|
||||
"",
|
||||
"mixed",
|
||||
"v2ray",
|
||||
"clash",
|
||||
"ss",
|
||||
};
|
||||
public static readonly List<string> SubConvertTargets =
|
||||
[
|
||||
"",
|
||||
"mixed",
|
||||
"v2ray",
|
||||
"clash",
|
||||
"ss"
|
||||
];
|
||||
|
||||
public static readonly List<string> SpeedTestUrls = new() {
|
||||
public static readonly List<string> SpeedTestUrls =
|
||||
[
|
||||
@"https://speed.cloudflare.com/__down?bytes=100000000",
|
||||
@"https://speed.cloudflare.com/__down?bytes=50000000",
|
||||
@"https://speed.cloudflare.com/__down?bytes=10000000",
|
||||
@"http://cachefly.cachefly.net/50mb.test",
|
||||
@"http://cachefly.cachefly.net/10mb.test"
|
||||
};
|
||||
@"https://cachefly.cachefly.net/50mb.test"
|
||||
];
|
||||
|
||||
public static readonly List<string> SpeedPingTestUrls = new() {
|
||||
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",
|
||||
};
|
||||
@"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://cdn.jsdelivr.net/gh/chocolate4u/Iran-v2ray-rules@release/{0}.dat"
|
||||
];
|
||||
|
||||
public static readonly List<string> SingboxRulesetSources =
|
||||
[
|
||||
"",
|
||||
@"https://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-rules-dat@release/sing-box/rule-set-{0}/{1}.srs",
|
||||
@"https://cdn.jsdelivr.net/gh/chocolate4u/Iran-sing-box-rules@rule-set/{1}.srs"
|
||||
];
|
||||
|
||||
public static readonly List<string> RoutingRulesSources =
|
||||
[
|
||||
"",
|
||||
@"https://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-custom-routing-list@main/v2rayN/template.json",
|
||||
@"https://cdn.jsdelivr.net/gh/Chocolate4U/Iran-v2ray-rules@main/v2rayN/template.json"
|
||||
];
|
||||
|
||||
public static readonly List<string> DNSTemplateSources =
|
||||
[
|
||||
"",
|
||||
@"https://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-custom-routing-list@main/v2rayN/",
|
||||
@"https://cdn.jsdelivr.net/gh/Chocolate4U/Iran-v2ray-rules@main/v2rayN/"
|
||||
];
|
||||
|
||||
public static readonly Dictionary<string, string> UserAgentTexts = new()
|
||||
{
|
||||
@@ -137,64 +170,335 @@
|
||||
{
|
||||
{EConfigType.VMess,"vmess://"},
|
||||
{EConfigType.Shadowsocks,"ss://"},
|
||||
{EConfigType.Socks,"socks://"},
|
||||
{EConfigType.SOCKS,"socks://"},
|
||||
{EConfigType.VLESS,"vless://"},
|
||||
{EConfigType.Trojan,"trojan://"},
|
||||
{EConfigType.Hysteria2,"hysteria2://"},
|
||||
{EConfigType.Tuic,"tuic://"},
|
||||
{EConfigType.Wireguard,"wireguard://"}
|
||||
{EConfigType.TUIC,"tuic://"},
|
||||
{EConfigType.WireGuard,"wireguard://"}
|
||||
};
|
||||
|
||||
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
|
||||
{
|
||||
{EConfigType.VMess,"vmess"},
|
||||
{EConfigType.Shadowsocks,"shadowsocks"},
|
||||
{EConfigType.Socks,"socks"},
|
||||
{EConfigType.Http,"http"},
|
||||
{EConfigType.SOCKS,"socks"},
|
||||
{EConfigType.HTTP,"http"},
|
||||
{EConfigType.VLESS,"vless"},
|
||||
{EConfigType.Trojan,"trojan"},
|
||||
{EConfigType.Hysteria2,"hysteria2"},
|
||||
{EConfigType.Tuic,"tuic"},
|
||||
{EConfigType.Wireguard,"wireguard"}
|
||||
{EConfigType.TUIC,"tuic"},
|
||||
{EConfigType.WireGuard,"wireguard"}
|
||||
};
|
||||
|
||||
public static readonly List<string> VmessSecurities = new() { "aes-128-gcm", "chacha20-poly1305", "auto", "none", "zero" };
|
||||
public static readonly List<string> SsSecurities = new() { "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "none", "plain" };
|
||||
public static readonly List<string> SsSecuritiesInSagerNet = new() { "none", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "rc4", "rc4-md5", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-cfb8", "aes-192-cfb8", "aes-256-cfb8", "aes-128-ofb", "aes-192-ofb", "aes-256-ofb", "bf-cfb", "cast5-cfb", "des-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "camellia-128-cfb8", "camellia-192-cfb8", "camellia-256-cfb8", "salsa20", "chacha20", "chacha20-ietf", "xchacha20" };
|
||||
public static readonly List<string> SsSecuritiesInXray = new() { "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "none", "plain", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" };
|
||||
public static readonly List<string> SsSecuritiesInSingbox = new() { "aes-256-gcm", "aes-192-gcm", "aes-128-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "none", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20" };
|
||||
public static readonly List<string> Flows = new() { "", "xtls-rprx-vision", "xtls-rprx-vision-udp443" };
|
||||
public static readonly List<string> Networks = new() { "tcp", "kcp", "ws", "httpupgrade", "splithttp", "h2", "quic", "grpc" };
|
||||
public static readonly List<string> KcpHeaderTypes = new() { "srtp", "utp", "wechat-video", "dtls", "wireguard" };
|
||||
public static readonly List<string> CoreTypes = new() { "v2fly", "SagerNet", "Xray", "sing_box" };
|
||||
public static readonly List<string> CoreTypes4VLESS = new() { "Xray", "sing_box" };
|
||||
public static readonly List<string> DomainStrategies = new() { "AsIs", "IPIfNonMatch", "IPOnDemand" };
|
||||
public static readonly List<string> DomainStrategies4Singbox = new() { "ipv4_only", "ipv6_only", "prefer_ipv4", "prefer_ipv6", "" };
|
||||
public static readonly List<string> DomainMatchers = new() { "linear", "mph", "" };
|
||||
public static readonly List<string> Fingerprints = new() { "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random", "randomized", "" };
|
||||
public static readonly List<string> UserAgent = new() { "chrome", "firefox", "safari", "edge", "none" };
|
||||
public static readonly List<string> VmessSecurities =
|
||||
[
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"auto",
|
||||
"none",
|
||||
"zero"
|
||||
];
|
||||
|
||||
public static readonly List<string> AllowInsecure = new() { "true", "false", "" };
|
||||
public static readonly List<string> DomainStrategy4Freedoms = new() { "AsIs", "UseIP", "UseIPv4", "UseIPv6", "" };
|
||||
public static readonly List<string> SingboxDomainStrategy4Out = new() { "ipv4_only", "prefer_ipv4", "prefer_ipv6", "ipv6_only", "" };
|
||||
public static readonly List<string> DomainDNSAddress = ["223.5.5.5", "223.6.6.6", "localhost"];
|
||||
public static readonly List<string> SingboxDomainDNSAddress = ["223.5.5.5", "223.6.6.6", "dhcp://auto"];
|
||||
public static readonly List<string> Languages = new() { "zh-Hans", "zh-Hant", "en", "fa-Ir", "ru" };
|
||||
public static readonly List<string> Alpns = new() { "h3", "h2", "http/1.1", "h3,h2", "h2,http/1.1", "h3,h2,http/1.1", "" };
|
||||
public static readonly List<string> LogLevels = new() { "debug", "info", "warning", "error", "none" };
|
||||
public static readonly List<string> InboundTags = new() { "socks", "http", "socks2", "http2" };
|
||||
public static readonly List<string> RuleProtocols = new() { "http", "tls", "bittorrent" };
|
||||
public static readonly List<string> RuleNetworks = new() { "", "tcp", "udp", "tcp,udp" };
|
||||
public static readonly List<string> destOverrideProtocols = ["http", "tls", "quic", "fakedns", "fakedns+others"];
|
||||
public static readonly List<string> TunMtus = new() { "1280", "1408", "1500", "9000" };
|
||||
public static readonly List<string> TunStacks = new() { "gvisor", "system" };
|
||||
public static readonly List<string> PresetMsgFilters = new() { "proxy", "direct", "block", "" };
|
||||
public static readonly List<string> SingboxMuxs = new() { "h2mux", "smux", "yamux", "" };
|
||||
public static readonly List<string> TuicCongestionControls = new() { "cubic", "new_reno", "bbr" };
|
||||
public static readonly List<string> SsSecurities =
|
||||
[
|
||||
"aes-256-gcm",
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"chacha20-ietf-poly1305",
|
||||
"none",
|
||||
"plain"
|
||||
];
|
||||
|
||||
public static readonly List<string> allowSelectType = new List<string> { "selector", "urltest", "loadbalance", "fallback" };
|
||||
public static readonly List<string> notAllowTestType = new List<string> { "selector", "urltest", "direct", "reject", "compatible", "pass", "loadbalance", "fallback" };
|
||||
public static readonly List<string> proxyVehicleType = new List<string> { "file", "http" };
|
||||
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"
|
||||
];
|
||||
|
||||
public static readonly List<string> CoreTypes =
|
||||
[
|
||||
"Xray",
|
||||
"sing_box"
|
||||
];
|
||||
|
||||
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> DomainMatchers =
|
||||
[
|
||||
"linear",
|
||||
"mph",
|
||||
""
|
||||
];
|
||||
|
||||
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> DomainDNSAddress =
|
||||
[
|
||||
"223.5.5.5",
|
||||
"223.6.6.6",
|
||||
"localhost"
|
||||
];
|
||||
|
||||
public static readonly List<string> SingboxDomainDNSAddress =
|
||||
[
|
||||
"223.5.5.5",
|
||||
"223.6.6.6",
|
||||
"dhcp://auto"
|
||||
];
|
||||
|
||||
public static readonly List<string> Languages =
|
||||
[
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
"en",
|
||||
"fa-Ir",
|
||||
"ru",
|
||||
"hu"
|
||||
];
|
||||
|
||||
public static readonly List<string> Alpns =
|
||||
[
|
||||
"h3",
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"h3,h2",
|
||||
"h2,http/1.1",
|
||||
"h3,h2,http/1.1",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> LogLevels =
|
||||
[
|
||||
"debug",
|
||||
"info",
|
||||
"warning",
|
||||
"error",
|
||||
"none"
|
||||
];
|
||||
|
||||
public static readonly 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<string> TunMtus =
|
||||
[
|
||||
"1280",
|
||||
"1408",
|
||||
"1500",
|
||||
"9000"
|
||||
];
|
||||
|
||||
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"
|
||||
];
|
||||
|
||||
#endregion const
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
global using ServiceLib.Common;
|
||||
global using ServiceLib.Enums;
|
||||
global using ServiceLib.Handler;
|
||||
global using ServiceLib.Handler.CoreConfig;
|
||||
global using ServiceLib.Handler.Fmt;
|
||||
global using ServiceLib.Handler.Statistics;
|
||||
global using ServiceLib.Services;
|
||||
global using ServiceLib.Services.Statistics;
|
||||
global using ServiceLib.Services.CoreConfig;
|
||||
global using ServiceLib.Models;
|
||||
global using ServiceLib.Resx;
|
||||
global using ServiceLib.Resx;
|
||||
global using ServiceLib.Handler.SysProxy;
|
||||
255
v2rayN/ServiceLib/Handler/AppHandler.cs
Normal file
255
v2rayN/ServiceLib/Handler/AppHandler.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public sealed class AppHandler
|
||||
{
|
||||
#region Property
|
||||
|
||||
private static readonly Lazy<AppHandler> _instance = new(() => new());
|
||||
private Config _config;
|
||||
private int? _statePort;
|
||||
private int? _statePort2;
|
||||
private Job? _processJob;
|
||||
private bool? _isAdministrator;
|
||||
public static AppHandler Instance => _instance.Value;
|
||||
public Config Config => _config;
|
||||
|
||||
public int StatePort
|
||||
{
|
||||
get
|
||||
{
|
||||
_statePort ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api));
|
||||
return _statePort.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public int StatePort2
|
||||
{
|
||||
get
|
||||
{
|
||||
_statePort2 ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api2));
|
||||
return _statePort2.Value + (_config.TunModeItem.EnableTun ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAdministrator
|
||||
{
|
||||
get
|
||||
{
|
||||
_isAdministrator ??= Utils.IsAdministrator();
|
||||
return _isAdministrator.Value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Property
|
||||
|
||||
#region Init
|
||||
|
||||
public bool InitApp()
|
||||
{
|
||||
if (Utils.HasWritePermission() == false)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Global.LocalAppData, "1", EnvironmentVariableTarget.Process);
|
||||
}
|
||||
|
||||
Logging.Setup();
|
||||
var config = ConfigHandler.LoadConfig();
|
||||
if (config == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_config = config;
|
||||
Thread.CurrentThread.CurrentUICulture = new(_config.UiItem.CurrentLanguage);
|
||||
|
||||
//Under Win10
|
||||
if (Utils.IsWindows() && Environment.OSVersion.Version.Major < 10)
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DOTNET_EnableWriteXorExecute", "0", EnvironmentVariableTarget.User);
|
||||
}
|
||||
|
||||
SQLiteHelper.Instance.CreateTable<SubItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ProfileItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ServerStatItem>();
|
||||
SQLiteHelper.Instance.CreateTable<RoutingItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
||||
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool InitComponents()
|
||||
{
|
||||
Logging.SaveLog($"v2rayN start up | {Utils.GetRuntimeInfo()}");
|
||||
Logging.LoggingEnabled(_config.GuiItem.EnableLog);
|
||||
|
||||
ClearExpiredFiles();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Reset()
|
||||
{
|
||||
_statePort = null;
|
||||
_statePort2 = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ClearExpiredFiles()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
FileManager.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
|
||||
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
|
||||
});
|
||||
}
|
||||
|
||||
#endregion Init
|
||||
|
||||
#region Config
|
||||
|
||||
public int GetLocalPort(EInboundProtocol protocol)
|
||||
{
|
||||
var localPort = _config.Inbound.FirstOrDefault(t => t.Protocol == nameof(EInboundProtocol.socks))?.LocalPort ?? 10808;
|
||||
return localPort + (int)protocol;
|
||||
}
|
||||
|
||||
public void AddProcess(IntPtr processHandle)
|
||||
{
|
||||
if (Utils.IsWindows())
|
||||
{
|
||||
_processJob ??= new();
|
||||
try
|
||||
{
|
||||
_processJob?.AddProcess(processHandle);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Config
|
||||
|
||||
#region SqliteHelper
|
||||
|
||||
public async Task<List<SubItem>?> SubItems()
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<SubItem>().OrderBy(t => t.Sort).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<SubItem?> GetSubItem(string? subid)
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<SubItem>().FirstOrDefaultAsync(t => t.Id == subid);
|
||||
}
|
||||
|
||||
public async Task<List<ProfileItem>?> ProfileItems(string subid)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(subid))
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().ToListAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().Where(t => t.Subid == subid).ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<string>?> ProfileItemIndexes(string subid)
|
||||
{
|
||||
return (await ProfileItems(subid))?.Select(t => t.IndexId)?.ToList();
|
||||
}
|
||||
|
||||
public async Task<List<ProfileItemModel>?> ProfileItems(string subid, string filter)
|
||||
{
|
||||
var sql = @$"select a.*
|
||||
,b.remarks subRemarks
|
||||
from ProfileItem a
|
||||
left join SubItem b on a.subid = b.id
|
||||
where 1=1 ";
|
||||
if (Utils.IsNotEmpty(subid))
|
||||
{
|
||||
sql += $" and a.subid = '{subid}'";
|
||||
}
|
||||
if (Utils.IsNotEmpty(filter))
|
||||
{
|
||||
if (filter.Contains('\''))
|
||||
{
|
||||
filter = filter.Replace("'", "");
|
||||
}
|
||||
sql += string.Format(" and (a.remarks like '%{0}%' or a.address like '%{0}%') ", filter);
|
||||
}
|
||||
|
||||
return await SQLiteHelper.Instance.QueryAsync<ProfileItemModel>(sql);
|
||||
}
|
||||
|
||||
public async Task<ProfileItem?> GetProfileItem(string indexId)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(indexId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
|
||||
}
|
||||
|
||||
public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(remarks))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks);
|
||||
}
|
||||
|
||||
public async Task<List<RoutingItem>?> RoutingItems()
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<RoutingItem?> GetRoutingItem(string id)
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().FirstOrDefaultAsync(it => it.Id == id);
|
||||
}
|
||||
|
||||
public async Task<List<DNSItem>?> DNSItems()
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<DNSItem>().ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<DNSItem?> GetDNSItem(ECoreType eCoreType)
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<DNSItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
|
||||
}
|
||||
|
||||
#endregion SqliteHelper
|
||||
|
||||
#region Core Type
|
||||
|
||||
public List<string> GetShadowsocksSecurities(ProfileItem profileItem)
|
||||
{
|
||||
var coreType = GetCoreType(profileItem, EConfigType.Shadowsocks);
|
||||
switch (coreType)
|
||||
{
|
||||
case ECoreType.v2fly:
|
||||
return Global.SsSecurities;
|
||||
|
||||
case ECoreType.Xray:
|
||||
return Global.SsSecuritiesInXray;
|
||||
|
||||
case ECoreType.sing_box:
|
||||
return Global.SsSecuritiesInSingbox;
|
||||
}
|
||||
return Global.SsSecuritiesInSingbox;
|
||||
}
|
||||
|
||||
public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType)
|
||||
{
|
||||
if (profileItem?.CoreType != null)
|
||||
{
|
||||
return (ECoreType)profileItem.CoreType;
|
||||
}
|
||||
|
||||
var item = _config.CoreTypeItem?.FirstOrDefault(it => it.ConfigType == eConfigType);
|
||||
return item?.CoreType ?? ECoreType.Xray;
|
||||
}
|
||||
|
||||
#endregion Core Type
|
||||
}
|
||||
}
|
||||
165
v2rayN/ServiceLib/Handler/AutoStartupHandler.cs
Normal file
165
v2rayN/ServiceLib/Handler/AutoStartupHandler.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System.Security.Principal;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
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())
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
|
||||
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 (Utils.IsNullOrEmpty(taskName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var logonUser = WindowsIdentity.GetCurrent().Name;
|
||||
using var taskService = new Microsoft.Win32.TaskScheduler.TaskService();
|
||||
var tasks = taskService.RootFolder.GetTasks(new Regex(taskName));
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
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(10) });
|
||||
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 = Utils.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
|
||||
}
|
||||
}
|
||||
@@ -7,15 +7,11 @@ namespace ServiceLib.Handler
|
||||
private static readonly Lazy<ClashApiHandler> instance = new(() => new());
|
||||
public static ClashApiHandler Instance => instance.Value;
|
||||
|
||||
private Dictionary<String, ProxiesItem>? _proxies;
|
||||
private Dictionary<string, ProxiesItem>? _proxies;
|
||||
public Dictionary<string, object> ProfileContent { get; set; }
|
||||
private static readonly string _tag = "ClashApiHandler";
|
||||
|
||||
public void GetClashProxies(Config config, Action<ClashProxies, ClashProviders> update)
|
||||
{
|
||||
Task.Run(() => GetClashProxiesAsync(config, update));
|
||||
}
|
||||
|
||||
private async Task GetClashProxiesAsync(Config config, Action<ClashProxies, ClashProviders> update)
|
||||
public async Task<Tuple<ClashProxies, ClashProviders>?> GetClashProxiesAsync(Config config)
|
||||
{
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
@@ -30,74 +26,75 @@ namespace ServiceLib.Handler
|
||||
if (clashProxies != null || clashProviders != null)
|
||||
{
|
||||
_proxies = clashProxies?.proxies;
|
||||
update(clashProxies, clashProviders);
|
||||
return;
|
||||
return new Tuple<ClashProxies, ClashProviders>(clashProxies, clashProviders);
|
||||
}
|
||||
Task.Delay(5000).Wait();
|
||||
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
update(null, null);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Action<ClashProxyModel?, string> update)
|
||||
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Action<ClashProxyModel?, string> updateFunc)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (blAll)
|
||||
{
|
||||
if (blAll)
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
if (_proxies != null)
|
||||
{
|
||||
if (_proxies != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Task.Delay(5000).Wait();
|
||||
}
|
||||
if (_proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lstProxy = new List<ClashProxyModel>();
|
||||
foreach (KeyValuePair<string, ProxiesItem> kv in _proxies)
|
||||
{
|
||||
if (Global.notAllowTestType.Contains(kv.Value.type.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
lstProxy.Add(new ClashProxyModel()
|
||||
{
|
||||
name = kv.Value.name,
|
||||
type = kv.Value.type.ToLower(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
Task.Delay(5000).Wait();
|
||||
}
|
||||
|
||||
if (lstProxy == null)
|
||||
if (_proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var urlBase = $"{GetApiUrl()}/proxies";
|
||||
urlBase += @"/{0}/delay?timeout=10000&url=" + LazyConfig.Instance.Config.speedTestItem.speedPingTestUrl;
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
foreach (var it in lstProxy)
|
||||
lstProxy = new List<ClashProxyModel>();
|
||||
foreach (KeyValuePair<string, ProxiesItem> kv in _proxies)
|
||||
{
|
||||
if (Global.notAllowTestType.Contains(it.type.ToLower()))
|
||||
if (Global.notAllowTestType.Contains(kv.Value.type.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var name = it.name;
|
||||
var url = string.Format(urlBase, name);
|
||||
tasks.Add(Task.Run(async () =>
|
||||
lstProxy.Add(new ClashProxyModel()
|
||||
{
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||
update(it, result);
|
||||
}));
|
||||
Name = kv.Value.name,
|
||||
Type = kv.Value.type.ToLower(),
|
||||
});
|
||||
}
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
}
|
||||
|
||||
Task.Delay(1000).Wait();
|
||||
update(null, "");
|
||||
});
|
||||
if (lstProxy == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var urlBase = $"{GetApiUrl()}/proxies";
|
||||
urlBase += @"/{0}/delay?timeout=10000&url=" + AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
foreach (var it in lstProxy)
|
||||
{
|
||||
if (Global.notAllowTestType.Contains(it.Type.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var name = it.Name;
|
||||
var url = string.Format(urlBase, name);
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||
updateFunc?.Invoke(it, result);
|
||||
}));
|
||||
}
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
Task.Delay(1000).Wait();
|
||||
updateFunc?.Invoke(null, "");
|
||||
});
|
||||
}
|
||||
|
||||
public List<ProxiesItem>? GetClashProxyGroups()
|
||||
@@ -113,12 +110,12 @@ namespace ServiceLib.Handler
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GetClashProxyGroups", ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async void ClashSetActiveProxy(string name, string nameNode)
|
||||
public async Task ClashSetActiveProxy(string name, string nameNode)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -129,28 +126,25 @@ namespace ServiceLib.Handler
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClashConfigUpdate(Dictionary<string, string> headers)
|
||||
public async Task ClashConfigUpdate(Dictionary<string, string> headers)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
if (_proxies == null)
|
||||
{
|
||||
if (_proxies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var urlBase = $"{GetApiUrl()}/configs";
|
||||
var urlBase = $"{GetApiUrl()}/configs";
|
||||
|
||||
await HttpClientHelper.Instance.PatchAsync(urlBase, headers);
|
||||
});
|
||||
await HttpClientHelper.Instance.PatchAsync(urlBase, headers);
|
||||
}
|
||||
|
||||
public async void ClashConfigReload(string filePath)
|
||||
public async Task ClashConfigReload(string filePath)
|
||||
{
|
||||
ClashConnectionClose("");
|
||||
await ClashConnectionClose("");
|
||||
try
|
||||
{
|
||||
var url = $"{GetApiUrl()}/configs?force=true";
|
||||
@@ -160,16 +154,11 @@ namespace ServiceLib.Handler
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetClashConnections(Config config, Action<ClashConnections> update)
|
||||
{
|
||||
Task.Run(() => GetClashConnectionsAsync(config, update));
|
||||
}
|
||||
|
||||
private async Task GetClashConnectionsAsync(Config config, Action<ClashConnections> update)
|
||||
public async Task<ClashConnections?> GetClashConnectionsAsync(Config config)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -177,15 +166,17 @@ namespace ServiceLib.Handler
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||
var clashConnections = JsonUtils.Deserialize<ClashConnections>(result);
|
||||
|
||||
update(clashConnections);
|
||||
return clashConnections;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async void ClashConnectionClose(string id)
|
||||
public async Task ClashConnectionClose(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -194,13 +185,13 @@ namespace ServiceLib.Handler
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetApiUrl()
|
||||
{
|
||||
return $"{Global.HttpProtocol}{Global.Loopback}:{LazyConfig.Instance.StatePort2}";
|
||||
return $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,171 +0,0 @@
|
||||
namespace ServiceLib.Handler.CoreConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Core configuration file processing class
|
||||
/// </summary>
|
||||
public class CoreConfigHandler
|
||||
{
|
||||
public static int GenerateClientConfig(ProfileItem node, string? fileName, out string msg, out string content)
|
||||
{
|
||||
content = string.Empty;
|
||||
try
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
return -1;
|
||||
}
|
||||
var config = LazyConfig.Instance.Config;
|
||||
|
||||
msg = ResUI.InitialConfiguration;
|
||||
if (node.configType == EConfigType.Custom)
|
||||
{
|
||||
if (node.coreType is ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo)
|
||||
{
|
||||
var configGenClash = new CoreConfigClash(config);
|
||||
return configGenClash.GenerateClientCustomConfig(node, fileName, out msg);
|
||||
}
|
||||
if (node.coreType is ECoreType.sing_box)
|
||||
{
|
||||
var configGenSingbox = new CoreConfigSingbox(config);
|
||||
return configGenSingbox.GenerateClientCustomConfig(node, fileName, out msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GenerateClientCustomConfig(node, fileName, out msg);
|
||||
}
|
||||
}
|
||||
else if (LazyConfig.Instance.GetCoreType(node, node.configType) == ECoreType.sing_box)
|
||||
{
|
||||
var configGenSingbox = new CoreConfigSingbox(config);
|
||||
if (configGenSingbox.GenerateClientConfigContent(node, out SingboxConfig? singboxConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
content = JsonUtils.Serialize(singboxConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils.ToFile(singboxConfig, fileName, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var coreConfigV2ray = new CoreConfigV2ray(config);
|
||||
if (coreConfigV2ray.GenerateClientConfigContent(node, out V2rayConfig? v2rayConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
content = JsonUtils.Serialize(v2rayConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils.ToFile(v2rayConfig, fileName, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GenerateClientConfig", ex);
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int GenerateClientCustomConfig(ProfileItem node, string? fileName, out string msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (node == null || fileName is null)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
string addressFileName = node.address;
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
addressFileName = Utils.GetConfigPath(addressFileName);
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
File.Copy(addressFileName, fileName);
|
||||
File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file.
|
||||
|
||||
//check again
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GenerateClientCustomConfig", ex);
|
||||
msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType, out string msg)
|
||||
{
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
if (new CoreConfigSingbox(config).GenerateClientSpeedtestConfig(selecteds, out SingboxConfig? singboxConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
JsonUtils.ToFile(singboxConfig, fileName, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (new CoreConfigV2ray(config).GenerateClientSpeedtestConfig(selecteds, out V2rayConfig? v2rayConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
JsonUtils.ToFile(v2rayConfig, fileName, false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int GenerateClientMultipleLoadConfig(Config config, string fileName, List<ProfileItem> selecteds, ECoreType coreType, out string msg)
|
||||
{
|
||||
msg = ResUI.CheckServerSettings;
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
if (new CoreConfigSingbox(config).GenerateClientMultipleLoadConfig(selecteds, out SingboxConfig? singboxConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
JsonUtils.ToFile(singboxConfig, fileName, false);
|
||||
}
|
||||
else if (coreType == ECoreType.Xray)
|
||||
{
|
||||
if (new CoreConfigV2ray(config).GenerateClientMultipleLoadConfig(selecteds, out V2rayConfig? v2rayConfig, out msg) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
JsonUtils.ToFile(v2rayConfig, fileName, false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
132
v2rayN/ServiceLib/Handler/CoreConfigHandler.cs
Normal file
132
v2rayN/ServiceLib/Handler/CoreConfigHandler.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
/// <summary>
|
||||
/// Core configuration file processing class
|
||||
/// </summary>
|
||||
public class CoreConfigHandler
|
||||
{
|
||||
private static readonly string _tag = "CoreConfigHandler";
|
||||
|
||||
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
|
||||
{
|
||||
var config = AppHandler.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 (AppHandler.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 (Utils.IsNotEmpty(fileName) && 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> GenerateClientMultipleLoadConfig(Config config, string fileName, List<ProfileItem> selecteds, ECoreType coreType)
|
||||
{
|
||||
var result = new RetResult();
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds);
|
||||
}
|
||||
else if (coreType == ECoreType.Xray)
|
||||
{
|
||||
result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds);
|
||||
}
|
||||
|
||||
if (result.Success != true)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
await File.WriteAllTextAsync(fileName, result.Data.ToString());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,285 +8,217 @@ namespace ServiceLib.Handler
|
||||
/// </summary>
|
||||
public class CoreHandler
|
||||
{
|
||||
private static readonly Lazy<CoreHandler> _instance = new(() => new());
|
||||
public static CoreHandler Instance => _instance.Value;
|
||||
private Config _config;
|
||||
private Process? _process;
|
||||
private Process? _processPre;
|
||||
private Action<bool, string> _updateFunc;
|
||||
private int _linuxSudoPid = -1;
|
||||
private Action<bool, string>? _updateFunc;
|
||||
private const string _tag = "CoreHandler";
|
||||
|
||||
public CoreHandler(Config config, Action<bool, string> update)
|
||||
public async Task Init(Config config, Action<bool, string> updateFunc)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
_updateFunc = updateFunc;
|
||||
|
||||
Environment.SetEnvironmentVariable("v2ray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("xray.location.asset", Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
||||
}
|
||||
Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
|
||||
|
||||
public void LoadCore(ProfileItem? node)
|
||||
{
|
||||
if (node == null)
|
||||
//Copy the bin folder to the storage location (for init)
|
||||
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
|
||||
{
|
||||
ShowMsg(false, ResUI.CheckServerSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
string fileName = Utils.GetConfigPath(Global.CoreConfigFileName);
|
||||
if (CoreConfigHandler.GenerateClientConfig(node, fileName, out string msg, out string content) != 0)
|
||||
{
|
||||
ShowMsg(false, msg);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowMsg(false, msg);
|
||||
ShowMsg(true, $"{node.GetSummary()}");
|
||||
CoreStop();
|
||||
CoreStart(node);
|
||||
|
||||
//In tun mode, do a delay check and restart the core
|
||||
//if (_config.tunModeItem.enableTun)
|
||||
//{
|
||||
// Observable.Range(1, 1)
|
||||
// .Delay(TimeSpan.FromSeconds(15))
|
||||
// .Subscribe(x =>
|
||||
// {
|
||||
// {
|
||||
// if (_process == null || _process.HasExited)
|
||||
// {
|
||||
// CoreStart(node);
|
||||
// ShowMsg(false, "Tun mode restart the core once");
|
||||
// Logging.SaveLog("Tun mode restart the core once");
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
public int LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
|
||||
{
|
||||
int pid = -1;
|
||||
var coreType = selecteds.Exists(t => t.configType == EConfigType.Hysteria2 || t.configType == EConfigType.Tuic || t.configType == EConfigType.Wireguard) ? ECoreType.sing_box : ECoreType.Xray;
|
||||
string configPath = Utils.GetConfigPath(Global.CoreSpeedtestConfigFileName);
|
||||
if (CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType, out string msg) != 0)
|
||||
{
|
||||
ShowMsg(false, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowMsg(false, msg);
|
||||
pid = CoreStartSpeedtest(configPath, coreType);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
public void CoreStop()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool hasProc = false;
|
||||
if (_process != null)
|
||||
var fromPath = Utils.GetBaseDirectory("bin");
|
||||
var toPath = Utils.GetBinPath("");
|
||||
if (fromPath != toPath)
|
||||
{
|
||||
KillProcess(_process);
|
||||
_process.Dispose();
|
||||
_process = null;
|
||||
hasProc = true;
|
||||
FileManager.CopyDirectory(fromPath, toPath, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (_processPre != null)
|
||||
if (Utils.IsNonWindows())
|
||||
{
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
|
||||
foreach (var it in coreInfo)
|
||||
{
|
||||
KillProcess(_processPre);
|
||||
_processPre.Dispose();
|
||||
_processPre = null;
|
||||
hasProc = true;
|
||||
}
|
||||
|
||||
if (!hasProc)
|
||||
{
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
|
||||
foreach (var it in coreInfo)
|
||||
if (it.CoreType == ECoreType.v2rayN)
|
||||
{
|
||||
if (it.coreType == ECoreType.v2rayN)
|
||||
if (Utils.UpgradeAppExists(out var upgradeFileName))
|
||||
{
|
||||
continue;
|
||||
await Utils.SetLinuxChmod(upgradeFileName);
|
||||
}
|
||||
foreach (string vName in it.coreExes)
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var name in it.CoreExes)
|
||||
{
|
||||
var exe = Utils.GetBinPath(Utils.GetExeName(name), it.CoreType.ToString());
|
||||
if (File.Exists(exe))
|
||||
{
|
||||
var existing = Process.GetProcessesByName(vName);
|
||||
foreach (Process p in existing)
|
||||
{
|
||||
string? path = p.MainModule?.FileName;
|
||||
if (path == Utils.GetExeName(Utils.GetBinPath(vName, it.coreType.ToString())))
|
||||
{
|
||||
KillProcess(p);
|
||||
}
|
||||
}
|
||||
await Utils.SetLinuxChmod(exe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
}
|
||||
|
||||
public async Task LoadCore(ProfileItem? node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
UpdateFunc(false, ResUI.CheckServerSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
var fileName = Utils.GetConfigPath(Global.CoreConfigFileName);
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
|
||||
if (result.Success != true)
|
||||
{
|
||||
UpdateFunc(true, result.Msg);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateFunc(false, $"{node.GetSummary()}");
|
||||
UpdateFunc(false, $"{Utils.GetRuntimeInfo()}");
|
||||
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||
await CoreStop();
|
||||
await Task.Delay(100);
|
||||
await CoreStart(node);
|
||||
await CoreStartPreService(node);
|
||||
if (_process != null)
|
||||
{
|
||||
UpdateFunc(true, $"{node.GetSummary()}");
|
||||
}
|
||||
}
|
||||
|
||||
public void CoreStopPid(int pid)
|
||||
public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
|
||||
{
|
||||
var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.WireGuard) ? ECoreType.sing_box : ECoreType.Xray;
|
||||
var configPath = Utils.GetConfigPath(Global.CoreSpeedtestConfigFileName);
|
||||
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
|
||||
UpdateFunc(false, result.Msg);
|
||||
if (result.Success != true)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||
UpdateFunc(false, configPath);
|
||||
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
||||
var proc = await RunProcess(coreInfo, Global.CoreSpeedtestConfigFileName, true, false);
|
||||
if (proc is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return proc.Id;
|
||||
}
|
||||
|
||||
public async Task CoreStop()
|
||||
{
|
||||
try
|
||||
{
|
||||
var _p = Process.GetProcessById(pid);
|
||||
KillProcess(_p);
|
||||
if (_process != null)
|
||||
{
|
||||
await ProcUtils.ProcessKill(_process, true);
|
||||
_process = null;
|
||||
}
|
||||
|
||||
if (_processPre != null)
|
||||
{
|
||||
await ProcUtils.ProcessKill(_processPre, true);
|
||||
_processPre = null;
|
||||
}
|
||||
|
||||
if (_linuxSudoPid > 0)
|
||||
{
|
||||
await KillProcessAsLinuxSudo();
|
||||
}
|
||||
_linuxSudoPid = -1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
#region Private
|
||||
|
||||
private string CoreFindExe(CoreInfo coreInfo)
|
||||
private async Task CoreStart(ProfileItem node)
|
||||
{
|
||||
string fileName = string.Empty;
|
||||
foreach (string name in coreInfo.coreExes)
|
||||
{
|
||||
string vName = Utils.GetExeName(name);
|
||||
vName = Utils.GetBinPath(vName, coreInfo.coreType.ToString());
|
||||
if (File.Exists(vName))
|
||||
{
|
||||
fileName = vName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
string msg = string.Format(ResUI.NotFoundCore, Utils.GetBinPath("", coreInfo.coreType.ToString()), string.Join(", ", coreInfo.coreExes.ToArray()), coreInfo.coreUrl);
|
||||
Logging.SaveLog(msg);
|
||||
ShowMsg(false, msg);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private void CoreStart(ProfileItem node)
|
||||
{
|
||||
ShowMsg(false, $"{Environment.OSVersion} - {(Environment.Is64BitOperatingSystem ? 64 : 32)}");
|
||||
ShowMsg(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||
|
||||
//ECoreType coreType;
|
||||
//if (node.configType != EConfigType.Custom && _config.tunModeItem.enableTun)
|
||||
//{
|
||||
// coreType = ECoreType.sing_box;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// coreType = LazyConfig.Instance.GetCoreType(node, node.configType);
|
||||
//}
|
||||
var coreType = LazyConfig.Instance.GetCoreType(node, node.configType);
|
||||
_config.runningCoreType = coreType;
|
||||
var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
||||
|
||||
var displayLog = node.configType != EConfigType.Custom || node.displayLog;
|
||||
var proc = RunProcess(node, coreInfo, "", displayLog);
|
||||
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
||||
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
|
||||
if (proc is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_process = proc;
|
||||
}
|
||||
|
||||
//start a pre service
|
||||
private async Task CoreStartPreService(ProfileItem node)
|
||||
{
|
||||
if (_process != null && !_process.HasExited)
|
||||
{
|
||||
ProfileItem? itemSocks = null;
|
||||
var preCoreType = ECoreType.sing_box;
|
||||
if (node.configType != EConfigType.Custom && coreType != ECoreType.sing_box && _config.tunModeItem.enableTun)
|
||||
{
|
||||
itemSocks = new ProfileItem()
|
||||
{
|
||||
coreType = preCoreType,
|
||||
configType = EConfigType.Socks,
|
||||
address = Global.Loopback,
|
||||
sni = node.address, //Tun2SocksAddress
|
||||
port = LazyConfig.Instance.GetLocalPort(EInboundProtocol.socks)
|
||||
};
|
||||
}
|
||||
else if ((node.configType == EConfigType.Custom && node.preSocksPort > 0))
|
||||
{
|
||||
preCoreType = _config.tunModeItem.enableTun ? ECoreType.sing_box : ECoreType.Xray;
|
||||
itemSocks = new ProfileItem()
|
||||
{
|
||||
coreType = preCoreType,
|
||||
configType = EConfigType.Socks,
|
||||
address = Global.Loopback,
|
||||
port = node.preSocksPort.Value,
|
||||
};
|
||||
_config.runningCoreType = preCoreType;
|
||||
}
|
||||
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
|
||||
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
|
||||
if (itemSocks != null)
|
||||
{
|
||||
string fileName2 = Utils.GetConfigPath(Global.CorePreConfigFileName);
|
||||
if (CoreConfigHandler.GenerateClientConfig(itemSocks, fileName2, out string msg2, out string configStr) == 0)
|
||||
var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box;
|
||||
var fileName = Utils.GetConfigPath(Global.CorePreConfigFileName);
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
|
||||
if (result.Success)
|
||||
{
|
||||
var coreInfo2 = CoreInfoHandler.Instance.GetCoreInfo(preCoreType);
|
||||
var proc2 = RunProcess(node, coreInfo2, $" -c {Global.CorePreConfigFileName}", true);
|
||||
if (proc2 is not null)
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType);
|
||||
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
|
||||
if (proc is null)
|
||||
{
|
||||
_processPre = proc2;
|
||||
return;
|
||||
}
|
||||
_processPre = proc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int CoreStartSpeedtest(string configPath, ECoreType coreType)
|
||||
private void UpdateFunc(bool notify, string msg)
|
||||
{
|
||||
ShowMsg(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||
|
||||
ShowMsg(false, configPath);
|
||||
try
|
||||
{
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
|
||||
var proc = RunProcess(new(), coreInfo, $" -c {Global.CoreSpeedtestConfigFileName}", true);
|
||||
if (proc is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return proc.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
string msg = ex.Message;
|
||||
ShowMsg(false, msg);
|
||||
return -1;
|
||||
}
|
||||
_updateFunc?.Invoke(notify, msg);
|
||||
}
|
||||
|
||||
private void ShowMsg(bool notify, string msg)
|
||||
private bool IsNeedSudo(ECoreType eCoreType)
|
||||
{
|
||||
_updateFunc(notify, msg);
|
||||
return _config.TunModeItem.EnableTun
|
||||
&& eCoreType == ECoreType.sing_box
|
||||
&& (Utils.IsNonWindows())
|
||||
//&& _config.TunModeItem.LinuxSudoPwd.IsNotEmpty()
|
||||
;
|
||||
}
|
||||
|
||||
#endregion Private
|
||||
|
||||
#region Process
|
||||
|
||||
private Process? RunProcess(ProfileItem node, CoreInfo coreInfo, string configPath, bool displayLog)
|
||||
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
|
||||
{
|
||||
var fileName = CoreInfoHandler.Instance.GetCoreExecFile(coreInfo, out var msg);
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
UpdateFunc(false, msg);
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string fileName = CoreFindExe(coreInfo);
|
||||
if (Utils.IsNullOrEmpty(fileName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Process proc = new()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = string.Format(coreInfo.arguments, configPath),
|
||||
Arguments = string.Format(coreInfo.Arguments, configPath),
|
||||
WorkingDirectory = Utils.GetConfigPath(),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = displayLog,
|
||||
@@ -296,83 +228,144 @@ namespace ServiceLib.Handler
|
||||
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
||||
}
|
||||
};
|
||||
var startUpErrorMessage = new StringBuilder();
|
||||
var startUpSuccessful = false;
|
||||
|
||||
var isNeedSudo = mayNeedSudo && IsNeedSudo(coreInfo.CoreType);
|
||||
if (isNeedSudo)
|
||||
{
|
||||
await RunProcessAsLinuxSudo(proc, fileName, coreInfo, configPath);
|
||||
}
|
||||
|
||||
if (displayLog)
|
||||
{
|
||||
proc.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
string msg = e.Data + Environment.NewLine;
|
||||
ShowMsg(false, msg);
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(e.Data)) return;
|
||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
||||
};
|
||||
proc.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
string msg = e.Data + Environment.NewLine;
|
||||
ShowMsg(false, msg);
|
||||
|
||||
if (!startUpSuccessful)
|
||||
{
|
||||
startUpErrorMessage.Append(msg);
|
||||
}
|
||||
}
|
||||
if (Utils.IsNullOrEmpty(e.Data)) return;
|
||||
UpdateFunc(false, e.Data + Environment.NewLine);
|
||||
};
|
||||
}
|
||||
proc.Start();
|
||||
|
||||
if (isNeedSudo && _config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
|
||||
{
|
||||
var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd);
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(pwd);
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(pwd);
|
||||
}
|
||||
if (isNeedSudo) _linuxSudoPid = proc.Id;
|
||||
|
||||
if (displayLog)
|
||||
{
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
}
|
||||
|
||||
if (proc.WaitForExit(1000))
|
||||
await Task.Delay(500);
|
||||
AppHandler.Instance.AddProcess(proc.Handle);
|
||||
if (proc is null or { HasExited: true })
|
||||
{
|
||||
proc.CancelErrorRead();
|
||||
throw new Exception(displayLog ? startUpErrorMessage.ToString() : "启动进程失败并退出 (Failed to start the process and exited)");
|
||||
throw new Exception(ResUI.FailedToRunCore);
|
||||
}
|
||||
else
|
||||
{
|
||||
startUpSuccessful = true;
|
||||
}
|
||||
|
||||
LazyConfig.Instance.AddProcess(proc.Handle);
|
||||
return proc;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
string msg = ex.Message;
|
||||
ShowMsg(true, msg);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
UpdateFunc(true, ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void KillProcess(Process? proc)
|
||||
#endregion Process
|
||||
|
||||
#region Linux
|
||||
|
||||
private async Task RunProcessAsLinuxSudo(Process proc, string fileName, CoreInfo coreInfo, string configPath)
|
||||
{
|
||||
if (proc is null)
|
||||
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetConfigPath(configPath).AppendQuotes())}";
|
||||
|
||||
var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh");
|
||||
proc.StartInfo.FileName = shFilePath;
|
||||
proc.StartInfo.Arguments = "";
|
||||
proc.StartInfo.WorkingDirectory = "";
|
||||
if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
proc.Kill();
|
||||
proc.WaitForExit(100);
|
||||
if (!proc.HasExited)
|
||||
{
|
||||
proc.Kill();
|
||||
proc.WaitForExit(100);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
proc.StartInfo.StandardInputEncoding = Encoding.UTF8;
|
||||
proc.StartInfo.RedirectStandardInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Process
|
||||
private async Task KillProcessAsLinuxSudo()
|
||||
{
|
||||
var cmdLine = $"kill {_linuxSudoPid}";
|
||||
var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh");
|
||||
Process proc = new()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = shFilePath,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
StandardInputEncoding = Encoding.UTF8,
|
||||
RedirectStandardInput = true
|
||||
}
|
||||
};
|
||||
proc.Start();
|
||||
|
||||
if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd);
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(pwd);
|
||||
await Task.Delay(10);
|
||||
await proc.StandardInput.WriteLineAsync(pwd);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
await proc.WaitForExitAsync(timeout.Token);
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
|
||||
private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName)
|
||||
{
|
||||
//Shell scripts
|
||||
var shFilePath = Utils.GetBinPath(AppHandler.Instance.IsAdministrator ? "root_" + fileName : fileName);
|
||||
File.Delete(shFilePath);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("#!/bin/sh");
|
||||
if (AppHandler.Instance.IsAdministrator)
|
||||
{
|
||||
sb.AppendLine($"{cmdLine}");
|
||||
}
|
||||
else if (_config.TunModeItem.LinuxSudoPwd.IsNullOrEmpty())
|
||||
{
|
||||
sb.AppendLine($"pkexec {cmdLine}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"sudo -S {cmdLine}");
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(shFilePath, sb.ToString());
|
||||
await Utils.SetLinuxChmod(shFilePath);
|
||||
Logging.SaveLog(shFilePath);
|
||||
|
||||
return shFilePath;
|
||||
}
|
||||
|
||||
#endregion Linux
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public sealed class CoreInfoHandler
|
||||
{
|
||||
@@ -19,7 +17,7 @@ namespace ServiceLib.Handler
|
||||
{
|
||||
InitCoreInfo();
|
||||
}
|
||||
return _coreInfo?.FirstOrDefault(t => t.coreType == coreType);
|
||||
return _coreInfo?.FirstOrDefault(t => t.CoreType == coreType);
|
||||
}
|
||||
|
||||
public List<CoreInfo> GetCoreInfo()
|
||||
@@ -31,187 +29,174 @@ namespace ServiceLib.Handler
|
||||
return _coreInfo ?? [];
|
||||
}
|
||||
|
||||
public string GetCoreExecFile(CoreInfo? coreInfo, out string msg)
|
||||
{
|
||||
var fileName = string.Empty;
|
||||
msg = string.Empty;
|
||||
foreach (var name in coreInfo?.CoreExes)
|
||||
{
|
||||
var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString());
|
||||
if (File.Exists(vName))
|
||||
{
|
||||
fileName = vName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
msg = string.Format(ResUI.NotFoundCore, Utils.GetBinPath("", coreInfo.CoreType.ToString()), string.Join(", ", coreInfo.CoreExes.ToArray()), coreInfo.Url);
|
||||
Logging.SaveLog(msg);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private void InitCoreInfo()
|
||||
{
|
||||
_coreInfo = [];
|
||||
_coreInfo =
|
||||
[
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.v2rayN,
|
||||
Url = Global.NUrl,
|
||||
ReleaseApiUrl = Global.NUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
DownloadUrlWin64 = Global.NUrl + "/download/{0}/v2rayN-windows-64.zip",
|
||||
DownloadUrlWinArm64 = Global.NUrl + "/download/{0}/v2rayN-windows-arm64.zip",
|
||||
DownloadUrlLinux64 = Global.NUrl + "/download/{0}/v2rayN-linux-64.zip",
|
||||
DownloadUrlLinuxArm64 = Global.NUrl + "/download/{0}/v2rayN-linux-arm64.zip",
|
||||
DownloadUrlOSX64 = Global.NUrl + "/download/{0}/v2rayN-macos-64.zip",
|
||||
DownloadUrlOSXArm64 = Global.NUrl + "/download/{0}/v2rayN-macos-arm64.zip",
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.v2rayN,
|
||||
coreUrl = Global.NUrl,
|
||||
coreReleaseApiUrl = Global.NUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
coreDownloadUrl32 = Global.NUrl + "/download/{0}/v2rayN-32.zip",
|
||||
coreDownloadUrl64 = Global.NUrl + "/download/{0}/v2rayN.zip",
|
||||
coreDownloadUrlArm64 = Global.NUrl + "/download/{0}/v2rayN-arm64.zip",
|
||||
coreDownloadUrlLinux32 = Global.NUrl + "/download/{0}/v2rayN-linux-32.zip",
|
||||
coreDownloadUrlLinux64 = Global.NUrl + "/download/{0}/v2rayN-linux-64.zip",
|
||||
coreDownloadUrlLinuxArm64 = Global.NUrl + "/download/{0}/v2rayN-linux-arm64.zip",
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.v2fly,
|
||||
CoreExes = ["wv2ray", "v2ray"],
|
||||
Arguments = "",
|
||||
Url = Global.V2flyCoreUrl,
|
||||
ReleaseApiUrl = Global.V2flyCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
Match = "V2Ray",
|
||||
VersionArg = "-version",
|
||||
RedirectInfo = true,
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.v2fly,
|
||||
coreExes = new List<string> { "wv2ray", "v2ray" },
|
||||
arguments = "",
|
||||
coreUrl = Global.V2flyCoreUrl,
|
||||
coreReleaseApiUrl = Global.V2flyCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "V2Ray",
|
||||
versionArg = "-version",
|
||||
redirectInfo = true,
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.v2fly_v5,
|
||||
CoreExes = ["v2ray"],
|
||||
Arguments = "run -c config.json -format jsonv5",
|
||||
Url = Global.V2flyCoreUrl,
|
||||
ReleaseApiUrl = Global.V2flyCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
Match = "V2Ray",
|
||||
VersionArg = "version",
|
||||
RedirectInfo = true,
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.SagerNet,
|
||||
coreExes = new List<string> { "SagerNet", "v2ray" },
|
||||
arguments = "run",
|
||||
coreUrl = Global.SagerNetCoreUrl,
|
||||
coreReleaseApiUrl = Global.SagerNetCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "V2Ray",
|
||||
versionArg = "version",
|
||||
redirectInfo = true,
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.Xray,
|
||||
CoreExes = ["xray", "wxray"],
|
||||
Arguments = "run -c {0}",
|
||||
Url = Global.XrayCoreUrl,
|
||||
ReleaseApiUrl = Global.XrayCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
DownloadUrlWin64 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-64.zip",
|
||||
DownloadUrlWinArm64 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-arm64-v8a.zip",
|
||||
DownloadUrlLinux64 = Global.XrayCoreUrl + "/download/{0}/Xray-linux-64.zip",
|
||||
DownloadUrlLinuxArm64 = Global.XrayCoreUrl + "/download/{0}/Xray-linux-arm64-v8a.zip",
|
||||
DownloadUrlOSX64 = Global.XrayCoreUrl + "/download/{0}/Xray-macos-64.zip",
|
||||
DownloadUrlOSXArm64 = Global.XrayCoreUrl + "/download/{0}/Xray-macos-arm64-v8a.zip",
|
||||
Match = "Xray",
|
||||
VersionArg = "-version",
|
||||
RedirectInfo = true,
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.v2fly_v5,
|
||||
coreExes = new List<string> { "v2ray" },
|
||||
arguments = "run -c config.json -format jsonv5",
|
||||
coreUrl = Global.V2flyCoreUrl,
|
||||
coreReleaseApiUrl = Global.V2flyCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "V2Ray",
|
||||
versionArg = "version",
|
||||
redirectInfo = true,
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.mihomo,
|
||||
CoreExes = ["mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "mihomo", "clash"],
|
||||
Arguments = "-f config.json" + PortableMode(),
|
||||
Url = Global.MihomoCoreUrl,
|
||||
ReleaseApiUrl = Global.MihomoCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
DownloadUrlWin64 = Global.MihomoCoreUrl + "/download/{0}/mihomo-windows-amd64-compatible-{0}.zip",
|
||||
DownloadUrlWinArm64 = Global.MihomoCoreUrl + "/download/{0}/mihomo-windows-arm64-{0}.zip",
|
||||
DownloadUrlLinux64 = Global.MihomoCoreUrl + "/download/{0}/mihomo-linux-amd64-compatible-{0}.gz",
|
||||
DownloadUrlLinuxArm64 = Global.MihomoCoreUrl + "/download/{0}/mihomo-linux-arm64-{0}.gz",
|
||||
DownloadUrlOSX64 = Global.MihomoCoreUrl + "/download/{0}/mihomo-darwin-amd64-compatible-{0}.gz",
|
||||
DownloadUrlOSXArm64 = Global.MihomoCoreUrl + "/download/{0}/mihomo-darwin-arm64-{0}.gz",
|
||||
Match = "Mihomo",
|
||||
VersionArg = "-v",
|
||||
RedirectInfo = true,
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.Xray,
|
||||
coreExes = new List<string> { "xray", "wxray" },
|
||||
arguments = "run {0}",
|
||||
coreUrl = Global.XrayCoreUrl,
|
||||
coreReleaseApiUrl = Global.XrayCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
coreDownloadUrl32 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-32.zip",
|
||||
coreDownloadUrl64 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-64.zip",
|
||||
coreDownloadUrlArm64 = Global.XrayCoreUrl + "/download/{0}/Xray-windows-arm64-v8a.zip",
|
||||
coreDownloadUrlLinux32 = Global.XrayCoreUrl + "/download/{0}/Xray-linux-32.zip",
|
||||
coreDownloadUrlLinux64 = Global.XrayCoreUrl + "/download/{0}/Xray-linux-64.zip",
|
||||
coreDownloadUrlLinuxArm64 = Global.XrayCoreUrl + "/download/{0}/Xray-linux-arm64-v8a.zip",
|
||||
match = "Xray",
|
||||
versionArg = "-version",
|
||||
redirectInfo = true,
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.hysteria,
|
||||
CoreExes = ["hysteria-windows-amd64", "hysteria"],
|
||||
Arguments = "",
|
||||
Url = Global.HysteriaCoreUrl,
|
||||
ReleaseApiUrl = Global.HysteriaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
RedirectInfo = true,
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.clash,
|
||||
coreExes = new List<string> { "clash-windows-amd64-v3", "clash-windows-amd64", "clash-windows-386", "clash" },
|
||||
arguments = "-f config.json" + PortableMode(),
|
||||
coreUrl = Global.ClashCoreUrl,
|
||||
coreReleaseApiUrl = Global.ClashCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "v",
|
||||
versionArg = "-v",
|
||||
redirectInfo = true,
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.naiveproxy,
|
||||
CoreExes = ["naiveproxy", "naive"],
|
||||
Arguments = "config.json",
|
||||
Url = Global.NaiveproxyCoreUrl,
|
||||
RedirectInfo = false,
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.clash_meta,
|
||||
coreExes = new List<string> { "Clash.Meta-windows-amd64-compatible", "Clash.Meta-windows-amd64", "Clash.Meta-windows-386", "Clash.Meta", "clash" },
|
||||
arguments = "-f config.json" + PortableMode(),
|
||||
coreUrl = Global.ClashMetaCoreUrl,
|
||||
coreReleaseApiUrl = Global.ClashMetaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
match = "v",
|
||||
versionArg = "-v",
|
||||
redirectInfo = true,
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.tuic,
|
||||
CoreExes = ["tuic-client", "tuic"],
|
||||
Arguments = "-c config.json",
|
||||
Url = Global.TuicCoreUrl,
|
||||
RedirectInfo = true,
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.mihomo,
|
||||
coreExes = new List<string> { $"mihomo-windows-amd64{(Avx2.X64.IsSupported ? "" : "-compatible")}", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-windows-386", "mihomo", "clash" },
|
||||
arguments = "-f config.json" + PortableMode(),
|
||||
coreUrl = Global.MihomoCoreUrl,
|
||||
coreReleaseApiUrl = Global.MihomoCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
coreDownloadUrl32 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-windows-386-{0}.zip",
|
||||
coreDownloadUrl64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-windows-amd64-compatible-{0}.zip",
|
||||
coreDownloadUrlArm64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-windows-arm64-{0}.zip",
|
||||
coreDownloadUrlLinux32 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-linux-386-{0}.gz",
|
||||
coreDownloadUrlLinux64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-linux-amd64-compatible-{0}.gz",
|
||||
coreDownloadUrlLinuxArm64 = Global.ClashMetaCoreUrl + "/download/{0}/mihomo-linux-arm64-{0}.gz",
|
||||
match = "Mihomo",
|
||||
versionArg = "-v",
|
||||
redirectInfo = true,
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.sing_box,
|
||||
CoreExes = ["sing-box-client", "sing-box"],
|
||||
Arguments = "run -c {0} --disable-color",
|
||||
Url = Global.SingboxCoreUrl,
|
||||
RedirectInfo = true,
|
||||
ReleaseApiUrl = Global.SingboxCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
DownloadUrlWin64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-amd64.zip",
|
||||
DownloadUrlWinArm64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-arm64.zip",
|
||||
DownloadUrlLinux64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-linux-amd64.tar.gz",
|
||||
DownloadUrlLinuxArm64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-linux-arm64.tar.gz",
|
||||
DownloadUrlOSX64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-darwin-amd64.tar.gz",
|
||||
DownloadUrlOSXArm64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-darwin-arm64.tar.gz",
|
||||
Match = "sing-box",
|
||||
VersionArg = "version",
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.hysteria,
|
||||
coreExes = new List<string> { "hysteria-windows-amd64", "hysteria-windows-386", "hysteria" },
|
||||
arguments = "",
|
||||
coreUrl = Global.HysteriaCoreUrl,
|
||||
coreReleaseApiUrl = Global.HysteriaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
redirectInfo = true,
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.juicity,
|
||||
CoreExes = ["juicity-client", "juicity"],
|
||||
Arguments = "run -c config.json",
|
||||
Url = Global.JuicityCoreUrl
|
||||
},
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.naiveproxy,
|
||||
coreExes = new List<string> { "naiveproxy", "naive" },
|
||||
arguments = "config.json",
|
||||
coreUrl = Global.NaiveproxyCoreUrl,
|
||||
redirectInfo = false,
|
||||
});
|
||||
new CoreInfo
|
||||
{
|
||||
CoreType = ECoreType.hysteria2,
|
||||
CoreExes = ["hysteria-windows-amd64", "hysteria-linux-amd64", "hysteria"],
|
||||
Arguments = "",
|
||||
Url = Global.HysteriaCoreUrl,
|
||||
ReleaseApiUrl = Global.HysteriaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
RedirectInfo = true,
|
||||
}
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.tuic,
|
||||
coreExes = new List<string> { "tuic-client", "tuic" },
|
||||
arguments = "-c config.json",
|
||||
coreUrl = Global.TuicCoreUrl,
|
||||
redirectInfo = true,
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.sing_box,
|
||||
coreExes = new List<string> { "sing-box-client", "sing-box" },
|
||||
arguments = "run {0} --disable-color",
|
||||
coreUrl = Global.SingboxCoreUrl,
|
||||
redirectInfo = true,
|
||||
coreReleaseApiUrl = Global.SingboxCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
coreDownloadUrl32 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-386.zip",
|
||||
coreDownloadUrl64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-amd64.zip",
|
||||
coreDownloadUrlArm64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-windows-arm64.zip",
|
||||
coreDownloadUrlLinux32 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-linux-386.tar.gz",
|
||||
coreDownloadUrlLinux64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-linux-amd64.tar.gz",
|
||||
coreDownloadUrlLinuxArm64 = Global.SingboxCoreUrl + "/download/{0}/sing-box-{1}-linux-arm64.tar.gz",
|
||||
match = "sing-box",
|
||||
versionArg = "version",
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.juicity,
|
||||
coreExes = new List<string> { "juicity-client", "juicity" },
|
||||
arguments = "run -c config.json",
|
||||
coreUrl = Global.JuicityCoreUrl
|
||||
});
|
||||
|
||||
_coreInfo.Add(new CoreInfo
|
||||
{
|
||||
coreType = ECoreType.hysteria2,
|
||||
coreExes = new List<string> { "hysteria-windows-amd64", "hysteria-windows-386", "hysteria" },
|
||||
arguments = "",
|
||||
coreUrl = Global.HysteriaCoreUrl,
|
||||
coreReleaseApiUrl = Global.HysteriaCoreUrl.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
redirectInfo = true,
|
||||
});
|
||||
];
|
||||
}
|
||||
|
||||
private string PortableMode()
|
||||
{
|
||||
return $" -d \"{Utils.GetBinPath("")}\"";
|
||||
return $" -d {Utils.GetBinPath("").AppendQuotes()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,14 @@ namespace ServiceLib.Handler.Fmt
|
||||
|
||||
protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(item.flow))
|
||||
if (Utils.IsNotEmpty(item.Flow))
|
||||
{
|
||||
dicQuery.Add("flow", item.flow);
|
||||
dicQuery.Add("flow", item.Flow);
|
||||
}
|
||||
|
||||
if (!Utils.IsNullOrEmpty(item.streamSecurity))
|
||||
if (Utils.IsNotEmpty(item.StreamSecurity))
|
||||
{
|
||||
dicQuery.Add("security", item.streamSecurity);
|
||||
dicQuery.Add("security", item.StreamSecurity);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -32,95 +32,113 @@ namespace ServiceLib.Handler.Fmt
|
||||
dicQuery.Add("security", securityDef);
|
||||
}
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.sni))
|
||||
if (Utils.IsNotEmpty(item.Sni))
|
||||
{
|
||||
dicQuery.Add("sni", item.sni);
|
||||
dicQuery.Add("sni", item.Sni);
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.alpn))
|
||||
if (Utils.IsNotEmpty(item.Alpn))
|
||||
{
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.fingerprint))
|
||||
if (Utils.IsNotEmpty(item.Fingerprint))
|
||||
{
|
||||
dicQuery.Add("fp", Utils.UrlEncode(item.fingerprint));
|
||||
dicQuery.Add("fp", Utils.UrlEncode(item.Fingerprint));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.publicKey))
|
||||
if (Utils.IsNotEmpty(item.PublicKey))
|
||||
{
|
||||
dicQuery.Add("pbk", Utils.UrlEncode(item.publicKey));
|
||||
dicQuery.Add("pbk", Utils.UrlEncode(item.PublicKey));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.shortId))
|
||||
if (Utils.IsNotEmpty(item.ShortId))
|
||||
{
|
||||
dicQuery.Add("sid", Utils.UrlEncode(item.shortId));
|
||||
dicQuery.Add("sid", Utils.UrlEncode(item.ShortId));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.spiderX))
|
||||
if (Utils.IsNotEmpty(item.SpiderX))
|
||||
{
|
||||
dicQuery.Add("spx", Utils.UrlEncode(item.spiderX));
|
||||
dicQuery.Add("spx", Utils.UrlEncode(item.SpiderX));
|
||||
}
|
||||
if (item.allowInsecure.Equals("true"))
|
||||
if (item.AllowInsecure.Equals("true"))
|
||||
{
|
||||
dicQuery.Add("allowInsecure", "1");
|
||||
}
|
||||
|
||||
dicQuery.Add("type", !Utils.IsNullOrEmpty(item.network) ? item.network : nameof(ETransport.tcp));
|
||||
dicQuery.Add("type", Utils.IsNotEmpty(item.Network) ? item.Network : nameof(ETransport.tcp));
|
||||
|
||||
switch (item.network)
|
||||
switch (item.Network)
|
||||
{
|
||||
case nameof(ETransport.tcp):
|
||||
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
|
||||
if (!Utils.IsNullOrEmpty(item.requestHost))
|
||||
dicQuery.Add("headerType", Utils.IsNotEmpty(item.HeaderType) ? item.HeaderType : Global.None);
|
||||
if (Utils.IsNotEmpty(item.RequestHost))
|
||||
{
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.RequestHost));
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.kcp):
|
||||
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
dicQuery.Add("headerType", Utils.IsNotEmpty(item.HeaderType) ? item.HeaderType : Global.None);
|
||||
if (Utils.IsNotEmpty(item.Path))
|
||||
{
|
||||
dicQuery.Add("seed", Utils.UrlEncode(item.path));
|
||||
dicQuery.Add("seed", Utils.UrlEncode(item.Path));
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.ws):
|
||||
case nameof(ETransport.httpupgrade):
|
||||
case nameof(ETransport.splithttp):
|
||||
if (!Utils.IsNullOrEmpty(item.requestHost))
|
||||
if (Utils.IsNotEmpty(item.RequestHost))
|
||||
{
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.RequestHost));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
if (Utils.IsNotEmpty(item.Path))
|
||||
{
|
||||
dicQuery.Add("path", Utils.UrlEncode(item.path));
|
||||
dicQuery.Add("path", Utils.UrlEncode(item.Path));
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.xhttp):
|
||||
if (Utils.IsNotEmpty(item.RequestHost))
|
||||
{
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.RequestHost));
|
||||
}
|
||||
if (Utils.IsNotEmpty(item.Path))
|
||||
{
|
||||
dicQuery.Add("path", Utils.UrlEncode(item.Path));
|
||||
}
|
||||
if (Utils.IsNotEmpty(item.HeaderType) && Global.XhttpMode.Contains(item.HeaderType))
|
||||
{
|
||||
dicQuery.Add("mode", Utils.UrlEncode(item.HeaderType));
|
||||
}
|
||||
if (Utils.IsNotEmpty(item.Extra))
|
||||
{
|
||||
dicQuery.Add("extra", Utils.UrlEncode(item.Extra));
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.http):
|
||||
case nameof(ETransport.h2):
|
||||
dicQuery["type"] = nameof(ETransport.http);
|
||||
if (!Utils.IsNullOrEmpty(item.requestHost))
|
||||
if (Utils.IsNotEmpty(item.RequestHost))
|
||||
{
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.requestHost));
|
||||
dicQuery.Add("host", Utils.UrlEncode(item.RequestHost));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
if (Utils.IsNotEmpty(item.Path))
|
||||
{
|
||||
dicQuery.Add("path", Utils.UrlEncode(item.path));
|
||||
dicQuery.Add("path", Utils.UrlEncode(item.Path));
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.quic):
|
||||
dicQuery.Add("headerType", !Utils.IsNullOrEmpty(item.headerType) ? item.headerType : Global.None);
|
||||
dicQuery.Add("quicSecurity", Utils.UrlEncode(item.requestHost));
|
||||
dicQuery.Add("key", Utils.UrlEncode(item.path));
|
||||
dicQuery.Add("headerType", Utils.IsNotEmpty(item.HeaderType) ? item.HeaderType : Global.None);
|
||||
dicQuery.Add("quicSecurity", Utils.UrlEncode(item.RequestHost));
|
||||
dicQuery.Add("key", Utils.UrlEncode(item.Path));
|
||||
break;
|
||||
|
||||
case nameof(ETransport.grpc):
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
if (Utils.IsNotEmpty(item.Path))
|
||||
{
|
||||
dicQuery.Add("authority", Utils.UrlEncode(item.requestHost));
|
||||
dicQuery.Add("serviceName", Utils.UrlEncode(item.path));
|
||||
if (item.headerType is Global.GrpcGunMode or Global.GrpcMultiMode)
|
||||
dicQuery.Add("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));
|
||||
dicQuery.Add("mode", Utils.UrlEncode(item.HeaderType));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -130,54 +148,60 @@ namespace ServiceLib.Handler.Fmt
|
||||
|
||||
protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item)
|
||||
{
|
||||
item.flow = query["flow"] ?? "";
|
||||
item.streamSecurity = query["security"] ?? "";
|
||||
item.sni = query["sni"] ?? "";
|
||||
item.alpn = Utils.UrlDecode(query["alpn"] ?? "");
|
||||
item.fingerprint = Utils.UrlDecode(query["fp"] ?? "");
|
||||
item.publicKey = Utils.UrlDecode(query["pbk"] ?? "");
|
||||
item.shortId = Utils.UrlDecode(query["sid"] ?? "");
|
||||
item.spiderX = Utils.UrlDecode(query["spx"] ?? "");
|
||||
item.allowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : "";
|
||||
item.Flow = query["flow"] ?? "";
|
||||
item.StreamSecurity = query["security"] ?? "";
|
||||
item.Sni = query["sni"] ?? "";
|
||||
item.Alpn = Utils.UrlDecode(query["alpn"] ?? "");
|
||||
item.Fingerprint = Utils.UrlDecode(query["fp"] ?? "");
|
||||
item.PublicKey = Utils.UrlDecode(query["pbk"] ?? "");
|
||||
item.ShortId = Utils.UrlDecode(query["sid"] ?? "");
|
||||
item.SpiderX = Utils.UrlDecode(query["spx"] ?? "");
|
||||
item.AllowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : "";
|
||||
|
||||
item.network = query["type"] ?? nameof(ETransport.tcp);
|
||||
switch (item.network)
|
||||
item.Network = query["type"] ?? nameof(ETransport.tcp);
|
||||
switch (item.Network)
|
||||
{
|
||||
case nameof(ETransport.tcp):
|
||||
item.headerType = query["headerType"] ?? Global.None;
|
||||
item.requestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
item.HeaderType = query["headerType"] ?? Global.None;
|
||||
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
|
||||
break;
|
||||
|
||||
case nameof(ETransport.kcp):
|
||||
item.headerType = query["headerType"] ?? Global.None;
|
||||
item.path = Utils.UrlDecode(query["seed"] ?? "");
|
||||
item.HeaderType = query["headerType"] ?? Global.None;
|
||||
item.Path = Utils.UrlDecode(query["seed"] ?? "");
|
||||
break;
|
||||
|
||||
case nameof(ETransport.ws):
|
||||
case nameof(ETransport.httpupgrade):
|
||||
case nameof(ETransport.splithttp):
|
||||
item.requestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
item.path = Utils.UrlDecode(query["path"] ?? "/");
|
||||
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
item.Path = Utils.UrlDecode(query["path"] ?? "/");
|
||||
break;
|
||||
|
||||
case nameof(ETransport.xhttp):
|
||||
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
item.Path = Utils.UrlDecode(query["path"] ?? "/");
|
||||
item.HeaderType = Utils.UrlDecode(query["mode"] ?? "");
|
||||
item.Extra = Utils.UrlDecode(query["extra"] ?? "");
|
||||
break;
|
||||
|
||||
case nameof(ETransport.http):
|
||||
case nameof(ETransport.h2):
|
||||
item.network = nameof(ETransport.h2);
|
||||
item.requestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
item.path = Utils.UrlDecode(query["path"] ?? "/");
|
||||
item.Network = nameof(ETransport.h2);
|
||||
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
|
||||
item.Path = Utils.UrlDecode(query["path"] ?? "/");
|
||||
break;
|
||||
|
||||
case nameof(ETransport.quic):
|
||||
item.headerType = query["headerType"] ?? Global.None;
|
||||
item.requestHost = query["quicSecurity"] ?? Global.None;
|
||||
item.path = Utils.UrlDecode(query["key"] ?? "");
|
||||
item.HeaderType = query["headerType"] ?? Global.None;
|
||||
item.RequestHost = query["quicSecurity"] ?? Global.None;
|
||||
item.Path = Utils.UrlDecode(query["key"] ?? "");
|
||||
break;
|
||||
|
||||
case nameof(ETransport.grpc):
|
||||
item.requestHost = Utils.UrlDecode(query["authority"] ?? "");
|
||||
item.path = Utils.UrlDecode(query["serviceName"] ?? "");
|
||||
item.headerType = Utils.UrlDecode(query["mode"] ?? Global.GrpcGunMode);
|
||||
item.RequestHost = Utils.UrlDecode(query["authority"] ?? "");
|
||||
item.Path = Utils.UrlDecode(query["serviceName"] ?? "");
|
||||
item.HeaderType = Utils.UrlDecode(query["mode"] ?? Global.GrpcGunMode);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -197,9 +221,19 @@ namespace ServiceLib.Handler.Fmt
|
||||
|
||||
protected static string WriteAllText(string strData, string ext = "json")
|
||||
{
|
||||
var fileName = Utils.GetTempPath($"{Utils.GetGUID(false)}.{ext}");
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.mihomo,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "clash_custom"
|
||||
CoreType = ECoreType.mihomo,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "clash_custom"
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
@@ -2,20 +2,22 @@
|
||||
{
|
||||
public class FmtHandler
|
||||
{
|
||||
private static readonly string _tag = "FmtHandler";
|
||||
|
||||
public static string? GetShareUri(ProfileItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = item.configType switch
|
||||
var url = item.ConfigType switch
|
||||
{
|
||||
EConfigType.VMess => VmessFmt.ToUri(item),
|
||||
EConfigType.Shadowsocks => ShadowsocksFmt.ToUri(item),
|
||||
EConfigType.Socks => SocksFmt.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.TUIC => TuicFmt.ToUri(item),
|
||||
EConfigType.WireGuard => WireguardFmt.ToUri(item),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -23,7 +25,7 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -49,7 +51,7 @@
|
||||
{
|
||||
return ShadowsocksFmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Socks]))
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS]))
|
||||
{
|
||||
return SocksFmt.Resolve(str, out msg);
|
||||
}
|
||||
@@ -65,11 +67,11 @@
|
||||
{
|
||||
return Hysteria2Fmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Tuic]))
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.TUIC]))
|
||||
{
|
||||
return TuicFmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Wireguard]))
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.WireGuard]))
|
||||
{
|
||||
return WireguardFmt.Resolve(str, out msg);
|
||||
}
|
||||
@@ -81,7 +83,7 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
msg = ResUI.Incorrectconfiguration;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -7,20 +7,21 @@
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Hysteria2
|
||||
ConfigType = EConfigType.Hysteria2
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
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);
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveStdTransport(query, ref item);
|
||||
item.path = Utils.UrlDecode(query["obfs-password"] ?? "");
|
||||
item.allowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false";
|
||||
item.Path = Utils.UrlDecode(query["obfs-password"] ?? "");
|
||||
item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false";
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -31,34 +32,27 @@
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
if (Utils.IsNotEmpty(item.Remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (!Utils.IsNullOrEmpty(item.sni))
|
||||
if (Utils.IsNotEmpty(item.Sni))
|
||||
{
|
||||
dicQuery.Add("sni", item.sni);
|
||||
dicQuery.Add("sni", item.Sni);
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.alpn))
|
||||
if (Utils.IsNotEmpty(item.Alpn))
|
||||
{
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
if (Utils.IsNotEmpty(item.Path))
|
||||
{
|
||||
dicQuery.Add("obfs", "salamander");
|
||||
dicQuery.Add("obfs-password", Utils.UrlEncode(item.path));
|
||||
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path));
|
||||
}
|
||||
dicQuery.Add("insecure", item.allowInsecure.ToLower() == "true" ? "1" : "0");
|
||||
dicQuery.Add("insecure", item.AllowInsecure.ToLower() == "true" ? "1" : "0");
|
||||
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
item.id,
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.Hysteria2]}{url}/{query}{remark}";
|
||||
return url;
|
||||
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark);
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
@@ -69,9 +63,9 @@
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.hysteria,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "hysteria_custom"
|
||||
CoreType = ECoreType.hysteria,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "hysteria_custom"
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
@@ -87,9 +81,9 @@
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.hysteria2,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "hysteria2_custom"
|
||||
CoreType = ECoreType.hysteria2,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "hysteria2_custom"
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.naiveproxy,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "naiveproxy_custom"
|
||||
CoreType = ECoreType.naiveproxy,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "naiveproxy_custom"
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (item.address.Length == 0 || item.port == 0 || item.security.Length == 0 || item.id.Length == 0)
|
||||
if (item.Address.Length == 0 || item.Port == 0 || item.Security.Length == 0 || item.Id.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.configType = EConfigType.Shadowsocks;
|
||||
item.ConfigType = EConfigType.Shadowsocks;
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -30,9 +30,9 @@ namespace ServiceLib.Handler.Fmt
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
if (Utils.IsNotEmpty(item.Remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
//url = string.Format("{0}:{1}@{2}:{3}",
|
||||
// item.security,
|
||||
@@ -41,10 +41,8 @@ namespace ServiceLib.Handler.Fmt
|
||||
// item.port);
|
||||
//url = Utile.Base64Encode(url);
|
||||
//new Sip002
|
||||
var pw = Utils.Base64Encode($"{item.security}:{item.id}");
|
||||
url = $"{pw}@{GetIpv6(item.address)}:{item.port}";
|
||||
url = $"{Global.ProtocolShares[EConfigType.Shadowsocks]}{url}{remark}";
|
||||
return url;
|
||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}");
|
||||
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);
|
||||
@@ -59,9 +57,9 @@ namespace ServiceLib.Handler.Fmt
|
||||
ProfileItem item = new();
|
||||
var base64 = match.Groups["base64"].Value.TrimEnd('/');
|
||||
var tag = match.Groups["tag"].Value;
|
||||
if (!Utils.IsNullOrEmpty(tag))
|
||||
if (Utils.IsNotEmpty(tag))
|
||||
{
|
||||
item.remarks = Utils.UrlDecode(tag);
|
||||
item.Remarks = Utils.UrlDecode(tag);
|
||||
}
|
||||
Match details;
|
||||
try
|
||||
@@ -74,31 +72,25 @@ namespace ServiceLib.Handler.Fmt
|
||||
}
|
||||
if (!details.Success)
|
||||
return null;
|
||||
item.security = details.Groups["method"].Value;
|
||||
item.id = details.Groups["password"].Value;
|
||||
item.address = details.Groups["hostname"].Value;
|
||||
item.port = Utils.ToInt(details.Groups["port"].Value);
|
||||
item.Security = details.Groups["method"].Value;
|
||||
item.Id = details.Groups["password"].Value;
|
||||
item.Address = details.Groups["hostname"].Value;
|
||||
item.Port = Utils.ToInt(details.Groups["port"].Value);
|
||||
return item;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveSip002(string result)
|
||||
{
|
||||
Uri parsedUrl;
|
||||
try
|
||||
{
|
||||
parsedUrl = new Uri(result);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
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,
|
||||
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
||||
Address = parsedUrl.IdnHost,
|
||||
Port = parsedUrl.Port,
|
||||
};
|
||||
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.UriEscaped);
|
||||
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||
//2022-blake3
|
||||
if (rawUserInfo.Contains(':'))
|
||||
{
|
||||
@@ -107,8 +99,8 @@ namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.security = userInfoParts[0];
|
||||
item.id = Utils.UrlDecode(userInfoParts[1]);
|
||||
item.Security = userInfoParts.First();
|
||||
item.Id = Utils.UrlDecode(userInfoParts.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -119,8 +111,8 @@ namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.security = userInfoParts[0];
|
||||
item.id = userInfoParts[1];
|
||||
item.Security = userInfoParts.First();
|
||||
item.Id = userInfoParts.Last();
|
||||
}
|
||||
|
||||
var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
|
||||
@@ -128,12 +120,12 @@ namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
//obfs-host exists
|
||||
var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host"));
|
||||
if (queryParameters["plugin"].Contains("obfs=http") && !Utils.IsNullOrEmpty(obfsHost))
|
||||
if (queryParameters["plugin"].Contains("obfs=http") && Utils.IsNotEmpty(obfsHost))
|
||||
{
|
||||
obfsHost = obfsHost?.Replace("obfs-host=", "");
|
||||
item.network = Global.DefaultNetwork;
|
||||
item.headerType = Global.TcpHeaderHttp;
|
||||
item.requestHost = obfsHost ?? "";
|
||||
item.Network = Global.DefaultNetwork;
|
||||
item.HeaderType = Global.TcpHeaderHttp;
|
||||
item.RequestHost = obfsHost ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -164,11 +156,11 @@ namespace ServiceLib.Handler.Fmt
|
||||
{
|
||||
var ssItem = new ProfileItem()
|
||||
{
|
||||
remarks = it.remarks,
|
||||
security = it.method,
|
||||
id = it.password,
|
||||
address = it.server,
|
||||
port = Utils.ToInt(it.server_port)
|
||||
Remarks = it.remarks,
|
||||
Security = it.method,
|
||||
Id = it.password,
|
||||
Address = it.server,
|
||||
Port = Utils.ToInt(it.server_port)
|
||||
};
|
||||
lst.Add(ssItem);
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
var profileIt = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.sing_box,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "singbox_custom",
|
||||
CoreType = ECoreType.sing_box,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "singbox_custom",
|
||||
};
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
@@ -42,9 +42,9 @@
|
||||
var fileName = WriteAllText(strData);
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.sing_box,
|
||||
address = fileName,
|
||||
remarks = subRemarks ?? "singbox_custom"
|
||||
CoreType = ECoreType.sing_box,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "singbox_custom"
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
|
||||
@@ -5,19 +5,18 @@
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem? item;
|
||||
|
||||
item = ResolveSocksNew(str) ?? ResolveSocks(str);
|
||||
var item = ResolveSocksNew(str) ?? ResolveSocks(str);
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (item.address.Length == 0 || item.port == 0)
|
||||
if (item.Address.Length == 0 || item.Port == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.configType = EConfigType.Socks;
|
||||
item.ConfigType = EConfigType.SOCKS;
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -25,46 +24,38 @@
|
||||
public static string? ToUri(ProfileItem? item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
string url = string.Empty;
|
||||
var url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
var remark = string.Empty;
|
||||
if (Utils.IsNotEmpty(item.Remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
//url = string.Format("{0}:{1}@{2}:{3}",
|
||||
// item.security,
|
||||
// item.id,
|
||||
// item.address,
|
||||
// item.port);
|
||||
//url = Utile.Base64Encode(url);
|
||||
//new
|
||||
var pw = Utils.Base64Encode($"{item.security}:{item.id}");
|
||||
url = $"{pw}@{GetIpv6(item.address)}:{item.port}";
|
||||
url = $"{Global.ProtocolShares[EConfigType.Socks]}{url}{remark}";
|
||||
return url;
|
||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}");
|
||||
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveSocks(string result)
|
||||
{
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Socks
|
||||
ConfigType = EConfigType.SOCKS
|
||||
};
|
||||
result = result[Global.ProtocolShares[EConfigType.Socks].Length..];
|
||||
result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..];
|
||||
//remark
|
||||
int indexRemark = result.IndexOf("#");
|
||||
var indexRemark = result.IndexOf("#");
|
||||
if (indexRemark > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
item.remarks = Utils.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1));
|
||||
item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1));
|
||||
}
|
||||
catch { }
|
||||
result = result[..indexRemark];
|
||||
}
|
||||
//part decode
|
||||
int indexS = result.IndexOf("@");
|
||||
var indexS = result.IndexOf("@");
|
||||
if (indexS > 0)
|
||||
{
|
||||
}
|
||||
@@ -73,52 +64,45 @@
|
||||
result = Utils.Base64Decode(result);
|
||||
}
|
||||
|
||||
string[] arr1 = result.Split('@');
|
||||
var arr1 = result.Split('@');
|
||||
if (arr1.Length != 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string[] arr21 = arr1[0].Split(':');
|
||||
//string[] arr22 = arr1[1].Split(':');
|
||||
int indexPort = arr1[1].LastIndexOf(":");
|
||||
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 = Utils.ToInt(arr1[1][(indexPort + 1)..]);
|
||||
item.security = arr21[0];
|
||||
item.id = arr21[1];
|
||||
item.Address = arr1[1][..indexPort];
|
||||
item.Port = Utils.ToInt(arr1[1][(indexPort + 1)..]);
|
||||
item.Security = arr21.First();
|
||||
item.Id = arr21[1];
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveSocksNew(string result)
|
||||
{
|
||||
Uri parsedUrl;
|
||||
try
|
||||
{
|
||||
parsedUrl = new Uri(result);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
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,
|
||||
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
|
||||
Address = parsedUrl.IdnHost,
|
||||
Port = parsedUrl.Port,
|
||||
};
|
||||
|
||||
// parse base64 UserInfo
|
||||
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped);
|
||||
string userInfo = Utils.Base64Decode(rawUserInfo);
|
||||
string[] userInfoParts = userInfo.Split(new[] { ':' }, 2);
|
||||
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||
var userInfo = Utils.Base64Decode(rawUserInfo);
|
||||
var userInfoParts = userInfo.Split([':'], 2);
|
||||
if (userInfoParts.Length == 2)
|
||||
{
|
||||
item.security = userInfoParts[0];
|
||||
item.id = userInfoParts[1];
|
||||
item.Security = userInfoParts.First();
|
||||
item.Id = userInfoParts[1];
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Trojan
|
||||
ConfigType = EConfigType.Trojan
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
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);
|
||||
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);
|
||||
@@ -30,20 +31,14 @@
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
if (Utils.IsNotEmpty(item.Remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
GetStdTransport(item, null, ref dicQuery);
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
item.id,
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.Trojan]}{url}{query}{remark}";
|
||||
return url;
|
||||
return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,24 +8,26 @@
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Tuic
|
||||
ConfigType = EConfigType.TUIC
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
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 userInfoParts = url.UserInfo.Split(new[] { ':' }, 2);
|
||||
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[0];
|
||||
item.security = userInfoParts[1];
|
||||
item.Id = userInfoParts.First();
|
||||
item.Security = userInfoParts.Last();
|
||||
}
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveStdTransport(query, ref item);
|
||||
item.headerType = query["congestion_control"] ?? "";
|
||||
item.HeaderType = query["congestion_control"] ?? "";
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -36,29 +38,22 @@
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
if (Utils.IsNotEmpty(item.Remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (!Utils.IsNullOrEmpty(item.sni))
|
||||
if (Utils.IsNotEmpty(item.Sni))
|
||||
{
|
||||
dicQuery.Add("sni", item.sni);
|
||||
dicQuery.Add("sni", item.Sni);
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.alpn))
|
||||
if (Utils.IsNotEmpty(item.Alpn))
|
||||
{
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||
}
|
||||
dicQuery.Add("congestion_control", item.headerType);
|
||||
dicQuery.Add("congestion_control", item.HeaderType);
|
||||
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
$"{item.id}:{item.security}",
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.Tuic]}{url}{query}{remark}";
|
||||
return url;
|
||||
return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
var profileIt = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.Xray,
|
||||
address = fileName,
|
||||
remarks = v2rayCon.remarks ?? subRemarks ?? "v2ray_custom",
|
||||
CoreType = ECoreType.Xray,
|
||||
Address = fileName,
|
||||
Remarks = v2rayCon.remarks ?? subRemarks ?? "v2ray_custom",
|
||||
};
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
@@ -43,9 +43,9 @@
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
coreType = ECoreType.Xray,
|
||||
address = fileName,
|
||||
remarks = v2rayConfig.remarks ?? subRemarks ?? "v2ray_custom"
|
||||
CoreType = ECoreType.Xray,
|
||||
Address = fileName,
|
||||
Remarks = v2rayConfig.remarks ?? subRemarks ?? "v2ray_custom"
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
|
||||
@@ -8,20 +8,21 @@
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.VLESS,
|
||||
security = Global.None
|
||||
ConfigType = EConfigType.VLESS,
|
||||
Security = Global.None
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
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);
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
item.security = query["encryption"] ?? Global.None;
|
||||
item.streamSecurity = query["security"] ?? "";
|
||||
item.Security = query["encryption"] ?? Global.None;
|
||||
item.StreamSecurity = query["security"] ?? "";
|
||||
ResolveStdTransport(query, ref item);
|
||||
|
||||
return item;
|
||||
@@ -33,28 +34,22 @@
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
if (Utils.IsNotEmpty(item.Remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (!Utils.IsNullOrEmpty(item.security))
|
||||
if (Utils.IsNotEmpty(item.Security))
|
||||
{
|
||||
dicQuery.Add("encryption", item.security);
|
||||
dicQuery.Add("encryption", item.Security);
|
||||
}
|
||||
else
|
||||
{
|
||||
dicQuery.Add("encryption", Global.None);
|
||||
}
|
||||
GetStdTransport(item, Global.None, ref dicQuery);
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
item.id,
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.VLESS]}{url}{query}{remark}";
|
||||
return url;
|
||||
return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,21 +24,21 @@
|
||||
|
||||
VmessQRCode vmessQRCode = new()
|
||||
{
|
||||
v = item.configVersion,
|
||||
ps = item.remarks.TrimEx(),
|
||||
add = item.address,
|
||||
port = item.port,
|
||||
id = item.id,
|
||||
aid = item.alterId,
|
||||
scy = item.security,
|
||||
net = item.network,
|
||||
type = item.headerType,
|
||||
host = item.requestHost,
|
||||
path = item.path,
|
||||
tls = item.streamSecurity,
|
||||
sni = item.sni,
|
||||
alpn = item.alpn,
|
||||
fp = item.fingerprint
|
||||
v = item.ConfigVersion,
|
||||
ps = item.Remarks.TrimEx(),
|
||||
add = item.Address,
|
||||
port = item.Port,
|
||||
id = item.Id,
|
||||
aid = item.AlterId,
|
||||
scy = item.Security,
|
||||
net = item.Network,
|
||||
type = item.HeaderType,
|
||||
host = item.RequestHost,
|
||||
path = item.Path,
|
||||
tls = item.StreamSecurity,
|
||||
sni = item.Sni,
|
||||
alpn = item.Alpn,
|
||||
fp = item.Fingerprint
|
||||
};
|
||||
|
||||
url = JsonUtils.Serialize(vmessQRCode);
|
||||
@@ -53,13 +53,12 @@
|
||||
msg = string.Empty;
|
||||
var item = new ProfileItem
|
||||
{
|
||||
configType = EConfigType.VMess
|
||||
ConfigType = EConfigType.VMess
|
||||
};
|
||||
|
||||
result = result[Global.ProtocolShares[EConfigType.VMess].Length..];
|
||||
result = Utils.Base64Decode(result);
|
||||
|
||||
//转成Json
|
||||
VmessQRCode? vmessQRCode = JsonUtils.Deserialize<VmessQRCode>(result);
|
||||
if (vmessQRCode == null)
|
||||
{
|
||||
@@ -67,33 +66,33 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
item.network = Global.DefaultNetwork;
|
||||
item.headerType = Global.None;
|
||||
item.Network = Global.DefaultNetwork;
|
||||
item.HeaderType = Global.None;
|
||||
|
||||
item.configVersion = Utils.ToInt(vmessQRCode.v);
|
||||
item.remarks = Utils.ToString(vmessQRCode.ps);
|
||||
item.address = Utils.ToString(vmessQRCode.add);
|
||||
item.port = Utils.ToInt(vmessQRCode.port);
|
||||
item.id = Utils.ToString(vmessQRCode.id);
|
||||
item.alterId = Utils.ToInt(vmessQRCode.aid);
|
||||
item.security = Utils.ToString(vmessQRCode.scy);
|
||||
item.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 = !Utils.IsNullOrEmpty(vmessQRCode.scy) ? vmessQRCode.scy : Global.DefaultSecurity;
|
||||
if (!Utils.IsNullOrEmpty(vmessQRCode.net))
|
||||
item.Security = Utils.IsNotEmpty(vmessQRCode.scy) ? vmessQRCode.scy : Global.DefaultSecurity;
|
||||
if (Utils.IsNotEmpty(vmessQRCode.net))
|
||||
{
|
||||
item.network = vmessQRCode.net;
|
||||
item.Network = vmessQRCode.net;
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(vmessQRCode.type))
|
||||
if (Utils.IsNotEmpty(vmessQRCode.type))
|
||||
{
|
||||
item.headerType = vmessQRCode.type;
|
||||
item.HeaderType = vmessQRCode.type;
|
||||
}
|
||||
|
||||
item.requestHost = Utils.ToString(vmessQRCode.host);
|
||||
item.path = Utils.ToString(vmessQRCode.path);
|
||||
item.streamSecurity = Utils.ToString(vmessQRCode.tls);
|
||||
item.sni = Utils.ToString(vmessQRCode.sni);
|
||||
item.alpn = Utils.ToString(vmessQRCode.alpn);
|
||||
item.fingerprint = Utils.ToString(vmessQRCode.fp);
|
||||
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;
|
||||
}
|
||||
@@ -102,16 +101,17 @@
|
||||
{
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.VMess,
|
||||
security = "auto"
|
||||
ConfigType = EConfigType.VMess,
|
||||
Security = "auto"
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
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);
|
||||
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);
|
||||
|
||||
@@ -8,22 +8,23 @@
|
||||
|
||||
ProfileItem item = new()
|
||||
{
|
||||
configType = EConfigType.Wireguard
|
||||
ConfigType = EConfigType.WireGuard
|
||||
};
|
||||
|
||||
Uri url = new(str);
|
||||
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);
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
|
||||
item.publicKey = Utils.UrlDecode(query["publickey"] ?? "");
|
||||
item.path = Utils.UrlDecode(query["reserved"] ?? "");
|
||||
item.requestHost = Utils.UrlDecode(query["address"] ?? "");
|
||||
item.shortId = Utils.UrlDecode(query["mtu"] ?? "");
|
||||
item.PublicKey = Utils.UrlDecode(query["publickey"] ?? "");
|
||||
item.Path = Utils.UrlDecode(query["reserved"] ?? "");
|
||||
item.RequestHost = Utils.UrlDecode(query["address"] ?? "");
|
||||
item.ShortId = Utils.UrlDecode(query["mtu"] ?? "");
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -34,36 +35,29 @@
|
||||
string url = string.Empty;
|
||||
|
||||
string remark = string.Empty;
|
||||
if (!Utils.IsNullOrEmpty(item.remarks))
|
||||
if (Utils.IsNotEmpty(item.Remarks))
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.remarks);
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (!Utils.IsNullOrEmpty(item.publicKey))
|
||||
if (Utils.IsNotEmpty(item.PublicKey))
|
||||
{
|
||||
dicQuery.Add("publickey", Utils.UrlEncode(item.publicKey));
|
||||
dicQuery.Add("publickey", Utils.UrlEncode(item.PublicKey));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.path))
|
||||
if (Utils.IsNotEmpty(item.Path))
|
||||
{
|
||||
dicQuery.Add("reserved", Utils.UrlEncode(item.path));
|
||||
dicQuery.Add("reserved", Utils.UrlEncode(item.Path));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.requestHost))
|
||||
if (Utils.IsNotEmpty(item.RequestHost))
|
||||
{
|
||||
dicQuery.Add("address", Utils.UrlEncode(item.requestHost));
|
||||
dicQuery.Add("address", Utils.UrlEncode(item.RequestHost));
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(item.shortId))
|
||||
if (Utils.IsNotEmpty(item.ShortId))
|
||||
{
|
||||
dicQuery.Add("mtu", Utils.UrlEncode(item.shortId));
|
||||
dicQuery.Add("mtu", Utils.UrlEncode(item.ShortId));
|
||||
}
|
||||
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
|
||||
|
||||
url = string.Format("{0}@{1}:{2}",
|
||||
Utils.UrlEncode(item.id),
|
||||
GetIpv6(item.address),
|
||||
item.port);
|
||||
url = $"{Global.ProtocolShares[EConfigType.Wireguard]}{url}/{query}{remark}";
|
||||
return url;
|
||||
return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public sealed class LazyConfig
|
||||
{
|
||||
private static readonly Lazy<LazyConfig> _instance = new(() => new());
|
||||
private Config _config;
|
||||
private int? _statePort;
|
||||
private int? _statePort2;
|
||||
|
||||
public static LazyConfig Instance => _instance.Value;
|
||||
public Config Config => _config;
|
||||
|
||||
public int StatePort
|
||||
{
|
||||
get
|
||||
{
|
||||
_statePort ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api));
|
||||
return _statePort.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public int StatePort2
|
||||
{
|
||||
get
|
||||
{
|
||||
_statePort2 ??= Utils.GetFreePort(GetLocalPort(EInboundProtocol.api2));
|
||||
return _statePort2.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private Job? _processJob;
|
||||
|
||||
public LazyConfig()
|
||||
{
|
||||
SQLiteHelper.Instance.CreateTable<SubItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ProfileItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ServerStatItem>();
|
||||
SQLiteHelper.Instance.CreateTable<RoutingItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
||||
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
||||
}
|
||||
|
||||
#region Config
|
||||
|
||||
public void SetConfig(Config config) => _config = config;
|
||||
|
||||
public int GetLocalPort(EInboundProtocol protocol)
|
||||
{
|
||||
var localPort = _config.inbound.FirstOrDefault(t => t.protocol == nameof(EInboundProtocol.socks))?.localPort ?? 10808;
|
||||
return localPort + (int)protocol;
|
||||
}
|
||||
|
||||
public void AddProcess(IntPtr processHandle)
|
||||
{
|
||||
if (Utils.IsWindows())
|
||||
{
|
||||
_processJob ??= new();
|
||||
_processJob?.AddProcess(processHandle);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Config
|
||||
|
||||
#region SqliteHelper
|
||||
|
||||
public List<SubItem> SubItems()
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<SubItem>().ToList();
|
||||
}
|
||||
|
||||
public SubItem GetSubItem(string subid)
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<SubItem>().FirstOrDefault(t => t.id == subid);
|
||||
}
|
||||
|
||||
public List<ProfileItem> ProfileItems(string subid)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(subid))
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().Where(t => t.subid == subid).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> ProfileItemIndexes(string subid)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(subid))
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().Select(t => t.indexId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().Where(t => t.subid == subid).Select(t => t.indexId).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public List<ProfileItemModel> ProfileItems(string subid, string filter)
|
||||
{
|
||||
var sql = @$"select a.*
|
||||
,b.remarks subRemarks
|
||||
from ProfileItem a
|
||||
left join SubItem b on a.subid = b.id
|
||||
where 1=1 ";
|
||||
if (!Utils.IsNullOrEmpty(subid))
|
||||
{
|
||||
sql += $" and a.subid = '{subid}'";
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(filter))
|
||||
{
|
||||
if (filter.Contains('\''))
|
||||
{
|
||||
filter = filter.Replace("'", "");
|
||||
}
|
||||
sql += String.Format(" and (a.remarks like '%{0}%' or a.address like '%{0}%') ", filter);
|
||||
}
|
||||
|
||||
return SQLiteHelper.Instance.Query<ProfileItemModel>(sql).ToList();
|
||||
}
|
||||
|
||||
public List<ProfileItemModel> ProfileItemsEx(string subid, string filter)
|
||||
{
|
||||
var lstModel = ProfileItems(_config.subIndexId, filter);
|
||||
|
||||
ConfigHandler.SetDefaultServer(_config, lstModel);
|
||||
|
||||
var lstServerStat = (_config.guiItem.enableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? [];
|
||||
var lstProfileExs = ProfileExHandler.Instance.ProfileExs;
|
||||
lstModel = (from t in lstModel
|
||||
join t2 in lstServerStat on t.indexId equals t2.indexId into t2b
|
||||
from t22 in t2b.DefaultIfEmpty()
|
||||
join t3 in lstProfileExs on t.indexId equals t3.indexId into t3b
|
||||
from t33 in t3b.DefaultIfEmpty()
|
||||
select new ProfileItemModel
|
||||
{
|
||||
indexId = t.indexId,
|
||||
configType = t.configType,
|
||||
remarks = t.remarks,
|
||||
address = t.address,
|
||||
port = t.port,
|
||||
security = t.security,
|
||||
network = t.network,
|
||||
streamSecurity = t.streamSecurity,
|
||||
subid = t.subid,
|
||||
subRemarks = t.subRemarks,
|
||||
isActive = t.indexId == _config.indexId,
|
||||
sort = t33 == null ? 0 : t33.sort,
|
||||
delay = t33 == null ? 0 : t33.delay,
|
||||
delayVal = t33?.delay != 0 ? $"{t33?.delay} {Global.DelayUnit}" : string.Empty,
|
||||
speedVal = t33?.speed != 0 ? $"{t33?.speed} {Global.SpeedUnit}" : string.Empty,
|
||||
todayDown = t22 == null ? "" : Utils.HumanFy(t22.todayDown),
|
||||
todayUp = t22 == null ? "" : Utils.HumanFy(t22.todayUp),
|
||||
totalDown = t22 == null ? "" : Utils.HumanFy(t22.totalDown),
|
||||
totalUp = t22 == null ? "" : Utils.HumanFy(t22.totalUp)
|
||||
}).OrderBy(t => t.sort).ToList();
|
||||
|
||||
return lstModel;
|
||||
}
|
||||
|
||||
public ProfileItem? GetProfileItem(string indexId)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(indexId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().FirstOrDefault(it => it.indexId == indexId);
|
||||
}
|
||||
|
||||
public ProfileItem? GetProfileItemViaRemarks(string remarks)
|
||||
{
|
||||
if (Utils.IsNullOrEmpty(remarks))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return SQLiteHelper.Instance.Table<ProfileItem>().FirstOrDefault(it => it.remarks == remarks);
|
||||
}
|
||||
|
||||
public List<RoutingItem> RoutingItems()
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<RoutingItem>().Where(it => it.locked == false).OrderBy(t => t.sort).ToList();
|
||||
}
|
||||
|
||||
public RoutingItem GetRoutingItem(string id)
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<RoutingItem>().FirstOrDefault(it => it.locked == false && it.id == id);
|
||||
}
|
||||
|
||||
public List<DNSItem> DNSItems()
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<DNSItem>().ToList();
|
||||
}
|
||||
|
||||
public DNSItem GetDNSItem(ECoreType eCoreType)
|
||||
{
|
||||
return SQLiteHelper.Instance.Table<DNSItem>().FirstOrDefault(it => it.coreType == eCoreType);
|
||||
}
|
||||
|
||||
#endregion SqliteHelper
|
||||
|
||||
#region Core Type
|
||||
|
||||
public List<string> GetShadowsocksSecurities(ProfileItem profileItem)
|
||||
{
|
||||
var coreType = GetCoreType(profileItem, EConfigType.Shadowsocks);
|
||||
switch (coreType)
|
||||
{
|
||||
case ECoreType.v2fly:
|
||||
return Global.SsSecurities;
|
||||
|
||||
case ECoreType.Xray:
|
||||
return Global.SsSecuritiesInXray;
|
||||
|
||||
case ECoreType.sing_box:
|
||||
return Global.SsSecuritiesInSingbox;
|
||||
}
|
||||
return Global.SsSecuritiesInSagerNet;
|
||||
}
|
||||
|
||||
public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType)
|
||||
{
|
||||
if (profileItem?.coreType != null)
|
||||
{
|
||||
return (ECoreType)profileItem.coreType;
|
||||
}
|
||||
|
||||
if (_config.coreTypeItem == null)
|
||||
{
|
||||
return ECoreType.Xray;
|
||||
}
|
||||
var item = _config.coreTypeItem.FirstOrDefault(it => it.configType == eConfigType);
|
||||
if (item == null)
|
||||
{
|
||||
return ECoreType.Xray;
|
||||
}
|
||||
return item.coreType;
|
||||
}
|
||||
|
||||
#endregion Core Type
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,16 @@ namespace ServiceLib.Handler
|
||||
{
|
||||
public class NoticeHandler
|
||||
{
|
||||
private static readonly Lazy<NoticeHandler> _instance = new(() => new());
|
||||
public static NoticeHandler Instance => _instance.Value;
|
||||
|
||||
public void Enqueue(string? content)
|
||||
{
|
||||
if (content.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
MessageBus.Current.SendMessage(content, Global.CommandSendSnackMsg);
|
||||
MessageBus.Current.SendMessage(content, EMsgCommand.SendSnackMsg.ToString());
|
||||
}
|
||||
|
||||
public void SendMessage(string? content)
|
||||
@@ -19,16 +22,16 @@ namespace ServiceLib.Handler
|
||||
{
|
||||
return;
|
||||
}
|
||||
MessageBus.Current.SendMessage(content, Global.CommandSendMsgView);
|
||||
MessageBus.Current.SendMessage(content, EMsgCommand.SendMsgView.ToString());
|
||||
}
|
||||
|
||||
public void SendMessage(string? content, bool time)
|
||||
public void SendMessageEx(string? content)
|
||||
{
|
||||
if (content.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
content = $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {content}";
|
||||
content = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss} {content}";
|
||||
SendMessage(content);
|
||||
}
|
||||
|
||||
|
||||
105
v2rayN/ServiceLib/Handler/PacHandler.cs
Normal file
105
v2rayN/ServiceLib/Handler/PacHandler.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public class PacHandler
|
||||
{
|
||||
private static string _configPath;
|
||||
private static int _httpPort;
|
||||
private static int _pacPort;
|
||||
private static TcpListener? _tcpListener;
|
||||
private static byte[] _writeContent;
|
||||
private static bool _isRunning;
|
||||
private static bool _needRestart = true;
|
||||
|
||||
public static async Task Start(string configPath, int httpPort, int pacPort)
|
||||
{
|
||||
_needRestart = (configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning);
|
||||
|
||||
_configPath = configPath;
|
||||
_httpPort = httpPort;
|
||||
_pacPort = pacPort;
|
||||
|
||||
await InitText();
|
||||
|
||||
if (_needRestart)
|
||||
{
|
||||
Stop();
|
||||
RunListener();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task InitText()
|
||||
{
|
||||
var path = Path.Combine(_configPath, "pac.txt");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
var pac = Utils.GetEmbedText(Global.PacFileName);
|
||||
await File.AppendAllTextAsync(path, pac);
|
||||
}
|
||||
|
||||
var pacText =
|
||||
(await File.ReadAllTextAsync(path)).Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;");
|
||||
|
||||
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);
|
||||
_writeContent = Encoding.UTF8.GetBytes(sb.ToString());
|
||||
}
|
||||
|
||||
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 = await _tcpListener.AcceptTcpClientAsync();
|
||||
await Task.Run(() => { WriteContent(client); });
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
private static void WriteContent(TcpClient client)
|
||||
{
|
||||
var stream = client.GetStream();
|
||||
stream.Write(_writeContent, 0, _writeContent.Length);
|
||||
stream.Flush();
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
if (_tcpListener == null) return;
|
||||
try
|
||||
{
|
||||
_isRunning = false;
|
||||
_tcpListener.Stop();
|
||||
_tcpListener = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,52 +9,61 @@ namespace ServiceLib.Handler
|
||||
private static readonly Lazy<ProfileExHandler> _instance = new(() => new());
|
||||
private ConcurrentBag<ProfileExItem> _lstProfileEx = [];
|
||||
private Queue<string> _queIndexIds = new();
|
||||
public ConcurrentBag<ProfileExItem> ProfileExs => _lstProfileEx;
|
||||
public static ProfileExHandler Instance => _instance.Value;
|
||||
private static readonly string _tag = "ProfileExHandler";
|
||||
|
||||
public ProfileExHandler()
|
||||
{
|
||||
Init();
|
||||
//Init();
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
public async Task Init()
|
||||
{
|
||||
await InitData();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
SaveQueueIndexIds();
|
||||
await Task.Delay(1000 * 600);
|
||||
await SaveQueueIndexIds();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void Init()
|
||||
public async Task<ConcurrentBag<ProfileExItem>> GetProfileExs()
|
||||
{
|
||||
SQLiteHelper.Instance.Execute($"delete from ProfileExItem where indexId not in ( select indexId from ProfileItem )");
|
||||
return await Task.FromResult(_lstProfileEx);
|
||||
}
|
||||
|
||||
_lstProfileEx = new(SQLiteHelper.Instance.Table<ProfileExItem>());
|
||||
private async Task InitData()
|
||||
{
|
||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem where indexId not in ( select indexId from ProfileItem )");
|
||||
|
||||
_lstProfileEx = new(await SQLiteHelper.Instance.TableAsync<ProfileExItem>().ToListAsync());
|
||||
}
|
||||
|
||||
private void IndexIdEnqueue(string indexId)
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(indexId) && !_queIndexIds.Contains(indexId))
|
||||
if (Utils.IsNotEmpty(indexId) && !_queIndexIds.Contains(indexId))
|
||||
{
|
||||
_queIndexIds.Enqueue(indexId);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveQueueIndexIds()
|
||||
private async Task SaveQueueIndexIds()
|
||||
{
|
||||
var cnt = _queIndexIds.Count;
|
||||
if (cnt > 0)
|
||||
{
|
||||
var lstExists = SQLiteHelper.Instance.Table<ProfileExItem>();
|
||||
var lstExists = await SQLiteHelper.Instance.TableAsync<ProfileExItem>().ToListAsync();
|
||||
List<ProfileExItem> lstInserts = [];
|
||||
List<ProfileExItem> lstUpdates = [];
|
||||
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
var id = _queIndexIds.Dequeue();
|
||||
var item = lstExists.FirstOrDefault(t => t.indexId == id);
|
||||
var itemNew = _lstProfileEx?.FirstOrDefault(t => t.indexId == id);
|
||||
var item = lstExists.FirstOrDefault(t => t.IndexId == id);
|
||||
var itemNew = _lstProfileEx?.FirstOrDefault(t => t.IndexId == id);
|
||||
if (itemNew is null)
|
||||
{
|
||||
continue;
|
||||
@@ -72,14 +81,14 @@ namespace ServiceLib.Handler
|
||||
try
|
||||
{
|
||||
if (lstInserts.Count() > 0)
|
||||
SQLiteHelper.Instance.InsertAll(lstInserts);
|
||||
await SQLiteHelper.Instance.InsertAllAsync(lstInserts);
|
||||
|
||||
if (lstUpdates.Count() > 0)
|
||||
SQLiteHelper.Instance.UpdateAll(lstUpdates);
|
||||
await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("ProfileExHandler", ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,78 +97,78 @@ namespace ServiceLib.Handler
|
||||
{
|
||||
profileEx = new()
|
||||
{
|
||||
indexId = indexId,
|
||||
delay = 0,
|
||||
speed = 0,
|
||||
sort = 0
|
||||
IndexId = indexId,
|
||||
Delay = 0,
|
||||
Speed = 0,
|
||||
Sort = 0
|
||||
};
|
||||
_lstProfileEx.Add(profileEx);
|
||||
IndexIdEnqueue(indexId);
|
||||
}
|
||||
|
||||
public void ClearAll()
|
||||
public async Task ClearAll()
|
||||
{
|
||||
SQLiteHelper.Instance.Execute($"delete from ProfileExItem ");
|
||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem ");
|
||||
_lstProfileEx = new();
|
||||
}
|
||||
|
||||
public void SaveTo()
|
||||
public async Task SaveTo()
|
||||
{
|
||||
try
|
||||
{
|
||||
SaveQueueIndexIds();
|
||||
await SaveQueueIndexIds();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTestDelay(string indexId, string delayVal)
|
||||
{
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId);
|
||||
if (profileEx == null)
|
||||
{
|
||||
AddProfileEx(indexId, ref profileEx);
|
||||
}
|
||||
|
||||
int.TryParse(delayVal, out int delay);
|
||||
profileEx.delay = delay;
|
||||
profileEx.Delay = delay;
|
||||
IndexIdEnqueue(indexId);
|
||||
}
|
||||
|
||||
public void SetTestSpeed(string indexId, string speedVal)
|
||||
{
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId);
|
||||
if (profileEx == null)
|
||||
{
|
||||
AddProfileEx(indexId, ref profileEx);
|
||||
}
|
||||
|
||||
decimal.TryParse(speedVal, out decimal speed);
|
||||
profileEx.speed = speed;
|
||||
profileEx.Speed = speed;
|
||||
IndexIdEnqueue(indexId);
|
||||
}
|
||||
|
||||
public void SetSort(string indexId, int sort)
|
||||
{
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId);
|
||||
if (profileEx == null)
|
||||
{
|
||||
AddProfileEx(indexId, ref profileEx);
|
||||
}
|
||||
profileEx.sort = sort;
|
||||
profileEx.Sort = sort;
|
||||
IndexIdEnqueue(indexId);
|
||||
}
|
||||
|
||||
public int GetSort(string indexId)
|
||||
{
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.indexId == indexId);
|
||||
var profileEx = _lstProfileEx.FirstOrDefault(t => t.IndexId == indexId);
|
||||
if (profileEx == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return profileEx.sort;
|
||||
return profileEx.Sort;
|
||||
}
|
||||
|
||||
public int GetMaxSort()
|
||||
@@ -168,7 +177,7 @@ namespace ServiceLib.Handler
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return _lstProfileEx.Max(t => t == null ? 0 : t.sort);
|
||||
return _lstProfileEx.Max(t => t == null ? 0 : t.Sort);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public class SpeedtestHandler
|
||||
{
|
||||
private Config? _config;
|
||||
private CoreHandler _coreHandler;
|
||||
private List<ServerTestItem> _selecteds;
|
||||
private ESpeedActionType _actionType;
|
||||
private Action<SpeedTestResult> _updateFunc;
|
||||
private bool _exitLoop = false;
|
||||
|
||||
public SpeedtestHandler(Config config, CoreHandler coreHandler, List<ProfileItem> selecteds, ESpeedActionType actionType, Action<SpeedTestResult> update)
|
||||
{
|
||||
_config = config;
|
||||
_coreHandler = coreHandler;
|
||||
_actionType = actionType;
|
||||
_updateFunc = update;
|
||||
|
||||
_selecteds = new List<ServerTestItem>();
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_selecteds.Add(new ServerTestItem()
|
||||
{
|
||||
indexId = it.indexId,
|
||||
address = it.address,
|
||||
port = it.port,
|
||||
configType = it.configType
|
||||
});
|
||||
}
|
||||
//clear test result
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
switch (actionType)
|
||||
{
|
||||
case ESpeedActionType.Tcping:
|
||||
case ESpeedActionType.Realping:
|
||||
UpdateFunc(it.indexId, ResUI.Speedtesting, "");
|
||||
ProfileExHandler.Instance.SetTestDelay(it.indexId, "0");
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Speedtest:
|
||||
UpdateFunc(it.indexId, "", ResUI.SpeedtestingWait);
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "0");
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Mixedtest:
|
||||
UpdateFunc(it.indexId, ResUI.Speedtesting, ResUI.SpeedtestingWait);
|
||||
ProfileExHandler.Instance.SetTestDelay(it.indexId, "0");
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "0");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (actionType)
|
||||
{
|
||||
case ESpeedActionType.Tcping:
|
||||
Task.Run(RunTcping);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Realping:
|
||||
Task.Run(RunRealPing);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Speedtest:
|
||||
Task.Run(RunSpeedTestAsync);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Mixedtest:
|
||||
Task.Run(RunMixedtestAsync);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ExitLoop()
|
||||
{
|
||||
_exitLoop = true;
|
||||
UpdateFunc("", ResUI.SpeedtestingStop);
|
||||
}
|
||||
|
||||
private Task RunTcping()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Task> tasks = [];
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
tasks.Add(Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
int time = GetTcpingTime(it.address, it.port);
|
||||
var output = FormatOut(time, Global.DelayUnit);
|
||||
|
||||
ProfileExHandler.Instance.SetTestDelay(it.indexId, output);
|
||||
UpdateFunc(it.indexId, output);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}));
|
||||
}
|
||||
Task.WaitAll([.. tasks]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ProfileExHandler.Instance.SaveTo();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task RunRealPing()
|
||||
{
|
||||
int pid = -1;
|
||||
try
|
||||
{
|
||||
string msg = string.Empty;
|
||||
|
||||
pid = _coreHandler.LoadCoreConfigSpeedtest(_selecteds);
|
||||
if (pid < 0)
|
||||
{
|
||||
UpdateFunc("", ResUI.FailedToRunCore);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
DownloadHandler downloadHandle = new DownloadHandler();
|
||||
|
||||
List<Task> tasks = new();
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
if (!it.allowTest)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
WebProxy webProxy = new(Global.Loopback, it.port);
|
||||
string output = await GetRealPingTime(downloadHandle, webProxy);
|
||||
|
||||
ProfileExHandler.Instance.SetTestDelay(it.indexId, output);
|
||||
UpdateFunc(it.indexId, output);
|
||||
int.TryParse(output, out int delay);
|
||||
it.delay = delay;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}));
|
||||
}
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pid > 0)
|
||||
{
|
||||
_coreHandler.CoreStopPid(pid);
|
||||
}
|
||||
ProfileExHandler.Instance.SaveTo();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RunSpeedTestAsync()
|
||||
{
|
||||
int pid = -1;
|
||||
//if (_actionType == ESpeedActionType.Mixedtest)
|
||||
//{
|
||||
// _selecteds = _selecteds.OrderBy(t => t.delay).ToList();
|
||||
//}
|
||||
|
||||
pid = _coreHandler.LoadCoreConfigSpeedtest(_selecteds);
|
||||
if (pid < 0)
|
||||
{
|
||||
UpdateFunc("", ResUI.FailedToRunCore);
|
||||
return;
|
||||
}
|
||||
|
||||
string url = _config.speedTestItem.speedTestUrl;
|
||||
var timeout = _config.speedTestItem.speedTestTimeout;
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
if (_exitLoop)
|
||||
{
|
||||
UpdateFunc(it.indexId, "", ResUI.SpeedtestingSkip);
|
||||
continue;
|
||||
}
|
||||
if (!it.allowTest)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//if (it.delay < 0)
|
||||
//{
|
||||
// UpdateFunc(it.indexId, "", ResUI.SpeedtestingSkip);
|
||||
// continue;
|
||||
//}
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "-1");
|
||||
UpdateFunc(it.indexId, "", ResUI.Speedtesting);
|
||||
|
||||
var item = LazyConfig.Instance.GetProfileItem(it.indexId);
|
||||
if (item is null) continue;
|
||||
|
||||
WebProxy webProxy = new(Global.Loopback, it.port);
|
||||
|
||||
await downloadHandle.DownloadDataAsync(url, webProxy, timeout, (bool success, string msg) =>
|
||||
{
|
||||
decimal.TryParse(msg, out decimal dec);
|
||||
if (dec > 0)
|
||||
{
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, msg);
|
||||
}
|
||||
UpdateFunc(it.indexId, "", msg);
|
||||
});
|
||||
}
|
||||
|
||||
if (pid > 0)
|
||||
{
|
||||
_coreHandler.CoreStopPid(pid);
|
||||
}
|
||||
UpdateFunc("", ResUI.SpeedtestingCompleted);
|
||||
ProfileExHandler.Instance.SaveTo();
|
||||
}
|
||||
|
||||
private async Task RunSpeedTestMulti()
|
||||
{
|
||||
int pid = -1;
|
||||
pid = _coreHandler.LoadCoreConfigSpeedtest(_selecteds);
|
||||
if (pid < 0)
|
||||
{
|
||||
UpdateFunc("", ResUI.FailedToRunCore);
|
||||
return;
|
||||
}
|
||||
|
||||
string url = _config.speedTestItem.speedTestUrl;
|
||||
var timeout = _config.speedTestItem.speedTestTimeout;
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
|
||||
foreach (var it in _selecteds)
|
||||
{
|
||||
if (_exitLoop)
|
||||
{
|
||||
UpdateFunc(it.indexId, "", ResUI.SpeedtestingSkip);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!it.allowTest)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.configType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.delay < 0)
|
||||
{
|
||||
UpdateFunc(it.indexId, "", ResUI.SpeedtestingSkip);
|
||||
continue;
|
||||
}
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, "-1");
|
||||
UpdateFunc(it.indexId, "", ResUI.Speedtesting);
|
||||
|
||||
var item = LazyConfig.Instance.GetProfileItem(it.indexId);
|
||||
if (item is null) continue;
|
||||
|
||||
WebProxy webProxy = new(Global.Loopback, it.port);
|
||||
_ = downloadHandle.DownloadDataAsync(url, webProxy, timeout, (bool success, string msg) =>
|
||||
{
|
||||
decimal.TryParse(msg, out decimal dec);
|
||||
if (dec > 0)
|
||||
{
|
||||
ProfileExHandler.Instance.SetTestSpeed(it.indexId, msg);
|
||||
}
|
||||
UpdateFunc(it.indexId, "", msg);
|
||||
});
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
|
||||
await Task.Delay((timeout + 2) * 1000);
|
||||
|
||||
if (pid > 0)
|
||||
{
|
||||
_coreHandler.CoreStopPid(pid);
|
||||
}
|
||||
UpdateFunc("", ResUI.SpeedtestingCompleted);
|
||||
ProfileExHandler.Instance.SaveTo();
|
||||
}
|
||||
|
||||
private async Task RunMixedtestAsync()
|
||||
{
|
||||
await RunRealPing();
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
await RunSpeedTestMulti();
|
||||
}
|
||||
|
||||
private async Task<string> GetRealPingTime(DownloadHandler downloadHandle, IWebProxy webProxy)
|
||||
{
|
||||
int responseTime = await downloadHandle.GetRealPingTime(_config.speedTestItem.speedPingTestUrl, webProxy, 10);
|
||||
//string output = Utile.IsNullOrEmpty(status) ? FormatOut(responseTime, "ms") : status;
|
||||
return FormatOut(responseTime, Global.DelayUnit);
|
||||
}
|
||||
|
||||
private int GetTcpingTime(string url, int port)
|
||||
{
|
||||
int responseTime = -1;
|
||||
|
||||
try
|
||||
{
|
||||
if (!IPAddress.TryParse(url, out IPAddress? ipAddress))
|
||||
{
|
||||
IPHostEntry ipHostInfo = System.Net.Dns.GetHostEntry(url);
|
||||
ipAddress = ipHostInfo.AddressList[0];
|
||||
}
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
IPEndPoint endPoint = new(ipAddress, port);
|
||||
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
var result = clientSocket.BeginConnect(endPoint, null, null);
|
||||
if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)))
|
||||
throw new TimeoutException("connect timeout (5s): " + url);
|
||||
clientSocket.EndConnect(result);
|
||||
|
||||
timer.Stop();
|
||||
responseTime = (int)timer.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
return responseTime;
|
||||
}
|
||||
|
||||
private string FormatOut(object time, string unit)
|
||||
{
|
||||
//if (time.ToString().Equals("-1"))
|
||||
//{
|
||||
// return "Timeout";
|
||||
//}
|
||||
return $"{time}";
|
||||
}
|
||||
|
||||
private void UpdateFunc(string indexId, string delay, string speed = "")
|
||||
{
|
||||
_updateFunc(new() { IndexId = indexId, Delay = delay, Speed = speed });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
namespace ServiceLib.Handler.Statistics
|
||||
{
|
||||
public class StatisticsHandler
|
||||
{
|
||||
private static readonly Lazy<StatisticsHandler> instance = new(() => new());
|
||||
public static StatisticsHandler Instance => instance.Value;
|
||||
|
||||
private Config _config;
|
||||
private ServerStatItem? _serverStatItem;
|
||||
private List<ServerStatItem> _lstServerStat;
|
||||
private Action<ServerSpeedItem> _updateFunc;
|
||||
private StatisticsV2ray? _statisticsV2Ray;
|
||||
private StatisticsSingbox? _statisticsSingbox;
|
||||
|
||||
public List<ServerStatItem> ServerStat => _lstServerStat;
|
||||
|
||||
public void Init(Config config, Action<ServerSpeedItem> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
if (!config.guiItem.enableStatistics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InitData();
|
||||
|
||||
_statisticsV2Ray = new StatisticsV2ray(config, UpdateServerStat);
|
||||
_statisticsSingbox = new StatisticsSingbox(config, UpdateServerStat);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
try
|
||||
{
|
||||
_statisticsV2Ray?.Close();
|
||||
_statisticsSingbox?.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAllServerStatistics()
|
||||
{
|
||||
SQLiteHelper.Instance.Execute($"delete from ServerStatItem ");
|
||||
_serverStatItem = null;
|
||||
_lstServerStat = new();
|
||||
}
|
||||
|
||||
public void SaveTo()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_lstServerStat != null)
|
||||
{
|
||||
SQLiteHelper.Instance.UpdateAll(_lstServerStat);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitData()
|
||||
{
|
||||
SQLiteHelper.Instance.Execute($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )");
|
||||
|
||||
long ticks = DateTime.Now.Date.Ticks;
|
||||
SQLiteHelper.Instance.Execute($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
|
||||
|
||||
_lstServerStat = SQLiteHelper.Instance.Table<ServerStatItem>().ToList();
|
||||
}
|
||||
|
||||
private void UpdateServerStat(ServerSpeedItem server)
|
||||
{
|
||||
GetServerStatItem(_config.indexId);
|
||||
|
||||
if (_serverStatItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (server.proxyUp != 0 || server.proxyDown != 0)
|
||||
{
|
||||
_serverStatItem.todayUp += server.proxyUp;
|
||||
_serverStatItem.todayDown += server.proxyDown;
|
||||
_serverStatItem.totalUp += server.proxyUp;
|
||||
_serverStatItem.totalDown += server.proxyDown;
|
||||
}
|
||||
|
||||
server.indexId = _config.indexId;
|
||||
server.todayUp = _serverStatItem.todayUp;
|
||||
server.todayDown = _serverStatItem.todayDown;
|
||||
server.totalUp = _serverStatItem.totalUp;
|
||||
server.totalDown = _serverStatItem.totalDown;
|
||||
_updateFunc(server);
|
||||
}
|
||||
|
||||
private void GetServerStatItem(string indexId)
|
||||
{
|
||||
long ticks = DateTime.Now.Date.Ticks;
|
||||
if (_serverStatItem != null && _serverStatItem.indexId != indexId)
|
||||
{
|
||||
_serverStatItem = null;
|
||||
}
|
||||
|
||||
if (_serverStatItem == null)
|
||||
{
|
||||
_serverStatItem = _lstServerStat.FirstOrDefault(t => t.indexId == indexId);
|
||||
if (_serverStatItem == null)
|
||||
{
|
||||
_serverStatItem = new ServerStatItem
|
||||
{
|
||||
indexId = indexId,
|
||||
totalUp = 0,
|
||||
totalDown = 0,
|
||||
todayUp = 0,
|
||||
todayDown = 0,
|
||||
dateNow = ticks
|
||||
};
|
||||
SQLiteHelper.Instance.Replace(_serverStatItem);
|
||||
_lstServerStat.Add(_serverStatItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (_serverStatItem.dateNow != ticks)
|
||||
{
|
||||
_serverStatItem.todayUp = 0;
|
||||
_serverStatItem.todayDown = 0;
|
||||
_serverStatItem.dateNow = ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
using Grpc.Core;
|
||||
using Grpc.Net.Client;
|
||||
using ProtosLib.Statistics;
|
||||
|
||||
namespace ServiceLib.Handler.Statistics
|
||||
{
|
||||
public class StatisticsV2ray
|
||||
{
|
||||
private Models.Config _config;
|
||||
private GrpcChannel? _channel;
|
||||
private StatsService.StatsServiceClient? _client;
|
||||
private bool _exitFlag;
|
||||
private Action<ServerSpeedItem> _updateFunc;
|
||||
|
||||
public StatisticsV2ray(Models.Config config, Action<ServerSpeedItem> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
_exitFlag = false;
|
||||
|
||||
GrpcInit();
|
||||
|
||||
Task.Run(Run);
|
||||
}
|
||||
|
||||
private void GrpcInit()
|
||||
{
|
||||
if (_channel is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel = GrpcChannel.ForAddress($"{Global.HttpProtocol}{Global.Loopback}:{LazyConfig.Instance.StatePort}");
|
||||
_client = new StatsService.StatsServiceClient(_channel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_exitFlag = true;
|
||||
}
|
||||
|
||||
private async void Run()
|
||||
{
|
||||
while (!_exitFlag)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
if (!(_config.IsRunningCore(ECoreType.Xray)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_channel?.State == ConnectivityState.Ready)
|
||||
{
|
||||
QueryStatsResponse? res = null;
|
||||
try
|
||||
{
|
||||
if (_client != null)
|
||||
res = await _client.QueryStatsAsync(new QueryStatsRequest() { Pattern = "", Reset = true });
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (res != null)
|
||||
{
|
||||
ParseOutput(res.Stat, out ServerSpeedItem server);
|
||||
_updateFunc(server);
|
||||
}
|
||||
}
|
||||
if (_channel != null)
|
||||
await _channel.ConnectAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseOutput(Google.Protobuf.Collections.RepeatedField<Stat> source, out ServerSpeedItem server)
|
||||
{
|
||||
server = new();
|
||||
long aggregateProxyUp = 0;
|
||||
long aggregateProxyDown = 0;
|
||||
try
|
||||
{
|
||||
foreach (Stat stat in source)
|
||||
{
|
||||
string name = stat.Name;
|
||||
long value = stat.Value / 1024; //KByte
|
||||
string[] nStr = name.Split(">>>".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
string type = "";
|
||||
|
||||
name = name.Trim();
|
||||
|
||||
name = nStr[1];
|
||||
type = nStr[3];
|
||||
|
||||
if (name.StartsWith(Global.ProxyTag))
|
||||
{
|
||||
if (type == "uplink")
|
||||
{
|
||||
aggregateProxyUp += value;
|
||||
}
|
||||
else if (type == "downlink")
|
||||
{
|
||||
aggregateProxyDown += value;
|
||||
}
|
||||
}
|
||||
else if (name == Global.DirectTag)
|
||||
{
|
||||
if (type == "uplink")
|
||||
{
|
||||
server.directUp = value;
|
||||
}
|
||||
else if (type == "downlink")
|
||||
{
|
||||
server.directDown = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
server.proxyUp = aggregateProxyUp;
|
||||
server.proxyDown = aggregateProxyDown;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
164
v2rayN/ServiceLib/Handler/StatisticsHandler.cs
Normal file
164
v2rayN/ServiceLib/Handler/StatisticsHandler.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public class StatisticsHandler
|
||||
{
|
||||
private static readonly Lazy<StatisticsHandler> instance = new(() => new());
|
||||
public static StatisticsHandler Instance => instance.Value;
|
||||
|
||||
private Config _config;
|
||||
private ServerStatItem? _serverStatItem;
|
||||
private List<ServerStatItem> _lstServerStat;
|
||||
private Action<ServerSpeedItem>? _updateFunc;
|
||||
|
||||
private StatisticsXrayService? _statisticsXray;
|
||||
private StatisticsSingboxService? _statisticsSingbox;
|
||||
private static readonly string _tag = "StatisticsHandler";
|
||||
public List<ServerStatItem> ServerStat => _lstServerStat;
|
||||
|
||||
public async Task Init(Config config, Action<ServerSpeedItem> updateFunc)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = updateFunc;
|
||||
if (config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
||||
{
|
||||
await InitData();
|
||||
|
||||
_statisticsXray = new StatisticsXrayService(config, UpdateServerStatHandler);
|
||||
_statisticsSingbox = new StatisticsSingboxService(config, UpdateServerStatHandler);
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
try
|
||||
{
|
||||
_statisticsXray?.Close();
|
||||
_statisticsSingbox?.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearAllServerStatistics()
|
||||
{
|
||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem ");
|
||||
_serverStatItem = null;
|
||||
_lstServerStat = new();
|
||||
}
|
||||
|
||||
public async Task SaveTo()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_lstServerStat != null)
|
||||
{
|
||||
await SQLiteHelper.Instance.UpdateAllAsync(_lstServerStat);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CloneServerStatItem(string indexId, string toIndexId)
|
||||
{
|
||||
if (_lstServerStat == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (indexId == toIndexId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var stat = _lstServerStat.FirstOrDefault(t => t.IndexId == indexId);
|
||||
if (stat == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var toStat = JsonUtils.DeepCopy(stat);
|
||||
toStat.IndexId = toIndexId;
|
||||
await SQLiteHelper.Instance.ReplaceAsync(toStat);
|
||||
_lstServerStat.Add(toStat);
|
||||
}
|
||||
|
||||
private async Task InitData()
|
||||
{
|
||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )");
|
||||
|
||||
long ticks = DateTime.Now.Date.Ticks;
|
||||
await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
|
||||
|
||||
_lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync();
|
||||
}
|
||||
|
||||
private void UpdateServerStatHandler(ServerSpeedItem server)
|
||||
{
|
||||
_ = UpdateServerStat(server);
|
||||
}
|
||||
|
||||
private async Task UpdateServerStat(ServerSpeedItem server)
|
||||
{
|
||||
await GetServerStatItem(_config.IndexId);
|
||||
|
||||
if (_serverStatItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (server.ProxyUp != 0 || server.ProxyDown != 0)
|
||||
{
|
||||
_serverStatItem.TodayUp += server.ProxyUp;
|
||||
_serverStatItem.TodayDown += server.ProxyDown;
|
||||
_serverStatItem.TotalUp += server.ProxyUp;
|
||||
_serverStatItem.TotalDown += server.ProxyDown;
|
||||
}
|
||||
|
||||
server.IndexId = _config.IndexId;
|
||||
server.TodayUp = _serverStatItem.TodayUp;
|
||||
server.TodayDown = _serverStatItem.TodayDown;
|
||||
server.TotalUp = _serverStatItem.TotalUp;
|
||||
server.TotalDown = _serverStatItem.TotalDown;
|
||||
_updateFunc?.Invoke(server);
|
||||
}
|
||||
|
||||
private async Task GetServerStatItem(string indexId)
|
||||
{
|
||||
long ticks = DateTime.Now.Date.Ticks;
|
||||
if (_serverStatItem != null && _serverStatItem.IndexId != indexId)
|
||||
{
|
||||
_serverStatItem = null;
|
||||
}
|
||||
|
||||
if (_serverStatItem == null)
|
||||
{
|
||||
_serverStatItem = _lstServerStat.FirstOrDefault(t => t.IndexId == indexId);
|
||||
if (_serverStatItem == null)
|
||||
{
|
||||
_serverStatItem = new ServerStatItem
|
||||
{
|
||||
IndexId = indexId,
|
||||
TotalUp = 0,
|
||||
TotalDown = 0,
|
||||
TodayUp = 0,
|
||||
TodayDown = 0,
|
||||
DateNow = ticks
|
||||
};
|
||||
await SQLiteHelper.Instance.ReplaceAsync(_serverStatItem);
|
||||
_lstServerStat.Add(_serverStatItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (_serverStatItem.DateNow != ticks)
|
||||
{
|
||||
_serverStatItem.TodayUp = 0;
|
||||
_serverStatItem.TodayDown = 0;
|
||||
_serverStatItem.DateNow = ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
202
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs
Normal file
202
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingLinux.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
namespace ServiceLib.Handler.SysProxy
|
||||
{
|
||||
public class ProxySettingLinux
|
||||
{
|
||||
public static async Task SetProxy(string host, int port, string exceptions)
|
||||
{
|
||||
var lstCmd = GetSetCmds(host, port, exceptions);
|
||||
|
||||
await ExecCmd(lstCmd);
|
||||
}
|
||||
|
||||
public static async Task UnsetProxy()
|
||||
{
|
||||
var lstCmd = GetUnsetCmds();
|
||||
|
||||
await ExecCmd(lstCmd);
|
||||
}
|
||||
|
||||
private static async Task ExecCmd(List<CmdItem> lstCmd)
|
||||
{
|
||||
foreach (var cmd in lstCmd)
|
||||
{
|
||||
if (cmd is null || cmd.Cmd.IsNullOrEmpty() || cmd.Arguments is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
await Task.Delay(10);
|
||||
await Utils.GetCliWrapOutput(cmd.Cmd, cmd.Arguments);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<CmdItem> GetSetCmds(string host, int port, string exceptions)
|
||||
{
|
||||
var isKde = IsKde(out var configDir);
|
||||
List<string> lstType = ["", "http", "https", "socks", "ftp"];
|
||||
List<CmdItem> lstCmd = [];
|
||||
|
||||
//GNOME
|
||||
foreach (var type in lstType)
|
||||
{
|
||||
lstCmd.AddRange(GetSetCmd4Gnome(type, host, port));
|
||||
}
|
||||
if (exceptions.IsNotEmpty())
|
||||
{
|
||||
lstCmd.AddRange(GetSetCmd4Gnome("exceptions", exceptions, 0));
|
||||
}
|
||||
|
||||
if (isKde)
|
||||
{
|
||||
foreach (var type in lstType)
|
||||
{
|
||||
lstCmd.AddRange(GetSetCmd4Kde(type, host, port, configDir));
|
||||
}
|
||||
if (exceptions.IsNotEmpty())
|
||||
{
|
||||
lstCmd.AddRange(GetSetCmd4Kde("exceptions", exceptions, 0, configDir));
|
||||
}
|
||||
|
||||
// Notify system to reload
|
||||
lstCmd.Add(new CmdItem()
|
||||
{
|
||||
Cmd = "dbus-send",
|
||||
Arguments = ["--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''"]
|
||||
});
|
||||
}
|
||||
return lstCmd;
|
||||
}
|
||||
|
||||
private static List<CmdItem> GetUnsetCmds()
|
||||
{
|
||||
var isKde = IsKde(out var configDir);
|
||||
List<CmdItem> lstCmd = [];
|
||||
|
||||
//GNOME
|
||||
lstCmd.Add(new CmdItem()
|
||||
{
|
||||
Cmd = "gsettings",
|
||||
Arguments = ["set", "org.gnome.system.proxy", "mode", "none"]
|
||||
});
|
||||
|
||||
if (isKde)
|
||||
{
|
||||
lstCmd.Add(new CmdItem()
|
||||
{
|
||||
Cmd = GetKdeVersion(),
|
||||
Arguments = ["--file", $"{configDir}/kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0"]
|
||||
});
|
||||
|
||||
// Notify system to reload
|
||||
lstCmd.Add(new CmdItem()
|
||||
{
|
||||
Cmd = "dbus-send",
|
||||
Arguments = ["--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''"]
|
||||
});
|
||||
}
|
||||
return lstCmd;
|
||||
}
|
||||
|
||||
private static List<CmdItem> GetSetCmd4Kde(string type, string host, int port, string configDir)
|
||||
{
|
||||
List<CmdItem> lstCmd = [];
|
||||
var cmd = GetKdeVersion();
|
||||
|
||||
if (type.IsNullOrEmpty())
|
||||
{
|
||||
lstCmd.Add(new()
|
||||
{
|
||||
Cmd = cmd,
|
||||
Arguments = ["--file", $"{configDir}/kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1"]
|
||||
});
|
||||
}
|
||||
else if (type == "exceptions")
|
||||
{
|
||||
lstCmd.Add(new()
|
||||
{
|
||||
Cmd = cmd,
|
||||
Arguments = ["--file", $"{configDir}/kioslaverc", "--group", "Proxy Settings", "--key", "NoProxyFor", host]
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var type2 = type.Equals("https") ? "http" : type;
|
||||
lstCmd.Add(new CmdItem()
|
||||
{
|
||||
Cmd = cmd,
|
||||
Arguments = ["--file", $"{configDir}/kioslaverc", "--group", "Proxy Settings", "--key", $"{type}Proxy", $"{type2}://{host}:{port}"]
|
||||
});
|
||||
}
|
||||
|
||||
return lstCmd;
|
||||
}
|
||||
|
||||
private static List<CmdItem> GetSetCmd4Gnome(string type, string host, int port)
|
||||
{
|
||||
List<CmdItem> lstCmd = [];
|
||||
|
||||
if (type.IsNullOrEmpty())
|
||||
{
|
||||
lstCmd.Add(new()
|
||||
{
|
||||
Cmd = "gsettings",
|
||||
Arguments = ["set", "org.gnome.system.proxy", "mode", "manual"]
|
||||
});
|
||||
}
|
||||
else if (type == "exceptions")
|
||||
{
|
||||
lstCmd.Add(new()
|
||||
{
|
||||
Cmd = "gsettings",
|
||||
Arguments = ["set", $"org.gnome.system.proxy", "ignore-hosts", JsonUtils.Serialize(host.Split(','), false)]
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
lstCmd.Add(new()
|
||||
{
|
||||
Cmd = "gsettings",
|
||||
Arguments = ["set", $"org.gnome.system.proxy.{type}", "host", host]
|
||||
});
|
||||
|
||||
lstCmd.Add(new()
|
||||
{
|
||||
Cmd = "gsettings",
|
||||
Arguments = ["set", $"org.gnome.system.proxy.{type}", "port", $"{port}"]
|
||||
});
|
||||
}
|
||||
|
||||
return lstCmd;
|
||||
}
|
||||
|
||||
private static bool IsKde(out string configDir)
|
||||
{
|
||||
configDir = "/home";
|
||||
var desktop = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP");
|
||||
var desktop2 = Environment.GetEnvironmentVariable("XDG_SESSION_DESKTOP");
|
||||
var isKde = string.Equals(desktop, "KDE", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(desktop, "plasma", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(desktop2, "KDE", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(desktop2, "plasma", StringComparison.OrdinalIgnoreCase);
|
||||
if (isKde)
|
||||
{
|
||||
var homeDir = Environment.GetEnvironmentVariable("HOME");
|
||||
if (homeDir != null)
|
||||
{
|
||||
configDir = Path.Combine(homeDir, ".config");
|
||||
}
|
||||
}
|
||||
|
||||
return isKde;
|
||||
}
|
||||
|
||||
private static string GetKdeVersion()
|
||||
{
|
||||
var ver = Environment.GetEnvironmentVariable("KDE_SESSION_VERSION") ?? "0";
|
||||
return ver switch
|
||||
{
|
||||
"6" => "kwriteconfig6",
|
||||
_ => "kwriteconfig5"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
101
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs
Normal file
101
v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
namespace ServiceLib.Handler.SysProxy
|
||||
{
|
||||
public class ProxySettingOSX
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用接口类型
|
||||
/// </summary>
|
||||
private static readonly List<string> LstInterface = ["Ethernet", "Wi-Fi", "Thunderbolt Bridge", "USB 10/100/1000 LAN"];
|
||||
|
||||
/// <summary>
|
||||
/// 代理类型,对应 http,https,socks
|
||||
/// </summary>
|
||||
private static readonly List<string> LstTypes = ["setwebproxy", "setsecurewebproxy", "setsocksfirewallproxy"];
|
||||
|
||||
public static async Task SetProxy(string host, int port, string exceptions)
|
||||
{
|
||||
var lstInterface = await GetListNetworkServices();
|
||||
var lstCmd = GetSetCmds(lstInterface, host, port, exceptions);
|
||||
await ExecCmd(lstCmd);
|
||||
}
|
||||
|
||||
public static async Task UnsetProxy()
|
||||
{
|
||||
var lstInterface = await GetListNetworkServices();
|
||||
var lstCmd = GetUnsetCmds(lstInterface);
|
||||
await ExecCmd(lstCmd);
|
||||
}
|
||||
|
||||
private static async Task ExecCmd(List<CmdItem> lstCmd)
|
||||
{
|
||||
foreach (var cmd in lstCmd)
|
||||
{
|
||||
if (cmd is null || cmd.Cmd.IsNullOrEmpty() || cmd.Arguments is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
await Task.Delay(10);
|
||||
await Utils.GetCliWrapOutput(cmd.Cmd, cmd.Arguments);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<CmdItem> GetSetCmds(List<string> lstInterface, string host, int port, string exceptions)
|
||||
{
|
||||
List<CmdItem> lstCmd = [];
|
||||
foreach (var interf in lstInterface)
|
||||
{
|
||||
foreach (var type in LstTypes)
|
||||
{
|
||||
lstCmd.Add(new CmdItem()
|
||||
{
|
||||
Cmd = "networksetup",
|
||||
Arguments = [$"-{type}", interf, host, port.ToString()]
|
||||
});
|
||||
}
|
||||
if (exceptions.IsNotEmpty())
|
||||
{
|
||||
List<string> args = [$"-setproxybypassdomains", interf];
|
||||
args.AddRange(exceptions.Split(','));
|
||||
lstCmd.Add(new CmdItem()
|
||||
{
|
||||
Cmd = "networksetup",
|
||||
Arguments = args
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return lstCmd;
|
||||
}
|
||||
|
||||
private static List<CmdItem> GetUnsetCmds(List<string> lstInterface)
|
||||
{
|
||||
List<CmdItem> lstCmd = [];
|
||||
foreach (var interf in lstInterface)
|
||||
{
|
||||
foreach (var type in LstTypes)
|
||||
{
|
||||
lstCmd.Add(new CmdItem()
|
||||
{
|
||||
Cmd = "networksetup",
|
||||
Arguments = [$"-{type}state", interf, "off"]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return lstCmd;
|
||||
}
|
||||
|
||||
public static async Task<List<string>> GetListNetworkServices()
|
||||
{
|
||||
var services = await Utils.GetListNetworkServices();
|
||||
if (services.IsNullOrEmpty())
|
||||
{
|
||||
return LstInterface;
|
||||
}
|
||||
|
||||
var lst = services.Split(Environment.NewLine).Where(t => t.Length > 0 && t.Contains('*') == false);
|
||||
return lst.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using static v2rayN.Common.ProxySetting.InternetConnectionOption;
|
||||
using System.Runtime.InteropServices;
|
||||
using static ServiceLib.Handler.SysProxy.ProxySettingWindows.InternetConnectionOption;
|
||||
|
||||
namespace v2rayN.Common
|
||||
namespace ServiceLib.Handler.SysProxy
|
||||
{
|
||||
internal class ProxySetting
|
||||
public class ProxySettingWindows
|
||||
{
|
||||
private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";
|
||||
|
||||
@@ -72,7 +72,6 @@ namespace v2rayN.Common
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetProxyFallback(strProxy, exceptions, type);
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -88,7 +87,7 @@ namespace v2rayN.Common
|
||||
}
|
||||
else if (type is 2 or 4) // named proxy or autoproxy script URL
|
||||
{
|
||||
optionCount = Utils.IsNullOrEmpty(exceptions) ? 2 : 3;
|
||||
optionCount = string.IsNullOrEmpty(exceptions) ? 2 : 3;
|
||||
}
|
||||
|
||||
int m_Int = (int)PerConnFlags.PROXY_TYPE_DIRECT;
|
||||
@@ -144,12 +143,12 @@ namespace v2rayN.Common
|
||||
{
|
||||
if (Environment.Is64BitOperatingSystem)
|
||||
{
|
||||
nint opt = new(optionsPtr.ToInt64() + i * optSize);
|
||||
nint opt = new(optionsPtr.ToInt64() + (i * optSize));
|
||||
Marshal.StructureToPtr(options[i], opt, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
nint opt = new(optionsPtr.ToInt32() + i * optSize);
|
||||
nint opt = new(optionsPtr.ToInt32() + (i * optSize));
|
||||
Marshal.StructureToPtr(options[i], opt, false);
|
||||
}
|
||||
}
|
||||
@@ -247,7 +246,7 @@ namespace v2rayN.Common
|
||||
|
||||
//[MarshalAs(UnmanagedType.)]
|
||||
public nint options;
|
||||
};
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct InternetConnectionOption
|
||||
99
v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs
Normal file
99
v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
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 = AppHandler.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())
|
||||
{
|
||||
PacHandler.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 (Utils.IsNullOrEmpty(config.SystemProxyItem.SystemProxyAdvancedProtocol))
|
||||
{
|
||||
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 = AppHandler.Instance.GetLocalPort(EInboundProtocol.pac);
|
||||
await PacHandler.Start(Utils.GetConfigPath(), port, portPac);
|
||||
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
|
||||
ProxySettingWindows.SetProxy(strProxy, "", 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,40 +5,36 @@
|
||||
private static readonly Lazy<TaskHandler> _instance = new(() => new());
|
||||
public static TaskHandler Instance => _instance.Value;
|
||||
|
||||
public TaskHandler()
|
||||
public void RegUpdateTask(Config config, Action<bool, string> updateFunc)
|
||||
{
|
||||
Task.Run(() => UpdateTaskRunSubscription(config, updateFunc));
|
||||
Task.Run(() => UpdateTaskRunGeo(config, updateFunc));
|
||||
}
|
||||
|
||||
public void RegUpdateTask(Config config, Action<bool, string> update)
|
||||
{
|
||||
Task.Run(() => UpdateTaskRunSubscription(config, update));
|
||||
Task.Run(() => UpdateTaskRunGeo(config, update));
|
||||
}
|
||||
|
||||
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> update)
|
||||
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> updateFunc)
|
||||
{
|
||||
await Task.Delay(60000);
|
||||
Logging.SaveLog("UpdateTaskRunSubscription");
|
||||
|
||||
var updateHandle = new UpdateHandler();
|
||||
var updateHandle = new UpdateService();
|
||||
while (true)
|
||||
{
|
||||
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
|
||||
var lstSubs = LazyConfig.Instance.SubItems()
|
||||
.Where(t => t.autoUpdateInterval > 0)
|
||||
.Where(t => updateTime - t.updateTime >= t.autoUpdateInterval * 60)
|
||||
var lstSubs = (await AppHandler.Instance.SubItems())
|
||||
.Where(t => t.AutoUpdateInterval > 0)
|
||||
.Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in lstSubs)
|
||||
{
|
||||
updateHandle.UpdateSubscriptionProcess(config, item.id, true, (bool success, string msg) =>
|
||||
{
|
||||
update(success, msg);
|
||||
if (success)
|
||||
Logging.SaveLog("subscription" + msg);
|
||||
});
|
||||
item.updateTime = updateTime;
|
||||
ConfigHandler.AddSubItem(config, item);
|
||||
await updateHandle.UpdateSubscriptionProcess(config, item.Id, true, (bool success, string msg) =>
|
||||
{
|
||||
updateFunc?.Invoke(success, msg);
|
||||
if (success)
|
||||
Logging.SaveLog("subscription" + msg);
|
||||
});
|
||||
item.UpdateTime = updateTime;
|
||||
await ConfigHandler.AddSubItem(config, item);
|
||||
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
@@ -46,30 +42,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateTaskRunGeo(Config config, Action<bool, string> update)
|
||||
private async Task UpdateTaskRunGeo(Config config, Action<bool, string> updateFunc)
|
||||
{
|
||||
var autoUpdateGeoTime = DateTime.Now;
|
||||
|
||||
await Task.Delay(1000 * 120);
|
||||
//await Task.Delay(1000 * 120);
|
||||
Logging.SaveLog("UpdateTaskRunGeo");
|
||||
|
||||
var updateHandle = new UpdateHandler();
|
||||
var updateHandle = new UpdateService();
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(1000 * 3600);
|
||||
|
||||
var dtNow = DateTime.Now;
|
||||
if (config.guiItem.autoUpdateInterval > 0)
|
||||
if (config.GuiItem.AutoUpdateInterval > 0)
|
||||
{
|
||||
if ((dtNow - autoUpdateGeoTime).Hours % config.guiItem.autoUpdateInterval == 0)
|
||||
if ((dtNow - autoUpdateGeoTime).Hours % config.GuiItem.AutoUpdateInterval == 0)
|
||||
{
|
||||
updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
|
||||
await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
|
||||
{
|
||||
update(false, msg);
|
||||
updateFunc?.Invoke(false, msg);
|
||||
});
|
||||
autoUpdateGeoTime = dtNow;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(1000 * 3600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,539 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public class UpdateHandler
|
||||
{
|
||||
private Action<bool, string> _updateFunc;
|
||||
private Config _config;
|
||||
|
||||
public event EventHandler<ResultEventArgs> AbsoluteCompleted;
|
||||
|
||||
public class ResultEventArgs : EventArgs
|
||||
{
|
||||
public bool Success;
|
||||
public string Msg;
|
||||
public string Url;
|
||||
|
||||
public ResultEventArgs(bool success, string msg, string url = "")
|
||||
{
|
||||
Success = success;
|
||||
Msg = msg;
|
||||
Url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckUpdateGuiN(Config config, Action<bool, string> update, bool preRelease)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
var url = string.Empty;
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
downloadHandle.UpdateCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
||||
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(url));
|
||||
fileName = Utils.UrlEncode(fileName);
|
||||
_updateFunc(true, fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
}
|
||||
};
|
||||
downloadHandle.Error += (sender2, args) =>
|
||||
{
|
||||
_updateFunc(false, args.GetException().Message);
|
||||
_updateFunc(false, "");
|
||||
};
|
||||
AbsoluteCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
|
||||
_updateFunc(false, args.Msg);
|
||||
|
||||
url = args.Url;
|
||||
AskToDownload(downloadHandle, url, true).ContinueWith(task =>
|
||||
{
|
||||
_updateFunc(false, "");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
_updateFunc(false, "");
|
||||
}
|
||||
};
|
||||
_updateFunc(false, string.Format(ResUI.MsgStartUpdating, ECoreType.v2rayN));
|
||||
CheckUpdateAsync(downloadHandle, ECoreType.v2rayN, preRelease);
|
||||
}
|
||||
|
||||
public async void CheckUpdateCore(ECoreType type, Config config, Action<bool, string> update, bool preRelease)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
var url = string.Empty;
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
downloadHandle.UpdateCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, ResUI.MsgDownloadV2rayCoreSuccessfully);
|
||||
_updateFunc(false, ResUI.MsgUnpacking);
|
||||
|
||||
try
|
||||
{
|
||||
_updateFunc(true, url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_updateFunc(false, ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
}
|
||||
};
|
||||
downloadHandle.Error += (sender2, args) =>
|
||||
{
|
||||
_updateFunc(false, args.GetException().Message);
|
||||
_updateFunc(false, "");
|
||||
};
|
||||
|
||||
AbsoluteCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
|
||||
_updateFunc(false, args.Msg);
|
||||
|
||||
url = args.Url;
|
||||
AskToDownload(downloadHandle, url, true).ContinueWith(task =>
|
||||
{
|
||||
_updateFunc(false, "");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
_updateFunc(false, "");
|
||||
}
|
||||
};
|
||||
_updateFunc(false, string.Format(ResUI.MsgStartUpdating, type));
|
||||
CheckUpdateAsync(downloadHandle, type, preRelease);
|
||||
}
|
||||
|
||||
public void UpdateSubscriptionProcess(Config config, string subId, bool blProxy, Action<bool, string> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
|
||||
_updateFunc(false, ResUI.MsgUpdateSubscriptionStart);
|
||||
var subItem = LazyConfig.Instance.SubItems().OrderBy(t => t.sort).ToList();
|
||||
|
||||
if (subItem == null || subItem.Count <= 0)
|
||||
{
|
||||
_updateFunc(false, ResUI.MsgNoValidSubscription);
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
foreach (var item in subItem)
|
||||
{
|
||||
string id = item.id.TrimEx();
|
||||
string url = item.url.TrimEx();
|
||||
string userAgent = item.userAgent.TrimEx();
|
||||
string hashCode = $"{item.remarks}->";
|
||||
if (Utils.IsNullOrEmpty(id) || Utils.IsNullOrEmpty(url) || (!Utils.IsNullOrEmpty(subId) && item.id != subId))
|
||||
{
|
||||
//_updateFunc(false, $"{hashCode}{ResUI.MsgNoValidSubscription}");
|
||||
continue;
|
||||
}
|
||||
if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.enabled == false)
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var downloadHandle = new DownloadHandler();
|
||||
downloadHandle.Error += (sender2, args) =>
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{args.GetException().Message}");
|
||||
};
|
||||
|
||||
_updateFunc(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
|
||||
|
||||
//one url
|
||||
url = Utils.GetPunycode(url);
|
||||
//convert
|
||||
if (!Utils.IsNullOrEmpty(item.convertTarget))
|
||||
{
|
||||
var subConvertUrl = Utils.IsNullOrEmpty(config.constItem.subConvertUrl) ? Global.SubConvertUrls.FirstOrDefault() : config.constItem.subConvertUrl;
|
||||
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
|
||||
if (!url.Contains("target="))
|
||||
{
|
||||
url += string.Format("&target={0}", item.convertTarget);
|
||||
}
|
||||
if (!url.Contains("config="))
|
||||
{
|
||||
url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault());
|
||||
}
|
||||
}
|
||||
var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent);
|
||||
if (blProxy && Utils.IsNullOrEmpty(result))
|
||||
{
|
||||
result = await downloadHandle.TryDownloadString(url, false, userAgent);
|
||||
}
|
||||
|
||||
//more url
|
||||
if (Utils.IsNullOrEmpty(item.convertTarget) && !Utils.IsNullOrEmpty(item.moreUrl.TrimEx()))
|
||||
{
|
||||
if (!Utils.IsNullOrEmpty(result) && Utils.IsBase64String(result!))
|
||||
{
|
||||
result = Utils.Base64Decode(result);
|
||||
}
|
||||
|
||||
var lstUrl = item.moreUrl.TrimEx().Split(",") ?? [];
|
||||
foreach (var it in lstUrl)
|
||||
{
|
||||
var url2 = Utils.GetPunycode(it);
|
||||
if (Utils.IsNullOrEmpty(url2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result2 = await downloadHandle.TryDownloadString(url2, blProxy, userAgent);
|
||||
if (blProxy && Utils.IsNullOrEmpty(result2))
|
||||
{
|
||||
result2 = await downloadHandle.TryDownloadString(url2, false, userAgent);
|
||||
}
|
||||
if (!Utils.IsNullOrEmpty(result2))
|
||||
{
|
||||
if (Utils.IsBase64String(result2!))
|
||||
{
|
||||
result += Utils.Base64Decode(result2);
|
||||
}
|
||||
else
|
||||
{
|
||||
result += result2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Utils.IsNullOrEmpty(result))
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
|
||||
if (result?.Length < 99)
|
||||
{
|
||||
_updateFunc(false, $"{hashCode}{result}");
|
||||
}
|
||||
|
||||
int ret = ConfigHandler.AddBatchServers(config, result, id, true);
|
||||
if (ret <= 0)
|
||||
{
|
||||
Logging.SaveLog("FailedImportSubscription");
|
||||
Logging.SaveLog(result);
|
||||
}
|
||||
_updateFunc(false,
|
||||
ret > 0
|
||||
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
|
||||
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
|
||||
}
|
||||
_updateFunc(false, "-------------------------------------------------------");
|
||||
}
|
||||
|
||||
_updateFunc(true, $"{ResUI.MsgUpdateSubscriptionEnd}");
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateGeoFileAll(Config config, Action<bool, string> update)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await UpdateGeoFile("geosite", _config, update);
|
||||
await UpdateGeoFile("geoip", _config, update);
|
||||
_updateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
|
||||
});
|
||||
}
|
||||
|
||||
public void RunAvailabilityCheck(Action<bool, string> update)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var time = await (new DownloadHandler()).RunAvailabilityCheck(null);
|
||||
|
||||
update(false, string.Format(ResUI.TestMeOutput, time));
|
||||
});
|
||||
}
|
||||
|
||||
#region private
|
||||
|
||||
private async void CheckUpdateAsync(DownloadHandler downloadHandle, ECoreType type, bool preRelease)
|
||||
{
|
||||
try
|
||||
{
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
||||
string url = coreInfo.coreReleaseApiUrl;
|
||||
|
||||
var result = await downloadHandle.DownloadStringAsync(url, true, Global.AppName);
|
||||
if (!Utils.IsNullOrEmpty(result))
|
||||
{
|
||||
ResponseHandler(type, result, preRelease);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.SaveLog("StatusCode error: " + url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
_updateFunc(false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Core版本
|
||||
/// </summary>
|
||||
private SemanticVersion GetCoreVersion(ECoreType type)
|
||||
{
|
||||
try
|
||||
{
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
||||
string filePath = string.Empty;
|
||||
foreach (string name in coreInfo.coreExes)
|
||||
{
|
||||
string vName = Utils.GetExeName(name);
|
||||
vName = Utils.GetBinPath(vName, coreInfo.coreType.ToString());
|
||||
if (File.Exists(vName))
|
||||
{
|
||||
filePath = vName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
string msg = string.Format(ResUI.NotFoundCore, @"", "", "");
|
||||
//ShowMsg(true, msg);
|
||||
return new SemanticVersion("");
|
||||
}
|
||||
|
||||
using Process p = new();
|
||||
p.StartInfo.FileName = filePath.AppendQuotes();
|
||||
p.StartInfo.Arguments = coreInfo.versionArg;
|
||||
p.StartInfo.WorkingDirectory = Utils.StartupPath();
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.StartInfo.CreateNoWindow = true;
|
||||
p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
|
||||
p.Start();
|
||||
p.WaitForExit(5000);
|
||||
string echo = p.StandardOutput.ReadToEnd();
|
||||
string version = string.Empty;
|
||||
switch (type)
|
||||
{
|
||||
case ECoreType.v2fly:
|
||||
case ECoreType.SagerNet:
|
||||
case ECoreType.Xray:
|
||||
case ECoreType.v2fly_v5:
|
||||
version = Regex.Match(echo, $"{coreInfo.match} ([0-9.]+) \\(").Groups[1].Value;
|
||||
break;
|
||||
|
||||
case ECoreType.clash:
|
||||
case ECoreType.clash_meta:
|
||||
case ECoreType.mihomo:
|
||||
version = Regex.Match(echo, $"v[0-9.]+").Groups[0].Value;
|
||||
break;
|
||||
|
||||
case ECoreType.sing_box:
|
||||
version = Regex.Match(echo, $"([0-9.]+)").Groups[1].Value;
|
||||
break;
|
||||
}
|
||||
return new SemanticVersion(version);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
_updateFunc(false, ex.Message);
|
||||
return new SemanticVersion("");
|
||||
}
|
||||
}
|
||||
|
||||
private void ResponseHandler(ECoreType type, string gitHubReleaseApi, bool preRelease)
|
||||
{
|
||||
try
|
||||
{
|
||||
var gitHubReleases = JsonUtils.Deserialize<List<GitHubRelease>>(gitHubReleaseApi);
|
||||
var gitHubRelease = preRelease ? gitHubReleases?.First() : gitHubReleases?.First(r => r.Prerelease == false);
|
||||
var version = new SemanticVersion(gitHubRelease?.TagName!);
|
||||
var body = gitHubRelease?.Body;
|
||||
|
||||
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(type);
|
||||
SemanticVersion curVersion;
|
||||
string message;
|
||||
string? url;
|
||||
switch (type)
|
||||
{
|
||||
case ECoreType.v2fly:
|
||||
case ECoreType.SagerNet:
|
||||
case ECoreType.Xray:
|
||||
case ECoreType.v2fly_v5:
|
||||
{
|
||||
curVersion = GetCoreVersion(type);
|
||||
message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v"));
|
||||
url = string.Format(GetUrlFromCore(coreInfo), version.ToVersionString("v"));
|
||||
break;
|
||||
}
|
||||
case ECoreType.clash:
|
||||
case ECoreType.clash_meta:
|
||||
case ECoreType.mihomo:
|
||||
{
|
||||
curVersion = GetCoreVersion(type);
|
||||
message = string.Format(ResUI.IsLatestCore, type, curVersion);
|
||||
url = string.Format(GetUrlFromCore(coreInfo), version.ToVersionString("v"));
|
||||
break;
|
||||
}
|
||||
case ECoreType.sing_box:
|
||||
{
|
||||
curVersion = GetCoreVersion(type);
|
||||
message = string.Format(ResUI.IsLatestCore, type, curVersion.ToVersionString("v"));
|
||||
url = string.Format(GetUrlFromCore(coreInfo), version.ToVersionString("v"), version);
|
||||
break;
|
||||
}
|
||||
case ECoreType.v2rayN:
|
||||
{
|
||||
curVersion = new SemanticVersion(Utils.GetVersionInfo());
|
||||
message = string.Format(ResUI.IsLatestN, type, curVersion);
|
||||
url = string.Format(GetUrlFromCore(coreInfo), version);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException("Type");
|
||||
}
|
||||
|
||||
if (curVersion >= version && version != new SemanticVersion(0, 0, 0))
|
||||
{
|
||||
AbsoluteCompleted?.Invoke(this, new ResultEventArgs(false, message));
|
||||
return;
|
||||
}
|
||||
|
||||
AbsoluteCompleted?.Invoke(this, new ResultEventArgs(true, body, url));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(ex.Message, ex);
|
||||
_updateFunc(false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetUrlFromCore(CoreInfo? coreInfo)
|
||||
{
|
||||
if (Utils.IsWindows())
|
||||
{
|
||||
return RuntimeInformation.ProcessArchitecture switch
|
||||
{
|
||||
Architecture.Arm64 => coreInfo?.coreDownloadUrlArm64,
|
||||
Architecture.X86 => coreInfo?.coreDownloadUrl32,
|
||||
Architecture.X64 => coreInfo?.coreDownloadUrl64,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
else if (Utils.IsLinux())
|
||||
{
|
||||
return RuntimeInformation.ProcessArchitecture switch
|
||||
{
|
||||
Architecture.Arm64 => coreInfo?.coreDownloadUrlLinuxArm64,
|
||||
Architecture.X86 => coreInfo?.coreDownloadUrlLinux32,
|
||||
Architecture.X64 => coreInfo?.coreDownloadUrlLinux64,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task AskToDownload(DownloadHandler downloadHandle, string url, bool blAsk)
|
||||
{
|
||||
//bool blDownload = false;
|
||||
//if (blAsk)
|
||||
//{
|
||||
// if (UI.ShowYesNo(string.Format(ResUI.DownloadYesNo, url)) == MessageBoxResult.Yes)
|
||||
// {
|
||||
// blDownload = true;
|
||||
// }
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// blDownload = true;
|
||||
//}
|
||||
//if (blDownload)
|
||||
//{
|
||||
await downloadHandle.DownloadFileAsync(url, true, 60);
|
||||
//}
|
||||
}
|
||||
|
||||
private async Task UpdateGeoFile(string geoName, Config config, Action<bool, string> update)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = update;
|
||||
var url = string.Format(Global.GeoUrl, geoName);
|
||||
|
||||
DownloadHandler downloadHandle = new();
|
||||
downloadHandle.UpdateCompleted += (sender2, args) =>
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
_updateFunc(false, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, geoName));
|
||||
|
||||
try
|
||||
{
|
||||
string fileName = Utils.GetTempPath(Utils.GetDownloadFileName(url));
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
string targetPath = Utils.GetBinPath($"{geoName}.dat");
|
||||
File.Copy(fileName, targetPath, true);
|
||||
|
||||
File.Delete(fileName);
|
||||
//_updateFunc(true, "");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_updateFunc(false, ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateFunc(false, args.Msg);
|
||||
}
|
||||
};
|
||||
downloadHandle.Error += (sender2, args) =>
|
||||
{
|
||||
_updateFunc(false, args.GetException().Message);
|
||||
};
|
||||
await AskToDownload(downloadHandle, url, false);
|
||||
}
|
||||
|
||||
#endregion private
|
||||
}
|
||||
}
|
||||
179
v2rayN/ServiceLib/Handler/WebDavHandler.cs
Normal file
179
v2rayN/ServiceLib/Handler/WebDavHandler.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System.Net;
|
||||
using WebDav;
|
||||
|
||||
namespace ServiceLib.Handler
|
||||
{
|
||||
public sealed class WebDavHandler
|
||||
{
|
||||
private static readonly Lazy<WebDavHandler> _instance = new(() => new());
|
||||
public static WebDavHandler Instance => _instance.Value;
|
||||
|
||||
private Config? _config;
|
||||
private WebDavClient? _client;
|
||||
private string? _lastDescription;
|
||||
private string _webDir = Global.AppName + "_backup";
|
||||
private readonly string _webFileName = "backup.zip";
|
||||
private readonly string _tag = "WebDav--";
|
||||
|
||||
public WebDavHandler()
|
||||
{
|
||||
_config = AppHandler.Instance.Config;
|
||||
}
|
||||
|
||||
private async Task<bool> GetClient()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_config.WebDavItem.Url.IsNullOrEmpty()
|
||||
|| _config.WebDavItem.UserName.IsNullOrEmpty()
|
||||
|| _config.WebDavItem.Password.IsNullOrEmpty())
|
||||
{
|
||||
throw new ArgumentException("webdav parameter error or null");
|
||||
}
|
||||
if (_client != null)
|
||||
{
|
||||
_client?.Dispose();
|
||||
_client = null;
|
||||
}
|
||||
if (_config.WebDavItem.DirName.IsNullOrEmpty())
|
||||
{
|
||||
_webDir = Global.AppName + "_backup";
|
||||
}
|
||||
else
|
||||
{
|
||||
_webDir = _config.WebDavItem.DirName.TrimEx();
|
||||
}
|
||||
|
||||
var clientParams = new WebDavClientParams
|
||||
{
|
||||
BaseAddress = new Uri(_config.WebDavItem.Url),
|
||||
Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password)
|
||||
};
|
||||
_client = new WebDavClient(clientParams);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SaveLog(ex);
|
||||
return false;
|
||||
}
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private async Task<bool> TryCreateDir()
|
||||
{
|
||||
if (_client is null) return false;
|
||||
try
|
||||
{
|
||||
var result2 = await _client.Mkcol(_webDir);
|
||||
if (result2.IsSuccessful)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
SaveLog(result2.Description);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SaveLog(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SaveLog(string desc)
|
||||
{
|
||||
_lastDescription = desc;
|
||||
Logging.SaveLog(_tag + desc);
|
||||
}
|
||||
|
||||
private void SaveLog(Exception ex)
|
||||
{
|
||||
_lastDescription = ex.Message;
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
|
||||
public async Task<bool> CheckConnection()
|
||||
{
|
||||
if (await GetClient() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
await TryCreateDir();
|
||||
|
||||
try
|
||||
{
|
||||
var testName = "readme_test";
|
||||
var myContent = new StringContent(testName);
|
||||
var result = await _client.PutFile($"{_webDir}/{testName}", myContent);
|
||||
if (result.IsSuccessful)
|
||||
{
|
||||
await _client.Delete($"{_webDir}/{testName}");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SaveLog(result.Description);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SaveLog(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> PutFile(string fileName)
|
||||
{
|
||||
if (await GetClient() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
await TryCreateDir();
|
||||
|
||||
try
|
||||
{
|
||||
await using var fs = File.OpenRead(fileName);
|
||||
var result = await _client.PutFile($"{_webDir}/{_webFileName}", fs); // upload a resource
|
||||
if (result.IsSuccessful)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SaveLog(result.Description);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SaveLog(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> GetRawFile(string fileName)
|
||||
{
|
||||
if (await GetClient() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
await TryCreateDir();
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _client.GetRawFile($"{_webDir}/{_webFileName}");
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
SaveLog(response.Description);
|
||||
return false;
|
||||
}
|
||||
|
||||
await using var outputFileStream = new FileStream(fileName, FileMode.Create);
|
||||
await response.Stream.CopyToAsync(outputFileStream);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SaveLog(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetLastError() => _lastDescription ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class CheckUpdateItem
|
||||
{
|
||||
public bool? isSelected { get; set; }
|
||||
public string coreType { get; set; }
|
||||
public string? remarks { get; set; }
|
||||
public string? fileName { get; set; }
|
||||
public bool? isFinished { get; set; }
|
||||
}
|
||||
}
|
||||
11
v2rayN/ServiceLib/Models/CheckUpdateModel.cs
Normal file
11
v2rayN/ServiceLib/Models/CheckUpdateModel.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class CheckUpdateModel
|
||||
{
|
||||
public bool? IsSelected { get; set; }
|
||||
public string? CoreType { get; set; }
|
||||
public string? Remarks { get; set; }
|
||||
public string? FileName { get; set; }
|
||||
public bool? IsFinished { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,16 @@
|
||||
{
|
||||
public class ClashConnectionModel
|
||||
{
|
||||
public string id { get; set; }
|
||||
public string network { get; set; }
|
||||
public string type { get; set; }
|
||||
public string host { get; set; }
|
||||
public ulong upload { get; set; }
|
||||
public ulong download { get; set; }
|
||||
public string uploadTraffic { get; set; }
|
||||
public string downloadTraffic { get; set; }
|
||||
public double time { get; set; }
|
||||
public string elapsed { get; set; }
|
||||
public string chain { get; set; }
|
||||
public string? Id { get; set; }
|
||||
public string? Network { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string? Host { get; set; }
|
||||
public ulong Upload { get; set; }
|
||||
public ulong Download { get; set; }
|
||||
public string? UploadTraffic { get; set; }
|
||||
public string? DownloadTraffic { get; set; }
|
||||
public double Time { get; set; }
|
||||
public string? Elapsed { get; set; }
|
||||
public string? Chain { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -9,29 +9,29 @@
|
||||
|
||||
public class ConnectionItem
|
||||
{
|
||||
public string id { get; set; } = string.Empty;
|
||||
public MetadataItem metadata { get; set; }
|
||||
public string? id { get; set; }
|
||||
public MetadataItem? metadata { get; set; }
|
||||
public ulong upload { get; set; }
|
||||
public ulong download { get; set; }
|
||||
public DateTime start { get; set; }
|
||||
public List<string>? chains { get; set; }
|
||||
public string rule { get; set; }
|
||||
public string rulePayload { get; set; }
|
||||
public string? rule { get; set; }
|
||||
public string? rulePayload { get; set; }
|
||||
}
|
||||
|
||||
public class MetadataItem
|
||||
{
|
||||
public string network { get; set; }
|
||||
public string type { get; set; }
|
||||
public string sourceIP { get; set; }
|
||||
public string destinationIP { get; set; }
|
||||
public string sourcePort { get; set; }
|
||||
public string destinationPort { get; set; }
|
||||
public string host { get; set; }
|
||||
public string nsMode { get; set; }
|
||||
public object uid { get; set; }
|
||||
public string process { get; set; }
|
||||
public string processPath { get; set; }
|
||||
public string remoteDestination { get; set; }
|
||||
public string? network { get; set; }
|
||||
public string? type { get; set; }
|
||||
public string? sourceIP { get; set; }
|
||||
public string? destinationIP { get; set; }
|
||||
public string? sourcePort { get; set; }
|
||||
public string? destinationPort { get; set; }
|
||||
public string? host { get; set; }
|
||||
public string? nsMode { get; set; }
|
||||
public object? uid { get; set; }
|
||||
public string? process { get; set; }
|
||||
public string? processPath { get; set; }
|
||||
public string? remoteDestination { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,14 @@ namespace ServiceLib.Models
|
||||
{
|
||||
public class ClashProviders
|
||||
{
|
||||
public Dictionary<String, ProvidersItem> providers { get; set; }
|
||||
public Dictionary<string, ProvidersItem>? providers { get; set; }
|
||||
|
||||
public class ProvidersItem
|
||||
{
|
||||
public string name { get; set; }
|
||||
public ProxiesItem[] proxies { get; set; }
|
||||
public string type { get; set; }
|
||||
public string vehicleType { get; set; }
|
||||
public string? name { get; set; }
|
||||
public List<ProxiesItem>? proxies { get; set; }
|
||||
public string? type { get; set; }
|
||||
public string? vehicleType { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,22 @@
|
||||
{
|
||||
public class ClashProxies
|
||||
{
|
||||
public Dictionary<String, ProxiesItem> proxies { get; set; }
|
||||
public Dictionary<string, ProxiesItem>? proxies { get; set; }
|
||||
|
||||
public class ProxiesItem
|
||||
{
|
||||
public string[] all { get; set; }
|
||||
public List<HistoryItem> history { get; set; }
|
||||
public string name { get; set; }
|
||||
public string type { get; set; }
|
||||
public List<string>? all { get; set; }
|
||||
public List<HistoryItem>? history { get; set; }
|
||||
public string? name { get; set; }
|
||||
public string? type { get; set; }
|
||||
public bool udp { get; set; }
|
||||
public string now { get; set; }
|
||||
public string? now { get; set; }
|
||||
public int delay { get; set; }
|
||||
}
|
||||
|
||||
public class HistoryItem
|
||||
{
|
||||
public string time { get; set; }
|
||||
public string? time { get; set; }
|
||||
public int delay { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
[Serializable]
|
||||
public class ClashProxyModel
|
||||
{
|
||||
public string name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string type { get; set; }
|
||||
public string? Type { get; set; }
|
||||
|
||||
public string now { get; set; }
|
||||
public string? Now { get; set; }
|
||||
|
||||
public int delay { get; set; }
|
||||
public int Delay { get; set; }
|
||||
|
||||
public string delayName { get; set; }
|
||||
public string? DelayName { get; set; }
|
||||
|
||||
public bool isActive { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
}
|
||||
8
v2rayN/ServiceLib/Models/CmdItem.cs
Normal file
8
v2rayN/ServiceLib/Models/CmdItem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace ServiceLib.Models
|
||||
{
|
||||
public class CmdItem
|
||||
{
|
||||
public string? Cmd { get; set; }
|
||||
public List<string>? Arguments { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
{
|
||||
public class ComboItem
|
||||
{
|
||||
public string ID
|
||||
public string? ID
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Text
|
||||
public string? Text
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
@@ -8,47 +8,49 @@
|
||||
{
|
||||
#region property
|
||||
|
||||
public string indexId { get; set; }
|
||||
public string subIndexId { get; set; }
|
||||
public string systemProxyExceptions { get; set; }
|
||||
public string systemProxyAdvancedProtocol { get; set; }
|
||||
public string IndexId { get; set; }
|
||||
public string SubIndexId { get; set; }
|
||||
|
||||
public ECoreType runningCoreType { get; set; }
|
||||
public ECoreType RunningCoreType { get; set; }
|
||||
|
||||
public bool IsRunningCore(ECoreType type)
|
||||
{
|
||||
if (type == ECoreType.Xray && runningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5 or ECoreType.SagerNet)
|
||||
switch (type)
|
||||
{
|
||||
return true;
|
||||
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
|
||||
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (type == ECoreType.clash && runningCoreType is ECoreType.sing_box or ECoreType.clash or ECoreType.clash_meta or ECoreType.mihomo)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion property
|
||||
|
||||
#region other entities
|
||||
|
||||
public CoreBasicItem coreBasicItem { get; set; }
|
||||
public TunModeItem tunModeItem { get; set; }
|
||||
public KcpItem kcpItem { get; set; }
|
||||
public GrpcItem grpcItem { get; set; }
|
||||
public RoutingBasicItem routingBasicItem { get; set; }
|
||||
public GUIItem guiItem { get; set; }
|
||||
public UIItem uiItem { get; set; }
|
||||
public ConstItem constItem { get; set; }
|
||||
public SpeedTestItem speedTestItem { get; set; }
|
||||
public Mux4RayItem mux4RayItem { get; set; }
|
||||
public Mux4SboxItem mux4SboxItem { get; set; }
|
||||
public HysteriaItem hysteriaItem { get; set; }
|
||||
public ClashUIItem clashUIItem { get; set; }
|
||||
public SystemProxyItem systemProxyItem { get; set; }
|
||||
public List<InItem> inbound { get; set; }
|
||||
public List<KeyEventItem> globalHotkeys { get; set; }
|
||||
public List<CoreTypeItem> coreTypeItem { get; set; }
|
||||
public CoreBasicItem CoreBasicItem { get; set; }
|
||||
public TunModeItem TunModeItem { get; set; }
|
||||
public KcpItem KcpItem { get; set; }
|
||||
public GrpcItem GrpcItem { get; set; }
|
||||
public RoutingBasicItem RoutingBasicItem { get; set; }
|
||||
public GUIItem GuiItem { get; set; }
|
||||
public MsgUIItem MsgUIItem { get; set; }
|
||||
public UIItem UiItem { get; set; }
|
||||
public ConstItem ConstItem { get; set; }
|
||||
public SpeedTestItem SpeedTestItem { get; set; }
|
||||
public Mux4RayItem Mux4RayItem { get; set; }
|
||||
public Mux4SboxItem Mux4SboxItem { get; set; }
|
||||
public HysteriaItem HysteriaItem { get; set; }
|
||||
public ClashUIItem ClashUIItem { get; set; }
|
||||
public SystemProxyItem SystemProxyItem { get; set; }
|
||||
public WebDavItem WebDavItem { get; set; }
|
||||
public CheckUpdateItem CheckUpdateItem { get; set; }
|
||||
public Fragment4RayItem? Fragment4RayItem { get; set; }
|
||||
public List<InItem> Inbound { get; set; }
|
||||
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
||||
public List<CoreTypeItem> CoreTypeItem { get; set; }
|
||||
|
||||
#endregion other entities
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user