mirror of
https://github.com/2dust/v2rayN.git
synced 2026-11-07 13:05:00 +03:00
Compare commits
178 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f98f517368 | |||
| 7931058342 | |||
| b53507f486 | |||
| 68ea10158a | |||
| 2f35e7a99c | |||
| 3c1ecf085b | |||
| 3a5293bf87 | |||
| ac43bb051d | |||
| 7b31bcdd9f | |||
| aea7078e95 | |||
| 9c82df5b49 | |||
| b5800f7dfc | |||
| 0f3a3eac02 | |||
| 54608ab2b9 | |||
| 6167624443 | |||
| 7a58e78381 | |||
| 677e81f9a7 | |||
| d9843dc775 | |||
| bceebc1661 | |||
| fdb733fa72 | |||
| 8d01d8fda5 | |||
| 018d541910 | |||
| 7e2e66bb0e | |||
| 3cb640c16b | |||
| fdde837698 | |||
| c7afef3d70 | |||
| 19d4f1fa83 | |||
| 7678ad9095 | |||
| 585c24526f | |||
| eb0f5bafde | |||
| d589713fd5 | |||
| 4550ddb14e | |||
| ffe401a26d | |||
| 8774e302b2 | |||
| df016dd55c | |||
| 9ea80671d3 | |||
| 449849d8e8 | |||
| 03b62b3d78 | |||
| 9f9b90cb97 | |||
| c42dcd2876 | |||
| 2fefafdd37 | |||
| 2c9a90c878 | |||
| 4e5f1838a2 | |||
| a45a1dc982 | |||
| fe183798b6 | |||
| 947c84cf10 | |||
| 9c74b51d74 | |||
| abd962ab31 | |||
| f3b894015e | |||
| 4562d4cf00 | |||
| bc36cf8a47 | |||
| cbdfe2e15a | |||
| 68583e20bc | |||
| 6d6459b009 | |||
| 807562b69e | |||
| 654d7d83d0 | |||
| 027252e687 | |||
| 5478c90180 | |||
| 28f30d7e97 | |||
| ae7d54c2e5 | |||
| 56d0d65b06 | |||
| 5e8e189c27 | |||
| 3fee86d44a | |||
| dd77eb79c6 | |||
| d26a2559a6 | |||
| e5ba1759aa | |||
| bfdee37cc1 | |||
| cf89cfcd95 | |||
| 39a988c704 | |||
| 2b28254fbc | |||
| 6e27dca6cd | |||
| 7cee98887b | |||
| 3885ff8b31 | |||
| 12abf383e9 | |||
| 5bef02bd6d | |||
| 592f1260b5 | |||
| 18303688d7 | |||
| 5c4b7f6636 | |||
| 37cce2fa35 | |||
| 6f8b65c75b | |||
| 83c63b914a | |||
| 1ca2485d2a | |||
| cc4154bb0d | |||
| d7f77f220c | |||
| 86f45d103d | |||
| 0077751f75 | |||
| fa2b4b3dc9 | |||
| 23cacb8339 | |||
| 9ffa6a4eb6 | |||
| 386209b835 | |||
| 830dc89c32 | |||
| 693afe3560 | |||
| 95361e8b65 | |||
| 3ff7299aca | |||
| 34fc4de0c2 | |||
| 91536d3923 | |||
| 6b87c09a96 | |||
| ecaac2ac61 | |||
| ad74b1584d | |||
| 514d76953a | |||
| 5b82f17995 | |||
| 20ce35bc30 | |||
| c0fca0dddd | |||
| 2ba896e17e | |||
| f61e6d8c63 | |||
| d3e2e55ecf | |||
| 30e663cd4f | |||
| 054efeb32c | |||
| 2ebd2b28a8 | |||
| 84f812c8ee | |||
| b6ee40ab8d | |||
| 7f24f4a15f | |||
| 0d307671d1 | |||
| 8ea5a57988 | |||
| 4fb41aeca1 | |||
| 3f0bcf7b83 | |||
| 7e712fcdeb | |||
| e634e6dae3 | |||
| 24f8d767b1 | |||
| 31a8ddef23 | |||
| 30e9e64fd5 | |||
| f677934257 | |||
| df7ca81837 | |||
| 53bd03dea2 | |||
| 1f8dd1a52d | |||
| d5460d758b | |||
| 6e38357b7d | |||
| 1990850d9a | |||
| e6cb146671 | |||
| 4da59cd767 | |||
| e20c11c1a7 | |||
| a6af95e083 | |||
| 6f06b16c76 | |||
| 70ddf4ecfc | |||
| 187356cb9e | |||
| 32583ea8b3 | |||
| 69797c10f2 | |||
| ddc8c9b1cd | |||
| 753e7b81b6 | |||
| 725b094fb1 | |||
| 6de5a5215d | |||
| 8d86aa2b72 | |||
| 1aee3950f4 | |||
| 091b79f7cf | |||
| ed2c77062e | |||
| 8b1105c7e2 | |||
| 11c203ad19 | |||
| ab6a6b879e | |||
| b218f0b501 | |||
| 7b5686cd8f | |||
| d727ff40bb | |||
| 1b5069a933 | |||
| 18ea6fdc00 | |||
| 67494108ad | |||
| 38b2a7d2ca | |||
| bf3703bca1 | |||
| 554632cc07 | |||
| 12fc3e9566 | |||
| c2ef3a4a8c | |||
| 86eb8297dd | |||
| c63d4e83f9 | |||
| bf1fb0f92e | |||
| 3c4865982b | |||
| 22c233f0cd | |||
| b2d6282755 | |||
| c8d89e3dce | |||
| d3b1810eab | |||
| 51409a3e28 | |||
| 1a0f50a41e | |||
| 83d4a9c18e | |||
| b4c20e7b81 | |||
| 7c76308c93 | |||
| f28fa31c14 | |||
| bbedc4dbb1 | |||
| ecf42cb85d | |||
| e4701d6703 | |||
| 54a47d00a3 | |||
| 964572817b |
@@ -9,6 +9,12 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
- 'V*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OutputArch: "linux-64"
|
OutputArch: "linux-64"
|
||||||
@@ -21,31 +27,30 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
configuration: [Release]
|
configuration: [Release]
|
||||||
|
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v6.0.2
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v5.0.0
|
uses: actions/setup-dotnet@v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd v2rayN
|
cd v2rayN
|
||||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64 --self-contained=true -o $OutputPath64
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64 -p:SelfContained=true -o "$OutputPath64"
|
||||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 --self-contained=true -o $OutputPathArm64
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 -p:SelfContained=true -o "$OutputPathArm64"
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-x64 --self-contained=true -p:PublishTrimmed=true -o $OutputPath64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-x64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPath64"
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4.6.2
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: v2rayN-linux
|
name: v2rayN-linux
|
||||||
path: |
|
path: |
|
||||||
@@ -56,8 +61,8 @@ jobs:
|
|||||||
if: github.event.inputs.release_tag != ''
|
if: github.event.inputs.release_tag != ''
|
||||||
run: |
|
run: |
|
||||||
chmod 755 package-debian.sh
|
chmod 755 package-debian.sh
|
||||||
./package-debian.sh $OutputArch $OutputPath64 ${{ github.event.inputs.release_tag }}
|
./package-debian.sh "$OutputArch" "$OutputPath64" "${{ github.event.inputs.release_tag }}"
|
||||||
./package-debian.sh $OutputArchArm $OutputPathArm64 ${{ github.event.inputs.release_tag }}
|
./package-debian.sh "$OutputArchArm" "$OutputPathArm64" "${{ github.event.inputs.release_tag }}"
|
||||||
|
|
||||||
- name: Upload deb to release
|
- name: Upload deb to release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
@@ -68,28 +73,13 @@ jobs:
|
|||||||
file_glob: true
|
file_glob: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
|
||||||
- name: Package AppImage
|
|
||||||
if: github.event.inputs.release_tag != ''
|
|
||||||
run: |
|
|
||||||
chmod a+x package-appimage.sh
|
|
||||||
./package-appimage.sh
|
|
||||||
|
|
||||||
- name: Upload AppImage to release
|
|
||||||
uses: svenstaro/upload-release-action@v2
|
|
||||||
if: github.event.inputs.release_tag != ''
|
|
||||||
with:
|
|
||||||
file: ${{ github.workspace }}/v2rayN*.AppImage
|
|
||||||
tag: ${{ github.event.inputs.release_tag }}
|
|
||||||
file_glob: true
|
|
||||||
prerelease: true
|
|
||||||
|
|
||||||
# release zip archive
|
# release zip archive
|
||||||
- name: Package release zip archive
|
- name: Package release zip archive
|
||||||
if: github.event.inputs.release_tag != ''
|
if: github.event.inputs.release_tag != ''
|
||||||
run: |
|
run: |
|
||||||
chmod 755 package-release-zip.sh
|
chmod 755 package-release-zip.sh
|
||||||
./package-release-zip.sh $OutputArch $OutputPath64
|
./package-release-zip.sh "$OutputArch" "$OutputPath64"
|
||||||
./package-release-zip.sh $OutputArchArm $OutputPathArm64
|
./package-release-zip.sh "$OutputArchArm" "$OutputPathArm64"
|
||||||
|
|
||||||
- name: Upload zip archive to release
|
- name: Upload zip archive to release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
@@ -100,36 +90,115 @@ jobs:
|
|||||||
file_glob: true
|
file_glob: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
|
||||||
# release RHEL package
|
rpm:
|
||||||
- name: Package RPM (RHEL-family)
|
needs: build
|
||||||
if: github.event.inputs.release_tag != ''
|
if: |
|
||||||
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag != '') ||
|
||||||
|
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
container:
|
||||||
|
image: registry.access.redhat.com/ubi10/ubi
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ github.event.inputs.release_tag != '' && github.event.inputs.release_tag || github.ref_name }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Prepare tools (Red Hat)
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
chmod 755 package-rhel.sh
|
set -euo pipefail
|
||||||
# Build for both x86_64 and aarch64 in one go (explicit version passed; no --buildfrom)
|
|
||||||
./package-rhel.sh "${{ github.event.inputs.release_tag }}" --arch all
|
. /etc/os-release
|
||||||
|
EL_MAJOR="${VERSION_ID%%.*}"
|
||||||
|
echo "EL_MAJOR=${EL_MAJOR}"
|
||||||
|
|
||||||
|
dnf -y makecache || true
|
||||||
|
command -v curl >/dev/null || dnf -y install curl ca-certificates
|
||||||
|
|
||||||
|
ARCH="$(uname -m)"
|
||||||
|
case "$ARCH" in x86_64|aarch64) ;; *) echo "Unsupported arch: $ARCH"; exit 1 ;; esac
|
||||||
|
|
||||||
|
install_epel_from_dir() {
|
||||||
|
local base="$1" rpm
|
||||||
|
echo "Try: $base"
|
||||||
|
|
||||||
|
rpm="$(
|
||||||
|
{
|
||||||
|
curl -fsSL "$base/Packages/" 2>/dev/null
|
||||||
|
curl -fsSL "$base/Packages/e/" 2>/dev/null | sed 's|href="|href="e/|'
|
||||||
|
} |
|
||||||
|
sed -n 's/.*href="\([^"]*epel-release-[^"]*\.noarch\.rpm\)".*/\1/p' |
|
||||||
|
sort -V | tail -n1
|
||||||
|
)" || true
|
||||||
|
|
||||||
|
if [[ -n "$rpm" ]]; then
|
||||||
|
dnf -y install "$base/Packages/$rpm"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
FEDORA="https://dl.fedoraproject.org/pub/epel/epel-release-latest-${EL_MAJOR}.noarch.rpm"
|
||||||
|
echo "Try Fedora: $FEDORA"
|
||||||
|
|
||||||
|
if curl -fsSLI "$FEDORA" >/dev/null; then
|
||||||
|
dnf -y install "$FEDORA"
|
||||||
|
else
|
||||||
|
ROCKY="https://dl.rockylinux.org/pub/rocky/${EL_MAJOR}/extras/${ARCH}/os"
|
||||||
|
if install_epel_from_dir "$ROCKY"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
ALMA="https://repo.almalinux.org/almalinux/${EL_MAJOR}/extras/${ARCH}/os"
|
||||||
|
if install_epel_from_dir "$ALMA"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
echo "EPEL bootstrap failed (Fedora/Rocky/Alma)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
dnf -y install sudo git rpm-build rpmdevtools dnf-plugins-core \
|
||||||
|
rsync findutils tar gzip unzip which
|
||||||
|
|
||||||
|
dnf repolist | grep -i epel || true
|
||||||
|
|
||||||
|
- name: Checkout repo (for scripts)
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
fetch-depth: '0'
|
||||||
|
|
||||||
|
- name: Restore build artifacts
|
||||||
|
uses: actions/download-artifact@v7
|
||||||
|
with:
|
||||||
|
name: v2rayN-linux
|
||||||
|
path: ${{ github.workspace }}/v2rayN/Release
|
||||||
|
|
||||||
|
- name: Ensure script permissions
|
||||||
|
run: chmod 755 package-rhel.sh
|
||||||
|
|
||||||
|
- name: Package RPM (RHEL-family)
|
||||||
|
run: ./package-rhel.sh "${RELEASE_TAG}" --arch all
|
||||||
|
|
||||||
- name: Collect RPMs into workspace
|
- name: Collect RPMs into workspace
|
||||||
if: github.event.inputs.release_tag != ''
|
|
||||||
run: |
|
run: |
|
||||||
mkdir -p "${{ github.workspace }}/dist/rpm"
|
mkdir -p "$GITHUB_WORKSPACE/dist/rpm"
|
||||||
rsync -av "$HOME/rpmbuild/RPMS/" "${{ github.workspace }}/dist/rpm/"
|
rsync -av "$HOME/rpmbuild/RPMS/" "$GITHUB_WORKSPACE/dist/rpm/" || true
|
||||||
# Rename to requested filenames
|
find "$GITHUB_WORKSPACE/dist/rpm" -name "v2rayN-*-1*.x86_64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm/v2rayN-linux-rhel-64.rpm" \; || true
|
||||||
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.x86_64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-x64.rpm" \; || true
|
find "$GITHUB_WORKSPACE/dist/rpm" -name "v2rayN-*-1*.aarch64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
|
||||||
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.aarch64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
|
echo "==== Dist tree ===="
|
||||||
|
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
|
||||||
|
|
||||||
- name: Upload RPM artifacts
|
- name: Upload RPM artifacts
|
||||||
if: github.event.inputs.release_tag != ''
|
uses: actions/upload-artifact@v6.0.0
|
||||||
uses: actions/upload-artifact@v4.6.2
|
|
||||||
with:
|
with:
|
||||||
name: v2rayN-rpm
|
name: v2rayN-rpm
|
||||||
path: |
|
path: dist/rpm/**/*.rpm
|
||||||
${{ github.workspace }}/dist/rpm/**/*.rpm
|
|
||||||
|
|
||||||
- name: Upload RPMs to release
|
- name: Upload RPMs to release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
if: github.event.inputs.release_tag != ''
|
|
||||||
with:
|
with:
|
||||||
file: ${{ github.workspace }}/dist/rpm/**/*.rpm
|
file: dist/rpm/**/*.rpm
|
||||||
tag: ${{ github.event.inputs.release_tag }}
|
tag: ${{ env.RELEASE_TAG }}
|
||||||
file_glob: true
|
file_glob: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
|||||||
@@ -26,26 +26,26 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v6.0.2
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v5.0.0
|
uses: actions/setup-dotnet@v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd v2rayN
|
cd v2rayN
|
||||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64 --self-contained=true -o $OutputPath64
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64 -p:SelfContained=true -o $OutputPath64
|
||||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 --self-contained=true -o $OutputPathArm64
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 -p:SelfContained=true -o $OutputPathArm64
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-x64 --self-contained=true -p:PublishTrimmed=true -o $OutputPath64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-x64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPath64
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4.6.2
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: v2rayN-macos
|
name: v2rayN-macos
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -26,26 +26,26 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v6.0.2
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v5.0.0
|
uses: actions/setup-dotnet@v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd v2rayN
|
cd v2rayN
|
||||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPath64
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||||
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4.6.2
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: v2rayN-windows-desktop
|
name: v2rayN-windows-desktop
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ env:
|
|||||||
OutputArchArm: "windows-arm64"
|
OutputArchArm: "windows-arm64"
|
||||||
OutputPath64: "${{ github.workspace }}/v2rayN/Release/windows-64"
|
OutputPath64: "${{ github.workspace }}/v2rayN/Release/windows-64"
|
||||||
OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/windows-arm64"
|
OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/windows-arm64"
|
||||||
OutputPath64Sc: "${{ github.workspace }}/v2rayN/Release/windows-64-SelfContained"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -27,26 +26,23 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v6.0.2
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: actions/setup-dotnet@v5.0.0
|
uses: actions/setup-dotnet@v5.0.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd v2rayN
|
cd v2rayN
|
||||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64
|
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc
|
|
||||||
|
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4.6.2
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: v2rayN-windows
|
name: v2rayN-windows
|
||||||
path: |
|
path: |
|
||||||
@@ -59,7 +55,6 @@ jobs:
|
|||||||
chmod 755 package-release-zip.sh
|
chmod 755 package-release-zip.sh
|
||||||
./package-release-zip.sh $OutputArch $OutputPath64
|
./package-release-zip.sh $OutputArch $OutputPath64
|
||||||
./package-release-zip.sh $OutputArchArm $OutputPathArm64
|
./package-release-zip.sh $OutputArchArm $OutputPathArm64
|
||||||
./package-release-zip.sh "windows-64-SelfContained" $OutputPath64Sc
|
|
||||||
|
|
||||||
- name: Upload zip archive to release
|
- name: Upload zip archive to release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Install deps
|
|
||||||
sudo apt update -y
|
|
||||||
sudo apt install -y libfuse2 wget file
|
|
||||||
|
|
||||||
# Get tools
|
|
||||||
wget -qO appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
|
||||||
chmod +x appimagetool
|
|
||||||
|
|
||||||
# x86_64 AppDir
|
|
||||||
APPDIR_X64="AppDir-x86_64"
|
|
||||||
rm -rf "$APPDIR_X64"
|
|
||||||
mkdir -p "$APPDIR_X64/usr/lib/v2rayN" "$APPDIR_X64/usr/bin" "$APPDIR_X64/usr/share/applications" "$APPDIR_X64/usr/share/pixmaps"
|
|
||||||
cp -rf "$OutputPath64"/* "$APPDIR_X64/usr/lib/v2rayN" || true
|
|
||||||
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/usr/share/pixmaps/v2rayN.png" || true
|
|
||||||
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/v2rayN.png" || true
|
|
||||||
|
|
||||||
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_X64/AppRun"
|
|
||||||
chmod +x "$APPDIR_X64/AppRun"
|
|
||||||
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_X64/usr/bin/v2rayN"
|
|
||||||
cat > "$APPDIR_X64/v2rayN.desktop" <<EOF
|
|
||||||
[Desktop Entry]
|
|
||||||
Name=v2rayN
|
|
||||||
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
|
||||||
Exec=v2rayN
|
|
||||||
Icon=v2rayN
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Categories=Network;
|
|
||||||
EOF
|
|
||||||
install -Dm644 "$APPDIR_X64/v2rayN.desktop" "$APPDIR_X64/usr/share/applications/v2rayN.desktop"
|
|
||||||
|
|
||||||
ARCH=x86_64 ./appimagetool "$APPDIR_X64" "v2rayN-${OutputArch}.AppImage"
|
|
||||||
file "v2rayN-${OutputArch}.AppImage" | grep -q 'x86-64'
|
|
||||||
|
|
||||||
# aarch64 AppDir
|
|
||||||
APPDIR_ARM64="AppDir-aarch64"
|
|
||||||
rm -rf "$APPDIR_ARM64"
|
|
||||||
mkdir -p "$APPDIR_ARM64/usr/lib/v2rayN" "$APPDIR_ARM64/usr/bin" "$APPDIR_ARM64/usr/share/applications" "$APPDIR_ARM64/usr/share/pixmaps"
|
|
||||||
cp -rf "$OutputPathArm64"/* "$APPDIR_ARM64/usr/lib/v2rayN" || true
|
|
||||||
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/usr/share/pixmaps/v2rayN.png" || true
|
|
||||||
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/v2rayN.png" || true
|
|
||||||
|
|
||||||
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_ARM64/AppRun"
|
|
||||||
chmod +x "$APPDIR_ARM64/AppRun"
|
|
||||||
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_ARM64/usr/bin/v2rayN"
|
|
||||||
cat > "$APPDIR_ARM64/v2rayN.desktop" <<EOF
|
|
||||||
[Desktop Entry]
|
|
||||||
Name=v2rayN
|
|
||||||
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
|
||||||
Exec=v2rayN
|
|
||||||
Icon=v2rayN
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Categories=Network;
|
|
||||||
EOF
|
|
||||||
install -Dm644 "$APPDIR_ARM64/v2rayN.desktop" "$APPDIR_ARM64/usr/share/applications/v2rayN.desktop"
|
|
||||||
|
|
||||||
# aarch64 runtime
|
|
||||||
wget -qO runtime-aarch64 https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-aarch64
|
|
||||||
chmod +x runtime-aarch64
|
|
||||||
|
|
||||||
# build aarch64 AppImage
|
|
||||||
ARCH=aarch64 ./appimagetool --runtime-file ./runtime-aarch64 "$APPDIR_ARM64" "v2rayN-${OutputArchArm}.AppImage"
|
|
||||||
file "v2rayN-${OutputArchArm}.AppImage" | grep -q 'ARM aarch64'
|
|
||||||
+1
-1
@@ -28,7 +28,7 @@ Package: v2rayN
|
|||||||
Version: $Version
|
Version: $Version
|
||||||
Architecture: $Arch2
|
Architecture: $Arch2
|
||||||
Maintainer: https://github.com/2dust/v2rayN
|
Maintainer: https://github.com/2dust/v2rayN
|
||||||
Depends: desktop-file-utils, xdg-utils
|
Depends: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1), libfreetype6 (>= 2.11)
|
||||||
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
|
|||||||
<true/>
|
<true/>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>12.7</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
+43
-120
@@ -1,16 +1,16 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
|
# ====== Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS ======
|
||||||
if [[ -r /etc/os-release ]]; then
|
if [[ -r /etc/os-release ]]; then
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
case "$ID" in
|
case "$ID" in
|
||||||
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
|
rhel|rocky|almalinux|fedora|centos)
|
||||||
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "[ERROR] Unsupported system: $NAME ($ID)."
|
echo "[ERROR] Unsupported system: $NAME ($ID)."
|
||||||
echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS or Ubuntu/Debian."
|
echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS."
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -19,6 +19,23 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ======================== Kernel version check (require >= 6.11) =======================
|
||||||
|
MIN_KERNEL_MAJOR=6
|
||||||
|
MIN_KERNEL_MINOR=11
|
||||||
|
KERNEL_FULL=$(uname -r)
|
||||||
|
KERNEL_MAJOR=$(echo "$KERNEL_FULL" | cut -d. -f1)
|
||||||
|
KERNEL_MINOR=$(echo "$KERNEL_FULL" | cut -d. -f2)
|
||||||
|
|
||||||
|
echo "[INFO] Detected kernel version: $KERNEL_FULL"
|
||||||
|
|
||||||
|
if (( KERNEL_MAJOR < MIN_KERNEL_MAJOR )) || { (( KERNEL_MAJOR == MIN_KERNEL_MAJOR )) && (( KERNEL_MINOR < MIN_KERNEL_MINOR )); }; then
|
||||||
|
echo "[ERROR] Kernel $KERNEL_FULL is too old. Requires Linux >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}."
|
||||||
|
echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[OK] Kernel version >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}."
|
||||||
|
|
||||||
# ===== Config & Parse arguments =========================================================
|
# ===== Config & Parse arguments =========================================================
|
||||||
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
|
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
|
||||||
WITH_CORE="both" # Default: bundle both xray+sing-box
|
WITH_CORE="both" # Default: bundle both xray+sing-box
|
||||||
@@ -63,7 +80,6 @@ host_arch="$(uname -m)"
|
|||||||
|
|
||||||
install_ok=0
|
install_ok=0
|
||||||
case "$ID" in
|
case "$ID" in
|
||||||
# ------------------------------ RHEL family (UNCHANGED) ------------------------------
|
|
||||||
rhel|rocky|almalinux|centos)
|
rhel|rocky|almalinux|centos)
|
||||||
if command -v dnf >/dev/null 2>&1; then
|
if command -v dnf >/dev/null 2>&1; then
|
||||||
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
||||||
@@ -75,58 +91,7 @@ case "$ID" in
|
|||||||
install_ok=1
|
install_ok=1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
# ------------------------------ Ubuntu ----------------------------------------------
|
*)
|
||||||
ubuntu)
|
|
||||||
sudo apt-get update
|
|
||||||
# Ensure 'universe' (Ubuntu) to get 'rpm'
|
|
||||||
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
|
|
||||||
sudo apt-get -y install software-properties-common || true
|
|
||||||
sudo add-apt-repository -y universe || true
|
|
||||||
sudo apt-get update
|
|
||||||
fi
|
|
||||||
# Base tools + rpm (provides rpmbuild)
|
|
||||||
sudo apt-get -y install curl unzip tar rsync rpm || true
|
|
||||||
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
|
|
||||||
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
|
|
||||||
# rpmbuild presence check
|
|
||||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
|
||||||
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
|
||||||
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# .NET SDK 8 (best effort via apt)
|
|
||||||
if ! command -v dotnet >/dev/null 2>&1; then
|
|
||||||
sudo apt-get -y install dotnet-sdk-8.0 || true
|
|
||||||
sudo apt-get -y install dotnet-sdk-8 || true
|
|
||||||
sudo apt-get -y install dotnet-sdk || true
|
|
||||||
fi
|
|
||||||
install_ok=1
|
|
||||||
;;
|
|
||||||
# ------------------------------ Debian (KEEP, with local dotnet install) ------------
|
|
||||||
debian)
|
|
||||||
sudo apt-get update
|
|
||||||
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip
|
|
||||||
sudo apt-get -y install curl unzip tar rsync rpm binutils || true
|
|
||||||
# rpmbuild presence check
|
|
||||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
|
||||||
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
|
||||||
echo " Please ensure 'rpm' is available from Debian repos."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet
|
|
||||||
if ! command -v dotnet >/dev/null 2>&1; then
|
|
||||||
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..."
|
|
||||||
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
|
|
||||||
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh"
|
|
||||||
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet"
|
|
||||||
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
|
|
||||||
export DOTNET_ROOT="$HOME/.dotnet"
|
|
||||||
if ! command -v dotnet >/dev/null 2>&1; then
|
|
||||||
echo "[ERROR] dotnet installation failed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
install_ok=1
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -137,7 +102,7 @@ fi
|
|||||||
|
|
||||||
command -v curl >/dev/null
|
command -v curl >/dev/null
|
||||||
|
|
||||||
# Root directory = the script's location
|
# Root directory
|
||||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
@@ -147,14 +112,14 @@ if [[ -f .gitmodules ]]; then
|
|||||||
git submodule update --init --recursive || true
|
git submodule update --init --recursive || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ===== Locate project ================================================================
|
# Locate project
|
||||||
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
if [[ ! -f "$PROJECT" ]]; then
|
if [[ ! -f "$PROJECT" ]]; then
|
||||||
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
|
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
|
||||||
fi
|
fi
|
||||||
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
|
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
|
||||||
|
|
||||||
# ===== Resolve GUI version & auto checkout ============================================
|
# Resolve GUI version & auto checkout
|
||||||
VERSION=""
|
VERSION=""
|
||||||
|
|
||||||
choose_channel() {
|
choose_channel() {
|
||||||
@@ -374,22 +339,6 @@ download_singbox() {
|
|||||||
install -Dm755 "$bin" "$outdir/sing-box"
|
install -Dm755 "$bin" "$outdir/sing-box"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- NEW: download_mihomo (REQUIRED in --netcore mode) ----
|
|
||||||
download_mihomo() {
|
|
||||||
# Download mihomo into outroot/bin/mihomo/mihomo
|
|
||||||
local outroot="$1"
|
|
||||||
local url=""
|
|
||||||
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
|
||||||
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64/bin/mihomo/mihomo"
|
|
||||||
else
|
|
||||||
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo"
|
|
||||||
fi
|
|
||||||
echo "[+] Download mihomo: $url"
|
|
||||||
mkdir -p "$outroot/bin/mihomo"
|
|
||||||
curl -fL "$url" -o "$outroot/bin/mihomo/mihomo"
|
|
||||||
chmod +x "$outroot/bin/mihomo/mihomo" || true
|
|
||||||
}
|
|
||||||
|
|
||||||
# Move geo files to a unified path: outroot/bin
|
# Move geo files to a unified path: outroot/bin
|
||||||
unify_geo_layout() {
|
unify_geo_layout() {
|
||||||
local outroot="$1"
|
local outroot="$1"
|
||||||
@@ -441,7 +390,7 @@ download_geo_assets() {
|
|||||||
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
|
||||||
done
|
done
|
||||||
for f in \
|
for f in \
|
||||||
geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \
|
geosite-cn.srs geosite-gfw.srs geosite-google.srs geosite-greatfire.srs \
|
||||||
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
|
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
|
||||||
curl -fsSL -o "$srss_dir/$f" \
|
curl -fsSL -o "$srss_dir/$f" \
|
||||||
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
||||||
@@ -474,8 +423,7 @@ download_v2rayn_bundle() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
|
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
|
||||||
# keep mihomo
|
find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
|
||||||
# find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
|
|
||||||
|
|
||||||
local nested_dir
|
local nested_dir
|
||||||
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
|
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
|
||||||
@@ -535,20 +483,11 @@ build_for_arch() {
|
|||||||
trap '[[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"' RETURN
|
trap '[[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"' RETURN
|
||||||
|
|
||||||
# rpmbuild topdir selection
|
# rpmbuild topdir selection
|
||||||
local TOPDIR SPECDIR SOURCEDIR USE_TOPDIR_DEFINE
|
local TOPDIR SPECDIR SOURCEDIR
|
||||||
if [[ "$ID" =~ ^(rhel|rocky|almalinux|centos)$ ]]; then
|
|
||||||
rpmdev-setuptree
|
rpmdev-setuptree
|
||||||
TOPDIR="${HOME}/rpmbuild"
|
TOPDIR="${HOME}/rpmbuild"
|
||||||
SPECDIR="${TOPDIR}/SPECS"
|
SPECDIR="${TOPDIR}/SPECS"
|
||||||
SOURCEDIR="${TOPDIR}/SOURCES"
|
SOURCEDIR="${TOPDIR}/SOURCES"
|
||||||
USE_TOPDIR_DEFINE=0
|
|
||||||
else
|
|
||||||
TOPDIR="${WORKDIR}/rpmbuild"
|
|
||||||
SPECDIR="${TOPDIR}/SPECS}"
|
|
||||||
SOURCEDIR="${TOPDIR}/SOURCES"
|
|
||||||
mkdir -p "${SPECDIR}" "${SOURCEDIR}" "${TOPDIR}/BUILD" "${TOPDIR}/RPMS" "${TOPDIR}/SRPMS"
|
|
||||||
USE_TOPDIR_DEFINE=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Stage publish content
|
# Stage publish content
|
||||||
mkdir -p "$WORKDIR/$PKGROOT"
|
mkdir -p "$WORKDIR/$PKGROOT"
|
||||||
@@ -585,8 +524,6 @@ build_for_arch() {
|
|||||||
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||||
fi
|
fi
|
||||||
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||||
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
|
|
||||||
download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Tarball
|
# Tarball
|
||||||
@@ -614,8 +551,14 @@ ExclusiveArch: aarch64 x86_64
|
|||||||
Source0: __PKGROOT__.tar.gz
|
Source0: __PKGROOT__.tar.gz
|
||||||
|
|
||||||
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
|
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
|
||||||
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
|
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||||
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL, xdg-utils
|
Requires: glibc >= 2.34
|
||||||
|
Requires: fontconfig >= 2.13.1
|
||||||
|
Requires: desktop-file-utils >= 0.26
|
||||||
|
Requires: xdg-utils >= 1.1.3
|
||||||
|
Requires: coreutils >= 8.32
|
||||||
|
Requires: bash >= 5.1
|
||||||
|
Requires: freetype >= 2.10
|
||||||
|
|
||||||
%description
|
%description
|
||||||
v2rayN Linux for Red Hat Enterprise Linux
|
v2rayN Linux for Red Hat Enterprise Linux
|
||||||
@@ -634,6 +577,12 @@ https://github.com/2dust/v2rayN
|
|||||||
install -dm0755 %{buildroot}/opt/v2rayN
|
install -dm0755 %{buildroot}/opt/v2rayN
|
||||||
cp -a * %{buildroot}/opt/v2rayN/
|
cp -a * %{buildroot}/opt/v2rayN/
|
||||||
|
|
||||||
|
install -dm0755 %{buildroot}%{_sysconfdir}/sudoers.d
|
||||||
|
cat > %{buildroot}%{_sysconfdir}/sudoers.d/v2rayn-mihomo-deny << 'EOF'
|
||||||
|
ALL ALL=(ALL) !/home/*/.local/share/v2rayN/bin/mihomo/mihomo
|
||||||
|
EOF
|
||||||
|
chmod 0440 %{buildroot}%{_sysconfdir}/sudoers.d/v2rayn-mihomo-deny
|
||||||
|
|
||||||
# Launcher (prefer native ELF first, then DLL fallback)
|
# Launcher (prefer native ELF first, then DLL fallback)
|
||||||
install -dm0755 %{buildroot}%{_bindir}
|
install -dm0755 %{buildroot}%{_bindir}
|
||||||
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
|
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
|
||||||
@@ -687,6 +636,7 @@ fi
|
|||||||
/opt/v2rayN
|
/opt/v2rayN
|
||||||
%{_datadir}/applications/v2rayn.desktop
|
%{_datadir}/applications/v2rayn.desktop
|
||||||
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
|
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
|
||||||
|
%config(noreplace) /etc/sudoers.d/v2rayn-mihomo-deny
|
||||||
SPEC
|
SPEC
|
||||||
|
|
||||||
# Autostart injection (inside %install) and %files entry
|
# Autostart injection (inside %install) and %files entry
|
||||||
@@ -726,35 +676,8 @@ SPEC
|
|||||||
sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE"
|
sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE"
|
||||||
sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE"
|
sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE"
|
||||||
|
|
||||||
# ----- Select proper 'strip' per target arch on Ubuntu only (cross-binutils) -----
|
|
||||||
# NOTE: We define only __strip to point to the target-arch strip.
|
|
||||||
# DO NOT override __brp_strip (it must stay the brp script path).
|
|
||||||
local STRIP_ARGS=()
|
|
||||||
if [[ "$ID" == "ubuntu" ]]; then
|
|
||||||
local STRIP_BIN=""
|
|
||||||
if [[ "$short" == "x64" ]]; then
|
|
||||||
STRIP_BIN="/usr/bin/x86_64-linux-gnu-strip"
|
|
||||||
else
|
|
||||||
STRIP_BIN="/usr/bin/aarch64-linux-gnu-strip"
|
|
||||||
fi
|
|
||||||
if [[ -x "$STRIP_BIN" ]]; then
|
|
||||||
STRIP_ARGS=( --define "__strip $STRIP_BIN" )
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build RPM for this arch (force rpm --target to match compile arch)
|
# Build RPM for this arch (force rpm --target to match compile arch)
|
||||||
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
|
rpmbuild -ba "$SPECFILE" --target "$rpm_target"
|
||||||
rpmbuild -ba "$SPECFILE" --define "_topdir $TOPDIR" --target "$rpm_target" "${STRIP_ARGS[@]}"
|
|
||||||
else
|
|
||||||
rpmbuild -ba "$SPECFILE" --target "$rpm_target" "${STRIP_ARGS[@]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy temporary rpmbuild to ~/rpmbuild on Debian/Ubuntu path
|
|
||||||
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
|
|
||||||
mkdir -p "$HOME/rpmbuild"
|
|
||||||
rsync -a "$TOPDIR"/ "$HOME/rpmbuild"/
|
|
||||||
TOPDIR="$HOME/rpmbuild"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Build done for $short. RPM at:"
|
echo "Build done for $short. RPM at:"
|
||||||
local f
|
local f
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.15.5</Version>
|
<Version>7.19.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||||
<NoWarn>CA1031;CS1591;NU1507;CA1416;IDE0058</NoWarn>
|
<NoWarn>CA1031;CS1591;NU1507;CA1416;IDE0058;IDE0053;IDE0200</NoWarn>
|
||||||
<Nullable>annotations</Nullable>
|
<Nullable>annotations</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Authors>2dust</Authors>
|
<Authors>2dust</Authors>
|
||||||
|
|||||||
@@ -5,24 +5,24 @@
|
|||||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.7" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.7" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.7" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
|
||||||
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.7" />
|
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" />
|
||||||
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
<PackageVersion Include="CliWrap" Version="3.10.0" />
|
||||||
<PackageVersion Include="Downloader" Version="4.0.3" />
|
<PackageVersion Include="Downloader" Version="4.0.3" />
|
||||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.1" />
|
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
|
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
|
||||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
|
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
|
||||||
<PackageVersion Include="QRCoder" Version="1.7.0" />
|
<PackageVersion Include="QRCoder" Version="1.7.0" />
|
||||||
<PackageVersion Include="ReactiveUI" Version="20.4.1" />
|
<PackageVersion Include="ReactiveUI" Version="22.3.1" />
|
||||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||||
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
|
<PackageVersion Include="ReactiveUI.WPF" Version="22.3.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.7" />
|
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.3" />
|
||||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
|
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7" />
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||||
<PackageVersion Include="NLog" Version="6.0.5" />
|
<PackageVersion Include="NLog" Version="6.1.0" />
|
||||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ public static class Extension
|
|||||||
{
|
{
|
||||||
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
|
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
|
||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value);
|
return string.IsNullOrWhiteSpace(value) || string.IsNullOrEmpty(value);
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
|
|
||||||
{
|
|
||||||
return string.IsNullOrWhiteSpace(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
|
public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
|
||||||
{
|
{
|
||||||
return !string.IsNullOrEmpty(value);
|
return !string.IsNullOrWhiteSpace(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? NullIfEmpty(this string? value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(value) ? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
|
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
|
||||||
@@ -94,4 +94,28 @@ public static class Extension
|
|||||||
{
|
{
|
||||||
return configType is EConfigType.Custom or EConfigType.PolicyGroup or EConfigType.ProxyChain;
|
return configType is EConfigType.Custom or EConfigType.PolicyGroup or EConfigType.ProxyChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Safely adds elements from a collection to the list. Does nothing if the source is null.
|
||||||
|
/// </summary>
|
||||||
|
public static void AddRangeSafe<T>(this ICollection<T> destination, IEnumerable<T>? source)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(destination);
|
||||||
|
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destination is List<T> list)
|
||||||
|
{
|
||||||
|
list.AddRange(source);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in source)
|
||||||
|
{
|
||||||
|
destination.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.IO.Compression;
|
|||||||
|
|
||||||
namespace ServiceLib.Common;
|
namespace ServiceLib.Common;
|
||||||
|
|
||||||
public static class FileManager
|
public static class FileUtils
|
||||||
{
|
{
|
||||||
private static readonly string _tag = "FileManager";
|
private static readonly string _tag = "FileManager";
|
||||||
|
|
||||||
@@ -35,9 +35,13 @@ public class JsonUtils
|
|||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <param name="obj"></param>
|
/// <param name="obj"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static T DeepCopy<T>(T obj)
|
public static T? DeepCopy<T>(T? obj)
|
||||||
{
|
{
|
||||||
return Deserialize<T>(Serialize(obj, false))!;
|
if (obj is null)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
return Deserialize<T>(Serialize(obj, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -67,7 +71,7 @@ public class JsonUtils
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="strJson"></param>
|
/// <param name="strJson"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static JsonNode? ParseJson(string strJson)
|
public static JsonNode? ParseJson(string? strJson)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -116,7 +120,7 @@ public class JsonUtils
|
|||||||
/// <param name="obj"></param>
|
/// <param name="obj"></param>
|
||||||
/// <param name="options"></param>
|
/// <param name="options"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string Serialize(object? obj, JsonSerializerOptions options)
|
public static string Serialize(object? obj, JsonSerializerOptions? options)
|
||||||
{
|
{
|
||||||
var result = string.Empty;
|
var result = string.Empty;
|
||||||
try
|
try
|
||||||
@@ -125,7 +129,7 @@ public class JsonUtils
|
|||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result = JsonSerializer.Serialize(obj, options);
|
result = JsonSerializer.Serialize(obj, options ?? _defaultSerializeOptions);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public class Utils
|
|||||||
{
|
{
|
||||||
private static readonly string _tag = "Utils";
|
private static readonly string _tag = "Utils";
|
||||||
|
|
||||||
#region 转换函数
|
#region Conversion Functions
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert to comma-separated string
|
/// Convert to comma-separated string
|
||||||
@@ -306,7 +306,10 @@ public class Utils
|
|||||||
public static bool IsBase64String(string? plainText)
|
public static bool IsBase64String(string? plainText)
|
||||||
{
|
{
|
||||||
if (plainText.IsNullOrEmpty())
|
if (plainText.IsNullOrEmpty())
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var buffer = new Span<byte>(new byte[plainText.Length]);
|
var buffer = new Span<byte>(new byte[plainText.Length]);
|
||||||
return Convert.TryFromBase64String(plainText, buffer, out var _);
|
return Convert.TryFromBase64String(plainText, buffer, out var _);
|
||||||
}
|
}
|
||||||
@@ -329,19 +332,17 @@ public class Utils
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dictionary<string, List<string>> ParseHostsToDictionary(string hostsContent)
|
public static Dictionary<string, List<string>> ParseHostsToDictionary(string? hostsContent)
|
||||||
{
|
{
|
||||||
|
if (hostsContent.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
var userHostsMap = hostsContent
|
var userHostsMap = hostsContent
|
||||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(line => line.Trim())
|
.Select(line => line.Trim())
|
||||||
// skip full-line comments
|
// skip full-line comments
|
||||||
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
|
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#'))
|
||||||
// strip inline comments (truncate at '#')
|
|
||||||
.Select(line =>
|
|
||||||
{
|
|
||||||
var index = line.IndexOf('#');
|
|
||||||
return index >= 0 ? line.Substring(0, index).Trim() : line;
|
|
||||||
})
|
|
||||||
// ensure line still contains valid parts
|
// ensure line still contains valid parts
|
||||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
@@ -422,9 +423,9 @@ public class Utils
|
|||||||
var domain = authority;
|
var domain = authority;
|
||||||
|
|
||||||
// Handle IPv6 addresses, e.g., "[2001:db8::1]:443"
|
// Handle IPv6 addresses, e.g., "[2001:db8::1]:443"
|
||||||
if (authority.StartsWith("[") && authority.Contains("]"))
|
if (authority.StartsWith('[') && authority.Contains(']'))
|
||||||
{
|
{
|
||||||
int closingBracketIndex = authority.LastIndexOf(']');
|
var closingBracketIndex = authority.LastIndexOf(']');
|
||||||
if (closingBracketIndex < authority.Length - 1 && authority[closingBracketIndex + 1] == ':')
|
if (closingBracketIndex < authority.Length - 1 && authority[closingBracketIndex + 1] == ':')
|
||||||
{
|
{
|
||||||
// Port exists
|
// Port exists
|
||||||
@@ -459,9 +460,21 @@ public class Utils
|
|||||||
return (domain, port);
|
return (domain, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion 转换函数
|
public static string? DomainStrategy4Sbox(string? strategy)
|
||||||
|
{
|
||||||
|
return strategy switch
|
||||||
|
{
|
||||||
|
not null when strategy.StartsWith("UseIPv4") => "prefer_ipv4",
|
||||||
|
not null when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
|
||||||
|
not null when strategy.StartsWith("ForceIPv4") => "ipv4_only",
|
||||||
|
not null when strategy.StartsWith("ForceIPv6") => "ipv6_only",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#region 数据检查
|
#endregion Conversion Functions
|
||||||
|
|
||||||
|
#region Data Checks
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determine if the input is a number
|
/// Determine if the input is a number
|
||||||
@@ -502,6 +515,31 @@ public class Utils
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsIpAddress(string? ip)
|
||||||
|
{
|
||||||
|
if (ip.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = ip.Trim();
|
||||||
|
|
||||||
|
// First, validate using built-in parser
|
||||||
|
if (!IPAddress.TryParse(ip, out var address))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For IPv4: ensure it has exactly 3 dots (meaning 4 parts)
|
||||||
|
if (address.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
return ip.Count(c => c == '.') == 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For IPv6: TryParse is already strict enough
|
||||||
|
return address.AddressFamily == AddressFamily.InterNetworkV6;
|
||||||
|
}
|
||||||
|
|
||||||
public static Uri? TryUri(string url)
|
public static Uri? TryUri(string url)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -520,61 +558,78 @@ public class Utils
|
|||||||
{
|
{
|
||||||
// Loopback address check (127.0.0.1 for IPv4, ::1 for IPv6)
|
// Loopback address check (127.0.0.1 for IPv4, ::1 for IPv6)
|
||||||
if (IPAddress.IsLoopback(address))
|
if (IPAddress.IsLoopback(address))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var ipBytes = address.GetAddressBytes();
|
var ipBytes = address.GetAddressBytes();
|
||||||
if (address.AddressFamily == AddressFamily.InterNetwork)
|
if (address.AddressFamily == AddressFamily.InterNetwork)
|
||||||
{
|
{
|
||||||
// IPv4 private address check
|
// IPv4 private address check
|
||||||
if (ipBytes[0] == 10)
|
if (ipBytes[0] == 10)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31)
|
if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (ipBytes[0] == 192 && ipBytes[1] == 168)
|
if (ipBytes[0] == 192 && ipBytes[1] == 168)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
else if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||||
{
|
{
|
||||||
// IPv6 private address check
|
// IPv6 private address check
|
||||||
// Link-local address fe80::/10
|
// Link-local address fe80::/10
|
||||||
if (ipBytes[0] == 0xfe && (ipBytes[1] & 0xc0) == 0x80)
|
if (ipBytes[0] == 0xfe && (ipBytes[1] & 0xc0) == 0x80)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Unique local address fc00::/7 (typically fd00::/8)
|
// Unique local address fc00::/7 (typically fd00::/8)
|
||||||
if ((ipBytes[0] & 0xfe) == 0xfc)
|
if ((ipBytes[0] & 0xfe) == 0xfc)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Private portion in IPv4-mapped addresses ::ffff:0:0/96
|
// Private portion in IPv4-mapped addresses ::ffff:0:0/96
|
||||||
if (address.IsIPv4MappedToIPv6)
|
if (address.IsIPv4MappedToIPv6)
|
||||||
{
|
{
|
||||||
var ipv4Bytes = ipBytes.Skip(12).ToArray();
|
var ipv4Bytes = ipBytes.Skip(12).ToArray();
|
||||||
if (ipv4Bytes[0] == 10)
|
if (ipv4Bytes[0] == 10)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31)
|
if (ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168)
|
if (ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion 数据检查
|
#endregion Data Checks
|
||||||
|
|
||||||
#region 测速
|
#region Speed Test
|
||||||
|
|
||||||
private static bool PortInUse(int port)
|
private static bool PortInUse(int port)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<IPEndPoint> lstIpEndPoints = new();
|
var (lstIpEndPoints, lstTcpConns) = GetActiveNetworkInfo();
|
||||||
List<TcpConnectionInformation> lstTcpConns = new();
|
|
||||||
|
|
||||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
|
|
||||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
|
|
||||||
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
|
|
||||||
|
|
||||||
if (lstIpEndPoints?.FindIndex(it => it.Port == port) >= 0)
|
if (lstIpEndPoints?.FindIndex(it => it.Port == port) >= 0)
|
||||||
{
|
{
|
||||||
@@ -616,9 +671,30 @@ public class Utils
|
|||||||
return 59090;
|
return 59090;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion 测速
|
public static (List<IPEndPoint> endpoints, List<TcpConnectionInformation> connections) GetActiveNetworkInfo()
|
||||||
|
{
|
||||||
|
var endpoints = new List<IPEndPoint>();
|
||||||
|
var connections = new List<TcpConnectionInformation>();
|
||||||
|
|
||||||
#region 杂项
|
try
|
||||||
|
{
|
||||||
|
var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||||
|
|
||||||
|
endpoints.AddRange(ipGlobalProperties.GetActiveTcpListeners());
|
||||||
|
endpoints.AddRange(ipGlobalProperties.GetActiveUdpListeners());
|
||||||
|
connections.AddRange(ipGlobalProperties.GetActiveTcpConnections());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (endpoints, connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Speed Test
|
||||||
|
|
||||||
|
#region Miscellaneous
|
||||||
|
|
||||||
public static bool UpgradeAppExists(out string upgradeFileName)
|
public static bool UpgradeAppExists(out string upgradeFileName)
|
||||||
{
|
{
|
||||||
@@ -694,27 +770,65 @@ public class Utils
|
|||||||
return Guid.TryParse(strSrc, out _);
|
return Guid.TryParse(strSrc, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dictionary<string, string> GetSystemHosts()
|
private static Dictionary<string, string> GetSystemHosts(string hostFile)
|
||||||
{
|
{
|
||||||
var systemHosts = new Dictionary<string, string>();
|
var systemHosts = new Dictionary<string, string>();
|
||||||
var hostFile = @"C:\Windows\System32\drivers\etc\hosts";
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (File.Exists(hostFile))
|
if (!File.Exists(hostFile))
|
||||||
{
|
{
|
||||||
|
return systemHosts;
|
||||||
|
}
|
||||||
var hosts = File.ReadAllText(hostFile).Replace("\r", "");
|
var hosts = File.ReadAllText(hostFile).Replace("\r", "");
|
||||||
var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
foreach (var host in hostsList)
|
foreach (var host in hostsList)
|
||||||
{
|
{
|
||||||
if (host.StartsWith("#"))
|
// Trim whitespace
|
||||||
|
var line = host.Trim();
|
||||||
|
|
||||||
|
// Skip comments and empty lines
|
||||||
|
if (line.IsNullOrEmpty() || line.StartsWith("#"))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
}
|
||||||
|
|
||||||
|
// Strip inline comments
|
||||||
|
var commentIndex = line.IndexOf('#');
|
||||||
|
if (commentIndex >= 0)
|
||||||
|
{
|
||||||
|
line = line.Substring(0, commentIndex).Trim();
|
||||||
|
}
|
||||||
|
if (line.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostItem = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (hostItem.Length < 2)
|
if (hostItem.Length < 2)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
systemHosts.Add(hostItem[1], hostItem[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ipAddress = hostItem[0];
|
||||||
|
var domain = hostItem[1];
|
||||||
|
|
||||||
|
// Validate IP address
|
||||||
|
if (!IsIpAddress(ipAddress))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate domain name
|
||||||
|
if (domain.IsNullOrEmpty() || domain.Length > 255)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
systemHosts[domain] = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return systemHosts;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -724,6 +838,19 @@ public class Utils
|
|||||||
return systemHosts;
|
return systemHosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, string> GetSystemHosts()
|
||||||
|
{
|
||||||
|
var hosts = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts");
|
||||||
|
var hostsIcs = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts.ics");
|
||||||
|
|
||||||
|
foreach (var (key, value) in hostsIcs)
|
||||||
|
{
|
||||||
|
hosts[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hosts;
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<string?> GetCliWrapOutput(string filePath, string? arg)
|
public static async Task<string?> GetCliWrapOutput(string filePath, string? arg)
|
||||||
{
|
{
|
||||||
return await GetCliWrapOutput(filePath, arg != null ? new List<string>() { arg } : null);
|
return await GetCliWrapOutput(filePath, arg != null ? new List<string>() { arg } : null);
|
||||||
@@ -762,7 +889,7 @@ public class Utils
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion 杂项
|
#endregion Miscellaneous
|
||||||
|
|
||||||
#region TempPath
|
#region TempPath
|
||||||
|
|
||||||
@@ -963,17 +1090,29 @@ public class Utils
|
|||||||
|
|
||||||
#region Platform
|
#region Platform
|
||||||
|
|
||||||
public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
public static bool IsWindows() => OperatingSystem.IsWindows();
|
||||||
|
|
||||||
public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
public static bool IsLinux() => OperatingSystem.IsLinux();
|
||||||
|
|
||||||
public static bool IsOSX() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
public static bool IsMacOS() => OperatingSystem.IsMacOS();
|
||||||
|
|
||||||
public static bool IsNonWindows() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
public static bool IsNonWindows() => !OperatingSystem.IsWindows();
|
||||||
|
|
||||||
public static string GetExeName(string name)
|
public static string GetExeName(string name)
|
||||||
{
|
{
|
||||||
return IsWindows() ? $"{name}.exe" : name;
|
if (name.IsNullOrEmpty() || IsNonWindows())
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{name}.exe";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsAdministrator()
|
public static bool IsAdministrator()
|
||||||
@@ -989,16 +1128,11 @@ public class Utils
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (IsWindows() || IsOSX())
|
if (IsWindows() || IsMacOS())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE")))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var exePath = GetExePath();
|
var exePath = GetExePath();
|
||||||
var baseDir = string.IsNullOrEmpty(exePath) ? StartupPath() : Path.GetDirectoryName(exePath) ?? "";
|
var baseDir = string.IsNullOrEmpty(exePath) ? StartupPath() : Path.GetDirectoryName(exePath) ?? "";
|
||||||
var p = baseDir.Replace('\\', '/');
|
var p = baseDir.Replace('\\', '/');
|
||||||
@@ -1008,11 +1142,6 @@ public class Utils
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.Contains("/.mount_", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p.StartsWith("/opt/v2rayN", StringComparison.OrdinalIgnoreCase))
|
if (p.StartsWith("/opt/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace ServiceLib.Common;
|
|
||||||
/*
|
|
||||||
* See:
|
|
||||||
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
|
|
||||||
*/
|
|
||||||
|
|
||||||
public sealed class WindowsJob : IDisposable
|
|
||||||
{
|
|
||||||
private IntPtr handle = IntPtr.Zero;
|
|
||||||
|
|
||||||
public WindowsJob()
|
|
||||||
{
|
|
||||||
handle = CreateJobObject(IntPtr.Zero, null);
|
|
||||||
var extendedInfoPtr = IntPtr.Zero;
|
|
||||||
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
|
|
||||||
{
|
|
||||||
LimitFlags = 0x2000
|
|
||||||
};
|
|
||||||
|
|
||||||
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
||||||
{
|
|
||||||
BasicLimitInformation = info
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
|
||||||
extendedInfoPtr = Marshal.AllocHGlobal(length);
|
|
||||||
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
|
|
||||||
|
|
||||||
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
|
|
||||||
(uint)length))
|
|
||||||
{
|
|
||||||
throw new Exception(string.Format("Unable to set information. Error: {0}",
|
|
||||||
Marshal.GetLastWin32Error()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (extendedInfoPtr != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(extendedInfoPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AddProcess(IntPtr processHandle)
|
|
||||||
{
|
|
||||||
var succ = AssignProcessToJobObject(handle, processHandle);
|
|
||||||
|
|
||||||
if (!succ)
|
|
||||||
{
|
|
||||||
Logging.SaveLog("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error());
|
|
||||||
}
|
|
||||||
|
|
||||||
return succ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AddProcess(int processId)
|
|
||||||
{
|
|
||||||
return AddProcess(Process.GetProcessById(processId).Handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
private bool disposed;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
disposed = true;
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// no managed objects to free
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handle != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
CloseHandle(handle);
|
|
||||||
handle = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~WindowsJob()
|
|
||||||
{
|
|
||||||
Dispose(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion IDisposable
|
|
||||||
|
|
||||||
#region Interop
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
|
||||||
private static extern IntPtr CreateJobObject(IntPtr a, string? lpName);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
|
||||||
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
|
||||||
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
|
||||||
private static extern bool CloseHandle(IntPtr hObject);
|
|
||||||
|
|
||||||
#endregion Interop
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Helper classes
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal struct IO_COUNTERS
|
|
||||||
{
|
|
||||||
public ulong ReadOperationCount;
|
|
||||||
public ulong WriteOperationCount;
|
|
||||||
public ulong OtherOperationCount;
|
|
||||||
public ulong ReadTransferCount;
|
|
||||||
public ulong WriteTransferCount;
|
|
||||||
public ulong OtherTransferCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
|
||||||
{
|
|
||||||
public long PerProcessUserTimeLimit;
|
|
||||||
public long PerJobUserTimeLimit;
|
|
||||||
public uint LimitFlags;
|
|
||||||
public UIntPtr MinimumWorkingSetSize;
|
|
||||||
public UIntPtr MaximumWorkingSetSize;
|
|
||||||
public uint ActiveProcessLimit;
|
|
||||||
public UIntPtr Affinity;
|
|
||||||
public uint PriorityClass;
|
|
||||||
public uint SchedulingClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct SECURITY_ATTRIBUTES
|
|
||||||
{
|
|
||||||
public uint nLength;
|
|
||||||
public IntPtr lpSecurityDescriptor;
|
|
||||||
public int bInheritHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
||||||
{
|
|
||||||
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
|
||||||
public IO_COUNTERS IoInfo;
|
|
||||||
public UIntPtr ProcessMemoryLimit;
|
|
||||||
public UIntPtr JobMemoryLimit;
|
|
||||||
public UIntPtr PeakProcessMemoryUsed;
|
|
||||||
public UIntPtr PeakJobMemoryUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum JobObjectInfoType
|
|
||||||
{
|
|
||||||
AssociateCompletionPortInformation = 7,
|
|
||||||
BasicLimitInformation = 2,
|
|
||||||
BasicUIRestrictions = 4,
|
|
||||||
EndOfJobTimeInformation = 6,
|
|
||||||
ExtendedLimitInformation = 9,
|
|
||||||
SecurityLimitInformation = 5,
|
|
||||||
GroupInformation = 11
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Helper classes
|
|
||||||
|
|
||||||
@@ -5,5 +5,6 @@ public enum ESpeedActionType
|
|||||||
Tcping,
|
Tcping,
|
||||||
Realping,
|
Realping,
|
||||||
Speedtest,
|
Speedtest,
|
||||||
Mixedtest
|
Mixedtest,
|
||||||
|
FastRealping
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-34
@@ -15,7 +15,6 @@ public class Global
|
|||||||
public const string CoreConfigFileName = "config.json";
|
public const string CoreConfigFileName = "config.json";
|
||||||
public const string CorePreConfigFileName = "configPre.json";
|
public const string CorePreConfigFileName = "configPre.json";
|
||||||
public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
|
public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
|
||||||
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
|
|
||||||
public const string ClashMixinConfigFileName = "Mixin.yaml";
|
public const string ClashMixinConfigFileName = "Mixin.yaml";
|
||||||
|
|
||||||
public const string NamespaceSample = "ServiceLib.Sample.";
|
public const string NamespaceSample = "ServiceLib.Sample.";
|
||||||
@@ -73,6 +72,7 @@ public class Global
|
|||||||
public const string GrpcMultiMode = "multi";
|
public const string GrpcMultiMode = "multi";
|
||||||
public const int MaxPort = 65536;
|
public const int MaxPort = 65536;
|
||||||
public const int MinFontSize = 8;
|
public const int MinFontSize = 8;
|
||||||
|
public const int MinFontSizeCount = 13;
|
||||||
public const string RebootAs = "rebootas";
|
public const string RebootAs = "rebootas";
|
||||||
public const string AvaAssets = "avares://v2rayN/Assets/";
|
public const string AvaAssets = "avares://v2rayN/Assets/";
|
||||||
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2";
|
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2";
|
||||||
@@ -88,6 +88,8 @@ public class Global
|
|||||||
public const string SingboxHostsDNSTag = "hosts_dns";
|
public const string SingboxHostsDNSTag = "hosts_dns";
|
||||||
public const string SingboxFakeDNSTag = "fake_dns";
|
public const string SingboxFakeDNSTag = "fake_dns";
|
||||||
|
|
||||||
|
public const int Hysteria2DefaultHopInt = 10;
|
||||||
|
|
||||||
public static readonly List<string> IEProxyProtocols =
|
public static readonly List<string> IEProxyProtocols =
|
||||||
[
|
[
|
||||||
"{ip}:{http_port}",
|
"{ip}:{http_port}",
|
||||||
@@ -106,7 +108,9 @@ public class Global
|
|||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> SubConvertConfig =
|
public static readonly List<string> SubConvertConfig =
|
||||||
[@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"];
|
[
|
||||||
|
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
|
||||||
|
];
|
||||||
|
|
||||||
public static readonly List<string> SubConvertTargets =
|
public static readonly List<string> SubConvertTargets =
|
||||||
[
|
[
|
||||||
@@ -141,11 +145,11 @@ public class Global
|
|||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> SingboxRulesetSources =
|
public static readonly List<string> SingboxRulesetSources =
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
|
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
|
||||||
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
|
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> RoutingRulesSources =
|
public static readonly List<string> RoutingRulesSources =
|
||||||
[
|
[
|
||||||
@@ -284,6 +288,16 @@ public class Global
|
|||||||
"dns"
|
"dns"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static readonly Dictionary<string, string> KcpHeaderMaskMap = new()
|
||||||
|
{
|
||||||
|
{ "srtp", "header-srtp" },
|
||||||
|
{ "utp", "header-utp" },
|
||||||
|
{ "wechat-video", "header-wechat" },
|
||||||
|
{ "dtls", "header-dtls" },
|
||||||
|
{ "wireguard", "header-wireguard" },
|
||||||
|
{ "dns", "header-dns" }
|
||||||
|
};
|
||||||
|
|
||||||
public static readonly List<string> CoreTypes =
|
public static readonly List<string> CoreTypes =
|
||||||
[
|
[
|
||||||
"Xray",
|
"Xray",
|
||||||
@@ -296,6 +310,7 @@ public class Global
|
|||||||
EConfigType.VLESS,
|
EConfigType.VLESS,
|
||||||
EConfigType.Shadowsocks,
|
EConfigType.Shadowsocks,
|
||||||
EConfigType.Trojan,
|
EConfigType.Trojan,
|
||||||
|
EConfigType.Hysteria2,
|
||||||
EConfigType.WireGuard,
|
EConfigType.WireGuard,
|
||||||
EConfigType.SOCKS,
|
EConfigType.SOCKS,
|
||||||
EConfigType.HTTP,
|
EConfigType.HTTP,
|
||||||
@@ -324,13 +339,13 @@ public class Global
|
|||||||
IPOnDemand
|
IPOnDemand
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> DomainStrategies4Singbox =
|
public static readonly List<string> DomainStrategies4Sbox =
|
||||||
[
|
[
|
||||||
"ipv4_only",
|
"",
|
||||||
"ipv6_only",
|
|
||||||
"prefer_ipv4",
|
"prefer_ipv4",
|
||||||
"prefer_ipv6",
|
"prefer_ipv6",
|
||||||
""
|
"ipv4_only",
|
||||||
|
"ipv6_only"
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> Fingerprints =
|
public static readonly List<string> Fingerprints =
|
||||||
@@ -372,28 +387,22 @@ public class Global
|
|||||||
""
|
""
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> DomainStrategy4Freedoms =
|
public static readonly List<string> DomainStrategy =
|
||||||
[
|
[
|
||||||
"AsIs",
|
"AsIs",
|
||||||
"UseIP",
|
"UseIP",
|
||||||
|
"UseIPv4v6",
|
||||||
|
"UseIPv6v4",
|
||||||
"UseIPv4",
|
"UseIPv4",
|
||||||
"UseIPv6",
|
"UseIPv6",
|
||||||
""
|
""
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> SingboxDomainStrategy4Out =
|
|
||||||
[
|
|
||||||
"",
|
|
||||||
"ipv4_only",
|
|
||||||
"prefer_ipv4",
|
|
||||||
"prefer_ipv6",
|
|
||||||
"ipv6_only"
|
|
||||||
];
|
|
||||||
|
|
||||||
public static readonly List<string> DomainDirectDNSAddress =
|
public static readonly List<string> DomainDirectDNSAddress =
|
||||||
[
|
[
|
||||||
"https://dns.alidns.com/dns-query",
|
"https://dns.alidns.com/dns-query",
|
||||||
"https://doh.pub/dns-query",
|
"https://doh.pub/dns-query",
|
||||||
|
"https://dns.alidns.com/dns-query,https://doh.pub/dns-query",
|
||||||
"223.5.5.5",
|
"223.5.5.5",
|
||||||
"119.29.29.29",
|
"119.29.29.29",
|
||||||
"localhost"
|
"localhost"
|
||||||
@@ -402,8 +411,9 @@ public class Global
|
|||||||
public static readonly List<string> DomainRemoteDNSAddress =
|
public static readonly List<string> DomainRemoteDNSAddress =
|
||||||
[
|
[
|
||||||
"https://cloudflare-dns.com/dns-query",
|
"https://cloudflare-dns.com/dns-query",
|
||||||
"https://dns.cloudflare.com/dns-query",
|
|
||||||
"https://dns.google/dns-query",
|
"https://dns.google/dns-query",
|
||||||
|
"https://cloudflare-dns.com/dns-query,https://dns.google/dns-query,8.8.8.8",
|
||||||
|
"https://dns.cloudflare.com/dns-query",
|
||||||
"https://doh.dns.sb/dns-query",
|
"https://doh.dns.sb/dns-query",
|
||||||
"https://doh.opendns.com/dns-query",
|
"https://doh.opendns.com/dns-query",
|
||||||
"https://common.dot.dns.yandex.net",
|
"https://common.dot.dns.yandex.net",
|
||||||
@@ -427,6 +437,7 @@ public class Global
|
|||||||
"zh-Hant",
|
"zh-Hant",
|
||||||
"en",
|
"en",
|
||||||
"fa-Ir",
|
"fa-Ir",
|
||||||
|
"fr",
|
||||||
"ru",
|
"ru",
|
||||||
"hu"
|
"hu"
|
||||||
];
|
];
|
||||||
@@ -584,7 +595,6 @@ public class Global
|
|||||||
|
|
||||||
public static readonly List<string> IPAPIUrls =
|
public static readonly List<string> IPAPIUrls =
|
||||||
[
|
[
|
||||||
@"https://speed.cloudflare.com/meta",
|
|
||||||
@"https://api.ip.sb/geoip",
|
@"https://api.ip.sb/geoip",
|
||||||
@"https://api-ipv4.ip.sb/geoip",
|
@"https://api-ipv4.ip.sb/geoip",
|
||||||
@"https://api-ipv6.ip.sb/geoip",
|
@"https://api-ipv6.ip.sb/geoip",
|
||||||
@@ -601,20 +611,20 @@ public class Global
|
|||||||
|
|
||||||
public static readonly Dictionary<string, List<string>> PredefinedHosts = new()
|
public static readonly Dictionary<string, List<string>> PredefinedHosts = new()
|
||||||
{
|
{
|
||||||
{ "dns.google", new List<string> { "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844" } },
|
{ "dns.google", ["8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844"] },
|
||||||
{ "dns.alidns.com", new List<string> { "223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1" } },
|
{ "dns.alidns.com", ["223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1"] },
|
||||||
{ "one.one.one.one", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
|
{ "one.one.one.one", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] },
|
||||||
{ "1dot1dot1dot1.cloudflare-dns.com", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
|
{ "1dot1dot1dot1.cloudflare-dns.com", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] },
|
||||||
{ "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } },
|
{ "cloudflare-dns.com", ["104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9"] },
|
||||||
{ "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } },
|
{ "dns.cloudflare.com", ["104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5"] },
|
||||||
{ "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
|
{ "dot.pub", ["1.12.12.12", "120.53.53.53"] },
|
||||||
{ "doh.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
|
{ "doh.pub", ["1.12.12.12", "120.53.53.53"] },
|
||||||
{ "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } },
|
{ "dns.quad9.net", ["9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9"] },
|
||||||
{ "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } },
|
{ "dns.yandex.net", ["77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff"] },
|
||||||
{ "dns.sb", new List<string> { "185.222.222.222", "2a09::" } },
|
{ "dns.sb", ["185.222.222.222", "2a09::"] },
|
||||||
{ "dns.umbrella.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
|
{ "dns.umbrella.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] },
|
||||||
{ "dns.sse.cisco.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
|
{ "dns.sse.cisco.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] },
|
||||||
{ "engage.cloudflareclient.com", new List<string> { "162.159.192.1", "2606:4700:d0::a29f:c001" } }
|
{ "engage.cloudflareclient.com", ["162.159.192.1"] }
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly List<string> ExpectedIPs =
|
public static readonly List<string> ExpectedIPs =
|
||||||
@@ -625,5 +635,13 @@ public class Global
|
|||||||
""
|
""
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> EchForceQuerys =
|
||||||
|
[
|
||||||
|
"none",
|
||||||
|
"half",
|
||||||
|
"full",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
#endregion const
|
#endregion const
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public static class AutoStartupHandler
|
|||||||
await SetTaskLinux();
|
await SetTaskLinux();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Utils.IsOSX())
|
else if (Utils.IsMacOS())
|
||||||
{
|
{
|
||||||
await ClearTaskOSX();
|
await ClearTaskOSX();
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public static class ConfigHandler
|
|||||||
|
|
||||||
config.UiItem ??= new UIItem()
|
config.UiItem ??= new UIItem()
|
||||||
{
|
{
|
||||||
EnableAutoAdjustMainLvColWidth = true
|
EnableUpdateSubOnlyRemarksExist = true
|
||||||
};
|
};
|
||||||
config.UiItem.MainColumnItem ??= new();
|
config.UiItem.MainColumnItem ??= new();
|
||||||
config.UiItem.WindowSizeItem ??= new();
|
config.UiItem.WindowSizeItem ??= new();
|
||||||
@@ -114,6 +114,8 @@ public static class ConfigHandler
|
|||||||
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
||||||
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
||||||
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
||||||
|
config.SimpleDNSItem.ServeStale ??= false;
|
||||||
|
config.SimpleDNSItem.ParallelQuery ??= false;
|
||||||
|
|
||||||
config.SpeedTestItem ??= new();
|
config.SpeedTestItem ??= new();
|
||||||
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
||||||
@@ -228,12 +230,8 @@ public static class ConfigHandler
|
|||||||
item.Remarks = profileItem.Remarks;
|
item.Remarks = profileItem.Remarks;
|
||||||
item.Address = profileItem.Address;
|
item.Address = profileItem.Address;
|
||||||
item.Port = profileItem.Port;
|
item.Port = profileItem.Port;
|
||||||
item.Ports = profileItem.Ports;
|
|
||||||
|
|
||||||
item.Id = profileItem.Id;
|
item.Password = profileItem.Password;
|
||||||
item.AlterId = profileItem.AlterId;
|
|
||||||
item.Security = profileItem.Security;
|
|
||||||
item.Flow = profileItem.Flow;
|
|
||||||
|
|
||||||
item.Network = profileItem.Network;
|
item.Network = profileItem.Network;
|
||||||
item.HeaderType = profileItem.HeaderType;
|
item.HeaderType = profileItem.HeaderType;
|
||||||
@@ -252,6 +250,12 @@ public static class ConfigHandler
|
|||||||
item.Mldsa65Verify = profileItem.Mldsa65Verify;
|
item.Mldsa65Verify = profileItem.Mldsa65Verify;
|
||||||
item.Extra = profileItem.Extra;
|
item.Extra = profileItem.Extra;
|
||||||
item.MuxEnabled = profileItem.MuxEnabled;
|
item.MuxEnabled = profileItem.MuxEnabled;
|
||||||
|
item.Cert = profileItem.Cert;
|
||||||
|
item.CertSha = profileItem.CertSha;
|
||||||
|
item.EchConfigList = profileItem.EchConfigList;
|
||||||
|
item.EchForceQuery = profileItem.EchForceQuery;
|
||||||
|
item.Finalmask = profileItem.Finalmask;
|
||||||
|
item.ProtoExtra = profileItem.ProtoExtra;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret = item.ConfigType switch
|
var ret = item.ConfigType switch
|
||||||
@@ -284,19 +288,22 @@ public static class ConfigHandler
|
|||||||
profileItem.ConfigType = EConfigType.VMess;
|
profileItem.ConfigType = EConfigType.VMess;
|
||||||
|
|
||||||
profileItem.Address = profileItem.Address.TrimEx();
|
profileItem.Address = profileItem.Address.TrimEx();
|
||||||
profileItem.Id = profileItem.Id.TrimEx();
|
profileItem.Password = profileItem.Password.TrimEx();
|
||||||
profileItem.Security = profileItem.Security.TrimEx();
|
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
VmessSecurity = profileItem.GetProtocolExtra().VmessSecurity?.TrimEx()
|
||||||
|
});
|
||||||
profileItem.Network = profileItem.Network.TrimEx();
|
profileItem.Network = profileItem.Network.TrimEx();
|
||||||
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
|
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
|
||||||
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
|
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
|
||||||
profileItem.Path = profileItem.Path.TrimEx();
|
profileItem.Path = profileItem.Path.TrimEx();
|
||||||
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
|
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
|
||||||
|
|
||||||
if (!Global.VmessSecurities.Contains(profileItem.Security))
|
if (!Global.VmessSecurities.Contains(profileItem.GetProtocolExtra().VmessSecurity))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (profileItem.Id.IsNullOrEmpty())
|
if (profileItem.Password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -354,11 +361,6 @@ public static class ConfigHandler
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (profileItem.ConfigType.IsGroupType())
|
|
||||||
{
|
|
||||||
var profileGroupItem = await AppManager.Instance.GetProfileGroupItem(it.IndexId);
|
|
||||||
await AddGroupServerCommon(config, profileItem, profileGroupItem, true);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await AddServerCommon(config, profileItem, true);
|
await AddServerCommon(config, profileItem, true);
|
||||||
@@ -447,13 +449,13 @@ public static class ConfigHandler
|
|||||||
/// <returns>0 if successful, -1 if failed</returns>
|
/// <returns>0 if successful, -1 if failed</returns>
|
||||||
public static async Task<int> MoveServer(Config config, List<ProfileItem> lstProfile, int index, EMove eMove, int pos = -1)
|
public static async Task<int> MoveServer(Config config, List<ProfileItem> lstProfile, int index, EMove eMove, int pos = -1)
|
||||||
{
|
{
|
||||||
int count = lstProfile.Count;
|
var count = lstProfile.Count;
|
||||||
if (index < 0 || index > lstProfile.Count - 1)
|
if (index < 0 || index > lstProfile.Count - 1)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < lstProfile.Count; i++)
|
for (var i = 0; i < lstProfile.Count; i++)
|
||||||
{
|
{
|
||||||
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
|
||||||
}
|
}
|
||||||
@@ -527,7 +529,7 @@ public static class ConfigHandler
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
var ext = Path.GetExtension(fileName);
|
var ext = Path.GetExtension(fileName);
|
||||||
string newFileName = $"{Utils.GetGuid()}{ext}";
|
var newFileName = $"{Utils.GetGuid()}{ext}";
|
||||||
//newFileName = Path.Combine(Utile.GetTempPath(), newFileName);
|
//newFileName = Path.Combine(Utile.GetTempPath(), newFileName);
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -604,14 +606,17 @@ public static class ConfigHandler
|
|||||||
profileItem.ConfigType = EConfigType.Shadowsocks;
|
profileItem.ConfigType = EConfigType.Shadowsocks;
|
||||||
|
|
||||||
profileItem.Address = profileItem.Address.TrimEx();
|
profileItem.Address = profileItem.Address.TrimEx();
|
||||||
profileItem.Id = profileItem.Id.TrimEx();
|
profileItem.Password = profileItem.Password.TrimEx();
|
||||||
profileItem.Security = profileItem.Security.TrimEx();
|
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
SsMethod = profileItem.GetProtocolExtra().SsMethod?.TrimEx()
|
||||||
|
});
|
||||||
|
|
||||||
if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
|
if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.GetProtocolExtra().SsMethod))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (profileItem.Id.IsNullOrEmpty())
|
if (profileItem.Password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -672,12 +677,12 @@ public static class ConfigHandler
|
|||||||
profileItem.ConfigType = EConfigType.Trojan;
|
profileItem.ConfigType = EConfigType.Trojan;
|
||||||
|
|
||||||
profileItem.Address = profileItem.Address.TrimEx();
|
profileItem.Address = profileItem.Address.TrimEx();
|
||||||
profileItem.Id = profileItem.Id.TrimEx();
|
profileItem.Password = profileItem.Password.TrimEx();
|
||||||
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
profileItem.StreamSecurity = Global.StreamSecurity;
|
profileItem.StreamSecurity = Global.StreamSecurity;
|
||||||
}
|
}
|
||||||
if (profileItem.Id.IsNullOrEmpty())
|
if (profileItem.Password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -699,21 +704,27 @@ public static class ConfigHandler
|
|||||||
public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true)
|
public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true)
|
||||||
{
|
{
|
||||||
profileItem.ConfigType = EConfigType.Hysteria2;
|
profileItem.ConfigType = EConfigType.Hysteria2;
|
||||||
profileItem.CoreType = ECoreType.sing_box;
|
//profileItem.CoreType = ECoreType.sing_box;
|
||||||
|
|
||||||
profileItem.Address = profileItem.Address.TrimEx();
|
profileItem.Address = profileItem.Address.TrimEx();
|
||||||
profileItem.Id = profileItem.Id.TrimEx();
|
profileItem.Password = profileItem.Password.TrimEx();
|
||||||
profileItem.Path = profileItem.Path.TrimEx();
|
|
||||||
profileItem.Network = string.Empty;
|
profileItem.Network = string.Empty;
|
||||||
|
|
||||||
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
profileItem.StreamSecurity = Global.StreamSecurity;
|
profileItem.StreamSecurity = Global.StreamSecurity;
|
||||||
}
|
}
|
||||||
if (profileItem.Id.IsNullOrEmpty())
|
if (profileItem.Password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
SalamanderPass = profileItem.GetProtocolExtra().SalamanderPass?.TrimEx(),
|
||||||
|
UpMbps = profileItem.GetProtocolExtra().UpMbps is null or < 0 ? config.HysteriaItem.UpMbps : profileItem.GetProtocolExtra().UpMbps,
|
||||||
|
DownMbps = profileItem.GetProtocolExtra().DownMbps is null or < 0 ? config.HysteriaItem.DownMbps : profileItem.GetProtocolExtra().DownMbps,
|
||||||
|
HopInterval = profileItem.GetProtocolExtra().HopInterval?.TrimEx(),
|
||||||
|
});
|
||||||
|
|
||||||
await AddServerCommon(config, profileItem, toFile);
|
await AddServerCommon(config, profileItem, toFile);
|
||||||
|
|
||||||
@@ -735,8 +746,8 @@ public static class ConfigHandler
|
|||||||
profileItem.CoreType = ECoreType.sing_box;
|
profileItem.CoreType = ECoreType.sing_box;
|
||||||
|
|
||||||
profileItem.Address = profileItem.Address.TrimEx();
|
profileItem.Address = profileItem.Address.TrimEx();
|
||||||
profileItem.Id = profileItem.Id.TrimEx();
|
profileItem.Username = profileItem.Username.TrimEx();
|
||||||
profileItem.Security = profileItem.Security.TrimEx();
|
profileItem.Password = profileItem.Password.TrimEx();
|
||||||
profileItem.Network = string.Empty;
|
profileItem.Network = string.Empty;
|
||||||
|
|
||||||
if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType))
|
if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType))
|
||||||
@@ -752,7 +763,7 @@ public static class ConfigHandler
|
|||||||
{
|
{
|
||||||
profileItem.Alpn = "h3";
|
profileItem.Alpn = "h3";
|
||||||
}
|
}
|
||||||
if (profileItem.Id.IsNullOrEmpty())
|
if (profileItem.Password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -775,17 +786,17 @@ public static class ConfigHandler
|
|||||||
profileItem.ConfigType = EConfigType.WireGuard;
|
profileItem.ConfigType = EConfigType.WireGuard;
|
||||||
|
|
||||||
profileItem.Address = profileItem.Address.TrimEx();
|
profileItem.Address = profileItem.Address.TrimEx();
|
||||||
profileItem.Id = profileItem.Id.TrimEx();
|
profileItem.Password = profileItem.Password.TrimEx();
|
||||||
profileItem.PublicKey = profileItem.PublicKey.TrimEx();
|
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||||
profileItem.Path = profileItem.Path.TrimEx();
|
|
||||||
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
|
|
||||||
profileItem.Network = string.Empty;
|
|
||||||
if (profileItem.ShortId.IsNullOrEmpty())
|
|
||||||
{
|
{
|
||||||
profileItem.ShortId = Global.TunMtus.First().ToString();
|
WgPublicKey = profileItem.GetProtocolExtra().WgPublicKey?.TrimEx(),
|
||||||
}
|
WgPresharedKey = profileItem.GetProtocolExtra().WgPresharedKey?.TrimEx(),
|
||||||
|
WgInterfaceAddress = profileItem.GetProtocolExtra().WgInterfaceAddress?.TrimEx(),
|
||||||
|
WgReserved = profileItem.GetProtocolExtra().WgReserved?.TrimEx(),
|
||||||
|
WgMtu = profileItem.GetProtocolExtra().WgMtu is null or <= 0 ? Global.TunMtus.First() : profileItem.GetProtocolExtra().WgMtu,
|
||||||
|
});
|
||||||
|
|
||||||
if (profileItem.Id.IsNullOrEmpty())
|
if (profileItem.Password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -809,14 +820,13 @@ public static class ConfigHandler
|
|||||||
profileItem.CoreType = ECoreType.sing_box;
|
profileItem.CoreType = ECoreType.sing_box;
|
||||||
|
|
||||||
profileItem.Address = profileItem.Address.TrimEx();
|
profileItem.Address = profileItem.Address.TrimEx();
|
||||||
profileItem.Id = profileItem.Id.TrimEx();
|
profileItem.Password = profileItem.Password.TrimEx();
|
||||||
profileItem.Security = profileItem.Security.TrimEx();
|
|
||||||
profileItem.Network = string.Empty;
|
profileItem.Network = string.Empty;
|
||||||
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
profileItem.StreamSecurity = Global.StreamSecurity;
|
profileItem.StreamSecurity = Global.StreamSecurity;
|
||||||
}
|
}
|
||||||
if (profileItem.Id.IsNullOrEmpty())
|
if (profileItem.Password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -835,7 +845,7 @@ public static class ConfigHandler
|
|||||||
/// <returns>0 if successful, -1 if failed</returns>
|
/// <returns>0 if successful, -1 if failed</returns>
|
||||||
public static async Task<int> SortServers(Config config, string subId, string colName, bool asc)
|
public static async Task<int> SortServers(Config config, string subId, string colName, bool asc)
|
||||||
{
|
{
|
||||||
var lstModel = await AppManager.Instance.ProfileItems(subId, "");
|
var lstModel = await AppManager.Instance.ProfileModels(subId, "");
|
||||||
if (lstModel.Count <= 0)
|
if (lstModel.Count <= 0)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
@@ -854,7 +864,7 @@ public static class ConfigHandler
|
|||||||
Remarks = t.Remarks,
|
Remarks = t.Remarks,
|
||||||
Address = t.Address,
|
Address = t.Address,
|
||||||
Port = t.Port,
|
Port = t.Port,
|
||||||
Security = t.Security,
|
//Security = t.Security,
|
||||||
Network = t.Network,
|
Network = t.Network,
|
||||||
StreamSecurity = t.StreamSecurity,
|
StreamSecurity = t.StreamSecurity,
|
||||||
Delay = t33?.Delay ?? 0,
|
Delay = t33?.Delay ?? 0,
|
||||||
@@ -953,26 +963,25 @@ public static class ConfigHandler
|
|||||||
profileItem.ConfigType = EConfigType.VLESS;
|
profileItem.ConfigType = EConfigType.VLESS;
|
||||||
|
|
||||||
profileItem.Address = profileItem.Address.TrimEx();
|
profileItem.Address = profileItem.Address.TrimEx();
|
||||||
profileItem.Id = profileItem.Id.TrimEx();
|
profileItem.Password = profileItem.Password.TrimEx();
|
||||||
profileItem.Security = profileItem.Security.TrimEx();
|
|
||||||
profileItem.Network = profileItem.Network.TrimEx();
|
profileItem.Network = profileItem.Network.TrimEx();
|
||||||
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
|
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
|
||||||
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
|
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
|
||||||
profileItem.Path = profileItem.Path.TrimEx();
|
profileItem.Path = profileItem.Path.TrimEx();
|
||||||
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
|
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
|
||||||
|
|
||||||
if (!Global.Flows.Contains(profileItem.Flow))
|
var vlessEncryption = profileItem.GetProtocolExtra().VlessEncryption?.TrimEx();
|
||||||
|
var flow = profileItem.GetProtocolExtra().Flow?.TrimEx() ?? string.Empty;
|
||||||
|
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||||
{
|
{
|
||||||
profileItem.Flow = Global.Flows.First();
|
VlessEncryption = vlessEncryption.IsNullOrEmpty() ? Global.None : vlessEncryption,
|
||||||
}
|
Flow = Global.Flows.Contains(flow) ? flow : Global.Flows.First(),
|
||||||
if (profileItem.Id.IsNullOrEmpty())
|
});
|
||||||
|
|
||||||
|
if (profileItem.Password.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (profileItem.Security.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
profileItem.Security = Global.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
await AddServerCommon(config, profileItem, toFile);
|
await AddServerCommon(config, profileItem, toFile);
|
||||||
|
|
||||||
@@ -1027,7 +1036,7 @@ public static class ConfigHandler
|
|||||||
/// <returns>0 if successful</returns>
|
/// <returns>0 if successful</returns>
|
||||||
public static async Task<int> AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true)
|
public static async Task<int> AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true)
|
||||||
{
|
{
|
||||||
profileItem.ConfigVersion = 2;
|
profileItem.ConfigVersion = 3;
|
||||||
|
|
||||||
if (profileItem.StreamSecurity.IsNotEmpty())
|
if (profileItem.StreamSecurity.IsNotEmpty())
|
||||||
{
|
{
|
||||||
@@ -1071,42 +1080,12 @@ public static class ConfigHandler
|
|||||||
|
|
||||||
if (toFile)
|
if (toFile)
|
||||||
{
|
{
|
||||||
|
profileItem.SetProtocolExtra();
|
||||||
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
|
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<int> AddGroupServerCommon(Config config, ProfileItem profileItem, ProfileGroupItem profileGroupItem, bool toFile = true)
|
|
||||||
{
|
|
||||||
var maxSort = -1;
|
|
||||||
if (profileItem.IndexId.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
profileItem.IndexId = Utils.GetGuid(false);
|
|
||||||
maxSort = ProfileExManager.Instance.GetMaxSort();
|
|
||||||
}
|
|
||||||
var groupType = profileItem.ConfigType == EConfigType.ProxyChain ? EConfigType.ProxyChain.ToString() : profileGroupItem.MultipleLoad.ToString();
|
|
||||||
profileItem.Address = $"{profileItem.CoreType}-{groupType}";
|
|
||||||
if (maxSort > 0)
|
|
||||||
{
|
|
||||||
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
|
|
||||||
}
|
|
||||||
if (toFile)
|
|
||||||
{
|
|
||||||
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
|
|
||||||
if (profileGroupItem != null)
|
|
||||||
{
|
|
||||||
profileGroupItem.IndexId = profileItem.IndexId;
|
|
||||||
await ProfileGroupItemManager.Instance.SaveItemAsync(profileGroupItem);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(profileItem.IndexId);
|
|
||||||
await ProfileGroupItemManager.Instance.SaveTo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compare two profile items to determine if they represent the same server
|
/// Compare two profile items to determine if they represent the same server
|
||||||
/// Used for deduplication and server matching
|
/// Used for deduplication and server matching
|
||||||
@@ -1122,22 +1101,29 @@ public static class ConfigHandler
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var oProtocolExtra = o.GetProtocolExtra();
|
||||||
|
var nProtocolExtra = n.GetProtocolExtra();
|
||||||
|
|
||||||
return o.ConfigType == n.ConfigType
|
return o.ConfigType == n.ConfigType
|
||||||
&& AreEqual(o.Address, n.Address)
|
&& AreEqual(o.Address, n.Address)
|
||||||
&& o.Port == n.Port
|
&& o.Port == n.Port
|
||||||
&& AreEqual(o.Id, n.Id)
|
&& AreEqual(o.Password, n.Password)
|
||||||
&& AreEqual(o.Security, n.Security)
|
&& AreEqual(oProtocolExtra.VlessEncryption, nProtocolExtra.VlessEncryption)
|
||||||
|
&& AreEqual(oProtocolExtra.SsMethod, nProtocolExtra.SsMethod)
|
||||||
|
&& AreEqual(oProtocolExtra.VmessSecurity, nProtocolExtra.VmessSecurity)
|
||||||
&& AreEqual(o.Network, n.Network)
|
&& AreEqual(o.Network, n.Network)
|
||||||
&& AreEqual(o.HeaderType, n.HeaderType)
|
&& AreEqual(o.HeaderType, n.HeaderType)
|
||||||
&& AreEqual(o.RequestHost, n.RequestHost)
|
&& AreEqual(o.RequestHost, n.RequestHost)
|
||||||
&& AreEqual(o.Path, n.Path)
|
&& AreEqual(o.Path, n.Path)
|
||||||
&& (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity)
|
&& (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity)
|
||||||
&& AreEqual(o.Flow, n.Flow)
|
&& AreEqual(oProtocolExtra.Flow, nProtocolExtra.Flow)
|
||||||
|
&& AreEqual(oProtocolExtra.SalamanderPass, nProtocolExtra.SalamanderPass)
|
||||||
&& AreEqual(o.Sni, n.Sni)
|
&& AreEqual(o.Sni, n.Sni)
|
||||||
&& AreEqual(o.Alpn, n.Alpn)
|
&& AreEqual(o.Alpn, n.Alpn)
|
||||||
&& AreEqual(o.Fingerprint, n.Fingerprint)
|
&& AreEqual(o.Fingerprint, n.Fingerprint)
|
||||||
&& AreEqual(o.PublicKey, n.PublicKey)
|
&& AreEqual(o.PublicKey, n.PublicKey)
|
||||||
&& AreEqual(o.ShortId, n.ShortId)
|
&& AreEqual(o.ShortId, n.ShortId)
|
||||||
|
&& AreEqual(o.Finalmask, n.Finalmask)
|
||||||
&& (!remarks || o.Remarks == n.Remarks);
|
&& (!remarks || o.Remarks == n.Remarks);
|
||||||
|
|
||||||
static bool AreEqual(string? a, string? b)
|
static bool AreEqual(string? a, string? b)
|
||||||
@@ -1193,7 +1179,7 @@ public static class ConfigHandler
|
|||||||
var indexId = Utils.GetGuid(false);
|
var indexId = Utils.GetGuid(false);
|
||||||
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
|
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
|
||||||
|
|
||||||
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId)).Remarks} ";
|
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId))?.Remarks} ";
|
||||||
if (coreType == ECoreType.Xray)
|
if (coreType == ECoreType.Xray)
|
||||||
{
|
{
|
||||||
remark += multipleLoad switch
|
remark += multipleLoad switch
|
||||||
@@ -1221,18 +1207,19 @@ public static class ConfigHandler
|
|||||||
CoreType = coreType,
|
CoreType = coreType,
|
||||||
ConfigType = EConfigType.PolicyGroup,
|
ConfigType = EConfigType.PolicyGroup,
|
||||||
Remarks = remark,
|
Remarks = remark,
|
||||||
|
IsSub = false
|
||||||
};
|
};
|
||||||
if (!subId.IsNullOrEmpty())
|
if (!subId.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
profile.Subid = subId;
|
profile.Subid = subId;
|
||||||
}
|
}
|
||||||
var profileGroup = new ProfileGroupItem
|
var extraItem = new ProtocolExtraItem
|
||||||
{
|
{
|
||||||
ChildItems = childProfileIndexId,
|
ChildItems = childProfileIndexId,
|
||||||
MultipleLoad = multipleLoad,
|
MultipleLoad = multipleLoad,
|
||||||
IndexId = indexId,
|
|
||||||
};
|
};
|
||||||
var ret = await AddGroupServerCommon(config, profile, profileGroup, true);
|
profile.SetProtocolExtra(extraItem);
|
||||||
|
var ret = await AddServerCommon(config, profile, true);
|
||||||
result.Success = ret == 0;
|
result.Success = ret == 0;
|
||||||
result.Data = indexId;
|
result.Data = indexId;
|
||||||
return result;
|
return result;
|
||||||
@@ -1246,32 +1233,14 @@ public static class ConfigHandler
|
|||||||
/// <param name="node">Server node that might need pre-SOCKS</param>
|
/// <param name="node">Server node that might need pre-SOCKS</param>
|
||||||
/// <param name="coreType">Core type being used</param>
|
/// <param name="coreType">Core type being used</param>
|
||||||
/// <returns>A SOCKS profile item or null if not needed</returns>
|
/// <returns>A SOCKS profile item or null if not needed</returns>
|
||||||
public static async Task<ProfileItem?> GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
|
public static ProfileItem? GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
|
||||||
{
|
{
|
||||||
|
if (node.ConfigType != EConfigType.Custom || !(node.PreSocksPort > 0))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
ProfileItem? itemSocks = null;
|
ProfileItem? itemSocks = null;
|
||||||
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
|
var preCoreType = AppManager.Instance.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
||||||
{
|
|
||||||
var tun2SocksAddress = node.Address;
|
|
||||||
if (node.ConfigType.IsGroupType())
|
|
||||||
{
|
|
||||||
var lstAddresses = (await ProfileGroupItemManager.GetAllChildDomainAddresses(node.IndexId)).ToList();
|
|
||||||
if (lstAddresses.Count > 0)
|
|
||||||
{
|
|
||||||
tun2SocksAddress = Utils.List2String(lstAddresses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itemSocks = new ProfileItem()
|
|
||||||
{
|
|
||||||
CoreType = ECoreType.sing_box,
|
|
||||||
ConfigType = EConfigType.SOCKS,
|
|
||||||
Address = Global.Loopback,
|
|
||||||
SpiderX = tun2SocksAddress, // Tun2SocksAddress
|
|
||||||
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)
|
|
||||||
{
|
|
||||||
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
|
||||||
itemSocks = new ProfileItem()
|
itemSocks = new ProfileItem()
|
||||||
{
|
{
|
||||||
CoreType = preCoreType,
|
CoreType = preCoreType,
|
||||||
@@ -1279,11 +1248,50 @@ public static class ConfigHandler
|
|||||||
Address = Global.Loopback,
|
Address = Global.Loopback,
|
||||||
Port = node.PreSocksPort.Value,
|
Port = node.PreSocksPort.Value,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
await Task.CompletedTask;
|
|
||||||
return itemSocks;
|
return itemSocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CoreConfigContext? GetPreSocksCoreConfigContext(CoreConfigContext nodeContext)
|
||||||
|
{
|
||||||
|
var config = nodeContext.AppConfig;
|
||||||
|
var node = nodeContext.Node;
|
||||||
|
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||||
|
|
||||||
|
var preSocksItem = GetPreSocksItem(config, node, coreType);
|
||||||
|
if (preSocksItem != null)
|
||||||
|
{
|
||||||
|
return nodeContext with { Node = preSocksItem, };
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!nodeContext.IsTunEnabled)
|
||||||
|
|| coreType != ECoreType.Xray
|
||||||
|
|| node.ConfigType == EConfigType.Custom)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var tunProtectSsPort = Utils.GetFreePort();
|
||||||
|
var proxyRelaySsPort = Utils.GetFreePort();
|
||||||
|
var preItem = new ProfileItem()
|
||||||
|
{
|
||||||
|
CoreType = ECoreType.sing_box,
|
||||||
|
ConfigType = EConfigType.Shadowsocks,
|
||||||
|
Address = Global.Loopback,
|
||||||
|
Port = proxyRelaySsPort,
|
||||||
|
Password = Global.None,
|
||||||
|
};
|
||||||
|
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
SsMethod = Global.None,
|
||||||
|
});
|
||||||
|
var preContext = nodeContext with
|
||||||
|
{
|
||||||
|
Node = preItem,
|
||||||
|
TunProtectSsPort = tunProtectSsPort,
|
||||||
|
ProxyRelaySsPort = proxyRelaySsPort,
|
||||||
|
};
|
||||||
|
return preContext;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove servers with invalid test results (timeout)
|
/// Remove servers with invalid test results (timeout)
|
||||||
/// Useful for cleaning up subscription lists
|
/// Useful for cleaning up subscription lists
|
||||||
@@ -1293,7 +1301,7 @@ public static class ConfigHandler
|
|||||||
/// <returns>Number of removed servers or -1 if failed</returns>
|
/// <returns>Number of removed servers or -1 if failed</returns>
|
||||||
public static async Task<int> RemoveInvalidServerResult(Config config, string subid)
|
public static async Task<int> RemoveInvalidServerResult(Config config, string subid)
|
||||||
{
|
{
|
||||||
var lstModel = await AppManager.Instance.ProfileItems(subid, "");
|
var lstModel = await AppManager.Instance.ProfileModels(subid, "");
|
||||||
if (lstModel is { Count: <= 0 })
|
if (lstModel is { Count: <= 0 })
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1355,7 +1363,7 @@ public static class ConfigHandler
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var profileItem = FmtHandler.ResolveConfig(str, out string msg);
|
var profileItem = FmtHandler.ResolveConfig(str, out var msg);
|
||||||
if (profileItem is null)
|
if (profileItem is null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -1439,7 +1447,7 @@ public static class ConfigHandler
|
|||||||
{
|
{
|
||||||
await RemoveServersViaSubid(config, subid, isSub);
|
await RemoveServersViaSubid(config, subid, isSub);
|
||||||
}
|
}
|
||||||
int count = 0;
|
var count = 0;
|
||||||
foreach (var it in lstProfiles)
|
foreach (var it in lstProfiles)
|
||||||
{
|
{
|
||||||
it.Subid = subid;
|
it.Subid = subid;
|
||||||
@@ -1529,7 +1537,7 @@ public static class ConfigHandler
|
|||||||
var lstSsServer = ShadowsocksFmt.ResolveSip008(strData);
|
var lstSsServer = ShadowsocksFmt.ResolveSip008(strData);
|
||||||
if (lstSsServer?.Count > 0)
|
if (lstSsServer?.Count > 0)
|
||||||
{
|
{
|
||||||
int counter = 0;
|
var counter = 0;
|
||||||
foreach (var ssItem in lstSsServer)
|
foreach (var ssItem in lstSsServer)
|
||||||
{
|
{
|
||||||
ssItem.Subid = subid;
|
ssItem.Subid = subid;
|
||||||
@@ -1649,7 +1657,9 @@ public static class ConfigHandler
|
|||||||
|
|
||||||
var uri = Utils.TryUri(url);
|
var uri = Utils.TryUri(url);
|
||||||
if (uri == null)
|
if (uri == null)
|
||||||
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
//Do not allow http protocol
|
//Do not allow http protocol
|
||||||
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
|
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
|
||||||
{
|
{
|
||||||
@@ -1704,7 +1714,7 @@ public static class ConfigHandler
|
|||||||
var maxSort = 0;
|
var maxSort = 0;
|
||||||
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
|
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
|
||||||
{
|
{
|
||||||
var lstSubs = (await AppManager.Instance.SubItems());
|
var lstSubs = await AppManager.Instance.SubItems();
|
||||||
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
|
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
|
||||||
}
|
}
|
||||||
item.Sort = maxSort + 1;
|
item.Sort = maxSort + 1;
|
||||||
@@ -1866,7 +1876,7 @@ public static class ConfigHandler
|
|||||||
/// <returns>0 if successful, -1 if failed</returns>
|
/// <returns>0 if successful, -1 if failed</returns>
|
||||||
public static async Task<int> MoveRoutingRule(List<RulesItem> rules, int index, EMove eMove, int pos = -1)
|
public static async Task<int> MoveRoutingRule(List<RulesItem> rules, int index, EMove eMove, int pos = -1)
|
||||||
{
|
{
|
||||||
int count = rules.Count;
|
var count = rules.Count;
|
||||||
if (index < 0 || index > rules.Count - 1)
|
if (index < 0 || index > rules.Count - 1)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
@@ -2016,11 +2026,15 @@ public static class ConfigHandler
|
|||||||
var downloadHandle = new DownloadService();
|
var downloadHandle = new DownloadService();
|
||||||
var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, "");
|
var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, "");
|
||||||
if (templateContent.IsNullOrEmpty())
|
if (templateContent.IsNullOrEmpty())
|
||||||
|
{
|
||||||
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
|
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
|
||||||
|
}
|
||||||
|
|
||||||
var template = JsonUtils.Deserialize<RoutingTemplate>(templateContent);
|
var template = JsonUtils.Deserialize<RoutingTemplate>(templateContent);
|
||||||
if (template == null)
|
if (template == null)
|
||||||
|
{
|
||||||
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
|
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
|
||||||
|
}
|
||||||
|
|
||||||
var items = await AppManager.Instance.RoutingItems();
|
var items = await AppManager.Instance.RoutingItems();
|
||||||
var maxSort = items.Count;
|
var maxSort = items.Count;
|
||||||
@@ -2033,14 +2047,18 @@ public static class ConfigHandler
|
|||||||
var item = template.RoutingItems[i];
|
var item = template.RoutingItems[i];
|
||||||
|
|
||||||
if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty())
|
if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty())
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var ruleSetsString = !item.RuleSet.IsNullOrEmpty()
|
var ruleSetsString = !item.RuleSet.IsNullOrEmpty()
|
||||||
? item.RuleSet
|
? item.RuleSet
|
||||||
: await downloadHandle.TryDownloadString(item.Url, true, "");
|
: await downloadHandle.TryDownloadString(item.Url, true, "");
|
||||||
|
|
||||||
if (ruleSetsString.IsNullOrEmpty())
|
if (ruleSetsString.IsNullOrEmpty())
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
item.Remarks = $"{template.Version}-{item.Remarks}";
|
item.Remarks = $"{template.Version}-{item.Remarks}";
|
||||||
item.Enabled = true;
|
item.Enabled = true;
|
||||||
@@ -2068,7 +2086,7 @@ public static class ConfigHandler
|
|||||||
/// <returns>0 if successful</returns>
|
/// <returns>0 if successful</returns>
|
||||||
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
|
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
|
||||||
{
|
{
|
||||||
var ver = "V3-";
|
var ver = "V4-";
|
||||||
var items = await AppManager.Instance.RoutingItems();
|
var items = await AppManager.Instance.RoutingItems();
|
||||||
|
|
||||||
//TODO Temporary code to be removed later
|
//TODO Temporary code to be removed later
|
||||||
@@ -2079,7 +2097,7 @@ public static class ConfigHandler
|
|||||||
items = await AppManager.Instance.RoutingItems();
|
items = await AppManager.Instance.RoutingItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!blImportAdvancedRules && items.Count > 0)
|
if (!blImportAdvancedRules && items.Count(u => u.Remarks.StartsWith(ver)) > 0)
|
||||||
{
|
{
|
||||||
//migrate
|
//migrate
|
||||||
//TODO Temporary code to be removed later
|
//TODO Temporary code to be removed later
|
||||||
@@ -2236,17 +2254,25 @@ public static class ConfigHandler
|
|||||||
var downloadHandle = new DownloadService();
|
var downloadHandle = new DownloadService();
|
||||||
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
|
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
|
||||||
if (templateContent.IsNullOrEmpty())
|
if (templateContent.IsNullOrEmpty())
|
||||||
|
{
|
||||||
return currentItem;
|
return currentItem;
|
||||||
|
}
|
||||||
|
|
||||||
var template = JsonUtils.Deserialize<DNSItem>(templateContent);
|
var template = JsonUtils.Deserialize<DNSItem>(templateContent);
|
||||||
if (template == null)
|
if (template == null)
|
||||||
|
{
|
||||||
return currentItem;
|
return currentItem;
|
||||||
|
}
|
||||||
|
|
||||||
if (!template.NormalDNS.IsNullOrEmpty())
|
if (!template.NormalDNS.IsNullOrEmpty())
|
||||||
|
{
|
||||||
template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, "");
|
template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, "");
|
||||||
|
}
|
||||||
|
|
||||||
if (!template.TunDNS.IsNullOrEmpty())
|
if (!template.TunDNS.IsNullOrEmpty())
|
||||||
|
{
|
||||||
template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, "");
|
template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, "");
|
||||||
|
}
|
||||||
|
|
||||||
template.Id = currentItem.Id;
|
template.Id = currentItem.Id;
|
||||||
template.Enabled = currentItem.Enabled;
|
template.Enabled = currentItem.Enabled;
|
||||||
@@ -2280,10 +2306,16 @@ public static class ConfigHandler
|
|||||||
var downloadHandle = new DownloadService();
|
var downloadHandle = new DownloadService();
|
||||||
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
|
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
|
||||||
if (templateContent.IsNullOrEmpty())
|
if (templateContent.IsNullOrEmpty())
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var template = JsonUtils.Deserialize<SimpleDNSItem>(templateContent);
|
var template = JsonUtils.Deserialize<SimpleDNSItem>(templateContent);
|
||||||
if (template == null)
|
if (template == null)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ public static class ConnectionHandler
|
|||||||
|
|
||||||
public static async Task<string> RunAvailabilityCheck()
|
public static async Task<string> RunAvailabilityCheck()
|
||||||
{
|
{
|
||||||
var time = await GetRealPingTime();
|
var time = await GetRealPingTimeInfo();
|
||||||
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
|
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
|
||||||
|
|
||||||
return string.Format(ResUI.TestMeOutput, time, ip);
|
return string.Format(ResUI.TestMeOutput, time, ip);
|
||||||
@@ -39,7 +39,7 @@ public static class ConnectionHandler
|
|||||||
return $"({country ?? "unknown"}) {ip}";
|
return $"({country ?? "unknown"}) {ip}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<int> GetRealPingTime()
|
private static async Task<int> GetRealPingTimeInfo()
|
||||||
{
|
{
|
||||||
var responseTime = -1;
|
var responseTime = -1;
|
||||||
try
|
try
|
||||||
@@ -50,7 +50,7 @@ public static class ConnectionHandler
|
|||||||
|
|
||||||
for (var i = 0; i < 2; i++)
|
for (var i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
responseTime = await HttpClientHelper.Instance.GetRealPingTime(url, webProxy, 10);
|
responseTime = await GetRealPingTime(url, webProxy, 10);
|
||||||
if (responseTime > 0)
|
if (responseTime > 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -65,4 +65,34 @@ public static class ConnectionHandler
|
|||||||
}
|
}
|
||||||
return responseTime;
|
return responseTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
|
||||||
|
{
|
||||||
|
var responseTime = -1;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var cts = new CancellationTokenSource();
|
||||||
|
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
|
||||||
|
using var client = new HttpClient(new SocketsHttpHandler()
|
||||||
|
{
|
||||||
|
Proxy = webProxy,
|
||||||
|
UseProxy = webProxy != null
|
||||||
|
});
|
||||||
|
|
||||||
|
List<int> oneTime = new();
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
var timer = Stopwatch.StartNew();
|
||||||
|
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
|
||||||
|
timer.Stop();
|
||||||
|
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,27 +7,27 @@ public static class CoreConfigHandler
|
|||||||
{
|
{
|
||||||
private static readonly string _tag = "CoreConfigHandler";
|
private static readonly string _tag = "CoreConfigHandler";
|
||||||
|
|
||||||
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
|
public static async Task<RetResult> GenerateClientConfig(CoreConfigContext context, string? fileName)
|
||||||
{
|
{
|
||||||
var config = AppManager.Instance.Config;
|
var config = AppManager.Instance.Config;
|
||||||
var result = new RetResult();
|
var result = new RetResult();
|
||||||
|
var node = context.Node;
|
||||||
|
|
||||||
if (node.ConfigType == EConfigType.Custom)
|
if (node.ConfigType == EConfigType.Custom)
|
||||||
{
|
{
|
||||||
result = node.CoreType switch
|
result = node.CoreType switch
|
||||||
{
|
{
|
||||||
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
|
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
|
||||||
ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName),
|
|
||||||
_ => await GenerateClientCustomConfig(node, fileName)
|
_ => await GenerateClientCustomConfig(node, fileName)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||||
{
|
{
|
||||||
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
|
result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node);
|
result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
}
|
}
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
@@ -58,7 +58,7 @@ public static class CoreConfigHandler
|
|||||||
File.Delete(fileName);
|
File.Delete(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
string addressFileName = node.Address;
|
var addressFileName = node.Address;
|
||||||
if (!File.Exists(addressFileName))
|
if (!File.Exists(addressFileName))
|
||||||
{
|
{
|
||||||
addressFileName = Utils.GetConfigPath(addressFileName);
|
addressFileName = Utils.GetConfigPath(addressFileName);
|
||||||
@@ -93,13 +93,26 @@ public static class CoreConfigHandler
|
|||||||
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType)
|
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType)
|
||||||
{
|
{
|
||||||
var result = new RetResult();
|
var result = new RetResult();
|
||||||
|
var context = await BuildCoreConfigContext(config, new());
|
||||||
|
var ids = selecteds.Where(serverTestItem => !serverTestItem.IndexId.IsNullOrEmpty())
|
||||||
|
.Select(serverTestItem => serverTestItem.IndexId);
|
||||||
|
var nodes = await AppManager.Instance.GetProfileItemsByIndexIds(ids);
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
var actNode = await FillNodeContext(context, node, true);
|
||||||
|
if (node.IndexId == actNode.IndexId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
context.ServerTestItemMap[node.IndexId] = actNode.IndexId;
|
||||||
|
}
|
||||||
if (coreType == ECoreType.sing_box)
|
if (coreType == ECoreType.sing_box)
|
||||||
{
|
{
|
||||||
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds);
|
result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(selecteds);
|
||||||
}
|
}
|
||||||
else if (coreType == ECoreType.Xray)
|
else if (coreType == ECoreType.Xray)
|
||||||
{
|
{
|
||||||
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds);
|
result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(selecteds);
|
||||||
}
|
}
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
@@ -109,20 +122,21 @@ public static class CoreConfigHandler
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
|
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, CoreConfigContext context, ServerTestItem testItem, string fileName)
|
||||||
{
|
{
|
||||||
var result = new RetResult();
|
var result = new RetResult();
|
||||||
|
var node = context.Node;
|
||||||
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||||
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
|
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
|
||||||
testItem.Port = port;
|
testItem.Port = port;
|
||||||
|
|
||||||
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||||
{
|
{
|
||||||
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
|
result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port);
|
result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(port);
|
||||||
}
|
}
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
@@ -132,4 +146,128 @@ public static class CoreConfigHandler
|
|||||||
await File.WriteAllTextAsync(fileName, result.Data.ToString());
|
await File.WriteAllTextAsync(fileName, result.Data.ToString());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<CoreConfigContext> BuildCoreConfigContext(Config config, ProfileItem node)
|
||||||
|
{
|
||||||
|
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray;
|
||||||
|
var context = new CoreConfigContext()
|
||||||
|
{
|
||||||
|
Node = node,
|
||||||
|
AllProxiesMap = [],
|
||||||
|
AppConfig = config,
|
||||||
|
FullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(coreType),
|
||||||
|
IsTunEnabled = config.TunModeItem.EnableTun,
|
||||||
|
SimpleDnsItem = config.SimpleDNSItem,
|
||||||
|
ProtectDomainList = [],
|
||||||
|
TunProtectSsPort = 0,
|
||||||
|
ProxyRelaySsPort = 0,
|
||||||
|
RawDnsItem = await AppManager.Instance.GetDNSItem(coreType),
|
||||||
|
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
|
||||||
|
};
|
||||||
|
context = context with
|
||||||
|
{
|
||||||
|
Node = await FillNodeContext(context, node)
|
||||||
|
};
|
||||||
|
if (!(context.RoutingItem?.RuleSet.IsNullOrEmpty() ?? true))
|
||||||
|
{
|
||||||
|
var rules = JsonUtils.Deserialize<List<RulesItem>>(context.RoutingItem?.RuleSet);
|
||||||
|
foreach (var ruleItem in rules.Where(ruleItem => !Global.OutboundTags.Contains(ruleItem.OutboundTag)))
|
||||||
|
{
|
||||||
|
var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag);
|
||||||
|
if (ruleOutboundNode != null)
|
||||||
|
{
|
||||||
|
var ruleOutboundNodeAct = await FillNodeContext(context, ruleOutboundNode, false);
|
||||||
|
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = ruleOutboundNodeAct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<ProfileItem> FillNodeContext(CoreConfigContext context, ProfileItem node, bool includeSubChain = true)
|
||||||
|
{
|
||||||
|
if (node.IndexId.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
var newItems = new List<ProfileItem> { node };
|
||||||
|
if (node.ConfigType.IsGroupType())
|
||||||
|
{
|
||||||
|
var (groupChildList, _) = await GroupProfileManager.GetChildProfileItems(node);
|
||||||
|
foreach (var childItem in groupChildList.Where(childItem => !context.AllProxiesMap.ContainsKey(childItem.IndexId)))
|
||||||
|
{
|
||||||
|
await FillNodeContext(context, childItem, false);
|
||||||
|
}
|
||||||
|
node.SetProtocolExtra(node.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
ChildItems = Utils.List2String(groupChildList.Select(n => n.IndexId).ToList()),
|
||||||
|
});
|
||||||
|
newItems.AddRange(groupChildList);
|
||||||
|
}
|
||||||
|
context.AllProxiesMap[node.IndexId] = node;
|
||||||
|
|
||||||
|
foreach (var item in newItems)
|
||||||
|
{
|
||||||
|
var address = item.Address;
|
||||||
|
if (Utils.IsDomain(address))
|
||||||
|
{
|
||||||
|
context.ProtectDomainList.Add(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.EchConfigList.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var echQuerySni = item.Sni;
|
||||||
|
if (item.StreamSecurity == Global.StreamSecurity
|
||||||
|
&& item.EchConfigList?.Contains("://") == true)
|
||||||
|
{
|
||||||
|
var idx = item.EchConfigList.IndexOf('+');
|
||||||
|
echQuerySni = idx > 0 ? item.EchConfigList[..idx] : item.Sni;
|
||||||
|
}
|
||||||
|
if (!Utils.IsDomain(echQuerySni))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
context.ProtectDomainList.Add(echQuerySni);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!includeSubChain || node.Subid.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||||
|
if (subItem == null)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
|
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
|
if (prevNode is null && nextNode is null)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prevNodeAct = prevNode is null ? null : await FillNodeContext(context, prevNode, false);
|
||||||
|
var nextNodeAct = nextNode is null ? null : await FillNodeContext(context, nextNode, false);
|
||||||
|
|
||||||
|
// Build new proxy chain node
|
||||||
|
var chainNode = new ProfileItem()
|
||||||
|
{
|
||||||
|
IndexId = $"inner-{Utils.GetGuid(false)}",
|
||||||
|
ConfigType = EConfigType.ProxyChain,
|
||||||
|
CoreType = node.CoreType ?? ECoreType.Xray,
|
||||||
|
};
|
||||||
|
List<string?> childItems = [prevNodeAct?.IndexId, node.IndexId, nextNodeAct?.IndexId];
|
||||||
|
var chainExtraItem = chainNode.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
GroupType = chainNode.ConfigType.ToString(),
|
||||||
|
ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())),
|
||||||
|
};
|
||||||
|
chainNode.SetProtocolExtra(chainExtraItem);
|
||||||
|
context.AllProxiesMap[chainNode.IndexId] = chainNode;
|
||||||
|
return chainNode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ public class AnytlsFmt : BaseFmt
|
|||||||
Port = parsedUrl.Port,
|
Port = parsedUrl.Port,
|
||||||
};
|
};
|
||||||
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||||
item.Id = rawUserInfo;
|
item.Password = rawUserInfo;
|
||||||
|
|
||||||
var query = Utils.ParseQueryString(parsedUrl.Query);
|
var query = Utils.ParseQueryString(parsedUrl.Query);
|
||||||
_ = ResolveStdTransport(query, ref item);
|
ResolveUriQuery(query, ref item);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,9 @@ public class AnytlsFmt : BaseFmt
|
|||||||
{
|
{
|
||||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
}
|
}
|
||||||
var pw = item.Id;
|
var pw = item.Password;
|
||||||
var dicQuery = new Dictionary<string, string>();
|
var dicQuery = new Dictionary<string, string>();
|
||||||
_ = GetStdTransport(item, Global.None, ref dicQuery);
|
ToUriQuery(item, Global.None, ref dicQuery);
|
||||||
|
|
||||||
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
|
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ namespace ServiceLib.Handler.Fmt;
|
|||||||
|
|
||||||
public class BaseFmt
|
public class BaseFmt
|
||||||
{
|
{
|
||||||
|
private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure" };
|
||||||
|
|
||||||
protected static string GetIpv6(string address)
|
protected static string GetIpv6(string address)
|
||||||
{
|
{
|
||||||
if (Utils.IsIpv6(address))
|
if (Utils.IsIpv6(address))
|
||||||
@@ -17,13 +19,8 @@ public class BaseFmt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
|
protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
|
||||||
{
|
{
|
||||||
if (item.Flow.IsNotEmpty())
|
|
||||||
{
|
|
||||||
dicQuery.Add("flow", item.Flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.StreamSecurity.IsNotEmpty())
|
if (item.StreamSecurity.IsNotEmpty())
|
||||||
{
|
{
|
||||||
dicQuery.Add("security", item.StreamSecurity);
|
dicQuery.Add("security", item.StreamSecurity);
|
||||||
@@ -37,11 +34,7 @@ public class BaseFmt
|
|||||||
}
|
}
|
||||||
if (item.Sni.IsNotEmpty())
|
if (item.Sni.IsNotEmpty())
|
||||||
{
|
{
|
||||||
dicQuery.Add("sni", item.Sni);
|
dicQuery.Add("sni", Utils.UrlEncode(item.Sni));
|
||||||
}
|
|
||||||
if (item.Alpn.IsNotEmpty())
|
|
||||||
{
|
|
||||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
|
||||||
}
|
}
|
||||||
if (item.Fingerprint.IsNotEmpty())
|
if (item.Fingerprint.IsNotEmpty())
|
||||||
{
|
{
|
||||||
@@ -63,9 +56,35 @@ public class BaseFmt
|
|||||||
{
|
{
|
||||||
dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify));
|
dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify));
|
||||||
}
|
}
|
||||||
if (item.AllowInsecure.Equals("true"))
|
|
||||||
|
if (item.StreamSecurity.Equals(Global.StreamSecurity))
|
||||||
{
|
{
|
||||||
dicQuery.Add("allowInsecure", "1");
|
if (item.Alpn.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||||
|
}
|
||||||
|
ToUriQueryAllowInsecure(item, ref dicQuery);
|
||||||
|
}
|
||||||
|
if (item.EchConfigList.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
|
||||||
|
}
|
||||||
|
if (item.CertSha.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha));
|
||||||
|
}
|
||||||
|
if (item.Finalmask.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var node = JsonUtils.ParseJson(item.Finalmask);
|
||||||
|
var finalmask = node != null
|
||||||
|
? JsonUtils.Serialize(node, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = false,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
})
|
||||||
|
: item.Finalmask;
|
||||||
|
dicQuery.Add("fm", Utils.UrlEncode(finalmask));
|
||||||
}
|
}
|
||||||
|
|
||||||
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
|
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
|
||||||
@@ -115,7 +134,16 @@ public class BaseFmt
|
|||||||
}
|
}
|
||||||
if (item.Extra.IsNotEmpty())
|
if (item.Extra.IsNotEmpty())
|
||||||
{
|
{
|
||||||
dicQuery.Add("extra", Utils.UrlEncode(item.Extra));
|
var node = JsonUtils.ParseJson(item.Extra);
|
||||||
|
var extra = node != null
|
||||||
|
? JsonUtils.Serialize(node, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = false,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
})
|
||||||
|
: item.Extra;
|
||||||
|
dicQuery.Add("extra", Utils.UrlEncode(extra));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -153,9 +181,41 @@ public class BaseFmt
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item)
|
protected static int ToUriQueryLite(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
||||||
|
{
|
||||||
|
if (item.Sni.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("sni", Utils.UrlEncode(item.Sni));
|
||||||
|
}
|
||||||
|
if (item.Alpn.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||||
|
}
|
||||||
|
|
||||||
|
ToUriQueryAllowInsecure(item, ref dicQuery);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ToUriQueryAllowInsecure(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
||||||
|
{
|
||||||
|
if (item.AllowInsecure.Equals(Global.AllowInsecure.First()))
|
||||||
|
{
|
||||||
|
// Add two for compatibility
|
||||||
|
dicQuery.Add("insecure", "1");
|
||||||
|
dicQuery.Add("allowInsecure", "1");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dicQuery.Add("insecure", "0");
|
||||||
|
dicQuery.Add("allowInsecure", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item)
|
||||||
{
|
{
|
||||||
item.Flow = GetQueryValue(query, "flow");
|
|
||||||
item.StreamSecurity = GetQueryValue(query, "security");
|
item.StreamSecurity = GetQueryValue(query, "security");
|
||||||
item.Sni = GetQueryValue(query, "sni");
|
item.Sni = GetQueryValue(query, "sni");
|
||||||
item.Alpn = GetQueryDecoded(query, "alpn");
|
item.Alpn = GetQueryDecoded(query, "alpn");
|
||||||
@@ -164,7 +224,39 @@ public class BaseFmt
|
|||||||
item.ShortId = GetQueryDecoded(query, "sid");
|
item.ShortId = GetQueryDecoded(query, "sid");
|
||||||
item.SpiderX = GetQueryDecoded(query, "spx");
|
item.SpiderX = GetQueryDecoded(query, "spx");
|
||||||
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
|
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
|
||||||
item.AllowInsecure = new[] { "allowInsecure", "allow_insecure", "insecure" }.Any(k => (query[k] ?? "") == "1") ? "true" : "";
|
item.EchConfigList = GetQueryDecoded(query, "ech");
|
||||||
|
item.CertSha = GetQueryDecoded(query, "pcs");
|
||||||
|
|
||||||
|
var finalmaskDecoded = GetQueryDecoded(query, "fm");
|
||||||
|
if (finalmaskDecoded.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var node = JsonUtils.ParseJson(finalmaskDecoded);
|
||||||
|
item.Finalmask = node != null
|
||||||
|
? JsonUtils.Serialize(node, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
})
|
||||||
|
: finalmaskDecoded;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.Finalmask = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
|
||||||
|
{
|
||||||
|
item.AllowInsecure = Global.AllowInsecure.First();
|
||||||
|
}
|
||||||
|
else if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "0"))
|
||||||
|
{
|
||||||
|
item.AllowInsecure = Global.AllowInsecure.Skip(1).First();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.AllowInsecure = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
item.Network = GetQueryValue(query, "type", nameof(ETransport.tcp));
|
item.Network = GetQueryValue(query, "type", nameof(ETransport.tcp));
|
||||||
switch (item.Network)
|
switch (item.Network)
|
||||||
@@ -189,7 +281,21 @@ public class BaseFmt
|
|||||||
item.RequestHost = GetQueryDecoded(query, "host");
|
item.RequestHost = GetQueryDecoded(query, "host");
|
||||||
item.Path = GetQueryDecoded(query, "path", "/");
|
item.Path = GetQueryDecoded(query, "path", "/");
|
||||||
item.HeaderType = GetQueryDecoded(query, "mode");
|
item.HeaderType = GetQueryDecoded(query, "mode");
|
||||||
item.Extra = GetQueryDecoded(query, "extra");
|
var extraDecoded = GetQueryDecoded(query, "extra");
|
||||||
|
if (extraDecoded.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var node = JsonUtils.ParseJson(extraDecoded);
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
extraDecoded = JsonUtils.Serialize(node, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.Extra = extraDecoded;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(ETransport.http):
|
case nameof(ETransport.http):
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class FmtHandler
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string str = config.TrimEx();
|
var str = config.TrimEx();
|
||||||
if (str.IsNullOrEmpty())
|
if (str.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
msg = ResUI.FailedReadConfiguration;
|
msg = ResUI.FailedReadConfiguration;
|
||||||
|
|||||||
@@ -12,19 +12,26 @@ public class Hysteria2Fmt : BaseFmt
|
|||||||
|
|
||||||
var url = Utils.TryUri(str);
|
var url = Utils.TryUri(str);
|
||||||
if (url == null)
|
if (url == null)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
item.Address = url.IdnHost;
|
item.Address = url.IdnHost;
|
||||||
item.Port = url.Port;
|
item.Port = url.Port;
|
||||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
item.Password = Utils.UrlDecode(url.UserInfo);
|
||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
ResolveStdTransport(query, ref item);
|
ResolveUriQuery(query, ref item);
|
||||||
item.Path = GetQueryDecoded(query, "obfs-password");
|
if (item.CertSha.IsNullOrEmpty())
|
||||||
item.AllowInsecure = GetQueryValue(query, "insecure") == "1" ? "true" : "false";
|
{
|
||||||
|
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||||
item.Ports = GetQueryDecoded(query, "mport");
|
}
|
||||||
|
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
Ports = GetQueryDecoded(query, "mport"),
|
||||||
|
SalamanderPass = GetQueryDecoded(query, "obfs-password"),
|
||||||
|
});
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -32,35 +39,42 @@ public class Hysteria2Fmt : BaseFmt
|
|||||||
public static string? ToUri(ProfileItem? item)
|
public static string? ToUri(ProfileItem? item)
|
||||||
{
|
{
|
||||||
if (item == null)
|
if (item == null)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
string url = string.Empty;
|
}
|
||||||
|
|
||||||
string remark = string.Empty;
|
var url = string.Empty;
|
||||||
|
|
||||||
|
var remark = string.Empty;
|
||||||
if (item.Remarks.IsNotEmpty())
|
if (item.Remarks.IsNotEmpty())
|
||||||
{
|
{
|
||||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
}
|
}
|
||||||
var dicQuery = new Dictionary<string, string>();
|
var dicQuery = new Dictionary<string, string>();
|
||||||
if (item.Sni.IsNotEmpty())
|
ToUriQueryLite(item, ref dicQuery);
|
||||||
{
|
var protocolExtraItem = item.GetProtocolExtra();
|
||||||
dicQuery.Add("sni", item.Sni);
|
|
||||||
}
|
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
||||||
if (item.Alpn.IsNotEmpty())
|
|
||||||
{
|
|
||||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
|
||||||
}
|
|
||||||
if (item.Path.IsNotEmpty())
|
|
||||||
{
|
{
|
||||||
dicQuery.Add("obfs", "salamander");
|
dicQuery.Add("obfs", "salamander");
|
||||||
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path));
|
dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass));
|
||||||
}
|
}
|
||||||
dicQuery.Add("insecure", item.AllowInsecure.ToLower() == "true" ? "1" : "0");
|
if (!protocolExtraItem.Ports.IsNullOrEmpty())
|
||||||
if (item.Ports.IsNotEmpty())
|
|
||||||
{
|
{
|
||||||
dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-')));
|
dicQuery.Add("mport", Utils.UrlEncode(protocolExtraItem.Ports.Replace(':', '-')));
|
||||||
|
}
|
||||||
|
if (!item.CertSha.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var sha = item.CertSha;
|
||||||
|
var idx = sha.IndexOf(',');
|
||||||
|
if (idx > 0)
|
||||||
|
{
|
||||||
|
sha = sha[..idx];
|
||||||
|
}
|
||||||
|
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark);
|
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Password, dicQuery, remark);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ProfileItem? ResolveFull2(string strData, string? subRemarks)
|
public static ProfileItem? ResolveFull2(string strData, string? subRemarks)
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ public class ShadowsocksFmt : BaseFmt
|
|||||||
{
|
{
|
||||||
return null;
|
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.GetProtocolExtra().SsMethod.IsNullOrEmpty() || item.Password.Length == 0)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -40,8 +41,69 @@ public class ShadowsocksFmt : BaseFmt
|
|||||||
// item.port);
|
// item.port);
|
||||||
//url = Utile.Base64Encode(url);
|
//url = Utile.Base64Encode(url);
|
||||||
//new Sip002
|
//new Sip002
|
||||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
|
var pw = Utils.Base64Encode($"{item.GetProtocolExtra().SsMethod}:{item.Password}", true);
|
||||||
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark);
|
|
||||||
|
// plugin
|
||||||
|
var plugin = string.Empty;
|
||||||
|
var pluginArgs = string.Empty;
|
||||||
|
|
||||||
|
if (item.Network == nameof(ETransport.tcp) && item.HeaderType == Global.TcpHeaderHttp)
|
||||||
|
{
|
||||||
|
plugin = "obfs-local";
|
||||||
|
pluginArgs = $"obfs=http;obfs-host={item.RequestHost};";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (item.Network == nameof(ETransport.ws))
|
||||||
|
{
|
||||||
|
pluginArgs += "mode=websocket;";
|
||||||
|
pluginArgs += $"host={item.RequestHost};";
|
||||||
|
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||||
|
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||||
|
var path = item.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
|
||||||
|
pluginArgs += $"path={path};";
|
||||||
|
}
|
||||||
|
else if (item.Network == nameof(ETransport.quic))
|
||||||
|
{
|
||||||
|
pluginArgs += "mode=quic;";
|
||||||
|
}
|
||||||
|
if (item.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
pluginArgs += "tls;";
|
||||||
|
var certs = CertPemManager.ParsePemChain(item.Cert);
|
||||||
|
if (certs.Count > 0)
|
||||||
|
{
|
||||||
|
var cert = certs.First();
|
||||||
|
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
|
||||||
|
const string endMarker = "\n-----END CERTIFICATE-----";
|
||||||
|
|
||||||
|
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
|
||||||
|
|
||||||
|
base64Content = base64Content.Replace("=", "\\=");
|
||||||
|
|
||||||
|
pluginArgs += $"certRaw={base64Content};";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pluginArgs.Length > 0)
|
||||||
|
{
|
||||||
|
plugin = "v2ray-plugin";
|
||||||
|
pluginArgs += "mux=0;";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dicQuery = new Dictionary<string, string>();
|
||||||
|
if (plugin.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var pluginStr = plugin + ";" + pluginArgs;
|
||||||
|
// pluginStr remove last ';' and url encode
|
||||||
|
if (pluginStr.EndsWith(';'))
|
||||||
|
{
|
||||||
|
pluginStr = pluginStr[..^1];
|
||||||
|
}
|
||||||
|
dicQuery["plugin"] = Utils.UrlEncode(pluginStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, dicQuery, remark);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
@@ -75,8 +137,8 @@ public class ShadowsocksFmt : BaseFmt
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
item.Security = details.Groups["method"].Value;
|
item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = details.Groups["method"].Value });
|
||||||
item.Id = details.Groups["password"].Value;
|
item.Password = details.Groups["password"].Value;
|
||||||
item.Address = details.Groups["hostname"].Value;
|
item.Address = details.Groups["hostname"].Value;
|
||||||
item.Port = details.Groups["port"].Value.ToInt();
|
item.Port = details.Groups["port"].Value.ToInt();
|
||||||
return item;
|
return item;
|
||||||
@@ -105,8 +167,8 @@ public class ShadowsocksFmt : BaseFmt
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
item.Security = userInfoParts.First();
|
item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
|
||||||
item.Id = Utils.UrlDecode(userInfoParts.Last());
|
item.Password = Utils.UrlDecode(userInfoParts.Last());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -117,28 +179,103 @@ public class ShadowsocksFmt : BaseFmt
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
item.Security = userInfoParts.First();
|
item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
|
||||||
item.Id = userInfoParts.Last();
|
item.Password = userInfoParts.Last();
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
|
var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
|
||||||
if (queryParameters["plugin"] != null)
|
if (queryParameters["plugin"] != null)
|
||||||
{
|
{
|
||||||
//obfs-host exists
|
var pluginStr = queryParameters["plugin"];
|
||||||
var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host"));
|
var pluginParts = pluginStr.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (queryParameters["plugin"].Contains("obfs=http") && obfsHost.IsNotEmpty())
|
|
||||||
|
if (pluginParts.Length == 0)
|
||||||
{
|
{
|
||||||
obfsHost = obfsHost?.Replace("obfs-host=", "");
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginName = pluginParts[0];
|
||||||
|
|
||||||
|
// A typo in https://github.com/shadowsocks/shadowsocks-org/blob/6b1c064db4129de99c516294960e731934841c94/docs/doc/sip002.md?plain=1#L15
|
||||||
|
// "simple-obfs" should be "obfs-local"
|
||||||
|
if (pluginName == "simple-obfs")
|
||||||
|
{
|
||||||
|
pluginName = "obfs-local";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse obfs-local plugin
|
||||||
|
if (pluginName == "obfs-local")
|
||||||
|
{
|
||||||
|
var obfsMode = pluginParts.FirstOrDefault(t => t.StartsWith("obfs="));
|
||||||
|
var obfsHost = pluginParts.FirstOrDefault(t => t.StartsWith("obfs-host="));
|
||||||
|
|
||||||
|
if ((!obfsMode.IsNullOrEmpty()) && obfsMode.Contains("obfs=http") && obfsHost.IsNotEmpty())
|
||||||
|
{
|
||||||
|
obfsHost = obfsHost.Replace("obfs-host=", "");
|
||||||
item.Network = Global.DefaultNetwork;
|
item.Network = Global.DefaultNetwork;
|
||||||
item.HeaderType = Global.TcpHeaderHttp;
|
item.HeaderType = Global.TcpHeaderHttp;
|
||||||
item.RequestHost = obfsHost ?? "";
|
item.RequestHost = obfsHost;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
// Parse v2ray-plugin
|
||||||
|
else if (pluginName == "v2ray-plugin")
|
||||||
|
{
|
||||||
|
var mode = pluginParts.FirstOrDefault(t => t.StartsWith("mode="), "websocket");
|
||||||
|
var host = pluginParts.FirstOrDefault(t => t.StartsWith("host="));
|
||||||
|
var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
|
||||||
|
var hasTls = pluginParts.Any(t => t == "tls");
|
||||||
|
var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
|
||||||
|
var mux = pluginParts.FirstOrDefault(t => t.StartsWith("mux="));
|
||||||
|
|
||||||
|
var modeValue = mode.Replace("mode=", "");
|
||||||
|
if (modeValue == "websocket")
|
||||||
|
{
|
||||||
|
item.Network = nameof(ETransport.ws);
|
||||||
|
if (!host.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
item.RequestHost = host.Replace("host=", "");
|
||||||
|
item.Sni = item.RequestHost;
|
||||||
|
}
|
||||||
|
if (!path.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var pathValue = path.Replace("path=", "");
|
||||||
|
pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
|
||||||
|
item.Path = pathValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (modeValue == "quic")
|
||||||
|
{
|
||||||
|
item.Network = nameof(ETransport.quic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTls)
|
||||||
|
{
|
||||||
|
item.StreamSecurity = Global.StreamSecurity;
|
||||||
|
|
||||||
|
if (!certRaw.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var certBase64 = certRaw.Replace("certRaw=", "");
|
||||||
|
|
||||||
|
certBase64 = certBase64.Replace("\\=", "=");
|
||||||
|
|
||||||
|
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
|
||||||
|
const string endMarker = "\n-----END CERTIFICATE-----";
|
||||||
|
var certPem = beginMarker + certBase64 + endMarker;
|
||||||
|
item.Cert = certPem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mux.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var muxValue = mux.Replace("mux=", "");
|
||||||
|
var muxCount = muxValue.ToInt();
|
||||||
|
if (muxCount > 0)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,11 +300,11 @@ public class ShadowsocksFmt : BaseFmt
|
|||||||
var ssItem = new ProfileItem()
|
var ssItem = new ProfileItem()
|
||||||
{
|
{
|
||||||
Remarks = it.remarks,
|
Remarks = it.remarks,
|
||||||
Security = it.method,
|
Password = it.password,
|
||||||
Id = it.password,
|
|
||||||
Address = it.server,
|
Address = it.server,
|
||||||
Port = it.server_port.ToInt()
|
Port = it.server_port.ToInt()
|
||||||
};
|
};
|
||||||
|
ssItem.SetProtocolExtra(new ProtocolExtraItem() { SsMethod = it.method });
|
||||||
lst.Add(ssItem);
|
lst.Add(ssItem);
|
||||||
}
|
}
|
||||||
return lst;
|
return lst;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class SocksFmt : BaseFmt
|
|||||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
}
|
}
|
||||||
//new
|
//new
|
||||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
|
var pw = Utils.Base64Encode($"{item.Username}:{item.Password}", true);
|
||||||
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
|
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,18 +45,18 @@ public class SocksFmt : BaseFmt
|
|||||||
};
|
};
|
||||||
result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..];
|
result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..];
|
||||||
//remark
|
//remark
|
||||||
var indexRemark = result.IndexOf("#");
|
var indexRemark = result.IndexOf('#');
|
||||||
if (indexRemark > 0)
|
if (indexRemark > 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1));
|
item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1));
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
result = result[..indexRemark];
|
result = result[..indexRemark];
|
||||||
}
|
}
|
||||||
//part decode
|
//part decode
|
||||||
var indexS = result.IndexOf("@");
|
var indexS = result.IndexOf('@');
|
||||||
if (indexS > 0)
|
if (indexS > 0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -78,9 +78,8 @@ public class SocksFmt : BaseFmt
|
|||||||
}
|
}
|
||||||
item.Address = arr1[1][..indexPort];
|
item.Address = arr1[1][..indexPort];
|
||||||
item.Port = arr1[1][(indexPort + 1)..].ToInt();
|
item.Port = arr1[1][(indexPort + 1)..].ToInt();
|
||||||
item.Security = arr21.First();
|
item.Username = arr21.First();
|
||||||
item.Id = arr21[1];
|
item.Password = arr21[1];
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,15 +97,14 @@ public class SocksFmt : BaseFmt
|
|||||||
Address = parsedUrl.IdnHost,
|
Address = parsedUrl.IdnHost,
|
||||||
Port = parsedUrl.Port,
|
Port = parsedUrl.Port,
|
||||||
};
|
};
|
||||||
|
|
||||||
// parse base64 UserInfo
|
// parse base64 UserInfo
|
||||||
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||||
var userInfo = Utils.Base64Decode(rawUserInfo);
|
var userInfo = Utils.Base64Decode(rawUserInfo);
|
||||||
var userInfoParts = userInfo.Split([':'], 2);
|
var userInfoParts = userInfo.Split([':'], 2);
|
||||||
if (userInfoParts.Length == 2)
|
if (userInfoParts.Length == 2)
|
||||||
{
|
{
|
||||||
item.Security = userInfoParts.First();
|
item.Username = userInfoParts.First();
|
||||||
item.Id = userInfoParts[1];
|
item.Password = userInfoParts[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ public class TrojanFmt : BaseFmt
|
|||||||
item.Address = url.IdnHost;
|
item.Address = url.IdnHost;
|
||||||
item.Port = url.Port;
|
item.Port = url.Port;
|
||||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
item.Password = Utils.UrlDecode(url.UserInfo);
|
||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
_ = ResolveStdTransport(query, ref item);
|
item.SetProtocolExtra(item.GetProtocolExtra() with { Flow = GetQueryValue(query, "flow") });
|
||||||
|
ResolveUriQuery(query, ref item);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -40,8 +41,12 @@ public class TrojanFmt : BaseFmt
|
|||||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
}
|
}
|
||||||
var dicQuery = new Dictionary<string, string>();
|
var dicQuery = new Dictionary<string, string>();
|
||||||
_ = GetStdTransport(item, null, ref dicQuery);
|
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
|
||||||
|
}
|
||||||
|
ToUriQuery(item, null, ref dicQuery);
|
||||||
|
|
||||||
return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark);
|
return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Password, dicQuery, remark);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ public class TuicFmt : BaseFmt
|
|||||||
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
|
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
|
||||||
if (userInfoParts.Length == 2)
|
if (userInfoParts.Length == 2)
|
||||||
{
|
{
|
||||||
item.Id = userInfoParts.First();
|
item.Username = userInfoParts.First();
|
||||||
item.Security = userInfoParts.Last();
|
item.Password = userInfoParts.Last();
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
ResolveStdTransport(query, ref item);
|
ResolveUriQuery(query, ref item);
|
||||||
item.HeaderType = GetQueryValue(query, "congestion_control");
|
item.HeaderType = GetQueryValue(query, "congestion_control");
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
@@ -47,17 +47,12 @@ public class TuicFmt : BaseFmt
|
|||||||
{
|
{
|
||||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dicQuery = new Dictionary<string, string>();
|
var dicQuery = new Dictionary<string, string>();
|
||||||
if (item.Sni.IsNotEmpty())
|
ToUriQueryLite(item, ref dicQuery);
|
||||||
{
|
|
||||||
dicQuery.Add("sni", item.Sni);
|
|
||||||
}
|
|
||||||
if (item.Alpn.IsNotEmpty())
|
|
||||||
{
|
|
||||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
|
||||||
}
|
|
||||||
dicQuery.Add("congestion_control", item.HeaderType);
|
dicQuery.Add("congestion_control", item.HeaderType);
|
||||||
|
|
||||||
return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark);
|
return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Username ?? ""}:{item.Password}", dicQuery, remark);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ public class VLESSFmt : BaseFmt
|
|||||||
ProfileItem item = new()
|
ProfileItem item = new()
|
||||||
{
|
{
|
||||||
ConfigType = EConfigType.VLESS,
|
ConfigType = EConfigType.VLESS,
|
||||||
Security = Global.None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var url = Utils.TryUri(str);
|
var url = Utils.TryUri(str);
|
||||||
@@ -21,12 +20,16 @@ public class VLESSFmt : BaseFmt
|
|||||||
item.Address = url.IdnHost;
|
item.Address = url.IdnHost;
|
||||||
item.Port = url.Port;
|
item.Port = url.Port;
|
||||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
item.Password = Utils.UrlDecode(url.UserInfo);
|
||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
item.Security = GetQueryValue(query, "encryption", Global.None);
|
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
VlessEncryption = GetQueryValue(query, "encryption", Global.None),
|
||||||
|
Flow = GetQueryValue(query, "flow")
|
||||||
|
});
|
||||||
item.StreamSecurity = GetQueryValue(query, "security");
|
item.StreamSecurity = GetQueryValue(query, "security");
|
||||||
_ = ResolveStdTransport(query, ref item);
|
ResolveUriQuery(query, ref item);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -44,16 +47,14 @@ public class VLESSFmt : BaseFmt
|
|||||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
}
|
}
|
||||||
var dicQuery = new Dictionary<string, string>();
|
var dicQuery = new Dictionary<string, string>();
|
||||||
if (item.Security.IsNotEmpty())
|
dicQuery.Add("encryption",
|
||||||
|
!item.GetProtocolExtra().VlessEncryption.IsNullOrEmpty() ? item.GetProtocolExtra().VlessEncryption : Global.None);
|
||||||
|
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
dicQuery.Add("encryption", item.Security);
|
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
|
||||||
}
|
}
|
||||||
else
|
ToUriQuery(item, Global.None, ref dicQuery);
|
||||||
{
|
|
||||||
dicQuery.Add("encryption", Global.None);
|
|
||||||
}
|
|
||||||
_ = GetStdTransport(item, Global.None, ref dicQuery);
|
|
||||||
|
|
||||||
return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark);
|
return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Password, dicQuery, remark);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,15 +23,16 @@ public class VmessFmt : BaseFmt
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var vmessQRCode = new VmessQRCode
|
var vmessQRCode = new VmessQRCode
|
||||||
{
|
{
|
||||||
v = item.ConfigVersion,
|
v = 2,
|
||||||
ps = item.Remarks.TrimEx(),
|
ps = item.Remarks.TrimEx(),
|
||||||
add = item.Address,
|
add = item.Address,
|
||||||
port = item.Port,
|
port = item.Port,
|
||||||
id = item.Id,
|
id = item.Password,
|
||||||
aid = item.AlterId,
|
aid = int.TryParse(item.GetProtocolExtra()?.AlterId, out var result) ? result : 0,
|
||||||
scy = item.Security,
|
scy = item.GetProtocolExtra().VmessSecurity ?? "",
|
||||||
net = item.Network,
|
net = item.Network,
|
||||||
type = item.HeaderType,
|
type = item.HeaderType,
|
||||||
host = item.RequestHost,
|
host = item.RequestHost,
|
||||||
@@ -39,7 +40,8 @@ public class VmessFmt : BaseFmt
|
|||||||
tls = item.StreamSecurity,
|
tls = item.StreamSecurity,
|
||||||
sni = item.Sni,
|
sni = item.Sni,
|
||||||
alpn = item.Alpn,
|
alpn = item.Alpn,
|
||||||
fp = item.Fingerprint
|
fp = item.Fingerprint,
|
||||||
|
insecure = item.AllowInsecure.Equals(Global.AllowInsecure.First()) ? "1" : "0"
|
||||||
};
|
};
|
||||||
|
|
||||||
var url = JsonUtils.Serialize(vmessQRCode);
|
var url = JsonUtils.Serialize(vmessQRCode);
|
||||||
@@ -70,15 +72,16 @@ public class VmessFmt : BaseFmt
|
|||||||
item.Network = Global.DefaultNetwork;
|
item.Network = Global.DefaultNetwork;
|
||||||
item.HeaderType = Global.None;
|
item.HeaderType = Global.None;
|
||||||
|
|
||||||
item.ConfigVersion = vmessQRCode.v;
|
//item.ConfigVersion = vmessQRCode.v;
|
||||||
item.Remarks = Utils.ToString(vmessQRCode.ps);
|
item.Remarks = Utils.ToString(vmessQRCode.ps);
|
||||||
item.Address = Utils.ToString(vmessQRCode.add);
|
item.Address = Utils.ToString(vmessQRCode.add);
|
||||||
item.Port = vmessQRCode.port;
|
item.Port = vmessQRCode.port;
|
||||||
item.Id = Utils.ToString(vmessQRCode.id);
|
item.Password = Utils.ToString(vmessQRCode.id);
|
||||||
item.AlterId = vmessQRCode.aid;
|
item.SetProtocolExtra(new ProtocolExtraItem
|
||||||
item.Security = Utils.ToString(vmessQRCode.scy);
|
{
|
||||||
|
AlterId = vmessQRCode.aid.ToString(),
|
||||||
item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity;
|
VmessSecurity = vmessQRCode.scy.IsNullOrEmpty() ? Global.DefaultSecurity : vmessQRCode.scy,
|
||||||
|
});
|
||||||
if (vmessQRCode.net.IsNotEmpty())
|
if (vmessQRCode.net.IsNotEmpty())
|
||||||
{
|
{
|
||||||
item.Network = vmessQRCode.net;
|
item.Network = vmessQRCode.net;
|
||||||
@@ -94,6 +97,7 @@ public class VmessFmt : BaseFmt
|
|||||||
item.Sni = Utils.ToString(vmessQRCode.sni);
|
item.Sni = Utils.ToString(vmessQRCode.sni);
|
||||||
item.Alpn = Utils.ToString(vmessQRCode.alpn);
|
item.Alpn = Utils.ToString(vmessQRCode.alpn);
|
||||||
item.Fingerprint = Utils.ToString(vmessQRCode.fp);
|
item.Fingerprint = Utils.ToString(vmessQRCode.fp);
|
||||||
|
item.AllowInsecure = vmessQRCode.insecure == "1" ? Global.AllowInsecure.First() : string.Empty;
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -103,7 +107,6 @@ public class VmessFmt : BaseFmt
|
|||||||
var item = new ProfileItem
|
var item = new ProfileItem
|
||||||
{
|
{
|
||||||
ConfigType = EConfigType.VMess,
|
ConfigType = EConfigType.VMess,
|
||||||
Security = "auto"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var url = Utils.TryUri(str);
|
var url = Utils.TryUri(str);
|
||||||
@@ -115,10 +118,15 @@ public class VmessFmt : BaseFmt
|
|||||||
item.Address = url.IdnHost;
|
item.Address = url.IdnHost;
|
||||||
item.Port = url.Port;
|
item.Port = url.Port;
|
||||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
item.Password = Utils.UrlDecode(url.UserInfo);
|
||||||
|
|
||||||
|
item.SetProtocolExtra(new ProtocolExtraItem
|
||||||
|
{
|
||||||
|
VmessSecurity = "auto",
|
||||||
|
});
|
||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
ResolveStdTransport(query, ref item);
|
ResolveUriQuery(query, ref item);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,17 @@ public class WireguardFmt : BaseFmt
|
|||||||
item.Address = url.IdnHost;
|
item.Address = url.IdnHost;
|
||||||
item.Port = url.Port;
|
item.Port = url.Port;
|
||||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
item.Password = Utils.UrlDecode(url.UserInfo);
|
||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
|
|
||||||
item.PublicKey = GetQueryDecoded(query, "publickey");
|
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||||
item.Path = GetQueryDecoded(query, "reserved");
|
{
|
||||||
item.RequestHost = GetQueryDecoded(query, "address");
|
WgPublicKey = GetQueryDecoded(query, "publickey"),
|
||||||
item.ShortId = GetQueryDecoded(query, "mtu");
|
WgReserved = GetQueryDecoded(query, "reserved"),
|
||||||
|
WgInterfaceAddress = GetQueryDecoded(query, "address"),
|
||||||
|
WgMtu = int.TryParse(GetQueryDecoded(query, "mtu"), out var mtuVal) ? mtuVal : 1280,
|
||||||
|
});
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -46,22 +49,19 @@ public class WireguardFmt : BaseFmt
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dicQuery = new Dictionary<string, string>();
|
var dicQuery = new Dictionary<string, string>();
|
||||||
if (item.PublicKey.IsNotEmpty())
|
if (!item.GetProtocolExtra().WgPublicKey.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
dicQuery.Add("publickey", Utils.UrlEncode(item.PublicKey));
|
dicQuery.Add("publickey", Utils.UrlEncode(item.GetProtocolExtra().WgPublicKey));
|
||||||
}
|
}
|
||||||
if (item.Path.IsNotEmpty())
|
if (!item.GetProtocolExtra().WgReserved.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
dicQuery.Add("reserved", Utils.UrlEncode(item.Path));
|
dicQuery.Add("reserved", Utils.UrlEncode(item.GetProtocolExtra().WgReserved));
|
||||||
}
|
}
|
||||||
if (item.RequestHost.IsNotEmpty())
|
if (!item.GetProtocolExtra().WgInterfaceAddress.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
dicQuery.Add("address", Utils.UrlEncode(item.RequestHost));
|
dicQuery.Add("address", Utils.UrlEncode(item.GetProtocolExtra().WgInterfaceAddress));
|
||||||
}
|
}
|
||||||
if (item.ShortId.IsNotEmpty())
|
dicQuery.Add("mtu", Utils.UrlEncode(item.GetProtocolExtra().WgMtu > 0 ? item.GetProtocolExtra().WgMtu.ToString() : "1280"));
|
||||||
{
|
return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Password, dicQuery, remark);
|
||||||
dicQuery.Add("mtu", Utils.UrlEncode(item.ShortId));
|
|
||||||
}
|
|
||||||
return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ public static class ProxySettingLinux
|
|||||||
|
|
||||||
private static async Task ExecCmd(List<string> args)
|
private static async Task ExecCmd(List<string> args)
|
||||||
{
|
{
|
||||||
var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false);
|
var customSystemProxyScriptPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyScriptPath;
|
||||||
|
var fileName = (customSystemProxyScriptPath.IsNotEmpty() && File.Exists(customSystemProxyScriptPath))
|
||||||
|
? customSystemProxyScriptPath
|
||||||
|
: await FileUtils.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false);
|
||||||
|
|
||||||
|
// TODO: temporarily notify which script is being used
|
||||||
|
NoticeManager.Instance.SendMessage(fileName);
|
||||||
|
|
||||||
await Utils.GetCliWrapOutput(fileName, args);
|
await Utils.GetCliWrapOutput(fileName, args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,13 @@ public static class ProxySettingOSX
|
|||||||
|
|
||||||
private static async Task ExecCmd(List<string> args)
|
private static async Task ExecCmd(List<string> args)
|
||||||
{
|
{
|
||||||
var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false);
|
var customSystemProxyScriptPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyScriptPath;
|
||||||
|
var fileName = (customSystemProxyScriptPath.IsNotEmpty() && File.Exists(customSystemProxyScriptPath))
|
||||||
|
? customSystemProxyScriptPath
|
||||||
|
: await FileUtils.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false);
|
||||||
|
|
||||||
|
// TODO: temporarily notify which script is being used
|
||||||
|
NoticeManager.Instance.SendMessage(fileName);
|
||||||
|
|
||||||
await Utils.GetCliWrapOutput(fileName, args);
|
await Utils.GetCliWrapOutput(fileName, args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public static class SysProxyHandler
|
|||||||
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
|
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ESysProxyType.ForcedChange when Utils.IsOSX():
|
case ESysProxyType.ForcedChange when Utils.IsMacOS():
|
||||||
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions);
|
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ public static class SysProxyHandler
|
|||||||
await ProxySettingLinux.UnsetProxy();
|
await ProxySettingLinux.UnsetProxy();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ESysProxyType.ForcedClear when Utils.IsOSX():
|
case ESysProxyType.ForcedClear when Utils.IsMacOS():
|
||||||
await ProxySettingOSX.UnsetProxy();
|
await ProxySettingOSX.UnsetProxy();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ public static class SysProxyHandler
|
|||||||
private static async Task SetWindowsProxyPac(int port)
|
private static async Task SetWindowsProxyPac(int port)
|
||||||
{
|
{
|
||||||
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
|
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
|
||||||
await PacManager.Instance.StartAsync(Utils.GetConfigPath(), port, portPac);
|
await PacManager.Instance.StartAsync(port, portPac);
|
||||||
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
|
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
|
||||||
ProxySettingWindows.SetProxy(strProxy, "", 4);
|
ProxySettingWindows.SetProxy(strProxy, "", 4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,28 +71,25 @@ public class DownloaderHelper
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var totalDatetime = DateTime.Now;
|
var lastUpdateTime = DateTime.Now;
|
||||||
var totalSecond = 0;
|
|
||||||
var hasValue = false;
|
var hasValue = false;
|
||||||
double maxSpeed = 0;
|
double maxSpeed = 0;
|
||||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||||
//downloader.DownloadStarted += (sender, value) =>
|
|
||||||
//{
|
|
||||||
// if (progress != null)
|
|
||||||
// {
|
|
||||||
// progress.Report("Start download data...");
|
|
||||||
// }
|
|
||||||
//};
|
|
||||||
downloader.DownloadProgressChanged += (sender, value) =>
|
downloader.DownloadProgressChanged += (sender, value) =>
|
||||||
{
|
{
|
||||||
var ts = DateTime.Now - totalDatetime;
|
if (progress != null && value.BytesPerSecondSpeed > 0)
|
||||||
if (progress != null && ts.Seconds > totalSecond)
|
|
||||||
{
|
{
|
||||||
hasValue = true;
|
hasValue = true;
|
||||||
totalSecond = ts.Seconds;
|
|
||||||
if (value.BytesPerSecondSpeed > maxSpeed)
|
if (value.BytesPerSecondSpeed > maxSpeed)
|
||||||
{
|
{
|
||||||
maxSpeed = value.BytesPerSecondSpeed;
|
maxSpeed = value.BytesPerSecondSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ts = DateTime.Now - lastUpdateTime;
|
||||||
|
if (ts.TotalMilliseconds >= 1000)
|
||||||
|
{
|
||||||
|
lastUpdateTime = DateTime.Now;
|
||||||
var speed = (maxSpeed / 1000 / 1000).ToString("#0.0");
|
var speed = (maxSpeed / 1000 / 1000).ToString("#0.0");
|
||||||
progress.Report(speed);
|
progress.Report(speed);
|
||||||
}
|
}
|
||||||
@@ -102,10 +99,19 @@ public class DownloaderHelper
|
|||||||
{
|
{
|
||||||
if (progress != null)
|
if (progress != null)
|
||||||
{
|
{
|
||||||
if (!hasValue && value.Error != null)
|
if (hasValue && maxSpeed > 0)
|
||||||
|
{
|
||||||
|
var finalSpeed = (maxSpeed / 1000 / 1000).ToString("#0.0");
|
||||||
|
progress.Report(finalSpeed);
|
||||||
|
}
|
||||||
|
else if (value.Error != null)
|
||||||
{
|
{
|
||||||
progress.Report(value.Error?.Message);
|
progress.Report(value.Error?.Message);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
progress.Report("0");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//progress.Report("......");
|
//progress.Report("......");
|
||||||
|
|||||||
@@ -49,15 +49,6 @@ public class HttpClientHelper
|
|||||||
return await httpClient.GetStringAsync(url);
|
return await httpClient.GetStringAsync(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string?> GetAsync(HttpClient client, string url, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
if (url.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await client.GetStringAsync(url, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PutAsync(string url, Dictionary<string, string> headers)
|
public async Task PutAsync(string url, Dictionary<string, string> headers)
|
||||||
{
|
{
|
||||||
var jsonContent = JsonUtils.Serialize(headers);
|
var jsonContent = JsonUtils.Serialize(headers);
|
||||||
@@ -80,156 +71,4 @@ public class HttpClientHelper
|
|||||||
{
|
{
|
||||||
await httpClient.DeleteAsync(url);
|
await httpClient.DeleteAsync(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress<double>? progress, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(url);
|
|
||||||
ArgumentNullException.ThrowIfNull(fileName);
|
|
||||||
if (File.Exists(fileName))
|
|
||||||
{
|
|
||||||
File.Delete(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
throw new Exception(response.StatusCode.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
var total = response.Content.Headers.ContentLength ?? -1L;
|
|
||||||
var canReportProgress = total != -1 && progress != null;
|
|
||||||
|
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(token);
|
|
||||||
await using var file = File.Create(fileName);
|
|
||||||
var totalRead = 0L;
|
|
||||||
var buffer = new byte[1024 * 1024];
|
|
||||||
var progressPercentage = 0;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
var read = await stream.ReadAsync(buffer, token);
|
|
||||||
totalRead += read;
|
|
||||||
|
|
||||||
if (read == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
await file.WriteAsync(buffer.AsMemory(0, read), token);
|
|
||||||
|
|
||||||
if (canReportProgress)
|
|
||||||
{
|
|
||||||
var percent = (int)(100.0 * totalRead / total);
|
|
||||||
//if (progressPercentage != percent && percent % 10 == 0)
|
|
||||||
{
|
|
||||||
progressPercentage = percent;
|
|
||||||
progress?.Report(percent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (canReportProgress)
|
|
||||||
{
|
|
||||||
progress?.Report(101);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DownloadDataAsync4Speed(HttpClient client, string url, IProgress<string> progress, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
if (url.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
throw new Exception(response.StatusCode.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
//var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
|
|
||||||
//var canReportProgress = total != -1 && progress != null;
|
|
||||||
|
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(token);
|
|
||||||
var totalRead = 0L;
|
|
||||||
var buffer = new byte[1024 * 64];
|
|
||||||
var isMoreToRead = true;
|
|
||||||
var progressSpeed = string.Empty;
|
|
||||||
var totalDatetime = DateTime.Now;
|
|
||||||
var totalSecond = 0;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
if (totalRead > 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var read = await stream.ReadAsync(buffer, token);
|
|
||||||
|
|
||||||
if (read == 0)
|
|
||||||
{
|
|
||||||
isMoreToRead = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var data = new byte[read];
|
|
||||||
buffer.ToList().CopyTo(0, data, 0, read);
|
|
||||||
|
|
||||||
totalRead += read;
|
|
||||||
|
|
||||||
var ts = DateTime.Now - totalDatetime;
|
|
||||||
if (progress != null && ts.Seconds > totalSecond)
|
|
||||||
{
|
|
||||||
totalSecond = ts.Seconds;
|
|
||||||
var speed = (totalRead * 1d / ts.TotalMilliseconds / 1000).ToString("#0.0");
|
|
||||||
if (progressSpeed != speed)
|
|
||||||
{
|
|
||||||
progressSpeed = speed;
|
|
||||||
progress.Report(speed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (isMoreToRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
|
|
||||||
{
|
|
||||||
var responseTime = -1;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var cts = new CancellationTokenSource();
|
|
||||||
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
|
|
||||||
using var client = new HttpClient(new SocketsHttpHandler()
|
|
||||||
{
|
|
||||||
Proxy = webProxy,
|
|
||||||
UseProxy = webProxy != null
|
|
||||||
});
|
|
||||||
|
|
||||||
List<int> oneTime = new();
|
|
||||||
for (var i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
var timer = Stopwatch.StartNew();
|
|
||||||
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
|
|
||||||
timer.Stop();
|
|
||||||
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
|
|
||||||
}
|
|
||||||
catch //(Exception ex)
|
|
||||||
{
|
|
||||||
//Utile.SaveLog(ex.Message, ex);
|
|
||||||
}
|
|
||||||
return responseTime;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,19 @@ namespace ServiceLib.Manager;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
|
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ActionPrecheckManager(Config config)
|
public class ActionPrecheckManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
|
private static readonly Lazy<ActionPrecheckManager> _instance = new();
|
||||||
public static ActionPrecheckManager Instance => _instance.Value;
|
public static ActionPrecheckManager Instance => _instance.Value;
|
||||||
|
|
||||||
private readonly Config _config = config;
|
// sing-box supported transports for different protocol types
|
||||||
|
private static readonly HashSet<string> SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)];
|
||||||
|
|
||||||
|
private static readonly HashSet<EConfigType> SingboxTransportSupportedProtocols =
|
||||||
|
[EConfigType.VMess, EConfigType.VLESS, EConfigType.Trojan, EConfigType.Shadowsocks];
|
||||||
|
|
||||||
|
private static readonly HashSet<string> SingboxShadowsocksAllowedTransports =
|
||||||
|
[nameof(ETransport.tcp), nameof(ETransport.ws), nameof(ETransport.quic)];
|
||||||
|
|
||||||
public async Task<List<string>> Check(string? indexId)
|
public async Task<List<string>> Check(string? indexId)
|
||||||
{
|
{
|
||||||
@@ -47,6 +54,7 @@ public class ActionPrecheckManager(Config config)
|
|||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||||
return await ValidateNodeAndCoreSupport(item, coreType);
|
return await ValidateNodeAndCoreSupport(item, coreType);
|
||||||
}
|
}
|
||||||
@@ -62,84 +70,156 @@ public class ActionPrecheckManager(Config config)
|
|||||||
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
|
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
else if (item.ConfigType.IsGroupType())
|
||||||
if (!item.IsComplex())
|
|
||||||
{
|
{
|
||||||
|
var groupErrors = await ValidateGroupNode(item, coreType);
|
||||||
|
errors.AddRange(groupErrors);
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
else if (!item.IsComplex())
|
||||||
|
{
|
||||||
|
var normalErrors = await ValidateNormalNode(item, coreType);
|
||||||
|
errors.AddRange(normalErrors);
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<string>> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null)
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
|
||||||
if (item.Address.IsNullOrEmpty())
|
if (item.Address.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
|
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Port is <= 0 or >= 65536)
|
if (item.Port is <= 0 or > 65535)
|
||||||
{
|
{
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
|
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var net = item.GetNetwork();
|
||||||
|
|
||||||
|
if (coreType == ECoreType.sing_box)
|
||||||
|
{
|
||||||
|
var transportError = ValidateSingboxTransport(item.ConfigType, net);
|
||||||
|
if (transportError != null)
|
||||||
|
{
|
||||||
|
errors.Add(transportError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Global.SingboxSupportConfigType.Contains(item.ConfigType))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
|
||||||
|
nameof(ECoreType.sing_box), item.ConfigType.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (coreType is ECoreType.Xray)
|
||||||
|
{
|
||||||
|
// Xray core does not support these protocols
|
||||||
|
if (!Global.XraySupportConfigType.Contains(item.ConfigType))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
|
||||||
|
nameof(ECoreType.Xray), item.ConfigType.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var protocolExtra = item.GetProtocolExtra();
|
||||||
|
|
||||||
switch (item.ConfigType)
|
switch (item.ConfigType)
|
||||||
{
|
{
|
||||||
case EConfigType.VMess:
|
case EConfigType.VMess:
|
||||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
if (item.Password.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Password))
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "Password"));
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EConfigType.VLESS:
|
case EConfigType.VLESS:
|
||||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id) && item.Id.Length > 30)
|
if (item.Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Password) && item.Password.Length > 30))
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
{
|
||||||
if (!Global.Flows.Contains(item.Flow))
|
errors.Add(string.Format(ResUI.InvalidProperty, "Password"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Global.Flows.Contains(protocolExtra.Flow ?? string.Empty))
|
||||||
|
{
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
|
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EConfigType.Shadowsocks:
|
case EConfigType.Shadowsocks:
|
||||||
if (item.Id.IsNullOrEmpty())
|
if (item.Password.IsNullOrEmpty())
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
{
|
||||||
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
errors.Add(string.Format(ResUI.InvalidProperty, "Password"));
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(protocolExtra.SsMethod) || !Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "SsMethod"));
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
|
if (item.StreamSecurity == Global.StreamSecurity)
|
||||||
&& item.StreamSecurity == Global.StreamSecurityReality
|
{
|
||||||
&& item.PublicKey.IsNullOrEmpty())
|
// check certificate validity
|
||||||
|
if (!item.Cert.IsNullOrEmpty()
|
||||||
|
&& (CertPemManager.ParsePemChain(item.Cert).Count == 0)
|
||||||
|
&& !item.CertSha.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.StreamSecurity == Global.StreamSecurityReality)
|
||||||
|
{
|
||||||
|
if (item.PublicKey.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
|
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (errors.Count > 0)
|
if (item.Network == nameof(ETransport.xhttp)
|
||||||
|
&& !item.Extra.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return errors;
|
// check xhttp extra json validity
|
||||||
|
var xhttpExtra = JsonUtils.ParseJson(item.Extra);
|
||||||
|
if (xhttpExtra is null)
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "XHTTP Extra"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.ConfigType.IsGroupType())
|
|
||||||
{
|
|
||||||
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
|
|
||||||
if (group is null || group.ChildItems.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
|
private async Task<List<string>> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null)
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
|
||||||
|
var hasCycle = await GroupProfileManager.HasCycle(item.IndexId, item.GetProtocolExtra());
|
||||||
if (hasCycle)
|
if (hasCycle)
|
||||||
{
|
{
|
||||||
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
|
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var child in Utils.String2List(group.ChildItems))
|
var (childItems, _) = await GroupProfileManager.GetChildProfileItems(item);
|
||||||
|
|
||||||
|
foreach (var childItem in childItems)
|
||||||
{
|
{
|
||||||
var childErrors = new List<string>();
|
var childErrors = new List<string>();
|
||||||
if (child.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var childItem = await AppManager.Instance.GetProfileItem(child);
|
|
||||||
if (childItem is null)
|
if (childItem is null)
|
||||||
{
|
{
|
||||||
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
|
childErrors.Add(string.Format(ResUI.NodeTagNotExist, ""));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,44 +230,34 @@ public class ActionPrecheckManager(Config config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
|
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
|
||||||
errors.AddRange(childErrors);
|
errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: ")));
|
||||||
}
|
}
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
var net = item.GetNetwork() ?? item.Network;
|
private static string? ValidateSingboxTransport(EConfigType configType, string net)
|
||||||
|
|
||||||
if (coreType == ECoreType.sing_box)
|
|
||||||
{
|
{
|
||||||
// sing-box does not support xhttp / kcp
|
// sing-box does not support xhttp / kcp transports
|
||||||
// sing-box does not support transports like ws/http/httpupgrade/etc. when the node is not vmess/trojan/vless
|
if (SingboxUnsupportedTransports.Contains(net))
|
||||||
if (net is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
|
||||||
{
|
{
|
||||||
errors.Add(string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net));
|
return string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net);
|
||||||
return errors;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan))
|
// sing-box does not support non-tcp transports for protocols other than vmess/trojan/vless/shadowsocks
|
||||||
|
if (!SingboxTransportSupportedProtocols.Contains(configType) && net != nameof(ETransport.tcp))
|
||||||
{
|
{
|
||||||
if (net is nameof(ETransport.ws) or nameof(ETransport.http) or nameof(ETransport.h2) or nameof(ETransport.quic) or nameof(ETransport.httpupgrade))
|
return string.Format(ResUI.CoreNotSupportProtocolTransport,
|
||||||
{
|
nameof(ECoreType.sing_box), configType.ToString(), net);
|
||||||
errors.Add(string.Format(ResUI.CoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), item.ConfigType.ToString(), net));
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (coreType is ECoreType.Xray)
|
|
||||||
{
|
|
||||||
// Xray core does not support these protocols
|
|
||||||
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
|
|
||||||
&& !item.IsComplex())
|
|
||||||
{
|
|
||||||
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
// sing-box shadowsocks only supports tcp/ws/quic transports
|
||||||
|
if (configType == EConfigType.Shadowsocks && !SingboxShadowsocksAllowedTransports.Contains(net))
|
||||||
|
{
|
||||||
|
return string.Format(ResUI.CoreNotSupportProtocolTransport,
|
||||||
|
nameof(ECoreType.sing_box), configType.ToString(), net);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item)
|
private async Task<List<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item)
|
||||||
@@ -228,7 +298,7 @@ public class ActionPrecheckManager(Config config)
|
|||||||
if (node is not null)
|
if (node is not null)
|
||||||
{
|
{
|
||||||
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
|
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
|
||||||
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
|
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + $"{node.Remarks}: " + s));
|
||||||
}
|
}
|
||||||
else if (tag.IsNotEmpty())
|
else if (tag.IsNotEmpty())
|
||||||
{
|
{
|
||||||
@@ -246,7 +316,7 @@ public class ActionPrecheckManager(Config config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = await ConfigHandler.GetDefaultRouting(AppManager.Instance.Config);
|
||||||
if (routing == null)
|
if (routing == null)
|
||||||
{
|
{
|
||||||
return errors;
|
return errors;
|
||||||
@@ -274,7 +344,7 @@ public class ActionPrecheckManager(Config config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
|
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
|
||||||
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
|
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + $"{tagItem.Remarks}: " + s));
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
|||||||
@@ -31,6 +31,23 @@ public sealed class AppManager
|
|||||||
|
|
||||||
public string LinuxSudoPwd { get; set; }
|
public string LinuxSudoPwd { get; set; }
|
||||||
|
|
||||||
|
public bool ShowInTaskbar { get; set; }
|
||||||
|
|
||||||
|
public ECoreType RunningCoreType { get; set; }
|
||||||
|
|
||||||
|
public bool IsRunningCore(ECoreType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion Property
|
#endregion Property
|
||||||
|
|
||||||
#region App
|
#region App
|
||||||
@@ -64,7 +81,9 @@ public sealed class AppManager
|
|||||||
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
||||||
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
||||||
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
|
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
|
||||||
|
#pragma warning disable CS0618
|
||||||
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
|
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
|
||||||
|
#pragma warning restore CS0618
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +96,11 @@ public sealed class AppManager
|
|||||||
_ = StatePort;
|
_ = StatePort;
|
||||||
_ = StatePort2;
|
_ = StatePort2;
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await MigrateProfileExtra();
|
||||||
|
}).Wait();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,10 +191,17 @@ public sealed class AppManager
|
|||||||
return (await ProfileItems(subid))?.Select(t => t.IndexId)?.ToList();
|
return (await ProfileItems(subid))?.Select(t => t.IndexId)?.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<ProfileItemModel>?> ProfileItems(string subid, string filter)
|
public async Task<List<ProfileItemModel>?> ProfileModels(string subid, string filter)
|
||||||
{
|
{
|
||||||
var sql = @$"select a.*
|
var sql = @$"select a.IndexId
|
||||||
,b.remarks subRemarks
|
,a.ConfigType
|
||||||
|
,a.Remarks
|
||||||
|
,a.Address
|
||||||
|
,a.Port
|
||||||
|
,a.Network
|
||||||
|
,a.StreamSecurity
|
||||||
|
,a.Subid
|
||||||
|
,b.remarks as subRemarks
|
||||||
from ProfileItem a
|
from ProfileItem a
|
||||||
left join SubItem b on a.subid = b.id
|
left join SubItem b on a.subid = b.id
|
||||||
where 1=1 ";
|
where 1=1 ";
|
||||||
@@ -199,6 +230,18 @@ public sealed class AppManager
|
|||||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
|
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<ProfileItem>> GetProfileItemsByIndexIds(IEnumerable<string> indexIds)
|
||||||
|
{
|
||||||
|
var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
||||||
|
if (ids.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||||
|
.Where(it => ids.Contains(it.IndexId))
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
|
public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
|
||||||
{
|
{
|
||||||
if (remarks.IsNullOrEmpty())
|
if (remarks.IsNullOrEmpty())
|
||||||
@@ -208,15 +251,6 @@ public sealed class AppManager
|
|||||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks);
|
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ProfileGroupItem?> GetProfileGroupItem(string indexId)
|
|
||||||
{
|
|
||||||
if (indexId.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<RoutingItem>?> RoutingItems()
|
public async Task<List<RoutingItem>?> RoutingItems()
|
||||||
{
|
{
|
||||||
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();
|
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();
|
||||||
@@ -247,6 +281,205 @@ public sealed class AppManager
|
|||||||
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
|
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task MigrateProfileExtra()
|
||||||
|
{
|
||||||
|
await MigrateProfileExtraGroup();
|
||||||
|
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
|
||||||
|
const int pageSize = 100;
|
||||||
|
var offset = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var sql = $"SELECT * FROM ProfileItem " +
|
||||||
|
$"WHERE ConfigVersion < 3 " +
|
||||||
|
$"AND ConfigType NOT IN ({(int)EConfigType.PolicyGroup}, {(int)EConfigType.ProxyChain}) " +
|
||||||
|
$"LIMIT {pageSize} OFFSET {offset}";
|
||||||
|
var batch = await SQLiteHelper.Instance.QueryAsync<ProfileItem>(sql);
|
||||||
|
if (batch is null || batch.Count == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var batchSuccessCount = await MigrateProfileExtraSub(batch);
|
||||||
|
|
||||||
|
// Only increment offset by the number of failed items that remain in the result set
|
||||||
|
// Successfully updated items are automatically excluded from future queries due to ConfigVersion = 3
|
||||||
|
offset += batch.Count - batchSuccessCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
//await ProfileGroupItemManager.Instance.ClearAll();
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> MigrateProfileExtraSub(List<ProfileItem> batch)
|
||||||
|
{
|
||||||
|
var updateProfileItems = new List<ProfileItem>();
|
||||||
|
|
||||||
|
foreach (var item in batch)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var extra = item.GetProtocolExtra();
|
||||||
|
switch (item.ConfigType)
|
||||||
|
{
|
||||||
|
case EConfigType.Shadowsocks:
|
||||||
|
extra = extra with { SsMethod = item.Security.NullIfEmpty() };
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EConfigType.VMess:
|
||||||
|
extra = extra with
|
||||||
|
{
|
||||||
|
AlterId = item.AlterId.ToString(),
|
||||||
|
VmessSecurity = item.Security.NullIfEmpty(),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EConfigType.VLESS:
|
||||||
|
extra = extra with
|
||||||
|
{
|
||||||
|
Flow = item.Flow.NullIfEmpty(),
|
||||||
|
VlessEncryption = item.Security,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EConfigType.Hysteria2:
|
||||||
|
extra = extra with
|
||||||
|
{
|
||||||
|
SalamanderPass = item.Path.NullIfEmpty(),
|
||||||
|
Ports = item.Ports.NullIfEmpty(),
|
||||||
|
UpMbps = _config.HysteriaItem.UpMbps,
|
||||||
|
DownMbps = _config.HysteriaItem.DownMbps,
|
||||||
|
HopInterval = _config.HysteriaItem.HopInterval.ToString(),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EConfigType.TUIC:
|
||||||
|
item.Username = item.Id;
|
||||||
|
item.Id = item.Security;
|
||||||
|
item.Password = item.Security;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EConfigType.HTTP:
|
||||||
|
case EConfigType.SOCKS:
|
||||||
|
item.Username = item.Security;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EConfigType.WireGuard:
|
||||||
|
extra = extra with
|
||||||
|
{
|
||||||
|
WgPublicKey = item.PublicKey.NullIfEmpty(),
|
||||||
|
WgInterfaceAddress = item.RequestHost.NullIfEmpty(),
|
||||||
|
WgReserved = item.Path.NullIfEmpty(),
|
||||||
|
WgMtu = int.TryParse(item.ShortId, out var mtu) ? mtu : 1280
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.SetProtocolExtra(extra);
|
||||||
|
|
||||||
|
item.Password = item.Id;
|
||||||
|
|
||||||
|
item.ConfigVersion = 3;
|
||||||
|
|
||||||
|
updateProfileItems.Add(item);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"MigrateProfileExtra Error: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateProfileItems.Count > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var count = await SQLiteHelper.Instance.UpdateAllAsync(updateProfileItems);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"MigrateProfileExtraGroup update error: {ex}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> MigrateProfileExtraGroup()
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
|
||||||
|
var groupItems = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!));
|
||||||
|
|
||||||
|
var sql = $"SELECT * FROM ProfileItem WHERE ConfigVersion < 3 AND ConfigType IN ({(int)EConfigType.PolicyGroup}, {(int)EConfigType.ProxyChain})";
|
||||||
|
var items = await SQLiteHelper.Instance.QueryAsync<ProfileItem>(sql);
|
||||||
|
|
||||||
|
if (items is null || items.Count == 0)
|
||||||
|
{
|
||||||
|
Logging.SaveLog("MigrateProfileExtraGroup: No items to migrate.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.SaveLog($"MigrateProfileExtraGroup: Found {items.Count} group items to migrate.");
|
||||||
|
|
||||||
|
var updateProfileItems = new List<ProfileItem>();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var extra = item.GetProtocolExtra();
|
||||||
|
|
||||||
|
extra = extra with { GroupType = nameof(item.ConfigType) };
|
||||||
|
groupItems.TryGetValue(item.IndexId, out var groupItem);
|
||||||
|
if (groupItem != null && !groupItem.NotHasChild())
|
||||||
|
{
|
||||||
|
extra = extra with
|
||||||
|
{
|
||||||
|
ChildItems = groupItem.ChildItems,
|
||||||
|
SubChildItems = groupItem.SubChildItems,
|
||||||
|
Filter = groupItem.Filter,
|
||||||
|
MultipleLoad = groupItem.MultipleLoad,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
item.SetProtocolExtra(extra);
|
||||||
|
|
||||||
|
item.ConfigVersion = 3;
|
||||||
|
updateProfileItems.Add(item);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"MigrateProfileExtraGroup item error [{item.IndexId}]: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateProfileItems.Count > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var count = await SQLiteHelper.Instance.UpdateAllAsync(updateProfileItems);
|
||||||
|
Logging.SaveLog($"MigrateProfileExtraGroup: Successfully updated {updateProfileItems.Count} items.");
|
||||||
|
return updateProfileItems.Count == count;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"MigrateProfileExtraGroup update error: {ex}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
//await ProfileGroupItemManager.Instance.ClearAll();
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
}
|
||||||
|
|
||||||
#endregion SqliteHelper
|
#endregion SqliteHelper
|
||||||
|
|
||||||
#region Core Type
|
#region Core Type
|
||||||
|
|||||||
@@ -0,0 +1,434 @@
|
|||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manager for certificate operations with CA pinning to prevent MITM attacks
|
||||||
|
/// </summary>
|
||||||
|
public class CertPemManager
|
||||||
|
{
|
||||||
|
private static readonly string _tag = "CertPemManager";
|
||||||
|
private static readonly Lazy<CertPemManager> _instance = new(() => new());
|
||||||
|
public static CertPemManager Instance => _instance.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks
|
||||||
|
/// </summary>
|
||||||
|
private static readonly HashSet<string> TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
"EBD41040E4BB3EC742C9E381D31EF2A41A48B6685C96E7CEF3C1DF6CD4331C99", // GlobalSign Root CA
|
||||||
|
"6DC47172E01CBCB0BF62580D895FE2B8AC9AD4F873801E0C10B9C837D21EB177", // Entrust.net Premium 2048 Secure Server CA
|
||||||
|
"73C176434F1BC6D5ADF45B0E76E727287C8DE57616C1E6E6141A2B2CBC7D8E4C", // Entrust Root Certification Authority
|
||||||
|
"D8E0FEBC1DB2E38D00940F37D27D41344D993E734B99D5656D9778D4D8143624", // Certum Root CA
|
||||||
|
"D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root
|
||||||
|
"85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2
|
||||||
|
"18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3
|
||||||
|
"CECDDC905099D8DADFC5B1D209B737CBE2C18CFB2C10C0FF0BCF0D3286FC1AA2", // XRamp Global CA Root
|
||||||
|
"C3846BF24B9E93CA64274C0EC67C1ECC5E024FFCACD2D74019350E81FE546AE4", // Go Daddy Class 2 CA
|
||||||
|
"1465FA205397B876FAA6F0A9958E5590E40FCC7FAA4FB7C2C8677521FB5FB658", // Starfield Class 2 CA
|
||||||
|
"3E9099B5015E8F486C00BCEA9D111EE721FABA355A89BCF1DF69561E3DC6325C", // DigiCert Assured ID Root CA
|
||||||
|
"4348A0E9444C78CB265E058D5E8944B4D84F9662BD26DB257F8934A443C70161", // DigiCert Global Root CA
|
||||||
|
"7431E5F4C3C1CE4690774F0B61E05440883BA9A01ED00BA6ABD7806ED3B118CF", // DigiCert High Assurance EV Root CA
|
||||||
|
"62DD0BE9B9F50A163EA0F8E75C053B1ECA57EA55C8688F647C6881F2C8357B95", // SwissSign Gold CA - G2
|
||||||
|
"F1C1B50AE5A20DD8030EC9F6BC24823DD367B5255759B4E71B61FCE9F7375D73", // SecureTrust CA
|
||||||
|
"4200F5043AC8590EBB527D209ED1503029FBCBD41CA1B506EC27F15ADE7DAC69", // Secure Global CA
|
||||||
|
"0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66", // COMODO Certification Authority
|
||||||
|
"1793927A0614549789ADCE2F8F34F7F0B66D0F3AE3A3B84D21EC15DBBA4FADC7", // COMODO ECC Certification Authority
|
||||||
|
"41C923866AB4CAD6B7AD578081582E020797A6CBDF4FFF78CE8396B38937D7F5", // OISTE WISeKey Global Root GA CA
|
||||||
|
"E3B6A2DB2ED7CE48842F7AC53241C7B71D54144BFB40C11F3F1D0B42F5EEA12D", // Certigna
|
||||||
|
"C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5", // ePKI Root Certification Authority
|
||||||
|
"EAA962C4FA4A6BAFEBE415196D351CCD888D4F53F3FA8AE6D7C466A94E6042BB", // certSIGN ROOT CA
|
||||||
|
"6C61DAC3A2DEF031506BE036D2A6FE401994FBD13DF9C8D466599274C446EC98", // NetLock Arany (Class Gold) Főtanúsítvány
|
||||||
|
"3C5F81FEA5FAB82C64BFA2EAECAFCDE8E077FC8620A7CAE537163DF36EDBF378", // Microsec e-Szigno Root CA 2009
|
||||||
|
"CBB522D7B7F127AD6A0113865BDF1CD4102E7D0759AF635A7CF4720DC963C53B", // GlobalSign Root CA - R3
|
||||||
|
"2530CC8E98321502BAD96F9B1FBA1B099E2D299E0F4548BB914F363BC0D4531F", // Izenpe.com
|
||||||
|
"45140B3247EB9CC8C5B4F0D7B53091F73292089E6E5A63E2749DD3ACA9198EDA", // Go Daddy Root Certificate Authority - G2
|
||||||
|
"2CE1CB0BF9D2F9E102993FBE215152C3B2DD0CABDE1C68E5319B839154DBB7F5", // Starfield Root Certificate Authority - G2
|
||||||
|
"568D6905A2C88708A4B3025190EDCFEDB1974A606A13C6E5290FCB2AE63EDAB5", // Starfield Services Root Certificate Authority - G2
|
||||||
|
"0376AB1D54C5F9803CE4B2E201A0EE7EEF7B57B636E8A93C9B8D4860C96F5FA7", // AffirmTrust Commercial
|
||||||
|
"0A81EC5A929777F145904AF38D5D509F66B5E2C58FCDB531058B0E17F3F0B41B", // AffirmTrust Networking
|
||||||
|
"70A73F7F376B60074248904534B11482D5BF0E698ECC498DF52577EBF2E93B9A", // AffirmTrust Premium
|
||||||
|
"BD71FDF6DA97E4CF62D1647ADD2581B07D79ADF8397EB4ECBA9C5E8488821423", // AffirmTrust Premium ECC
|
||||||
|
"5C58468D55F58E497E743982D2B50010B6D165374ACF83A7D4A32DB768C4408E", // Certum Trusted Network CA
|
||||||
|
"BFD88FE1101C41AE3E801BF8BE56350EE9BAD1A6B9BD515EDC5C6D5B8711AC44", // TWCA Root Certification Authority
|
||||||
|
"513B2CECB810D4CDE5DD85391ADFC6C2DD60D87BB736D2B521484AA47A0EBEF6", // Security Communication RootCA2
|
||||||
|
"55926084EC963A64B96E2ABE01CE0BA86A64FBFEBCC7AAB5AFC155B37FD76066", // Actalis Authentication Root CA
|
||||||
|
"9A114025197C5BB95D94E63D55CD43790847B646B23CDF11ADA4A00EFF15FB48", // Buypass Class 2 Root CA
|
||||||
|
"EDF7EBBCA27A2A384D387B7D4010C666E2EDB4843E4C29B4AE1D5B9332E6B24D", // Buypass Class 3 Root CA
|
||||||
|
"FD73DAD31C644FF1B43BEF0CCDDA96710B9CD9875ECA7E31707AF3E96D522BBD", // T-TeleSec GlobalRoot Class 3
|
||||||
|
"49E7A442ACF0EA6287050054B52564B650E4F49E42E348D6AA38E039E957B1C1", // D-TRUST Root Class 3 CA 2 2009
|
||||||
|
"EEC5496B988CE98625B934092EEC2908BED0B0F316C2D4730C84EAF1F3D34881", // D-TRUST Root Class 3 CA 2 EV 2009
|
||||||
|
"E23D4A036D7B70E9F595B1422079D2B91EDFBB1FB651A0633EAA8A9DC5F80703", // CA Disig Root R2
|
||||||
|
"9A6EC012E1A7DA9DBE34194D478AD7C0DB1822FB071DF12981496ED104384113", // ACCVRAIZ1
|
||||||
|
"59769007F7685D0FCD50872F9F95D5755A5B2B457D81F3692B610A98672F0E1B", // TWCA Global Root CA
|
||||||
|
"DD6936FE21F8F077C123A1A521C12224F72255B73E03A7260693E8A24B0FA389", // TeliaSonera Root CA v1
|
||||||
|
"91E2F5788D5810EBA7BA58737DE1548A8ECACD014598BC0B143E041B17052552", // T-TeleSec GlobalRoot Class 2
|
||||||
|
"F356BEA244B7A91EB35D53CA9AD7864ACE018E2D35D5F8F96DDF68A6F41AA474", // Atos TrustedRoot 2011
|
||||||
|
"8A866FD1B276B57E578E921C65828A2BED58E9F2F288054134B7F1F4BFC9CC74", // QuoVadis Root CA 1 G3
|
||||||
|
"8FE4FB0AF93A4D0D67DB0BEBB23E37C71BF325DCBCDD240EA04DAF58B47E1840", // QuoVadis Root CA 2 G3
|
||||||
|
"88EF81DE202EB018452E43F864725CEA5FBD1FC2D9D205730709C5D8B8690F46", // QuoVadis Root CA 3 G3
|
||||||
|
"7D05EBB682339F8C9451EE094EEBFEFA7953A114EDB2F44949452FAB7D2FC185", // DigiCert Assured ID Root G2
|
||||||
|
"7E37CB8B4C47090CAB36551BA6F45DB840680FBA166A952DB100717F43053FC2", // DigiCert Assured ID Root G3
|
||||||
|
"CB3CCBB76031E5E0138F8DD39A23F9DE47FFC35E43C1144CEA27D46A5AB1CB5F", // DigiCert Global Root G2
|
||||||
|
"31AD6648F8104138C738F39EA4320133393E3A18CC02296EF97C2AC9EF6731D0", // DigiCert Global Root G3
|
||||||
|
"552F7BDCF1A7AF9E6CE672017F4F12ABF77240C78E761AC203D1D9D20AC89988", // DigiCert Trusted Root G4
|
||||||
|
"52F0E1C4E58EC629291B60317F074671B85D7EA80D5B07273463534B32B40234", // COMODO RSA Certification Authority
|
||||||
|
"E793C9B02FD8AA13E21C31228ACCB08119643B749C898964B1746D46C3D4CBD2", // USERTrust RSA Certification Authority
|
||||||
|
"4FF460D54B9C86DABFBCFC5712E0400D2BED3FBC4D4FBDAA86E06ADCD2A9AD7A", // USERTrust ECC Certification Authority
|
||||||
|
"179FBC148A3DD00FD24EA13458CC43BFA7F59C8182D783A513F6EBEC100C8924", // GlobalSign ECC Root CA - R5
|
||||||
|
"3C4FB0B95AB8B30032F432B86F535FE172C185D0FD39865837CF36187FA6F428", // Staat der Nederlanden Root CA - G3
|
||||||
|
"5D56499BE4D2E08BCFCAD08A3E38723D50503BDE706948E42F55603019E528AE", // IdenTrust Commercial Root CA 1
|
||||||
|
"30D0895A9A448A262091635522D1F52010B5867ACAE12C78EF958FD4F4389F2F", // IdenTrust Public Sector Root CA 1
|
||||||
|
"43DF5774B03E7FEF5FE40D931A7BEDF1BB2E6B42738C4E6D3841103D3AA7F339", // Entrust Root Certification Authority - G2
|
||||||
|
"02ED0EB28C14DA45165C566791700D6451D7FB56F0B2AB1D3B8EB070E56EDFF5", // Entrust Root Certification Authority - EC1
|
||||||
|
"5CC3D78E4E1D5E45547A04E6873E64F90CF9536D1CCC2EF800F355C4C5FD70FD", // CFCA EV ROOT
|
||||||
|
"6B9C08E86EB0F767CFAD65CD98B62149E5494A67F5845E7BD1ED019F27B86BD6", // OISTE WISeKey Global Root GB CA
|
||||||
|
"A1339D33281A0B56E557D3D32B1CE7F9367EB094BD5FA72A7E5004C8DED7CAFE", // SZAFIR ROOT CA2
|
||||||
|
"B676F2EDDAE8775CD36CB0F63CD1D4603961F49E6265BA013A2F0307B6D0B804", // Certum Trusted Network CA 2
|
||||||
|
"A040929A02CE53B4ACF4F2FFC6981CE4496F755E6D45FE0B2A692BCD52523F36", // Hellenic Academic and Research Institutions RootCA 2015
|
||||||
|
"44B545AA8A25E65A73CA15DC27FC36D24C1CB9953A066539B11582DC487B4833", // Hellenic Academic and Research Institutions ECC RootCA 2015
|
||||||
|
"96BCEC06264976F37460779ACF28C5A7CFE8A3C0AAE11A8FFCEE05C0BDDF08C6", // ISRG Root X1
|
||||||
|
"EBC5570C29018C4D67B1AA127BAF12F703B4611EBC17B7DAB5573894179B93FA", // AC RAIZ FNMT-RCM
|
||||||
|
"8ECDE6884F3D87B1125BA31AC3FCB13D7016DE7F57CC904FE1CB97C6AE98196E", // Amazon Root CA 1
|
||||||
|
"1BA5B2AA8C65401A82960118F80BEC4F62304D83CEC4713A19C39C011EA46DB4", // Amazon Root CA 2
|
||||||
|
"18CE6CFE7BF14E60B2E347B8DFE868CB31D02EBB3ADA271569F50343B46DB3A4", // Amazon Root CA 3
|
||||||
|
"E35D28419ED02025CFA69038CD623962458DA5C695FBDEA3C22B0BFB25897092", // Amazon Root CA 4
|
||||||
|
"A1A86D04121EB87F027C66F53303C28E5739F943FC84B38AD6AF009035DD9457", // D-TRUST Root CA 3 2013
|
||||||
|
"46EDC3689046D53A453FB3104AB80DCAEC658B2660EA1629DD7E867990648716", // TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1
|
||||||
|
"BFFF8FD04433487D6A8AA60C1A29767A9FC2BBB05E420F713A13B992891D3893", // GDCA TrustAUTH R5 ROOT
|
||||||
|
"85666A562EE0BE5CE925C1D8890A6F76A87EC16D4D7D5F29EA7419CF20123B69", // SSL.com Root Certification Authority RSA
|
||||||
|
"3417BB06CC6007DA1B961C920B8AB4CE3FAD820E4AA30B9ACBC4A74EBDCEBC65", // SSL.com Root Certification Authority ECC
|
||||||
|
"2E7BF16CC22485A7BBE2AA8696750761B0AE39BE3B2FE9D0CC6D4EF73491425C", // SSL.com EV Root Certification Authority RSA R2
|
||||||
|
"22A2C1F7BDED704CC1E701B5F408C310880FE956B5DE2A4A44F99C873A25A7C8", // SSL.com EV Root Certification Authority ECC
|
||||||
|
"2CABEAFE37D06CA22ABA7391C0033D25982952C453647349763A3AB5AD6CCF69", // GlobalSign Root CA - R6
|
||||||
|
"8560F91C3624DABA9570B5FEA0DBE36FF11A8323BE9486854FB3F34A5571198D", // OISTE WISeKey Global Root GC CA
|
||||||
|
"9BEA11C976FE014764C1BE56A6F914B5A560317ABD9988393382E5161AA0493C", // UCA Global G2 Root
|
||||||
|
"D43AF9B35473755C9684FC06D7D8CB70EE5C28E773FB294EB41EE71722924D24", // UCA Extended Validation Root
|
||||||
|
"D48D3D23EEDB50A459E55197601C27774B9D7B18C94D5A059511A10250B93168", // Certigna Root CA
|
||||||
|
"40F6AF0346A99AA1CD1D555A4E9CCE62C7F9634603EE406615833DC8C8D00367", // emSign Root CA - G1
|
||||||
|
"86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B", // emSign ECC Root CA - G3
|
||||||
|
"125609AA301DA0A249B97A8239CB6A34216F44DCAC9F3954B14292F2E8C8608F", // emSign Root CA - C1
|
||||||
|
"BC4D809B15189D78DB3E1D8CF4F9726A795DA1643CA5F1358E1DDB0EDC0D7EB3", // emSign ECC Root CA - C3
|
||||||
|
"5A2FC03F0C83B090BBFA40604B0988446C7636183DF9846E17101A447FB8EFD6", // Hongkong Post Root CA 3
|
||||||
|
"DB3517D1F6732A2D5AB97C533EC70779EE3270A62FB4AC4238372460E6F01E88", // Entrust Root Certification Authority - G4
|
||||||
|
"358DF39D764AF9E1B766E9C972DF352EE15CFAC227AF6AD1D70E8E4A6EDCBA02", // Microsoft ECC Root Certificate Authority 2017
|
||||||
|
"C741F70F4B2A8D88BF2E71C14122EF53EF10EBA0CFA5E64CFA20F418853073E0", // Microsoft RSA Root Certificate Authority 2017
|
||||||
|
"BEB00B30839B9BC32C32E4447905950641F26421B15ED089198B518AE2EA1B99", // e-Szigno Root CA 2017
|
||||||
|
"657CFE2FA73FAA38462571F332A2363A46FCE7020951710702CDFBB6EEDA3305", // certSIGN Root CA G2
|
||||||
|
"97552015F5DDFC3C8788C006944555408894450084F100867086BC1A2BB58DC8", // Trustwave Global Certification Authority
|
||||||
|
"945BBC825EA554F489D1FD51A73DDF2EA624AC7019A05205225C22A78CCFA8B4", // Trustwave Global ECC P256 Certification Authority
|
||||||
|
"55903859C8C0C3EBB8759ECE4E2557225FF5758BBD38EBD48276601E1BD58097", // Trustwave Global ECC P384 Certification Authority
|
||||||
|
"88F438DCF8FFD1FA8F429115FFE5F82AE1E06E0C70C375FAAD717B34A49E7265", // NAVER Global Root Certification Authority
|
||||||
|
"554153B13D2CF9DDB753BFBE1A4E0AE08D0AA4187058FE60A2B862B2E4B87BCB", // AC RAIZ FNMT-RCM SERVIDORES SEGUROS
|
||||||
|
"319AF0A7729E6F89269C131EA6A3A16FCD86389FDCAB3C47A4A675C161A3F974", // GlobalSign Secure Mail Root R45
|
||||||
|
"5CBF6FB81FD417EA4128CD6F8172A3C9402094F74AB2ED3A06B4405D04F30B19", // GlobalSign Secure Mail Root E45
|
||||||
|
"4FA3126D8D3A11D1C4855A4F807CBAD6CF919D3A5A88B03BEA2C6372D93C40C9", // GlobalSign Root R46
|
||||||
|
"CBB9C44D84B8043E1050EA31A69F514955D7BFD2E2C6B49301019AD61D9F5058", // GlobalSign Root E46
|
||||||
|
"9A296A5182D1D451A2E37F439B74DAAFA267523329F90F9A0D2007C334E23C9A", // GLOBALTRUST 2020
|
||||||
|
"FB8FEC759169B9106B1E511644C618C51304373F6C0643088D8BEFFD1B997599", // ANF Secure Server Root CA
|
||||||
|
"6B328085625318AA50D173C98D8BDA09D57E27413D114CF787A0F5D06C030CF6", // Certum EC-384 CA
|
||||||
|
"FE7696573855773E37A95E7AD4D9CC96C30157C15D31765BA9B15704E1AE78FD", // Certum Trusted Root CA
|
||||||
|
"2E44102AB58CB85419451C8E19D9ACF3662CAFBC614B6A53960A30F7D0E2EB41", // TunTrust Root CA
|
||||||
|
"D95D0E8EDA79525BF9BEB11B14D2100D3294985F0C62D9FABD9CD999ECCB7B1D", // HARICA TLS RSA Root CA 2021
|
||||||
|
"3F99CC474ACFCE4DFED58794665E478D1547739F2E780F1BB4CA9B133097D401", // HARICA TLS ECC Root CA 2021
|
||||||
|
"1BE7ABE30686B16348AFD1C61B6866A0EA7F4821E67D5E8AF937CF8011BC750D", // HARICA Client RSA Root CA 2021
|
||||||
|
"8DD4B5373CB0DE36769C12339280D82746B3AA6CD426E797A31BABE4279CF00B", // HARICA Client ECC Root CA 2021
|
||||||
|
"57DE0583EFD2B26E0361DA99DA9DF4648DEF7EE8441C3B728AFA9BCDE0F9B26A", // Autoridad de Certificacion Firmaprofesional CIF A62634068
|
||||||
|
"30FBBA2C32238E2A98547AF97931E550428B9B3F1C8EEB6633DCFA86C5B27DD3", // vTrus ECC Root CA
|
||||||
|
"8A71DE6559336F426C26E53880D00D88A18DA4C6A91F0DCB6194E206C5C96387", // vTrus Root CA
|
||||||
|
"69729B8E15A86EFC177A57AFB7171DFC64ADD28C2FCA8CF1507E34453CCB1470", // ISRG Root X2
|
||||||
|
"F015CE3CC239BFEF064BE9F1D2C417E1A0264A0A94BE1F0C8D121864EB6949CC", // HiPKI Root CA - G1
|
||||||
|
"B085D70B964F191A73E4AF0D54AE7A0E07AAFDAF9B71DD0862138AB7325A24A2", // GlobalSign ECC Root CA - R4
|
||||||
|
"D947432ABDE7B7FA90FC2E6B59101B1280E0E1C7E4E40FA3C6887FFF57A7F4CF", // GTS Root R1
|
||||||
|
"8D25CD97229DBF70356BDA4EB3CC734031E24CF00FAFCFD32DC76EB5841C7EA8", // GTS Root R2
|
||||||
|
"34D8A73EE208D9BCDB0D956520934B4E40E69482596E8B6F73C8426B010A6F48", // GTS Root R3
|
||||||
|
"349DFA4058C5E263123B398AE795573C4E1313C83FE68F93556CD5E8031B3C7D", // GTS Root R4
|
||||||
|
"242B69742FCB1E5B2ABF98898B94572187544E5B4D9911786573621F6A74B82C", // Telia Root CA v2
|
||||||
|
"E59AAA816009C22BFF5B25BAD37DF306F049797C1F81D85AB089E657BD8F0044", // D-TRUST BR Root CA 1 2020
|
||||||
|
"08170D1AA36453901A2F959245E347DB0C8D37ABAABC56B81AA100DC958970DB", // D-TRUST EV Root CA 1 2020
|
||||||
|
"018E13F0772532CF809BD1B17281867283FC48C6E13BE9C69812854A490C1B05", // DigiCert TLS ECC P384 Root G5
|
||||||
|
"371A00DC0533B3721A7EEB40E8419E70799D2B0A0F2C1D80693165F7CEC4AD75", // DigiCert TLS RSA4096 Root G5
|
||||||
|
"E8E8176536A60CC2C4E10187C3BEFCA20EF263497018F566D5BEA0F94D0C111B", // DigiCert SMIME ECC P384 Root G5
|
||||||
|
"90370D3EFA88BF58C30105BA25104A358460A7FA52DFC2011DF233A0F417912A", // DigiCert SMIME RSA4096 Root G5
|
||||||
|
"77B82CD8644C4305F7ACC5CB156B45675004033D51C60C6202A8E0C33467D3A0", // Certainly Root R1
|
||||||
|
"B4585F22E4AC756A4E8612A1361C5D9D031A93FD84FEBB778FA3068B0FC42DC2", // Certainly Root E1
|
||||||
|
"82BD5D851ACF7F6E1BA7BFCBC53030D0E7BC3C21DF772D858CAB41D199BDF595", // DIGITALSIGN GLOBAL ROOT RSA CA
|
||||||
|
"261D7114AE5F8FF2D8C7209A9DE4289E6AFC9D717023D85450909199F1857CFE", // DIGITALSIGN GLOBAL ROOT ECDSA CA
|
||||||
|
"E74FBDA55BD564C473A36B441AA799C8A68E077440E8288B9FA1E50E4BBACA11", // Security Communication ECC RootCA1
|
||||||
|
"F3896F88FE7C0A882766A7FA6AD2749FB57A7F3E98FB769C1FA7B09C2C44D5AE", // BJCA Global Root CA1
|
||||||
|
"574DF6931E278039667B720AFDC1600FC27EB66DD3092979FB73856487212882", // BJCA Global Root CA2
|
||||||
|
"48E1CF9E43B688A51044160F46D773B8277FE45BEAAD0E4DF90D1974382FEA99", // LAWtrust Root CA2 (4096)
|
||||||
|
"22D9599234D60F1D4BC7C7E96F43FA555B07301FD475175089DAFB8C25E477B3", // Sectigo Public Email Protection Root E46
|
||||||
|
"D5917A7791EB7CF20A2E57EB98284A67B28A57E89182DA53D546678C9FDE2B4F", // Sectigo Public Email Protection Root R46
|
||||||
|
"C90F26F0FB1B4018B22227519B5CA2B53E2CA5B3BE5CF18EFE1BEF47380C5383", // Sectigo Public Server Authentication Root E46
|
||||||
|
"7BB647A62AEEAC88BF257AA522D01FFEA395E0AB45C73F93F65654EC38F25A06", // Sectigo Public Server Authentication Root R46
|
||||||
|
"8FAF7D2E2CB4709BB8E0B33666BF75A5DD45B5DE480F8EA8D4BFE6BEBC17F2ED", // SSL.com TLS RSA Root CA 2022
|
||||||
|
"C32FFD9F46F936D16C3673990959434B9AD60AAFBB9E7CF33654F144CC1BA143", // SSL.com TLS ECC Root CA 2022
|
||||||
|
"AD7DD58D03AEDB22A30B5084394920CE12230C2D8017AD9B81AB04079BDD026B", // SSL.com Client ECC Root CA 2022
|
||||||
|
"1D4CA4A2AB21D0093659804FC0EB2175A617279B56A2475245C9517AFEB59153", // SSL.com Client RSA Root CA 2022
|
||||||
|
"E38655F4B0190C84D3B3893D840A687E190A256D98052F159E6D4A39F589A6EB", // Atos TrustedRoot Root CA ECC G2 2020
|
||||||
|
"78833A783BB2986C254B9370D3C20E5EBA8FA7840CBF63FE17297A0B0119685E", // Atos TrustedRoot Root CA RSA G2 2020
|
||||||
|
"B2FAE53E14CCD7AB9212064701AE279C1D8988FACB775FA8A008914E663988A8", // Atos TrustedRoot Root CA ECC TLS 2021
|
||||||
|
"81A9088EA59FB364C548A6F85559099B6F0405EFBF18E5324EC9F457BA00112F", // Atos TrustedRoot Root CA RSA TLS 2021
|
||||||
|
"E0D3226AEB1163C2E48FF9BE3B50B4C6431BE7BB1EACC5C36B5D5EC509039A08", // TrustAsia Global Root CA G3
|
||||||
|
"BE4B56CB5056C0136A526DF444508DAA36A0B54F42E4AC38F72AF470E479654C", // TrustAsia Global Root CA G4
|
||||||
|
"D92C171F5CF890BA428019292927FE22F3207FD2B54449CB6F675AF4922146E2", // D-Trust SBR Root CA 1 2022
|
||||||
|
"DBA84DD7EF622D485463A90137EA4D574DF8550928F6AFA03B4D8B1141E636CC", // D-Trust SBR Root CA 2 2022
|
||||||
|
"3AE6DF7E0D637A65A8C81612EC6F9A142F85A16834C10280D88E707028518755", // Telekom Security SMIME ECC Root 2021
|
||||||
|
"578AF4DED0853F4E5998DB4AEAF9CBEA8D945F60B620A38D1A3C13B2BC7BA8E1", // Telekom Security TLS ECC Root 2020
|
||||||
|
"78A656344F947E9CC0F734D9053D32F6742086B6B9CD2CAE4FAE1A2E4EFDE048", // Telekom Security SMIME RSA Root 2023
|
||||||
|
"EFC65CADBB59ADB6EFE84DA22311B35624B71B3B1EA0DA8B6655174EC8978646", // Telekom Security TLS RSA Root 2023
|
||||||
|
"BEF256DAF26E9C69BDEC1602359798F3CAF71821A03E018257C53C65617F3D4A", // FIRMAPROFESIONAL CA ROOT-A WEB
|
||||||
|
"3F63BB2814BE174EC8B6439CF08D6D56F0B7C405883A5648A334424D6B3EC558", // TWCA CYBER Root CA
|
||||||
|
"3A0072D49FFC04E996C59AEB75991D3C340F3615D6FD4DCE90AC0B3D88EAD4F4", // TWCA Global Root CA G2
|
||||||
|
"3F034BB5704D44B2D08545A02057DE93EBF3905FCE721ACBC730C06DDAEE904E", // SecureSign Root CA12
|
||||||
|
"4B009C1034494F9AB56BBA3BA1D62731FC4D20D8955ADCEC10A925607261E338", // SecureSign Root CA14
|
||||||
|
"E778F0F095FE843729CD1A0082179E5314A9C291442805E1FB1D8FB6B8886C3A", // SecureSign Root CA15
|
||||||
|
"0552E6F83FDF65E8FA9670E666DF28A4E21340B510CBE52566F97C4FB94B2BD1", // D-TRUST BR Root CA 2 2023
|
||||||
|
"436472C1009A325C54F1A5BBB5468A7BAEECCBE05DE5F099CB70D3FE41E13C16", // TrustAsia SMIME ECC Root CA
|
||||||
|
"C7796BEB62C101BB143D262A7C96A0C6168183223EF50D699632D86E03B8CC9B", // TrustAsia SMIME RSA Root CA
|
||||||
|
"C0076B9EF0531FB1A656D67C4EBE97CD5DBAA41EF44598ACC2489878C92D8711", // TrustAsia TLS ECC Root CA
|
||||||
|
"06C08D7DAFD876971EB1124FE67F847EC0C7A158D3EA53CBE940E2EA9791F4C3", // TrustAsia TLS RSA Root CA
|
||||||
|
"8E8221B2E7D4007836A1672F0DCC299C33BC07D316F132FA1A206D587150F1CE", // D-TRUST EV Root CA 2 2023
|
||||||
|
"9A12C392BFE57891A0C545309D4D9FD567E480CB613D6342278B195C79A7931F", // SwissSign RSA SMIME Root CA 2022 - 1
|
||||||
|
"193144F431E0FDDB740717D4DE926A571133884B4360D30E272913CBE660CE41", // SwissSign RSA TLS Root CA 2022 - 1
|
||||||
|
"D9A32485A8CCA85539CEF12FFFFF711378A17851D73DA2732AB4302D763BD62B", // OISTE Client Root ECC G1
|
||||||
|
"D02A0F994A868C66395F2E7A880DF509BD0C29C96DE16015A0FD501EDA4F96A9", // OISTE Client Root RSA G1
|
||||||
|
"EEC997C0C30F216F7E3B8B307D2BAE42412D753FC8219DAFD1520B2572850F49", // OISTE Server Root ECC G1
|
||||||
|
"9AE36232A5189FFDDB353DFD26520C015395D22777DAC59DB57B98C089A651E6", // OISTE Server Root RSA G1
|
||||||
|
"B49141502D00663D740F2E7EC340C52800962666121A36D09CF7DD2B90384FB4", // e-Szigno TLS Root CA 2023
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get certificate in PEM format from a server with CA pinning validation
|
||||||
|
/// </summary>
|
||||||
|
public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (domain, _, port, _) = Utils.ParseUrl(target);
|
||||||
|
|
||||||
|
using var cts = new CancellationTokenSource();
|
||||||
|
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
|
||||||
|
|
||||||
|
using var client = new TcpClient();
|
||||||
|
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
|
||||||
|
|
||||||
|
await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
|
||||||
|
|
||||||
|
var sslOptions = new SslClientAuthenticationOptions
|
||||||
|
{
|
||||||
|
TargetHost = serverName,
|
||||||
|
RemoteCertificateValidationCallback = ValidateServerCertificate
|
||||||
|
};
|
||||||
|
|
||||||
|
await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
|
||||||
|
|
||||||
|
var remote = ssl.RemoteCertificate;
|
||||||
|
if (remote == null)
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var leaf = new X509Certificate2(remote);
|
||||||
|
return (ExportCertToPem(leaf), null);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, new TimeoutException($"Connection timeout after {timeout} seconds"));
|
||||||
|
return (null, $"Connection timeout after {timeout} seconds");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return (null, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get certificate chain in PEM format from a server with CA pinning validation
|
||||||
|
/// </summary>
|
||||||
|
public async Task<(List<string>, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4)
|
||||||
|
{
|
||||||
|
var pemList = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (domain, _, port, _) = Utils.ParseUrl(target);
|
||||||
|
|
||||||
|
using var cts = new CancellationTokenSource();
|
||||||
|
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
|
||||||
|
|
||||||
|
using var client = new TcpClient();
|
||||||
|
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
|
||||||
|
|
||||||
|
await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
|
||||||
|
|
||||||
|
var sslOptions = new SslClientAuthenticationOptions
|
||||||
|
{
|
||||||
|
TargetHost = serverName,
|
||||||
|
RemoteCertificateValidationCallback = ValidateServerCertificate
|
||||||
|
};
|
||||||
|
|
||||||
|
await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
|
||||||
|
|
||||||
|
if (ssl.RemoteCertificate is not X509Certificate2 certChain)
|
||||||
|
{
|
||||||
|
return (pemList, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var chain = new X509Chain();
|
||||||
|
chain.Build(certChain);
|
||||||
|
|
||||||
|
pemList.AddRange(chain.ChainElements.Select(element => ExportCertToPem(element.Certificate)));
|
||||||
|
|
||||||
|
return (pemList, null);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, new TimeoutException($"Connection timeout after {timeout} seconds"));
|
||||||
|
return (pemList, $"Connection timeout after {timeout} seconds");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
return (pemList, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validate server certificate with CA pinning
|
||||||
|
/// </summary>
|
||||||
|
private bool ValidateServerCertificate(
|
||||||
|
object sender,
|
||||||
|
X509Certificate? certificate,
|
||||||
|
X509Chain? chain,
|
||||||
|
SslPolicyErrors sslPolicyErrors)
|
||||||
|
{
|
||||||
|
if (certificate == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check certificate name mismatch
|
||||||
|
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build certificate chain
|
||||||
|
var cert2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate);
|
||||||
|
var certChain = chain ?? new X509Chain();
|
||||||
|
|
||||||
|
certChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
|
||||||
|
certChain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
|
||||||
|
certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
|
||||||
|
certChain.ChainPolicy.VerificationTime = DateTime.Now;
|
||||||
|
|
||||||
|
certChain.Build(cert2);
|
||||||
|
|
||||||
|
// Find root CA
|
||||||
|
if (certChain.ChainElements.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootCert = certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate;
|
||||||
|
var rootThumbprint = rootCert.GetCertHashString(HashAlgorithmName.SHA256);
|
||||||
|
|
||||||
|
return TrustedCaThumbprints.Contains(rootThumbprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ExportCertToPem(X509Certificate2 cert)
|
||||||
|
{
|
||||||
|
var der = cert.Export(X509ContentType.Cert);
|
||||||
|
var b64 = Convert.ToBase64String(der);
|
||||||
|
return $"-----BEGIN CERTIFICATE-----\n{b64}\n-----END CERTIFICATE-----\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse concatenated PEM certificates string into a list of individual certificates
|
||||||
|
/// Normalizes format: removes line breaks from base64 content for better compatibility
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pemChain">Concatenated PEM certificates string (supports both \r\n and \n line endings)</param>
|
||||||
|
/// <returns>List of individual PEM certificate strings with normalized format</returns>
|
||||||
|
public static List<string> ParsePemChain(string pemChain)
|
||||||
|
{
|
||||||
|
var certs = new List<string>();
|
||||||
|
if (string.IsNullOrWhiteSpace(pemChain))
|
||||||
|
{
|
||||||
|
return certs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize line endings (CRLF -> LF) at the beginning
|
||||||
|
pemChain = pemChain.Replace("\r\n", "\n").Replace("\r", "\n");
|
||||||
|
|
||||||
|
const string beginMarker = "-----BEGIN CERTIFICATE-----";
|
||||||
|
const string endMarker = "-----END CERTIFICATE-----";
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
while (index < pemChain.Length)
|
||||||
|
{
|
||||||
|
var beginIndex = pemChain.IndexOf(beginMarker, index, StringComparison.Ordinal);
|
||||||
|
if (beginIndex == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endIndex = pemChain.IndexOf(endMarker, beginIndex, StringComparison.Ordinal);
|
||||||
|
if (endIndex == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract certificate content
|
||||||
|
var base64Start = beginIndex + beginMarker.Length;
|
||||||
|
var base64Content = pemChain.Substring(base64Start, endIndex - base64Start);
|
||||||
|
|
||||||
|
// Remove all whitespace from base64 content
|
||||||
|
base64Content = new string(base64Content.Where(c => !char.IsWhiteSpace(c)).ToArray());
|
||||||
|
|
||||||
|
// Reconstruct with clean format: BEGIN marker + base64 (no line breaks) + END marker
|
||||||
|
var normalizedCert = $"{beginMarker}\n{base64Content}\n{endMarker}\n";
|
||||||
|
certs.Add(normalizedCert);
|
||||||
|
|
||||||
|
// Move to next certificate
|
||||||
|
index = endIndex + endMarker.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return certs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Concatenate a list of PEM certificates into a single string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pemList">List of individual PEM certificate strings</param>
|
||||||
|
/// <returns>Concatenated PEM certificates string</returns>
|
||||||
|
public static string ConcatenatePemChain(IEnumerable<string> pemList)
|
||||||
|
{
|
||||||
|
if (pemList == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Concat(pemList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cert = X509Certificate2.CreateFromPem(pemCert);
|
||||||
|
var thumbprint = cert.GetCertHashString(HashAlgorithmName.SHA256);
|
||||||
|
if (includeColon)
|
||||||
|
{
|
||||||
|
return string.Join(":", thumbprint.Chunk(2).Select(c => new string(c)));
|
||||||
|
}
|
||||||
|
return thumbprint;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,8 +34,8 @@ public class CoreAdminManager
|
|||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
sb.AppendLine("#!/bin/bash");
|
sb.AppendLine("#!/bin/bash");
|
||||||
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
|
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
|
||||||
sb.AppendLine($"sudo -S {cmdLine}");
|
sb.AppendLine($"exec sudo -S -- {cmdLine}");
|
||||||
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
|
var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
|
||||||
|
|
||||||
var procService = new ProcessService(
|
var procService = new ProcessService(
|
||||||
fileName: shFilePath,
|
fileName: shFilePath,
|
||||||
@@ -67,8 +67,8 @@ public class CoreAdminManager
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var shellFileName = Utils.IsOSX() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName;
|
var shellFileName = Utils.IsMacOS() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName;
|
||||||
var shFilePath = await FileManager.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
|
var shFilePath = await FileUtils.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
|
||||||
if (shFilePath.Contains(' '))
|
if (shFilePath.Contains(' '))
|
||||||
{
|
{
|
||||||
shFilePath = shFilePath.AppendQuotes();
|
shFilePath = shFilePath.AppendQuotes();
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public sealed class CoreInfoManager
|
|||||||
new CoreInfo
|
new CoreInfo
|
||||||
{
|
{
|
||||||
CoreType = ECoreType.mihomo,
|
CoreType = ECoreType.mihomo,
|
||||||
CoreExes = ["mihomo-windows-amd64-v1", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
|
CoreExes = GetMihomoCoreExes(),
|
||||||
Arguments = "-f {0}" + PortableMode(),
|
Arguments = "-f {0}" + PortableMode(),
|
||||||
Url = GetCoreUrl(ECoreType.mihomo),
|
Url = GetCoreUrl(ECoreType.mihomo),
|
||||||
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||||
@@ -248,4 +248,34 @@ public sealed class CoreInfoManager
|
|||||||
{
|
{
|
||||||
return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases";
|
return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<string>? GetMihomoCoreExes()
|
||||||
|
{
|
||||||
|
var names = new List<string>();
|
||||||
|
|
||||||
|
if (Utils.IsWindows())
|
||||||
|
{
|
||||||
|
names.Add("mihomo-windows-amd64-v1");
|
||||||
|
names.Add("mihomo-windows-amd64-compatible");
|
||||||
|
names.Add("mihomo-windows-amd64");
|
||||||
|
names.Add("mihomo-windows-arm64");
|
||||||
|
}
|
||||||
|
else if (Utils.IsLinux())
|
||||||
|
{
|
||||||
|
names.Add("mihomo-linux-amd64-v1");
|
||||||
|
names.Add("mihomo-linux-amd64");
|
||||||
|
names.Add("mihomo-linux-arm64");
|
||||||
|
}
|
||||||
|
else if (Utils.IsMacOS())
|
||||||
|
{
|
||||||
|
names.Add("mihomo-darwin-amd64-v1");
|
||||||
|
names.Add("mihomo-darwin-amd64");
|
||||||
|
names.Add("mihomo-darwin-arm64");
|
||||||
|
}
|
||||||
|
|
||||||
|
names.Add("clash");
|
||||||
|
names.Add("mihomo");
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class CoreManager
|
|||||||
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
||||||
public static CoreManager Instance => _instance.Value;
|
public static CoreManager Instance => _instance.Value;
|
||||||
private Config _config;
|
private Config _config;
|
||||||
private WindowsJob? _processJob;
|
private WindowsJobService? _processJob;
|
||||||
private ProcessService? _processService;
|
private ProcessService? _processService;
|
||||||
private ProcessService? _processPreService;
|
private ProcessService? _processPreService;
|
||||||
private bool _linuxSudo = false;
|
private bool _linuxSudo = false;
|
||||||
@@ -27,7 +27,7 @@ public class CoreManager
|
|||||||
var toPath = Utils.GetBinPath("");
|
var toPath = Utils.GetBinPath("");
|
||||||
if (fromPath != toPath)
|
if (fromPath != toPath)
|
||||||
{
|
{
|
||||||
FileManager.CopyDirectory(fromPath, toPath, true, false);
|
FileUtils.CopyDirectory(fromPath, toPath, true, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,17 @@ public class CoreManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
|
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
|
||||||
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
|
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node);
|
||||||
|
var preContext = ConfigHandler.GetPreSocksCoreConfigContext(context);
|
||||||
|
if (preContext is not null)
|
||||||
|
{
|
||||||
|
context = context with
|
||||||
|
{
|
||||||
|
TunProtectSsPort = preContext.TunProtectSsPort,
|
||||||
|
ProxyRelaySsPort = preContext.ProxyRelaySsPort,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
await UpdateFunc(true, result.Msg);
|
await UpdateFunc(true, result.Msg);
|
||||||
@@ -85,8 +95,8 @@ public class CoreManager
|
|||||||
await WindowsUtils.RemoveTunDevice();
|
await WindowsUtils.RemoveTunDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreStart(node);
|
await CoreStart(context);
|
||||||
await CoreStartPreService(node);
|
await CoreStartPreService(preContext);
|
||||||
if (_processService != null)
|
if (_processService != null)
|
||||||
{
|
{
|
||||||
await UpdateFunc(true, $"{node.GetSummary()}");
|
await UpdateFunc(true, $"{node.GetSummary()}");
|
||||||
@@ -122,7 +132,8 @@ public class CoreManager
|
|||||||
|
|
||||||
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
||||||
var configPath = Utils.GetBinConfigPath(fileName);
|
var configPath = Utils.GetBinConfigPath(fileName);
|
||||||
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
|
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node);
|
||||||
|
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -165,9 +176,10 @@ public class CoreManager
|
|||||||
|
|
||||||
#region Private
|
#region Private
|
||||||
|
|
||||||
private async Task CoreStart(ProfileItem node)
|
private async Task CoreStart(CoreConfigContext context)
|
||||||
{
|
{
|
||||||
var coreType = _config.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
var node = context.Node;
|
||||||
|
var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||||
|
|
||||||
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
||||||
@@ -179,17 +191,13 @@ public class CoreManager
|
|||||||
_processService = proc;
|
_processService = proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CoreStartPreService(ProfileItem node)
|
private async Task CoreStartPreService(CoreConfigContext? preContext)
|
||||||
{
|
{
|
||||||
if (_processService != null && !_processService.HasExited)
|
if (_processService is { HasExited: false } && preContext != null)
|
||||||
{
|
{
|
||||||
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
var preCoreType = preContext?.Node?.CoreType ?? ECoreType.sing_box;
|
||||||
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
|
|
||||||
if (itemSocks != null)
|
|
||||||
{
|
|
||||||
var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box;
|
|
||||||
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
|
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
|
||||||
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
|
var result = await CoreConfigHandler.GenerateClientConfig(preContext, fileName);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
|
||||||
@@ -202,7 +210,6 @@ public class CoreManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateFunc(bool notify, string msg)
|
private async Task UpdateFunc(bool notify, string msg)
|
||||||
{
|
{
|
||||||
@@ -226,7 +233,7 @@ public class CoreManager
|
|||||||
{
|
{
|
||||||
if (mayNeedSudo
|
if (mayNeedSudo
|
||||||
&& _config.TunModeItem.EnableTun
|
&& _config.TunModeItem.EnableTun
|
||||||
&& coreInfo.CoreType == ECoreType.sing_box
|
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo)
|
||||||
&& Utils.IsNonWindows())
|
&& Utils.IsNonWindows())
|
||||||
{
|
{
|
||||||
_linuxSudo = true;
|
_linuxSudo = true;
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
|
public class GroupProfileManager
|
||||||
|
{
|
||||||
|
public static async Task<bool> HasCycle(ProfileItem item)
|
||||||
|
{
|
||||||
|
return await HasCycle(item.IndexId, item.GetProtocolExtra());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo)
|
||||||
|
{
|
||||||
|
return await HasCycle(indexId, extraInfo, new HashSet<string>(), new HashSet<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo, HashSet<string> visited, HashSet<string> stack)
|
||||||
|
{
|
||||||
|
if (indexId.IsNullOrEmpty() || extraInfo == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.Contains(indexId))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visited.Contains(indexId))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
visited.Add(indexId);
|
||||||
|
stack.Add(indexId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (extraInfo.GroupType.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extraInfo.ChildItems.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var childIds = Utils.String2List(extraInfo.ChildItems)
|
||||||
|
?.Where(p => !string.IsNullOrEmpty(p))
|
||||||
|
.ToList();
|
||||||
|
if (childIds == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in childIds)
|
||||||
|
{
|
||||||
|
var childItem = await AppManager.Instance.GetProfileItem(child);
|
||||||
|
if (await HasCycle(child, childItem?.GetProtocolExtra(), visited, stack))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
stack.Remove(indexId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<(List<ProfileItem> Items, ProtocolExtraItem? Extra)> GetChildProfileItems(ProfileItem profileItem)
|
||||||
|
{
|
||||||
|
var protocolExtra = profileItem?.GetProtocolExtra();
|
||||||
|
return (await GetChildProfileItemsByProtocolExtra(protocolExtra), protocolExtra);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<ProfileItem>> GetChildProfileItemsByProtocolExtra(ProtocolExtraItem? protocolExtra)
|
||||||
|
{
|
||||||
|
if (protocolExtra == null)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = new List<ProfileItem>();
|
||||||
|
items.AddRange(await GetSubChildProfileItems(protocolExtra));
|
||||||
|
items.AddRange(await GetSelectedChildProfileItems(protocolExtra));
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<ProfileItem>> GetSelectedChildProfileItems(ProtocolExtraItem? extra)
|
||||||
|
{
|
||||||
|
if (extra == null || extra.ChildItems.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
var childProfileIds = Utils.String2List(extra.ChildItems)
|
||||||
|
?.Where(p => !string.IsNullOrEmpty(p))
|
||||||
|
.ToList() ?? [];
|
||||||
|
if (childProfileIds.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var childProfiles = await AppManager.Instance.GetProfileItemsByIndexIds(childProfileIds);
|
||||||
|
if (childProfiles == null || childProfiles.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var profileMap = childProfiles
|
||||||
|
.Where(p => p != null && !p.IndexId.IsNullOrEmpty())
|
||||||
|
.GroupBy(p => p!.IndexId!)
|
||||||
|
.ToDictionary(g => g.Key, g => g.First());
|
||||||
|
|
||||||
|
var ordered = new List<ProfileItem>(childProfileIds.Count);
|
||||||
|
foreach (var id in childProfileIds)
|
||||||
|
{
|
||||||
|
if (id != null && profileMap.TryGetValue(id, out var item) && item != null)
|
||||||
|
{
|
||||||
|
ordered.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<ProfileItem>> GetSubChildProfileItems(ProtocolExtraItem? extra)
|
||||||
|
{
|
||||||
|
if (extra == null || extra.SubChildItems.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty);
|
||||||
|
|
||||||
|
return childProfiles?.Where(p =>
|
||||||
|
p != null &&
|
||||||
|
p.IsValid() &&
|
||||||
|
!p.ConfigType.IsComplexType() &&
|
||||||
|
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
|
||||||
|
)
|
||||||
|
.ToList() ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<ProfileItem>> GetAllChildProfileItems(ProfileItem profileItem)
|
||||||
|
{
|
||||||
|
var allChildItems = new List<ProfileItem>();
|
||||||
|
var visited = new HashSet<string>();
|
||||||
|
|
||||||
|
await CollectChildItems(profileItem, allChildItems, visited);
|
||||||
|
|
||||||
|
return allChildItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CollectChildItems(ProfileItem profileItem, List<ProfileItem> allChildItems, HashSet<string> visited)
|
||||||
|
{
|
||||||
|
var (childItems, _) = await GetChildProfileItems(profileItem);
|
||||||
|
foreach (var child in childItems.Where(child => visited.Add(child.IndexId)))
|
||||||
|
{
|
||||||
|
allChildItems.Add(child);
|
||||||
|
|
||||||
|
if (child.ConfigType.IsGroupType())
|
||||||
|
{
|
||||||
|
await CollectChildItems(child, allChildItems, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ public class PacManager
|
|||||||
private static readonly Lazy<PacManager> _instance = new(() => new PacManager());
|
private static readonly Lazy<PacManager> _instance = new(() => new PacManager());
|
||||||
public static PacManager Instance => _instance.Value;
|
public static PacManager Instance => _instance.Value;
|
||||||
|
|
||||||
private string _configPath;
|
|
||||||
private int _httpPort;
|
private int _httpPort;
|
||||||
private int _pacPort;
|
private int _pacPort;
|
||||||
private TcpListener? _tcpListener;
|
private TcpListener? _tcpListener;
|
||||||
@@ -13,11 +12,10 @@ public class PacManager
|
|||||||
private bool _isRunning;
|
private bool _isRunning;
|
||||||
private bool _needRestart = true;
|
private bool _needRestart = true;
|
||||||
|
|
||||||
public async Task StartAsync(string configPath, int httpPort, int pacPort)
|
public async Task StartAsync(int httpPort, int pacPort)
|
||||||
{
|
{
|
||||||
_needRestart = configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning;
|
_needRestart = httpPort != _httpPort || pacPort != _pacPort || !_isRunning;
|
||||||
|
|
||||||
_configPath = configPath;
|
|
||||||
_httpPort = httpPort;
|
_httpPort = httpPort;
|
||||||
_pacPort = pacPort;
|
_pacPort = pacPort;
|
||||||
|
|
||||||
@@ -32,22 +30,22 @@ public class PacManager
|
|||||||
|
|
||||||
private async Task InitText()
|
private async Task InitText()
|
||||||
{
|
{
|
||||||
var path = Path.Combine(_configPath, "pac.txt");
|
var customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyPacPath;
|
||||||
|
var fileName = (customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath))
|
||||||
|
? customSystemProxyPacPath
|
||||||
|
: Path.Combine(Utils.GetConfigPath(), "pac.txt");
|
||||||
|
|
||||||
// Delete the old pac file
|
// TODO: temporarily notify which script is being used
|
||||||
if (File.Exists(path) && Utils.GetFileHash(path).Equals("b590c07280f058ef05d5394aa2f927fe"))
|
NoticeManager.Instance.SendMessage(fileName);
|
||||||
{
|
|
||||||
File.Delete(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(fileName))
|
||||||
{
|
{
|
||||||
var pac = EmbedUtils.GetEmbedText(Global.PacFileName);
|
var pac = EmbedUtils.GetEmbedText(Global.PacFileName);
|
||||||
await File.AppendAllTextAsync(path, pac);
|
await File.AppendAllTextAsync(fileName, pac);
|
||||||
}
|
}
|
||||||
|
|
||||||
var pacText =
|
var pacText = await File.ReadAllTextAsync(fileName);
|
||||||
(await File.ReadAllTextAsync(path)).Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;");
|
pacText = pacText.Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;");
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("HTTP/1.0 200 OK");
|
sb.AppendLine("HTTP/1.0 200 OK");
|
||||||
|
|||||||
@@ -1,284 +0,0 @@
|
|||||||
namespace ServiceLib.Manager;
|
|
||||||
|
|
||||||
public class ProfileGroupItemManager
|
|
||||||
{
|
|
||||||
private static readonly Lazy<ProfileGroupItemManager> _instance = new(() => new());
|
|
||||||
private ConcurrentDictionary<string, ProfileGroupItem> _items = new();
|
|
||||||
|
|
||||||
public static ProfileGroupItemManager Instance => _instance.Value;
|
|
||||||
private static readonly string _tag = "ProfileGroupItemManager";
|
|
||||||
|
|
||||||
private ProfileGroupItemManager()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Init()
|
|
||||||
{
|
|
||||||
await InitData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read-only getters: do not create or mark dirty
|
|
||||||
public bool TryGet(string indexId, out ProfileGroupItem? item)
|
|
||||||
{
|
|
||||||
item = null;
|
|
||||||
if (string.IsNullOrWhiteSpace(indexId))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _items.TryGetValue(indexId, out item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProfileGroupItem? GetOrDefault(string indexId)
|
|
||||||
{
|
|
||||||
return string.IsNullOrWhiteSpace(indexId) ? null : (_items.TryGetValue(indexId, out var v) ? v : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitData()
|
|
||||||
{
|
|
||||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem where IndexId not in ( select indexId from ProfileItem )");
|
|
||||||
|
|
||||||
var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
|
|
||||||
_items = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProfileGroupItem AddProfileGroupItem(string indexId)
|
|
||||||
{
|
|
||||||
var profileGroupItem = new ProfileGroupItem()
|
|
||||||
{
|
|
||||||
IndexId = indexId,
|
|
||||||
ChildItems = string.Empty,
|
|
||||||
MultipleLoad = EMultipleLoad.LeastPing
|
|
||||||
};
|
|
||||||
|
|
||||||
_items[indexId] = profileGroupItem;
|
|
||||||
return profileGroupItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProfileGroupItem GetProfileGroupItem(string indexId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(indexId))
|
|
||||||
{
|
|
||||||
indexId = Utils.GetGuid(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _items.GetOrAdd(indexId, AddProfileGroupItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ClearAll()
|
|
||||||
{
|
|
||||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem ");
|
|
||||||
_items.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SaveTo()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var lstExists = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
|
|
||||||
var existsMap = lstExists.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!);
|
|
||||||
|
|
||||||
var lstInserts = new List<ProfileGroupItem>();
|
|
||||||
var lstUpdates = new List<ProfileGroupItem>();
|
|
||||||
|
|
||||||
foreach (var item in _items.Values)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(item.IndexId))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existsMap.ContainsKey(item.IndexId))
|
|
||||||
{
|
|
||||||
lstUpdates.Add(item);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
lstInserts.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (lstInserts.Count > 0)
|
|
||||||
{
|
|
||||||
await SQLiteHelper.Instance.InsertAllAsync(lstInserts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lstUpdates.Count > 0)
|
|
||||||
{
|
|
||||||
await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProfileGroupItem GetOrCreateAndMarkDirty(string indexId)
|
|
||||||
{
|
|
||||||
return GetProfileGroupItem(indexId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
await SaveTo();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SaveItemAsync(ProfileGroupItem item)
|
|
||||||
{
|
|
||||||
if (item is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(item.IndexId))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("IndexId required", nameof(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
_items[item.IndexId] = item;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var lst = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().Where(t => t.IndexId == item.IndexId).ToListAsync();
|
|
||||||
if (lst != null && lst.Count > 0)
|
|
||||||
{
|
|
||||||
await SQLiteHelper.Instance.UpdateAllAsync(new List<ProfileGroupItem> { item });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await SQLiteHelper.Instance.InsertAllAsync(new List<ProfileGroupItem> { item });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Helper
|
|
||||||
|
|
||||||
public static bool HasCycle(string? indexId)
|
|
||||||
{
|
|
||||||
return HasCycle(indexId, new HashSet<string>(), new HashSet<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool HasCycle(string? indexId, HashSet<string> visited, HashSet<string> stack)
|
|
||||||
{
|
|
||||||
if (indexId.IsNullOrEmpty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (stack.Contains(indexId))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (visited.Contains(indexId))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
visited.Add(indexId);
|
|
||||||
stack.Add(indexId);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Instance.TryGet(indexId, out var groupItem);
|
|
||||||
|
|
||||||
if (groupItem == null || groupItem.ChildItems.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var childIds = Utils.String2List(groupItem.ChildItems)
|
|
||||||
.Where(p => !string.IsNullOrEmpty(p))
|
|
||||||
.ToList();
|
|
||||||
if (childIds == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var child in childIds)
|
|
||||||
{
|
|
||||||
if (HasCycle(child, visited, stack))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
stack.Remove(indexId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<(List<ProfileItem> Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId)
|
|
||||||
{
|
|
||||||
Instance.TryGet(indexId, out var profileGroupItem);
|
|
||||||
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return (new List<ProfileItem>(), profileGroupItem);
|
|
||||||
}
|
|
||||||
var items = await GetChildProfileItems(profileGroupItem);
|
|
||||||
return (items, profileGroupItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<List<ProfileItem>> GetChildProfileItems(ProfileGroupItem? group)
|
|
||||||
{
|
|
||||||
if (group == null || group.ChildItems.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return new();
|
|
||||||
}
|
|
||||||
var childProfiles = (await Task.WhenAll(
|
|
||||||
Utils.String2List(group.ChildItems)
|
|
||||||
.Where(p => !p.IsNullOrEmpty())
|
|
||||||
.Select(AppManager.Instance.GetProfileItem)
|
|
||||||
))
|
|
||||||
.Where(p =>
|
|
||||||
p != null &&
|
|
||||||
p.IsValid() &&
|
|
||||||
p.ConfigType != EConfigType.Custom
|
|
||||||
)
|
|
||||||
.ToList();
|
|
||||||
return childProfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<HashSet<string>> GetAllChildDomainAddresses(string indexId)
|
|
||||||
{
|
|
||||||
// include grand children
|
|
||||||
var childAddresses = new HashSet<string>();
|
|
||||||
if (!Instance.TryGet(indexId, out var groupItem) || groupItem.ChildItems.IsNullOrEmpty())
|
|
||||||
return childAddresses;
|
|
||||||
|
|
||||||
var childIds = Utils.String2List(groupItem.ChildItems);
|
|
||||||
|
|
||||||
foreach (var childId in childIds)
|
|
||||||
{
|
|
||||||
var childNode = await AppManager.Instance.GetProfileItem(childId);
|
|
||||||
if (childNode == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!childNode.IsComplex())
|
|
||||||
{
|
|
||||||
childAddresses.Add(childNode.Address);
|
|
||||||
}
|
|
||||||
else if (childNode.ConfigType.IsGroupType())
|
|
||||||
{
|
|
||||||
var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId);
|
|
||||||
foreach (var addr in subAddresses)
|
|
||||||
{
|
|
||||||
childAddresses.Add(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return childAddresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Helper
|
|
||||||
}
|
|
||||||
@@ -56,9 +56,9 @@ public class TaskManager
|
|||||||
{
|
{
|
||||||
//Logging.SaveLog("Execute delete expired files");
|
//Logging.SaveLog("Execute delete expired files");
|
||||||
|
|
||||||
FileManager.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
|
FileUtils.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
|
||||||
FileManager.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
|
FileUtils.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
|
||||||
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
|
FileUtils.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -111,11 +111,10 @@ public class TaskManager
|
|||||||
{
|
{
|
||||||
Logging.SaveLog("Execute update geo files");
|
Logging.SaveLog("Execute update geo files");
|
||||||
|
|
||||||
var updateHandle = new UpdateService();
|
await new UpdateService(_config, async (success, msg) =>
|
||||||
await updateHandle.UpdateGeoFileAll(_config, async (success, msg) =>
|
|
||||||
{
|
{
|
||||||
await _updateFunc?.Invoke(false, msg);
|
await _updateFunc?.Invoke(false, msg);
|
||||||
});
|
}).UpdateGeoFileAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,9 +43,12 @@ public sealed class WebDavManager
|
|||||||
_webDir = _config.WebDavItem.DirName.TrimEx();
|
_webDir = _config.WebDavItem.DirName.TrimEx();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure BaseAddress URL ends with a trailing slash
|
||||||
|
var baseUrl = _config.WebDavItem.Url.Trim().TrimEnd('/') + "/";
|
||||||
|
|
||||||
var clientParams = new WebDavClientParams
|
var clientParams = new WebDavClientParams
|
||||||
{
|
{
|
||||||
BaseAddress = new Uri(_config.WebDavItem.Url),
|
BaseAddress = new Uri(baseUrl),
|
||||||
Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password)
|
Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password)
|
||||||
};
|
};
|
||||||
_client = new WebDavClient(clientParams);
|
_client = new WebDavClient(clientParams);
|
||||||
|
|||||||
@@ -8,21 +8,6 @@ public class Config
|
|||||||
public string IndexId { get; set; }
|
public string IndexId { get; set; }
|
||||||
public string SubIndexId { get; set; }
|
public string SubIndexId { get; set; }
|
||||||
|
|
||||||
public ECoreType RunningCoreType { get; set; }
|
|
||||||
|
|
||||||
public bool IsRunningCore(ECoreType type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion property
|
#endregion property
|
||||||
|
|
||||||
#region other entities
|
#region other entities
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ public class UIItem
|
|||||||
public bool DoubleClick2Activate { get; set; }
|
public bool DoubleClick2Activate { get; set; }
|
||||||
public bool AutoHideStartup { get; set; }
|
public bool AutoHideStartup { get; set; }
|
||||||
public bool Hide2TrayWhenClose { get; set; }
|
public bool Hide2TrayWhenClose { get; set; }
|
||||||
public bool ShowInTaskbar { get; set; }
|
|
||||||
public bool MacOSShowInDock { get; set; }
|
public bool MacOSShowInDock { get; set; }
|
||||||
public List<ColumnItem> MainColumnItem { get; set; }
|
public List<ColumnItem> MainColumnItem { get; set; }
|
||||||
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
||||||
@@ -145,7 +144,6 @@ public class TunModeItem
|
|||||||
public bool StrictRoute { get; set; } = true;
|
public bool StrictRoute { get; set; } = true;
|
||||||
public string Stack { get; set; }
|
public string Stack { get; set; }
|
||||||
public int Mtu { get; set; }
|
public int Mtu { get; set; }
|
||||||
public bool EnableExInbound { get; set; }
|
|
||||||
public bool EnableIPv6Address { get; set; }
|
public bool EnableIPv6Address { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +217,8 @@ public class SystemProxyItem
|
|||||||
public string SystemProxyExceptions { get; set; }
|
public string SystemProxyExceptions { get; set; }
|
||||||
public bool NotProxyLocalAddress { get; set; } = true;
|
public bool NotProxyLocalAddress { get; set; } = true;
|
||||||
public string SystemProxyAdvancedProtocol { get; set; }
|
public string SystemProxyAdvancedProtocol { get; set; }
|
||||||
|
public string? CustomSystemProxyPacPath { get; set; }
|
||||||
|
public string? CustomSystemProxyScriptPath { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
@@ -264,9 +264,10 @@ public class SimpleDNSItem
|
|||||||
public string? DirectDNS { get; set; }
|
public string? DirectDNS { get; set; }
|
||||||
public string? RemoteDNS { get; set; }
|
public string? RemoteDNS { get; set; }
|
||||||
public string? BootstrapDNS { get; set; }
|
public string? BootstrapDNS { get; set; }
|
||||||
public string? RayStrategy4Freedom { get; set; }
|
public string? Strategy4Freedom { get; set; }
|
||||||
public string? SingboxStrategy4Direct { get; set; }
|
public string? Strategy4Proxy { get; set; }
|
||||||
public string? SingboxStrategy4Proxy { get; set; }
|
public bool? ServeStale { get; set; }
|
||||||
|
public bool? ParallelQuery { get; set; }
|
||||||
public string? Hosts { get; set; }
|
public string? Hosts { get; set; }
|
||||||
public string? DirectExpectedIPs { get; set; }
|
public string? DirectExpectedIPs { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
|
public record CoreConfigContext
|
||||||
|
{
|
||||||
|
public required ProfileItem Node { get; init; }
|
||||||
|
public RoutingItem? RoutingItem { get; init; }
|
||||||
|
public DNSItem? RawDnsItem { get; init; }
|
||||||
|
public SimpleDNSItem SimpleDnsItem { get; init; } = new();
|
||||||
|
public Dictionary<string, ProfileItem> AllProxiesMap { get; init; } = new();
|
||||||
|
public Config AppConfig { get; init; } = new();
|
||||||
|
public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new();
|
||||||
|
|
||||||
|
// Test ServerTestItem Map
|
||||||
|
public Dictionary<string, string> ServerTestItemMap { get; init; } = new();
|
||||||
|
|
||||||
|
// TUN Compatibility
|
||||||
|
public bool IsTunEnabled { get; init; } = false;
|
||||||
|
public HashSet<string> ProtectDomainList { get; init; } = new();
|
||||||
|
// -> tun inbound --(if routing proxy)--> relay outbound
|
||||||
|
// -> proxy core (relay inbound --> proxy outbound --(dialerProxy)--> protect outbound)
|
||||||
|
// -> protect inbound -> direct proxy outbound data -> internet
|
||||||
|
public int TunProtectSsPort { get; init; } = 0;
|
||||||
|
public int ProxyRelaySsPort { get; init; } = 0;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace ServiceLib.Models;
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
|
[Obsolete("Use ProtocolExtraItem instead.")]
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ProfileGroupItem
|
public class ProfileGroupItem
|
||||||
{
|
{
|
||||||
@@ -8,5 +9,14 @@ public class ProfileGroupItem
|
|||||||
|
|
||||||
public string ChildItems { get; set; }
|
public string ChildItems { get; set; }
|
||||||
|
|
||||||
|
public string? SubChildItems { get; set; }
|
||||||
|
|
||||||
|
public string? Filter { get; set; }
|
||||||
|
|
||||||
public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing;
|
public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing;
|
||||||
|
|
||||||
|
public bool NotHasChild()
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(ChildItems) && string.IsNullOrWhiteSpace(SubChildItems);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
namespace ServiceLib.Models;
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ProfileItem : ReactiveObject
|
public class ProfileItem
|
||||||
{
|
{
|
||||||
|
private ProtocolExtraItem? _protocolExtraCache;
|
||||||
|
|
||||||
public ProfileItem()
|
public ProfileItem()
|
||||||
{
|
{
|
||||||
IndexId = string.Empty;
|
IndexId = string.Empty;
|
||||||
ConfigType = EConfigType.VMess;
|
ConfigType = EConfigType.VMess;
|
||||||
ConfigVersion = 2;
|
ConfigVersion = 3;
|
||||||
|
Subid = string.Empty;
|
||||||
Address = string.Empty;
|
Address = string.Empty;
|
||||||
Port = 0;
|
Port = 0;
|
||||||
Id = string.Empty;
|
Password = string.Empty;
|
||||||
AlterId = 0;
|
Username = string.Empty;
|
||||||
Security = string.Empty;
|
|
||||||
Network = string.Empty;
|
Network = string.Empty;
|
||||||
Remarks = string.Empty;
|
Remarks = string.Empty;
|
||||||
HeaderType = string.Empty;
|
HeaderType = string.Empty;
|
||||||
@@ -20,15 +22,13 @@ public class ProfileItem : ReactiveObject
|
|||||||
Path = string.Empty;
|
Path = string.Empty;
|
||||||
StreamSecurity = string.Empty;
|
StreamSecurity = string.Empty;
|
||||||
AllowInsecure = string.Empty;
|
AllowInsecure = string.Empty;
|
||||||
Subid = string.Empty;
|
|
||||||
Flow = string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region function
|
#region function
|
||||||
|
|
||||||
public string GetSummary()
|
public string GetSummary()
|
||||||
{
|
{
|
||||||
var summary = $"[{(ConfigType).ToString()}] ";
|
var summary = $"[{ConfigType.ToString()}] ";
|
||||||
if (IsComplex())
|
if (IsComplex())
|
||||||
{
|
{
|
||||||
summary += $"[{CoreType.ToString()}]{Remarks}";
|
summary += $"[{CoreType.ToString()}]{Remarks}";
|
||||||
@@ -69,30 +69,50 @@ public class ProfileItem : ReactiveObject
|
|||||||
public bool IsValid()
|
public bool IsValid()
|
||||||
{
|
{
|
||||||
if (IsComplex())
|
if (IsComplex())
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (Address.IsNullOrEmpty() || Port is <= 0 or >= 65536)
|
if (Address.IsNullOrEmpty() || Port is <= 0 or >= 65536)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
switch (ConfigType)
|
switch (ConfigType)
|
||||||
{
|
{
|
||||||
case EConfigType.VMess:
|
case EConfigType.VMess:
|
||||||
if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id))
|
if (Password.IsNullOrEmpty() || !Utils.IsGuidByParse(Password))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EConfigType.VLESS:
|
case EConfigType.VLESS:
|
||||||
if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30))
|
if (Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(Password) && Password.Length > 30))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
if (!Global.Flows.Contains(Flow))
|
}
|
||||||
|
|
||||||
|
if (!Global.Flows.Contains(GetProtocolExtra().Flow ?? string.Empty))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EConfigType.Shadowsocks:
|
case EConfigType.Shadowsocks:
|
||||||
if (Id.IsNullOrEmpty())
|
if (Password.IsNullOrEmpty())
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security))
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(GetProtocolExtra().SsMethod)
|
||||||
|
|| !Global.SsSecuritiesInSingbox.Contains(GetProtocolExtra().SsMethod))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,39 +126,74 @@ public class ProfileItem : ReactiveObject
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetProtocolExtra(ProtocolExtraItem extraItem)
|
||||||
|
{
|
||||||
|
_protocolExtraCache = extraItem;
|
||||||
|
ProtoExtra = JsonUtils.Serialize(extraItem, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetProtocolExtra()
|
||||||
|
{
|
||||||
|
ProtoExtra = JsonUtils.Serialize(_protocolExtraCache, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProtocolExtraItem GetProtocolExtra()
|
||||||
|
{
|
||||||
|
return _protocolExtraCache ??= JsonUtils.Deserialize<ProtocolExtraItem>(ProtoExtra) ?? new ProtocolExtraItem();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion function
|
#endregion function
|
||||||
|
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public string IndexId { get; set; }
|
public string IndexId { get; set; }
|
||||||
|
|
||||||
public EConfigType ConfigType { get; set; }
|
public EConfigType ConfigType { get; set; }
|
||||||
|
public ECoreType? CoreType { get; set; }
|
||||||
public int ConfigVersion { get; set; }
|
public int ConfigVersion { get; set; }
|
||||||
|
public string Subid { get; set; }
|
||||||
|
public bool IsSub { get; set; } = true;
|
||||||
|
public int? PreSocksPort { get; set; }
|
||||||
|
public bool DisplayLog { get; set; } = true;
|
||||||
|
public string Remarks { get; set; }
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
public string Ports { get; set; }
|
public string Password { get; set; }
|
||||||
public string Id { get; set; }
|
public string Username { get; set; }
|
||||||
public int AlterId { get; set; }
|
|
||||||
public string Security { get; set; }
|
|
||||||
public string Network { get; set; }
|
public string Network { get; set; }
|
||||||
public string Remarks { get; set; }
|
|
||||||
public string HeaderType { get; set; }
|
public string HeaderType { get; set; }
|
||||||
public string RequestHost { get; set; }
|
public string RequestHost { get; set; }
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public string StreamSecurity { get; set; }
|
public string StreamSecurity { get; set; }
|
||||||
public string AllowInsecure { get; set; }
|
public string AllowInsecure { get; set; }
|
||||||
public string Subid { get; set; }
|
|
||||||
public bool IsSub { get; set; } = true;
|
|
||||||
public string Flow { get; set; }
|
|
||||||
public string Sni { get; set; }
|
public string Sni { get; set; }
|
||||||
public string Alpn { get; set; } = string.Empty;
|
public string Alpn { get; set; } = string.Empty;
|
||||||
public ECoreType? CoreType { get; set; }
|
|
||||||
public int? PreSocksPort { get; set; }
|
|
||||||
public string Fingerprint { get; set; }
|
public string Fingerprint { get; set; }
|
||||||
public bool DisplayLog { get; set; } = true;
|
|
||||||
public string PublicKey { get; set; }
|
public string PublicKey { get; set; }
|
||||||
public string ShortId { get; set; }
|
public string ShortId { get; set; }
|
||||||
public string SpiderX { get; set; }
|
public string SpiderX { get; set; }
|
||||||
public string Mldsa65Verify { get; set; }
|
public string Mldsa65Verify { get; set; }
|
||||||
public string Extra { get; set; }
|
public string Extra { get; set; }
|
||||||
public bool? MuxEnabled { get; set; }
|
public bool? MuxEnabled { get; set; }
|
||||||
|
public string Cert { get; set; }
|
||||||
|
public string CertSha { get; set; }
|
||||||
|
public string EchConfigList { get; set; }
|
||||||
|
public string EchForceQuery { get; set; }
|
||||||
|
public string Finalmask { get; set; }
|
||||||
|
|
||||||
|
public string ProtoExtra { get; set; }
|
||||||
|
|
||||||
|
[Obsolete("Use ProtocolExtraItem.Ports instead.")]
|
||||||
|
public string Ports { get; set; }
|
||||||
|
|
||||||
|
[Obsolete("Use ProtocolExtraItem.AlterId instead.")]
|
||||||
|
public int AlterId { get; set; }
|
||||||
|
|
||||||
|
[Obsolete("Use ProtocolExtraItem.Flow instead.")]
|
||||||
|
public string Flow { get; set; }
|
||||||
|
|
||||||
|
[Obsolete("Use ProfileItem.Password instead.")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[Obsolete("Use ProtocolExtraItem.xxx instead.")]
|
||||||
|
public string Security { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
namespace ServiceLib.Models;
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ProfileItemModel : ProfileItem
|
public class ProfileItemModel : ReactiveObject
|
||||||
{
|
{
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
|
public string IndexId { get; set; }
|
||||||
|
public EConfigType ConfigType { get; set; }
|
||||||
|
public string Remarks { get; set; }
|
||||||
|
public string Address { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
public string Network { get; set; }
|
||||||
|
public string StreamSecurity { get; set; }
|
||||||
|
public string Subid { get; set; }
|
||||||
public string SubRemarks { get; set; }
|
public string SubRemarks { get; set; }
|
||||||
|
public int Sort { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int Delay { get; set; }
|
public int Delay { get; set; }
|
||||||
|
|
||||||
public decimal Speed { get; set; }
|
public decimal Speed { get; set; }
|
||||||
public int Sort { get; set; }
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string DelayVal { get; set; }
|
public string DelayVal { get; set; }
|
||||||
@@ -29,4 +37,15 @@ public class ProfileItemModel : ProfileItem
|
|||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string TotalDown { get; set; }
|
public string TotalDown { get; set; }
|
||||||
|
|
||||||
|
public string GetSummary()
|
||||||
|
{
|
||||||
|
var summary = $"[{ConfigType}] {Remarks}";
|
||||||
|
if (!ConfigType.IsComplexType())
|
||||||
|
{
|
||||||
|
summary += $"({Address}:{Port})";
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
|
public record ProtocolExtraItem
|
||||||
|
{
|
||||||
|
// vmess
|
||||||
|
public string? AlterId { get; init; }
|
||||||
|
public string? VmessSecurity { get; init; }
|
||||||
|
|
||||||
|
// vless
|
||||||
|
public string? Flow { get; init; }
|
||||||
|
public string? VlessEncryption { get; init; }
|
||||||
|
//public string? VisionSeed { get; init; }
|
||||||
|
|
||||||
|
// shadowsocks
|
||||||
|
//public string? PluginArgs { get; init; }
|
||||||
|
public string? SsMethod { get; init; }
|
||||||
|
|
||||||
|
// wireguard
|
||||||
|
public string? WgPublicKey { get; init; }
|
||||||
|
public string? WgPresharedKey { get; init; }
|
||||||
|
public string? WgInterfaceAddress { get; init; }
|
||||||
|
public string? WgReserved { get; init; }
|
||||||
|
public int? WgMtu { get; init; }
|
||||||
|
|
||||||
|
// hysteria2
|
||||||
|
public string? SalamanderPass { get; init; }
|
||||||
|
public int? UpMbps { get; init; }
|
||||||
|
public int? DownMbps { get; init; }
|
||||||
|
public string? Ports { get; init; }
|
||||||
|
public string? HopInterval { get; init; }
|
||||||
|
|
||||||
|
// group profile
|
||||||
|
public string? GroupType { get; init; }
|
||||||
|
public string? ChildItems { get; init; }
|
||||||
|
public string? SubChildItems { get; init; }
|
||||||
|
public string? Filter { get; init; }
|
||||||
|
public EMultipleLoad? MultipleLoad { get; init; }
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace ServiceLib.Common;
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
public class SemanticVersion
|
public class SemanticVersion
|
||||||
{
|
{
|
||||||
@@ -68,6 +68,7 @@ public class Rule4Sbox
|
|||||||
public List<string>? ip_cidr { get; set; }
|
public List<string>? ip_cidr { get; set; }
|
||||||
public List<string>? source_ip_cidr { get; set; }
|
public List<string>? source_ip_cidr { get; set; }
|
||||||
public List<string>? process_name { get; set; }
|
public List<string>? process_name { get; set; }
|
||||||
|
public List<string>? process_path { get; set; }
|
||||||
public List<string>? rule_set { get; set; }
|
public List<string>? rule_set { get; set; }
|
||||||
public List<Rule4Sbox>? rules { get; set; }
|
public List<Rule4Sbox>? rules { get; set; }
|
||||||
public string? action { get; set; }
|
public string? action { get; set; }
|
||||||
@@ -181,6 +182,15 @@ public class Tls4Sbox
|
|||||||
public bool? fragment { get; set; }
|
public bool? fragment { get; set; }
|
||||||
public string? fragment_fallback_delay { get; set; }
|
public string? fragment_fallback_delay { get; set; }
|
||||||
public bool? record_fragment { get; set; }
|
public bool? record_fragment { get; set; }
|
||||||
|
public List<string>? certificate { get; set; }
|
||||||
|
public Ech4Sbox? ech { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Ech4Sbox
|
||||||
|
{
|
||||||
|
public bool enabled { get; set; }
|
||||||
|
public List<string>? config { get; set; }
|
||||||
|
public string? query_server_name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Multiplex4Sbox
|
public class Multiplex4Sbox
|
||||||
@@ -215,6 +225,8 @@ public class Transport4Sbox
|
|||||||
public string? idle_timeout { get; set; }
|
public string? idle_timeout { get; set; }
|
||||||
public string? ping_timeout { get; set; }
|
public string? ping_timeout { get; set; }
|
||||||
public bool? permit_without_stream { get; set; }
|
public bool? permit_without_stream { get; set; }
|
||||||
|
public int? max_early_data { get; set; }
|
||||||
|
public string? early_data_header_name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Headers4Sbox
|
public class Headers4Sbox
|
||||||
@@ -243,7 +255,7 @@ public class Server4Sbox : BaseServer4Sbox
|
|||||||
// public List<string>? path { get; set; } // hosts
|
// public List<string>? path { get; set; } // hosts
|
||||||
public Dictionary<string, List<string>>? predefined { get; set; }
|
public Dictionary<string, List<string>>? predefined { get; set; }
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated in sing-box 1.12.0 , kept for backward compatibility
|
||||||
public string? address { get; set; }
|
public string? address { get; set; }
|
||||||
|
|
||||||
public string? address_resolver { get; set; }
|
public string? address_resolver { get; set; }
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
|
public class UpdateResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Msg { get; set; }
|
||||||
|
public SemanticVersion? Version { get; set; }
|
||||||
|
public string? Url { get; set; }
|
||||||
|
|
||||||
|
public UpdateResult(bool success, string? msg)
|
||||||
|
{
|
||||||
|
Success = success;
|
||||||
|
Msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateResult(bool success, SemanticVersion? version)
|
||||||
|
{
|
||||||
|
Success = success;
|
||||||
|
Version = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ namespace ServiceLib.Models;
|
|||||||
public class V2rayConfig
|
public class V2rayConfig
|
||||||
{
|
{
|
||||||
public Log4Ray log { get; set; }
|
public Log4Ray log { get; set; }
|
||||||
public Dns4Ray dns { get; set; }
|
public object dns { get; set; }
|
||||||
public List<Inbounds4Ray> inbounds { get; set; }
|
public List<Inbounds4Ray> inbounds { get; set; }
|
||||||
public List<Outbounds4Ray> outbounds { get; set; }
|
public List<Outbounds4Ray> outbounds { get; set; }
|
||||||
public Routing4Ray routing { get; set; }
|
public Routing4Ray routing { get; set; }
|
||||||
@@ -105,6 +105,8 @@ public class Outbounds4Ray
|
|||||||
|
|
||||||
public string protocol { get; set; }
|
public string protocol { get; set; }
|
||||||
|
|
||||||
|
public string? targetStrategy { get; set; }
|
||||||
|
|
||||||
public Outboundsettings4Ray settings { get; set; }
|
public Outboundsettings4Ray settings { get; set; }
|
||||||
|
|
||||||
public StreamSettings4Ray streamSettings { get; set; }
|
public StreamSettings4Ray streamSettings { get; set; }
|
||||||
@@ -128,7 +130,8 @@ public class Outboundsettings4Ray
|
|||||||
|
|
||||||
public string? secretKey { get; set; }
|
public string? secretKey { get; set; }
|
||||||
|
|
||||||
public List<string>? address { get; set; }
|
public Object? address { get; set; }
|
||||||
|
public int? port { get; set; }
|
||||||
|
|
||||||
public List<WireguardPeer4Ray>? peers { get; set; }
|
public List<WireguardPeer4Ray>? peers { get; set; }
|
||||||
|
|
||||||
@@ -139,6 +142,8 @@ public class Outboundsettings4Ray
|
|||||||
public List<int>? reserved { get; set; }
|
public List<int>? reserved { get; set; }
|
||||||
|
|
||||||
public int? workers { get; set; }
|
public int? workers { get; set; }
|
||||||
|
|
||||||
|
public int? version { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WireguardPeer4Ray
|
public class WireguardPeer4Ray
|
||||||
@@ -203,18 +208,15 @@ public class Dns4Ray
|
|||||||
{
|
{
|
||||||
public Dictionary<string, object>? hosts { get; set; }
|
public Dictionary<string, object>? hosts { get; set; }
|
||||||
public List<object> servers { get; set; }
|
public List<object> servers { get; set; }
|
||||||
public string? clientIp { get; set; }
|
public bool? serveStale { get; set; }
|
||||||
public string? queryStrategy { get; set; }
|
public bool? enableParallelQuery { get; set; }
|
||||||
public bool? disableCache { get; set; }
|
|
||||||
public bool? disableFallback { get; set; }
|
|
||||||
public bool? disableFallbackIfMatch { get; set; }
|
|
||||||
public bool? useSystemHosts { get; set; }
|
|
||||||
public string? tag { get; set; }
|
public string? tag { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DnsServer4Ray
|
public class DnsServer4Ray
|
||||||
{
|
{
|
||||||
public string? address { get; set; }
|
public string? address { get; set; }
|
||||||
|
public int? port { get; set; }
|
||||||
public List<string>? domains { get; set; }
|
public List<string>? domains { get; set; }
|
||||||
public bool? skipFallback { get; set; }
|
public bool? skipFallback { get; set; }
|
||||||
public List<string>? expectedIPs { get; set; }
|
public List<string>? expectedIPs { get; set; }
|
||||||
@@ -255,6 +257,8 @@ public class RulesItem4Ray
|
|||||||
public List<string>? domain { get; set; }
|
public List<string>? domain { get; set; }
|
||||||
|
|
||||||
public List<string>? protocol { get; set; }
|
public List<string>? protocol { get; set; }
|
||||||
|
|
||||||
|
public List<string>? process { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BalancersItem4Ray
|
public class BalancersItem4Ray
|
||||||
@@ -335,6 +339,10 @@ public class StreamSettings4Ray
|
|||||||
|
|
||||||
public GrpcSettings4Ray? grpcSettings { get; set; }
|
public GrpcSettings4Ray? grpcSettings { get; set; }
|
||||||
|
|
||||||
|
public HysteriaSettings4Ray? hysteriaSettings { get; set; }
|
||||||
|
|
||||||
|
public Finalmask4Ray? finalmask { get; set; }
|
||||||
|
|
||||||
public Sockopt4Ray? sockopt { get; set; }
|
public Sockopt4Ray? sockopt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,6 +361,17 @@ public class TlsSettings4Ray
|
|||||||
public string? shortId { get; set; }
|
public string? shortId { get; set; }
|
||||||
public string? spiderX { get; set; }
|
public string? spiderX { get; set; }
|
||||||
public string? mldsa65Verify { get; set; }
|
public string? mldsa65Verify { get; set; }
|
||||||
|
public List<CertificateSettings4Ray>? certificates { get; set; }
|
||||||
|
public string? pinnedPeerCertSha256 { get; set; }
|
||||||
|
public bool? disableSystemRoot { get; set; }
|
||||||
|
public string? echConfigList { get; set; }
|
||||||
|
public string? echForceQuery { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CertificateSettings4Ray
|
||||||
|
{
|
||||||
|
public List<string>? certificate { get; set; }
|
||||||
|
public string? usage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TcpSettings4Ray
|
public class TcpSettings4Ray
|
||||||
@@ -367,8 +386,6 @@ public class Header4Ray
|
|||||||
public object request { get; set; }
|
public object request { get; set; }
|
||||||
|
|
||||||
public object response { get; set; }
|
public object response { get; set; }
|
||||||
|
|
||||||
public string? domain { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class KcpSettings4Ray
|
public class KcpSettings4Ray
|
||||||
@@ -386,10 +403,6 @@ public class KcpSettings4Ray
|
|||||||
public int readBufferSize { get; set; }
|
public int readBufferSize { get; set; }
|
||||||
|
|
||||||
public int writeBufferSize { get; set; }
|
public int writeBufferSize { get; set; }
|
||||||
|
|
||||||
public Header4Ray header { get; set; }
|
|
||||||
|
|
||||||
public string seed { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WsSettings4Ray
|
public class WsSettings4Ray
|
||||||
@@ -402,8 +415,6 @@ public class WsSettings4Ray
|
|||||||
|
|
||||||
public class Headers4Ray
|
public class Headers4Ray
|
||||||
{
|
{
|
||||||
public string Host { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("User-Agent")]
|
[JsonPropertyName("User-Agent")]
|
||||||
public string UserAgent { get; set; }
|
public string UserAgent { get; set; }
|
||||||
}
|
}
|
||||||
@@ -450,6 +461,39 @@ public class GrpcSettings4Ray
|
|||||||
public int? initial_windows_size { get; set; }
|
public int? initial_windows_size { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class HysteriaSettings4Ray
|
||||||
|
{
|
||||||
|
public int version { get; set; }
|
||||||
|
public string? auth { get; set; }
|
||||||
|
public string? up { get; set; }
|
||||||
|
public string? down { get; set; }
|
||||||
|
public HysteriaUdpHop4Ray? udphop { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HysteriaUdpHop4Ray
|
||||||
|
{
|
||||||
|
public string? port { get; set; }
|
||||||
|
public string? interval { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Finalmask4Ray
|
||||||
|
{
|
||||||
|
public List<Mask4Ray>? tcp { get; set; }
|
||||||
|
public List<Mask4Ray>? udp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Mask4Ray
|
||||||
|
{
|
||||||
|
public string type { get; set; }
|
||||||
|
public object? settings { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MaskSettings4Ray
|
||||||
|
{
|
||||||
|
public string? password { get; set; }
|
||||||
|
public string? domain { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class AccountsItem4Ray
|
public class AccountsItem4Ray
|
||||||
{
|
{
|
||||||
public string user { get; set; }
|
public string user { get; set; }
|
||||||
|
|||||||
@@ -38,4 +38,6 @@ public class VmessQRCode
|
|||||||
public string alpn { get; set; } = string.Empty;
|
public string alpn { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string fp { get; set; } = string.Empty;
|
public string fp { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string insecure { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+315
-105
@@ -19,7 +19,7 @@ namespace ServiceLib.Resx {
|
|||||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
public class ResUI {
|
public class ResUI {
|
||||||
@@ -87,6 +87,24 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Certificate not set 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string CertNotSet {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CertNotSet", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Certificate set 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string CertSet {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CertSet", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Please check the Configuration settings first. 的本地化字符串。
|
/// 查找类似 Please check the Configuration settings first. 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -511,7 +529,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Next proxy Configuration remarks 的本地化字符串。
|
/// 查找类似 Next proxy remarks 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string LvNextProfile {
|
public static string LvNextProfile {
|
||||||
get {
|
get {
|
||||||
@@ -529,7 +547,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Previous proxy Configuration remarks 的本地化字符串。
|
/// 查找类似 Previous proxy remarks 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string LvPrevProfile {
|
public static string LvPrevProfile {
|
||||||
get {
|
get {
|
||||||
@@ -718,7 +736,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [Anytls] Configuration 的本地化字符串。
|
/// 查找类似 Add [Anytls] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddAnytlsServer {
|
public static string menuAddAnytlsServer {
|
||||||
get {
|
get {
|
||||||
@@ -727,7 +745,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add Child Configuration 的本地化字符串。
|
/// 查找类似 Add Child 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddChildServer {
|
public static string menuAddChildServer {
|
||||||
get {
|
get {
|
||||||
@@ -736,7 +754,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。
|
/// 查找类似 Add a custom configuration 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddCustomServer {
|
public static string menuAddCustomServer {
|
||||||
get {
|
get {
|
||||||
@@ -745,7 +763,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [HTTP] Configuration 的本地化字符串。
|
/// 查找类似 Add [HTTP] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddHttpServer {
|
public static string menuAddHttpServer {
|
||||||
get {
|
get {
|
||||||
@@ -754,7 +772,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [Hysteria2] Configuration 的本地化字符串。
|
/// 查找类似 Add [Hysteria2] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddHysteria2Server {
|
public static string menuAddHysteria2Server {
|
||||||
get {
|
get {
|
||||||
@@ -763,7 +781,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add Policy Group Configuration 的本地化字符串。
|
/// 查找类似 Add Policy Group 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddPolicyGroupServer {
|
public static string menuAddPolicyGroupServer {
|
||||||
get {
|
get {
|
||||||
@@ -772,7 +790,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add Proxy Chain Configuration 的本地化字符串。
|
/// 查找类似 Add Proxy Chain 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddProxyChainServer {
|
public static string menuAddProxyChainServer {
|
||||||
get {
|
get {
|
||||||
@@ -781,7 +799,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。
|
/// 查找类似 Import Share Links from clipboard 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddServerViaClipboard {
|
public static string menuAddServerViaClipboard {
|
||||||
get {
|
get {
|
||||||
@@ -799,7 +817,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Scan QR code on the screen (Ctrl+S) 的本地化字符串。
|
/// 查找类似 Scan QR code on the screen 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddServerViaScan {
|
public static string menuAddServerViaScan {
|
||||||
get {
|
get {
|
||||||
@@ -808,7 +826,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。
|
/// 查找类似 Add [Shadowsocks] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddShadowsocksServer {
|
public static string menuAddShadowsocksServer {
|
||||||
get {
|
get {
|
||||||
@@ -817,7 +835,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [SOCKS] Configuration 的本地化字符串。
|
/// 查找类似 Add [SOCKS] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddSocksServer {
|
public static string menuAddSocksServer {
|
||||||
get {
|
get {
|
||||||
@@ -826,7 +844,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [Trojan] Configuration 的本地化字符串。
|
/// 查找类似 Add [Trojan] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddTrojanServer {
|
public static string menuAddTrojanServer {
|
||||||
get {
|
get {
|
||||||
@@ -835,7 +853,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [TUIC] Configuration 的本地化字符串。
|
/// 查找类似 Add [TUIC] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddTuicServer {
|
public static string menuAddTuicServer {
|
||||||
get {
|
get {
|
||||||
@@ -844,7 +862,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [VLESS] Configuration 的本地化字符串。
|
/// 查找类似 Add [VLESS] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddVlessServer {
|
public static string menuAddVlessServer {
|
||||||
get {
|
get {
|
||||||
@@ -853,7 +871,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [VMess] Configuration 的本地化字符串。
|
/// 查找类似 Add [VMess] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddVmessServer {
|
public static string menuAddVmessServer {
|
||||||
get {
|
get {
|
||||||
@@ -862,7 +880,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Add [WireGuard] Configuration 的本地化字符串。
|
/// 查找类似 Add [WireGuard] 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuAddWireguardServer {
|
public static string menuAddWireguardServer {
|
||||||
get {
|
get {
|
||||||
@@ -934,7 +952,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Clone selected Configuration 的本地化字符串。
|
/// 查找类似 Clone selected 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuCopyServer {
|
public static string menuCopyServer {
|
||||||
get {
|
get {
|
||||||
@@ -952,7 +970,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Edit Configuration (Ctrl+D) 的本地化字符串。
|
/// 查找类似 Edit 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuEditServer {
|
public static string menuEditServer {
|
||||||
get {
|
get {
|
||||||
@@ -979,7 +997,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Export selected Configuration for complete configuration 的本地化字符串。
|
/// 查找类似 Export selected for complete configuration 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuExport2ClientConfig {
|
public static string menuExport2ClientConfig {
|
||||||
get {
|
get {
|
||||||
@@ -988,7 +1006,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Export selected Configuration for complete configuration to clipboard 的本地化字符串。
|
/// 查找类似 Export selected for complete configuration to clipboard 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuExport2ClientConfigClipboard {
|
public static string menuExport2ClientConfigClipboard {
|
||||||
get {
|
get {
|
||||||
@@ -997,7 +1015,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Export Share Link to Clipboard (Ctrl+C) 的本地化字符串。
|
/// 查找类似 Export Share Link to Clipboard 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuExport2ShareUrl {
|
public static string menuExport2ShareUrl {
|
||||||
get {
|
get {
|
||||||
@@ -1015,7 +1033,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Export Configuration 的本地化字符串。
|
/// 查找类似 Export 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuExportConfig {
|
public static string menuExportConfig {
|
||||||
get {
|
get {
|
||||||
@@ -1023,6 +1041,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Test real delay 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string menuFastRealPing {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("menuFastRealPing", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Full Config Template Setting 的本地化字符串。
|
/// 查找类似 Full Config Template Setting 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1042,7 +1069,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Multi-Configuration Fallback by sing-box 的本地化字符串。
|
/// 查找类似 Fallback by sing-box 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuGenGroupMultipleServerSingBoxFallback {
|
public static string menuGenGroupMultipleServerSingBoxFallback {
|
||||||
get {
|
get {
|
||||||
@@ -1051,7 +1078,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
|
/// 查找类似 LeastPing by sing-box 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuGenGroupMultipleServerSingBoxLeastPing {
|
public static string menuGenGroupMultipleServerSingBoxLeastPing {
|
||||||
get {
|
get {
|
||||||
@@ -1060,7 +1087,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Multi-Configuration Fallback by Xray 的本地化字符串。
|
/// 查找类似 Fallback by Xray 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuGenGroupMultipleServerXrayFallback {
|
public static string menuGenGroupMultipleServerXrayFallback {
|
||||||
get {
|
get {
|
||||||
@@ -1069,7 +1096,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
|
/// 查找类似 LeastLoad by Xray 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuGenGroupMultipleServerXrayLeastLoad {
|
public static string menuGenGroupMultipleServerXrayLeastLoad {
|
||||||
get {
|
get {
|
||||||
@@ -1078,7 +1105,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
|
/// 查找类似 LeastPing by Xray 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuGenGroupMultipleServerXrayLeastPing {
|
public static string menuGenGroupMultipleServerXrayLeastPing {
|
||||||
get {
|
get {
|
||||||
@@ -1087,7 +1114,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
|
/// 查找类似 Random by Xray 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuGenGroupMultipleServerXrayRandom {
|
public static string menuGenGroupMultipleServerXrayRandom {
|
||||||
get {
|
get {
|
||||||
@@ -1096,7 +1123,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
|
/// 查找类似 RoundRobin by Xray 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuGenGroupMultipleServerXrayRoundRobin {
|
public static string menuGenGroupMultipleServerXrayRoundRobin {
|
||||||
get {
|
get {
|
||||||
@@ -1222,7 +1249,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Move to bottom (B) 的本地化字符串。
|
/// 查找类似 Move to bottom 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuMoveBottom {
|
public static string menuMoveBottom {
|
||||||
get {
|
get {
|
||||||
@@ -1231,7 +1258,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Down (D) 的本地化字符串。
|
/// 查找类似 Down 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuMoveDown {
|
public static string menuMoveDown {
|
||||||
get {
|
get {
|
||||||
@@ -1258,7 +1285,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Move to top (T) 的本地化字符串。
|
/// 查找类似 Move to top 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuMoveTop {
|
public static string menuMoveTop {
|
||||||
get {
|
get {
|
||||||
@@ -1267,7 +1294,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Up (U) 的本地化字符串。
|
/// 查找类似 Up 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuMoveUp {
|
public static string menuMoveUp {
|
||||||
get {
|
get {
|
||||||
@@ -1285,7 +1312,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Copy (Ctrl+C) 的本地化字符串。
|
/// 查找类似 Copy 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuMsgViewCopy {
|
public static string menuMsgViewCopy {
|
||||||
get {
|
get {
|
||||||
@@ -1303,7 +1330,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Select all (Ctrl+A) 的本地化字符串。
|
/// 查找类似 Select all 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuMsgViewSelectAll {
|
public static string menuMsgViewSelectAll {
|
||||||
get {
|
get {
|
||||||
@@ -1375,7 +1402,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Select active node (Enter) 的本地化字符串。
|
/// 查找类似 Select active node 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuProxiesSelectActivity {
|
public static string menuProxiesSelectActivity {
|
||||||
get {
|
get {
|
||||||
@@ -1384,7 +1411,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Test Configurations real delay (Ctrl+R) 的本地化字符串。
|
/// 查找类似 Test real delay 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuRealPingServer {
|
public static string menuRealPingServer {
|
||||||
get {
|
get {
|
||||||
@@ -1474,7 +1501,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Remove Child Configuration 的本地化字符串。
|
/// 查找类似 Remove Child 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuRemoveChildServer {
|
public static string menuRemoveChildServer {
|
||||||
get {
|
get {
|
||||||
@@ -1483,7 +1510,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Remove duplicate Configurations 的本地化字符串。
|
/// 查找类似 Remove duplicate 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuRemoveDuplicateServer {
|
public static string menuRemoveDuplicateServer {
|
||||||
get {
|
get {
|
||||||
@@ -1501,7 +1528,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Remove selected Configurations (Delete) 的本地化字符串。
|
/// 查找类似 Remove selected 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuRemoveServer {
|
public static string menuRemoveServer {
|
||||||
get {
|
get {
|
||||||
@@ -1537,7 +1564,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Remove selected (Delete) 的本地化字符串。
|
/// 查找类似 Remove selected 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuRoutingAdvancedRemove {
|
public static string menuRoutingAdvancedRemove {
|
||||||
get {
|
get {
|
||||||
@@ -1546,7 +1573,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Set as active rule (Enter) 的本地化字符串。
|
/// 查找类似 Set as active rule 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuRoutingAdvancedSetDefault {
|
public static string menuRoutingAdvancedSetDefault {
|
||||||
get {
|
get {
|
||||||
@@ -1618,7 +1645,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Remove Rule (Delete) 的本地化字符串。
|
/// 查找类似 Remove Rule 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuRuleRemove {
|
public static string menuRuleRemove {
|
||||||
get {
|
get {
|
||||||
@@ -1627,7 +1654,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Select all (Ctrl+A) 的本地化字符串。
|
/// 查找类似 Select all 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuSelectAll {
|
public static string menuSelectAll {
|
||||||
get {
|
get {
|
||||||
@@ -1636,7 +1663,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Server List 的本地化字符串。
|
/// 查找类似 Configuration item 1, Auto add from subscription group 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuServerList {
|
public static string menuServerList {
|
||||||
get {
|
get {
|
||||||
@@ -1645,7 +1672,25 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Configurations 的本地化字符串。
|
/// 查找类似 Configuration Item 2, Select and add from self-built 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string menuServerList2 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("menuServerList2", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Configuration item preview 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string menuServerListPreview {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("menuServerListPreview", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Configuration 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuServers {
|
public static string menuServers {
|
||||||
get {
|
get {
|
||||||
@@ -1654,7 +1699,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Set as active Configuration (Enter) 的本地化字符串。
|
/// 查找类似 Set as active 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuSetDefaultServer {
|
public static string menuSetDefaultServer {
|
||||||
get {
|
get {
|
||||||
@@ -1672,7 +1717,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Share Configuration (Ctrl+F) 的本地化字符串。
|
/// 查找类似 Share 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuShareServer {
|
public static string menuShareServer {
|
||||||
get {
|
get {
|
||||||
@@ -1699,7 +1744,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Test Configurations download speed (Ctrl+T) 的本地化字符串。
|
/// 查找类似 Test download speed 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuSpeedServer {
|
public static string menuSpeedServer {
|
||||||
get {
|
get {
|
||||||
@@ -1843,7 +1888,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Test Configurations with tcping (Ctrl+O) 的本地化字符串。
|
/// 查找类似 Test tcping 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string menuTcpingServer {
|
public static string menuTcpingServer {
|
||||||
get {
|
get {
|
||||||
@@ -1951,7 +1996,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Configuration filter, press Enter to execute 的本地化字符串。
|
/// 查找类似 Filter, press Enter to execute 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string MsgServerTitle {
|
public static string MsgServerTitle {
|
||||||
get {
|
get {
|
||||||
@@ -2248,7 +2293,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Are you sure you want to remove the Configuration? 的本地化字符串。
|
/// 查找类似 Are you sure you want to remove? 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string RemoveServer {
|
public static string RemoveServer {
|
||||||
get {
|
get {
|
||||||
@@ -2292,6 +2337,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Please set a valid domain 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string ServerNameMustBeValidDomain {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ServerNameMustBeValidDomain", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。
|
/// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2319,6 +2373,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Press ESC to terminate the test 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string SpeedtestingPressEscToExit {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SpeedtestingPressEscToExit", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Skip test 的本地化字符串。
|
/// 查找类似 Skip test 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2347,7 +2410,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Waiting for testing (press ESC to terminate)... 的本地化字符串。
|
/// 查找类似 Waiting... 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string SpeedtestingWait {
|
public static string SpeedtestingWait {
|
||||||
get {
|
get {
|
||||||
@@ -2553,6 +2616,36 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Certificate Pinning 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbCertPinning {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbCertPinning", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Pinned certificate (fill in either one)
|
||||||
|
///When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
|
||||||
|
///
|
||||||
|
///The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbCertPinningTips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbCertPinningTips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Certificate fingerprint (SHA-256) 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbCertSha256Tips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbCertSha256Tips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Clear system proxy 的本地化字符串。
|
/// 查找类似 Clear system proxy 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2643,6 +2736,24 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Direct Target Resolution Strategy 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbDirectResolveStrategy {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbDirectResolveStrategy", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbDirectResolveStrategyTips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbDirectResolveStrategyTips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Display GUI 的本地化字符串。
|
/// 查找类似 Display GUI 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2715,6 +2826,33 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 By default, invoked only during routing for resolution 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbDomesticDNSTips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbDomesticDNSTips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 EchConfigList 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbEchConfigList {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbEchConfigList", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 EchForceQuery 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbEchForceQuery {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbEchForceQuery", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Edit 的本地化字符串。
|
/// 查找类似 Edit 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2760,6 +2898,33 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Fetch Certificate 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbFetchCert {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbFetchCert", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Fetch Certificate Chain 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbFetchCertChain {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbFetchCertChain", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Finalmask 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbFinalmask {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbFinalmask", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Fingerprint 的本地化字符串。
|
/// 查找类似 Fingerprint 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2778,6 +2943,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Full certificate (chain), PEM format 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbFullCertTips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbFullCertTips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. 的本地化字符串。
|
/// 查找类似 This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2841,6 +3015,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Port hopping interval 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbHopInt7 {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbHopInt7", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 UUID(id) 的本地化字符串。
|
/// 查找类似 UUID(id) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2931,6 +3114,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Parallel Query 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbParallelQuery {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbParallelQuery", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Path 的本地化字符串。
|
/// 查找类似 Path 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2949,6 +3141,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Auto add filtered configuration from subscription groups 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbPolicyGroupSubChildTip {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbPolicyGroupSubChildTip", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Policy Group Type 的本地化字符串。
|
/// 查找类似 Policy Group Type 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3076,7 +3277,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Via proxy — please ensure remote availability 的本地化字符串。
|
/// 查找类似 By default, invoked only during routing for resolution; ensure the remote server can reach this DNS 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string TbRemoteDNSTips {
|
public static string TbRemoteDNSTips {
|
||||||
get {
|
get {
|
||||||
@@ -3084,6 +3285,24 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Proxy Target Resolution Strategy 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbRemoteResolveStrategy {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbRemoteResolveStrategy", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbRemoteResolveStrategyTips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbRemoteResolveStrategyTips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Camouflage domain(host) 的本地化字符串。
|
/// 查找类似 Camouflage domain(host) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3148,7 +3367,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Full process name (Tun mode) 的本地化字符串。
|
/// 查找类似 Process (Tun mode) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string TbRoutingRuleProcess {
|
public static string TbRoutingRuleProcess {
|
||||||
get {
|
get {
|
||||||
@@ -3219,15 +3438,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbSBDirectResolveStrategy {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbSBDirectResolveStrategy", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 sing-box Full Config Template 的本地化字符串。
|
/// 查找类似 sing-box Full Config Template 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3246,15 +3456,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 sing-box Remote Resolution Strategy 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbSBRemoteResolveStrategy {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbSBRemoteResolveStrategy", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Encryption method (security) 的本地化字符串。
|
/// 查找类似 Encryption method (security) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3300,6 +3501,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Serve Stale 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbServeStale {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbServeStale", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Set system proxy 的本地化字符串。
|
/// 查找类似 Set system proxy 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3426,6 +3636,24 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Custom PAC file path 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsCustomSystemProxyPacPath {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsCustomSystemProxyPacPath", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Custom system proxy script file path 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsCustomSystemProxyScriptPath {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsCustomSystemProxyScriptPath", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Allow Insecure 的本地化字符串。
|
/// 查找类似 Allow Insecure 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3552,15 +3780,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Enable additional Inbound 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbSettingsEnableExInbound {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbSettingsEnableExInbound", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Enable fragment 的本地化字符串。
|
/// 查找类似 Enable fragment 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3570,15 +3789,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 which conflicts with the group previous proxy 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbSettingsEnableFragmentTips {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbSettingsEnableFragmentTips", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Enable hardware acceleration (requires restart) 的本地化字符串。
|
/// 查找类似 Enable hardware acceleration (requires restart) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3750,6 +3960,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 macOS displays this in the Dock (requires restart) 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsMacOSShowInDock {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSettingsMacOSShowInDock", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Main layout orientation (requires restart) 的本地化字符串。
|
/// 查找类似 Main layout orientation (requires restart) 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3985,7 +4204,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Tray right-click menu Configurations display limit 的本地化字符串。
|
/// 查找类似 Tray right-click menu display limit 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string TbSettingsTrayMenuServersLimit {
|
public static string TbSettingsTrayMenuServersLimit {
|
||||||
get {
|
get {
|
||||||
@@ -4246,7 +4465,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs 的本地化字符串。
|
/// 查找类似 When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string TbValidateDirectExpectedIPsDesc {
|
public static string TbValidateDirectExpectedIPsDesc {
|
||||||
get {
|
get {
|
||||||
@@ -4254,15 +4473,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 xray Freedom Resolution Strategy 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbXrayFreedomStrategy {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbXrayFreedomStrategy", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 The delay: {0} ms, {1} 的本地化字符串。
|
/// 查找类似 The delay: {0} ms, {1} 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -472,10 +472,10 @@
|
|||||||
<value>زبان</value>
|
<value>زبان</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
||||||
<value>وارد کردن URL انبوه از کلیپ بورد (Ctrl+V)</value>
|
<value>وارد کردن URL انبوه از کلیپ بورد</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaScan" xml:space="preserve">
|
<data name="menuAddServerViaScan" xml:space="preserve">
|
||||||
<value>اسکن کد QR روی صفحه (Ctrl+S)</value>
|
<value>اسکن کد QR روی صفحه</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuCopyServer" xml:space="preserve">
|
<data name="menuCopyServer" xml:space="preserve">
|
||||||
<value>سرور انتخاب شده را شبیه سازی کنید</value>
|
<value>سرور انتخاب شده را شبیه سازی کنید</value>
|
||||||
@@ -484,31 +484,31 @@
|
|||||||
<value>سرورهای تکراری را حذف کنید</value>
|
<value>سرورهای تکراری را حذف کنید</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveServer" xml:space="preserve">
|
<data name="menuRemoveServer" xml:space="preserve">
|
||||||
<value>حذف سرورهای انتخابی (Delete)</value>
|
<value>حذف سرورهای انتخابی</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSetDefaultServer" xml:space="preserve">
|
<data name="menuSetDefaultServer" xml:space="preserve">
|
||||||
<value>به عنوان سرور فعال تنظیم کنید (Enter)</value>
|
<value>به عنوان سرور فعال تنظیم کنید</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuClearServerStatistics" xml:space="preserve">
|
<data name="menuClearServerStatistics" xml:space="preserve">
|
||||||
<value>تمام آمار خدمات را پاک کنید</value>
|
<value>تمام آمار خدمات را پاک کنید</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRealPingServer" xml:space="preserve">
|
<data name="menuRealPingServer" xml:space="preserve">
|
||||||
<value>آزمایش سرورها با تاخیر واقعی (Ctrl+R)</value>
|
<value>آزمایش سرورها با تاخیر واقعی</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSortServerResult" xml:space="preserve">
|
<data name="menuSortServerResult" xml:space="preserve">
|
||||||
<value>مرتب سازی بر اساس نتیجه تست</value>
|
<value>مرتب سازی بر اساس نتیجه تست</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSpeedServer" xml:space="preserve">
|
<data name="menuSpeedServer" xml:space="preserve">
|
||||||
<value>تست سرعت دانلود سرورها (Ctrl+T)</value>
|
<value>تست سرعت دانلود سرورها</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuTcpingServer" xml:space="preserve">
|
<data name="menuTcpingServer" xml:space="preserve">
|
||||||
<value>تست سرورها با tcping (Ctrl+O)</value>
|
<value>تست سرورها با tcping</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ClientConfig" xml:space="preserve">
|
<data name="menuExport2ClientConfig" xml:space="preserve">
|
||||||
<value>سرور انتخابی را برای پیکربندی کلاینت صادر کنید</value>
|
<value>سرور انتخابی را برای پیکربندی کلاینت صادر کنید</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ShareUrl" xml:space="preserve">
|
<data name="menuExport2ShareUrl" xml:space="preserve">
|
||||||
<value>URL های اشتراک گذاری را به کلیپ بورد صادر کنید (Ctrl+C)</value>
|
<value>URL های اشتراک گذاری را به کلیپ بورد صادر کنید</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddCustomServer" xml:space="preserve">
|
<data name="menuAddCustomServer" xml:space="preserve">
|
||||||
<value>یک سرور پیکربندی سفارشی اضافه شود</value>
|
<value>یک سرور پیکربندی سفارشی اضافه شود</value>
|
||||||
@@ -529,19 +529,19 @@
|
|||||||
<value>سرور [VMess] را اضافه کنید</value>
|
<value>سرور [VMess] را اضافه کنید</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSelectAll" xml:space="preserve">
|
<data name="menuSelectAll" xml:space="preserve">
|
||||||
<value>انتخاب همه (Ctrl+A)</value>
|
<value>انتخاب همه</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewClear" xml:space="preserve">
|
<data name="menuMsgViewClear" xml:space="preserve">
|
||||||
<value>همه را پاک کن</value>
|
<value>همه را پاک کن</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopy" xml:space="preserve">
|
<data name="menuMsgViewCopy" xml:space="preserve">
|
||||||
<value>کپی (Ctrl+C)</value>
|
<value>کپی</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
||||||
<value>کپی همه</value>
|
<value>کپی همه</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
||||||
<value>انتخاب همه (Ctrl+A)</value>
|
<value>انتخاب همه</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSubAdd" xml:space="preserve">
|
<data name="menuSubAdd" xml:space="preserve">
|
||||||
<value>اضافه کردن</value>
|
<value>اضافه کردن</value>
|
||||||
@@ -796,13 +796,13 @@
|
|||||||
<value>به پایین حرکت شود(B)</value>
|
<value>به پایین حرکت شود(B)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveDown" xml:space="preserve">
|
<data name="menuMoveDown" xml:space="preserve">
|
||||||
<value>پایین (D)</value>
|
<value>پایین</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveTop" xml:space="preserve">
|
<data name="menuMoveTop" xml:space="preserve">
|
||||||
<value>حرکت به بالا (T)</value>
|
<value>حرکت به بالا</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveUp" xml:space="preserve">
|
<data name="menuMoveUp" xml:space="preserve">
|
||||||
<value>بالا (U)</value>
|
<value>بالا</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgFilterTitle" xml:space="preserve">
|
<data name="MsgFilterTitle" xml:space="preserve">
|
||||||
<value>فیلتر، از عبارات منظم پشتیبانی می کند</value>
|
<value>فیلتر، از عبارات منظم پشتیبانی می کند</value>
|
||||||
@@ -922,7 +922,7 @@
|
|||||||
<value>رد شدن از آزمون</value>
|
<value>رد شدن از آزمون</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuEditServer" xml:space="preserve">
|
<data name="menuEditServer" xml:space="preserve">
|
||||||
<value>ویرایش سرور (Ctrl+D)</value>
|
<value>ویرایش سرور</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
||||||
<value>دوبار کلیک کردن سرور باعث فعال شدن آن می شود</value>
|
<value>دوبار کلیک کردن سرور باعث فعال شدن آن می شود</value>
|
||||||
@@ -976,7 +976,10 @@
|
|||||||
<value>فعال سازی شتاب دهنده سخت افزاری (نیاز به راهاندازی مجدد)</value>
|
<value>فعال سازی شتاب دهنده سخت افزاری (نیاز به راهاندازی مجدد)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedtestingWait" xml:space="preserve">
|
<data name="SpeedtestingWait" xml:space="preserve">
|
||||||
<value>در انتظار آزمایش (برای پایان دادن به ESC فشار دهید)...</value>
|
<value>در انتظار آزمایش...</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
|
||||||
|
<value>برای پایان دادن به ESC فشار دهید</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TipDisplayLog" xml:space="preserve">
|
<data name="TipDisplayLog" xml:space="preserve">
|
||||||
<value>لطفاً در صورت قطع غیرعادی آن را خاموش کنید</value>
|
<value>لطفاً در صورت قطع غیرعادی آن را خاموش کنید</value>
|
||||||
@@ -1024,7 +1027,7 @@
|
|||||||
<value>پروتکل sing-box Mux</value>
|
<value>پروتکل sing-box Mux</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||||
<value>نام کامل فرانید (حالت Tun)</value>
|
<value>Process (Tun mode)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||||
<value>IP or IP CIDR</value>
|
<value>IP or IP CIDR</value>
|
||||||
@@ -1068,9 +1071,6 @@
|
|||||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
<value>MTU</value>
|
<value>MTU</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
|
||||||
<value>فعال سازی additional Inbound</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||||
<value>فعال سازی آدرس IPv6</value>
|
<value>فعال سازی آدرس IPv6</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1110,9 +1110,6 @@
|
|||||||
<data name="menuAddHttpServer" xml:space="preserve">
|
<data name="menuAddHttpServer" xml:space="preserve">
|
||||||
<value>افزودن سرور [HTTP]</value>
|
<value>افزودن سرور [HTTP]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
|
||||||
<value>which conflicts with the group previous proxy</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||||
<value>فعال کردن فرگمنت</value>
|
<value>فعال کردن فرگمنت</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1204,7 +1201,7 @@
|
|||||||
<value>تازه سازی پروکسی ها</value>
|
<value>تازه سازی پروکسی ها</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||||
<value>انتخاب گره فعال (Enter)</value>
|
<value>انتخاب گره فعال</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
||||||
<value>استراتژی دامنه پیش فرض برای خروجی</value>
|
<value>استراتژی دامنه پیش فرض برای خروجی</value>
|
||||||
@@ -1416,17 +1413,11 @@
|
|||||||
<data name="TbDomesticDNS" xml:space="preserve">
|
<data name="TbDomesticDNS" xml:space="preserve">
|
||||||
<value>Domestic DNS</value>
|
<value>Domestic DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||||
<value>Via proxy — please ensure remote availability</value>
|
<value>Direct Target Resolution Strategy</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||||
<value>xray Freedom Resolution Strategy</value>
|
<value>Proxy Target Resolution Strategy</value>
|
||||||
</data>
|
|
||||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box Direct Resolution Strategy</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box Remote Resolution Strategy</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Add Common DNS Hosts</value>
|
<value>Add Common DNS Hosts</value>
|
||||||
@@ -1450,7 +1441,7 @@
|
|||||||
<value>Validate Regional Domain IPs</value>
|
<value>Validate Regional Domain IPs</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||||
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
|
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||||
<value>Enable Custom DNS</value>
|
<value>Enable Custom DNS</value>
|
||||||
@@ -1537,7 +1528,7 @@
|
|||||||
<value>Remove Child Configuration</value>
|
<value>Remove Child Configuration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuServerList" xml:space="preserve">
|
<data name="menuServerList" xml:space="preserve">
|
||||||
<value>Server List</value>
|
<value>Configuration item 1, Auto add from subscription group</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFallback" xml:space="preserve">
|
<data name="TbFallback" xml:space="preserve">
|
||||||
<value>Fallback</value>
|
<value>Fallback</value>
|
||||||
@@ -1596,4 +1587,85 @@
|
|||||||
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
||||||
<value>Resolve DNS server domains, requires IP</value>
|
<value>Resolve DNS server domains, requires IP</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="menuFastRealPing" xml:space="preserve">
|
||||||
|
<value>Test real delay</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
|
<value>Auto add filtered configuration from subscription groups</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Server Certificate (PEM format, optional)
|
||||||
|
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
|
||||||
|
|
||||||
|
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCertChain" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate Chain</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
|
||||||
|
<value>Please set a valid domain</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertNotSet" xml:space="preserve">
|
||||||
|
<value>Certificate not set</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertSet" xml:space="preserve">
|
||||||
|
<value>Certificate set</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
|
||||||
|
<value>Custom PAC file path</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
|
||||||
|
<value>Custom system proxy script file path</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
|
||||||
|
<value>macOS displays this in the Dock (requires restart)</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullCertTips" xml:space="preserve">
|
||||||
|
<value>Full certificate (chain), PEM format</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertSha256Tips" xml:space="preserve">
|
||||||
|
<value>Certificate fingerprint (SHA-256)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbServeStale" xml:space="preserve">
|
||||||
|
<value>Serve Stale</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbParallelQuery" xml:space="preserve">
|
||||||
|
<value>Parallel Query</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDomesticDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbHopInt7" xml:space="preserve">
|
||||||
|
<value>Port hopping interval</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerListPreview" xml:space="preserve">
|
||||||
|
<value>Configuration item preview</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFinalmask" xml:space="preserve">
|
||||||
|
<value>Finalmask</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -472,10 +472,10 @@
|
|||||||
<value>Nyelv (Újraindítás)</value>
|
<value>Nyelv (Újraindítás)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
||||||
<value>Megosztási linkek importálása vágólapról (Ctrl+V)</value>
|
<value>Megosztási linkek importálása vágólapról</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaScan" xml:space="preserve">
|
<data name="menuAddServerViaScan" xml:space="preserve">
|
||||||
<value>QR kód beolvasása a képernyőről (Ctrl+S)</value>
|
<value>QR kód beolvasása a képernyőről</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuCopyServer" xml:space="preserve">
|
<data name="menuCopyServer" xml:space="preserve">
|
||||||
<value>Kijelölt konfiguráció klónozása</value>
|
<value>Kijelölt konfiguráció klónozása</value>
|
||||||
@@ -484,31 +484,31 @@
|
|||||||
<value>Ismétlődő konfigurációk eltávolítása</value>
|
<value>Ismétlődő konfigurációk eltávolítása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveServer" xml:space="preserve">
|
<data name="menuRemoveServer" xml:space="preserve">
|
||||||
<value>Kijelölt konfigurációk eltávolítása (Delete)</value>
|
<value>Kijelölt konfigurációk eltávolítása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSetDefaultServer" xml:space="preserve">
|
<data name="menuSetDefaultServer" xml:space="preserve">
|
||||||
<value>Beállítás aktív konfigurációként (Enter)</value>
|
<value>Beállítás aktív konfigurációként</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuClearServerStatistics" xml:space="preserve">
|
<data name="menuClearServerStatistics" xml:space="preserve">
|
||||||
<value>Összes szolgáltatás statisztika törlése</value>
|
<value>Összes szolgáltatás statisztika törlése</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRealPingServer" xml:space="preserve">
|
<data name="menuRealPingServer" xml:space="preserve">
|
||||||
<value>Konfigurációk valós késleltetésének tesztelése (Ctrl+R)</value>
|
<value>Konfigurációk valós késleltetésének tesztelése</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSortServerResult" xml:space="preserve">
|
<data name="menuSortServerResult" xml:space="preserve">
|
||||||
<value>Rendezés teszteredmény szerint</value>
|
<value>Rendezés teszteredmény szerint</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSpeedServer" xml:space="preserve">
|
<data name="menuSpeedServer" xml:space="preserve">
|
||||||
<value>Konfigurációk letöltési sebességének tesztelése (Ctrl+T)</value>
|
<value>Konfigurációk letöltési sebességének tesztelése</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuTcpingServer" xml:space="preserve">
|
<data name="menuTcpingServer" xml:space="preserve">
|
||||||
<value>Konfigurációk tesztelése tcpinggel (Ctrl+O)</value>
|
<value>Konfigurációk tesztelése tcpinggel</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ClientConfig" xml:space="preserve">
|
<data name="menuExport2ClientConfig" xml:space="preserve">
|
||||||
<value>Kijelölt konfiguráció exportálása teljes konfigurációként</value>
|
<value>Kijelölt konfiguráció exportálása teljes konfigurációként</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ShareUrl" xml:space="preserve">
|
<data name="menuExport2ShareUrl" xml:space="preserve">
|
||||||
<value>Megosztási link exportálása vágólapra (Ctrl+C)</value>
|
<value>Megosztási link exportálása vágólapra</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddCustomServer" xml:space="preserve">
|
<data name="menuAddCustomServer" xml:space="preserve">
|
||||||
<value>Egyéni konfiguráció hozzáadása</value>
|
<value>Egyéni konfiguráció hozzáadása</value>
|
||||||
@@ -529,19 +529,19 @@
|
|||||||
<value>[VMess] konfiguráció hozzáadása</value>
|
<value>[VMess] konfiguráció hozzáadása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSelectAll" xml:space="preserve">
|
<data name="menuSelectAll" xml:space="preserve">
|
||||||
<value>Összes kijelölése (Ctrl+A)</value>
|
<value>Összes kijelölése</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewClear" xml:space="preserve">
|
<data name="menuMsgViewClear" xml:space="preserve">
|
||||||
<value>Összes törlése</value>
|
<value>Összes törlése</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopy" xml:space="preserve">
|
<data name="menuMsgViewCopy" xml:space="preserve">
|
||||||
<value>Másolás (Ctrl+C)</value>
|
<value>Másolás</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
||||||
<value>Összes másolása</value>
|
<value>Összes másolása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
||||||
<value>Összes kijelölése (Ctrl+A)</value>
|
<value>Összes kijelölése</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSubAdd" xml:space="preserve">
|
<data name="menuSubAdd" xml:space="preserve">
|
||||||
<value>Hozzáadás</value>
|
<value>Hozzáadás</value>
|
||||||
@@ -781,7 +781,7 @@
|
|||||||
<value>PAC mód</value>
|
<value>PAC mód</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuShareServer" xml:space="preserve">
|
<data name="menuShareServer" xml:space="preserve">
|
||||||
<value>Konfiguráció megosztása (Ctrl+F)</value>
|
<value>Konfiguráció megosztása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRouting" xml:space="preserve">
|
<data name="menuRouting" xml:space="preserve">
|
||||||
<value>Útválasztás</value>
|
<value>Útválasztás</value>
|
||||||
@@ -793,16 +793,16 @@
|
|||||||
<value>Futtatás rendszergazdaként</value>
|
<value>Futtatás rendszergazdaként</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveBottom" xml:space="preserve">
|
<data name="menuMoveBottom" xml:space="preserve">
|
||||||
<value>Mozgatás alulra (B)</value>
|
<value>Mozgatás alulra</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveDown" xml:space="preserve">
|
<data name="menuMoveDown" xml:space="preserve">
|
||||||
<value>Le (D)</value>
|
<value>Le</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveTop" xml:space="preserve">
|
<data name="menuMoveTop" xml:space="preserve">
|
||||||
<value>Mozgatás felülre (T)</value>
|
<value>Mozgatás felülre</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveUp" xml:space="preserve">
|
<data name="menuMoveUp" xml:space="preserve">
|
||||||
<value>Fel (U)</value>
|
<value>Fel</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgFilterTitle" xml:space="preserve">
|
<data name="MsgFilterTitle" xml:space="preserve">
|
||||||
<value>Szűrő, támogatja a reguláris kifejezéseket</value>
|
<value>Szűrő, támogatja a reguláris kifejezéseket</value>
|
||||||
@@ -817,10 +817,10 @@
|
|||||||
<value>Szabályok importálása</value>
|
<value>Szabályok importálása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
|
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
|
||||||
<value>Kijelölt eltávolítása (Delete)</value>
|
<value>Kijelölt eltávolítása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>Beállítás aktív szabályként (Enter)</value>
|
<value>Beállítás aktív szabályként</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>Tartomány stratégia</value>
|
<value>Tartomány stratégia</value>
|
||||||
@@ -853,7 +853,7 @@
|
|||||||
<value>Szabálylista</value>
|
<value>Szabálylista</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRuleRemove" xml:space="preserve">
|
<data name="menuRuleRemove" xml:space="preserve">
|
||||||
<value>Szabály eltávolítása (Delete)</value>
|
<value>Szabály eltávolítása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
||||||
<value>Útválasztási szabály részleteinek beállítása</value>
|
<value>Útválasztási szabály részleteinek beállítása</value>
|
||||||
@@ -922,7 +922,7 @@
|
|||||||
<value>Teszt kihagyása</value>
|
<value>Teszt kihagyása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuEditServer" xml:space="preserve">
|
<data name="menuEditServer" xml:space="preserve">
|
||||||
<value>Konfiguráció szerkesztése (Ctrl+D)</value>
|
<value>Konfiguráció szerkesztése</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
||||||
<value>Dupla kattintás a konfigurációra aktiválja</value>
|
<value>Dupla kattintás a konfigurációra aktiválja</value>
|
||||||
@@ -976,7 +976,10 @@
|
|||||||
<value>Hardveres gyorsítás engedélyezése (újraindítást igényel)</value>
|
<value>Hardveres gyorsítás engedélyezése (újraindítást igényel)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedtestingWait" xml:space="preserve">
|
<data name="SpeedtestingWait" xml:space="preserve">
|
||||||
<value>Tesztelésre vár (ESC megnyomásával megszakítható)...</value>
|
<value>Tesztelésre vár...</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
|
||||||
|
<value>ESC megnyomásával megszakítható</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TipDisplayLog" xml:space="preserve">
|
<data name="TipDisplayLog" xml:space="preserve">
|
||||||
<value>Kérjük, kapcsolja ki rendellenes megszakadás esetén</value>
|
<value>Kérjük, kapcsolja ki rendellenes megszakadás esetén</value>
|
||||||
@@ -1024,7 +1027,7 @@
|
|||||||
<value>sing-box Mux protokoll</value>
|
<value>sing-box Mux protokoll</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||||
<value>Teljes folyamatnév (Tun mód)</value>
|
<value>Process (Tun mode)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||||
<value>IP vagy IP CIDR</value>
|
<value>IP vagy IP CIDR</value>
|
||||||
@@ -1068,9 +1071,6 @@
|
|||||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
<value>MTU</value>
|
<value>MTU</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
|
||||||
<value>További bejövő engedélyezése</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||||
<value>IPv6 cím engedélyezése</value>
|
<value>IPv6 cím engedélyezése</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1110,9 +1110,6 @@
|
|||||||
<data name="menuAddHttpServer" xml:space="preserve">
|
<data name="menuAddHttpServer" xml:space="preserve">
|
||||||
<value>HTTP konfiguráció hozzáadása</value>
|
<value>HTTP konfiguráció hozzáadása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
|
||||||
<value>which conflicts with the group previous proxy</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||||
<value>Fragment engedélyezése</value>
|
<value>Fragment engedélyezése</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1204,7 +1201,7 @@
|
|||||||
<value>Proxyk frissítése</value>
|
<value>Proxyk frissítése</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||||
<value>Aktív csomópont kiválasztása (Enter)</value>
|
<value>Aktív csomópont kiválasztása</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
||||||
<value>Alapértelmezett tartomány stratégia kimenő forgalomhoz</value>
|
<value>Alapértelmezett tartomány stratégia kimenő forgalomhoz</value>
|
||||||
@@ -1416,17 +1413,11 @@
|
|||||||
<data name="TbDomesticDNS" xml:space="preserve">
|
<data name="TbDomesticDNS" xml:space="preserve">
|
||||||
<value>Domestic DNS</value>
|
<value>Domestic DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||||
<value>Via proxy — please ensure remote availability</value>
|
<value>Direct Target Resolution Strategy</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||||
<value>xray Freedom Resolution Strategy</value>
|
<value>Proxy Target Resolution Strategy</value>
|
||||||
</data>
|
|
||||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box Direct Resolution Strategy</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box Remote Resolution Strategy</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Add Common DNS Hosts</value>
|
<value>Add Common DNS Hosts</value>
|
||||||
@@ -1450,7 +1441,7 @@
|
|||||||
<value>Validate Regional Domain IPs</value>
|
<value>Validate Regional Domain IPs</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||||
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
|
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||||
<value>Enable Custom DNS</value>
|
<value>Enable Custom DNS</value>
|
||||||
@@ -1537,7 +1528,7 @@
|
|||||||
<value>Remove Child Configuration</value>
|
<value>Remove Child Configuration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuServerList" xml:space="preserve">
|
<data name="menuServerList" xml:space="preserve">
|
||||||
<value>Server List</value>
|
<value>Configuration item 1, Auto add from subscription group</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFallback" xml:space="preserve">
|
<data name="TbFallback" xml:space="preserve">
|
||||||
<value>Fallback</value>
|
<value>Fallback</value>
|
||||||
@@ -1596,4 +1587,85 @@
|
|||||||
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
||||||
<value>Resolve DNS server domains, requires IP</value>
|
<value>Resolve DNS server domains, requires IP</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="menuFastRealPing" xml:space="preserve">
|
||||||
|
<value>Test real delay</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
|
<value>Auto add filtered configuration from subscription groups</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Pinned certificate (fill in either one)
|
||||||
|
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
|
||||||
|
|
||||||
|
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCertChain" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate Chain</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
|
||||||
|
<value>Please set a valid domain</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertNotSet" xml:space="preserve">
|
||||||
|
<value>Certificate not set</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertSet" xml:space="preserve">
|
||||||
|
<value>Certificate set</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
|
||||||
|
<value>Custom PAC file path</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
|
||||||
|
<value>Custom system proxy script file path</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
|
||||||
|
<value>macOS displays this in the Dock (requires restart)</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullCertTips" xml:space="preserve">
|
||||||
|
<value>Full certificate (chain), PEM format</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertSha256Tips" xml:space="preserve">
|
||||||
|
<value>Certificate fingerprint (SHA-256)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbServeStale" xml:space="preserve">
|
||||||
|
<value>Serve Stale</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbParallelQuery" xml:space="preserve">
|
||||||
|
<value>Parallel Query</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDomesticDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbHopInt7" xml:space="preserve">
|
||||||
|
<value>Port hopping interval</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerListPreview" xml:space="preserve">
|
||||||
|
<value>Configuration item preview</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFinalmask" xml:space="preserve">
|
||||||
|
<value>Finalmask</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -271,7 +271,7 @@
|
|||||||
<value>Configurations deduplication completed. Old: {0}, New: {1}.</value>
|
<value>Configurations deduplication completed. Old: {0}, New: {1}.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoveServer" xml:space="preserve">
|
<data name="RemoveServer" xml:space="preserve">
|
||||||
<value>Are you sure you want to remove the Configuration?</value>
|
<value>Are you sure you want to remove?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SaveClientConfigurationIn" xml:space="preserve">
|
<data name="SaveClientConfigurationIn" xml:space="preserve">
|
||||||
<value>The client configuration file is saved at: {0}</value>
|
<value>The client configuration file is saved at: {0}</value>
|
||||||
@@ -397,7 +397,7 @@
|
|||||||
<value>Local</value>
|
<value>Local</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgServerTitle" xml:space="preserve">
|
<data name="MsgServerTitle" xml:space="preserve">
|
||||||
<value>Configuration filter, press Enter to execute</value>
|
<value>Filter, press Enter to execute</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuCheckUpdate" xml:space="preserve">
|
<data name="menuCheckUpdate" xml:space="preserve">
|
||||||
<value>Check Update</value>
|
<value>Check Update</value>
|
||||||
@@ -427,7 +427,7 @@
|
|||||||
<value>Routing Setting</value>
|
<value>Routing Setting</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuServers" xml:space="preserve">
|
<data name="menuServers" xml:space="preserve">
|
||||||
<value>Configurations</value>
|
<value>Configuration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSetting" xml:space="preserve">
|
<data name="menuSetting" xml:space="preserve">
|
||||||
<value>Settings</value>
|
<value>Settings</value>
|
||||||
@@ -472,76 +472,76 @@
|
|||||||
<value>Language (Restart)</value>
|
<value>Language (Restart)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
||||||
<value>Import Share Links from clipboard (Ctrl+V)</value>
|
<value>Import Share Links from clipboard</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaScan" xml:space="preserve">
|
<data name="menuAddServerViaScan" xml:space="preserve">
|
||||||
<value>Scan QR code on the screen (Ctrl+S)</value>
|
<value>Scan QR code on the screen</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuCopyServer" xml:space="preserve">
|
<data name="menuCopyServer" xml:space="preserve">
|
||||||
<value>Clone selected Configuration</value>
|
<value>Clone selected</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveDuplicateServer" xml:space="preserve">
|
<data name="menuRemoveDuplicateServer" xml:space="preserve">
|
||||||
<value>Remove duplicate Configurations</value>
|
<value>Remove duplicate</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveServer" xml:space="preserve">
|
<data name="menuRemoveServer" xml:space="preserve">
|
||||||
<value>Remove selected Configurations (Delete)</value>
|
<value>Remove selected</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSetDefaultServer" xml:space="preserve">
|
<data name="menuSetDefaultServer" xml:space="preserve">
|
||||||
<value>Set as active Configuration (Enter)</value>
|
<value>Set as active</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuClearServerStatistics" xml:space="preserve">
|
<data name="menuClearServerStatistics" xml:space="preserve">
|
||||||
<value>Clear all service statistics</value>
|
<value>Clear all service statistics</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRealPingServer" xml:space="preserve">
|
<data name="menuRealPingServer" xml:space="preserve">
|
||||||
<value>Test Configurations real delay (Ctrl+R)</value>
|
<value>Test real delay</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSortServerResult" xml:space="preserve">
|
<data name="menuSortServerResult" xml:space="preserve">
|
||||||
<value>Sort by test result</value>
|
<value>Sort by test result</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSpeedServer" xml:space="preserve">
|
<data name="menuSpeedServer" xml:space="preserve">
|
||||||
<value>Test Configurations download speed (Ctrl+T)</value>
|
<value>Test download speed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuTcpingServer" xml:space="preserve">
|
<data name="menuTcpingServer" xml:space="preserve">
|
||||||
<value>Test Configurations with tcping (Ctrl+O)</value>
|
<value>Test tcping</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ClientConfig" xml:space="preserve">
|
<data name="menuExport2ClientConfig" xml:space="preserve">
|
||||||
<value>Export selected Configuration for complete configuration</value>
|
<value>Export selected for complete configuration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ShareUrl" xml:space="preserve">
|
<data name="menuExport2ShareUrl" xml:space="preserve">
|
||||||
<value>Export Share Link to Clipboard (Ctrl+C)</value>
|
<value>Export Share Link to Clipboard</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddCustomServer" xml:space="preserve">
|
<data name="menuAddCustomServer" xml:space="preserve">
|
||||||
<value>Add a custom configuration Configuration</value>
|
<value>Add a custom configuration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddShadowsocksServer" xml:space="preserve">
|
<data name="menuAddShadowsocksServer" xml:space="preserve">
|
||||||
<value>Add [Shadowsocks] Configuration</value>
|
<value>Add [Shadowsocks] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddSocksServer" xml:space="preserve">
|
<data name="menuAddSocksServer" xml:space="preserve">
|
||||||
<value>Add [SOCKS] Configuration</value>
|
<value>Add [SOCKS] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddTrojanServer" xml:space="preserve">
|
<data name="menuAddTrojanServer" xml:space="preserve">
|
||||||
<value>Add [Trojan] Configuration</value>
|
<value>Add [Trojan] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddVlessServer" xml:space="preserve">
|
<data name="menuAddVlessServer" xml:space="preserve">
|
||||||
<value>Add [VLESS] Configuration</value>
|
<value>Add [VLESS] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddVmessServer" xml:space="preserve">
|
<data name="menuAddVmessServer" xml:space="preserve">
|
||||||
<value>Add [VMess] Configuration</value>
|
<value>Add [VMess] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSelectAll" xml:space="preserve">
|
<data name="menuSelectAll" xml:space="preserve">
|
||||||
<value>Select all (Ctrl+A)</value>
|
<value>Select all</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewClear" xml:space="preserve">
|
<data name="menuMsgViewClear" xml:space="preserve">
|
||||||
<value>Clear all</value>
|
<value>Clear all</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopy" xml:space="preserve">
|
<data name="menuMsgViewCopy" xml:space="preserve">
|
||||||
<value>Copy (Ctrl+C)</value>
|
<value>Copy</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
||||||
<value>Copy all</value>
|
<value>Copy all</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
||||||
<value>Select all (Ctrl+A)</value>
|
<value>Select all</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSubAdd" xml:space="preserve">
|
<data name="menuSubAdd" xml:space="preserve">
|
||||||
<value>Add</value>
|
<value>Add</value>
|
||||||
@@ -748,7 +748,7 @@
|
|||||||
<value>System proxy settings</value>
|
<value>System proxy settings</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
|
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
|
||||||
<value>Tray right-click menu Configurations display limit</value>
|
<value>Tray right-click menu display limit</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsUdpEnabled" xml:space="preserve">
|
<data name="TbSettingsUdpEnabled" xml:space="preserve">
|
||||||
<value>Enable UDP</value>
|
<value>Enable UDP</value>
|
||||||
@@ -781,7 +781,7 @@
|
|||||||
<value>PAC mode</value>
|
<value>PAC mode</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuShareServer" xml:space="preserve">
|
<data name="menuShareServer" xml:space="preserve">
|
||||||
<value>Share Configuration (Ctrl+F)</value>
|
<value>Share</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRouting" xml:space="preserve">
|
<data name="menuRouting" xml:space="preserve">
|
||||||
<value>Routing</value>
|
<value>Routing</value>
|
||||||
@@ -793,16 +793,16 @@
|
|||||||
<value>Run as Admin</value>
|
<value>Run as Admin</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveBottom" xml:space="preserve">
|
<data name="menuMoveBottom" xml:space="preserve">
|
||||||
<value>Move to bottom (B)</value>
|
<value>Move to bottom</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveDown" xml:space="preserve">
|
<data name="menuMoveDown" xml:space="preserve">
|
||||||
<value>Down (D)</value>
|
<value>Down</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveTop" xml:space="preserve">
|
<data name="menuMoveTop" xml:space="preserve">
|
||||||
<value>Move to top (T)</value>
|
<value>Move to top</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveUp" xml:space="preserve">
|
<data name="menuMoveUp" xml:space="preserve">
|
||||||
<value>Up (U)</value>
|
<value>Up</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgFilterTitle" xml:space="preserve">
|
<data name="MsgFilterTitle" xml:space="preserve">
|
||||||
<value>Filter, supports regular expressions</value>
|
<value>Filter, supports regular expressions</value>
|
||||||
@@ -817,10 +817,10 @@
|
|||||||
<value>Import Rules</value>
|
<value>Import Rules</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
|
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
|
||||||
<value>Remove selected (Delete)</value>
|
<value>Remove selected</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>Set as active rule (Enter)</value>
|
<value>Set as active rule</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>Domain strategy</value>
|
<value>Domain strategy</value>
|
||||||
@@ -853,7 +853,7 @@
|
|||||||
<value>Rule List</value>
|
<value>Rule List</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRuleRemove" xml:space="preserve">
|
<data name="menuRuleRemove" xml:space="preserve">
|
||||||
<value>Remove Rule (Delete)</value>
|
<value>Remove Rule</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
||||||
<value>Routing Rule Details Setting</value>
|
<value>Routing Rule Details Setting</value>
|
||||||
@@ -922,7 +922,7 @@
|
|||||||
<value>Skip test</value>
|
<value>Skip test</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuEditServer" xml:space="preserve">
|
<data name="menuEditServer" xml:space="preserve">
|
||||||
<value>Edit Configuration (Ctrl+D)</value>
|
<value>Edit </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
||||||
<value>Double-clicking Configuration makes it active</value>
|
<value>Double-clicking Configuration makes it active</value>
|
||||||
@@ -976,7 +976,10 @@
|
|||||||
<value>Enable hardware acceleration (requires restart)</value>
|
<value>Enable hardware acceleration (requires restart)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedtestingWait" xml:space="preserve">
|
<data name="SpeedtestingWait" xml:space="preserve">
|
||||||
<value>Waiting for testing (press ESC to terminate)...</value>
|
<value>Waiting...</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
|
||||||
|
<value>Press ESC to terminate the test</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TipDisplayLog" xml:space="preserve">
|
<data name="TipDisplayLog" xml:space="preserve">
|
||||||
<value>Please turn off when there is an abnormal disconnection</value>
|
<value>Please turn off when there is an abnormal disconnection</value>
|
||||||
@@ -1024,7 +1027,7 @@
|
|||||||
<value>sing-box Mux Protocol</value>
|
<value>sing-box Mux Protocol</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||||
<value>Full process name (Tun mode)</value>
|
<value>Process (Tun mode)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||||
<value>IP or IP CIDR</value>
|
<value>IP or IP CIDR</value>
|
||||||
@@ -1033,7 +1036,7 @@
|
|||||||
<value>Domain</value>
|
<value>Domain</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddHysteria2Server" xml:space="preserve">
|
<data name="menuAddHysteria2Server" xml:space="preserve">
|
||||||
<value>Add [Hysteria2] Configuration</value>
|
<value>Add [Hysteria2] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
|
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
|
||||||
<value>Hysteria Max bandwidth (Up/Down)</value>
|
<value>Hysteria Max bandwidth (Up/Down)</value>
|
||||||
@@ -1042,16 +1045,16 @@
|
|||||||
<value>Use System Hosts</value>
|
<value>Use System Hosts</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddTuicServer" xml:space="preserve">
|
<data name="menuAddTuicServer" xml:space="preserve">
|
||||||
<value>Add [TUIC] Configuration</value>
|
<value>Add [TUIC] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbHeaderType8" xml:space="preserve">
|
<data name="TbHeaderType8" xml:space="preserve">
|
||||||
<value>Congestion control</value>
|
<value>Congestion control</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LvPrevProfile" xml:space="preserve">
|
<data name="LvPrevProfile" xml:space="preserve">
|
||||||
<value>Previous proxy Configuration remarks</value>
|
<value>Previous proxy remarks</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LvNextProfile" xml:space="preserve">
|
<data name="LvNextProfile" xml:space="preserve">
|
||||||
<value>Next proxy Configuration remarks</value>
|
<value>Next proxy remarks</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>Please make sure the Configuration remarks exist and are unique</value>
|
<value>Please make sure the Configuration remarks exist and are unique</value>
|
||||||
@@ -1068,14 +1071,11 @@
|
|||||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
<value>MTU</value>
|
<value>MTU</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
|
||||||
<value>Enable additional Inbound</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||||
<value>Enable IPv6 Address</value>
|
<value>Enable IPv6 Address</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddWireguardServer" xml:space="preserve">
|
<data name="menuAddWireguardServer" xml:space="preserve">
|
||||||
<value>Add [WireGuard] Configuration</value>
|
<value>Add [WireGuard] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPrivateKey" xml:space="preserve">
|
<data name="TbPrivateKey" xml:space="preserve">
|
||||||
<value>Private Key</value>
|
<value>Private Key</value>
|
||||||
@@ -1108,10 +1108,7 @@
|
|||||||
<value>*grpc Authority</value>
|
<value>*grpc Authority</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddHttpServer" xml:space="preserve">
|
<data name="menuAddHttpServer" xml:space="preserve">
|
||||||
<value>Add [HTTP] Configuration</value>
|
<value>Add [HTTP]</value>
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
|
||||||
<value>which conflicts with the group previous proxy</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||||
<value>Enable fragment</value>
|
<value>Enable fragment</value>
|
||||||
@@ -1204,7 +1201,7 @@
|
|||||||
<value>Refresh Proxies</value>
|
<value>Refresh Proxies</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||||
<value>Select active node (Enter)</value>
|
<value>Select active node</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
||||||
<value>Default domain strategy for outbound</value>
|
<value>Default domain strategy for outbound</value>
|
||||||
@@ -1222,7 +1219,7 @@
|
|||||||
<value>Export Base64-encoded Share Links to Clipboard</value>
|
<value>Export Base64-encoded Share Links to Clipboard</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
|
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
|
||||||
<value>Export selected Configuration for complete configuration to clipboard</value>
|
<value>Export selected for complete configuration to clipboard</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuShowOrHideMainWindow" xml:space="preserve">
|
<data name="menuShowOrHideMainWindow" xml:space="preserve">
|
||||||
<value>Show or hide the main window</value>
|
<value>Show or hide the main window</value>
|
||||||
@@ -1378,22 +1375,22 @@
|
|||||||
<value>Generate Policy Group from Multiple Profiles</value>
|
<value>Generate Policy Group from Multiple Profiles</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||||
<value>Multi-Configuration Random by Xray</value>
|
<value>Random by Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||||
<value>Multi-Configuration RoundRobin by Xray</value>
|
<value>RoundRobin by Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||||
<value>Multi-Configuration LeastPing by Xray</value>
|
<value>LeastPing by Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||||
<value>Multi-Configuration LeastLoad by Xray</value>
|
<value>LeastLoad by Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||||
<value>Multi-Configuration LeastPing by sing-box</value>
|
<value>LeastPing by sing-box</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExportConfig" xml:space="preserve">
|
<data name="menuExportConfig" xml:space="preserve">
|
||||||
<value>Export Configuration</value>
|
<value>Export</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||||
<value>Current connection info test URL</value>
|
<value>Current connection info test URL</value>
|
||||||
@@ -1408,7 +1405,7 @@
|
|||||||
<value>Mldsa65Verify</value>
|
<value>Mldsa65Verify</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddAnytlsServer" xml:space="preserve">
|
<data name="menuAddAnytlsServer" xml:space="preserve">
|
||||||
<value>Add [Anytls] Configuration</value>
|
<value>Add [Anytls]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNS" xml:space="preserve">
|
<data name="TbRemoteDNS" xml:space="preserve">
|
||||||
<value>Remote DNS</value>
|
<value>Remote DNS</value>
|
||||||
@@ -1416,17 +1413,11 @@
|
|||||||
<data name="TbDomesticDNS" xml:space="preserve">
|
<data name="TbDomesticDNS" xml:space="preserve">
|
||||||
<value>Domestic DNS</value>
|
<value>Domestic DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||||
<value>Via proxy — please ensure remote availability</value>
|
<value>Direct Target Resolution Strategy</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||||
<value>xray Freedom Resolution Strategy</value>
|
<value>Proxy Target Resolution Strategy</value>
|
||||||
</data>
|
|
||||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box Direct Resolution Strategy</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box Remote Resolution Strategy</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Add Common DNS Hosts</value>
|
<value>Add Common DNS Hosts</value>
|
||||||
@@ -1450,7 +1441,7 @@
|
|||||||
<value>Validate Regional Domain IPs</value>
|
<value>Validate Regional Domain IPs</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||||
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
|
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||||
<value>Enable Custom DNS</value>
|
<value>Enable Custom DNS</value>
|
||||||
@@ -1525,28 +1516,28 @@
|
|||||||
<value>Policy Group Type</value>
|
<value>Policy Group Type</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
||||||
<value>Add Policy Group Configuration</value>
|
<value>Add Policy Group </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddProxyChainServer" xml:space="preserve">
|
<data name="menuAddProxyChainServer" xml:space="preserve">
|
||||||
<value>Add Proxy Chain Configuration</value>
|
<value>Add Proxy Chain</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddChildServer" xml:space="preserve">
|
<data name="menuAddChildServer" xml:space="preserve">
|
||||||
<value>Add Child Configuration</value>
|
<value>Add Child </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||||
<value>Remove Child Configuration</value>
|
<value>Remove Child </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuServerList" xml:space="preserve">
|
<data name="menuServerList" xml:space="preserve">
|
||||||
<value>Server List</value>
|
<value>Configuration item 1, Auto add from subscription group</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFallback" xml:space="preserve">
|
<data name="TbFallback" xml:space="preserve">
|
||||||
<value>Fallback</value>
|
<value>Fallback</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||||
<value>Multi-Configuration Fallback by sing-box</value>
|
<value>Fallback by sing-box</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||||
<value>Multi-Configuration Fallback by Xray</value>
|
<value>Fallback by Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
||||||
<value>Core '{0}' does not support network type '{1}'.</value>
|
<value>Core '{0}' does not support network type '{1}'.</value>
|
||||||
@@ -1596,4 +1587,85 @@
|
|||||||
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
||||||
<value>Resolve DNS server domains, requires IP</value>
|
<value>Resolve DNS server domains, requires IP</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="menuFastRealPing" xml:space="preserve">
|
||||||
|
<value>Test real delay</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
|
<value>Auto add filtered configuration from subscription groups</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Pinned certificate (fill in either one)
|
||||||
|
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
|
||||||
|
|
||||||
|
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCertChain" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate Chain</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
|
||||||
|
<value>Please set a valid domain</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertNotSet" xml:space="preserve">
|
||||||
|
<value>Certificate not set</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertSet" xml:space="preserve">
|
||||||
|
<value>Certificate set</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
|
||||||
|
<value>Custom PAC file path</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
|
||||||
|
<value>Custom system proxy script file path</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
|
||||||
|
<value>macOS displays this in the Dock (requires restart)</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullCertTips" xml:space="preserve">
|
||||||
|
<value>Full certificate (chain), PEM format</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertSha256Tips" xml:space="preserve">
|
||||||
|
<value>Certificate fingerprint (SHA-256)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbServeStale" xml:space="preserve">
|
||||||
|
<value>Serve Stale</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbParallelQuery" xml:space="preserve">
|
||||||
|
<value>Parallel Query</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDomesticDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbHopInt7" xml:space="preserve">
|
||||||
|
<value>Port hopping interval</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerListPreview" xml:space="preserve">
|
||||||
|
<value>Configuration item preview</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFinalmask" xml:space="preserve">
|
||||||
|
<value>Finalmask</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -472,10 +472,10 @@
|
|||||||
<value>Язык (требуется перезапуск)</value>
|
<value>Язык (требуется перезапуск)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
||||||
<value>Импорт массива URL из буфера обмена (Ctrl+V)</value>
|
<value>Импорт массива URL из буфера обмена</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaScan" xml:space="preserve">
|
<data name="menuAddServerViaScan" xml:space="preserve">
|
||||||
<value>Сканировать QR-код с экрана (Ctrl+S)</value>
|
<value>Сканировать QR-код с экрана</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuCopyServer" xml:space="preserve">
|
<data name="menuCopyServer" xml:space="preserve">
|
||||||
<value>Клонировать выбранный сервер</value>
|
<value>Клонировать выбранный сервер</value>
|
||||||
@@ -484,31 +484,31 @@
|
|||||||
<value>Удалить дубликаты серверов</value>
|
<value>Удалить дубликаты серверов</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveServer" xml:space="preserve">
|
<data name="menuRemoveServer" xml:space="preserve">
|
||||||
<value>Удалить выбранные серверы (Delete)</value>
|
<value>Удалить выбранные серверы</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSetDefaultServer" xml:space="preserve">
|
<data name="menuSetDefaultServer" xml:space="preserve">
|
||||||
<value>Установить как активный сервер (Enter)</value>
|
<value>Установить как активный сервер</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuClearServerStatistics" xml:space="preserve">
|
<data name="menuClearServerStatistics" xml:space="preserve">
|
||||||
<value>Очистить всю статистику</value>
|
<value>Очистить всю статистику</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRealPingServer" xml:space="preserve">
|
<data name="menuRealPingServer" xml:space="preserve">
|
||||||
<value>Тест на реальную задержку сервера (Ctrl+R)</value>
|
<value>Тест на реальную задержку сервера</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSortServerResult" xml:space="preserve">
|
<data name="menuSortServerResult" xml:space="preserve">
|
||||||
<value>Сортировать по результату теста</value>
|
<value>Сортировать по результату теста</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSpeedServer" xml:space="preserve">
|
<data name="menuSpeedServer" xml:space="preserve">
|
||||||
<value>Тест на скорость загрузки сервера (Ctrl+T)</value>
|
<value>Тест на скорость загрузки сервера</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuTcpingServer" xml:space="preserve">
|
<data name="menuTcpingServer" xml:space="preserve">
|
||||||
<value>Тест задержки с tcping (Ctrl+O)</value>
|
<value>Тест задержки с tcping</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ClientConfig" xml:space="preserve">
|
<data name="menuExport2ClientConfig" xml:space="preserve">
|
||||||
<value>Экспортировать выбранный сервер для клиента</value>
|
<value>Экспортировать выбранный сервер для клиента</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ShareUrl" xml:space="preserve">
|
<data name="menuExport2ShareUrl" xml:space="preserve">
|
||||||
<value>Экспорт URL-адресов общего доступа в буфер обмена (Ctrl+C)</value>
|
<value>Экспорт URL-адресов общего доступа в буфер обмена</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddCustomServer" xml:space="preserve">
|
<data name="menuAddCustomServer" xml:space="preserve">
|
||||||
<value>Добавить сервер пользовательской конфигурации</value>
|
<value>Добавить сервер пользовательской конфигурации</value>
|
||||||
@@ -529,19 +529,19 @@
|
|||||||
<value>Добавить сервер [VMess]</value>
|
<value>Добавить сервер [VMess]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSelectAll" xml:space="preserve">
|
<data name="menuSelectAll" xml:space="preserve">
|
||||||
<value>Выбрать все (Ctrl+A)</value>
|
<value>Выбрать все</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewClear" xml:space="preserve">
|
<data name="menuMsgViewClear" xml:space="preserve">
|
||||||
<value>Очистить все</value>
|
<value>Очистить все</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopy" xml:space="preserve">
|
<data name="menuMsgViewCopy" xml:space="preserve">
|
||||||
<value>Скопировать (Ctrl+C)</value>
|
<value>Скопировать</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
||||||
<value>Скопировать все</value>
|
<value>Скопировать все</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
||||||
<value>Выбрать все (Ctrl+A)</value>
|
<value>Выбрать все</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSubAdd" xml:space="preserve">
|
<data name="menuSubAdd" xml:space="preserve">
|
||||||
<value>Добавить</value>
|
<value>Добавить</value>
|
||||||
@@ -781,7 +781,7 @@
|
|||||||
<value>Режим PAC</value>
|
<value>Режим PAC</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuShareServer" xml:space="preserve">
|
<data name="menuShareServer" xml:space="preserve">
|
||||||
<value>Поделиться сервером (Ctrl+F)</value>
|
<value>Поделиться сервером</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRouting" xml:space="preserve">
|
<data name="menuRouting" xml:space="preserve">
|
||||||
<value>Маршрутизация</value>
|
<value>Маршрутизация</value>
|
||||||
@@ -793,16 +793,16 @@
|
|||||||
<value>Администратор</value>
|
<value>Администратор</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveBottom" xml:space="preserve">
|
<data name="menuMoveBottom" xml:space="preserve">
|
||||||
<value>Спуститься вниз (B)</value>
|
<value>Спуститься вниз</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveDown" xml:space="preserve">
|
<data name="menuMoveDown" xml:space="preserve">
|
||||||
<value>Вниз (D)</value>
|
<value>Вниз</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveTop" xml:space="preserve">
|
<data name="menuMoveTop" xml:space="preserve">
|
||||||
<value>Подняться наверх (T)</value>
|
<value>Подняться наверх</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveUp" xml:space="preserve">
|
<data name="menuMoveUp" xml:space="preserve">
|
||||||
<value>Вверх (U)</value>
|
<value>Вверх</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgFilterTitle" xml:space="preserve">
|
<data name="MsgFilterTitle" xml:space="preserve">
|
||||||
<value>Фильтр, поддерживает regex</value>
|
<value>Фильтр, поддерживает regex</value>
|
||||||
@@ -853,7 +853,7 @@
|
|||||||
<value>Список правил</value>
|
<value>Список правил</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRuleRemove" xml:space="preserve">
|
<data name="menuRuleRemove" xml:space="preserve">
|
||||||
<value>Удалить правила (Delete)</value>
|
<value>Удалить правила</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
||||||
<value>Детальные настройки правил маршрутизации</value>
|
<value>Детальные настройки правил маршрутизации</value>
|
||||||
@@ -922,7 +922,7 @@
|
|||||||
<value>Пропустить тест</value>
|
<value>Пропустить тест</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuEditServer" xml:space="preserve">
|
<data name="menuEditServer" xml:space="preserve">
|
||||||
<value>Редактировать сервер (Ctrl+D)</value>
|
<value>Редактировать сервер</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
||||||
<value>Двойной клик чтобы сделать сервер активным</value>
|
<value>Двойной клик чтобы сделать сервер активным</value>
|
||||||
@@ -976,7 +976,10 @@
|
|||||||
<value>Включить аппаратное ускорение (требуется перезагрузка)</value>
|
<value>Включить аппаратное ускорение (требуется перезагрузка)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedtestingWait" xml:space="preserve">
|
<data name="SpeedtestingWait" xml:space="preserve">
|
||||||
<value>Ожидание тестирования (нажмите ESC для отмены)…</value>
|
<value>Ожидание тестирования…</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
|
||||||
|
<value>нажмите ESC для отмены</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TipDisplayLog" xml:space="preserve">
|
<data name="TipDisplayLog" xml:space="preserve">
|
||||||
<value>Отключите при аномальном разрыве соединения</value>
|
<value>Отключите при аномальном разрыве соединения</value>
|
||||||
@@ -1024,7 +1027,7 @@
|
|||||||
<value>Протокол Mux для sing-box</value>
|
<value>Протокол Mux для sing-box</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||||
<value>Полное имя процесса (режим TUN)</value>
|
<value>Process (Tun mode)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||||
<value>IP-адрес или сеть CIDR</value>
|
<value>IP-адрес или сеть CIDR</value>
|
||||||
@@ -1068,9 +1071,6 @@
|
|||||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
<value>MTU</value>
|
<value>MTU</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
|
||||||
<value>Включить дополнительный входящий канал</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||||
<value>Включить IPv6 адреса</value>
|
<value>Включить IPv6 адреса</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1110,9 +1110,6 @@
|
|||||||
<data name="menuAddHttpServer" xml:space="preserve">
|
<data name="menuAddHttpServer" xml:space="preserve">
|
||||||
<value>Добавить сервер [HTTP]</value>
|
<value>Добавить сервер [HTTP]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
|
||||||
<value>что конфликтует с предыдущим прокси группы</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||||
<value>Включить фрагментацию (Fragment)</value>
|
<value>Включить фрагментацию (Fragment)</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1204,7 +1201,7 @@
|
|||||||
<value>Обновить прокси</value>
|
<value>Обновить прокси</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||||
<value>Сделать узел активным (Enter)</value>
|
<value>Сделать узел активным</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
||||||
<value>Стратегия домена по умолчанию для исходящих</value>
|
<value>Стратегия домена по умолчанию для исходящих</value>
|
||||||
@@ -1416,17 +1413,11 @@
|
|||||||
<data name="TbDomesticDNS" xml:space="preserve">
|
<data name="TbDomesticDNS" xml:space="preserve">
|
||||||
<value>Внутренний DNS</value>
|
<value>Внутренний DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||||
<value>Via proxy — please ensure remote availability</value>
|
<value>Direct Target Resolution Strategy</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||||
<value>Стратегия резолвинга Freedom (Xray)</value>
|
<value>Proxy Target Resolution Strategy</value>
|
||||||
</data>
|
|
||||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
|
||||||
<value>Стратегия прямого резолвинга (sing-box)</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
|
||||||
<value>Стратегия удалённого резолвинга (sing-box)</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Добавить стандартные записи hosts (DNS)</value>
|
<value>Добавить стандартные записи hosts (DNS)</value>
|
||||||
@@ -1450,7 +1441,7 @@
|
|||||||
<value>Проверять IP-адреса региональных доменов</value>
|
<value>Проверять IP-адреса региональных доменов</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||||
<value>При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn), и оставляет только ожидаемые IP-адреса</value>
|
<value>При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn - geoip:cn), и оставляет только ожидаемые IP-адреса</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||||
<value>Включить пользовательский DNS</value>
|
<value>Включить пользовательский DNS</value>
|
||||||
@@ -1537,7 +1528,7 @@
|
|||||||
<value>Remove Child Configuration</value>
|
<value>Remove Child Configuration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuServerList" xml:space="preserve">
|
<data name="menuServerList" xml:space="preserve">
|
||||||
<value>Server List</value>
|
<value>Configuration item 1, Auto add from subscription group</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFallback" xml:space="preserve">
|
<data name="TbFallback" xml:space="preserve">
|
||||||
<value>Fallback</value>
|
<value>Fallback</value>
|
||||||
@@ -1596,4 +1587,85 @@
|
|||||||
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
||||||
<value>Resolve DNS server domains, requires IP</value>
|
<value>Resolve DNS server domains, requires IP</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="menuFastRealPing" xml:space="preserve">
|
||||||
|
<value>Test real delay</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
|
<value>Auto add filtered configuration from subscription groups</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>Certificate Pinning</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>Pinned certificate (fill in either one)
|
||||||
|
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
|
||||||
|
|
||||||
|
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCertChain" xml:space="preserve">
|
||||||
|
<value>Fetch Certificate Chain</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
|
||||||
|
<value>Please set a valid domain</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertNotSet" xml:space="preserve">
|
||||||
|
<value>Certificate not set</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertSet" xml:space="preserve">
|
||||||
|
<value>Certificate set</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
|
||||||
|
<value>Custom PAC file path</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
|
||||||
|
<value>Custom system proxy script file path</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
|
||||||
|
<value>macOS displays this in the Dock (requires restart)</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullCertTips" xml:space="preserve">
|
||||||
|
<value>Full certificate (chain), PEM format</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertSha256Tips" xml:space="preserve">
|
||||||
|
<value>Certificate fingerprint (SHA-256)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbServeStale" xml:space="preserve">
|
||||||
|
<value>Serve Stale</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbParallelQuery" xml:space="preserve">
|
||||||
|
<value>Parallel Query</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDomesticDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbHopInt7" xml:space="preserve">
|
||||||
|
<value>Port hopping interval</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerListPreview" xml:space="preserve">
|
||||||
|
<value>Configuration item preview</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFinalmask" xml:space="preserve">
|
||||||
|
<value>Finalmask</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -427,7 +427,7 @@
|
|||||||
<value>路由设置</value>
|
<value>路由设置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuServers" xml:space="preserve">
|
<data name="menuServers" xml:space="preserve">
|
||||||
<value>配置文件</value>
|
<value>配置项</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSetting" xml:space="preserve">
|
<data name="menuSetting" xml:space="preserve">
|
||||||
<value>设置</value>
|
<value>设置</value>
|
||||||
@@ -472,10 +472,10 @@
|
|||||||
<value>语言 (需重启)</value>
|
<value>语言 (需重启)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
||||||
<value>从剪贴板导入分享链接 (Ctrl+V)</value>
|
<value>从剪贴板导入分享链接</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaScan" xml:space="preserve">
|
<data name="menuAddServerViaScan" xml:space="preserve">
|
||||||
<value>扫描屏幕上的二维码 (Ctrl+S)</value>
|
<value>扫描屏幕上的二维码</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuCopyServer" xml:space="preserve">
|
<data name="menuCopyServer" xml:space="preserve">
|
||||||
<value>克隆所选</value>
|
<value>克隆所选</value>
|
||||||
@@ -484,31 +484,31 @@
|
|||||||
<value>移除重复</value>
|
<value>移除重复</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveServer" xml:space="preserve">
|
<data name="menuRemoveServer" xml:space="preserve">
|
||||||
<value>移除所选 (多选) (Delete)</value>
|
<value>移除所选 (多选)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSetDefaultServer" xml:space="preserve">
|
<data name="menuSetDefaultServer" xml:space="preserve">
|
||||||
<value>设为活动 (Enter)</value>
|
<value>设为活动</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuClearServerStatistics" xml:space="preserve">
|
<data name="menuClearServerStatistics" xml:space="preserve">
|
||||||
<value>清除所有服务统计数据</value>
|
<value>清除所有服务统计数据</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRealPingServer" xml:space="preserve">
|
<data name="menuRealPingServer" xml:space="preserve">
|
||||||
<value>测试真连接延迟 (多选) (Ctrl+R)</value>
|
<value>测试真连接延迟 (多选)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSortServerResult" xml:space="preserve">
|
<data name="menuSortServerResult" xml:space="preserve">
|
||||||
<value>按测试结果排序</value>
|
<value>按测试结果排序</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSpeedServer" xml:space="preserve">
|
<data name="menuSpeedServer" xml:space="preserve">
|
||||||
<value>测试速度 (多选) (Ctrl+T)</value>
|
<value>测试速度 (多选)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuTcpingServer" xml:space="preserve">
|
<data name="menuTcpingServer" xml:space="preserve">
|
||||||
<value>测试延迟 Tcping (多选) (Ctrl+O)</value>
|
<value>测试延迟 Tcping (多选)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ClientConfig" xml:space="preserve">
|
<data name="menuExport2ClientConfig" xml:space="preserve">
|
||||||
<value>导出所选完整配置</value>
|
<value>导出所选完整配置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ShareUrl" xml:space="preserve">
|
<data name="menuExport2ShareUrl" xml:space="preserve">
|
||||||
<value>导出分享链接至剪贴板 (多选) (Ctrl+C)</value>
|
<value>导出分享链接至剪贴板 (多选)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddCustomServer" xml:space="preserve">
|
<data name="menuAddCustomServer" xml:space="preserve">
|
||||||
<value>添加自定义配置</value>
|
<value>添加自定义配置</value>
|
||||||
@@ -529,19 +529,19 @@
|
|||||||
<value>添加 [VMess] </value>
|
<value>添加 [VMess] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSelectAll" xml:space="preserve">
|
<data name="menuSelectAll" xml:space="preserve">
|
||||||
<value>全选 (Ctrl+A)</value>
|
<value>全选</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewClear" xml:space="preserve">
|
<data name="menuMsgViewClear" xml:space="preserve">
|
||||||
<value>清除所有</value>
|
<value>清除所有</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopy" xml:space="preserve">
|
<data name="menuMsgViewCopy" xml:space="preserve">
|
||||||
<value>复制 (Ctrl+C)</value>
|
<value>复制</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
||||||
<value>复制所有</value>
|
<value>复制所有</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
||||||
<value>全选 (Ctrl+A)</value>
|
<value>全选</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSubAdd" xml:space="preserve">
|
<data name="menuSubAdd" xml:space="preserve">
|
||||||
<value>添加</value>
|
<value>添加</value>
|
||||||
@@ -781,7 +781,7 @@
|
|||||||
<value>Pac 模式</value>
|
<value>Pac 模式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuShareServer" xml:space="preserve">
|
<data name="menuShareServer" xml:space="preserve">
|
||||||
<value>分享 (Ctrl+F)</value>
|
<value>分享</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRouting" xml:space="preserve">
|
<data name="menuRouting" xml:space="preserve">
|
||||||
<value>路由</value>
|
<value>路由</value>
|
||||||
@@ -793,16 +793,16 @@
|
|||||||
<value>以管理员身份运行</value>
|
<value>以管理员身份运行</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveBottom" xml:space="preserve">
|
<data name="menuMoveBottom" xml:space="preserve">
|
||||||
<value>下移至底 (B)</value>
|
<value>下移至底</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveDown" xml:space="preserve">
|
<data name="menuMoveDown" xml:space="preserve">
|
||||||
<value>下移 (D)</value>
|
<value>下移</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveTop" xml:space="preserve">
|
<data name="menuMoveTop" xml:space="preserve">
|
||||||
<value>上移至顶 (T)</value>
|
<value>上移至顶</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveUp" xml:space="preserve">
|
<data name="menuMoveUp" xml:space="preserve">
|
||||||
<value>上移 (U)</value>
|
<value>上移</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgFilterTitle" xml:space="preserve">
|
<data name="MsgFilterTitle" xml:space="preserve">
|
||||||
<value>过滤器 (支持正则)</value>
|
<value>过滤器 (支持正则)</value>
|
||||||
@@ -817,10 +817,10 @@
|
|||||||
<value>一键导入规则集</value>
|
<value>一键导入规则集</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
|
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
|
||||||
<value>移除所选规则 (Delete)</value>
|
<value>移除所选规则</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>设为活动规则 (Enter)</value>
|
<value>设为活动规则</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>域名解析策略</value>
|
<value>域名解析策略</value>
|
||||||
@@ -853,7 +853,7 @@
|
|||||||
<value>规则列表</value>
|
<value>规则列表</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRuleRemove" xml:space="preserve">
|
<data name="menuRuleRemove" xml:space="preserve">
|
||||||
<value>移除所选规则 (Delete)</value>
|
<value>移除所选规则</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
||||||
<value>路由规则详情设置</value>
|
<value>路由规则详情设置</value>
|
||||||
@@ -922,7 +922,7 @@
|
|||||||
<value>跳过测试</value>
|
<value>跳过测试</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuEditServer" xml:space="preserve">
|
<data name="menuEditServer" xml:space="preserve">
|
||||||
<value>编辑 (Ctrl+D)</value>
|
<value>编辑</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
||||||
<value>主界面双击设为活动</value>
|
<value>主界面双击设为活动</value>
|
||||||
@@ -976,7 +976,10 @@
|
|||||||
<value>启用硬件加速 (需重启)</value>
|
<value>启用硬件加速 (需重启)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedtestingWait" xml:space="preserve">
|
<data name="SpeedtestingWait" xml:space="preserve">
|
||||||
<value>等待测试中 (按 ESC 终止)...</value>
|
<value>等待测试...</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
|
||||||
|
<value>按 ESC 可终止测试</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TipDisplayLog" xml:space="preserve">
|
<data name="TipDisplayLog" xml:space="preserve">
|
||||||
<value>当有异常断流时请关闭</value>
|
<value>当有异常断流时请关闭</value>
|
||||||
@@ -1021,7 +1024,7 @@
|
|||||||
<value>sing-box Mux 多路复用协议</value>
|
<value>sing-box Mux 多路复用协议</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||||
<value>进程名全称 (Tun 模式)</value>
|
<value>进程 (Tun 模式)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||||
<value>IP 或 IP CIDR</value>
|
<value>IP 或 IP CIDR</value>
|
||||||
@@ -1065,9 +1068,6 @@
|
|||||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
<value>MTU</value>
|
<value>MTU</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
|
||||||
<value>启用额外监听端口</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||||
<value>启用 IPv6</value>
|
<value>启用 IPv6</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1107,9 +1107,6 @@
|
|||||||
<data name="menuAddHttpServer" xml:space="preserve">
|
<data name="menuAddHttpServer" xml:space="preserve">
|
||||||
<value>添加 [HTTP] </value>
|
<value>添加 [HTTP] </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
|
||||||
<value>和分组前置代理冲突</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||||
<value>启用分片 (Fragment)</value>
|
<value>启用分片 (Fragment)</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1201,7 +1198,7 @@
|
|||||||
<value>刷新</value>
|
<value>刷新</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||||
<value>设为活动 (Enter)</value>
|
<value>设为活动</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
||||||
<value>Outbound 默认解析策略</value>
|
<value>Outbound 默认解析策略</value>
|
||||||
@@ -1413,17 +1410,11 @@
|
|||||||
<data name="TbDomesticDNS" xml:space="preserve">
|
<data name="TbDomesticDNS" xml:space="preserve">
|
||||||
<value>直连 DNS</value>
|
<value>直连 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||||
<value>通过代理,请确保远程可用</value>
|
<value>直连目标解析策略</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||||
<value>xray freedom 解析策略</value>
|
<value>代理目标解析策略</value>
|
||||||
</data>
|
|
||||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box 直连解析策略</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box 远程解析策略</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>添加常用 DNS Hosts</value>
|
<value>添加常用 DNS Hosts</value>
|
||||||
@@ -1447,7 +1438,7 @@
|
|||||||
<value>校验相应地区域名 IP</value>
|
<value>校验相应地区域名 IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||||
<value>配置后,会对相应地区域名(如 geosite:cn)的返回 IP 进行校验,仅返回期望 IP</value>
|
<value>配置后,会对相应地区域名(如 geosite:cn - geoip:cn)的返回 IP 进行校验,仅返回期望 IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||||
<value>启用自定义 DNS</value>
|
<value>启用自定义 DNS</value>
|
||||||
@@ -1528,13 +1519,13 @@
|
|||||||
<value>添加链式代理</value>
|
<value>添加链式代理</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddChildServer" xml:space="preserve">
|
<data name="menuAddChildServer" xml:space="preserve">
|
||||||
<value>添加子项</value>
|
<value>添加子配置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||||
<value>删除子项</value>
|
<value>删除子配置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuServerList" xml:space="preserve">
|
<data name="menuServerList" xml:space="preserve">
|
||||||
<value>子项列表</value>
|
<value>子配置项一,从订阅分组中自动添加</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFallback" xml:space="preserve">
|
<data name="TbFallback" xml:space="preserve">
|
||||||
<value>故障转移</value>
|
<value>故障转移</value>
|
||||||
@@ -1593,4 +1584,85 @@
|
|||||||
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
||||||
<value>解析 DNS 服务器域名,需指定为 IP</value>
|
<value>解析 DNS 服务器域名,需指定为 IP</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="menuFastRealPing" xml:space="preserve">
|
||||||
|
<value>一键测试真连接延迟</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
|
<value>自动从订阅分组添加过滤后的配置</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>固定证书</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>固定证书(二选一填写即可)
|
||||||
|
当指定此证书后,将固定该证书,并禁用“跳过证书验证”选项。
|
||||||
|
|
||||||
|
“获取证书”操作可能失败,原因包括使用了自签名证书,或系统中存在不受信任甚至恶意的 CA。</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>获取证书</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCertChain" xml:space="preserve">
|
||||||
|
<value>获取证书链</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
|
||||||
|
<value>请设置有效的域名</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertNotSet" xml:space="preserve">
|
||||||
|
<value>证书未设置</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertSet" xml:space="preserve">
|
||||||
|
<value>证书已设置</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
|
||||||
|
<value>自定义 PAC 文件路径</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
|
||||||
|
<value>自定义系统代理脚本文件路径</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
|
||||||
|
<value>macOS 在 Dock 栏中显示 (需重启)</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
|
<value>子配置项二,从自建中选择添加</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullCertTips" xml:space="preserve">
|
||||||
|
<value>完整证书(链),PEM 格式</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertSha256Tips" xml:space="preserve">
|
||||||
|
<value>证书指纹(SHA-256)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbServeStale" xml:space="preserve">
|
||||||
|
<value>乐观缓存</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbParallelQuery" xml:space="preserve">
|
||||||
|
<value>并行查询</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDomesticDNSTips" xml:space="preserve">
|
||||||
|
<value>默认仅在路由阶段被调用解析</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||||
|
<value>默认仅在路由阶段被调用解析;请确保远程服务器可访问该 DNS</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>当未选择或 "AsIs" 时,使用系统 DNS 进行解析;否则,使用内部 DNS 模块解析。</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>当未选择或 "AsIs" 时,由远程服务器端 DNS 解析;否则,使用内部 DNS 模块解析。</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbHopInt7" xml:space="preserve">
|
||||||
|
<value>端口跳跃间隔</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerListPreview" xml:space="preserve">
|
||||||
|
<value>子配置项预览</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFinalmask" xml:space="preserve">
|
||||||
|
<value>Finalmask</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
<value>匯出分享連結至剪貼簿成功</value>
|
<value>匯出分享連結至剪貼簿成功</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CheckServerSettings" xml:space="preserve">
|
<data name="CheckServerSettings" xml:space="preserve">
|
||||||
<value>請先檢查設定檔設定</value>
|
<value>請先檢查設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ConfigurationFormatIncorrect" xml:space="preserve">
|
<data name="ConfigurationFormatIncorrect" xml:space="preserve">
|
||||||
<value>設定格式不正確</value>
|
<value>設定格式不正確</value>
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
<value>下載開始...</value>
|
<value>下載開始...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FailedConversionConfiguration" xml:space="preserve">
|
<data name="FailedConversionConfiguration" xml:space="preserve">
|
||||||
<value>轉換設定檔失敗</value>
|
<value>轉換設定失敗</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FailedGenDefaultConfiguration" xml:space="preserve">
|
<data name="FailedGenDefaultConfiguration" xml:space="preserve">
|
||||||
<value>生成預設設定檔失敗</value>
|
<value>生成預設設定檔失敗</value>
|
||||||
@@ -142,10 +142,10 @@
|
|||||||
<value>獲取預設設定失敗</value>
|
<value>獲取預設設定失敗</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FailedImportedCustomServer" xml:space="preserve">
|
<data name="FailedImportedCustomServer" xml:space="preserve">
|
||||||
<value>匯入自訂設定設定檔失敗</value>
|
<value>匯入自訂設定失敗</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FailedReadConfiguration" xml:space="preserve">
|
<data name="FailedReadConfiguration" xml:space="preserve">
|
||||||
<value>讀取設定檔失敗</value>
|
<value>讀取設定失敗</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectServerPort" xml:space="preserve">
|
<data name="FillCorrectServerPort" xml:space="preserve">
|
||||||
<value>請填寫正確格式的埠</value>
|
<value>請填寫正確格式的埠</value>
|
||||||
@@ -265,13 +265,13 @@
|
|||||||
<value>請選擇協定</value>
|
<value>請選擇協定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PleaseSelectServer" xml:space="preserve">
|
<data name="PleaseSelectServer" xml:space="preserve">
|
||||||
<value>請先選擇設定檔</value>
|
<value>請先選擇設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoveDuplicateServerResult" xml:space="preserve">
|
<data name="RemoveDuplicateServerResult" xml:space="preserve">
|
||||||
<value>設定檔去重完成。原數量: {0},現數量: {1}。</value>
|
<value>去重完成。原數量: {0},現數量: {1}。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoveServer" xml:space="preserve">
|
<data name="RemoveServer" xml:space="preserve">
|
||||||
<value>是否確定移除設定檔?</value>
|
<value>是否確定移除?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SaveClientConfigurationIn" xml:space="preserve">
|
<data name="SaveClientConfigurationIn" xml:space="preserve">
|
||||||
<value>用戶端設定檔儲存在:{0}</value>
|
<value>用戶端設定檔儲存在:{0}</value>
|
||||||
@@ -283,10 +283,10 @@
|
|||||||
<value>設定成功。{0}</value>
|
<value>設定成功。{0}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
|
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
|
||||||
<value>成功匯入自訂設定設定檔</value>
|
<value>成功匯入自訂節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
|
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
|
||||||
<value>成功從剪貼簿匯入 {0} 個設定檔</value>
|
<value>成功從剪貼簿匯入 {0} 個節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SuccessfullyImportedServerViaScan" xml:space="preserve">
|
<data name="SuccessfullyImportedServerViaScan" xml:space="preserve">
|
||||||
<value>掃描匯入分享連結成功</value>
|
<value>掃描匯入分享連結成功</value>
|
||||||
@@ -385,7 +385,7 @@
|
|||||||
<value>所有</value>
|
<value>所有</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillServerAddressCustom" xml:space="preserve">
|
<data name="FillServerAddressCustom" xml:space="preserve">
|
||||||
<value>請瀏覽匯入設定檔設定</value>
|
<value>請瀏覽匯入設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Speedtesting" xml:space="preserve">
|
<data name="Speedtesting" xml:space="preserve">
|
||||||
<value>測試中...</value>
|
<value>測試中...</value>
|
||||||
@@ -397,7 +397,7 @@
|
|||||||
<value>本機</value>
|
<value>本機</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgServerTitle" xml:space="preserve">
|
<data name="MsgServerTitle" xml:space="preserve">
|
||||||
<value>設定檔過濾,按 Enter 執行</value>
|
<value>過濾器,按 Enter 執行</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuCheckUpdate" xml:space="preserve">
|
<data name="menuCheckUpdate" xml:space="preserve">
|
||||||
<value>檢查更新</value>
|
<value>檢查更新</value>
|
||||||
@@ -472,76 +472,76 @@
|
|||||||
<value>語言 (需重啟)</value>
|
<value>語言 (需重啟)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
||||||
<value>從剪貼簿導入分享連結 (Ctrl+V)</value>
|
<value>從剪貼簿導入分享連結</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddServerViaScan" xml:space="preserve">
|
<data name="menuAddServerViaScan" xml:space="preserve">
|
||||||
<value>掃描螢幕上的二維碼 (Ctrl+S)</value>
|
<value>掃描螢幕上的二維碼</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuCopyServer" xml:space="preserve">
|
<data name="menuCopyServer" xml:space="preserve">
|
||||||
<value>複製所選設定檔</value>
|
<value>複製所選</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveDuplicateServer" xml:space="preserve">
|
<data name="menuRemoveDuplicateServer" xml:space="preserve">
|
||||||
<value>移除重複的設定檔</value>
|
<value>移除重複</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveServer" xml:space="preserve">
|
<data name="menuRemoveServer" xml:space="preserve">
|
||||||
<value>移除所選設定檔 (多選) (Delete)</value>
|
<value>移除所選 (多選)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSetDefaultServer" xml:space="preserve">
|
<data name="menuSetDefaultServer" xml:space="preserve">
|
||||||
<value>設為活動設定檔 (Enter)</value>
|
<value>設為活動</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuClearServerStatistics" xml:space="preserve">
|
<data name="menuClearServerStatistics" xml:space="preserve">
|
||||||
<value>清除所有服務統計資料</value>
|
<value>清除所有服務統計資料</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRealPingServer" xml:space="preserve">
|
<data name="menuRealPingServer" xml:space="preserve">
|
||||||
<value>測試設定檔真連線延遲 (多選) (Ctrl+R)</value>
|
<value>測試真連線延遲 (多選)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSortServerResult" xml:space="preserve">
|
<data name="menuSortServerResult" xml:space="preserve">
|
||||||
<value>按測試結果排序</value>
|
<value>按測試結果排序</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSpeedServer" xml:space="preserve">
|
<data name="menuSpeedServer" xml:space="preserve">
|
||||||
<value>測試設定檔速度 (多選) (Ctrl+T)</value>
|
<value>測試速度 (多選)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuTcpingServer" xml:space="preserve">
|
<data name="menuTcpingServer" xml:space="preserve">
|
||||||
<value>測試設定檔延遲 Tcping (多選) (Ctrl+O)</value>
|
<value>測試延遲 Tcping (多選)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ClientConfig" xml:space="preserve">
|
<data name="menuExport2ClientConfig" xml:space="preserve">
|
||||||
<value>匯出所選設定檔完整設定</value>
|
<value>匯出所選完整設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ShareUrl" xml:space="preserve">
|
<data name="menuExport2ShareUrl" xml:space="preserve">
|
||||||
<value>匯出分享連結至剪貼簿 (多選) (Ctrl+C)</value>
|
<value>匯出分享連結至剪貼簿 (多選)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddCustomServer" xml:space="preserve">
|
<data name="menuAddCustomServer" xml:space="preserve">
|
||||||
<value>新增自訂設定設定檔</value>
|
<value>新增自訂節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddShadowsocksServer" xml:space="preserve">
|
<data name="menuAddShadowsocksServer" xml:space="preserve">
|
||||||
<value>新增 [Shadowsocks] 設定檔</value>
|
<value>新增 [Shadowsocks] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddSocksServer" xml:space="preserve">
|
<data name="menuAddSocksServer" xml:space="preserve">
|
||||||
<value>新增 [SOCKS] 設定檔</value>
|
<value>新增 [SOCKS] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddTrojanServer" xml:space="preserve">
|
<data name="menuAddTrojanServer" xml:space="preserve">
|
||||||
<value>新增 [Trojan] 設定檔</value>
|
<value>新增 [Trojan] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddVlessServer" xml:space="preserve">
|
<data name="menuAddVlessServer" xml:space="preserve">
|
||||||
<value>新增 [VLESS] 設定檔</value>
|
<value>新增 [VLESS] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddVmessServer" xml:space="preserve">
|
<data name="menuAddVmessServer" xml:space="preserve">
|
||||||
<value>新增 [VMess] 設定檔</value>
|
<value>新增 [VMess] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSelectAll" xml:space="preserve">
|
<data name="menuSelectAll" xml:space="preserve">
|
||||||
<value>全選 (Ctrl+A)</value>
|
<value>全選</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewClear" xml:space="preserve">
|
<data name="menuMsgViewClear" xml:space="preserve">
|
||||||
<value>清除所有</value>
|
<value>清除所有</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopy" xml:space="preserve">
|
<data name="menuMsgViewCopy" xml:space="preserve">
|
||||||
<value>複製 (Ctrl+C)</value>
|
<value>複製</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
<data name="menuMsgViewCopyAll" xml:space="preserve">
|
||||||
<value>複製所有</value>
|
<value>複製所有</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
<data name="menuMsgViewSelectAll" xml:space="preserve">
|
||||||
<value>全選 (Ctrl+A)</value>
|
<value>全選</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuSubAdd" xml:space="preserve">
|
<data name="menuSubAdd" xml:space="preserve">
|
||||||
<value>新增</value>
|
<value>新增</value>
|
||||||
@@ -676,7 +676,7 @@
|
|||||||
<value>Core: 基礎設定</value>
|
<value>Core: 基礎設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDnsRay" xml:space="preserve">
|
<data name="TbCustomDnsRay" xml:space="preserve">
|
||||||
<value>V2ray Custom DNS</value>
|
<value>v2ray 自訂 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsCoreKcp" xml:space="preserve">
|
<data name="TbSettingsCoreKcp" xml:space="preserve">
|
||||||
<value>Core: KCP 設定</value>
|
<value>Core: KCP 設定</value>
|
||||||
@@ -691,7 +691,7 @@
|
|||||||
<value>Outbound Freedom domainStrategy</value>
|
<value>Outbound Freedom domainStrategy</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableAutoAdjustMainLvColWidth" xml:space="preserve">
|
<data name="TbSettingsEnableAutoAdjustMainLvColWidth" xml:space="preserve">
|
||||||
<value>在更新訂閱後自動調整設定檔列寬</value>
|
<value>在更新訂閱後自動調整列寬</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableCheckPreReleaseUpdate" xml:space="preserve">
|
<data name="TbSettingsEnableCheckPreReleaseUpdate" xml:space="preserve">
|
||||||
<value>檢查 Pre-Release 更新 (請謹慎啟用)</value>
|
<value>檢查 Pre-Release 更新 (請謹慎啟用)</value>
|
||||||
@@ -700,7 +700,7 @@
|
|||||||
<value>例外</value>
|
<value>例外</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsExceptionTip" xml:space="preserve">
|
<data name="TbSettingsExceptionTip" xml:space="preserve">
|
||||||
<value>例外:對於下列字元開頭的位址,不使用代理設定檔。使用分號 (;) 分隔。</value>
|
<value>例外:對於下列字元開頭的位址,不使用代理。使用分號 (;) 分隔。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve">
|
<data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve">
|
||||||
<value>顯示即時速度(需重啟)</value>
|
<value>顯示即時速度(需重啟)</value>
|
||||||
@@ -748,7 +748,7 @@
|
|||||||
<value>系統代理設定</value>
|
<value>系統代理設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
|
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
|
||||||
<value>工具列右鍵選單設定檔展示數量限制</value>
|
<value>工具列右鍵選單設定展示數量限制</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsUdpEnabled" xml:space="preserve">
|
<data name="TbSettingsUdpEnabled" xml:space="preserve">
|
||||||
<value>開啟 UDP</value>
|
<value>開啟 UDP</value>
|
||||||
@@ -781,7 +781,7 @@
|
|||||||
<value>PAC 模式</value>
|
<value>PAC 模式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuShareServer" xml:space="preserve">
|
<data name="menuShareServer" xml:space="preserve">
|
||||||
<value>分享設定檔 (Ctrl+F)</value>
|
<value>分享</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRouting" xml:space="preserve">
|
<data name="menuRouting" xml:space="preserve">
|
||||||
<value>路由</value>
|
<value>路由</value>
|
||||||
@@ -793,16 +793,16 @@
|
|||||||
<value>以管理員身份執行</value>
|
<value>以管理員身份執行</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveBottom" xml:space="preserve">
|
<data name="menuMoveBottom" xml:space="preserve">
|
||||||
<value>下移至底部 (B)</value>
|
<value>下移至底部</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveDown" xml:space="preserve">
|
<data name="menuMoveDown" xml:space="preserve">
|
||||||
<value>下移 (D)</value>
|
<value>下移</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveTop" xml:space="preserve">
|
<data name="menuMoveTop" xml:space="preserve">
|
||||||
<value>上移至頂部 (T)</value>
|
<value>上移至頂部</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMoveUp" xml:space="preserve">
|
<data name="menuMoveUp" xml:space="preserve">
|
||||||
<value>上移 (U)</value>
|
<value>上移</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgFilterTitle" xml:space="preserve">
|
<data name="MsgFilterTitle" xml:space="preserve">
|
||||||
<value>過濾 (允許正則)</value>
|
<value>過濾 (允許正則)</value>
|
||||||
@@ -817,10 +817,10 @@
|
|||||||
<value>一鍵匯入規則集</value>
|
<value>一鍵匯入規則集</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
|
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
|
||||||
<value>移除所選規則 (Delete)</value>
|
<value>移除所選規則</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
|
||||||
<value>設為活動規則 (Enter)</value>
|
<value>設為活動規則</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbdomainStrategy" xml:space="preserve">
|
<data name="TbdomainStrategy" xml:space="preserve">
|
||||||
<value>域名解析策略</value>
|
<value>域名解析策略</value>
|
||||||
@@ -853,7 +853,7 @@
|
|||||||
<value>規則列表</value>
|
<value>規則列表</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRuleRemove" xml:space="preserve">
|
<data name="menuRuleRemove" xml:space="preserve">
|
||||||
<value>移除所選規則 (Delete)</value>
|
<value>移除所選規則</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
|
||||||
<value>路由規則詳情設定</value>
|
<value>路由規則詳情設定</value>
|
||||||
@@ -883,7 +883,7 @@
|
|||||||
<value>請勿將代理伺服器用於本機(Intranet)位址</value>
|
<value>請勿將代理伺服器用於本機(Intranet)位址</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuMixedTestServer" xml:space="preserve">
|
<data name="menuMixedTestServer" xml:space="preserve">
|
||||||
<value>一鍵多執行緒測試延遲和速度 (Ctrl+E)</value>
|
<value>一鍵延遲與速度測試 (Ctrl+E)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LvTestDelay" xml:space="preserve">
|
<data name="LvTestDelay" xml:space="preserve">
|
||||||
<value>延遲 (ms)</value>
|
<value>延遲 (ms)</value>
|
||||||
@@ -913,7 +913,7 @@
|
|||||||
<value>移至訂閱分組</value>
|
<value>移至訂閱分組</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableDragDropSort" xml:space="preserve">
|
<data name="TbSettingsEnableDragDropSort" xml:space="preserve">
|
||||||
<value>啟動設定檔拖放排序 (需重啟)</value>
|
<value>啟用拖放排序 (需重啟)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAutoRefresh" xml:space="preserve">
|
<data name="TbAutoRefresh" xml:space="preserve">
|
||||||
<value>自動重新整理</value>
|
<value>自動重新整理</value>
|
||||||
@@ -922,10 +922,10 @@
|
|||||||
<value>跳過測試</value>
|
<value>跳過測試</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuEditServer" xml:space="preserve">
|
<data name="menuEditServer" xml:space="preserve">
|
||||||
<value>編輯設定檔 (Ctrl+D)</value>
|
<value>編輯</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
|
||||||
<value>主介面輕按兩下設為活動設定檔</value>
|
<value>主介面輕按兩下設為活動</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedtestingCompleted" xml:space="preserve">
|
<data name="SpeedtestingCompleted" xml:space="preserve">
|
||||||
<value>測試完成</value>
|
<value>測試完成</value>
|
||||||
@@ -943,7 +943,7 @@
|
|||||||
<value>目前字型 (需重啟)</value>
|
<value>目前字型 (需重啟)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsCurrentFontFamilyTip" xml:space="preserve">
|
<data name="TbSettingsCurrentFontFamilyTip" xml:space="preserve">
|
||||||
<value>複製字型 TTF/TTC 檔案到目錄 guiFonts,重啟設定</value>
|
<value>複製字型 TTF/TTC 檔案到目錄 guiFonts,重新啟動後生效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsSocksPortTip" xml:space="preserve">
|
<data name="TbSettingsSocksPortTip" xml:space="preserve">
|
||||||
<value>Pac 連接埠 = +3;Xray API 連接埠 = +4;mihomo API 連接埠 = +5;</value>
|
<value>Pac 連接埠 = +3;Xray API 連接埠 = +4;mihomo API 連接埠 = +5;</value>
|
||||||
@@ -976,7 +976,10 @@
|
|||||||
<value>啟用硬體加速 (需重啟)</value>
|
<value>啟用硬體加速 (需重啟)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SpeedtestingWait" xml:space="preserve">
|
<data name="SpeedtestingWait" xml:space="preserve">
|
||||||
<value>等待測試中(按 ESC 終止)...</value>
|
<value>等待測試中...</value>
|
||||||
|
</data>
|
||||||
|
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
|
||||||
|
<value>按 ECS 以終止測試</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TipDisplayLog" xml:space="preserve">
|
<data name="TipDisplayLog" xml:space="preserve">
|
||||||
<value>當有異常斷流時請關閉</value>
|
<value>當有異常斷流時請關閉</value>
|
||||||
@@ -1003,10 +1006,10 @@
|
|||||||
<value>不需要轉換時請留空</value>
|
<value>不需要轉換時請留空</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuDNSSetting" xml:space="preserve">
|
<data name="menuDNSSetting" xml:space="preserve">
|
||||||
<value>DNS 設定</value>
|
<value>DNS設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDnsSingbox" xml:space="preserve">
|
<data name="TbCustomDnsSingbox" xml:space="preserve">
|
||||||
<value>sing-box Custom DNS</value>
|
<value>sing-box 自訂 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
|
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
|
||||||
<value>請填寫 DNS JSON 結構,點擊查看檔案</value>
|
<value>請填寫 DNS JSON 結構,點擊查看檔案</value>
|
||||||
@@ -1021,7 +1024,7 @@
|
|||||||
<value>sing-box Mux 多路復用協定</value>
|
<value>sing-box Mux 多路復用協定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||||
<value>行程名全稱 (Tun 模式)</value>
|
<value>行程 (Tun 模式)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||||
<value>IP 或 IP CIDR</value>
|
<value>IP 或 IP CIDR</value>
|
||||||
@@ -1030,7 +1033,7 @@
|
|||||||
<value>Domain</value>
|
<value>Domain</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddHysteria2Server" xml:space="preserve">
|
<data name="menuAddHysteria2Server" xml:space="preserve">
|
||||||
<value>添加 [Hysteria2] 設定檔</value>
|
<value>新增 [Hysteria2] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
|
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
|
||||||
<value>Hysteria 最大頻寬 (Up/Dw)</value>
|
<value>Hysteria 最大頻寬 (Up/Dw)</value>
|
||||||
@@ -1039,19 +1042,19 @@
|
|||||||
<value>使用系統 hosts</value>
|
<value>使用系統 hosts</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddTuicServer" xml:space="preserve">
|
<data name="menuAddTuicServer" xml:space="preserve">
|
||||||
<value>新增 [TUIC] 設定檔</value>
|
<value>新增 [TUIC] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbHeaderType8" xml:space="preserve">
|
<data name="TbHeaderType8" xml:space="preserve">
|
||||||
<value>擁塞控制算法</value>
|
<value>擁塞控制算法</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LvPrevProfile" xml:space="preserve">
|
<data name="LvPrevProfile" xml:space="preserve">
|
||||||
<value>前置代理設定檔別名</value>
|
<value>前置代理節點別名</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LvNextProfile" xml:space="preserve">
|
<data name="LvNextProfile" xml:space="preserve">
|
||||||
<value>落地代理設定檔別名</value>
|
<value>落地代理節點別名</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>請確保設定檔別名存在並且唯一</value>
|
<value>請確保節點別名存在並且唯一</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
<value>自動路由</value>
|
<value>自動路由</value>
|
||||||
@@ -1065,14 +1068,11 @@
|
|||||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
<value>MTU</value>
|
<value>MTU</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
|
||||||
<value>啟用額外偵聽連接埠</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||||
<value>啟用 IPv6</value>
|
<value>啟用 IPv6</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddWireguardServer" xml:space="preserve">
|
<data name="menuAddWireguardServer" xml:space="preserve">
|
||||||
<value>添加 [WireGuard] 設定檔</value>
|
<value>新增 [WireGuard] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPrivateKey" xml:space="preserve">
|
<data name="TbPrivateKey" xml:space="preserve">
|
||||||
<value>PrivateKey</value>
|
<value>PrivateKey</value>
|
||||||
@@ -1105,10 +1105,7 @@
|
|||||||
<value>*grpc Authority</value>
|
<value>*grpc Authority</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddHttpServer" xml:space="preserve">
|
<data name="menuAddHttpServer" xml:space="preserve">
|
||||||
<value>新增 [HTTP] 設定檔</value>
|
<value>新增 [HTTP] 節點</value>
|
||||||
</data>
|
|
||||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
|
||||||
<value>和分組前置代理衝突</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||||
<value>啟用分片(Fragment)</value>
|
<value>啟用分片(Fragment)</value>
|
||||||
@@ -1201,7 +1198,7 @@
|
|||||||
<value>重新整理</value>
|
<value>重新整理</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
<data name="menuProxiesSelectActivity" xml:space="preserve">
|
||||||
<value>設為活動節點 (Enter)</value>
|
<value>設為活動節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
|
||||||
<value>Outbound 預設解析策略</value>
|
<value>Outbound 預設解析策略</value>
|
||||||
@@ -1219,13 +1216,13 @@
|
|||||||
<value>匯出分享連結至剪貼簿 (多選) Base64 編碼</value>
|
<value>匯出分享連結至剪貼簿 (多選) Base64 編碼</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
|
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
|
||||||
<value>匯出所選設定檔完整設定至剪貼簿</value>
|
<value>匯出所選完整設定至剪貼簿</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuShowOrHideMainWindow" xml:space="preserve">
|
<data name="menuShowOrHideMainWindow" xml:space="preserve">
|
||||||
<value>顯示或隱藏主介面</value>
|
<value>顯示或隱藏主介面</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPreSocksPort4Sub" xml:space="preserve">
|
<data name="TbPreSocksPort4Sub" xml:space="preserve">
|
||||||
<value>自訂設定的 Socks 連接埠</value>
|
<value>自訂 Socks 連接埠</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuBackupAndRestore" xml:space="preserve">
|
<data name="menuBackupAndRestore" xml:space="preserve">
|
||||||
<value>備份和還原</value>
|
<value>備份和還原</value>
|
||||||
@@ -1309,7 +1306,7 @@
|
|||||||
<value>請不要使用不安全的 HTTP 協定訂閱位址</value>
|
<value>請不要使用不安全的 HTTP 協定訂閱位址</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
|
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
|
||||||
<value>安裝字體到系統中,選擇或填入字體名稱,重新啟動設定</value>
|
<value>安裝字體到系統中,選擇或填入字體名稱,重新啟動後生效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExitTips" xml:space="preserve">
|
<data name="menuExitTips" xml:space="preserve">
|
||||||
<value>是否確定退出?</value>
|
<value>是否確定退出?</value>
|
||||||
@@ -1336,7 +1333,7 @@
|
|||||||
<value>多執行緒測試時的並發數量</value>
|
<value>多執行緒測試時的並發數量</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsExceptionTip2" xml:space="preserve">
|
<data name="TbSettingsExceptionTip2" xml:space="preserve">
|
||||||
<value>例外:對於下列位址不使用代理設定檔,使用逗號 (,) 分隔。</value>
|
<value>例外:對於下列位址不使用代理,使用逗號 (,) 分隔。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsDestOverride" xml:space="preserve">
|
<data name="TbSettingsDestOverride" xml:space="preserve">
|
||||||
<value>流量探測類型</value>
|
<value>流量探測類型</value>
|
||||||
@@ -1372,31 +1369,31 @@
|
|||||||
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
|
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||||
<value>Generate Policy Group from Multiple Profiles</value>
|
<value>多選生成策略組</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||||
<value>多設定檔隨機 Xray</value>
|
<value>多選隨機 Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||||
<value>多設定檔負載平衡 Xray</value>
|
<value>多選負載平衡 Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||||
<value>多設定檔最低延遲 Xray</value>
|
<value>多選最低延遲 Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||||
<value>多設定檔最穩定 Xray</value>
|
<value>多選最穩定 Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||||
<value>多設定檔最低延遲 sing-box</value>
|
<value>多選最低延遲 sing-box</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuExportConfig" xml:space="preserve">
|
<data name="menuExportConfig" xml:space="preserve">
|
||||||
<value>匯出設定檔</value>
|
<value>匯出</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||||
<value>目前連接資訊測試地址</value>
|
<value>目前連接資訊測試地址</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRuleOutboundTagTip" xml:space="preserve">
|
<data name="TbRuleOutboundTagTip" xml:space="preserve">
|
||||||
<value>可以填寫設定檔別名,請確保存在並唯一</value>
|
<value>可以填寫節點別名,請確保存在並唯一</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
|
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
|
||||||
<value>密碼錯誤,請重試。</value>
|
<value>密碼錯誤,請重試。</value>
|
||||||
@@ -1405,192 +1402,267 @@
|
|||||||
<value>Mldsa65Verify</value>
|
<value>Mldsa65Verify</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddAnytlsServer" xml:space="preserve">
|
<data name="menuAddAnytlsServer" xml:space="preserve">
|
||||||
<value>新增 [Anytls] 設定檔</value>
|
<value>新增 [Anytls] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNS" xml:space="preserve">
|
<data name="TbRemoteDNS" xml:space="preserve">
|
||||||
<value>Remote DNS</value>
|
<value>遠程 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDomesticDNS" xml:space="preserve">
|
<data name="TbDomesticDNS" xml:space="preserve">
|
||||||
<value>Domestic DNS</value>
|
<value>直連 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||||
<value>Via proxy — please ensure remote availability</value>
|
<value>直連目標解析策略</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||||
<value>xray Freedom Resolution Strategy</value>
|
<value>代理目標解析策略</value>
|
||||||
</data>
|
|
||||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box Direct Resolution Strategy</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
|
||||||
<value>sing-box Remote Resolution Strategy</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Add Common DNS Hosts</value>
|
<value>新增常用 DNS Hosts</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
||||||
<value>Block SVCB and HTTPS Queries</value>
|
<value>阻止 SVCB 和 HTTPS 查詢</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS Hosts:(“網域名稱1 ip1 ip2” 一行一個)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>Basic DNS Settings</value>
|
<value>DNS 基礎設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ThAdvancedDNSSettings" xml:space="preserve">
|
<data name="ThAdvancedDNSSettings" xml:space="preserve">
|
||||||
<value>Advanced DNS Settings</value>
|
<value>DNS 進階設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
|
||||||
<value>Validate Regional Domain IPs</value>
|
<value>校驗相應地區域名 IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||||
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
|
<value>配置後,會對相應地區域名(如 geosite:cn - geoip:cn)的返回 IP 進行校驗,僅返回期望 IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||||
<value>Enable Custom DNS</value>
|
<value>啟用自訂 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
|
<value>自訂 DNS 已啟用,此頁面配置將無效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>Block ECH and HTTP/3 availability checks when enabled</value>
|
<value>開啟後將阻止 ECH 和 HTTP/3 可用性查詢</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>Please fill in the correct config template</value>
|
<value>請填寫正確的配置範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuFullConfigTemplate" xml:space="preserve">
|
<data name="menuFullConfigTemplate" xml:space="preserve">
|
||||||
<value>Full Config Template Setting</value>
|
<value>完整配置範本設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
||||||
<value>Enable Full Config Template</value>
|
<value>啟用完整配置範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
||||||
<value>v2ray Full Config Template</value>
|
<value>v2ray 完整配置範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
|
<value>僅添加出站配置,routing.balancers 和 routing.rules.outboundTag,點擊查看文檔</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
||||||
<value>Do Not Add Non-Proxy Protocol Outbound</value>
|
<value>不添加非代理協定出站</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
||||||
<value>Set Upstream Proxy Tag</value>
|
<value>設定上游代理 tag</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
||||||
<value>sing-box Full Config Template</value>
|
<value>sing-box 完整配置範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
|
<value>僅添加出站和端點配置,點擊查看文檔</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
|
<value>此功能供高級用戶和有特殊需求的用戶使用。 啟用此功能後,將忽略 Core 的基礎設定,DNS 設定 ,路由設定。你需要保證系統代理的埠和流量統計等功能的配置正確,一切都由你來設定。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>開始解析和處理訂閱內容</value>
|
<value>開始解析和處理訂閱內容</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSelectProfile" xml:space="preserve">
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
<value>Select Profile</value>
|
<value>選擇節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
<value>默認全域生效,內置 FakeIP 過濾,僅在 sing-box 中生效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>Please Add At Least One Configuration</value>
|
<value>請至少添加一個節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
||||||
<value>Policy Group</value>
|
<value>策略組</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbConfigTypeProxyChain" xml:space="preserve">
|
<data name="TbConfigTypeProxyChain" xml:space="preserve">
|
||||||
<value>Proxy Chain</value>
|
<value>鏈式代理</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbLeastPing" xml:space="preserve">
|
<data name="TbLeastPing" xml:space="preserve">
|
||||||
<value>Lowest Latency</value>
|
<value>最低延遲</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRandom" xml:space="preserve">
|
<data name="TbRandom" xml:space="preserve">
|
||||||
<value>Random</value>
|
<value>隨機</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRoundRobin" xml:space="preserve">
|
<data name="TbRoundRobin" xml:space="preserve">
|
||||||
<value>Round Robin</value>
|
<value>負載均衡</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbLeastLoad" xml:space="preserve">
|
<data name="TbLeastLoad" xml:space="preserve">
|
||||||
<value>Most Stable</value>
|
<value>最穩定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPolicyGroupType" xml:space="preserve">
|
<data name="TbPolicyGroupType" xml:space="preserve">
|
||||||
<value>Policy Group Type</value>
|
<value>策略組類型</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
||||||
<value>Add Policy Group Configuration</value>
|
<value>添加策略組</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddProxyChainServer" xml:space="preserve">
|
<data name="menuAddProxyChainServer" xml:space="preserve">
|
||||||
<value>Add Proxy Chain Configuration</value>
|
<value>添加鏈式代理</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuAddChildServer" xml:space="preserve">
|
<data name="menuAddChildServer" xml:space="preserve">
|
||||||
<value>Add Child Configuration</value>
|
<value>添加子配置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||||
<value>Remove Child Configuration</value>
|
<value>刪除子配置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuServerList" xml:space="preserve">
|
<data name="menuServerList" xml:space="preserve">
|
||||||
<value>Server List</value>
|
<value>子配置項目一,從訂閱分組中自動新增</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFallback" xml:space="preserve">
|
<data name="TbFallback" xml:space="preserve">
|
||||||
<value>Fallback</value>
|
<value>容錯移轉</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||||
<value>Multi-Configuration Fallback by sing-box</value>
|
<value>多選容錯移轉 sing-box</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||||
<value>Multi-Configuration Fallback by Xray</value>
|
<value>多選容錯移轉 Xray</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
||||||
<value>Core '{0}' does not support network type '{1}'.</value>
|
<value>核心 '{0}' 不支援網路類型 '{1}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
|
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
|
||||||
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
|
<value>核心 '{0}' 在使用傳輸方式 '{2}' 時不支援協定 '{1}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CoreNotSupportProtocol" xml:space="preserve">
|
<data name="CoreNotSupportProtocol" xml:space="preserve">
|
||||||
<value>Core '{0}' does not support protocol '{1}'.</value>
|
<value>核心 '{0}' 不支援協定 '{1}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ProxyChainedPrefix" xml:space="preserve">
|
<data name="ProxyChainedPrefix" xml:space="preserve">
|
||||||
<value>Proxy chained: </value>
|
<value>代理鏈: </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
|
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
|
||||||
<value>Routing rule outbound: </value>
|
<value>路由規則出站: </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PolicyGroupPrefix" xml:space="preserve">
|
<data name="PolicyGroupPrefix" xml:space="preserve">
|
||||||
<value>Policy group: </value>
|
<value>策略組: </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NodeTagNotExist" xml:space="preserve">
|
<data name="NodeTagNotExist" xml:space="preserve">
|
||||||
<value>Node alias '{0}' does not exist.</value>
|
<value>別名 '{0}' 不存在。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GroupEmpty" xml:space="preserve">
|
<data name="GroupEmpty" xml:space="preserve">
|
||||||
<value>Group '{0}' is empty. Please add at least one node.</value>
|
<value>組“{0}”為空.請至少添加一個配置。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidProperty" xml:space="preserve">
|
<data name="InvalidProperty" xml:space="preserve">
|
||||||
<value>The {0} property is invalid, please check.</value>
|
<value>{0}屬性無效,請檢查</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GroupSelfReference" xml:space="preserve">
|
<data name="GroupSelfReference" xml:space="preserve">
|
||||||
<value>{0} 分組不能引用自身或循環引用</value>
|
<value>{0} 分組不能引用自身或循環引用</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NotSupportProtocol" xml:space="preserve">
|
<data name="NotSupportProtocol" xml:space="preserve">
|
||||||
<value>Not support protocol '{0}'.</value>
|
<value>不支援協定 '{0}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
||||||
<value>如果系統沒有托盤功能,請不要開啟</value>
|
<value>如果系統沒有託盤功能,請不要開啟</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRuleType" xml:space="preserve">
|
<data name="TbRuleType" xml:space="preserve">
|
||||||
<value>规则类型</value>
|
<value>規則類型</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRuleTypeTips" xml:space="preserve">
|
<data name="TbRuleTypeTips" xml:space="preserve">
|
||||||
<value>可对 Routing 和 DNS 单独设定规则,ALL 则都生效</value>
|
<value>可對 Routing 和 DNS 單獨設定規則,ALL 則都生效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBootstrapDNS" xml:space="preserve">
|
<data name="TbBootstrapDNS" xml:space="preserve">
|
||||||
<value>Bootstrap DNS</value>
|
<value>Bootstrap DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
<data name="TbBootstrapDNSTips" xml:space="preserve">
|
||||||
<value>Resolve DNS server domains, requires IP</value>
|
<value>解析 DNS 伺服器網域名稱,需指定為 IP</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuFastRealPing" xml:space="preserve">
|
||||||
|
<value>一鍵測試真連線延遲</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
|
||||||
|
<value>自動從訂閱分組新增過濾後的配置</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinning" xml:space="preserve">
|
||||||
|
<value>憑證綁定</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertPinningTips" xml:space="preserve">
|
||||||
|
<value>固定憑證(二選一填寫即可)
|
||||||
|
若已指定,憑證將會被綁定,並且「跳過憑證驗證」將被停用。
|
||||||
|
|
||||||
|
若使用自簽憑證,或系統中存在不受信任或惡意的 CA,「取得憑證」動作可能會失敗。</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
|
<value>獲取憑證</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFetchCertChain" xml:space="preserve">
|
||||||
|
<value>獲取憑證鏈</value>
|
||||||
|
</data>
|
||||||
|
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
|
||||||
|
<value>請設定有效的網域名稱</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertNotSet" xml:space="preserve">
|
||||||
|
<value>尚未設定憑證</value>
|
||||||
|
</data>
|
||||||
|
<data name="CertSet" xml:space="preserve">
|
||||||
|
<value>已設定憑證</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
|
||||||
|
<value>自訂 PAC 檔案路徑</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
|
||||||
|
<value>自訂系統代理程式腳本檔案路徑</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
|
||||||
|
<value>macOS 在 Dock 欄顯示 (需重啟)</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
|
<value>子配置項二,從自建中選擇新增</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFullCertTips" xml:space="preserve">
|
||||||
|
<value>Full certificate (chain), PEM format</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCertSha256Tips" xml:space="preserve">
|
||||||
|
<value>Certificate fingerprint (SHA-256)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbServeStale" xml:space="preserve">
|
||||||
|
<value>Serve Stale</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbParallelQuery" xml:space="preserve">
|
||||||
|
<value>Parallel Query</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDomesticDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||||
|
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
||||||
|
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbHopInt7" xml:space="preserve">
|
||||||
|
<value>Port hopping interval</value>
|
||||||
|
</data>
|
||||||
|
<data name="menuServerListPreview" xml:space="preserve">
|
||||||
|
<value>Configuration item preview</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFinalmask" xml:space="preserve">
|
||||||
|
<value>Finalmask</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"access": "Vaccess.log",
|
"access": "Vaccess.log",
|
||||||
"error": "Verror.log",
|
"error": "Verror.log",
|
||||||
@@ -6,34 +6,6 @@
|
|||||||
},
|
},
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
|
||||||
"tag": "proxy",
|
|
||||||
"protocol": "vmess",
|
|
||||||
"settings": {
|
|
||||||
"vnext": [{
|
|
||||||
"address": "",
|
|
||||||
"port": 0,
|
|
||||||
"users": [{
|
|
||||||
"id": "",
|
|
||||||
"security": "auto"
|
|
||||||
}]
|
|
||||||
}],
|
|
||||||
"servers": [{
|
|
||||||
"address": "",
|
|
||||||
"method": "",
|
|
||||||
"ota": false,
|
|
||||||
"password": "",
|
|
||||||
"port": 0,
|
|
||||||
"level": 1
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"streamSettings": {
|
|
||||||
"network": "tcp"
|
|
||||||
},
|
|
||||||
"mux": {
|
|
||||||
"enabled": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
|||||||
@@ -5,19 +5,12 @@
|
|||||||
},
|
},
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
|
||||||
"type": "vless",
|
|
||||||
"tag": "proxy",
|
|
||||||
"server": "",
|
|
||||||
"server_port": 443
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": []
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,20 +13,19 @@
|
|||||||
"api.ip.sb"
|
"api.ip.sb"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"remarks": "Google cn",
|
|
||||||
"outboundTag": "proxy",
|
|
||||||
"domain": [
|
|
||||||
"domain:googleapis.cn",
|
|
||||||
"domain:gstatic.com"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"remarks": "阻断udp443",
|
"remarks": "阻断udp443",
|
||||||
"outboundTag": "block",
|
"outboundTag": "block",
|
||||||
"port": "443",
|
"port": "443",
|
||||||
"network": "udp"
|
"network": "udp"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"remarks": "代理Google",
|
||||||
|
"outboundTag": "proxy",
|
||||||
|
"domain": [
|
||||||
|
"geosite:google"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"remarks": "绕过局域网IP",
|
"remarks": "绕过局域网IP",
|
||||||
"outboundTag": "direct",
|
"outboundTag": "direct",
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"remarks": "Google cn",
|
|
||||||
"outboundTag": "proxy",
|
|
||||||
"domain": [
|
|
||||||
"domain:googleapis.cn",
|
|
||||||
"domain:gstatic.com"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"remarks": "阻断udp443",
|
"remarks": "阻断udp443",
|
||||||
"outboundTag": "block",
|
"outboundTag": "block",
|
||||||
"port": "443",
|
"port": "443",
|
||||||
"network": "udp"
|
"network": "udp"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"remarks": "代理Google",
|
||||||
|
"outboundTag": "proxy",
|
||||||
|
"domain": [
|
||||||
|
"geosite:google"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"remarks": "绕过局域网IP",
|
"remarks": "绕过局域网IP",
|
||||||
"outboundTag": "direct",
|
"outboundTag": "direct",
|
||||||
|
|||||||
@@ -14,9 +14,8 @@
|
|||||||
],
|
],
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"domain_suffix": [
|
"rule_set": [
|
||||||
"googleapis.cn",
|
"geosite-google"
|
||||||
"gstatic.com"
|
|
||||||
],
|
],
|
||||||
"server": "remote",
|
"server": "remote",
|
||||||
"strategy": "prefer_ipv4"
|
"strategy": "prefer_ipv4"
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
"address": "1.1.1.1",
|
"address": "1.1.1.1",
|
||||||
"skipFallback": true,
|
"skipFallback": true,
|
||||||
"domains": [
|
"domains": [
|
||||||
"domain:googleapis.cn",
|
"geosite:google"
|
||||||
"domain:gstatic.com"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,6 +47,18 @@ echo "============================================"
|
|||||||
echo "Starting termination of process $PID and all its children"
|
echo "Starting termination of process $PID and all its children"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
|
|
||||||
|
# Try graceful termination first
|
||||||
|
echo "Attempting graceful termination (SIGTERM) of PID: $PID"
|
||||||
|
kill -15 "$PID" 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
# If still running, fall back to kill_children
|
||||||
|
if ps -p $PID > /dev/null; then
|
||||||
|
echo "Process $PID did not exit after SIGTERM; proceeding with forced termination of its children and itself"
|
||||||
|
else
|
||||||
|
echo "Process $PID exited cleanly after SIGTERM"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Find and kill all child processes
|
# Find and kill all child processes
|
||||||
kill_children "$PID"
|
kill_children "$PID"
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,20 @@ echo "============================================"
|
|||||||
echo "Starting termination of process $PID and all its descendants"
|
echo "Starting termination of process $PID and all its descendants"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
|
|
||||||
|
# Try graceful termination first
|
||||||
|
echo "Attempting graceful termination (SIGTERM) of PID: $PID"
|
||||||
|
kill -15 "$PID" 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# If still running, fall back to kill_descendants
|
||||||
|
# Use the macOS-native 'kill -0' check
|
||||||
|
if kill -0 $PID 2>/dev/null; then
|
||||||
|
echo "Process $PID did not exit after SIGTERM; proceeding with forced termination of its descendants and itself"
|
||||||
|
else
|
||||||
|
echo "Process $PID exited cleanly after SIGTERM"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Find and kill all descendant processes
|
# Find and kill all descendant processes
|
||||||
kill_descendants "$PID"
|
kill_descendants "$PID"
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,8 @@
|
|||||||
],
|
],
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"domain_suffix": [
|
"rule_set": [
|
||||||
"googleapis.cn",
|
"geosite-google"
|
||||||
"gstatic.com"
|
|
||||||
],
|
],
|
||||||
"server": "remote",
|
"server": "remote",
|
||||||
"strategy": "prefer_ipv4"
|
"strategy": "prefer_ipv4"
|
||||||
|
|||||||
@@ -57,6 +57,9 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Update="Resx\ResUI.fr.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Update="Resx\ResUI.hu.resx">
|
<EmbeddedResource Update="Resx\ResUI.hu.resx">
|
||||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
|||||||
@@ -1,43 +1,34 @@
|
|||||||
namespace ServiceLib.Services.CoreConfig;
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
public partial class CoreConfigSingboxService(Config config)
|
public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||||
{
|
{
|
||||||
private readonly Config _config = config;
|
|
||||||
private static readonly string _tag = "CoreConfigSingboxService";
|
private static readonly string _tag = "CoreConfigSingboxService";
|
||||||
|
private readonly Config _config = context.AppConfig;
|
||||||
|
private readonly ProfileItem _node = context.Node;
|
||||||
|
|
||||||
|
private SingboxConfig _coreConfig = new();
|
||||||
|
|
||||||
#region public gen function
|
#region public gen function
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
|
public RetResult GenerateClientConfigContent()
|
||||||
{
|
{
|
||||||
var ret = new RetResult();
|
var ret = new RetResult();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (node == null
|
if (_node == null
|
||||||
|| !node.IsValid())
|
|| !_node.IsValid())
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.Msg = ResUI.InitialConfiguration;
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
if (node.ConfigType.IsGroupType())
|
|
||||||
{
|
|
||||||
switch (node.ConfigType)
|
|
||||||
{
|
|
||||||
case EConfigType.PolicyGroup:
|
|
||||||
return await GenerateClientMultipleLoadConfig(node);
|
|
||||||
|
|
||||||
case EConfigType.ProxyChain:
|
|
||||||
return await GenerateClientChainConfig(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
@@ -45,44 +36,76 @@ public partial class CoreConfigSingboxService(Config config)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||||
if (singboxConfig == null)
|
if (_coreConfig == null)
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenLog(singboxConfig);
|
GenLog();
|
||||||
|
|
||||||
await GenInbounds(singboxConfig);
|
GenInbounds();
|
||||||
|
|
||||||
if (node.ConfigType == EConfigType.WireGuard)
|
GenOutbounds();
|
||||||
{
|
|
||||||
singboxConfig.outbounds.RemoveAt(0);
|
|
||||||
var endpoints = new Endpoints4Sbox();
|
|
||||||
await GenEndpoint(node, endpoints);
|
|
||||||
endpoints.tag = Global.ProxyTag;
|
|
||||||
singboxConfig.endpoints = new() { endpoints };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await GenOutbound(node, singboxConfig.outbounds.First());
|
|
||||||
}
|
|
||||||
|
|
||||||
await GenMoreOutbounds(node, singboxConfig);
|
GenRouting();
|
||||||
|
|
||||||
await GenRouting(singboxConfig);
|
GenDns();
|
||||||
|
|
||||||
await GenDns(node, singboxConfig);
|
GenExperimental();
|
||||||
|
|
||||||
await GenExperimental(singboxConfig);
|
ConvertGeo2Ruleset();
|
||||||
|
|
||||||
await ConvertGeo2Ruleset(singboxConfig);
|
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
|
|
||||||
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
|
ret.Data = ApplyFullConfigTemplate();
|
||||||
|
if (context.TunProtectSsPort is > 0 and <= 65535)
|
||||||
|
{
|
||||||
|
var ssInbound = new
|
||||||
|
{
|
||||||
|
type = "shadowsocks",
|
||||||
|
tag = "tun-protect-ss",
|
||||||
|
listen = Global.Loopback,
|
||||||
|
listen_port = context.TunProtectSsPort,
|
||||||
|
method = "none",
|
||||||
|
password = "none",
|
||||||
|
};
|
||||||
|
var directRule = new Rule4Sbox()
|
||||||
|
{
|
||||||
|
inbound = new List<string> { ssInbound.tag },
|
||||||
|
outbound = Global.DirectTag,
|
||||||
|
};
|
||||||
|
var singboxConfigNode = JsonUtils.ParseJson(ret.Data.ToString())!.AsObject();
|
||||||
|
var inboundsNode = singboxConfigNode["inbounds"]!.AsArray();
|
||||||
|
inboundsNode.Add(JsonUtils.SerializeToNode(ssInbound, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||||
|
}));
|
||||||
|
var routeNode = singboxConfigNode["route"]?.AsObject();
|
||||||
|
var rulesNode = routeNode?["rules"]?.AsArray();
|
||||||
|
var protectRuleNode = JsonUtils.SerializeToNode(directRule,
|
||||||
|
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
|
||||||
|
if (rulesNode != null)
|
||||||
|
{
|
||||||
|
rulesNode.Insert(0, protectRuleNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var newRulesNode = new JsonArray() { protectRuleNode };
|
||||||
|
if (routeNode is null)
|
||||||
|
{
|
||||||
|
var newRouteNode = new JsonObject() { ["rules"] = newRulesNode };
|
||||||
|
singboxConfigNode["route"] = newRouteNode;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
routeNode["rules"] = newRulesNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret.Data = JsonUtils.Serialize(singboxConfigNode);
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -93,17 +116,11 @@ public partial class CoreConfigSingboxService(Config config)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
|
public RetResult GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
|
||||||
{
|
{
|
||||||
var ret = new RetResult();
|
var ret = new RetResult();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_config == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Msg = ResUI.InitialConfiguration;
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||||
@@ -114,29 +131,19 @@ public partial class CoreConfigSingboxService(Config config)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||||
if (singboxConfig == null)
|
if (_coreConfig == null)
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
List<IPEndPoint> lstIpEndPoints = new();
|
|
||||||
List<TcpConnectionInformation> lstTcpConns = new();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
|
|
||||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
|
|
||||||
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
await GenLog(singboxConfig);
|
var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo();
|
||||||
//GenDns(new(), singboxConfig);
|
|
||||||
singboxConfig.inbounds.Clear();
|
GenLog();
|
||||||
singboxConfig.outbounds.RemoveAt(0);
|
GenMinimizedDns();
|
||||||
|
_coreConfig.inbounds.Clear();
|
||||||
|
_coreConfig.outbounds.RemoveAt(0);
|
||||||
|
|
||||||
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||||
|
|
||||||
@@ -150,8 +157,9 @@ public partial class CoreConfigSingboxService(Config config)
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId);
|
||||||
if (item is null || item.IsComplex() || !item.IsValid())
|
var item = context.AllProxiesMap.GetValueOrDefault(actIndexId);
|
||||||
|
if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -190,26 +198,11 @@ public partial class CoreConfigSingboxService(Config config)
|
|||||||
type = EInboundProtocol.mixed.ToString(),
|
type = EInboundProtocol.mixed.ToString(),
|
||||||
};
|
};
|
||||||
inbound.tag = inbound.type + inbound.listen_port.ToString();
|
inbound.tag = inbound.type + inbound.listen_port.ToString();
|
||||||
singboxConfig.inbounds.Add(inbound);
|
_coreConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
//outbound
|
|
||||||
var server = await GenServer(item);
|
|
||||||
if (server is null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
var tag = Global.ProxyTag + inbound.listen_port.ToString();
|
var tag = Global.ProxyTag + inbound.listen_port.ToString();
|
||||||
server.tag = tag;
|
var serverList = new CoreConfigSingboxService(context with { Node = item }).BuildAllProxyOutbounds(tag);
|
||||||
if (server is Endpoints4Sbox endpoint)
|
FillRangeProxy(serverList, _coreConfig, false);
|
||||||
{
|
|
||||||
singboxConfig.endpoints ??= new();
|
|
||||||
singboxConfig.endpoints.Add(endpoint);
|
|
||||||
}
|
|
||||||
else if (server is Outbound4Sbox outbound)
|
|
||||||
{
|
|
||||||
singboxConfig.outbounds.Add(outbound);
|
|
||||||
}
|
|
||||||
|
|
||||||
//rule
|
//rule
|
||||||
Rule4Sbox rule = new()
|
Rule4Sbox rule = new()
|
||||||
@@ -217,25 +210,11 @@ public partial class CoreConfigSingboxService(Config config)
|
|||||||
inbound = new List<string> { inbound.tag },
|
inbound = new List<string> { inbound.tag },
|
||||||
outbound = tag
|
outbound = tag
|
||||||
};
|
};
|
||||||
singboxConfig.route.rules.Add(rule);
|
_coreConfig.route.rules.Add(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
|
||||||
if (rawDNSItem != null && rawDNSItem.Enabled == true)
|
|
||||||
{
|
|
||||||
await GenDnsDomainsCompatible(singboxConfig, rawDNSItem);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
|
|
||||||
}
|
|
||||||
singboxConfig.route.default_domain_resolver = new()
|
|
||||||
{
|
|
||||||
server = Global.SingboxLocalDNSTag,
|
|
||||||
};
|
|
||||||
|
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -246,20 +225,20 @@ public partial class CoreConfigSingboxService(Config config)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
|
public RetResult GenerateClientSpeedtestConfig(int port)
|
||||||
{
|
{
|
||||||
var ret = new RetResult();
|
var ret = new RetResult();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (node == null
|
if (_node == null
|
||||||
|| !node.IsValid())
|
|| !_node.IsValid())
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,44 +251,20 @@ public partial class CoreConfigSingboxService(Config config)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||||
if (singboxConfig == null)
|
if (_coreConfig == null)
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenLog(singboxConfig);
|
GenLog();
|
||||||
if (node.ConfigType == EConfigType.WireGuard)
|
GenOutbounds();
|
||||||
{
|
GenMinimizedDns();
|
||||||
singboxConfig.outbounds.RemoveAt(0);
|
|
||||||
var endpoints = new Endpoints4Sbox();
|
|
||||||
await GenEndpoint(node, endpoints);
|
|
||||||
endpoints.tag = Global.ProxyTag;
|
|
||||||
singboxConfig.endpoints = new() { endpoints };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await GenOutbound(node, singboxConfig.outbounds.First());
|
|
||||||
}
|
|
||||||
await GenMoreOutbounds(node, singboxConfig);
|
|
||||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
|
||||||
if (item != null && item.Enabled == true)
|
|
||||||
{
|
|
||||||
await GenDnsDomainsCompatible(singboxConfig, item);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
|
|
||||||
}
|
|
||||||
singboxConfig.route.default_domain_resolver = new()
|
|
||||||
{
|
|
||||||
server = Global.SingboxLocalDNSTag,
|
|
||||||
};
|
|
||||||
|
|
||||||
singboxConfig.route.rules.Clear();
|
_coreConfig.route.rules.Clear();
|
||||||
singboxConfig.inbounds.Clear();
|
_coreConfig.inbounds.Clear();
|
||||||
singboxConfig.inbounds.Add(new()
|
_coreConfig.inbounds.Add(new()
|
||||||
{
|
{
|
||||||
tag = $"{EInboundProtocol.mixed}{port}",
|
tag = $"{EInboundProtocol.mixed}{port}",
|
||||||
listen = Global.Loopback,
|
listen = Global.Loopback,
|
||||||
@@ -319,202 +274,7 @@ public partial class CoreConfigSingboxService(Config config)
|
|||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode)
|
|
||||||
{
|
|
||||||
var ret = new RetResult();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_config == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Msg = ResUI.InitialConfiguration;
|
|
||||||
|
|
||||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
|
||||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
|
||||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
|
||||||
if (singboxConfig == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
singboxConfig.outbounds.RemoveAt(0);
|
|
||||||
|
|
||||||
await GenLog(singboxConfig);
|
|
||||||
await GenInbounds(singboxConfig);
|
|
||||||
|
|
||||||
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
|
|
||||||
if (groupRet != 0)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
await GenRouting(singboxConfig);
|
|
||||||
await GenExperimental(singboxConfig);
|
|
||||||
await GenDns(null, singboxConfig);
|
|
||||||
await ConvertGeo2Ruleset(singboxConfig);
|
|
||||||
|
|
||||||
ret.Success = true;
|
|
||||||
|
|
||||||
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
|
|
||||||
{
|
|
||||||
var ret = new RetResult();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_config == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Msg = ResUI.InitialConfiguration;
|
|
||||||
|
|
||||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
|
||||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
|
||||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
|
||||||
if (singboxConfig == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
singboxConfig.outbounds.RemoveAt(0);
|
|
||||||
|
|
||||||
await GenLog(singboxConfig);
|
|
||||||
await GenInbounds(singboxConfig);
|
|
||||||
|
|
||||||
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
|
|
||||||
if (groupRet != 0)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
await GenRouting(singboxConfig);
|
|
||||||
await GenExperimental(singboxConfig);
|
|
||||||
await GenDns(null, singboxConfig);
|
|
||||||
await ConvertGeo2Ruleset(singboxConfig);
|
|
||||||
|
|
||||||
ret.Success = true;
|
|
||||||
|
|
||||||
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
|
|
||||||
{
|
|
||||||
var ret = new RetResult();
|
|
||||||
if (node == null || fileName is null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Msg = ResUI.InitialConfiguration;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (node == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.Exists(fileName))
|
|
||||||
{
|
|
||||||
File.Delete(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var addressFileName = node.Address;
|
|
||||||
if (addressFileName.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
if (!File.Exists(addressFileName))
|
|
||||||
{
|
|
||||||
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
|
|
||||||
}
|
|
||||||
if (!File.Exists(addressFileName))
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedReadConfiguration + "1";
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.Address == Global.CoreMultipleLoadConfigFileName)
|
|
||||||
{
|
|
||||||
var txtFile = File.ReadAllText(addressFileName);
|
|
||||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(txtFile);
|
|
||||||
if (singboxConfig == null)
|
|
||||||
{
|
|
||||||
File.Copy(addressFileName, fileName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await GenInbounds(singboxConfig);
|
|
||||||
await GenExperimental(singboxConfig);
|
|
||||||
|
|
||||||
var content = JsonUtils.Serialize(singboxConfig, true);
|
|
||||||
await File.WriteAllTextAsync(fileName, content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
File.Copy(addressFileName, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
//check again
|
|
||||||
if (!File.Exists(fileName))
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedReadConfiguration + "2";
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
|
||||||
ret.Success = true;
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -2,29 +2,29 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigSingboxService
|
public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig)
|
private string ApplyFullConfigTemplate()
|
||||||
{
|
{
|
||||||
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
|
var fullConfigTemplate = context.FullConfigTemplate;
|
||||||
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled)
|
if (fullConfigTemplate is not { Enabled: true })
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(singboxConfig);
|
return JsonUtils.Serialize(_coreConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
||||||
if (fullConfigTemplateItem.IsNullOrEmpty())
|
if (fullConfigTemplateItem.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(singboxConfig);
|
return JsonUtils.Serialize(_coreConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||||
if (fullConfigTemplateNode == null)
|
if (fullConfigTemplateNode == null)
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(singboxConfig);
|
return JsonUtils.Serialize(_coreConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process outbounds
|
// Process outbounds
|
||||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
|
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : [];
|
||||||
foreach (var outbound in singboxConfig.outbounds)
|
foreach (var outbound in _coreConfig.outbounds)
|
||||||
{
|
{
|
||||||
if (outbound.type.ToLower() is "direct" or "block")
|
if (outbound.type.ToLower() is "direct" or "block")
|
||||||
{
|
{
|
||||||
@@ -42,10 +42,10 @@ public partial class CoreConfigSingboxService
|
|||||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||||
|
|
||||||
// Process endpoints
|
// Process endpoints
|
||||||
if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0)
|
if (_coreConfig.endpoints != null && _coreConfig.endpoints.Count > 0)
|
||||||
{
|
{
|
||||||
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray();
|
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : [];
|
||||||
foreach (var endpoint in singboxConfig.endpoints)
|
foreach (var endpoint in _coreConfig.endpoints)
|
||||||
{
|
{
|
||||||
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
@@ -56,6 +56,6 @@ public partial class CoreConfigSingboxService
|
|||||||
fullConfigTemplateNode["endpoints"] = customEndpointsNode;
|
fullConfigTemplateNode["endpoints"] = customEndpointsNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
|
return JsonUtils.Serialize(fullConfigTemplateNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,25 +2,25 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigSingboxService
|
public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
|
private void GenDns()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
var item = context.RawDnsItem;
|
||||||
if (item != null && item.Enabled == true)
|
if (item is { Enabled: true })
|
||||||
{
|
{
|
||||||
return await GenDnsCompatible(node, singboxConfig);
|
GenDnsCustom();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var simpleDNSItem = _config.SimpleDNSItem;
|
GenDnsServers();
|
||||||
await GenDnsServers(singboxConfig, simpleDNSItem);
|
GenDnsRules();
|
||||||
await GenDnsRules(singboxConfig, simpleDNSItem);
|
|
||||||
|
|
||||||
singboxConfig.dns ??= new Dns4Sbox();
|
_coreConfig.dns ??= new Dns4Sbox();
|
||||||
singboxConfig.dns.independent_cache = true;
|
_coreConfig.dns.independent_cache = true;
|
||||||
|
|
||||||
// final dns
|
// final dns
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = context.RoutingItem;
|
||||||
var useDirectDns = false;
|
var useDirectDns = false;
|
||||||
if (routing != null)
|
if (routing != null)
|
||||||
{
|
{
|
||||||
@@ -32,35 +32,34 @@ public partial class CoreConfigSingboxService
|
|||||||
lastRule.Network == "tcp,udp" ||
|
lastRule.Network == "tcp,udp" ||
|
||||||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
||||||
}
|
}
|
||||||
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
|
_coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
|
||||||
if ((!useDirectDns) && simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
|
var simpleDnsItem = context.SimpleDnsItem;
|
||||||
|
if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
|
||||||
{
|
{
|
||||||
singboxConfig.dns.rules.Add(new()
|
_coreConfig.dns.rules.Add(new()
|
||||||
{
|
{
|
||||||
server = Global.SingboxFakeDNSTag,
|
server = Global.SingboxFakeDNSTag,
|
||||||
query_type = new List<int> { 1, 28 }, // A and AAAA
|
query_type = new List<int> { 1, 28 }, // A and AAAA
|
||||||
rewrite_ttl = 1,
|
rewrite_ttl = 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenOutboundDnsRule(node, singboxConfig);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
|
private void GenDnsServers()
|
||||||
{
|
{
|
||||||
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
|
var simpleDnsItem = context.SimpleDnsItem;
|
||||||
|
var finalDns = GenBootstrapDns();
|
||||||
|
|
||||||
var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS);
|
var directDns = ParseDnsAddress(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First());
|
||||||
directDns.tag = Global.SingboxDirectDNSTag;
|
directDns.tag = Global.SingboxDirectDNSTag;
|
||||||
directDns.domain_resolver = Global.SingboxLocalDNSTag;
|
directDns.domain_resolver = Global.SingboxLocalDNSTag;
|
||||||
|
|
||||||
var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS);
|
var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First());
|
||||||
remoteDns.tag = Global.SingboxRemoteDNSTag;
|
remoteDns.tag = Global.SingboxRemoteDNSTag;
|
||||||
remoteDns.detour = Global.ProxyTag;
|
remoteDns.detour = Global.ProxyTag;
|
||||||
remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
|
remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
|
||||||
@@ -71,12 +70,12 @@ public partial class CoreConfigSingboxService
|
|||||||
type = "hosts",
|
type = "hosts",
|
||||||
predefined = new(),
|
predefined = new(),
|
||||||
};
|
};
|
||||||
if (simpleDNSItem.AddCommonHosts == true)
|
if (simpleDnsItem.AddCommonHosts == true)
|
||||||
{
|
{
|
||||||
hostsDns.predefined = Global.PredefinedHosts;
|
hostsDns.predefined = Global.PredefinedHosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (simpleDNSItem.UseSystemHosts == true)
|
if (simpleDnsItem.UseSystemHosts == true)
|
||||||
{
|
{
|
||||||
var systemHosts = Utils.GetSystemHosts();
|
var systemHosts = Utils.GetSystemHosts();
|
||||||
if (systemHosts != null && systemHosts.Count > 0)
|
if (systemHosts != null && systemHosts.Count > 0)
|
||||||
@@ -88,14 +87,9 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
|
||||||
{
|
{
|
||||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
hostsDns.predefined[kvp.Key] = kvp.Value.Where(Utils.IsIpAddress).ToList();
|
||||||
|
|
||||||
foreach (var kvp in userHostsMap)
|
|
||||||
{
|
|
||||||
hostsDns.predefined[kvp.Key] = kvp.Value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var host in hostsDns.predefined)
|
foreach (var host in hostsDns.predefined)
|
||||||
@@ -114,14 +108,14 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
singboxConfig.dns ??= new Dns4Sbox();
|
_coreConfig.dns ??= new Dns4Sbox();
|
||||||
singboxConfig.dns.servers ??= new List<Server4Sbox>();
|
_coreConfig.dns.servers ??= [];
|
||||||
singboxConfig.dns.servers.Add(remoteDns);
|
_coreConfig.dns.servers.Add(remoteDns);
|
||||||
singboxConfig.dns.servers.Add(directDns);
|
_coreConfig.dns.servers.Add(directDns);
|
||||||
singboxConfig.dns.servers.Add(hostsDns);
|
_coreConfig.dns.servers.Add(hostsDns);
|
||||||
|
|
||||||
// fake ip
|
// fake ip
|
||||||
if (simpleDNSItem.FakeIP == true)
|
if (simpleDnsItem.FakeIP == true)
|
||||||
{
|
{
|
||||||
var fakeip = new Server4Sbox
|
var fakeip = new Server4Sbox
|
||||||
{
|
{
|
||||||
@@ -130,55 +124,103 @@ public partial class CoreConfigSingboxService
|
|||||||
inet4_range = "198.18.0.0/15",
|
inet4_range = "198.18.0.0/15",
|
||||||
inet6_range = "fc00::/18",
|
inet6_range = "fc00::/18",
|
||||||
};
|
};
|
||||||
singboxConfig.dns.servers.Add(fakeip);
|
_coreConfig.dns.servers.Add(fakeip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(0);
|
private Server4Sbox GenBootstrapDns()
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
|
|
||||||
{
|
{
|
||||||
var finalDns = ParseDnsAddress(simpleDNSItem.BootstrapDNS);
|
var finalDns = ParseDnsAddress(context.SimpleDnsItem?.BootstrapDNS ?? Global.DomainPureIPDNSAddress.First());
|
||||||
finalDns.tag = Global.SingboxLocalDNSTag;
|
finalDns.tag = Global.SingboxLocalDNSTag;
|
||||||
singboxConfig.dns ??= new Dns4Sbox();
|
_coreConfig.dns ??= new Dns4Sbox();
|
||||||
singboxConfig.dns.servers ??= new List<Server4Sbox>();
|
_coreConfig.dns.servers ??= [];
|
||||||
singboxConfig.dns.servers.Add(finalDns);
|
_coreConfig.dns.servers.Add(finalDns);
|
||||||
return await Task.FromResult(finalDns);
|
return finalDns;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
|
private void GenDnsRules()
|
||||||
{
|
{
|
||||||
singboxConfig.dns ??= new Dns4Sbox();
|
var simpleDnsItem = context.SimpleDnsItem;
|
||||||
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
_coreConfig.dns ??= new Dns4Sbox();
|
||||||
|
_coreConfig.dns.rules ??= [];
|
||||||
|
|
||||||
singboxConfig.dns.rules.AddRange(new[]
|
_coreConfig.dns.rules.AddRange(new[]
|
||||||
{
|
{
|
||||||
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
|
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
|
||||||
new Rule4Sbox
|
new Rule4Sbox
|
||||||
|
{
|
||||||
|
server = Global.SingboxDirectDNSTag,
|
||||||
|
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||||
|
domain = context.ProtectDomainList.ToList(),
|
||||||
|
},
|
||||||
|
new Rule4Sbox
|
||||||
{
|
{
|
||||||
server = Global.SingboxRemoteDNSTag,
|
server = Global.SingboxRemoteDNSTag,
|
||||||
strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy,
|
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy),
|
||||||
clash_mode = ERuleMode.Global.ToString()
|
clash_mode = ERuleMode.Global.ToString()
|
||||||
},
|
},
|
||||||
new Rule4Sbox
|
new Rule4Sbox
|
||||||
{
|
{
|
||||||
server = Global.SingboxDirectDNSTag,
|
server = Global.SingboxDirectDNSTag,
|
||||||
strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct,
|
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||||
clash_mode = ERuleMode.Direct.ToString()
|
clash_mode = ERuleMode.Direct.ToString()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (simpleDNSItem.BlockBindingQuery == true)
|
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
|
||||||
{
|
{
|
||||||
singboxConfig.dns.rules.Add(new()
|
var predefined = kvp.Value.First();
|
||||||
|
if (predefined.IsNullOrEmpty() || Utils.IsIpAddress(predefined))
|
||||||
{
|
{
|
||||||
query_type = new List<int> { 64, 65 },
|
continue;
|
||||||
|
}
|
||||||
|
if (predefined.StartsWith('#') && int.TryParse(predefined.AsSpan(1), out var rcode))
|
||||||
|
{
|
||||||
|
// xray syntactic sugar for predefined
|
||||||
|
// etc. #0 -> NOERROR
|
||||||
|
_coreConfig.dns.rules.Add(new()
|
||||||
|
{
|
||||||
|
query_type = [1, 28],
|
||||||
|
domain = [kvp.Key],
|
||||||
action = "predefined",
|
action = "predefined",
|
||||||
rcode = "NOTIMP"
|
rcode = rcode switch
|
||||||
|
{
|
||||||
|
0 => "NOERROR",
|
||||||
|
1 => "FORMERR",
|
||||||
|
2 => "SERVFAIL",
|
||||||
|
3 => "NXDOMAIN",
|
||||||
|
4 => "NOTIMP",
|
||||||
|
5 => "REFUSED",
|
||||||
|
_ => "NOERROR",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// CNAME record
|
||||||
|
Rule4Sbox rule = new()
|
||||||
|
{
|
||||||
|
query_type = [1, 28],
|
||||||
|
action = "predefined",
|
||||||
|
rcode = "NOERROR",
|
||||||
|
answer = [$"*. IN CNAME {predefined}."],
|
||||||
|
};
|
||||||
|
if (ParseV2Domain(kvp.Key, rule))
|
||||||
|
{
|
||||||
|
_coreConfig.dns.rules.Add(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simpleDnsItem.BlockBindingQuery == true)
|
||||||
|
{
|
||||||
|
_coreConfig.dns.rules.Add(new()
|
||||||
|
{
|
||||||
|
query_type = [64, 65],
|
||||||
|
action = "predefined",
|
||||||
|
rcode = "NOERROR"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == true)
|
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true)
|
||||||
{
|
{
|
||||||
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
|
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
|
||||||
fakeipFilterRule.invert = true;
|
fakeipFilterRule.invert = true;
|
||||||
@@ -188,30 +230,33 @@ public partial class CoreConfigSingboxService
|
|||||||
type = "logical",
|
type = "logical",
|
||||||
mode = "and",
|
mode = "and",
|
||||||
rewrite_ttl = 1,
|
rewrite_ttl = 1,
|
||||||
rules = new List<Rule4Sbox>
|
rules =
|
||||||
|
[
|
||||||
|
new()
|
||||||
{
|
{
|
||||||
new() {
|
query_type = [1, 28], // A and AAAA
|
||||||
query_type = new List<int> { 1, 28 }, // A and AAAA
|
|
||||||
},
|
},
|
||||||
fakeipFilterRule,
|
fakeipFilterRule
|
||||||
}
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
singboxConfig.dns.rules.Add(rule4Fake);
|
_coreConfig.dns.rules.Add(rule4Fake);
|
||||||
}
|
}
|
||||||
|
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = context.RoutingItem;
|
||||||
if (routing == null)
|
if (routing == null)
|
||||||
return 0;
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
|
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
|
||||||
var expectedIPCidr = new List<string>();
|
var expectedIPCidr = new List<string>();
|
||||||
var expectedIPsRegions = new List<string>();
|
var expectedIPsRegions = new List<string>();
|
||||||
var regionNames = new HashSet<string>();
|
var regionNames = new HashSet<string>();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
|
if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs))
|
||||||
{
|
{
|
||||||
var ipItems = simpleDNSItem.DirectExpectedIPs
|
var ipItems = simpleDnsItem.DirectExpectedIPs
|
||||||
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
|
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(s => s.Trim())
|
.Select(s => s.Trim())
|
||||||
.Where(s => !string.IsNullOrEmpty(s))
|
.Where(s => !string.IsNullOrEmpty(s))
|
||||||
@@ -259,7 +304,7 @@ public partial class CoreConfigSingboxService
|
|||||||
if (item.OutboundTag == Global.DirectTag)
|
if (item.OutboundTag == Global.DirectTag)
|
||||||
{
|
{
|
||||||
rule.server = Global.SingboxDirectDNSTag;
|
rule.server = Global.SingboxDirectDNSTag;
|
||||||
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Direct) ? null : simpleDNSItem.SingboxStrategy4Direct;
|
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
|
||||||
|
|
||||||
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
|
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -284,31 +329,46 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
|
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
|
||||||
{
|
{
|
||||||
var rule4Fake = JsonUtils.DeepCopy(rule);
|
var rule4Fake = JsonUtils.DeepCopy(rule);
|
||||||
rule4Fake.server = Global.SingboxFakeDNSTag;
|
rule4Fake.server = Global.SingboxFakeDNSTag;
|
||||||
rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA
|
rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA
|
||||||
rule4Fake.rewrite_ttl = 1;
|
rule4Fake.rewrite_ttl = 1;
|
||||||
singboxConfig.dns.rules.Add(rule4Fake);
|
_coreConfig.dns.rules.Add(rule4Fake);
|
||||||
}
|
}
|
||||||
rule.server = Global.SingboxRemoteDNSTag;
|
rule.server = Global.SingboxRemoteDNSTag;
|
||||||
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Proxy) ? null : simpleDNSItem.SingboxStrategy4Proxy;
|
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
singboxConfig.dns.rules.Add(rule);
|
_coreConfig.dns.rules.Add(rule);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
private void GenMinimizedDns()
|
||||||
|
{
|
||||||
|
GenDnsServers();
|
||||||
|
foreach (var server in _coreConfig.dns!.servers.Where(s => !string.IsNullOrEmpty(s.detour)).ToList())
|
||||||
|
{
|
||||||
|
_coreConfig.dns.servers.Remove(server);
|
||||||
|
}
|
||||||
|
_coreConfig.dns ??= new();
|
||||||
|
_coreConfig.dns.rules ??= [];
|
||||||
|
_coreConfig.dns.rules.Clear();
|
||||||
|
_coreConfig.dns.final = Global.SingboxDirectDNSTag;
|
||||||
|
_coreConfig.route.default_domain_resolver = new()
|
||||||
|
{
|
||||||
|
server = Global.SingboxDirectDNSTag,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenDnsCompatible(ProfileItem? node, SingboxConfig singboxConfig)
|
private void GenDnsCustom()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
var item = context.RawDnsItem;
|
||||||
var strDNS = string.Empty;
|
var strDNS = string.Empty;
|
||||||
if (_config.TunModeItem.EnableTun)
|
if (context.IsTunEnabled)
|
||||||
{
|
{
|
||||||
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
|
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
|
||||||
}
|
}
|
||||||
@@ -320,61 +380,33 @@ public partial class CoreConfigSingboxService
|
|||||||
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
|
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
|
||||||
if (dns4Sbox is null)
|
if (dns4Sbox is null)
|
||||||
{
|
{
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
singboxConfig.dns = dns4Sbox;
|
_coreConfig.dns = dns4Sbox;
|
||||||
|
if (dns4Sbox.servers?.Count > 0 &&
|
||||||
if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty())
|
dns4Sbox.servers.First().address.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
await GenDnsDomainsCompatible(singboxConfig, item);
|
GenDnsProtectCustom();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await GenDnsDomainsLegacyCompatible(singboxConfig, item);
|
GenDnsProtectCustomLegacy();
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenOutboundDnsRule(node, singboxConfig);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem)
|
private void GenDnsProtectCustom()
|
||||||
{
|
{
|
||||||
var dns4Sbox = singboxConfig.dns ?? new();
|
var dnsItem = context.RawDnsItem;
|
||||||
|
var dns4Sbox = _coreConfig.dns ?? new();
|
||||||
dns4Sbox.servers ??= [];
|
dns4Sbox.servers ??= [];
|
||||||
dns4Sbox.rules ??= [];
|
dns4Sbox.rules ??= [];
|
||||||
|
|
||||||
var tag = Global.SingboxLocalDNSTag;
|
var tag = Global.SingboxLocalDNSTag;
|
||||||
|
|
||||||
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
|
|
||||||
|
|
||||||
var localDnsServer = ParseDnsAddress(finalDnsAddress);
|
|
||||||
localDnsServer.tag = tag;
|
|
||||||
|
|
||||||
dns4Sbox.servers.Add(localDnsServer);
|
|
||||||
|
|
||||||
singboxConfig.dns = dns4Sbox;
|
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<int> GenDnsDomainsLegacyCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem)
|
|
||||||
{
|
|
||||||
var dns4Sbox = singboxConfig.dns ?? new();
|
|
||||||
dns4Sbox.servers ??= [];
|
|
||||||
dns4Sbox.rules ??= [];
|
|
||||||
|
|
||||||
var tag = Global.SingboxLocalDNSTag;
|
|
||||||
dns4Sbox.servers.Add(new()
|
|
||||||
{
|
|
||||||
tag = tag,
|
|
||||||
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
|
|
||||||
detour = Global.DirectTag,
|
|
||||||
strategy = string.IsNullOrEmpty(dnsItem?.DomainStrategy4Freedom) ? null : dnsItem?.DomainStrategy4Freedom,
|
|
||||||
});
|
|
||||||
dns4Sbox.rules.Insert(0, new()
|
dns4Sbox.rules.Insert(0, new()
|
||||||
{
|
{
|
||||||
server = tag,
|
server = tag,
|
||||||
@@ -386,56 +418,41 @@ public partial class CoreConfigSingboxService
|
|||||||
clash_mode = ERuleMode.Global.ToString()
|
clash_mode = ERuleMode.Global.ToString()
|
||||||
});
|
});
|
||||||
|
|
||||||
var lstDomain = singboxConfig.outbounds
|
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
|
||||||
.Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server))
|
|
||||||
.Select(t => t.server)
|
var localDnsServer = ParseDnsAddress(finalDnsAddress);
|
||||||
.Distinct()
|
localDnsServer.tag = tag;
|
||||||
.ToList();
|
|
||||||
if (lstDomain != null && lstDomain.Count > 0)
|
dns4Sbox.servers.Add(localDnsServer);
|
||||||
{
|
dns4Sbox.rules.Insert(0, BuildProtectDomainRule());
|
||||||
dns4Sbox.rules.Insert(0, new()
|
|
||||||
{
|
_coreConfig.dns = dns4Sbox;
|
||||||
server = tag,
|
|
||||||
domain = lstDomain
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
singboxConfig.dns = dns4Sbox;
|
private void GenDnsProtectCustomLegacy()
|
||||||
return await Task.FromResult(0);
|
{
|
||||||
|
GenDnsProtectCustom();
|
||||||
|
|
||||||
|
_coreConfig.dns?.servers?.RemoveAll(s => s.tag == Global.SingboxLocalDNSTag);
|
||||||
|
var dnsItem = context.RawDnsItem;
|
||||||
|
var localDnsServer = new Server4Sbox()
|
||||||
|
{
|
||||||
|
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress)
|
||||||
|
? Global.DomainPureIPDNSAddress.FirstOrDefault()
|
||||||
|
: dnsItem?.DomainDNSAddress,
|
||||||
|
tag = Global.SingboxLocalDNSTag,
|
||||||
|
detour = Global.DirectTag,
|
||||||
|
};
|
||||||
|
_coreConfig.dns?.servers?.Add(localDnsServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenOutboundDnsRule(ProfileItem? node, SingboxConfig singboxConfig)
|
private Rule4Sbox BuildProtectDomainRule()
|
||||||
{
|
{
|
||||||
if (node == null)
|
return new()
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string> domain = new();
|
|
||||||
if (Utils.IsDomain(node.Address)) // normal outbound
|
|
||||||
{
|
|
||||||
domain.Add(node.Address);
|
|
||||||
}
|
|
||||||
if (node.Address == Global.Loopback && node.SpiderX.IsNotEmpty()) // Tun2SocksAddress
|
|
||||||
{
|
|
||||||
domain.AddRange(Utils.String2List(node.SpiderX)
|
|
||||||
.Where(Utils.IsDomain)
|
|
||||||
.Distinct()
|
|
||||||
.ToList());
|
|
||||||
}
|
|
||||||
if (domain.Count == 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
|
||||||
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
|
|
||||||
{
|
{
|
||||||
server = Global.SingboxLocalDNSTag,
|
server = Global.SingboxLocalDNSTag,
|
||||||
domain = domain,
|
domain = context.ProtectDomainList.ToList(),
|
||||||
});
|
};
|
||||||
|
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Server4Sbox? ParseDnsAddress(string address)
|
private static Server4Sbox? ParseDnsAddress(string address)
|
||||||
@@ -470,14 +487,16 @@ public partial class CoreConfigSingboxService
|
|||||||
{
|
{
|
||||||
// udp dns
|
// udp dns
|
||||||
server.type = "udp";
|
server.type = "udp";
|
||||||
server.server = addressFirst;
|
|
||||||
return server;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// server.type = scheme.ToLower();
|
||||||
|
|
||||||
//server.type = scheme.ToLower();
|
|
||||||
// remove "+local" suffix
|
// remove "+local" suffix
|
||||||
// TODO: "+local" suffix decide server.detour = "direct" ?
|
// TODO: "+local" suffix decide server.detour = "direct" ?
|
||||||
server.type = scheme.Replace("+local", "", StringComparison.OrdinalIgnoreCase).ToLower();
|
server.type = scheme.Replace("+local", "", StringComparison.OrdinalIgnoreCase).ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
server.server = domain;
|
server.server = domain;
|
||||||
if (port != 0)
|
if (port != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigSingboxService
|
public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
private async Task<int> GenInbounds(SingboxConfig singboxConfig)
|
private void GenInbounds()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var listen = "0.0.0.0";
|
var listen = "0.0.0.0";
|
||||||
singboxConfig.inbounds = [];
|
var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
|
_coreConfig.inbounds = [];
|
||||||
|
|
||||||
if (!_config.TunModeItem.EnableTun
|
if (!context.IsTunEnabled
|
||||||
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box))
|
|| (context.IsTunEnabled && _node.Port != listenPort))
|
||||||
{
|
{
|
||||||
var inbound = new Inbound4Sbox()
|
var inbound = new Inbound4Sbox()
|
||||||
{
|
{
|
||||||
@@ -18,23 +19,23 @@ public partial class CoreConfigSingboxService
|
|||||||
tag = EInboundProtocol.socks.ToString(),
|
tag = EInboundProtocol.socks.ToString(),
|
||||||
listen = Global.Loopback,
|
listen = Global.Loopback,
|
||||||
};
|
};
|
||||||
singboxConfig.inbounds.Add(inbound);
|
_coreConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
inbound.listen_port = listenPort;
|
||||||
|
|
||||||
if (_config.Inbound.First().SecondLocalPortEnabled)
|
if (_config.Inbound.First().SecondLocalPortEnabled)
|
||||||
{
|
{
|
||||||
var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true);
|
var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true);
|
||||||
singboxConfig.inbounds.Add(inbound2);
|
_coreConfig.inbounds.Add(inbound2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_config.Inbound.First().AllowLANConn)
|
if (_config.Inbound.First().AllowLANConn)
|
||||||
{
|
{
|
||||||
if (_config.Inbound.First().NewPort4LAN)
|
if (_config.Inbound.First().NewPort4LAN)
|
||||||
{
|
{
|
||||||
var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true);
|
var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true);
|
||||||
inbound3.listen = listen;
|
inbound3.listen = listen;
|
||||||
singboxConfig.inbounds.Add(inbound3);
|
_coreConfig.inbounds.Add(inbound3);
|
||||||
|
|
||||||
//auth
|
//auth
|
||||||
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
||||||
@@ -49,7 +50,7 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_config.TunModeItem.EnableTun)
|
if (context.IsTunEnabled)
|
||||||
{
|
{
|
||||||
if (_config.TunModeItem.Mtu <= 0)
|
if (_config.TunModeItem.Mtu <= 0)
|
||||||
{
|
{
|
||||||
@@ -61,7 +62,7 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
|
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
|
||||||
tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun";
|
tunInbound.interface_name = Utils.IsMacOS() ? $"utun{new Random().Next(99)}" : "singbox_tun";
|
||||||
tunInbound.mtu = _config.TunModeItem.Mtu;
|
tunInbound.mtu = _config.TunModeItem.Mtu;
|
||||||
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
||||||
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
||||||
@@ -71,17 +72,16 @@ public partial class CoreConfigSingboxService
|
|||||||
tunInbound.address = ["172.18.0.1/30"];
|
tunInbound.address = ["172.18.0.1/30"];
|
||||||
}
|
}
|
||||||
|
|
||||||
singboxConfig.inbounds.Add(tunInbound);
|
_coreConfig.inbounds.Add(tunInbound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
|
private Inbound4Sbox BuildInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
|
||||||
{
|
{
|
||||||
var inbound = JsonUtils.DeepCopy(inItem);
|
var inbound = JsonUtils.DeepCopy(inItem);
|
||||||
inbound.tag = protocol.ToString();
|
inbound.tag = protocol.ToString();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigSingboxService
|
public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
private async Task<int> GenLog(SingboxConfig singboxConfig)
|
private void GenLog()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -11,11 +11,11 @@ public partial class CoreConfigSingboxService
|
|||||||
case "debug":
|
case "debug":
|
||||||
case "info":
|
case "info":
|
||||||
case "error":
|
case "error":
|
||||||
singboxConfig.log.level = _config.CoreBasicItem.Loglevel;
|
_coreConfig.log.level = _config.CoreBasicItem.Loglevel;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "warning":
|
case "warning":
|
||||||
singboxConfig.log.level = "warn";
|
_coreConfig.log.level = "warn";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -23,18 +23,17 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
if (_config.CoreBasicItem.Loglevel == Global.None)
|
if (_config.CoreBasicItem.Loglevel == Global.None)
|
||||||
{
|
{
|
||||||
singboxConfig.log.disabled = true;
|
_coreConfig.log.disabled = true;
|
||||||
}
|
}
|
||||||
if (_config.CoreBasicItem.LogEnabled)
|
if (_config.CoreBasicItem.LogEnabled)
|
||||||
{
|
{
|
||||||
var dtNow = DateTime.Now;
|
var dtNow = DateTime.Now;
|
||||||
singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
|
_coreConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,47 +2,47 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigSingboxService
|
public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
private async Task<int> GenRouting(SingboxConfig singboxConfig)
|
private void GenRouting()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
singboxConfig.route.final = Global.ProxyTag;
|
_coreConfig.route.final = Global.ProxyTag;
|
||||||
var item = _config.SimpleDNSItem;
|
var simpleDnsItem = context.SimpleDnsItem;
|
||||||
|
|
||||||
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
|
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
|
||||||
var directDNSStrategy = item.SingboxStrategy4Direct.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : item.SingboxStrategy4Direct;
|
var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
|
||||||
|
|
||||||
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
var rawDNSItem = context.RawDnsItem;
|
||||||
if (rawDNSItem != null && rawDNSItem.Enabled == true)
|
if (rawDNSItem is { Enabled: true })
|
||||||
{
|
{
|
||||||
defaultDomainResolverTag = Global.SingboxLocalDNSTag;
|
defaultDomainResolverTag = Global.SingboxLocalDNSTag;
|
||||||
directDNSStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : rawDNSItem.DomainStrategy4Freedom;
|
directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
|
||||||
}
|
}
|
||||||
singboxConfig.route.default_domain_resolver = new()
|
_coreConfig.route.default_domain_resolver = new()
|
||||||
{
|
{
|
||||||
server = defaultDomainResolverTag,
|
server = defaultDomainResolverTag,
|
||||||
strategy = directDNSStrategy
|
strategy = directDnsStrategy
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_config.TunModeItem.EnableTun)
|
if (_config.TunModeItem.EnableTun)
|
||||||
{
|
{
|
||||||
singboxConfig.route.auto_detect_interface = true;
|
_coreConfig.route.auto_detect_interface = true;
|
||||||
|
|
||||||
var tunRules = JsonUtils.Deserialize<List<Rule4Sbox>>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName));
|
var tunRules = JsonUtils.Deserialize<List<Rule4Sbox>>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName));
|
||||||
if (tunRules != null)
|
if (tunRules != null)
|
||||||
{
|
{
|
||||||
singboxConfig.route.rules.AddRange(tunRules);
|
_coreConfig.route.rules.AddRange(tunRules);
|
||||||
}
|
}
|
||||||
|
|
||||||
GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe);
|
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
|
||||||
singboxConfig.route.rules.Add(new()
|
_coreConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
port = new() { 53 },
|
port = [53],
|
||||||
action = "hijack-dns",
|
action = "hijack-dns",
|
||||||
process_name = lstDnsExe
|
process_name = lstDnsExe
|
||||||
});
|
});
|
||||||
|
|
||||||
singboxConfig.route.rules.Add(new()
|
_coreConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
outbound = Global.DirectTag,
|
outbound = Global.DirectTag,
|
||||||
process_name = lstDirectExe
|
process_name = lstDirectExe
|
||||||
@@ -51,73 +51,62 @@ public partial class CoreConfigSingboxService
|
|||||||
|
|
||||||
if (_config.Inbound.First().SniffingEnabled)
|
if (_config.Inbound.First().SniffingEnabled)
|
||||||
{
|
{
|
||||||
singboxConfig.route.rules.Add(new()
|
_coreConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
action = "sniff"
|
action = "sniff"
|
||||||
});
|
});
|
||||||
singboxConfig.route.rules.Add(new()
|
_coreConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
protocol = new() { "dns" },
|
protocol = ["dns"],
|
||||||
action = "hijack-dns"
|
action = "hijack-dns"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
singboxConfig.route.rules.Add(new()
|
_coreConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
port = new() { 53 },
|
port = [53],
|
||||||
network = new() { "udp" },
|
network = ["udp"],
|
||||||
action = "hijack-dns"
|
action = "hijack-dns"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostsDomains = new List<string>();
|
var hostsDomains = new List<string>();
|
||||||
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
if (rawDNSItem is not { Enabled: true })
|
||||||
if (dnsItem == null || dnsItem.Enabled == false)
|
|
||||||
{
|
{
|
||||||
var simpleDNSItem = _config.SimpleDNSItem;
|
var userHostsMap = Utils.ParseHostsToDictionary(simpleDnsItem.Hosts);
|
||||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
hostsDomains.AddRange(userHostsMap.Select(kvp => kvp.Key));
|
||||||
{
|
if (simpleDnsItem.UseSystemHosts == true)
|
||||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
|
||||||
foreach (var kvp in userHostsMap)
|
|
||||||
{
|
|
||||||
hostsDomains.Add(kvp.Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (simpleDNSItem.UseSystemHosts == true)
|
|
||||||
{
|
{
|
||||||
var systemHostsMap = Utils.GetSystemHosts();
|
var systemHostsMap = Utils.GetSystemHosts();
|
||||||
foreach (var kvp in systemHostsMap)
|
hostsDomains.AddRange(systemHostsMap.Select(kvp => kvp.Key));
|
||||||
{
|
|
||||||
hostsDomains.Add(kvp.Key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hostsDomains.Count > 0)
|
if (hostsDomains.Count > 0)
|
||||||
{
|
{
|
||||||
singboxConfig.route.rules.Add(new()
|
_coreConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
action = "resolve",
|
action = "resolve",
|
||||||
domain = hostsDomains,
|
domain = hostsDomains,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
singboxConfig.route.rules.Add(new()
|
_coreConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
outbound = Global.DirectTag,
|
outbound = Global.DirectTag,
|
||||||
clash_mode = ERuleMode.Direct.ToString()
|
clash_mode = ERuleMode.Direct.ToString()
|
||||||
});
|
});
|
||||||
singboxConfig.route.rules.Add(new()
|
_coreConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
outbound = Global.ProxyTag,
|
outbound = Global.ProxyTag,
|
||||||
clash_mode = ERuleMode.Global.ToString()
|
clash_mode = ERuleMode.Global.ToString()
|
||||||
});
|
});
|
||||||
|
|
||||||
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
|
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty();
|
||||||
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = context.RoutingItem;
|
||||||
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
|
if (routing.DomainStrategy4Singbox.IsNotEmpty())
|
||||||
{
|
{
|
||||||
domainStrategy = defaultRouting.DomainStrategy4Singbox;
|
domainStrategy = routing.DomainStrategy4Singbox;
|
||||||
}
|
}
|
||||||
var resolveRule = new Rule4Sbox
|
var resolveRule = new Rule4Sbox
|
||||||
{
|
{
|
||||||
@@ -126,10 +115,9 @@ public partial class CoreConfigSingboxService
|
|||||||
};
|
};
|
||||||
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
|
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
|
||||||
{
|
{
|
||||||
singboxConfig.route.rules.Add(resolveRule);
|
_coreConfig.route.rules.Add(resolveRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
|
||||||
var ipRules = new List<RulesItem>();
|
var ipRules = new List<RulesItem>();
|
||||||
if (routing != null)
|
if (routing != null)
|
||||||
{
|
{
|
||||||
@@ -146,7 +134,7 @@ public partial class CoreConfigSingboxService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenRoutingUserRule(item1, singboxConfig);
|
GenRoutingUserRule(item1);
|
||||||
|
|
||||||
if (item1.Ip?.Count > 0)
|
if (item1.Ip?.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -156,10 +144,10 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch)
|
if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch)
|
||||||
{
|
{
|
||||||
singboxConfig.route.rules.Add(resolveRule);
|
_coreConfig.route.rules.Add(resolveRule);
|
||||||
foreach (var item2 in ipRules)
|
foreach (var item2 in ipRules)
|
||||||
{
|
{
|
||||||
await GenRoutingUserRule(item2, singboxConfig);
|
GenRoutingUserRule(item2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,10 +155,9 @@ public partial class CoreConfigSingboxService
|
|||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenRoutingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe)
|
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
|
||||||
{
|
{
|
||||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -194,20 +181,22 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lstDnsExe = new List<string>(dnsExeSet);
|
var lstDnsExe = new List<string>(dnsExeSet);
|
||||||
lstDirectExe = new List<string>(directExeSet);
|
var lstDirectExe = new List<string>(directExeSet);
|
||||||
|
|
||||||
|
return (lstDnsExe, lstDirectExe);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig)
|
private void GenRoutingUserRule(RulesItem? item)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (item == null)
|
if (item == null)
|
||||||
{
|
{
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
|
item.OutboundTag = GenRoutingUserRuleOutbound(item.OutboundTag ?? Global.ProxyTag);
|
||||||
var rules = singboxConfig.route.rules;
|
var rules = _coreConfig.route.rules;
|
||||||
|
|
||||||
var rule = new Rule4Sbox();
|
var rule = new Rule4Sbox();
|
||||||
if (item.OutboundTag == "block")
|
if (item.OutboundTag == "block")
|
||||||
@@ -250,8 +239,10 @@ public partial class CoreConfigSingboxService
|
|||||||
foreach (var it in item.Domain)
|
foreach (var it in item.Domain)
|
||||||
{
|
{
|
||||||
if (ParseV2Domain(it, rule1))
|
if (ParseV2Domain(it, rule1))
|
||||||
|
{
|
||||||
countDomain++;
|
countDomain++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (countDomain > 0)
|
if (countDomain > 0)
|
||||||
{
|
{
|
||||||
rules.Add(rule1);
|
rules.Add(rule1);
|
||||||
@@ -265,8 +256,10 @@ public partial class CoreConfigSingboxService
|
|||||||
foreach (var it in item.Ip)
|
foreach (var it in item.Ip)
|
||||||
{
|
{
|
||||||
if (ParseV2Address(it, rule2))
|
if (ParseV2Address(it, rule2))
|
||||||
|
{
|
||||||
countIp++;
|
countIp++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (countIp > 0)
|
if (countIp > 0)
|
||||||
{
|
{
|
||||||
rules.Add(rule2);
|
rules.Add(rule2);
|
||||||
@@ -274,13 +267,51 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_config.TunModeItem.EnableTun && item.Process?.Count > 0)
|
if (item.Process?.Count > 0)
|
||||||
{
|
{
|
||||||
rule3.process_name = item.Process;
|
var ruleProcName = JsonUtils.DeepCopy(rule3);
|
||||||
rules.Add(rule3);
|
ruleProcName.process_name ??= [];
|
||||||
|
var ruleProcPath = JsonUtils.DeepCopy(rule3);
|
||||||
|
ruleProcPath.process_path ??= [];
|
||||||
|
foreach (var process in item.Process)
|
||||||
|
{
|
||||||
|
// sing-box doesn't support this, fall back to process name match
|
||||||
|
if (process is "self/" or "xray/")
|
||||||
|
{
|
||||||
|
ruleProcName.process_name.Add(Utils.GetExeName("sing-box"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.Contains('/') || process.Contains('\\'))
|
||||||
|
{
|
||||||
|
var procPath = process;
|
||||||
|
if (Utils.IsWindows())
|
||||||
|
{
|
||||||
|
procPath = procPath.Replace('/', '\\');
|
||||||
|
}
|
||||||
|
ruleProcPath.process_path.Add(procPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sing-box strictly matches the exe suffix on Windows
|
||||||
|
var procName = Utils.GetExeName(process);
|
||||||
|
|
||||||
|
ruleProcName.process_name.Add(procName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ruleProcName.process_name.Count > 0)
|
||||||
|
{
|
||||||
|
rules.Add(ruleProcName);
|
||||||
hasDomainIp = true;
|
hasDomainIp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ruleProcPath.process_path.Count > 0)
|
||||||
|
{
|
||||||
|
rules.Add(ruleProcPath);
|
||||||
|
hasDomainIp = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasDomainIp
|
if (!hasDomainIp
|
||||||
&& (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null || rule.network != null))
|
&& (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null || rule.network != null))
|
||||||
{
|
{
|
||||||
@@ -291,12 +322,11 @@ public partial class CoreConfigSingboxService
|
|||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ParseV2Domain(string domain, Rule4Sbox rule)
|
private static bool ParseV2Domain(string domain, Rule4Sbox rule)
|
||||||
{
|
{
|
||||||
if (domain.StartsWith("#") || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
|
if (domain.StartsWith('#') || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -312,10 +342,8 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
else if (domain.StartsWith("domain:"))
|
else if (domain.StartsWith("domain:"))
|
||||||
{
|
{
|
||||||
rule.domain ??= [];
|
|
||||||
rule.domain_suffix ??= [];
|
rule.domain_suffix ??= [];
|
||||||
rule.domain?.Add(domain.Substring(7));
|
rule.domain_suffix?.Add(domain.Substring(7));
|
||||||
rule.domain_suffix?.Add("." + domain.Substring(7));
|
|
||||||
}
|
}
|
||||||
else if (domain.StartsWith("full:"))
|
else if (domain.StartsWith("full:"))
|
||||||
{
|
{
|
||||||
@@ -335,7 +363,7 @@ public partial class CoreConfigSingboxService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ParseV2Address(string address, Rule4Sbox rule)
|
private static bool ParseV2Address(string address, Rule4Sbox rule)
|
||||||
{
|
{
|
||||||
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
|
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
|
||||||
{
|
{
|
||||||
@@ -368,14 +396,14 @@ public partial class CoreConfigSingboxService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig)
|
private string GenRoutingUserRuleOutbound(string outboundTag)
|
||||||
{
|
{
|
||||||
if (Global.OutboundTags.Contains(outboundTag))
|
if (Global.OutboundTags.Contains(outboundTag))
|
||||||
{
|
{
|
||||||
return outboundTag;
|
return outboundTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
|
||||||
|
|
||||||
if (node == null
|
if (node == null
|
||||||
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
|
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
|
||||||
@@ -385,39 +413,15 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tag = $"{node.IndexId}-{Global.ProxyTag}";
|
var tag = $"{node.IndexId}-{Global.ProxyTag}";
|
||||||
if (singboxConfig.outbounds.Any(o => o.tag == tag)
|
if (_coreConfig.outbounds.Any(o => o.tag.StartsWith(tag))
|
||||||
|| (singboxConfig.endpoints != null && singboxConfig.endpoints.Any(e => e.tag == tag)))
|
|| (_coreConfig.endpoints != null && _coreConfig.endpoints.Any(e => e.tag.StartsWith(tag))))
|
||||||
{
|
{
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.ConfigType.IsGroupType())
|
var proxyOutbounds = new CoreConfigSingboxService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
|
||||||
{
|
FillRangeProxy(proxyOutbounds, _coreConfig, false);
|
||||||
var ret = await GenGroupOutbound(node, singboxConfig, tag);
|
|
||||||
if (ret == 0)
|
|
||||||
{
|
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
return Global.ProxyTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
var server = await GenServer(node);
|
|
||||||
if (server is null)
|
|
||||||
{
|
|
||||||
return Global.ProxyTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
server.tag = tag;
|
|
||||||
if (server is Endpoints4Sbox endpoint)
|
|
||||||
{
|
|
||||||
singboxConfig.endpoints ??= new();
|
|
||||||
singboxConfig.endpoints.Add(endpoint);
|
|
||||||
}
|
|
||||||
else if (server is Outbound4Sbox outbound)
|
|
||||||
{
|
|
||||||
singboxConfig.outbounds.Add(outbound);
|
|
||||||
}
|
|
||||||
|
|
||||||
return server.tag;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,28 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigSingboxService
|
public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
private async Task<int> ConvertGeo2Ruleset(SingboxConfig singboxConfig)
|
private void ConvertGeo2Ruleset()
|
||||||
{
|
{
|
||||||
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
|
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
|
||||||
{
|
{
|
||||||
if (rule_set != null)
|
if (rule_set != null)
|
||||||
|
{
|
||||||
ruleSets.AddRange(rule_set);
|
ruleSets.AddRange(rule_set);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
var geosite = "geosite";
|
var geosite = "geosite";
|
||||||
var geoip = "geoip";
|
var geoip = "geoip";
|
||||||
var ruleSets = new List<string>();
|
var ruleSets = new List<string>();
|
||||||
|
|
||||||
//convert route geosite & geoip to ruleset
|
//convert route geosite & geoip to ruleset
|
||||||
foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
|
foreach (var rule in _coreConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
|
||||||
{
|
{
|
||||||
rule.rule_set ??= new List<string>();
|
rule.rule_set ??= new List<string>();
|
||||||
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
||||||
rule.geosite = null;
|
rule.geosite = null;
|
||||||
AddRuleSets(ruleSets, rule.rule_set);
|
AddRuleSets(ruleSets, rule.rule_set);
|
||||||
}
|
}
|
||||||
foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
|
foreach (var rule in _coreConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
|
||||||
{
|
{
|
||||||
rule.rule_set ??= new List<string>();
|
rule.rule_set ??= new List<string>();
|
||||||
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
||||||
@@ -30,24 +32,24 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
|
|
||||||
//convert dns geosite & geoip to ruleset
|
//convert dns geosite & geoip to ruleset
|
||||||
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
|
foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
|
||||||
{
|
{
|
||||||
rule.rule_set ??= new List<string>();
|
rule.rule_set ??= new List<string>();
|
||||||
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
||||||
rule.geosite = null;
|
rule.geosite = null;
|
||||||
}
|
}
|
||||||
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
|
foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
|
||||||
{
|
{
|
||||||
rule.rule_set ??= new List<string>();
|
rule.rule_set ??= new List<string>();
|
||||||
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
||||||
rule.geoip = null;
|
rule.geoip = null;
|
||||||
}
|
}
|
||||||
foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
|
foreach (var dnsRule in _coreConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
|
||||||
{
|
{
|
||||||
AddRuleSets(ruleSets, dnsRule.rule_set);
|
AddRuleSets(ruleSets, dnsRule.rule_set);
|
||||||
}
|
}
|
||||||
//rules in rules
|
//rules in rules
|
||||||
foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? [])
|
foreach (var item in _coreConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? [])
|
||||||
{
|
{
|
||||||
foreach (var item2 in item ?? [])
|
foreach (var item2 in item ?? [])
|
||||||
{
|
{
|
||||||
@@ -58,7 +60,7 @@ public partial class CoreConfigSingboxService
|
|||||||
//load custom ruleset file
|
//load custom ruleset file
|
||||||
List<Ruleset4Sbox> customRulesets = [];
|
List<Ruleset4Sbox> customRulesets = [];
|
||||||
|
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = context.RoutingItem;
|
||||||
if (routing.CustomRulesetPath4Singbox.IsNotEmpty())
|
if (routing.CustomRulesetPath4Singbox.IsNotEmpty())
|
||||||
{
|
{
|
||||||
var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox);
|
var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox);
|
||||||
@@ -76,7 +78,7 @@ public partial class CoreConfigSingboxService
|
|||||||
var localSrss = Utils.GetBinPath("srss");
|
var localSrss = Utils.GetBinPath("srss");
|
||||||
|
|
||||||
//Add ruleset srs
|
//Add ruleset srs
|
||||||
singboxConfig.route.rule_set = [];
|
_coreConfig.route.rule_set = [];
|
||||||
foreach (var item in new HashSet<string>(ruleSets))
|
foreach (var item in new HashSet<string>(ruleSets))
|
||||||
{
|
{
|
||||||
if (item.IsNullOrEmpty())
|
if (item.IsNullOrEmpty())
|
||||||
@@ -111,9 +113,7 @@ public partial class CoreConfigSingboxService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
singboxConfig.route.rule_set.Add(customRuleset);
|
_coreConfig.route.rule_set.Add(customRuleset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigSingboxService
|
public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
private async Task<int> GenExperimental(SingboxConfig singboxConfig)
|
private void GenExperimental()
|
||||||
{
|
{
|
||||||
//if (_config.guiItem.enableStatistics)
|
//if (_config.guiItem.enableStatistics)
|
||||||
{
|
{
|
||||||
singboxConfig.experimental ??= new Experimental4Sbox();
|
_coreConfig.experimental ??= new Experimental4Sbox();
|
||||||
singboxConfig.experimental.clash_api = new Clash_Api4Sbox()
|
_coreConfig.experimental.clash_api = new Clash_Api4Sbox()
|
||||||
{
|
{
|
||||||
external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}",
|
external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}",
|
||||||
};
|
};
|
||||||
@@ -15,15 +15,13 @@ public partial class CoreConfigSingboxService
|
|||||||
|
|
||||||
if (_config.CoreBasicItem.EnableCacheFile4Sbox)
|
if (_config.CoreBasicItem.EnableCacheFile4Sbox)
|
||||||
{
|
{
|
||||||
singboxConfig.experimental ??= new Experimental4Sbox();
|
_coreConfig.experimental ??= new Experimental4Sbox();
|
||||||
singboxConfig.experimental.cache_file = new CacheFile4Sbox()
|
_coreConfig.experimental.cache_file = new CacheFile4Sbox()
|
||||||
{
|
{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
path = Utils.GetBinPath("cache.db"),
|
path = Utils.GetBinPath("cache.db"),
|
||||||
store_fakeip = _config.SimpleDNSItem.FakeIP == true
|
store_fakeip = context.SimpleDnsItem.FakeIP == true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,39 @@
|
|||||||
namespace ServiceLib.Services.CoreConfig;
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
public partial class CoreConfigV2rayService(Config config)
|
public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||||
{
|
{
|
||||||
private readonly Config _config = config;
|
|
||||||
private static readonly string _tag = "CoreConfigV2rayService";
|
private static readonly string _tag = "CoreConfigV2rayService";
|
||||||
|
private readonly Config _config = context.AppConfig;
|
||||||
|
private readonly ProfileItem _node = context.Node;
|
||||||
|
|
||||||
|
private V2rayConfig _coreConfig = new();
|
||||||
|
|
||||||
#region public gen function
|
#region public gen function
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
|
public RetResult GenerateClientConfigContent()
|
||||||
{
|
{
|
||||||
var ret = new RetResult();
|
var ret = new RetResult();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (node == null
|
if (context.IsTunEnabled && context.TunProtectSsPort > 0 && context.ProxyRelaySsPort > 0)
|
||||||
|| !node.IsValid())
|
{
|
||||||
|
return GenerateClientProxyRelayConfig();
|
||||||
|
}
|
||||||
|
if (_node == null
|
||||||
|
|| !_node.IsValid())
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.GetNetwork() is nameof(ETransport.quic))
|
if (_node.GetNetwork() is nameof(ETransport.quic))
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.Msg = ResUI.InitialConfiguration;
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
if (node.ConfigType.IsGroupType())
|
|
||||||
{
|
|
||||||
switch (node.ConfigType)
|
|
||||||
{
|
|
||||||
case EConfigType.PolicyGroup:
|
|
||||||
return await GenerateClientMultipleLoadConfig(node);
|
|
||||||
|
|
||||||
case EConfigType.ProxyChain:
|
|
||||||
return await GenerateClientChainConfig(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
@@ -46,30 +41,28 @@ public partial class CoreConfigV2rayService(Config config)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||||
if (v2rayConfig == null)
|
if (_coreConfig == null)
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenLog(v2rayConfig);
|
GenLog();
|
||||||
|
|
||||||
await GenInbounds(v2rayConfig);
|
GenInbounds();
|
||||||
|
|
||||||
await GenOutbound(node, v2rayConfig.outbounds.First());
|
GenOutbounds();
|
||||||
|
|
||||||
await GenMoreOutbounds(node, v2rayConfig);
|
GenRouting();
|
||||||
|
|
||||||
await GenRouting(v2rayConfig);
|
GenDns();
|
||||||
|
|
||||||
await GenDns(node, v2rayConfig);
|
GenStatistic();
|
||||||
|
|
||||||
await GenStatistic(v2rayConfig);
|
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
|
ret.Data = ApplyFullConfigTemplate();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -80,180 +73,11 @@ public partial class CoreConfigV2rayService(Config config)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode)
|
public RetResult GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
|
||||||
{
|
|
||||||
var ret = new RetResult();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_config == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Msg = ResUI.InitialConfiguration;
|
|
||||||
|
|
||||||
string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
|
||||||
string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
|
||||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
|
||||||
if (v2rayConfig == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
v2rayConfig.outbounds.RemoveAt(0);
|
|
||||||
|
|
||||||
await GenLog(v2rayConfig);
|
|
||||||
await GenInbounds(v2rayConfig);
|
|
||||||
|
|
||||||
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
|
|
||||||
if (groupRet != 0)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
await GenRouting(v2rayConfig);
|
|
||||||
await GenDns(null, v2rayConfig);
|
|
||||||
await GenStatistic(v2rayConfig);
|
|
||||||
|
|
||||||
var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}";
|
|
||||||
|
|
||||||
//add rule
|
|
||||||
var rules = v2rayConfig.routing.rules;
|
|
||||||
if (rules?.Count > 0 && ((v2rayConfig.routing.balancers?.Count ?? 0) > 0))
|
|
||||||
{
|
|
||||||
var balancerTagSet = v2rayConfig.routing.balancers
|
|
||||||
.Select(b => b.tag)
|
|
||||||
.ToHashSet();
|
|
||||||
|
|
||||||
foreach (var rule in rules)
|
|
||||||
{
|
|
||||||
if (rule.outboundTag == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (balancerTagSet.Contains(rule.outboundTag))
|
|
||||||
{
|
|
||||||
rule.balancerTag = rule.outboundTag;
|
|
||||||
rule.outboundTag = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var outboundWithSuffix = rule.outboundTag + Global.BalancerTagSuffix;
|
|
||||||
if (balancerTagSet.Contains(outboundWithSuffix))
|
|
||||||
{
|
|
||||||
rule.balancerTag = outboundWithSuffix;
|
|
||||||
rule.outboundTag = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
|
||||||
{
|
|
||||||
v2rayConfig.routing.rules.Add(new()
|
|
||||||
{
|
|
||||||
ip = ["0.0.0.0/0", "::/0"],
|
|
||||||
balancerTag = defaultBalancerTag,
|
|
||||||
type = "field"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
v2rayConfig.routing.rules.Add(new()
|
|
||||||
{
|
|
||||||
network = "tcp,udp",
|
|
||||||
balancerTag = defaultBalancerTag,
|
|
||||||
type = "field"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Success = true;
|
|
||||||
|
|
||||||
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
|
|
||||||
{
|
|
||||||
var ret = new RetResult();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_config == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Msg = ResUI.InitialConfiguration;
|
|
||||||
|
|
||||||
string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
|
||||||
string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
|
||||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
|
||||||
if (v2rayConfig == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
v2rayConfig.outbounds.RemoveAt(0);
|
|
||||||
|
|
||||||
await GenLog(v2rayConfig);
|
|
||||||
await GenInbounds(v2rayConfig);
|
|
||||||
|
|
||||||
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
|
|
||||||
if (groupRet != 0)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
await GenRouting(v2rayConfig);
|
|
||||||
await GenDns(null, v2rayConfig);
|
|
||||||
await GenStatistic(v2rayConfig);
|
|
||||||
|
|
||||||
ret.Success = true;
|
|
||||||
|
|
||||||
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
|
|
||||||
{
|
{
|
||||||
var ret = new RetResult();
|
var ret = new RetResult();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_config == null)
|
|
||||||
{
|
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Msg = ResUI.InitialConfiguration;
|
ret.Msg = ResUI.InitialConfiguration;
|
||||||
|
|
||||||
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||||
@@ -264,29 +88,19 @@ public partial class CoreConfigV2rayService(Config config)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||||
if (v2rayConfig == null)
|
if (_coreConfig == null)
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
List<IPEndPoint> lstIpEndPoints = new();
|
|
||||||
List<TcpConnectionInformation> lstTcpConns = new();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
|
|
||||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
|
|
||||||
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
await GenLog(v2rayConfig);
|
var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo();
|
||||||
v2rayConfig.inbounds.Clear();
|
|
||||||
v2rayConfig.outbounds.Clear();
|
GenLog();
|
||||||
v2rayConfig.routing.rules.Clear();
|
_coreConfig.inbounds.Clear();
|
||||||
|
_coreConfig.outbounds.Clear();
|
||||||
|
_coreConfig.routing.rules.Clear();
|
||||||
|
|
||||||
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||||
|
|
||||||
@@ -300,8 +114,9 @@ public partial class CoreConfigV2rayService(Config config)
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId);
|
||||||
if (item is null || item.IsComplex() || !item.IsValid())
|
var item = context.AllProxiesMap.GetValueOrDefault(actIndexId);
|
||||||
|
if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -340,27 +155,40 @@ public partial class CoreConfigV2rayService(Config config)
|
|||||||
protocol = EInboundProtocol.mixed.ToString(),
|
protocol = EInboundProtocol.mixed.ToString(),
|
||||||
};
|
};
|
||||||
inbound.tag = inbound.protocol + inbound.port.ToString();
|
inbound.tag = inbound.protocol + inbound.port.ToString();
|
||||||
v2rayConfig.inbounds.Add(inbound);
|
_coreConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
|
var tag = Global.ProxyTag + inbound.port.ToString();
|
||||||
|
var isBalancer = false;
|
||||||
//outbound
|
//outbound
|
||||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
var proxyOutbounds =
|
||||||
await GenOutbound(item, outbound);
|
new CoreConfigV2rayService(context with { Node = item }).BuildAllProxyOutbounds(tag);
|
||||||
outbound.tag = Global.ProxyTag + inbound.port.ToString();
|
_coreConfig.outbounds.AddRange(proxyOutbounds);
|
||||||
v2rayConfig.outbounds.Add(outbound);
|
if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1)
|
||||||
|
{
|
||||||
|
isBalancer = true;
|
||||||
|
var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
|
||||||
|
GenObservatory(multipleLoad, tag);
|
||||||
|
GenBalancer(multipleLoad, tag);
|
||||||
|
}
|
||||||
|
|
||||||
//rule
|
//rule
|
||||||
RulesItem4Ray rule = new()
|
RulesItem4Ray rule = new()
|
||||||
{
|
{
|
||||||
inboundTag = new List<string> { inbound.tag },
|
inboundTag = new List<string> { inbound.tag },
|
||||||
outboundTag = outbound.tag,
|
outboundTag = tag,
|
||||||
type = "field"
|
type = "field"
|
||||||
};
|
};
|
||||||
v2rayConfig.routing.rules.Add(rule);
|
if (isBalancer)
|
||||||
|
{
|
||||||
|
rule.balancerTag = tag;
|
||||||
|
rule.outboundTag = null;
|
||||||
|
}
|
||||||
|
_coreConfig.routing.rules.Add(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -371,21 +199,21 @@ public partial class CoreConfigV2rayService(Config config)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
|
public RetResult GenerateClientSpeedtestConfig(int port)
|
||||||
{
|
{
|
||||||
var ret = new RetResult();
|
var ret = new RetResult();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (node == null
|
if (_node == null
|
||||||
|| !node.IsValid())
|
|| !_node.IsValid())
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.CheckServerSettings;
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.GetNetwork() is nameof(ETransport.quic))
|
if (_node.GetNetwork() is nameof(ETransport.quic))
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,20 +224,19 @@ public partial class CoreConfigV2rayService(Config config)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||||
if (v2rayConfig == null)
|
if (_coreConfig == null)
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenLog(v2rayConfig);
|
GenLog();
|
||||||
await GenOutbound(node, v2rayConfig.outbounds.First());
|
GenOutbounds();
|
||||||
await GenMoreOutbounds(node, v2rayConfig);
|
|
||||||
|
|
||||||
v2rayConfig.routing.rules.Clear();
|
_coreConfig.routing.rules.Clear();
|
||||||
v2rayConfig.inbounds.Clear();
|
_coreConfig.inbounds.Clear();
|
||||||
v2rayConfig.inbounds.Add(new()
|
_coreConfig.inbounds.Add(new()
|
||||||
{
|
{
|
||||||
tag = $"{EInboundProtocol.socks}{port}",
|
tag = $"{EInboundProtocol.socks}{port}",
|
||||||
listen = Global.Loopback,
|
listen = Global.Loopback,
|
||||||
@@ -419,7 +246,108 @@ public partial class CoreConfigV2rayService(Config config)
|
|||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RetResult GenerateClientProxyRelayConfig()
|
||||||
|
{
|
||||||
|
var ret = new RetResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_node == null
|
||||||
|
|| !_node.IsValid())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.CheckServerSettings;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_node.GetNetwork() is nameof(ETransport.quic))
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||||
|
if (result.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||||
|
if (_coreConfig == null)
|
||||||
|
{
|
||||||
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
GenLog();
|
||||||
|
_coreConfig.outbounds.Clear();
|
||||||
|
GenOutbounds();
|
||||||
|
|
||||||
|
var protectNode = new ProfileItem()
|
||||||
|
{
|
||||||
|
CoreType = ECoreType.Xray,
|
||||||
|
ConfigType = EConfigType.Shadowsocks,
|
||||||
|
Address = Global.Loopback,
|
||||||
|
Port = context.TunProtectSsPort,
|
||||||
|
Password = Global.None,
|
||||||
|
};
|
||||||
|
protectNode.SetProtocolExtra(protectNode.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
SsMethod = Global.None,
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var outbound in _coreConfig.outbounds.Where(outbound => outbound.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
|
||||||
|
{
|
||||||
|
outbound.streamSettings ??= new StreamSettings4Ray();
|
||||||
|
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
|
||||||
|
outbound.streamSettings.sockopt.dialerProxy = "tun-project-ss";
|
||||||
|
}
|
||||||
|
_coreConfig.outbounds.Add(new CoreConfigV2rayService(context with
|
||||||
|
{
|
||||||
|
Node = protectNode,
|
||||||
|
}).BuildProxyOutbound("tun-project-ss"));
|
||||||
|
|
||||||
|
var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 };
|
||||||
|
_coreConfig.routing.rules =
|
||||||
|
[
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
inboundTag = new List<string> { "proxy-relay-ss" },
|
||||||
|
outboundTag = hasBalancer ? null : Global.ProxyTag,
|
||||||
|
balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix: null,
|
||||||
|
type = "field"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
_coreConfig.inbounds.Clear();
|
||||||
|
|
||||||
|
var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!;
|
||||||
|
configNode["inbounds"]!.AsArray().Add(new
|
||||||
|
{
|
||||||
|
listen = Global.Loopback,
|
||||||
|
port = context.ProxyRelaySsPort,
|
||||||
|
protocol = "shadowsocks",
|
||||||
|
settings = new
|
||||||
|
{
|
||||||
|
network = "tcp,udp",
|
||||||
|
method = Global.None,
|
||||||
|
password = Global.None,
|
||||||
|
},
|
||||||
|
tag = "proxy-relay-ss",
|
||||||
|
});
|
||||||
|
|
||||||
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
|
ret.Success = true;
|
||||||
|
ret.Data = JsonUtils.Serialize(configNode);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigV2rayService
|
public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
private async Task<int> GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
|
private void GenObservatory(EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
|
||||||
{
|
{
|
||||||
// Collect all existing subject selectors from both observatories
|
// Collect all existing subject selectors from both observatories
|
||||||
var subjectSelectors = new List<string>();
|
var subjectSelectors = new List<string>();
|
||||||
subjectSelectors.AddRange(v2rayConfig.burstObservatory?.subjectSelector ?? []);
|
subjectSelectors.AddRange(_coreConfig.burstObservatory?.subjectSelector ?? []);
|
||||||
subjectSelectors.AddRange(v2rayConfig.observatory?.subjectSelector ?? []);
|
subjectSelectors.AddRange(_coreConfig.observatory?.subjectSelector ?? []);
|
||||||
|
|
||||||
// Case 1: exact match already exists -> nothing to do
|
// Case 1: exact match already exists -> nothing to do
|
||||||
if (subjectSelectors.Any(baseTagName.StartsWith))
|
if (subjectSelectors.Any(baseTagName.StartsWith))
|
||||||
return await Task.FromResult(0);
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Case 2: prefix match exists -> reuse it and move to the first position
|
// Case 2: prefix match exists -> reuse it and move to the first position
|
||||||
var matched = subjectSelectors.FirstOrDefault(s => s.StartsWith(baseTagName));
|
var matched = subjectSelectors.FirstOrDefault(s => s.StartsWith(baseTagName));
|
||||||
@@ -19,28 +21,28 @@ public partial class CoreConfigV2rayService
|
|||||||
{
|
{
|
||||||
baseTagName = matched;
|
baseTagName = matched;
|
||||||
|
|
||||||
if (v2rayConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true)
|
if (_coreConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true)
|
||||||
{
|
{
|
||||||
v2rayConfig.burstObservatory.subjectSelector.Remove(baseTagName);
|
_coreConfig.burstObservatory.subjectSelector.Remove(baseTagName);
|
||||||
v2rayConfig.burstObservatory.subjectSelector.Insert(0, baseTagName);
|
_coreConfig.burstObservatory.subjectSelector.Insert(0, baseTagName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v2rayConfig.observatory?.subjectSelector?.Contains(baseTagName) == true)
|
if (_coreConfig.observatory?.subjectSelector?.Contains(baseTagName) == true)
|
||||||
{
|
{
|
||||||
v2rayConfig.observatory.subjectSelector.Remove(baseTagName);
|
_coreConfig.observatory.subjectSelector.Remove(baseTagName);
|
||||||
v2rayConfig.observatory.subjectSelector.Insert(0, baseTagName);
|
_coreConfig.observatory.subjectSelector.Insert(0, baseTagName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(0);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 3: need to create or insert based on multipleLoad type
|
// Case 3: need to create or insert based on multipleLoad type
|
||||||
if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback)
|
if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback)
|
||||||
{
|
{
|
||||||
if (v2rayConfig.burstObservatory is null)
|
if (_coreConfig.burstObservatory is null)
|
||||||
{
|
{
|
||||||
// Create new burst observatory with default ping config
|
// Create new burst observatory with default ping config
|
||||||
v2rayConfig.burstObservatory = new BurstObservatory4Ray
|
_coreConfig.burstObservatory = new BurstObservatory4Ray
|
||||||
{
|
{
|
||||||
subjectSelector = [baseTagName],
|
subjectSelector = [baseTagName],
|
||||||
pingConfig = new()
|
pingConfig = new()
|
||||||
@@ -54,16 +56,16 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
v2rayConfig.burstObservatory.subjectSelector ??= new();
|
_coreConfig.burstObservatory.subjectSelector ??= new();
|
||||||
v2rayConfig.burstObservatory.subjectSelector.Add(baseTagName);
|
_coreConfig.burstObservatory.subjectSelector.Add(baseTagName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (multipleLoad is EMultipleLoad.LeastPing)
|
else if (multipleLoad is EMultipleLoad.LeastPing)
|
||||||
{
|
{
|
||||||
if (v2rayConfig.observatory is null)
|
if (_coreConfig.observatory is null)
|
||||||
{
|
{
|
||||||
// Create new observatory with default probe config
|
// Create new observatory with default probe config
|
||||||
v2rayConfig.observatory = new Observatory4Ray
|
_coreConfig.observatory = new Observatory4Ray
|
||||||
{
|
{
|
||||||
subjectSelector = [baseTagName],
|
subjectSelector = [baseTagName],
|
||||||
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
|
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
|
||||||
@@ -73,15 +75,13 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
v2rayConfig.observatory.subjectSelector ??= new();
|
_coreConfig.observatory.subjectSelector ??= new();
|
||||||
v2rayConfig.observatory.subjectSelector.Add(baseTagName);
|
_coreConfig.observatory.subjectSelector.Add(baseTagName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(0);
|
private void GenBalancer(EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
|
|
||||||
{
|
{
|
||||||
var strategyType = multipleLoad switch
|
var strategyType = multipleLoad switch
|
||||||
{
|
{
|
||||||
@@ -105,8 +105,7 @@ public partial class CoreConfigV2rayService
|
|||||||
},
|
},
|
||||||
tag = balancerTag,
|
tag = balancerTag,
|
||||||
};
|
};
|
||||||
v2rayConfig.routing.balancers ??= new();
|
_coreConfig.routing.balancers ??= new();
|
||||||
v2rayConfig.routing.balancers.Add(balancer);
|
_coreConfig.routing.balancers.Add(balancer);
|
||||||
return await Task.FromResult(balancerTag);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,29 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigV2rayService
|
public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig)
|
private string ApplyFullConfigTemplate()
|
||||||
{
|
{
|
||||||
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
var fullConfigTemplate = context.FullConfigTemplate;
|
||||||
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
|
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(v2rayConfig);
|
return JsonUtils.Serialize(_coreConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config);
|
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config);
|
||||||
if (fullConfigTemplateNode == null)
|
if (fullConfigTemplateNode == null)
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(v2rayConfig);
|
return JsonUtils.Serialize(_coreConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle balancer and rules modifications (for multiple load scenarios)
|
// Handle balancer and rules modifications (for multiple load scenarios)
|
||||||
if (v2rayConfig.routing?.balancers?.Count > 0)
|
if (_coreConfig.routing?.balancers?.Count > 0)
|
||||||
{
|
{
|
||||||
var balancer = v2rayConfig.routing.balancers.First();
|
var balancer =
|
||||||
|
_coreConfig.routing.balancers.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null);
|
||||||
|
|
||||||
// Modify existing rules in custom config
|
// Modify existing rules in custom config
|
||||||
|
if (balancer != null)
|
||||||
|
{
|
||||||
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
|
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
|
||||||
if (rulesNode != null)
|
if (rulesNode != null)
|
||||||
{
|
{
|
||||||
@@ -34,6 +37,7 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure routing node exists
|
// Ensure routing node exists
|
||||||
if (fullConfigTemplateNode["routing"] == null)
|
if (fullConfigTemplateNode["routing"] == null)
|
||||||
@@ -44,7 +48,7 @@ public partial class CoreConfigV2rayService
|
|||||||
// Handle balancers - append instead of override
|
// Handle balancers - append instead of override
|
||||||
if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode)
|
if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode)
|
||||||
{
|
{
|
||||||
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers)
|
if (JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)) is JsonArray newBalancers)
|
||||||
{
|
{
|
||||||
foreach (var balancerNode in newBalancers)
|
foreach (var balancerNode in newBalancers)
|
||||||
{
|
{
|
||||||
@@ -54,41 +58,41 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers));
|
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v2rayConfig.observatory != null)
|
if (_coreConfig.observatory != null)
|
||||||
{
|
{
|
||||||
if (fullConfigTemplateNode["observatory"] == null)
|
if (fullConfigTemplateNode["observatory"] == null)
|
||||||
{
|
{
|
||||||
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.observatory));
|
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.observatory));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var subjectSelector = v2rayConfig.observatory.subjectSelector;
|
var subjectSelector = _coreConfig.observatory.subjectSelector;
|
||||||
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||||
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v2rayConfig.burstObservatory != null)
|
if (_coreConfig.burstObservatory != null)
|
||||||
{
|
{
|
||||||
if (fullConfigTemplateNode["burstObservatory"] == null)
|
if (fullConfigTemplateNode["burstObservatory"] == null)
|
||||||
{
|
{
|
||||||
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.burstObservatory));
|
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.burstObservatory));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var subjectSelector = v2rayConfig.burstObservatory.subjectSelector;
|
var subjectSelector = _coreConfig.burstObservatory.subjectSelector;
|
||||||
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||||
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle outbounds - append instead of override
|
var customOutboundsNode = new JsonArray();
|
||||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
|
|
||||||
foreach (var outbound in v2rayConfig.outbounds)
|
foreach (var outbound in _coreConfig.outbounds)
|
||||||
{
|
{
|
||||||
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
|
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
|
||||||
{
|
{
|
||||||
@@ -97,16 +101,32 @@ public partial class CoreConfigV2rayService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() == true) && (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) && !(Utils.IsPrivateNetwork(outbound.settings?.servers?.FirstOrDefault()?.address ?? string.Empty) || Utils.IsPrivateNetwork(outbound.settings?.vnext?.FirstOrDefault()?.address ?? string.Empty)))
|
else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()
|
||||||
|
&& (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true))
|
||||||
|
{
|
||||||
|
var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address
|
||||||
|
?? outbound.settings?.vnext?.FirstOrDefault()?.address
|
||||||
|
?? string.Empty;
|
||||||
|
if (!Utils.IsPrivateNetwork(outboundAddress))
|
||||||
{
|
{
|
||||||
outbound.streamSettings ??= new StreamSettings4Ray();
|
outbound.streamSettings ??= new StreamSettings4Ray();
|
||||||
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
|
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
|
||||||
outbound.streamSettings.sockopt.dialerProxy = fullConfigTemplate.ProxyDetour;
|
outbound.streamSettings.sockopt.dialerProxy = fullConfigTemplate.ProxyDetour;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fullConfigTemplateNode["outbounds"] is JsonArray templateOutbounds)
|
||||||
|
{
|
||||||
|
foreach (var outbound in templateOutbounds)
|
||||||
|
{
|
||||||
|
customOutboundsNode.Add(outbound?.DeepClone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||||
|
|
||||||
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
|
return JsonUtils.Serialize(fullConfigTemplateNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,70 +2,97 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigV2rayService
|
public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
private async Task<int> GenDns(ProfileItem? node, V2rayConfig v2rayConfig)
|
private void GenDns()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
var item = context.RawDnsItem;
|
||||||
if (item != null && item.Enabled == true)
|
if (item is { Enabled: true })
|
||||||
{
|
{
|
||||||
var result = await GenDnsCompatible(node, v2rayConfig);
|
GenDnsCustom();
|
||||||
|
|
||||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
if (_coreConfig.routing.domainStrategy != Global.IPIfNonMatch)
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// DNS routing
|
// DNS routing
|
||||||
v2rayConfig.dns.tag = Global.DnsTag;
|
var dnsObj = JsonUtils.SerializeToNode(_coreConfig.dns);
|
||||||
v2rayConfig.routing.rules.Add(new RulesItem4Ray
|
if (dnsObj == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsObj["tag"] = Global.DnsTag;
|
||||||
|
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(dnsObj));
|
||||||
|
_coreConfig.routing.rules.Add(new RulesItem4Ray
|
||||||
{
|
{
|
||||||
type = "field",
|
type = "field",
|
||||||
inboundTag = new List<string> { Global.DnsTag },
|
inboundTag = new List<string> { Global.DnsTag },
|
||||||
outboundTag = Global.ProxyTag,
|
outboundTag = Global.ProxyTag,
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
var simpleDnsItem = context.SimpleDnsItem;
|
||||||
|
var dnsItem = _coreConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray();
|
||||||
|
|
||||||
return result;
|
var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs;
|
||||||
}
|
|
||||||
var simpleDNSItem = _config.SimpleDNSItem;
|
|
||||||
var domainStrategy4Freedom = simpleDNSItem?.RayStrategy4Freedom;
|
|
||||||
|
|
||||||
//Outbound Freedom domainStrategy
|
//Outbound Freedom domainStrategy
|
||||||
if (domainStrategy4Freedom.IsNotEmpty())
|
if (strategy4Freedom.IsNotEmpty() && strategy4Freedom != Global.AsIs)
|
||||||
{
|
{
|
||||||
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||||
if (outbound != null)
|
if (outbound != null)
|
||||||
{
|
{
|
||||||
outbound.settings = new()
|
outbound.settings = new()
|
||||||
{
|
{
|
||||||
domainStrategy = domainStrategy4Freedom,
|
domainStrategy = strategy4Freedom,
|
||||||
userLevel = 0
|
userLevel = 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenDnsServers(node, v2rayConfig, simpleDNSItem);
|
var strategy4Proxy = simpleDnsItem?.Strategy4Proxy ?? Global.AsIs;
|
||||||
await GenDnsHosts(v2rayConfig, simpleDNSItem);
|
//Outbound Proxy domainStrategy
|
||||||
|
if (strategy4Proxy.IsNotEmpty() && strategy4Proxy != Global.AsIs)
|
||||||
|
{
|
||||||
|
var xraySupportConfigTypeNames = Global.XraySupportConfigType
|
||||||
|
.Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x])
|
||||||
|
.ToHashSet();
|
||||||
|
_coreConfig.outbounds
|
||||||
|
.Where(t => xraySupportConfigTypeNames.Contains(t.protocol))
|
||||||
|
.ToList()
|
||||||
|
.ForEach(outbound => outbound.targetStrategy = strategy4Proxy);
|
||||||
|
}
|
||||||
|
|
||||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
FillDnsServers(dnsItem);
|
||||||
|
FillDnsHosts(dnsItem);
|
||||||
|
|
||||||
|
dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null;
|
||||||
|
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
|
||||||
|
|
||||||
|
if (_coreConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
||||||
{
|
{
|
||||||
// DNS routing
|
// DNS routing
|
||||||
v2rayConfig.dns.tag = Global.DnsTag;
|
dnsItem.tag = Global.DnsTag;
|
||||||
v2rayConfig.routing.rules.Add(new RulesItem4Ray
|
_coreConfig.routing.rules.Add(new RulesItem4Ray
|
||||||
{
|
{
|
||||||
type = "field",
|
type = "field",
|
||||||
inboundTag = new List<string> { Global.DnsTag },
|
inboundTag = new List<string> { Global.DnsTag },
|
||||||
outboundTag = Global.ProxyTag,
|
outboundTag = Global.ProxyTag,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_coreConfig.dns = dnsItem;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
|
private void FillDnsServers(Dns4Ray dnsItem)
|
||||||
{
|
{
|
||||||
|
var simpleDNSItem = context.SimpleDnsItem;
|
||||||
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
|
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
|
||||||
{
|
{
|
||||||
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
|
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
|
||||||
@@ -77,11 +104,25 @@ public partial class CoreConfigV2rayService
|
|||||||
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
|
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
|
||||||
}
|
}
|
||||||
|
|
||||||
static object CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
|
static object? CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
|
||||||
{
|
{
|
||||||
|
var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress);
|
||||||
|
var domainFinal = dnsAddress;
|
||||||
|
int? portFinal = null;
|
||||||
|
if (scheme.IsNullOrEmpty() || scheme.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
domainFinal = domain;
|
||||||
|
portFinal = port > 0 ? port : null;
|
||||||
|
}
|
||||||
|
else if (scheme.StartsWith("tcp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
domainFinal = scheme + "://" + domain;
|
||||||
|
portFinal = port > 0 ? port : null;
|
||||||
|
}
|
||||||
var dnsServer = new DnsServer4Ray
|
var dnsServer = new DnsServer4Ray
|
||||||
{
|
{
|
||||||
address = dnsAddress,
|
address = domainFinal,
|
||||||
|
port = portFinal,
|
||||||
skipFallback = true,
|
skipFallback = true,
|
||||||
domains = domains.Count > 0 ? domains : null,
|
domains = domains.Count > 0 ? domains : null,
|
||||||
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
|
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
|
||||||
@@ -92,8 +133,8 @@ public partial class CoreConfigV2rayService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.FirstOrDefault());
|
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.First());
|
||||||
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.FirstOrDefault());
|
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.First());
|
||||||
|
|
||||||
var directDomainList = new List<string>();
|
var directDomainList = new List<string>();
|
||||||
var directGeositeList = new List<string>();
|
var directGeositeList = new List<string>();
|
||||||
@@ -103,7 +144,7 @@ public partial class CoreConfigV2rayService
|
|||||||
var expectedIPs = new List<string>();
|
var expectedIPs = new List<string>();
|
||||||
var regionNames = new HashSet<string>();
|
var regionNames = new HashSet<string>();
|
||||||
|
|
||||||
var bootstrapDNSAddress = ParseDnsAddresses(simpleDNSItem?.BootstrapDNS, Global.DomainPureIPDNSAddress.FirstOrDefault());
|
var bootstrapDNSAddress = ParseDnsAddresses(simpleDNSItem?.BootstrapDNS, Global.DomainPureIPDNSAddress.First());
|
||||||
var dnsServerDomains = new List<string>();
|
var dnsServerDomains = new List<string>();
|
||||||
|
|
||||||
foreach (var dns in directDNSAddress)
|
foreach (var dns in directDNSAddress)
|
||||||
@@ -155,11 +196,9 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = context.RoutingItem;
|
||||||
List<RulesItem>? rules = null;
|
List<RulesItem>? rules = null;
|
||||||
if (routing != null)
|
rules = JsonUtils.Deserialize<List<RulesItem>>(routing?.RuleSet) ?? [];
|
||||||
{
|
|
||||||
rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
|
|
||||||
foreach (var item in rules)
|
foreach (var item in rules)
|
||||||
{
|
{
|
||||||
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
|
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
|
||||||
@@ -175,12 +214,15 @@ public partial class CoreConfigV2rayService
|
|||||||
foreach (var domain in item.Domain)
|
foreach (var domain in item.Domain)
|
||||||
{
|
{
|
||||||
if (domain.StartsWith('#'))
|
if (domain.StartsWith('#'))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
|
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
|
||||||
|
|
||||||
if (item.OutboundTag == Global.DirectTag)
|
if (item.OutboundTag == Global.DirectTag)
|
||||||
{
|
{
|
||||||
if (normalizedDomain.StartsWith("geosite:"))
|
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
|
||||||
{
|
{
|
||||||
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
|
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
|
||||||
}
|
}
|
||||||
@@ -191,7 +233,7 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
else if (item.OutboundTag != Global.BlockTag)
|
else if (item.OutboundTag != Global.BlockTag)
|
||||||
{
|
{
|
||||||
if (normalizedDomain.StartsWith("geosite:"))
|
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
|
||||||
{
|
{
|
||||||
proxyGeositeList.Add(normalizedDomain);
|
proxyGeositeList.Add(normalizedDomain);
|
||||||
}
|
}
|
||||||
@@ -202,33 +244,13 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.ProtectDomainList.Count > 0)
|
||||||
|
{
|
||||||
|
directDomainList.AddRange(context.ProtectDomainList);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Utils.IsDomain(node?.Address))
|
dnsItem.servers ??= [];
|
||||||
{
|
|
||||||
directDomainList.Add(node.Address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node?.Subid is not null)
|
|
||||||
{
|
|
||||||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
|
||||||
if (subItem is not null)
|
|
||||||
{
|
|
||||||
foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile })
|
|
||||||
{
|
|
||||||
var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile);
|
|
||||||
if (profileNode is not null
|
|
||||||
&& Global.XraySupportConfigType.Contains(profileNode.ConfigType)
|
|
||||||
&& Utils.IsDomain(profileNode.Address))
|
|
||||||
{
|
|
||||||
directDomainList.Add(profileNode.Address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v2rayConfig.dns ??= new Dns4Ray();
|
|
||||||
v2rayConfig.dns.servers ??= new List<object>();
|
|
||||||
|
|
||||||
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
|
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
|
||||||
{
|
{
|
||||||
@@ -236,7 +258,7 @@ public partial class CoreConfigV2rayService
|
|||||||
{
|
{
|
||||||
foreach (var dnsAddress in dnsAddresses)
|
foreach (var dnsAddress in dnsAddresses)
|
||||||
{
|
{
|
||||||
v2rayConfig.dns.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
|
dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,22 +280,20 @@ public partial class CoreConfigV2rayService
|
|||||||
|| lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
|| lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
||||||
|
|
||||||
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
|
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
|
||||||
v2rayConfig.dns.servers.AddRange(defaultDnsServers);
|
dnsItem.servers.AddRange(defaultDnsServers);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenDnsHosts(V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
|
private void FillDnsHosts(Dns4Ray dnsItem)
|
||||||
{
|
{
|
||||||
|
var simpleDNSItem = context.SimpleDnsItem;
|
||||||
if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty())
|
if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return await Task.FromResult(0);
|
return;
|
||||||
}
|
}
|
||||||
v2rayConfig.dns ??= new Dns4Ray();
|
dnsItem.hosts ??= new Dictionary<string, object>();
|
||||||
v2rayConfig.dns.hosts ??= new Dictionary<string, object>();
|
|
||||||
if (simpleDNSItem.AddCommonHosts == true)
|
if (simpleDNSItem.AddCommonHosts == true)
|
||||||
{
|
{
|
||||||
v2rayConfig.dns.hosts = Global.PredefinedHosts.ToDictionary(
|
dnsItem.hosts = Global.PredefinedHosts.ToDictionary(
|
||||||
kvp => kvp.Key,
|
kvp => kvp.Key,
|
||||||
kvp => (object)kvp.Value
|
kvp => (object)kvp.Value
|
||||||
);
|
);
|
||||||
@@ -282,7 +302,7 @@ public partial class CoreConfigV2rayService
|
|||||||
if (simpleDNSItem.UseSystemHosts == true)
|
if (simpleDNSItem.UseSystemHosts == true)
|
||||||
{
|
{
|
||||||
var systemHosts = Utils.GetSystemHosts();
|
var systemHosts = Utils.GetSystemHosts();
|
||||||
var normalHost = v2rayConfig?.dns?.hosts;
|
var normalHost = dnsItem.hosts;
|
||||||
|
|
||||||
if (normalHost != null && systemHosts?.Count > 0)
|
if (normalHost != null && systemHosts?.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -293,23 +313,17 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts))
|
||||||
{
|
{
|
||||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
dnsItem.hosts[kvp.Key] = kvp.Value;
|
||||||
|
|
||||||
foreach (var kvp in userHostsMap)
|
|
||||||
{
|
|
||||||
v2rayConfig.dns.hosts[kvp.Key] = kvp.Value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<int> GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig)
|
private void GenDnsCustom()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
var item = context.RawDnsItem;
|
||||||
var normalDNS = item?.NormalDNS;
|
var normalDNS = item?.NormalDNS;
|
||||||
var domainStrategy4Freedom = item?.DomainStrategy4Freedom;
|
var domainStrategy4Freedom = item?.DomainStrategy4Freedom;
|
||||||
if (normalDNS.IsNullOrEmpty())
|
if (normalDNS.IsNullOrEmpty())
|
||||||
@@ -320,7 +334,7 @@ public partial class CoreConfigV2rayService
|
|||||||
//Outbound Freedom domainStrategy
|
//Outbound Freedom domainStrategy
|
||||||
if (domainStrategy4Freedom.IsNotEmpty())
|
if (domainStrategy4Freedom.IsNotEmpty())
|
||||||
{
|
{
|
||||||
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||||
if (outbound != null)
|
if (outbound != null)
|
||||||
{
|
{
|
||||||
outbound.settings = new();
|
outbound.settings = new();
|
||||||
@@ -333,8 +347,8 @@ public partial class CoreConfigV2rayService
|
|||||||
if (obj is null)
|
if (obj is null)
|
||||||
{
|
{
|
||||||
List<string> servers = [];
|
List<string> servers = [];
|
||||||
string[] arrDNS = normalDNS.Split(',');
|
var arrDNS = normalDNS.Split(',');
|
||||||
foreach (string str in arrDNS)
|
foreach (var str in arrDNS)
|
||||||
{
|
{
|
||||||
servers.Add(str);
|
servers.Add(str);
|
||||||
}
|
}
|
||||||
@@ -354,7 +368,10 @@ public partial class CoreConfigV2rayService
|
|||||||
foreach (var host in systemHosts)
|
foreach (var host in systemHosts)
|
||||||
{
|
{
|
||||||
if (normalHost1[host.Key] != null)
|
if (normalHost1[host.Key] != null)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
normalHost1[host.Key] = host.Value;
|
normalHost1[host.Key] = host.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,63 +389,37 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await GenDnsDomainsCompatible(node, obj, item);
|
FillDnsDomainsCustom(obj);
|
||||||
|
|
||||||
v2rayConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
|
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dnsItem)
|
private void FillDnsDomainsCustom(JsonNode dns)
|
||||||
{
|
{
|
||||||
if (node == null)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
var servers = dns["servers"];
|
var servers = dns["servers"];
|
||||||
if (servers != null)
|
if (servers == null)
|
||||||
{
|
{
|
||||||
var domainList = new List<string>();
|
return;
|
||||||
if (Utils.IsDomain(node.Address))
|
|
||||||
{
|
|
||||||
domainList.Add(node.Address);
|
|
||||||
}
|
|
||||||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
|
||||||
if (subItem is not null)
|
|
||||||
{
|
|
||||||
// Previous proxy
|
|
||||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
|
||||||
if (prevNode is not null
|
|
||||||
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)
|
|
||||||
&& Utils.IsDomain(prevNode.Address))
|
|
||||||
{
|
|
||||||
domainList.Add(prevNode.Address);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next proxy
|
var domainList = context.ProtectDomainList;
|
||||||
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
if (domainList.Count <= 0)
|
||||||
if (nextNode is not null
|
|
||||||
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)
|
|
||||||
&& Utils.IsDomain(nextNode.Address))
|
|
||||||
{
|
{
|
||||||
domainList.Add(nextNode.Address);
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (domainList.Count > 0)
|
var dnsItem = context.RawDnsItem;
|
||||||
{
|
|
||||||
var dnsServer = new DnsServer4Ray()
|
var dnsServer = new DnsServer4Ray()
|
||||||
{
|
{
|
||||||
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
|
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
|
||||||
skipFallback = true,
|
skipFallback = true,
|
||||||
domains = domainList
|
domains = domainList.ToList(),
|
||||||
};
|
};
|
||||||
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,35 +2,35 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigV2rayService
|
public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
private async Task<int> GenInbounds(V2rayConfig v2rayConfig)
|
private void GenInbounds()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var listen = "0.0.0.0";
|
var listen = "0.0.0.0";
|
||||||
v2rayConfig.inbounds = [];
|
_coreConfig.inbounds = [];
|
||||||
|
|
||||||
var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
|
var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
|
||||||
v2rayConfig.inbounds.Add(inbound);
|
_coreConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
if (_config.Inbound.First().SecondLocalPortEnabled)
|
if (_config.Inbound.First().SecondLocalPortEnabled)
|
||||||
{
|
{
|
||||||
var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
|
var inbound2 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
|
||||||
v2rayConfig.inbounds.Add(inbound2);
|
_coreConfig.inbounds.Add(inbound2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_config.Inbound.First().AllowLANConn)
|
if (_config.Inbound.First().AllowLANConn)
|
||||||
{
|
{
|
||||||
if (_config.Inbound.First().NewPort4LAN)
|
if (_config.Inbound.First().NewPort4LAN)
|
||||||
{
|
{
|
||||||
var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
|
var inbound3 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
|
||||||
inbound3.listen = listen;
|
inbound3.listen = listen;
|
||||||
v2rayConfig.inbounds.Add(inbound3);
|
_coreConfig.inbounds.Add(inbound3);
|
||||||
|
|
||||||
//auth
|
//auth
|
||||||
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
||||||
{
|
{
|
||||||
inbound3.settings.auth = "password";
|
inbound3.settings.auth = "password";
|
||||||
inbound3.settings.accounts = new List<AccountsItem4Ray> { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } };
|
inbound3.settings.accounts = new List<AccountsItem4Ray> { new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -43,12 +43,11 @@ public partial class CoreConfigV2rayService
|
|||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
|
private Inbounds4Ray BuildInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
|
||||||
{
|
{
|
||||||
string result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
|
var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return new();
|
return new();
|
||||||
|
|||||||
@@ -2,28 +2,27 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigV2rayService
|
public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
private async Task<int> GenLog(V2rayConfig v2rayConfig)
|
private void GenLog()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_config.CoreBasicItem.LogEnabled)
|
if (_config.CoreBasicItem.LogEnabled)
|
||||||
{
|
{
|
||||||
var dtNow = DateTime.Now;
|
var dtNow = DateTime.Now;
|
||||||
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
_coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||||
v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
|
_coreConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
|
||||||
v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
|
_coreConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
_coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||||
v2rayConfig.log.access = null;
|
_coreConfig.log.access = null;
|
||||||
v2rayConfig.log.error = null;
|
_coreConfig.log.error = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,20 +2,20 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigV2rayService
|
public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
private async Task<int> GenRouting(V2rayConfig v2rayConfig)
|
private void GenRouting()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (v2rayConfig.routing?.rules != null)
|
if (_coreConfig.routing?.rules != null)
|
||||||
{
|
{
|
||||||
v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
|
_coreConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
|
||||||
|
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = context.RoutingItem;
|
||||||
if (routing != null)
|
if (routing != null)
|
||||||
{
|
{
|
||||||
if (routing.DomainStrategy.IsNotEmpty())
|
if (routing.DomainStrategy.IsNotEmpty())
|
||||||
{
|
{
|
||||||
v2rayConfig.routing.domainStrategy = routing.DomainStrategy;
|
_coreConfig.routing.domainStrategy = routing.DomainStrategy;
|
||||||
}
|
}
|
||||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||||
foreach (var item in rules)
|
foreach (var item in rules)
|
||||||
@@ -31,7 +31,18 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
|
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
|
||||||
await GenRoutingUserRule(item2, v2rayConfig);
|
GenRoutingUserRule(item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var balancerTagList = _coreConfig.routing.balancers
|
||||||
|
?.Select(p => p.tag)
|
||||||
|
.ToList() ?? [];
|
||||||
|
if (balancerTagList.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var rulesItem in _coreConfig.routing.rules.Where(r => balancerTagList.Contains(r.outboundTag + Global.BalancerTagSuffix)))
|
||||||
|
{
|
||||||
|
rulesItem.balancerTag = rulesItem.outboundTag + Global.BalancerTagSuffix;
|
||||||
|
rulesItem.outboundTag = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,80 +51,94 @@ public partial class CoreConfigV2rayService
|
|||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig)
|
private void GenRoutingUserRule(RulesItem4Ray? userRule)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (rule == null)
|
if (userRule == null)
|
||||||
{
|
{
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig);
|
userRule.outboundTag = GenRoutingUserRuleOutbound(userRule.outboundTag ?? Global.ProxyTag);
|
||||||
|
|
||||||
if (rule.port.IsNullOrEmpty())
|
if (userRule.port.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
rule.port = null;
|
userRule.port = null;
|
||||||
}
|
}
|
||||||
if (rule.network.IsNullOrEmpty())
|
if (userRule.network.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
rule.network = null;
|
userRule.network = null;
|
||||||
}
|
}
|
||||||
if (rule.domain?.Count == 0)
|
if (userRule.domain?.Count == 0)
|
||||||
{
|
{
|
||||||
rule.domain = null;
|
userRule.domain = null;
|
||||||
}
|
}
|
||||||
if (rule.ip?.Count == 0)
|
if (userRule.ip?.Count == 0)
|
||||||
{
|
{
|
||||||
rule.ip = null;
|
userRule.ip = null;
|
||||||
}
|
}
|
||||||
if (rule.protocol?.Count == 0)
|
if (userRule.protocol?.Count == 0)
|
||||||
{
|
{
|
||||||
rule.protocol = null;
|
userRule.protocol = null;
|
||||||
}
|
}
|
||||||
if (rule.inboundTag?.Count == 0)
|
if (userRule.inboundTag?.Count == 0)
|
||||||
{
|
{
|
||||||
rule.inboundTag = null;
|
userRule.inboundTag = null;
|
||||||
|
}
|
||||||
|
if (userRule.process?.Count == 0)
|
||||||
|
{
|
||||||
|
userRule.process = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasDomainIp = false;
|
var hasDomainIp = false;
|
||||||
if (rule.domain?.Count > 0)
|
if (userRule.domain?.Count > 0)
|
||||||
{
|
{
|
||||||
var it = JsonUtils.DeepCopy(rule);
|
var it = JsonUtils.DeepCopy(userRule);
|
||||||
it.ip = null;
|
it.ip = null;
|
||||||
|
it.process = null;
|
||||||
it.type = "field";
|
it.type = "field";
|
||||||
for (var k = it.domain.Count - 1; k >= 0; k--)
|
for (var k = it.domain.Count - 1; k >= 0; k--)
|
||||||
{
|
{
|
||||||
if (it.domain[k].StartsWith("#"))
|
if (it.domain[k].StartsWith('#'))
|
||||||
{
|
{
|
||||||
it.domain.RemoveAt(k);
|
it.domain.RemoveAt(k);
|
||||||
}
|
}
|
||||||
it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ",");
|
it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ",");
|
||||||
}
|
}
|
||||||
v2rayConfig.routing.rules.Add(it);
|
_coreConfig.routing.rules.Add(it);
|
||||||
hasDomainIp = true;
|
hasDomainIp = true;
|
||||||
}
|
}
|
||||||
if (rule.ip?.Count > 0)
|
if (userRule.ip?.Count > 0)
|
||||||
{
|
{
|
||||||
var it = JsonUtils.DeepCopy(rule);
|
var it = JsonUtils.DeepCopy(userRule);
|
||||||
it.domain = null;
|
it.domain = null;
|
||||||
|
it.process = null;
|
||||||
it.type = "field";
|
it.type = "field";
|
||||||
v2rayConfig.routing.rules.Add(it);
|
_coreConfig.routing.rules.Add(it);
|
||||||
|
hasDomainIp = true;
|
||||||
|
}
|
||||||
|
if (userRule.process?.Count > 0)
|
||||||
|
{
|
||||||
|
var it = JsonUtils.DeepCopy(userRule);
|
||||||
|
it.domain = null;
|
||||||
|
it.ip = null;
|
||||||
|
it.type = "field";
|
||||||
|
_coreConfig.routing.rules.Add(it);
|
||||||
hasDomainIp = true;
|
hasDomainIp = true;
|
||||||
}
|
}
|
||||||
if (!hasDomainIp)
|
if (!hasDomainIp)
|
||||||
{
|
{
|
||||||
if (rule.port.IsNotEmpty()
|
if (userRule.port.IsNotEmpty()
|
||||||
|| rule.protocol?.Count > 0
|
|| userRule.protocol?.Count > 0
|
||||||
|| rule.inboundTag?.Count > 0
|
|| userRule.inboundTag?.Count > 0
|
||||||
|| rule.network != null
|
|| userRule.network != null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var it = JsonUtils.DeepCopy(rule);
|
var it = JsonUtils.DeepCopy(userRule);
|
||||||
it.type = "field";
|
it.type = "field";
|
||||||
v2rayConfig.routing.rules.Add(it);
|
_coreConfig.routing.rules.Add(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,17 +146,16 @@ public partial class CoreConfigV2rayService
|
|||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig)
|
private string GenRoutingUserRuleOutbound(string outboundTag)
|
||||||
{
|
{
|
||||||
if (Global.OutboundTags.Contains(outboundTag))
|
if (Global.OutboundTags.Contains(outboundTag))
|
||||||
{
|
{
|
||||||
return outboundTag;
|
return outboundTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
|
||||||
|
|
||||||
if (node == null
|
if (node == null
|
||||||
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
|
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
|
||||||
@@ -141,27 +165,20 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tag = $"{node.IndexId}-{Global.ProxyTag}";
|
var tag = $"{node.IndexId}-{Global.ProxyTag}";
|
||||||
if (v2rayConfig.outbounds.Any(p => p.tag == tag))
|
if (_coreConfig.outbounds.Any(p => p.tag.StartsWith(tag)))
|
||||||
{
|
{
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.ConfigType.IsGroupType())
|
var proxyOutbounds = new CoreConfigV2rayService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
|
||||||
{
|
_coreConfig.outbounds.AddRange(proxyOutbounds);
|
||||||
var ret = await GenGroupOutbound(node, v2rayConfig, tag);
|
if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1)
|
||||||
if (ret == 0)
|
|
||||||
{
|
{
|
||||||
|
var multipleLoad = node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
|
||||||
|
GenObservatory(multipleLoad, tag);
|
||||||
|
GenBalancer(multipleLoad, tag);
|
||||||
|
}
|
||||||
|
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
return Global.ProxyTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
|
||||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
|
||||||
await GenOutbound(node, outbound);
|
|
||||||
outbound.tag = tag;
|
|
||||||
v2rayConfig.outbounds.Add(outbound);
|
|
||||||
|
|
||||||
return outbound.tag;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,26 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigV2rayService
|
public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
private async Task<int> GenStatistic(V2rayConfig v2rayConfig)
|
private void GenStatistic()
|
||||||
{
|
{
|
||||||
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
||||||
{
|
{
|
||||||
string tag = EInboundProtocol.api.ToString();
|
var tag = EInboundProtocol.api.ToString();
|
||||||
Metrics4Ray apiObj = new();
|
Metrics4Ray apiObj = new();
|
||||||
Policy4Ray policyObj = new();
|
Policy4Ray policyObj = new();
|
||||||
SystemPolicy4Ray policySystemSetting = new();
|
SystemPolicy4Ray policySystemSetting = new();
|
||||||
|
|
||||||
v2rayConfig.stats = new Stats4Ray();
|
_coreConfig.stats = new Stats4Ray();
|
||||||
|
|
||||||
apiObj.tag = tag;
|
apiObj.tag = tag;
|
||||||
v2rayConfig.metrics = apiObj;
|
_coreConfig.metrics = apiObj;
|
||||||
|
|
||||||
policySystemSetting.statsOutboundDownlink = true;
|
policySystemSetting.statsOutboundDownlink = true;
|
||||||
policySystemSetting.statsOutboundUplink = true;
|
policySystemSetting.statsOutboundUplink = true;
|
||||||
policyObj.system = policySystemSetting;
|
policyObj.system = policySystemSetting;
|
||||||
v2rayConfig.policy = policyObj;
|
_coreConfig.policy = policyObj;
|
||||||
|
|
||||||
if (!v2rayConfig.inbounds.Exists(item => item.tag == tag))
|
if (!_coreConfig.inbounds.Exists(item => item.tag == tag))
|
||||||
{
|
{
|
||||||
Inbounds4Ray apiInbound = new();
|
Inbounds4Ray apiInbound = new();
|
||||||
Inboundsettings4Ray apiInboundSettings = new();
|
Inboundsettings4Ray apiInboundSettings = new();
|
||||||
@@ -31,10 +31,10 @@ public partial class CoreConfigV2rayService
|
|||||||
apiInbound.protocol = Global.InboundAPIProtocol;
|
apiInbound.protocol = Global.InboundAPIProtocol;
|
||||||
apiInboundSettings.address = Global.Loopback;
|
apiInboundSettings.address = Global.Loopback;
|
||||||
apiInbound.settings = apiInboundSettings;
|
apiInbound.settings = apiInboundSettings;
|
||||||
v2rayConfig.inbounds.Add(apiInbound);
|
_coreConfig.inbounds.Add(apiInbound);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag))
|
if (!_coreConfig.routing.rules.Exists(item => item.outboundTag == tag))
|
||||||
{
|
{
|
||||||
RulesItem4Ray apiRoutingRule = new()
|
RulesItem4Ray apiRoutingRule = new()
|
||||||
{
|
{
|
||||||
@@ -43,9 +43,8 @@ public partial class CoreConfigV2rayService
|
|||||||
type = "field"
|
type = "field"
|
||||||
};
|
};
|
||||||
|
|
||||||
v2rayConfig.routing.rules.Add(apiRoutingRule);
|
_coreConfig.routing.rules.Add(apiRoutingRule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await Task.FromResult(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace ServiceLib.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DownloadService
|
public class DownloadService
|
||||||
{
|
{
|
||||||
public event EventHandler<RetResult>? UpdateCompleted;
|
public event EventHandler<UpdateResult>? UpdateCompleted;
|
||||||
|
|
||||||
public event ErrorEventHandler? Error;
|
public event ErrorEventHandler? Error;
|
||||||
|
|
||||||
@@ -40,10 +40,10 @@ public class DownloadService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}"));
|
UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} {url}"));
|
||||||
|
|
||||||
var progress = new Progress<double>();
|
var progress = new Progress<double>();
|
||||||
progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new RetResult(value > 100, $"...{value}%"));
|
progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new UpdateResult(value > 100, $"...{value}%"));
|
||||||
|
|
||||||
var webProxy = await GetWebProxy(blProxy);
|
var webProxy = await GetWebProxy(blProxy);
|
||||||
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,
|
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,
|
||||||
@@ -71,7 +71,7 @@ public class DownloadService
|
|||||||
AllowAutoRedirect = false,
|
AllowAutoRedirect = false,
|
||||||
Proxy = await GetWebProxy(blProxy)
|
Proxy = await GetWebProxy(blProxy)
|
||||||
};
|
};
|
||||||
HttpClient client = new(webRequestHandler);
|
var client = new HttpClient(webRequestHandler);
|
||||||
|
|
||||||
var response = await client.GetAsync(url);
|
var response = await client.GetAsync(url);
|
||||||
if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null)
|
if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null)
|
||||||
@@ -156,7 +156,7 @@ public class DownloadService
|
|||||||
}
|
}
|
||||||
|
|
||||||
using var cts = new CancellationTokenSource();
|
using var cts = new CancellationTokenSource();
|
||||||
var result = await HttpClientHelper.Instance.GetAsync(client, url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
|
var result = await client.GetStringAsync(url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -19,14 +19,19 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
|
|
||||||
public void ExitLoop()
|
public void ExitLoop()
|
||||||
{
|
{
|
||||||
if (_lstExitLoop.Count > 0)
|
if (!_lstExitLoop.IsEmpty)
|
||||||
{
|
{
|
||||||
UpdateFunc("", ResUI.SpeedtestingStop);
|
_ = UpdateFunc("", ResUI.SpeedtestingStop);
|
||||||
|
|
||||||
_lstExitLoop.Clear();
|
_lstExitLoop.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShouldStopTest(string exitLoopKey)
|
||||||
|
{
|
||||||
|
return !_lstExitLoop.Any(p => p == exitLoopKey);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RunAsync(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
private async Task RunAsync(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
||||||
{
|
{
|
||||||
var exitLoopKey = Utils.GetGuid(false);
|
var exitLoopKey = Utils.GetGuid(false);
|
||||||
@@ -103,6 +108,11 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lstSelected.Count > 1 && (actionType == ESpeedActionType.Speedtest || actionType == ESpeedActionType.Mixedtest))
|
||||||
|
{
|
||||||
|
NoticeManager.Instance.Enqueue(ResUI.SpeedtestingPressEscToExit);
|
||||||
|
}
|
||||||
|
|
||||||
return lstSelected;
|
return lstSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +162,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
var pageSizeNext = pageSize / 2;
|
var pageSizeNext = pageSize / 2;
|
||||||
if (lstFailed.Count > 0 && pageSizeNext > 0)
|
if (lstFailed.Count > 0 && pageSizeNext > 0)
|
||||||
{
|
{
|
||||||
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
if (ShouldStopTest(exitLoopKey))
|
||||||
{
|
{
|
||||||
await UpdateFunc("", ResUI.SpeedtestingSkip);
|
await UpdateFunc("", ResUI.SpeedtestingSkip);
|
||||||
return;
|
return;
|
||||||
@@ -188,8 +198,15 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
{
|
{
|
||||||
if (!it.AllowTest)
|
if (!it.AllowTest)
|
||||||
{
|
{
|
||||||
|
await UpdateFunc(it.IndexId, ResUI.SpeedtestingSkip);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ShouldStopTest(exitLoopKey))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
tasks.Add(Task.Run(async () =>
|
tasks.Add(Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await DoRealPing(it);
|
await DoRealPing(it);
|
||||||
@@ -218,7 +235,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
List<Task> tasks = new();
|
List<Task> tasks = new();
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
{
|
{
|
||||||
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
|
if (ShouldStopTest(exitLoopKey))
|
||||||
{
|
{
|
||||||
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
||||||
continue;
|
continue;
|
||||||
@@ -234,13 +251,20 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
if (processService is null)
|
if (processService is null)
|
||||||
{
|
{
|
||||||
await UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
|
await UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
var delay = await DoRealPing(it);
|
var delay = await DoRealPing(it);
|
||||||
if (blSpeedTest)
|
if (blSpeedTest)
|
||||||
{
|
{
|
||||||
|
if (ShouldStopTest(exitLoopKey))
|
||||||
|
{
|
||||||
|
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (delay > 0)
|
if (delay > 0)
|
||||||
{
|
{
|
||||||
await DoSpeedTest(downloadHandle, it);
|
await DoSpeedTest(downloadHandle, it);
|
||||||
@@ -251,7 +275,6 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
@@ -272,7 +295,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
private async Task<int> DoRealPing(ServerTestItem it)
|
private async Task<int> DoRealPing(ServerTestItem it)
|
||||||
{
|
{
|
||||||
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
||||||
var responseTime = await HttpClientHelper.Instance.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
var responseTime = await ConnectionHandler.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
|
||||||
|
|
||||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||||
await UpdateFunc(it.IndexId, responseTime.ToString());
|
await UpdateFunc(it.IndexId, responseTime.ToString());
|
||||||
@@ -301,8 +324,6 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
{
|
{
|
||||||
var responseTime = -1;
|
var responseTime = -1;
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!IPAddress.TryParse(url, out var ipAddress))
|
if (!IPAddress.TryParse(url, out var ipAddress))
|
||||||
{
|
{
|
||||||
var ipHostInfo = await Dns.GetHostEntryAsync(url);
|
var ipHostInfo = await Dns.GetHostEntryAsync(url);
|
||||||
@@ -313,19 +334,18 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
|
||||||
var timer = Stopwatch.StartNew();
|
var timer = Stopwatch.StartNew();
|
||||||
var result = clientSocket.BeginConnect(endPoint, null, null);
|
try
|
||||||
if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)))
|
|
||||||
{
|
{
|
||||||
throw new TimeoutException("connect timeout (5s): " + url);
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||||
|
await clientSocket.ConnectAsync(endPoint, cts.Token).ConfigureAwait(false);
|
||||||
|
responseTime = (int)timer.ElapsedMilliseconds;
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
responseTime = (int)timer.Elapsed.TotalMilliseconds;
|
|
||||||
|
|
||||||
clientSocket.EndConnect(result);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
}
|
||||||
return responseTime;
|
return responseTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class StatisticsSingboxService
|
|||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_config.IsRunningCore(ECoreType.sing_box))
|
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class StatisticsXrayService
|
|||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_config.RunningCoreType != ECoreType.Xray)
|
if (AppManager.Instance.RunningCoreType != ECoreType.Xray)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user