mirror of
https://github.com/2dust/v2rayN.git
synced 2026-03-23 00:41:27 +05:00
Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06cec89ec9 | ||
|
|
26f65dd3b2 | ||
|
|
0c2114d2e1 | ||
|
|
4af528f8e2 | ||
|
|
588e82f0d9 | ||
|
|
0c13488410 | ||
|
|
a88396c11d | ||
|
|
ef5fee9975 | ||
|
|
df800a60c2 | ||
|
|
679bd8afcc | ||
|
|
66e1aeae1f | ||
|
|
e03c22092f | ||
|
|
c0aa829abb | ||
|
|
b8f7cc0768 | ||
|
|
81da72bb39 | ||
|
|
d9201157c8 | ||
|
|
e179d5bc42 | ||
|
|
4d2f32099e | ||
|
|
f24a79aa2c | ||
|
|
9a3604e89b | ||
|
|
fd7cf0d453 | ||
|
|
65cf782eb0 | ||
|
|
bfa9eaa5ec | ||
|
|
cea725ae3d | ||
|
|
c9df9a0001 | ||
|
|
56f1794e47 | ||
|
|
a71ebbd01c | ||
|
|
9f6237fb21 | ||
|
|
99d67ca3f1 | ||
|
|
d56e896f07 | ||
|
|
6b4ae5a386 | ||
|
|
a3ff31088e | ||
|
|
584e538623 | ||
|
|
67c4ae02ba | ||
|
|
ed1275e29f | ||
|
|
0cf07e925f | ||
|
|
49e487886d | ||
|
|
ad07f281c7 | ||
|
|
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 |
75
.github/workflows/build-linux.yml
vendored
75
.github/workflows/build-linux.yml
vendored
@@ -31,13 +31,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6.0.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.0.0
|
||||
uses: actions/setup-dotnet@v5.2.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: v2rayN-linux
|
||||
path: |
|
||||
@@ -97,26 +97,79 @@ jobs:
|
||||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: quay.io/almalinuxorg/10-base:latest
|
||||
options: --platform=linux/amd64/v2
|
||||
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: |
|
||||
dnf -y makecache
|
||||
dnf -y install epel-release
|
||||
dnf -y install sudo git rpm-build rpmdevtools dnf-plugins-core rsync findutils tar gzip unzip which
|
||||
set -euo pipefail
|
||||
|
||||
. /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.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Restore build artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: v2rayN-linux
|
||||
path: ${{ github.workspace }}/v2rayN/Release
|
||||
@@ -137,7 +190,7 @@ jobs:
|
||||
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
|
||||
|
||||
- name: Upload RPM artifacts
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: v2rayN-rpm
|
||||
path: dist/rpm/**/*.rpm
|
||||
|
||||
6
.github/workflows/build-osx.yml
vendored
6
.github/workflows/build-osx.yml
vendored
@@ -26,13 +26,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6.0.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Setup
|
||||
uses: actions/setup-dotnet@v5.0.0
|
||||
uses: actions/setup-dotnet@v5.2.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: v2rayN-macos
|
||||
path: |
|
||||
|
||||
6
.github/workflows/build-windows-desktop.yml
vendored
6
.github/workflows/build-windows-desktop.yml
vendored
@@ -26,13 +26,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6.0.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Setup
|
||||
uses: actions/setup-dotnet@v5.0.0
|
||||
uses: actions/setup-dotnet@v5.2.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
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
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: v2rayN-windows-desktop
|
||||
path: |
|
||||
|
||||
19
.github/workflows/build-windows.yml
vendored
19
.github/workflows/build-windows.yml
vendored
@@ -15,7 +15,6 @@ env:
|
||||
OutputArchArm: "windows-arm64"
|
||||
OutputPath64: "${{ github.workspace }}/v2rayN/Release/windows-64"
|
||||
OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/windows-arm64"
|
||||
OutputPath64Sc: "${{ github.workspace }}/v2rayN/Release/windows-64-SelfContained"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -27,26 +26,23 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6.0.0
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Setup
|
||||
uses: actions/setup-dotnet@v5.0.0
|
||||
uses: actions/setup-dotnet@v5.2.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cd v2rayN
|
||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc
|
||||
|
||||
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 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||
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 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v5.0.0
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: v2rayN-windows
|
||||
path: |
|
||||
@@ -59,7 +55,6 @@ jobs:
|
||||
chmod 755 package-release-zip.sh
|
||||
./package-release-zip.sh $OutputArch $OutputPath64
|
||||
./package-release-zip.sh $OutputArchArm $OutputPathArm64
|
||||
./package-release-zip.sh "windows-64-SelfContained" $OutputPath64Sc
|
||||
|
||||
- name: Upload zip archive to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
|
||||
@@ -16,7 +16,7 @@ cp -rf $OutputPath "${PackagePath}/opt/v2rayN"
|
||||
echo "When this file exists, app will not store configs under this folder" > "${PackagePath}/opt/v2rayN/NotStoreConfigHere.txt"
|
||||
|
||||
if [ $Arch = "linux-64" ]; then
|
||||
Arch2="amd64"
|
||||
Arch2="amd64"
|
||||
else
|
||||
Arch2="arm64"
|
||||
fi
|
||||
@@ -32,9 +32,8 @@ Depends: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26),
|
||||
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||
EOF
|
||||
|
||||
cat >"${PackagePath}/DEBIAN/postinst" <<-EOF
|
||||
if [ ! -s /usr/share/applications/v2rayN.desktop ]; then
|
||||
cat >/usr/share/applications/v2rayN.desktop<<-END
|
||||
mkdir -p "${PackagePath}/usr/share/applications"
|
||||
cat >"${PackagePath}/usr/share/applications/v2rayN.desktop" <<-EOF
|
||||
[Desktop Entry]
|
||||
Name=v2rayN
|
||||
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||
@@ -43,10 +42,12 @@ Icon=/opt/v2rayN/v2rayN.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Network;Application;
|
||||
END
|
||||
fi
|
||||
EOF
|
||||
|
||||
update-desktop-database
|
||||
cat >"${PackagePath}/DEBIAN/postinst" <<-'EOF'
|
||||
set -e
|
||||
update-desktop-database || true
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
sudo chmod 0755 "${PackagePath}/DEBIAN/postinst"
|
||||
|
||||
488
package-rhel.sh
488
package-rhel.sh
@@ -1,45 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
|
||||
if [[ -r /etc/os-release ]]; then
|
||||
. /etc/os-release
|
||||
case "$ID" in
|
||||
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
|
||||
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
||||
;;
|
||||
*)
|
||||
echo "[ERROR] Unsupported system: $NAME ($ID)."
|
||||
echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS or Ubuntu/Debian."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "[ERROR] Cannot detect system (missing /etc/os-release)."
|
||||
exit 1
|
||||
# Require Red Hat base branch
|
||||
. /etc/os-release
|
||||
|
||||
case "${ID:-}" in
|
||||
rhel|rocky|almalinux|fedora|centos)
|
||||
echo "Detected supported system: ${NAME:-$ID} ${VERSION_ID:-}"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported system: ${NAME:-unknown} (${ID:-unknown})."
|
||||
echo "This script only supports: RHEL / Rocky / AlmaLinux / Fedora / CentOS."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Kernel version
|
||||
MIN_KERNEL="6.11"
|
||||
CURRENT_KERNEL="$(uname -r)"
|
||||
|
||||
lowest="$(printf '%s\n%s\n' "$MIN_KERNEL" "$CURRENT_KERNEL" | sort -V | head -n1)"
|
||||
|
||||
if [[ "$lowest" != "$MIN_KERNEL" ]]; then
|
||||
echo "Kernel $CURRENT_KERNEL is below $MIN_KERNEL"
|
||||
exit 1
|
||||
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 "[OK] Kernel $CURRENT_KERNEL verified."
|
||||
|
||||
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+, Debian 13+)."
|
||||
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
|
||||
WITH_CORE="both" # Default: bundle both xray+sing-box
|
||||
AUTOSTART=0 # 1 = enable system-wide autostart (/etc/xdg/autostart)
|
||||
FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads
|
||||
ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target)
|
||||
BUILD_FROM="" # --buildfrom 1|2|3 to select channel non-interactively
|
||||
@@ -55,7 +46,6 @@ if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--with-core) WITH_CORE="${2:-both}"; shift 2;;
|
||||
--autostart) AUTOSTART=1; shift;;
|
||||
--xray-ver) XRAY_VER="${2:-}"; shift 2;;
|
||||
--singbox-ver) SING_VER="${2:-}"; shift 2;;
|
||||
--netcore) FORCE_NETCORE=1; shift;;
|
||||
@@ -69,92 +59,28 @@ done
|
||||
|
||||
# Conflict: version number AND --buildfrom cannot be used together
|
||||
if [[ -n "${VERSION_ARG:-}" && -n "${BUILD_FROM:-}" ]]; then
|
||||
echo "[ERROR] You cannot specify both an explicit version and --buildfrom at the same time."
|
||||
echo "You cannot specify both an explicit version and --buildfrom at the same time."
|
||||
echo " Provide either a version (e.g. 7.14.0) OR --buildfrom 1|2|3."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ===== Environment check + Dependencies ========================================
|
||||
# Check and install dependencies
|
||||
host_arch="$(uname -m)"
|
||||
[[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; }
|
||||
|
||||
install_ok=0
|
||||
case "$ID" in
|
||||
# ------------------------------ RHEL family (UNCHANGED) ------------------------------
|
||||
rhel|rocky|almalinux|centos)
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
||||
sudo dnf -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
|
||||
install_ok=1
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
sudo yum -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
||||
sudo yum -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
|
||||
install_ok=1
|
||||
fi
|
||||
;;
|
||||
# ------------------------------ Ubuntu ----------------------------------------------
|
||||
ubuntu)
|
||||
sudo apt-get update
|
||||
# Ensure 'universe' (Ubuntu) to get 'rpm'
|
||||
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
|
||||
sudo apt-get -y install software-properties-common || true
|
||||
sudo add-apt-repository -y universe || true
|
||||
sudo apt-get update
|
||||
fi
|
||||
# Base tools + rpm (provides rpmbuild)
|
||||
sudo apt-get -y install curl unzip tar rsync rpm || true
|
||||
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
|
||||
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
|
||||
# rpmbuild presence check
|
||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
||||
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
|
||||
exit 1
|
||||
fi
|
||||
# .NET SDK 8 (best effort via apt)
|
||||
if ! command -v dotnet >/dev/null 2>&1; then
|
||||
sudo apt-get -y install dotnet-sdk-8.0 || true
|
||||
sudo apt-get -y install dotnet-sdk-8 || true
|
||||
sudo apt-get -y install dotnet-sdk || true
|
||||
fi
|
||||
install_ok=1
|
||||
;;
|
||||
# ------------------------------ Debian (KEEP, with local dotnet install) ------------
|
||||
debian)
|
||||
sudo apt-get update
|
||||
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip
|
||||
sudo apt-get -y install curl unzip tar rsync rpm binutils || true
|
||||
# rpmbuild presence check
|
||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
||||
echo " Please ensure 'rpm' is available from Debian repos."
|
||||
exit 1
|
||||
fi
|
||||
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet
|
||||
if ! command -v dotnet >/dev/null 2>&1; then
|
||||
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..."
|
||||
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
|
||||
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh"
|
||||
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet"
|
||||
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
|
||||
export DOTNET_ROOT="$HOME/.dotnet"
|
||||
if ! command -v dotnet >/dev/null 2>&1; then
|
||||
echo "[ERROR] dotnet installation failed."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
install_ok=1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$install_ok" -ne 1 ]]; then
|
||||
echo "[WARN] Could not auto-install dependencies for '$ID'. Make sure these are available:"
|
||||
echo " dotnet-sdk 8.x, curl, unzip, tar, rsync, rpm, rpmdevtools, rpm-build (on RPM-based distros)"
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
sudo dnf -y install rpm-build rpmdevtools curl unzip tar jq rsync dotnet-sdk-8.0 \
|
||||
&& install_ok=1
|
||||
fi
|
||||
|
||||
command -v curl >/dev/null
|
||||
if [[ "$install_ok" -ne 1 ]]; then
|
||||
echo "Could not auto-install dependencies for '$ID'. Make sure these are available:"
|
||||
echo "dotnet-sdk 8.x, curl, unzip, tar, rsync, rpm, rpmdevtools, rpm-build (on Red Hat branch)"
|
||||
fi
|
||||
|
||||
# Root directory = the script's location
|
||||
# Root directory
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
@@ -164,16 +90,13 @@ if [[ -f .gitmodules ]]; then
|
||||
git submodule update --init --recursive || true
|
||||
fi
|
||||
|
||||
# ===== Locate project ================================================================
|
||||
# Locate project
|
||||
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
if [[ ! -f "$PROJECT" ]]; then
|
||||
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
|
||||
fi
|
||||
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
|
||||
|
||||
# ===== Resolve GUI version & auto checkout ============================================
|
||||
VERSION=""
|
||||
|
||||
choose_channel() {
|
||||
# If --buildfrom provided, map it directly and skip interaction.
|
||||
if [[ -n "${BUILD_FROM:-}" ]]; then
|
||||
@@ -187,60 +110,35 @@ choose_channel() {
|
||||
|
||||
# Print menu to stderr and read from /dev/tty so stdout only carries the token.
|
||||
local ch="latest" sel=""
|
||||
|
||||
if [[ -t 0 ]]; then
|
||||
echo "[?] Choose v2rayN release channel:" >&2
|
||||
echo " 1) Latest (stable) [default]" >&2
|
||||
echo " 2) Pre-release (preview)" >&2
|
||||
echo " 3) Keep current (do nothing)" >&2
|
||||
printf "Enter 1, 2 or 3 [default 1]: " >&2
|
||||
|
||||
if read -r sel </dev/tty; then
|
||||
case "${sel:-}" in
|
||||
2) ch="prerelease" ;;
|
||||
3) ch="keep" ;;
|
||||
*) ch="latest" ;;
|
||||
esac
|
||||
else
|
||||
ch="latest"
|
||||
fi
|
||||
else
|
||||
ch="latest"
|
||||
fi
|
||||
|
||||
echo "$ch"
|
||||
}
|
||||
|
||||
get_latest_tag_latest() {
|
||||
# Resolve /releases/latest → tag_name
|
||||
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \
|
||||
| grep -Eo '"tag_name":\s*"v?[^"]+"' \
|
||||
| head -n1 \
|
||||
| sed -E 's/.*"tag_name":\s*"v?([^"]+)".*/\1/'
|
||||
| jq -re '.tag_name' \
|
||||
| sed 's/^v//'
|
||||
}
|
||||
|
||||
get_latest_tag_prerelease() {
|
||||
# Resolve newest prerelease=true tag; prefer jq, fallback to sed/grep (no awk)
|
||||
local json tag
|
||||
json="$(curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20")" || return 1
|
||||
|
||||
# 1) Use jq if present
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
tag="$(printf '%s' "$json" \
|
||||
| jq -r '[.[] | select(.prerelease==true)][0].tag_name' 2>/dev/null \
|
||||
| sed 's/^v//')" || true
|
||||
fi
|
||||
|
||||
# 2) Fallback to sed/grep only
|
||||
if [[ -z "${tag:-}" || "${tag:-}" == "null" ]]; then
|
||||
tag="$(printf '%s' "$json" \
|
||||
| tr '\n' ' ' \
|
||||
| sed 's/},[[:space:]]*{/\n/g' \
|
||||
| grep -m1 -E '"prerelease"[[:space:]]*:[[:space:]]*true' \
|
||||
| grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"v?[^"]+"' \
|
||||
| head -n1 \
|
||||
| sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/')" || true
|
||||
fi
|
||||
|
||||
[[ -n "${tag:-}" && "${tag:-}" != "null" ]] || return 1
|
||||
printf '%s\n' "$tag"
|
||||
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20" \
|
||||
| jq -re 'first(.[] | select(.prerelease == true) | .tag_name)' \
|
||||
| sed 's/^v//'
|
||||
}
|
||||
|
||||
git_try_checkout() {
|
||||
@@ -248,11 +146,7 @@ git_try_checkout() {
|
||||
local want="$1" ref=""
|
||||
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
git fetch --tags --force --prune --depth=1 || true
|
||||
if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then
|
||||
ref="v${want}"
|
||||
elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then
|
||||
ref="${want}"
|
||||
elif git rev-parse --verify "${want}" >/dev/null 2>&1; then
|
||||
if git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then
|
||||
ref="${want}"
|
||||
fi
|
||||
if [[ -n "$ref" ]]; then
|
||||
@@ -268,88 +162,56 @@ git_try_checkout() {
|
||||
return 1
|
||||
}
|
||||
|
||||
apply_channel_or_keep() {
|
||||
local ch="$1" tag
|
||||
|
||||
if [[ "$ch" == "keep" ]]; then
|
||||
echo "[*] Keep current repository state (no checkout)."
|
||||
VERSION="$(git describe --tags --abbrev=0 2>/dev/null || echo '0.0.0+git')"
|
||||
VERSION="${VERSION#v}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[*] Resolving ${ch} tag from GitHub releases..."
|
||||
if [[ "$ch" == "prerelease" ]]; then
|
||||
tag="$(get_latest_tag_prerelease || true)"
|
||||
else
|
||||
tag="$(get_latest_tag_latest || true)"
|
||||
fi
|
||||
|
||||
[[ -n "$tag" ]] || { echo "Failed to resolve latest tag for channel '${ch}'."; exit 1; }
|
||||
echo "[*] Latest tag for '${ch}': ${tag}"
|
||||
git_try_checkout "$tag" || { echo "Failed to checkout '${tag}'."; exit 1; }
|
||||
VERSION="${tag#v}"
|
||||
}
|
||||
|
||||
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
if [[ -n "${VERSION_ARG:-}" ]]; then
|
||||
echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}"
|
||||
if git_try_checkout "${VERSION_ARG#v}"; then
|
||||
VERSION="${VERSION_ARG#v}"
|
||||
clean_ver="${VERSION_ARG#v}"
|
||||
if git_try_checkout "$clean_ver"; then
|
||||
VERSION="$clean_ver"
|
||||
else
|
||||
echo "[WARN] Tag '${VERSION_ARG}' not found."
|
||||
ch="$(choose_channel)"
|
||||
if [[ "$ch" == "keep" ]]; then
|
||||
echo "[*] Keep current repository state (no checkout)."
|
||||
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||
VERSION="$(git describe --tags --abbrev=0)"
|
||||
else
|
||||
VERSION="0.0.0+git"
|
||||
fi
|
||||
VERSION="${VERSION#v}"
|
||||
else
|
||||
echo "[*] Resolving ${ch} tag from GitHub releases..."
|
||||
tag=""
|
||||
if [[ "$ch" == "prerelease" ]]; then
|
||||
tag="$(get_latest_tag_prerelease || true)"
|
||||
if [[ -z "$tag" ]]; then
|
||||
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
|
||||
tag="$(get_latest_tag_latest || true)"
|
||||
fi
|
||||
else
|
||||
tag="$(get_latest_tag_latest || true)"
|
||||
fi
|
||||
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
|
||||
echo "[*] Latest tag for '${ch}': ${tag}"
|
||||
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
|
||||
VERSION="${tag#v}"
|
||||
fi
|
||||
apply_channel_or_keep "$ch"
|
||||
fi
|
||||
else
|
||||
ch="$(choose_channel)"
|
||||
if [[ "$ch" == "keep" ]]; then
|
||||
echo "[*] Keep current repository state (no checkout)."
|
||||
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||
VERSION="$(git describe --tags --abbrev=0)"
|
||||
else
|
||||
VERSION="0.0.0+git"
|
||||
fi
|
||||
VERSION="${VERSION#v}"
|
||||
else
|
||||
echo "[*] Resolving ${ch} tag from GitHub releases..."
|
||||
tag=""
|
||||
if [[ "$ch" == "prerelease" ]]; then
|
||||
tag="$(get_latest_tag_prerelease || true)"
|
||||
if [[ -z "$tag" ]]; then
|
||||
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
|
||||
tag="$(get_latest_tag_latest || true)"
|
||||
fi
|
||||
else
|
||||
tag="$(get_latest_tag_latest || true)"
|
||||
fi
|
||||
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
|
||||
echo "[*] Latest tag for '${ch}': ${tag}"
|
||||
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
|
||||
VERSION="${tag#v}"
|
||||
fi
|
||||
apply_channel_or_keep "$ch"
|
||||
fi
|
||||
else
|
||||
echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree."
|
||||
VERSION="${VERSION_ARG:-}"
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||
VERSION="$(git describe --tags --abbrev=0)"
|
||||
else
|
||||
VERSION="0.0.0+git"
|
||||
fi
|
||||
fi
|
||||
VERSION="${VERSION#v}"
|
||||
echo "Current directory is not a git repo; proceeding on current tree."
|
||||
VERSION="${VERSION_ARG:-0.0.0}"
|
||||
fi
|
||||
|
||||
VERSION="${VERSION#v}"
|
||||
echo "[*] GUI version resolved as: ${VERSION}"
|
||||
|
||||
# ===== Helpers for core/rules download (use RID_DIR for arch sync) =====================
|
||||
# Helpers for core
|
||||
download_xray() {
|
||||
# Download Xray core and install to outdir/xray
|
||||
# Download Xray core
|
||||
local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip"
|
||||
mkdir -p "$outdir"
|
||||
if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi
|
||||
if [[ -z "$ver" ]]; then
|
||||
ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \
|
||||
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
|
||||
@@ -368,10 +230,9 @@ download_xray() {
|
||||
}
|
||||
|
||||
download_singbox() {
|
||||
# Download sing-box core and install to outdir/sing-box
|
||||
# Download sing-box
|
||||
local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin
|
||||
mkdir -p "$outdir"
|
||||
if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi
|
||||
if [[ -z "$ver" ]]; then
|
||||
ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \
|
||||
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
|
||||
@@ -391,23 +252,7 @@ download_singbox() {
|
||||
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 outroot/bin
|
||||
unify_geo_layout() {
|
||||
local outroot="$1"
|
||||
mkdir -p "$outroot/bin"
|
||||
@@ -419,18 +264,13 @@ unify_geo_layout() {
|
||||
"geoip.metadb" \
|
||||
)
|
||||
for n in "${names[@]}"; do
|
||||
# If file exists under bin/xray/, move it up to bin/
|
||||
if [[ -f "$outroot/bin/xray/$n" ]]; then
|
||||
mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n"
|
||||
fi
|
||||
# If file already in bin/, leave it as-is
|
||||
if [[ -f "$outroot/bin/$n" ]]; then
|
||||
:
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Download geo/rule assets; then unify to bin/
|
||||
# Download geo/rule assets
|
||||
download_geo_assets() {
|
||||
local outroot="$1"
|
||||
local bin_dir="$outroot/bin"
|
||||
@@ -464,7 +304,7 @@ download_geo_assets() {
|
||||
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
||||
done
|
||||
|
||||
# Unify to bin/
|
||||
# Unify to bin
|
||||
unify_geo_layout "$outroot"
|
||||
}
|
||||
|
||||
@@ -491,12 +331,11 @@ download_v2rayn_bundle() {
|
||||
fi
|
||||
|
||||
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
|
||||
# keep mihomo
|
||||
# find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
|
||||
find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
|
||||
|
||||
local nested_dir
|
||||
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
|
||||
if [[ -n "${nested_dir:-}" && -d "$nested_dir/bin" ]]; then
|
||||
if [[ -n "$nested_dir" && -d "$nested_dir/bin" ]]; then
|
||||
mkdir -p "$outroot/bin"
|
||||
rsync -a "$nested_dir/bin/" "$outroot/bin/"
|
||||
rm -rf "$nested_dir"
|
||||
@@ -520,7 +359,7 @@ build_for_arch() {
|
||||
case "$short" in
|
||||
x64) rid="linux-x64"; rpm_target="x86_64"; archdir="x86_64" ;;
|
||||
arm64) rid="linux-arm64"; rpm_target="aarch64"; archdir="aarch64" ;;
|
||||
*) echo "[ERROR] Unknown arch '$short' (use x64|arm64)"; return 1;;
|
||||
*) echo "Unknown arch '$short' (use x64|arm64)"; return 1;;
|
||||
esac
|
||||
|
||||
echo "[*] Building for target: $short (RID=$rid, RPM --target $rpm_target)"
|
||||
@@ -533,8 +372,7 @@ build_for_arch() {
|
||||
dotnet publish "$PROJECT" \
|
||||
-c Release -r "$rid" \
|
||||
-p:PublishSingleFile=false \
|
||||
-p:SelfContained=true \
|
||||
-p:IncludeNativeLibrariesForSelfExtract=true
|
||||
-p:SelfContained=true
|
||||
|
||||
# Per-arch variables (scoped)
|
||||
local RID_DIR="$rid"
|
||||
@@ -552,20 +390,11 @@ build_for_arch() {
|
||||
trap '[[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"' RETURN
|
||||
|
||||
# rpmbuild topdir selection
|
||||
local TOPDIR SPECDIR SOURCEDIR USE_TOPDIR_DEFINE
|
||||
if [[ "$ID" =~ ^(rhel|rocky|almalinux|centos)$ ]]; then
|
||||
rpmdev-setuptree
|
||||
TOPDIR="${HOME}/rpmbuild"
|
||||
SPECDIR="${TOPDIR}/SPECS"
|
||||
SOURCEDIR="${TOPDIR}/SOURCES"
|
||||
USE_TOPDIR_DEFINE=0
|
||||
else
|
||||
TOPDIR="${WORKDIR}/rpmbuild"
|
||||
SPECDIR="${TOPDIR}/SPECS}"
|
||||
SOURCEDIR="${TOPDIR}/SOURCES"
|
||||
mkdir -p "${SPECDIR}" "${SOURCEDIR}" "${TOPDIR}/BUILD" "${TOPDIR}/RPMS" "${TOPDIR}/SRPMS"
|
||||
USE_TOPDIR_DEFINE=1
|
||||
fi
|
||||
local TOPDIR SPECDIR SOURCEDIR
|
||||
rpmdev-setuptree
|
||||
TOPDIR="${HOME}/rpmbuild"
|
||||
SPECDIR="${TOPDIR}/SPECS"
|
||||
SOURCEDIR="${TOPDIR}/SOURCES"
|
||||
|
||||
# Stage publish content
|
||||
mkdir -p "$WORKDIR/$PKGROOT"
|
||||
@@ -580,30 +409,28 @@ build_for_arch() {
|
||||
mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box"
|
||||
|
||||
# Bundle / cores per-arch
|
||||
fetch_separate_cores_and_rules() {
|
||||
local outroot="$1"
|
||||
|
||||
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
|
||||
download_xray "$outroot/bin/xray" || echo "[!] xray download failed (skipped)"
|
||||
fi
|
||||
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
|
||||
download_singbox "$outroot/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||
fi
|
||||
download_geo_assets "$outroot" || echo "[!] Geo rules download failed (skipped)"
|
||||
}
|
||||
|
||||
if [[ "$FORCE_NETCORE" -eq 0 ]]; then
|
||||
if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then
|
||||
echo "[*] Using v2rayN bundle archive."
|
||||
else
|
||||
echo "[*] Bundle failed, fallback to separate core + rules."
|
||||
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
|
||||
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
|
||||
fi
|
||||
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
|
||||
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||
fi
|
||||
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||
fetch_separate_cores_and_rules "$WORKDIR/$PKGROOT"
|
||||
fi
|
||||
else
|
||||
echo "[*] --netcore specified: use separate core + rules."
|
||||
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
|
||||
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
|
||||
fi
|
||||
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
|
||||
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||
fi
|
||||
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
|
||||
download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
|
||||
fetch_separate_cores_and_rules "$WORKDIR/$PKGROOT"
|
||||
fi
|
||||
|
||||
# Tarball
|
||||
@@ -712,72 +539,12 @@ fi
|
||||
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
|
||||
SPEC
|
||||
|
||||
# Autostart injection (inside %install) and %files entry
|
||||
if [[ "$AUTOSTART" -eq 1 ]]; then
|
||||
awk '
|
||||
BEGIN{ins=0}
|
||||
/^%post$/ && !ins {
|
||||
print "# --- Autostart (.desktop) ---"
|
||||
print "install -dm0755 %{buildroot}/etc/xdg/autostart"
|
||||
print "cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << '\''EOF'\''"
|
||||
print "[Desktop Entry]"
|
||||
print "Type=Application"
|
||||
print "Name=v2rayN (Autostart)"
|
||||
print "Exec=v2rayn"
|
||||
print "X-GNOME-Autostart-enabled=true"
|
||||
print "NoDisplay=false"
|
||||
print "EOF"
|
||||
ins=1
|
||||
}
|
||||
{print}
|
||||
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
|
||||
|
||||
awk '
|
||||
BEGIN{infiles=0; done=0}
|
||||
/^%files$/ {infiles=1}
|
||||
infiles && done==0 && $0 ~ /%{_datadir}\/icons\/hicolor\/256x256\/apps\/v2rayn\.png/ {
|
||||
print
|
||||
print "%config(noreplace) /etc/xdg/autostart/v2rayn.desktop"
|
||||
done=1
|
||||
next
|
||||
}
|
||||
{print}
|
||||
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
|
||||
fi
|
||||
|
||||
# Replace placeholders
|
||||
sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE"
|
||||
sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE"
|
||||
|
||||
# ----- Select proper 'strip' per target arch on Ubuntu only (cross-binutils) -----
|
||||
# NOTE: We define only __strip to point to the target-arch strip.
|
||||
# DO NOT override __brp_strip (it must stay the brp script path).
|
||||
local STRIP_ARGS=()
|
||||
if [[ "$ID" == "ubuntu" ]]; then
|
||||
local STRIP_BIN=""
|
||||
if [[ "$short" == "x64" ]]; then
|
||||
STRIP_BIN="/usr/bin/x86_64-linux-gnu-strip"
|
||||
else
|
||||
STRIP_BIN="/usr/bin/aarch64-linux-gnu-strip"
|
||||
fi
|
||||
if [[ -x "$STRIP_BIN" ]]; then
|
||||
STRIP_ARGS=( --define "__strip $STRIP_BIN" )
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build RPM for this arch (force rpm --target to match compile arch)
|
||||
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
|
||||
rpmbuild -ba "$SPECFILE" --define "_topdir $TOPDIR" --target "$rpm_target" "${STRIP_ARGS[@]}"
|
||||
else
|
||||
rpmbuild -ba "$SPECFILE" --target "$rpm_target" "${STRIP_ARGS[@]}"
|
||||
fi
|
||||
|
||||
# Copy temporary rpmbuild to ~/rpmbuild on Debian/Ubuntu path
|
||||
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
|
||||
mkdir -p "$HOME/rpmbuild"
|
||||
rsync -a "$TOPDIR"/ "$HOME/rpmbuild"/
|
||||
TOPDIR="$HOME/rpmbuild"
|
||||
fi
|
||||
# Build RPM for this arch
|
||||
rpmbuild -ba "$SPECFILE" --target "$rpm_target"
|
||||
|
||||
echo "Build done for $short. RPM at:"
|
||||
local f
|
||||
@@ -790,33 +557,18 @@ SPEC
|
||||
|
||||
# ===== Arch selection and build orchestration =========================================
|
||||
case "${ARCH_OVERRIDE:-}" in
|
||||
"")
|
||||
# No --arch: use host architecture
|
||||
if [[ "$host_arch" == "aarch64" ]]; then
|
||||
build_for_arch arm64
|
||||
else
|
||||
build_for_arch x64
|
||||
fi
|
||||
;;
|
||||
x64|amd64)
|
||||
build_for_arch x64
|
||||
;;
|
||||
arm64|aarch64)
|
||||
build_for_arch arm64
|
||||
;;
|
||||
all)
|
||||
BUILT_ALL=1
|
||||
# Build x64 and arm64 separately; each package contains its own arch-only binaries.
|
||||
build_for_arch x64
|
||||
build_for_arch arm64
|
||||
;;
|
||||
*)
|
||||
echo "[ERROR] Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."
|
||||
exit 1
|
||||
;;
|
||||
all) targets=(x64 arm64); BUILT_ALL=1 ;;
|
||||
x64|amd64) targets=(x64) ;;
|
||||
arm64|aarch64) targets=(arm64) ;;
|
||||
"") targets=($([[ "$host_arch" == "aarch64" ]] && echo arm64 || echo x64)) ;;
|
||||
*) echo "Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."; exit 1 ;;
|
||||
esac
|
||||
|
||||
# ===== Final summary if building both arches ==========================================
|
||||
for arch in "${targets[@]}"; do
|
||||
build_for_arch "$arch"
|
||||
done
|
||||
|
||||
# Print Both arches information
|
||||
if [[ "$BUILT_ALL" -eq 1 ]]; then
|
||||
echo ""
|
||||
echo "================ Build Summary (both architectures) ================"
|
||||
@@ -825,7 +577,7 @@ if [[ "$BUILT_ALL" -eq 1 ]]; then
|
||||
echo "$rp"
|
||||
done
|
||||
else
|
||||
echo "[WARN] No RPMs detected in summary (check build logs above)."
|
||||
echo "No RPMs detected in summary (check build logs above)."
|
||||
fi
|
||||
echo "==================================================================="
|
||||
echo "===================================================================="
|
||||
fi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.16.4</Version>
|
||||
<Version>7.19.3</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -5,24 +5,24 @@
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.9" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.9" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.9" />
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.0" />
|
||||
<PackageVersion Include="Downloader" Version="4.0.3" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" />
|
||||
<PackageVersion Include="Downloader" Version="4.1.1" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
|
||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.0" />
|
||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
|
||||
<PackageVersion Include="QRCoder" Version="1.7.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="22.2.1" />
|
||||
<PackageVersion Include="ReactiveUI" Version="22.3.1" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="22.2.1" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.1" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="22.3.1" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.3" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.1" />
|
||||
<PackageVersion Include="NLog" Version="6.0.6" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||
<PackageVersion Include="NLog" Version="6.1.1" />
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<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)
|
||||
{
|
||||
return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
|
||||
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value);
|
||||
return string.IsNullOrWhiteSpace(value) || string.IsNullOrEmpty(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)
|
||||
@@ -94,4 +94,28 @@ public static class Extension
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,19 +332,17 @@ public class Utils
|
||||
.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
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
// skip full-line comments
|
||||
.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;
|
||||
})
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#'))
|
||||
// ensure line still contains valid parts
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
@@ -462,6 +460,18 @@ public class Utils
|
||||
return (domain, port);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
#endregion Conversion Functions
|
||||
|
||||
#region Data Checks
|
||||
@@ -487,6 +497,13 @@ public class Utils
|
||||
return false;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(domain);
|
||||
if (ext.IsNotEmpty()
|
||||
&& ext[1..].ToLowerInvariant() is "json" or "txt" or "xml" or "cfg" or "ini" or "log" or "yaml" or "yml" or "toml")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Uri.CheckHostName(domain) == UriHostNameType.Dns;
|
||||
}
|
||||
|
||||
@@ -505,6 +522,31 @@ public class Utils
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -594,12 +636,7 @@ public class Utils
|
||||
{
|
||||
try
|
||||
{
|
||||
List<IPEndPoint> lstIpEndPoints = new();
|
||||
List<TcpConnectionInformation> lstTcpConns = new();
|
||||
|
||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
|
||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
|
||||
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
|
||||
var (lstIpEndPoints, lstTcpConns) = GetActiveNetworkInfo();
|
||||
|
||||
if (lstIpEndPoints?.FindIndex(it => it.Port == port) >= 0)
|
||||
{
|
||||
@@ -641,6 +678,27 @@ public class Utils
|
||||
return 59090;
|
||||
}
|
||||
|
||||
public static (List<IPEndPoint> endpoints, List<TcpConnectionInformation> connections) GetActiveNetworkInfo()
|
||||
{
|
||||
var endpoints = new List<IPEndPoint>();
|
||||
var connections = new List<TcpConnectionInformation>();
|
||||
|
||||
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
|
||||
@@ -719,33 +777,65 @@ public class Utils
|
||||
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 hostFile = @"C:\Windows\System32\drivers\etc\hosts";
|
||||
try
|
||||
{
|
||||
if (File.Exists(hostFile))
|
||||
if (!File.Exists(hostFile))
|
||||
{
|
||||
var hosts = File.ReadAllText(hostFile).Replace("\r", "");
|
||||
var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var host in hostsList)
|
||||
{
|
||||
if (host.StartsWith("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (hostItem.Length < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
systemHosts.Add(hostItem[1], hostItem[0]);
|
||||
}
|
||||
return systemHosts;
|
||||
}
|
||||
var hosts = File.ReadAllText(hostFile).Replace("\r", "");
|
||||
var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var host in hostsList)
|
||||
{
|
||||
// Trim whitespace
|
||||
var line = host.Trim();
|
||||
|
||||
// Skip comments and empty lines
|
||||
if (line.IsNullOrEmpty() || line.StartsWith("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -755,6 +845,19 @@ public class Utils
|
||||
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)
|
||||
{
|
||||
return await GetCliWrapOutput(filePath, arg != null ? new List<string>() { arg } : null);
|
||||
@@ -1004,7 +1107,19 @@ public class Utils
|
||||
|
||||
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()
|
||||
|
||||
@@ -15,7 +15,6 @@ public class Global
|
||||
public const string CoreConfigFileName = "config.json";
|
||||
public const string CorePreConfigFileName = "configPre.json";
|
||||
public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
|
||||
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
|
||||
public const string ClashMixinConfigFileName = "Mixin.yaml";
|
||||
|
||||
public const string NamespaceSample = "ServiceLib.Sample.";
|
||||
@@ -50,6 +49,7 @@ public class Global
|
||||
public const string DirectTag = "direct";
|
||||
public const string BlockTag = "block";
|
||||
public const string DnsTag = "dns-module";
|
||||
public const string DirectDnsTag = "direct-dns";
|
||||
public const string BalancerTagSuffix = "-round";
|
||||
public const string StreamSecurity = "tls";
|
||||
public const string StreamSecurityReality = "reality";
|
||||
@@ -73,6 +73,7 @@ public class Global
|
||||
public const string GrpcMultiMode = "multi";
|
||||
public const int MaxPort = 65536;
|
||||
public const int MinFontSize = 8;
|
||||
public const int MinFontSizeCount = 13;
|
||||
public const string RebootAs = "rebootas";
|
||||
public const string AvaAssets = "avares://v2rayN/Assets/";
|
||||
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2";
|
||||
@@ -88,231 +89,262 @@ public class Global
|
||||
public const string SingboxHostsDNSTag = "hosts_dns";
|
||||
public const string SingboxFakeDNSTag = "fake_dns";
|
||||
|
||||
public const int Hysteria2DefaultHopInt = 10;
|
||||
|
||||
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";
|
||||
|
||||
public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$";
|
||||
|
||||
public static readonly List<string> PolicyGroupDefaultFilterList =
|
||||
[
|
||||
// All nodes (exclude traffic/expiry info)
|
||||
PolicyGroupDefaultAllFilter,
|
||||
// Low multiplier nodes, e.g. ×0.1, 0.5x, 0.1倍
|
||||
@"^.*(?:[×xX✕*]\s*0\.[0-9]+|0\.[0-9]+\s*[×xX✕*倍]).*$",
|
||||
// Dedicated line nodes, e.g. IPLC, IEPL
|
||||
$@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:专线|IPLC|IEPL|中转).*$",
|
||||
// Japan nodes
|
||||
$@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan).*$",
|
||||
];
|
||||
|
||||
public static readonly List<string> IEProxyProtocols =
|
||||
[
|
||||
"{ip}:{http_port}",
|
||||
"socks={ip}:{socks_port}",
|
||||
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
|
||||
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
|
||||
""
|
||||
"socks={ip}:{socks_port}",
|
||||
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
|
||||
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> SubConvertUrls =
|
||||
[
|
||||
@"https://sub.xeton.dev/sub?url={0}",
|
||||
@"https://api.dler.io/sub?url={0}",
|
||||
@"http://127.0.0.1:25500/sub?url={0}",
|
||||
""
|
||||
@"https://api.dler.io/sub?url={0}",
|
||||
@"http://127.0.0.1:25500/sub?url={0}",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> SubConvertConfig =
|
||||
[@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"];
|
||||
[
|
||||
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
|
||||
];
|
||||
|
||||
public static readonly List<string> SubConvertTargets =
|
||||
[
|
||||
"",
|
||||
"mixed",
|
||||
"v2ray",
|
||||
"clash",
|
||||
"ss"
|
||||
"mixed",
|
||||
"v2ray",
|
||||
"clash",
|
||||
"ss"
|
||||
];
|
||||
|
||||
public static readonly List<string> SpeedTestUrls =
|
||||
[
|
||||
@"https://cachefly.cachefly.net/50mb.test",
|
||||
@"https://speed.cloudflare.com/__down?bytes=10000000",
|
||||
@"https://speed.cloudflare.com/__down?bytes=50000000",
|
||||
@"https://speed.cloudflare.com/__down?bytes=100000000",
|
||||
];
|
||||
@"https://speed.cloudflare.com/__down?bytes=10000000",
|
||||
@"https://speed.cloudflare.com/__down?bytes=50000000",
|
||||
@"https://speed.cloudflare.com/__down?bytes=100000000",
|
||||
];
|
||||
|
||||
public static readonly List<string> SpeedPingTestUrls =
|
||||
[
|
||||
@"https://www.google.com/generate_204",
|
||||
@"https://www.gstatic.com/generate_204",
|
||||
@"https://www.apple.com/library/test/success.html",
|
||||
@"http://www.msftconnecttest.com/connecttest.txt"
|
||||
@"https://www.gstatic.com/generate_204",
|
||||
@"https://www.apple.com/library/test/success.html",
|
||||
@"http://www.msftconnecttest.com/connecttest.txt"
|
||||
];
|
||||
|
||||
public static readonly List<string> GeoFilesSources =
|
||||
[
|
||||
"",
|
||||
@"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat",
|
||||
@"https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/{0}.dat"
|
||||
@"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat",
|
||||
@"https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/{0}.dat"
|
||||
];
|
||||
|
||||
public static readonly List<string> SingboxRulesetSources =
|
||||
[
|
||||
"",
|
||||
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
|
||||
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
|
||||
];
|
||||
[
|
||||
"",
|
||||
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
|
||||
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
|
||||
];
|
||||
|
||||
public static readonly List<string> RoutingRulesSources =
|
||||
[
|
||||
"",
|
||||
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/template.json",
|
||||
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/template.json"
|
||||
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/template.json",
|
||||
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/template.json"
|
||||
];
|
||||
|
||||
public static readonly List<string> DNSTemplateSources =
|
||||
[
|
||||
"",
|
||||
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/",
|
||||
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/"
|
||||
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/",
|
||||
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/"
|
||||
];
|
||||
|
||||
public static readonly Dictionary<string, string> UserAgentTexts = new()
|
||||
{
|
||||
{"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" },
|
||||
{"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" },
|
||||
{"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" },
|
||||
{"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" },
|
||||
{"none",""}
|
||||
};
|
||||
{
|
||||
{"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" },
|
||||
{"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" },
|
||||
{"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" },
|
||||
{"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" },
|
||||
{"none",""}
|
||||
};
|
||||
|
||||
public const string Hysteria2ProtocolShare = "hy2://";
|
||||
|
||||
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
|
||||
{
|
||||
{ EConfigType.VMess, "vmess://" },
|
||||
{ EConfigType.Shadowsocks, "ss://" },
|
||||
{ EConfigType.SOCKS, "socks://" },
|
||||
{ EConfigType.VLESS, "vless://" },
|
||||
{ EConfigType.Trojan, "trojan://" },
|
||||
{ EConfigType.Hysteria2, "hysteria2://" },
|
||||
{ EConfigType.TUIC, "tuic://" },
|
||||
{ EConfigType.WireGuard, "wireguard://" },
|
||||
{ EConfigType.Anytls, "anytls://" }
|
||||
};
|
||||
{
|
||||
{ EConfigType.VMess, "vmess://" },
|
||||
{ EConfigType.Shadowsocks, "ss://" },
|
||||
{ EConfigType.SOCKS, "socks://" },
|
||||
{ EConfigType.VLESS, "vless://" },
|
||||
{ EConfigType.Trojan, "trojan://" },
|
||||
{ EConfigType.Hysteria2, "hysteria2://" },
|
||||
{ EConfigType.TUIC, "tuic://" },
|
||||
{ EConfigType.WireGuard, "wireguard://" },
|
||||
{ EConfigType.Anytls, "anytls://" }
|
||||
};
|
||||
|
||||
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
|
||||
{
|
||||
{ EConfigType.VMess, "vmess" },
|
||||
{ EConfigType.Shadowsocks, "shadowsocks" },
|
||||
{ EConfigType.SOCKS, "socks" },
|
||||
{ EConfigType.HTTP, "http" },
|
||||
{ EConfigType.VLESS, "vless" },
|
||||
{ EConfigType.Trojan, "trojan" },
|
||||
{ EConfigType.Hysteria2, "hysteria2" },
|
||||
{ EConfigType.TUIC, "tuic" },
|
||||
{ EConfigType.WireGuard, "wireguard" },
|
||||
{ EConfigType.Anytls, "anytls" }
|
||||
};
|
||||
{
|
||||
{ EConfigType.VMess, "vmess" },
|
||||
{ EConfigType.Shadowsocks, "shadowsocks" },
|
||||
{ EConfigType.SOCKS, "socks" },
|
||||
{ EConfigType.HTTP, "http" },
|
||||
{ EConfigType.VLESS, "vless" },
|
||||
{ EConfigType.Trojan, "trojan" },
|
||||
{ EConfigType.Hysteria2, "hysteria2" },
|
||||
{ EConfigType.TUIC, "tuic" },
|
||||
{ EConfigType.WireGuard, "wireguard" },
|
||||
{ EConfigType.Anytls, "anytls" }
|
||||
};
|
||||
|
||||
public static readonly List<string> VmessSecurities =
|
||||
[
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"auto",
|
||||
"none",
|
||||
"zero"
|
||||
"chacha20-poly1305",
|
||||
"auto",
|
||||
"none",
|
||||
"zero"
|
||||
];
|
||||
|
||||
public static readonly List<string> SsSecurities =
|
||||
[
|
||||
"aes-256-gcm",
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"chacha20-ietf-poly1305",
|
||||
"none",
|
||||
"plain"
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"chacha20-ietf-poly1305",
|
||||
"none",
|
||||
"plain"
|
||||
];
|
||||
|
||||
public static readonly List<string> SsSecuritiesInXray =
|
||||
[
|
||||
"aes-256-gcm",
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
"none",
|
||||
"plain",
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305"
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
"none",
|
||||
"plain",
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305"
|
||||
];
|
||||
|
||||
public static readonly List<string> SsSecuritiesInSingbox =
|
||||
[
|
||||
"aes-256-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-128-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
"none",
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"rc4-md5",
|
||||
"chacha20-ietf",
|
||||
"xchacha20"
|
||||
"aes-192-gcm",
|
||||
"aes-128-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
"none",
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"rc4-md5",
|
||||
"chacha20-ietf",
|
||||
"xchacha20"
|
||||
];
|
||||
|
||||
public static readonly List<string> Flows =
|
||||
[
|
||||
"",
|
||||
"xtls-rprx-vision",
|
||||
"xtls-rprx-vision-udp443"
|
||||
"xtls-rprx-vision",
|
||||
"xtls-rprx-vision-udp443"
|
||||
];
|
||||
|
||||
public static readonly List<string> Networks =
|
||||
[
|
||||
"tcp",
|
||||
"kcp",
|
||||
"ws",
|
||||
"httpupgrade",
|
||||
"xhttp",
|
||||
"h2",
|
||||
"quic",
|
||||
"grpc"
|
||||
"kcp",
|
||||
"ws",
|
||||
"httpupgrade",
|
||||
"xhttp",
|
||||
"h2",
|
||||
"quic",
|
||||
"grpc"
|
||||
];
|
||||
|
||||
public static readonly List<string> KcpHeaderTypes =
|
||||
[
|
||||
"srtp",
|
||||
"utp",
|
||||
"wechat-video",
|
||||
"dtls",
|
||||
"wireguard",
|
||||
"dns"
|
||||
"utp",
|
||||
"wechat-video",
|
||||
"dtls",
|
||||
"wireguard",
|
||||
"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 =
|
||||
[
|
||||
"Xray",
|
||||
"sing_box"
|
||||
"sing_box"
|
||||
];
|
||||
|
||||
public static readonly HashSet<EConfigType> XraySupportConfigType =
|
||||
[
|
||||
EConfigType.VMess,
|
||||
EConfigType.VLESS,
|
||||
EConfigType.Shadowsocks,
|
||||
EConfigType.Trojan,
|
||||
EConfigType.WireGuard,
|
||||
EConfigType.SOCKS,
|
||||
EConfigType.HTTP,
|
||||
EConfigType.VLESS,
|
||||
EConfigType.Shadowsocks,
|
||||
EConfigType.Trojan,
|
||||
EConfigType.Hysteria2,
|
||||
EConfigType.WireGuard,
|
||||
EConfigType.SOCKS,
|
||||
EConfigType.HTTP,
|
||||
];
|
||||
|
||||
public static readonly HashSet<EConfigType> SingboxSupportConfigType =
|
||||
[
|
||||
EConfigType.VMess,
|
||||
EConfigType.VLESS,
|
||||
EConfigType.Shadowsocks,
|
||||
EConfigType.Trojan,
|
||||
EConfigType.Hysteria2,
|
||||
EConfigType.TUIC,
|
||||
EConfigType.Anytls,
|
||||
EConfigType.WireGuard,
|
||||
EConfigType.SOCKS,
|
||||
EConfigType.HTTP,
|
||||
EConfigType.VLESS,
|
||||
EConfigType.Shadowsocks,
|
||||
EConfigType.Trojan,
|
||||
EConfigType.Hysteria2,
|
||||
EConfigType.TUIC,
|
||||
EConfigType.Anytls,
|
||||
EConfigType.WireGuard,
|
||||
EConfigType.SOCKS,
|
||||
EConfigType.HTTP,
|
||||
];
|
||||
|
||||
public static readonly HashSet<EConfigType> SingboxOnlyConfigType = SingboxSupportConfigType.Except(XraySupportConfigType).ToHashSet();
|
||||
@@ -324,132 +356,127 @@ public class Global
|
||||
IPOnDemand
|
||||
];
|
||||
|
||||
public static readonly List<string> DomainStrategies4Singbox =
|
||||
public static readonly List<string> DomainStrategies4Sbox =
|
||||
[
|
||||
"",
|
||||
"prefer_ipv4",
|
||||
"prefer_ipv6",
|
||||
"ipv4_only",
|
||||
"ipv6_only",
|
||||
"prefer_ipv4",
|
||||
"prefer_ipv6",
|
||||
""
|
||||
"ipv6_only"
|
||||
];
|
||||
|
||||
public static readonly List<string> Fingerprints =
|
||||
[
|
||||
"chrome",
|
||||
"firefox",
|
||||
"safari",
|
||||
"ios",
|
||||
"android",
|
||||
"edge",
|
||||
"360",
|
||||
"qq",
|
||||
"random",
|
||||
"randomized",
|
||||
""
|
||||
"firefox",
|
||||
"safari",
|
||||
"ios",
|
||||
"android",
|
||||
"edge",
|
||||
"360",
|
||||
"qq",
|
||||
"random",
|
||||
"randomized",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> UserAgent =
|
||||
[
|
||||
"chrome",
|
||||
"firefox",
|
||||
"safari",
|
||||
"edge",
|
||||
"none"
|
||||
"firefox",
|
||||
"safari",
|
||||
"edge",
|
||||
"none"
|
||||
];
|
||||
|
||||
public static readonly List<string> XhttpMode =
|
||||
[
|
||||
"auto",
|
||||
"packet-up",
|
||||
"stream-up",
|
||||
"stream-one"
|
||||
"packet-up",
|
||||
"stream-up",
|
||||
"stream-one"
|
||||
];
|
||||
|
||||
public static readonly List<string> AllowInsecure =
|
||||
[
|
||||
"true",
|
||||
"false",
|
||||
""
|
||||
"false",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> DomainStrategy4Freedoms =
|
||||
public static readonly List<string> DomainStrategy =
|
||||
[
|
||||
"AsIs",
|
||||
"UseIP",
|
||||
"UseIPv4",
|
||||
"UseIPv6",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> SingboxDomainStrategy4Out =
|
||||
[
|
||||
"",
|
||||
"ipv4_only",
|
||||
"prefer_ipv4",
|
||||
"prefer_ipv6",
|
||||
"ipv6_only"
|
||||
"UseIP",
|
||||
"UseIPv4v6",
|
||||
"UseIPv6v4",
|
||||
"UseIPv4",
|
||||
"UseIPv6",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> DomainDirectDNSAddress =
|
||||
[
|
||||
"https://dns.alidns.com/dns-query",
|
||||
"https://doh.pub/dns-query",
|
||||
"223.5.5.5",
|
||||
"119.29.29.29",
|
||||
"localhost"
|
||||
"https://doh.pub/dns-query",
|
||||
"https://dns.alidns.com/dns-query,https://doh.pub/dns-query",
|
||||
"223.5.5.5",
|
||||
"119.29.29.29",
|
||||
"localhost"
|
||||
];
|
||||
|
||||
public static readonly List<string> DomainRemoteDNSAddress =
|
||||
[
|
||||
"https://cloudflare-dns.com/dns-query",
|
||||
"https://dns.cloudflare.com/dns-query",
|
||||
"https://dns.google/dns-query",
|
||||
"https://doh.dns.sb/dns-query",
|
||||
"https://doh.opendns.com/dns-query",
|
||||
"https://common.dot.dns.yandex.net",
|
||||
"8.8.8.8",
|
||||
"1.1.1.1",
|
||||
"185.222.222.222",
|
||||
"208.67.222.222",
|
||||
"77.88.8.8"
|
||||
"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.opendns.com/dns-query",
|
||||
"https://common.dot.dns.yandex.net",
|
||||
"8.8.8.8",
|
||||
"1.1.1.1",
|
||||
"185.222.222.222",
|
||||
"208.67.222.222",
|
||||
"77.88.8.8"
|
||||
];
|
||||
|
||||
public static readonly List<string> DomainPureIPDNSAddress =
|
||||
[
|
||||
"223.5.5.5",
|
||||
"119.29.29.29",
|
||||
"localhost"
|
||||
"119.29.29.29",
|
||||
"localhost"
|
||||
];
|
||||
|
||||
public static readonly List<string> Languages =
|
||||
[
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
"en",
|
||||
"fa-Ir",
|
||||
"fr",
|
||||
"ru",
|
||||
"hu"
|
||||
"zh-Hant",
|
||||
"en",
|
||||
"fa-Ir",
|
||||
"fr",
|
||||
"ru",
|
||||
"hu"
|
||||
];
|
||||
|
||||
public static readonly List<string> Alpns =
|
||||
[
|
||||
"h3",
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"h3,h2",
|
||||
"h2,http/1.1",
|
||||
"h3,h2,http/1.1",
|
||||
""
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"h3,h2",
|
||||
"h2,http/1.1",
|
||||
"h3,h2,http/1.1",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> LogLevels =
|
||||
[
|
||||
"debug",
|
||||
"info",
|
||||
"warning",
|
||||
"error",
|
||||
"none"
|
||||
"info",
|
||||
"warning",
|
||||
"error",
|
||||
"none"
|
||||
];
|
||||
|
||||
public static readonly Dictionary<string, string> LogLevelColors = new()
|
||||
@@ -463,32 +490,32 @@ public class Global
|
||||
public static readonly List<string> InboundTags =
|
||||
[
|
||||
"socks",
|
||||
"socks2",
|
||||
"socks3"
|
||||
"socks2",
|
||||
"socks3"
|
||||
];
|
||||
|
||||
public static readonly List<string> RuleProtocols =
|
||||
[
|
||||
"http",
|
||||
"tls",
|
||||
"bittorrent"
|
||||
"tls",
|
||||
"bittorrent"
|
||||
];
|
||||
|
||||
public static readonly List<string> RuleNetworks =
|
||||
[
|
||||
"",
|
||||
"tcp",
|
||||
"udp",
|
||||
"tcp,udp"
|
||||
"tcp",
|
||||
"udp",
|
||||
"tcp,udp"
|
||||
];
|
||||
|
||||
public static readonly List<string> destOverrideProtocols =
|
||||
[
|
||||
"http",
|
||||
"tls",
|
||||
"quic",
|
||||
"fakedns",
|
||||
"fakedns+others"
|
||||
"tls",
|
||||
"quic",
|
||||
"fakedns",
|
||||
"fakedns+others"
|
||||
];
|
||||
|
||||
public static readonly List<int> TunMtus =
|
||||
@@ -504,88 +531,87 @@ public class Global
|
||||
public static readonly List<string> TunStacks =
|
||||
[
|
||||
"gvisor",
|
||||
"system",
|
||||
"mixed"
|
||||
"system",
|
||||
"mixed"
|
||||
];
|
||||
|
||||
public static readonly List<string> PresetMsgFilters =
|
||||
[
|
||||
"proxy",
|
||||
"direct",
|
||||
"block",
|
||||
""
|
||||
"direct",
|
||||
"block",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> SingboxMuxs =
|
||||
[
|
||||
"h2mux",
|
||||
"smux",
|
||||
"yamux",
|
||||
""
|
||||
"smux",
|
||||
"yamux",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> TuicCongestionControls =
|
||||
[
|
||||
"cubic",
|
||||
"new_reno",
|
||||
"bbr"
|
||||
"new_reno",
|
||||
"bbr"
|
||||
];
|
||||
|
||||
public static readonly List<string> allowSelectType =
|
||||
[
|
||||
"selector",
|
||||
"urltest",
|
||||
"loadbalance",
|
||||
"fallback"
|
||||
"urltest",
|
||||
"loadbalance",
|
||||
"fallback"
|
||||
];
|
||||
|
||||
public static readonly List<string> notAllowTestType =
|
||||
[
|
||||
"selector",
|
||||
"urltest",
|
||||
"direct",
|
||||
"reject",
|
||||
"compatible",
|
||||
"pass",
|
||||
"loadbalance",
|
||||
"fallback"
|
||||
"urltest",
|
||||
"direct",
|
||||
"reject",
|
||||
"compatible",
|
||||
"pass",
|
||||
"loadbalance",
|
||||
"fallback"
|
||||
];
|
||||
|
||||
public static readonly List<string> proxyVehicleType =
|
||||
[
|
||||
"file",
|
||||
"http"
|
||||
"http"
|
||||
];
|
||||
|
||||
public static readonly Dictionary<ECoreType, string> CoreUrls = new()
|
||||
{
|
||||
{ ECoreType.v2fly, "v2fly/v2ray-core" },
|
||||
{ ECoreType.v2fly_v5, "v2fly/v2ray-core" },
|
||||
{ ECoreType.Xray, "XTLS/Xray-core" },
|
||||
{ ECoreType.sing_box, "SagerNet/sing-box" },
|
||||
{ ECoreType.mihomo, "MetaCubeX/mihomo" },
|
||||
{ ECoreType.hysteria, "apernet/hysteria" },
|
||||
{ ECoreType.hysteria2, "apernet/hysteria" },
|
||||
{ ECoreType.naiveproxy, "klzgrad/naiveproxy" },
|
||||
{ ECoreType.tuic, "EAimTY/tuic" },
|
||||
{ ECoreType.juicity, "juicity/juicity" },
|
||||
{ ECoreType.brook, "txthinking/brook" },
|
||||
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
|
||||
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
|
||||
{ ECoreType.mieru, "enfein/mieru" },
|
||||
{ ECoreType.v2rayN, "2dust/v2rayN" },
|
||||
};
|
||||
{
|
||||
{ ECoreType.v2fly, "v2fly/v2ray-core" },
|
||||
{ ECoreType.v2fly_v5, "v2fly/v2ray-core" },
|
||||
{ ECoreType.Xray, "XTLS/Xray-core" },
|
||||
{ ECoreType.sing_box, "SagerNet/sing-box" },
|
||||
{ ECoreType.mihomo, "MetaCubeX/mihomo" },
|
||||
{ ECoreType.hysteria, "apernet/hysteria" },
|
||||
{ ECoreType.hysteria2, "apernet/hysteria" },
|
||||
{ ECoreType.naiveproxy, "klzgrad/naiveproxy" },
|
||||
{ ECoreType.tuic, "EAimTY/tuic" },
|
||||
{ ECoreType.juicity, "juicity/juicity" },
|
||||
{ ECoreType.brook, "txthinking/brook" },
|
||||
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
|
||||
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
|
||||
{ ECoreType.mieru, "enfein/mieru" },
|
||||
{ ECoreType.v2rayN, "2dust/v2rayN" },
|
||||
};
|
||||
|
||||
public static readonly List<string> OtherGeoUrls =
|
||||
[
|
||||
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat",
|
||||
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
|
||||
@"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"
|
||||
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
|
||||
@"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"
|
||||
];
|
||||
|
||||
public static readonly List<string> IPAPIUrls =
|
||||
[
|
||||
@"https://speed.cloudflare.com/meta",
|
||||
@"https://api.ip.sb/geoip",
|
||||
@"https://api-ipv4.ip.sb/geoip",
|
||||
@"https://api-ipv6.ip.sb/geoip",
|
||||
@@ -601,29 +627,37 @@ public class Global
|
||||
];
|
||||
|
||||
public static readonly Dictionary<string, List<string>> PredefinedHosts = new()
|
||||
{
|
||||
{ "dns.google", new List<string> { "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844" } },
|
||||
{ "dns.alidns.com", new List<string> { "223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1" } },
|
||||
{ "one.one.one.one", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
|
||||
{ "1dot1dot1dot1.cloudflare-dns.com", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
|
||||
{ "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } },
|
||||
{ "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } },
|
||||
{ "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
|
||||
{ "doh.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
|
||||
{ "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } },
|
||||
{ "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } },
|
||||
{ "dns.sb", new List<string> { "185.222.222.222", "2a09::" } },
|
||||
{ "dns.umbrella.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
|
||||
{ "dns.sse.cisco.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
|
||||
{ "engage.cloudflareclient.com", new List<string> { "162.159.192.1", "2606:4700:d0::a29f:c001" } }
|
||||
};
|
||||
{
|
||||
{ "dns.google", ["8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844"] },
|
||||
{ "dns.alidns.com", ["223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1"] },
|
||||
{ "one.one.one.one", ["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", ["104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9"] },
|
||||
{ "dns.cloudflare.com", ["104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5"] },
|
||||
{ "dot.pub", ["1.12.12.12", "120.53.53.53"] },
|
||||
{ "doh.pub", ["1.12.12.12", "120.53.53.53"] },
|
||||
{ "dns.quad9.net", ["9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9"] },
|
||||
{ "dns.yandex.net", ["77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff"] },
|
||||
{ "dns.sb", ["185.222.222.222", "2a09::"] },
|
||||
{ "dns.umbrella.com", ["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", ["162.159.192.1"] }
|
||||
};
|
||||
|
||||
public static readonly List<string> ExpectedIPs =
|
||||
[
|
||||
"geoip:cn",
|
||||
"geoip:ir",
|
||||
"geoip:ru",
|
||||
""
|
||||
"geoip:ir",
|
||||
"geoip:ru",
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> EchForceQuerys =
|
||||
[
|
||||
"none",
|
||||
"half",
|
||||
"full",
|
||||
""
|
||||
];
|
||||
|
||||
#endregion const
|
||||
|
||||
@@ -24,6 +24,7 @@ global using ServiceLib.Common;
|
||||
global using ServiceLib.Enums;
|
||||
global using ServiceLib.Events;
|
||||
global using ServiceLib.Handler;
|
||||
global using ServiceLib.Handler.Builder;
|
||||
global using ServiceLib.Handler.Fmt;
|
||||
global using ServiceLib.Handler.SysProxy;
|
||||
global using ServiceLib.Helper;
|
||||
|
||||
438
v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs
Normal file
438
v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs
Normal file
@@ -0,0 +1,438 @@
|
||||
namespace ServiceLib.Handler.Builder;
|
||||
|
||||
public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult)
|
||||
{
|
||||
public bool Success => ValidatorResult.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the results of a full context build, including the main context and an optional
|
||||
/// pre-socks context (e.g. for TUN protection or pre-socks chaining).
|
||||
/// </summary>
|
||||
public record CoreConfigContextBuilderAllResult(
|
||||
CoreConfigContextBuilderResult MainResult,
|
||||
CoreConfigContextBuilderResult? PreSocksResult)
|
||||
{
|
||||
/// <summary>True only when both the main result and (if present) the pre-socks result succeeded.</summary>
|
||||
public bool Success => MainResult.Success && (PreSocksResult?.Success ?? true);
|
||||
|
||||
/// <summary>
|
||||
/// Merges all errors and warnings from the main result and the optional pre-socks result
|
||||
/// into a single <see cref="NodeValidatorResult"/> for unified notification.
|
||||
/// </summary>
|
||||
public NodeValidatorResult CombinedValidatorResult => new(
|
||||
[.. MainResult.ValidatorResult.Errors, .. PreSocksResult?.ValidatorResult.Errors ?? []],
|
||||
[.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]);
|
||||
|
||||
/// <summary>
|
||||
/// The main context with TunProtectSsPort/ProxyRelaySsPort and ProtectDomainList merged in
|
||||
/// from the pre-socks result (if any). Pass this to the core runner.
|
||||
/// </summary>
|
||||
public CoreConfigContext ResolvedMainContext => PreSocksResult is not null
|
||||
? MainResult.Context with
|
||||
{
|
||||
TunProtectSsPort = PreSocksResult.Context.TunProtectSsPort,
|
||||
ProxyRelaySsPort = PreSocksResult.Context.ProxyRelaySsPort,
|
||||
ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []],
|
||||
}
|
||||
: MainResult.Context;
|
||||
}
|
||||
|
||||
public class CoreConfigContextBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a <see cref="CoreConfigContext"/> for the given node, resolves its proxy map,
|
||||
/// and processes outbound nodes referenced by routing rules.
|
||||
/// </summary>
|
||||
public static async Task<CoreConfigContextBuilderResult> Build(Config config, ProfileItem node)
|
||||
{
|
||||
var runCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||
var coreType = runCoreType == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray;
|
||||
var context = new CoreConfigContext()
|
||||
{
|
||||
Node = node,
|
||||
RunCoreType = runCoreType,
|
||||
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),
|
||||
};
|
||||
var validatorResult = NodeValidatorResult.Empty();
|
||||
var (actNode, nodeValidatorResult) = await ResolveNodeAsync(context, node);
|
||||
if (!nodeValidatorResult.Success)
|
||||
{
|
||||
return new CoreConfigContextBuilderResult(context, nodeValidatorResult);
|
||||
}
|
||||
context = context with { Node = actNode };
|
||||
validatorResult.Warnings.AddRange(nodeValidatorResult.Warnings);
|
||||
if (!(context.RoutingItem?.RuleSet.IsNullOrEmpty() ?? true))
|
||||
{
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(context.RoutingItem?.RuleSet) ?? [];
|
||||
foreach (var ruleItem in rules.Where(ruleItem => ruleItem.Enabled && !Global.OutboundTags.Contains(ruleItem.OutboundTag)))
|
||||
{
|
||||
if (ruleItem.OutboundTag.IsNullOrEmpty())
|
||||
{
|
||||
validatorResult.Warnings.Add(string.Format(ResUI.MsgRoutingRuleEmptyOutboundTag, ruleItem.Remarks));
|
||||
ruleItem.OutboundTag = Global.ProxyTag;
|
||||
continue;
|
||||
}
|
||||
var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag);
|
||||
if (ruleOutboundNode == null)
|
||||
{
|
||||
validatorResult.Warnings.Add(string.Format(ResUI.MsgRoutingRuleOutboundNodeNotFound, ruleItem.Remarks, ruleItem.OutboundTag));
|
||||
ruleItem.OutboundTag = Global.ProxyTag;
|
||||
continue;
|
||||
}
|
||||
|
||||
var (actRuleNode, ruleNodeValidatorResult) = await ResolveNodeAsync(context, ruleOutboundNode, false);
|
||||
validatorResult.Warnings.AddRange(ruleNodeValidatorResult.Warnings.Select(w =>
|
||||
string.Format(ResUI.MsgRoutingRuleOutboundNodeWarning, ruleItem.Remarks, ruleItem.OutboundTag, w)));
|
||||
if (!ruleNodeValidatorResult.Success)
|
||||
{
|
||||
validatorResult.Warnings.AddRange(ruleNodeValidatorResult.Errors.Select(e =>
|
||||
string.Format(ResUI.MsgRoutingRuleOutboundNodeError, ruleItem.Remarks, ruleItem.OutboundTag, e)));
|
||||
ruleItem.OutboundTag = Global.ProxyTag;
|
||||
continue;
|
||||
}
|
||||
|
||||
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode;
|
||||
}
|
||||
}
|
||||
|
||||
return new CoreConfigContextBuilderResult(context, validatorResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the main <see cref="CoreConfigContext"/> for <paramref name="node"/> and, when
|
||||
/// the main build succeeds, also builds the optional pre-socks context required for TUN
|
||||
/// protection or pre-socks proxy chaining.
|
||||
/// </summary>
|
||||
public static async Task<CoreConfigContextBuilderAllResult> BuildAll(Config config, ProfileItem node)
|
||||
{
|
||||
var mainResult = await Build(config, node);
|
||||
if (!mainResult.Success)
|
||||
{
|
||||
return new CoreConfigContextBuilderAllResult(mainResult, null);
|
||||
}
|
||||
|
||||
var preResult = await BuildPreSocksIfNeeded(mainResult.Context);
|
||||
return new CoreConfigContextBuilderAllResult(mainResult, preResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a pre-socks context is required for <paramref name="nodeContext"/>
|
||||
/// and, if so, builds and returns it. Returns <c>null</c> when no pre-socks core is needed.
|
||||
/// </summary>
|
||||
private static async Task<CoreConfigContextBuilderResult?> BuildPreSocksIfNeeded(CoreConfigContext nodeContext)
|
||||
{
|
||||
var config = nodeContext.AppConfig;
|
||||
var node = nodeContext.Node;
|
||||
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||
|
||||
var preSocksItem = ConfigHandler.GetPreSocksItem(config, node, coreType);
|
||||
if (preSocksItem != null)
|
||||
{
|
||||
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
|
||||
return preSocksResult with
|
||||
{
|
||||
Context = preSocksResult.Context with
|
||||
{
|
||||
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 preResult2 = await Build(nodeContext.AppConfig, preItem);
|
||||
return preResult2 with
|
||||
{
|
||||
Context = preResult2.Context with
|
||||
{
|
||||
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []],
|
||||
TunProtectSsPort = tunProtectSsPort,
|
||||
ProxyRelaySsPort = proxyRelaySsPort,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain.
|
||||
/// Returns the effective (possibly replaced) node and the validation result.
|
||||
/// </summary>
|
||||
public static async Task<(ProfileItem, NodeValidatorResult)> ResolveNodeAsync(CoreConfigContext context,
|
||||
ProfileItem node,
|
||||
bool includeSubChain = true)
|
||||
{
|
||||
if (node.IndexId.IsNullOrEmpty())
|
||||
{
|
||||
return (node, NodeValidatorResult.Empty());
|
||||
}
|
||||
|
||||
if (includeSubChain)
|
||||
{
|
||||
var (virtualChainNode, chainValidatorResult) = await BuildSubscriptionChainNodeAsync(node);
|
||||
if (virtualChainNode != null)
|
||||
{
|
||||
context.AllProxiesMap[virtualChainNode.IndexId] = virtualChainNode;
|
||||
var (resolvedNode, resolvedResult) = await ResolveNodeAsync(context, virtualChainNode, false);
|
||||
resolvedResult.Warnings.InsertRange(0, chainValidatorResult.Warnings);
|
||||
return (resolvedNode, resolvedResult);
|
||||
}
|
||||
// Chain not built but warnings may still exist (e.g. missing profiles)
|
||||
if (chainValidatorResult.Warnings.Count > 0)
|
||||
{
|
||||
var fillResult = await RegisterNodeAsync(context, node);
|
||||
fillResult.Warnings.InsertRange(0, chainValidatorResult.Warnings);
|
||||
return (node, fillResult);
|
||||
}
|
||||
}
|
||||
|
||||
var registerResult = await RegisterNodeAsync(context, node);
|
||||
return (node, registerResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the node's subscription defines prev/next profiles, creates a virtual
|
||||
/// <see cref="EConfigType.ProxyChain"/> node that wraps them together.
|
||||
/// Returns <c>null</c> as the chain item when no chain is needed.
|
||||
/// Any warnings (e.g. missing prev/next profile) are returned in the validator result.
|
||||
/// </summary>
|
||||
private static async Task<(ProfileItem? ChainNode, NodeValidatorResult ValidatorResult)> BuildSubscriptionChainNodeAsync(ProfileItem node)
|
||||
{
|
||||
var result = NodeValidatorResult.Empty();
|
||||
|
||||
if (node.Subid.IsNullOrEmpty() || node.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
return (null, result);
|
||||
}
|
||||
|
||||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||
if (subItem == null)
|
||||
{
|
||||
return (null, result);
|
||||
}
|
||||
|
||||
ProfileItem? prevNode = null;
|
||||
ProfileItem? nextNode = null;
|
||||
|
||||
if (!subItem.PrevProfile.IsNullOrEmpty())
|
||||
{
|
||||
prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||
if (prevNode == null)
|
||||
{
|
||||
result.Warnings.Add(string.Format(ResUI.MsgSubscriptionPrevProfileNotFound, subItem.PrevProfile));
|
||||
}
|
||||
}
|
||||
if (!subItem.NextProfile.IsNullOrEmpty())
|
||||
{
|
||||
nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||
if (nextNode == null)
|
||||
{
|
||||
result.Warnings.Add(string.Format(ResUI.MsgSubscriptionNextProfileNotFound, subItem.NextProfile));
|
||||
}
|
||||
}
|
||||
|
||||
if (prevNode is null && nextNode is null)
|
||||
{
|
||||
return (null, result);
|
||||
}
|
||||
|
||||
// Build new proxy chain node
|
||||
var chainNode = new ProfileItem()
|
||||
{
|
||||
IndexId = $"inner-{Utils.GetGuid(false)}",
|
||||
ConfigType = EConfigType.ProxyChain,
|
||||
CoreType = AppManager.Instance.GetCoreType(node, node.ConfigType),
|
||||
Remarks = node.Remarks,
|
||||
};
|
||||
List<string?> childItems = [prevNode?.IndexId, node.IndexId, nextNode?.IndexId];
|
||||
var chainExtraItem = chainNode.GetProtocolExtra() with
|
||||
{
|
||||
GroupType = chainNode.ConfigType.ToString(),
|
||||
ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())),
|
||||
};
|
||||
chainNode.SetProtocolExtra(chainExtraItem);
|
||||
return (chainNode, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches registration to either <see cref="RegisterGroupNodeAsync"/> or
|
||||
/// <see cref="RegisterSingleNodeAsync"/> based on the node's config type.
|
||||
/// </summary>
|
||||
private static async Task<NodeValidatorResult> RegisterNodeAsync(CoreConfigContext context, ProfileItem node)
|
||||
{
|
||||
if (node.ConfigType.IsGroupType())
|
||||
{
|
||||
return await RegisterGroupNodeAsync(context, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RegisterSingleNodeAsync(context, node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a single (non-group) node and, on success, adds it to the proxy map
|
||||
/// and records any domain addresses that should bypass the proxy.
|
||||
/// </summary>
|
||||
private static NodeValidatorResult RegisterSingleNodeAsync(CoreConfigContext context, ProfileItem node)
|
||||
{
|
||||
if (node.ConfigType.IsGroupType())
|
||||
{
|
||||
return NodeValidatorResult.Empty();
|
||||
}
|
||||
|
||||
var nodeValidatorResult = NodeValidator.Validate(node, context.RunCoreType);
|
||||
if (!nodeValidatorResult.Success)
|
||||
{
|
||||
return nodeValidatorResult;
|
||||
}
|
||||
|
||||
context.AllProxiesMap[node.IndexId] = node;
|
||||
|
||||
var address = node.Address;
|
||||
if (Utils.IsDomain(address))
|
||||
{
|
||||
context.ProtectDomainList.Add(address);
|
||||
}
|
||||
|
||||
if (!node.EchConfigList.IsNullOrEmpty())
|
||||
{
|
||||
var echQuerySni = node.Sni;
|
||||
if (node.StreamSecurity == Global.StreamSecurity
|
||||
&& node.EchConfigList?.Contains("://") == true)
|
||||
{
|
||||
var idx = node.EchConfigList.IndexOf('+');
|
||||
echQuerySni = idx > 0 ? node.EchConfigList[..idx] : node.Sni;
|
||||
}
|
||||
|
||||
if (Utils.IsDomain(echQuerySni))
|
||||
{
|
||||
context.ProtectDomainList.Add(echQuerySni);
|
||||
}
|
||||
}
|
||||
|
||||
return nodeValidatorResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entry point for registering a group node. Initialises the visited/ancestor sets
|
||||
/// and delegates to <see cref="TraverseGroupNodeAsync"/>.
|
||||
/// </summary>
|
||||
private static async Task<NodeValidatorResult> RegisterGroupNodeAsync(CoreConfigContext context,
|
||||
ProfileItem node)
|
||||
{
|
||||
if (!node.ConfigType.IsGroupType())
|
||||
{
|
||||
return NodeValidatorResult.Empty();
|
||||
}
|
||||
|
||||
HashSet<string> ancestors = [node.IndexId];
|
||||
HashSet<string> globalVisited = [node.IndexId];
|
||||
return await TraverseGroupNodeAsync(context, node, globalVisited, ancestors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively walks the children of a group node, registering valid leaf nodes
|
||||
/// and nested groups. Detects cycles via <paramref name="ancestorsGroup"/> and
|
||||
/// deduplicates shared nodes via <paramref name="globalVisitedGroup"/>.
|
||||
/// </summary>
|
||||
private static async Task<NodeValidatorResult> TraverseGroupNodeAsync(
|
||||
CoreConfigContext context,
|
||||
ProfileItem node,
|
||||
HashSet<string> globalVisitedGroup,
|
||||
HashSet<string> ancestorsGroup)
|
||||
{
|
||||
var (groupChildList, _) = await GroupProfileManager.GetChildProfileItems(node);
|
||||
List<string> childIndexIdList = [];
|
||||
var childNodeValidatorResult = NodeValidatorResult.Empty();
|
||||
foreach (var childNode in groupChildList)
|
||||
{
|
||||
if (ancestorsGroup.Contains(childNode.IndexId))
|
||||
{
|
||||
childNodeValidatorResult.Errors.Add(
|
||||
string.Format(ResUI.MsgGroupCycleDependency, node.Remarks, childNode.Remarks));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (globalVisitedGroup.Contains(childNode.IndexId))
|
||||
{
|
||||
childIndexIdList.Add(childNode.IndexId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!childNode.ConfigType.IsGroupType())
|
||||
{
|
||||
var childNodeResult = RegisterSingleNodeAsync(context, childNode);
|
||||
childNodeValidatorResult.Warnings.AddRange(childNodeResult.Warnings.Select(w =>
|
||||
string.Format(ResUI.MsgGroupChildNodeWarning, node.Remarks, childNode.Remarks, w)));
|
||||
childNodeValidatorResult.Errors.AddRange(childNodeResult.Errors.Select(e =>
|
||||
string.Format(ResUI.MsgGroupChildNodeError, node.Remarks, childNode.Remarks, e)));
|
||||
if (!childNodeResult.Success)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
globalVisitedGroup.Add(childNode.IndexId);
|
||||
childIndexIdList.Add(childNode.IndexId);
|
||||
continue;
|
||||
}
|
||||
|
||||
var newAncestorsGroup = new HashSet<string>(ancestorsGroup) { childNode.IndexId };
|
||||
var childGroupResult =
|
||||
await TraverseGroupNodeAsync(context, childNode, globalVisitedGroup, newAncestorsGroup);
|
||||
childNodeValidatorResult.Warnings.AddRange(childGroupResult.Warnings.Select(w =>
|
||||
string.Format(ResUI.MsgGroupChildGroupNodeWarning, node.Remarks, childNode.Remarks, w)));
|
||||
childNodeValidatorResult.Errors.AddRange(childGroupResult.Errors.Select(e =>
|
||||
string.Format(ResUI.MsgGroupChildGroupNodeError, node.Remarks, childNode.Remarks, e)));
|
||||
if (!childGroupResult.Success)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
globalVisitedGroup.Add(childNode.IndexId);
|
||||
childIndexIdList.Add(childNode.IndexId);
|
||||
}
|
||||
|
||||
if (childIndexIdList.Count == 0)
|
||||
{
|
||||
childNodeValidatorResult.Errors.Add(string.Format(ResUI.MsgGroupNoValidChildNode, node.Remarks));
|
||||
return childNodeValidatorResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
childNodeValidatorResult.Warnings.AddRange(childNodeValidatorResult.Errors);
|
||||
childNodeValidatorResult.Errors.Clear();
|
||||
}
|
||||
|
||||
node.SetProtocolExtra(node.GetProtocolExtra() with { ChildItems = Utils.List2String(childIndexIdList), });
|
||||
context.AllProxiesMap[node.IndexId] = node;
|
||||
return childNodeValidatorResult;
|
||||
}
|
||||
}
|
||||
177
v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs
Normal file
177
v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
namespace ServiceLib.Handler.Builder;
|
||||
|
||||
public record NodeValidatorResult(List<string> Errors, List<string> Warnings)
|
||||
{
|
||||
public bool Success => Errors.Count == 0;
|
||||
|
||||
public static NodeValidatorResult Empty()
|
||||
{
|
||||
return new NodeValidatorResult([], []);
|
||||
}
|
||||
}
|
||||
|
||||
public class NodeValidator
|
||||
{
|
||||
// Static validator rules
|
||||
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 static NodeValidatorResult Validate(ProfileItem item, ECoreType coreType)
|
||||
{
|
||||
var v = new ValidationContext();
|
||||
ValidateNodeAndCoreSupport(item, coreType, v);
|
||||
return v.ToResult();
|
||||
}
|
||||
|
||||
private class ValidationContext
|
||||
{
|
||||
public List<string> Errors { get; } = [];
|
||||
public List<string> Warnings { get; } = [];
|
||||
|
||||
public void Error(string message)
|
||||
{
|
||||
Errors.Add(message);
|
||||
}
|
||||
|
||||
public void Warning(string message)
|
||||
{
|
||||
Warnings.Add(message);
|
||||
}
|
||||
|
||||
public void Assert(bool condition, string errorMsg)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
Error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
public NodeValidatorResult ToResult()
|
||||
{
|
||||
return new NodeValidatorResult(Errors, Warnings);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateNodeAndCoreSupport(ProfileItem item, ECoreType coreType, ValidationContext v)
|
||||
{
|
||||
if (item.ConfigType is EConfigType.Custom)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.ConfigType.IsGroupType())
|
||||
{
|
||||
// Group logic is handled in ValidateGroupNode
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic Property Validation
|
||||
v.Assert(!item.Address.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Address"));
|
||||
v.Assert(item.Port is > 0 and <= 65535, string.Format(ResUI.MsgInvalidProperty, "Port"));
|
||||
|
||||
// Network & Core Logic
|
||||
var net = item.GetNetwork();
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
var transportError = ValidateSingboxTransport(item.ConfigType, net);
|
||||
if (transportError != null)
|
||||
{
|
||||
v.Error(transportError);
|
||||
}
|
||||
|
||||
if (!Global.SingboxSupportConfigType.Contains(item.ConfigType))
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgCoreNotSupportProtocol, nameof(ECoreType.sing_box), item.ConfigType));
|
||||
}
|
||||
}
|
||||
else if (coreType is ECoreType.Xray)
|
||||
{
|
||||
if (!Global.XraySupportConfigType.Contains(item.ConfigType))
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgCoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType));
|
||||
}
|
||||
}
|
||||
|
||||
// Protocol Specifics
|
||||
var protocolExtra = item.GetProtocolExtra();
|
||||
switch (item.ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
v.Assert(!item.Password.IsNullOrEmpty() && Utils.IsGuidByParse(item.Password),
|
||||
string.Format(ResUI.MsgInvalidProperty, "Password"));
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
v.Assert(
|
||||
!item.Password.IsNullOrEmpty()
|
||||
&& (Utils.IsGuidByParse(item.Password) || item.Password.Length <= 30),
|
||||
string.Format(ResUI.MsgInvalidProperty, "Password")
|
||||
);
|
||||
v.Assert(Global.Flows.Contains(protocolExtra.Flow ?? string.Empty),
|
||||
string.Format(ResUI.MsgInvalidProperty, "Flow"));
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
v.Assert(!item.Password.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Password"));
|
||||
v.Assert(
|
||||
!string.IsNullOrEmpty(protocolExtra.SsMethod) &&
|
||||
Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod),
|
||||
string.Format(ResUI.MsgInvalidProperty, "SsMethod"));
|
||||
break;
|
||||
}
|
||||
|
||||
// TLS & Security
|
||||
if (item.StreamSecurity == Global.StreamSecurity)
|
||||
{
|
||||
if (!item.Cert.IsNullOrEmpty() && CertPemManager.ParsePemChain(item.Cert).Count == 0 &&
|
||||
!item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, "TLS Certificate"));
|
||||
}
|
||||
}
|
||||
|
||||
if (item.StreamSecurity == Global.StreamSecurityReality)
|
||||
{
|
||||
v.Assert(!item.PublicKey.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "PublicKey"));
|
||||
}
|
||||
|
||||
if (item.Network == nameof(ETransport.xhttp) && !item.Extra.IsNullOrEmpty())
|
||||
{
|
||||
if (JsonUtils.ParseJson(item.Extra) is null)
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, "XHTTP Extra"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ValidateSingboxTransport(EConfigType configType, string net)
|
||||
{
|
||||
// sing-box does not support xhttp / kcp transports
|
||||
if (SingboxUnsupportedTransports.Contains(net))
|
||||
{
|
||||
return string.Format(ResUI.MsgCoreNotSupportNetwork, nameof(ECoreType.sing_box), net);
|
||||
}
|
||||
|
||||
// sing-box does not support non-tcp transports for protocols other than vmess/trojan/vless/shadowsocks
|
||||
if (!SingboxTransportSupportedProtocols.Contains(configType) && net != nameof(ETransport.tcp))
|
||||
{
|
||||
return string.Format(ResUI.MsgCoreNotSupportProtocolTransport,
|
||||
nameof(ECoreType.sing_box), configType.ToString(), net);
|
||||
}
|
||||
|
||||
// sing-box shadowsocks only supports tcp/ws/quic transports
|
||||
if (configType == EConfigType.Shadowsocks && !SingboxShadowsocksAllowedTransports.Contains(net))
|
||||
{
|
||||
return string.Format(ResUI.MsgCoreNotSupportProtocolTransport,
|
||||
nameof(ECoreType.sing_box), configType.ToString(), net);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,8 @@ public static class ConfigHandler
|
||||
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
||||
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
||||
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
||||
config.SimpleDNSItem.ServeStale ??= false;
|
||||
config.SimpleDNSItem.ParallelQuery ??= false;
|
||||
|
||||
config.SpeedTestItem ??= new();
|
||||
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
||||
@@ -152,6 +154,7 @@ public static class ConfigHandler
|
||||
DownMbps = 100
|
||||
};
|
||||
config.ClashUIItem ??= new();
|
||||
config.ClashUIItem.ConnectionsColumnItem ??= new();
|
||||
config.SystemProxyItem ??= new();
|
||||
config.WebDavItem ??= new();
|
||||
config.CheckUpdateItem ??= new();
|
||||
@@ -228,12 +231,9 @@ public static class ConfigHandler
|
||||
item.Remarks = profileItem.Remarks;
|
||||
item.Address = profileItem.Address;
|
||||
item.Port = profileItem.Port;
|
||||
item.Ports = profileItem.Ports;
|
||||
|
||||
item.Id = profileItem.Id;
|
||||
item.AlterId = profileItem.AlterId;
|
||||
item.Security = profileItem.Security;
|
||||
item.Flow = profileItem.Flow;
|
||||
item.Username = profileItem.Username;
|
||||
item.Password = profileItem.Password;
|
||||
|
||||
item.Network = profileItem.Network;
|
||||
item.HeaderType = profileItem.HeaderType;
|
||||
@@ -253,6 +253,11 @@ public static class ConfigHandler
|
||||
item.Extra = profileItem.Extra;
|
||||
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
|
||||
@@ -285,19 +290,22 @@ public static class ConfigHandler
|
||||
profileItem.ConfigType = EConfigType.VMess;
|
||||
|
||||
profileItem.Address = profileItem.Address.TrimEx();
|
||||
profileItem.Id = profileItem.Id.TrimEx();
|
||||
profileItem.Security = profileItem.Security.TrimEx();
|
||||
profileItem.Password = profileItem.Password.TrimEx();
|
||||
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||
{
|
||||
VmessSecurity = profileItem.GetProtocolExtra().VmessSecurity?.TrimEx()
|
||||
});
|
||||
profileItem.Network = profileItem.Network.TrimEx();
|
||||
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
|
||||
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
|
||||
profileItem.Path = profileItem.Path.TrimEx();
|
||||
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
|
||||
|
||||
if (!Global.VmessSecurities.Contains(profileItem.Security))
|
||||
if (!Global.VmessSecurities.Contains(profileItem.GetProtocolExtra().VmessSecurity))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (profileItem.Id.IsNullOrEmpty())
|
||||
if (profileItem.Password.IsNullOrEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -355,11 +363,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
|
||||
{
|
||||
await AddServerCommon(config, profileItem, true);
|
||||
@@ -605,14 +608,17 @@ public static class ConfigHandler
|
||||
profileItem.ConfigType = EConfigType.Shadowsocks;
|
||||
|
||||
profileItem.Address = profileItem.Address.TrimEx();
|
||||
profileItem.Id = profileItem.Id.TrimEx();
|
||||
profileItem.Security = profileItem.Security.TrimEx();
|
||||
profileItem.Password = profileItem.Password.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;
|
||||
}
|
||||
if (profileItem.Id.IsNullOrEmpty())
|
||||
if (profileItem.Password.IsNullOrEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -673,12 +679,12 @@ public static class ConfigHandler
|
||||
profileItem.ConfigType = EConfigType.Trojan;
|
||||
|
||||
profileItem.Address = profileItem.Address.TrimEx();
|
||||
profileItem.Id = profileItem.Id.TrimEx();
|
||||
profileItem.Password = profileItem.Password.TrimEx();
|
||||
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.StreamSecurity = Global.StreamSecurity;
|
||||
}
|
||||
if (profileItem.Id.IsNullOrEmpty())
|
||||
if (profileItem.Password.IsNullOrEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -700,21 +706,25 @@ public static class ConfigHandler
|
||||
public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true)
|
||||
{
|
||||
profileItem.ConfigType = EConfigType.Hysteria2;
|
||||
profileItem.CoreType = ECoreType.sing_box;
|
||||
//profileItem.CoreType = ECoreType.sing_box;
|
||||
|
||||
profileItem.Address = profileItem.Address.TrimEx();
|
||||
profileItem.Id = profileItem.Id.TrimEx();
|
||||
profileItem.Path = profileItem.Path.TrimEx();
|
||||
profileItem.Password = profileItem.Password.TrimEx();
|
||||
profileItem.Network = string.Empty;
|
||||
|
||||
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.StreamSecurity = Global.StreamSecurity;
|
||||
}
|
||||
if (profileItem.Id.IsNullOrEmpty())
|
||||
if (profileItem.Password.IsNullOrEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||
{
|
||||
SalamanderPass = profileItem.GetProtocolExtra().SalamanderPass?.TrimEx(),
|
||||
HopInterval = profileItem.GetProtocolExtra().HopInterval?.TrimEx(),
|
||||
});
|
||||
|
||||
await AddServerCommon(config, profileItem, toFile);
|
||||
|
||||
@@ -736,8 +746,8 @@ public static class ConfigHandler
|
||||
profileItem.CoreType = ECoreType.sing_box;
|
||||
|
||||
profileItem.Address = profileItem.Address.TrimEx();
|
||||
profileItem.Id = profileItem.Id.TrimEx();
|
||||
profileItem.Security = profileItem.Security.TrimEx();
|
||||
profileItem.Username = profileItem.Username.TrimEx();
|
||||
profileItem.Password = profileItem.Password.TrimEx();
|
||||
profileItem.Network = string.Empty;
|
||||
|
||||
if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType))
|
||||
@@ -753,7 +763,7 @@ public static class ConfigHandler
|
||||
{
|
||||
profileItem.Alpn = "h3";
|
||||
}
|
||||
if (profileItem.Id.IsNullOrEmpty())
|
||||
if (profileItem.Password.IsNullOrEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -776,17 +786,17 @@ public static class ConfigHandler
|
||||
profileItem.ConfigType = EConfigType.WireGuard;
|
||||
|
||||
profileItem.Address = profileItem.Address.TrimEx();
|
||||
profileItem.Id = profileItem.Id.TrimEx();
|
||||
profileItem.PublicKey = profileItem.PublicKey.TrimEx();
|
||||
profileItem.Path = profileItem.Path.TrimEx();
|
||||
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
|
||||
profileItem.Network = string.Empty;
|
||||
if (profileItem.ShortId.IsNullOrEmpty())
|
||||
profileItem.Password = profileItem.Password.TrimEx();
|
||||
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -810,14 +820,13 @@ public static class ConfigHandler
|
||||
profileItem.CoreType = ECoreType.sing_box;
|
||||
|
||||
profileItem.Address = profileItem.Address.TrimEx();
|
||||
profileItem.Id = profileItem.Id.TrimEx();
|
||||
profileItem.Security = profileItem.Security.TrimEx();
|
||||
profileItem.Password = profileItem.Password.TrimEx();
|
||||
profileItem.Network = string.Empty;
|
||||
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.StreamSecurity = Global.StreamSecurity;
|
||||
}
|
||||
if (profileItem.Id.IsNullOrEmpty())
|
||||
if (profileItem.Password.IsNullOrEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -836,7 +845,7 @@ public static class ConfigHandler
|
||||
/// <returns>0 if successful, -1 if failed</returns>
|
||||
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)
|
||||
{
|
||||
return -1;
|
||||
@@ -855,7 +864,7 @@ public static class ConfigHandler
|
||||
Remarks = t.Remarks,
|
||||
Address = t.Address,
|
||||
Port = t.Port,
|
||||
Security = t.Security,
|
||||
//Security = t.Security,
|
||||
Network = t.Network,
|
||||
StreamSecurity = t.StreamSecurity,
|
||||
Delay = t33?.Delay ?? 0,
|
||||
@@ -954,26 +963,25 @@ public static class ConfigHandler
|
||||
profileItem.ConfigType = EConfigType.VLESS;
|
||||
|
||||
profileItem.Address = profileItem.Address.TrimEx();
|
||||
profileItem.Id = profileItem.Id.TrimEx();
|
||||
profileItem.Security = profileItem.Security.TrimEx();
|
||||
profileItem.Password = profileItem.Password.TrimEx();
|
||||
profileItem.Network = profileItem.Network.TrimEx();
|
||||
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
|
||||
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
|
||||
profileItem.Path = profileItem.Path.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();
|
||||
}
|
||||
if (profileItem.Id.IsNullOrEmpty())
|
||||
VlessEncryption = vlessEncryption.IsNullOrEmpty() ? Global.None : vlessEncryption,
|
||||
Flow = Global.Flows.Contains(flow) ? flow : Global.Flows.First(),
|
||||
});
|
||||
|
||||
if (profileItem.Password.IsNullOrEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (profileItem.Security.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.Security = Global.None;
|
||||
}
|
||||
|
||||
await AddServerCommon(config, profileItem, toFile);
|
||||
|
||||
@@ -1028,12 +1036,12 @@ public static class ConfigHandler
|
||||
/// <returns>0 if successful</returns>
|
||||
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 != Global.StreamSecurity
|
||||
&& profileItem.StreamSecurity != Global.StreamSecurityReality)
|
||||
if (profileItem.StreamSecurity is not Global.StreamSecurity
|
||||
and not Global.StreamSecurityReality)
|
||||
{
|
||||
profileItem.StreamSecurity = string.Empty;
|
||||
}
|
||||
@@ -1072,42 +1080,12 @@ public static class ConfigHandler
|
||||
|
||||
if (toFile)
|
||||
{
|
||||
profileItem.SetProtocolExtra();
|
||||
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
|
||||
}
|
||||
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>
|
||||
/// Compare two profile items to determine if they represent the same server
|
||||
/// Used for deduplication and server matching
|
||||
@@ -1123,22 +1101,29 @@ public static class ConfigHandler
|
||||
return false;
|
||||
}
|
||||
|
||||
var oProtocolExtra = o.GetProtocolExtra();
|
||||
var nProtocolExtra = n.GetProtocolExtra();
|
||||
|
||||
return o.ConfigType == n.ConfigType
|
||||
&& AreEqual(o.Address, n.Address)
|
||||
&& o.Port == n.Port
|
||||
&& AreEqual(o.Id, n.Id)
|
||||
&& AreEqual(o.Security, n.Security)
|
||||
&& AreEqual(o.Password, n.Password)
|
||||
&& AreEqual(oProtocolExtra.VlessEncryption, nProtocolExtra.VlessEncryption)
|
||||
&& AreEqual(oProtocolExtra.SsMethod, nProtocolExtra.SsMethod)
|
||||
&& AreEqual(oProtocolExtra.VmessSecurity, nProtocolExtra.VmessSecurity)
|
||||
&& AreEqual(o.Network, n.Network)
|
||||
&& AreEqual(o.HeaderType, n.HeaderType)
|
||||
&& AreEqual(o.RequestHost, n.RequestHost)
|
||||
&& AreEqual(o.Path, n.Path)
|
||||
&& (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.Alpn, n.Alpn)
|
||||
&& AreEqual(o.Fingerprint, n.Fingerprint)
|
||||
&& AreEqual(o.PublicKey, n.PublicKey)
|
||||
&& AreEqual(o.ShortId, n.ShortId)
|
||||
&& AreEqual(o.Finalmask, n.Finalmask)
|
||||
&& (!remarks || o.Remarks == n.Remarks);
|
||||
|
||||
static bool AreEqual(string? a, string? b)
|
||||
@@ -1180,46 +1165,28 @@ public static class ConfigHandler
|
||||
|
||||
/// <summary>
|
||||
/// Create a group server that combines multiple servers for load balancing
|
||||
/// Generates a configuration file that references multiple servers
|
||||
/// Generates a PolicyGroup profile with references to the sub-items
|
||||
/// </summary>
|
||||
/// <param name="config">Current configuration</param>
|
||||
/// <param name="selecteds">Selected servers to combine</param>
|
||||
/// <param name="coreType">Core type to use (Xray or sing_box)</param>
|
||||
/// <param name="multipleLoad">Load balancing algorithm</param>
|
||||
/// <param name="subItem">Sub-item for grouping</param>
|
||||
/// <returns>Result object with success state and data</returns>
|
||||
public static async Task<RetResult> AddGroupServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad, string? subId)
|
||||
public static async Task<RetResult> AddGroupAllServer(Config config, SubItem? subItem)
|
||||
{
|
||||
var result = new RetResult();
|
||||
|
||||
var indexId = Utils.GetGuid(false);
|
||||
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
|
||||
var subId = subItem?.Id;
|
||||
if (subId.IsNullOrEmpty())
|
||||
{
|
||||
result.Success = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId)).Remarks} ";
|
||||
if (coreType == ECoreType.Xray)
|
||||
{
|
||||
remark += multipleLoad switch
|
||||
{
|
||||
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing,
|
||||
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback,
|
||||
EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom,
|
||||
EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
|
||||
EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad,
|
||||
_ => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
|
||||
};
|
||||
}
|
||||
else if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
remark += multipleLoad switch
|
||||
{
|
||||
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
|
||||
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback,
|
||||
_ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
|
||||
};
|
||||
}
|
||||
var indexId = Utils.GetGuid(false);
|
||||
var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup}";
|
||||
var profile = new ProfileItem
|
||||
{
|
||||
IndexId = indexId,
|
||||
CoreType = coreType,
|
||||
CoreType = ECoreType.Xray,
|
||||
ConfigType = EConfigType.PolicyGroup,
|
||||
Remarks = remark,
|
||||
IsSub = false
|
||||
@@ -1228,18 +1195,106 @@ public static class ConfigHandler
|
||||
{
|
||||
profile.Subid = subId;
|
||||
}
|
||||
var profileGroup = new ProfileGroupItem
|
||||
var extraItem = new ProtocolExtraItem
|
||||
{
|
||||
ChildItems = childProfileIndexId,
|
||||
MultipleLoad = multipleLoad,
|
||||
IndexId = indexId,
|
||||
MultipleLoad = EMultipleLoad.LeastPing,
|
||||
GroupType = profile.ConfigType.ToString(),
|
||||
SubChildItems = subId,
|
||||
Filter = Global.PolicyGroupDefaultAllFilter,
|
||||
};
|
||||
var ret = await AddGroupServerCommon(config, profile, profileGroup, true);
|
||||
profile.SetProtocolExtra(extraItem);
|
||||
var ret = await AddServerCommon(config, profile, true);
|
||||
result.Success = ret == 0;
|
||||
result.Data = indexId;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string CombineWithDefaultAllFilter(string regionPattern)
|
||||
{
|
||||
return $"^(?!.*(?:{Global.PolicyGroupExcludeKeywords})).*(?:{regionPattern}).*$";
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> PolicyGroupRegionFilters = new()
|
||||
{
|
||||
{ "JP", "日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan" },
|
||||
{ "US", "美国|\\b[Uu][Ss]\\b|🇺🇸|[Uu]nited [Ss]tates|\\b[Uu][Ss][Aa]\\b" },
|
||||
{ "HK", "香港|\\b[Hh][Kk]\\b|🇭🇰|[Hh]ong ?[Kk]ong" },
|
||||
{ "TW", "台湾|台灣|\\b[Tt][Ww]\\b|🇹🇼|[Tt]aiwan" },
|
||||
{ "KR", "韩国|\\b[Kk][Rr]\\b|🇰🇷|[Kk]orea" },
|
||||
{ "SG", "新加坡|\\b[Ss][Gg]\\b|🇸🇬|[Ss]ingapore" },
|
||||
{ "DE", "德国|\\b[Dd][Ee]\\b|🇩🇪|[Gg]ermany" },
|
||||
{ "FR", "法国|\\b[Ff][Rr]\\b|🇫🇷|[Ff]rance" },
|
||||
{ "GB", "英国|\\b[Gg][Bb]\\b|🇬🇧|[Uu]nited [Kk]ingdom|[Bb]ritain" },
|
||||
{ "CA", "加拿大|🇨🇦|[Cc]anada" },
|
||||
{ "AU", "澳大利亚|\\b[Aa][Uu]\\b|🇦🇺|[Aa]ustralia" },
|
||||
{ "RU", "俄罗斯|\\b[Rr][Uu]\\b|🇷🇺|[Rr]ussia" },
|
||||
{ "BR", "巴西|\\b[Bb][Rr]\\b|🇧🇷|[Bb]razil" },
|
||||
{ "IN", "印度|🇮🇳|[Ii]ndia" },
|
||||
{ "VN", "越南|\\b[Vv][Nn]\\b|🇻🇳|[Vv]ietnam" },
|
||||
{ "ID", "印度尼西亚|\\b[Ii][Dd]\\b|🇮🇩|[Ii]ndonesia" },
|
||||
{ "MX", "墨西哥|\\b[Mm][Xx]\\b|🇲🇽|[Mm]exico" }
|
||||
};
|
||||
|
||||
public static async Task<RetResult> AddGroupRegionServer(Config config, SubItem? subItem)
|
||||
{
|
||||
var result = new RetResult();
|
||||
var subId = subItem?.Id;
|
||||
if (subId.IsNullOrEmpty())
|
||||
{
|
||||
result.Success = false;
|
||||
return result;
|
||||
}
|
||||
var childProfiles = await AppManager.Instance.ProfileItems(subId);
|
||||
List<string> indexIdList = [];
|
||||
|
||||
foreach (var regionFilter in PolicyGroupRegionFilters)
|
||||
{
|
||||
var indexId = Utils.GetGuid(false);
|
||||
var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup} - {regionFilter.Key}";
|
||||
var profile = new ProfileItem
|
||||
{
|
||||
IndexId = indexId,
|
||||
CoreType = ECoreType.Xray,
|
||||
ConfigType = EConfigType.PolicyGroup,
|
||||
Remarks = remark,
|
||||
IsSub = false
|
||||
};
|
||||
if (!subId.IsNullOrEmpty())
|
||||
{
|
||||
profile.Subid = subId;
|
||||
}
|
||||
var extraItem = new ProtocolExtraItem
|
||||
{
|
||||
MultipleLoad = EMultipleLoad.LeastPing,
|
||||
GroupType = profile.ConfigType.ToString(),
|
||||
SubChildItems = subId,
|
||||
Filter = CombineWithDefaultAllFilter(regionFilter.Value),
|
||||
};
|
||||
profile.SetProtocolExtra(extraItem);
|
||||
|
||||
var matchedChildProfiles = childProfiles?.Where(p =>
|
||||
p != null &&
|
||||
p.IsValid() &&
|
||||
!p.ConfigType.IsComplexType() &&
|
||||
(extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter))
|
||||
)
|
||||
.ToList() ?? [];
|
||||
if (matchedChildProfiles.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var ret = await AddServerCommon(config, profile, true);
|
||||
if (ret == 0)
|
||||
{
|
||||
indexIdList.Add(indexId);
|
||||
}
|
||||
}
|
||||
result.Success = indexIdList.Count > 0;
|
||||
result.Data = indexIdList;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a SOCKS server profile for pre-SOCKS functionality
|
||||
/// Used when TUN mode is enabled or when a custom config has a pre-SOCKS port
|
||||
@@ -1248,41 +1303,21 @@ public static class ConfigHandler
|
||||
/// <param name="node">Server node that might need pre-SOCKS</param>
|
||||
/// <param name="coreType">Core type being used</param>
|
||||
/// <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;
|
||||
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;
|
||||
itemSocks = new ProfileItem()
|
||||
{
|
||||
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()
|
||||
{
|
||||
CoreType = preCoreType,
|
||||
ConfigType = EConfigType.SOCKS,
|
||||
Address = Global.Loopback,
|
||||
Port = node.PreSocksPort.Value,
|
||||
};
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
CoreType = preCoreType,
|
||||
ConfigType = EConfigType.SOCKS,
|
||||
Address = Global.Loopback,
|
||||
Port = node.PreSocksPort.Value,
|
||||
};
|
||||
return itemSocks;
|
||||
}
|
||||
|
||||
@@ -1295,7 +1330,8 @@ public static class ConfigHandler
|
||||
/// <returns>Number of removed servers or -1 if failed</returns>
|
||||
public static async Task<int> RemoveInvalidServerResult(Config config, string subid)
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileItems(subid, "");
|
||||
var lstModel = await AppManager.Instance.ProfileModels(subid, "");
|
||||
lstModel.RemoveAll(t => t.ConfigType.IsComplexType());
|
||||
if (lstModel is { Count: <= 0 })
|
||||
{
|
||||
return -1;
|
||||
|
||||
@@ -7,27 +7,27 @@ public static class 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 result = new RetResult();
|
||||
var node = context.Node;
|
||||
|
||||
if (node.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
result = node.CoreType switch
|
||||
{
|
||||
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
|
||||
ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName),
|
||||
_ => await GenerateClientCustomConfig(node, fileName)
|
||||
};
|
||||
}
|
||||
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||
else if (context.RunCoreType == ECoreType.sing_box)
|
||||
{
|
||||
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
|
||||
result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node);
|
||||
result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||
}
|
||||
if (result.Success != true)
|
||||
{
|
||||
@@ -93,13 +93,29 @@ public static class CoreConfigHandler
|
||||
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType)
|
||||
{
|
||||
var result = new RetResult();
|
||||
var dummyNode = new ProfileItem
|
||||
{
|
||||
CoreType = coreType
|
||||
};
|
||||
var builderResult = await CoreConfigContextBuilder.Build(config, dummyNode);
|
||||
var context = builderResult.Context;
|
||||
foreach (var testItem in selecteds)
|
||||
{
|
||||
var node = testItem.Profile;
|
||||
var (actNode, _) = await CoreConfigContextBuilder.ResolveNodeAsync(context, node, true);
|
||||
if (node.IndexId == actNode.IndexId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
context.ServerTestItemMap[node.IndexId] = actNode.IndexId;
|
||||
}
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds);
|
||||
result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(selecteds);
|
||||
}
|
||||
else if (coreType == ECoreType.Xray)
|
||||
{
|
||||
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds);
|
||||
result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(selecteds);
|
||||
}
|
||||
if (result.Success != true)
|
||||
{
|
||||
@@ -109,20 +125,20 @@ public static class CoreConfigHandler
|
||||
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 initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
|
||||
testItem.Port = port;
|
||||
|
||||
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
|
||||
if (context.RunCoreType == ECoreType.sing_box)
|
||||
{
|
||||
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
|
||||
result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port);
|
||||
result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(port);
|
||||
}
|
||||
if (result.Success != true)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ public class AnytlsFmt : BaseFmt
|
||||
Port = parsedUrl.Port,
|
||||
};
|
||||
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||
item.Id = rawUserInfo;
|
||||
item.Password = rawUserInfo;
|
||||
|
||||
var query = Utils.ParseQueryString(parsedUrl.Query);
|
||||
ResolveUriQuery(query, ref item);
|
||||
@@ -39,7 +39,7 @@ public class AnytlsFmt : BaseFmt
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var pw = item.Id;
|
||||
var pw = item.Password;
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
ToUriQuery(item, Global.None, ref dicQuery);
|
||||
|
||||
|
||||
@@ -21,11 +21,6 @@ public class BaseFmt
|
||||
|
||||
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())
|
||||
{
|
||||
dicQuery.Add("security", item.StreamSecurity);
|
||||
@@ -70,6 +65,27 @@ public class BaseFmt
|
||||
}
|
||||
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));
|
||||
|
||||
@@ -118,7 +134,16 @@ public class BaseFmt
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -191,7 +216,6 @@ public class BaseFmt
|
||||
|
||||
protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item)
|
||||
{
|
||||
item.Flow = GetQueryValue(query, "flow");
|
||||
item.StreamSecurity = GetQueryValue(query, "security");
|
||||
item.Sni = GetQueryValue(query, "sni");
|
||||
item.Alpn = GetQueryDecoded(query, "alpn");
|
||||
@@ -200,6 +224,26 @@ public class BaseFmt
|
||||
item.ShortId = GetQueryDecoded(query, "sid");
|
||||
item.SpiderX = GetQueryDecoded(query, "spx");
|
||||
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
|
||||
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"))
|
||||
{
|
||||
@@ -237,7 +281,21 @@ public class BaseFmt
|
||||
item.RequestHost = GetQueryDecoded(query, "host");
|
||||
item.Path = GetQueryDecoded(query, "path", "/");
|
||||
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;
|
||||
|
||||
case nameof(ETransport.http):
|
||||
|
||||
@@ -19,12 +19,19 @@ public class Hysteria2Fmt : BaseFmt
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
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);
|
||||
ResolveUriQuery(query, ref item);
|
||||
item.Path = GetQueryDecoded(query, "obfs-password");
|
||||
item.Ports = GetQueryDecoded(query, "mport");
|
||||
if (item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||
}
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
Ports = GetQueryDecoded(query, "mport"),
|
||||
SalamanderPass = GetQueryDecoded(query, "obfs-password"),
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -45,18 +52,29 @@ public class Hysteria2Fmt : BaseFmt
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
ToUriQueryLite(item, ref dicQuery);
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
|
||||
if (item.Path.IsNotEmpty())
|
||||
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("obfs", "salamander");
|
||||
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path));
|
||||
dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass));
|
||||
}
|
||||
if (item.Ports.IsNotEmpty())
|
||||
if (!protocolExtraItem.Ports.IsNullOrEmpty())
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -12,7 +12,8 @@ public class ShadowsocksFmt : BaseFmt
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -40,7 +41,7 @@ public class ShadowsocksFmt : BaseFmt
|
||||
// item.port);
|
||||
//url = Utile.Base64Encode(url);
|
||||
//new Sip002
|
||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
|
||||
var pw = Utils.Base64Encode($"{item.GetProtocolExtra().SsMethod}:{item.Password}", true);
|
||||
|
||||
// plugin
|
||||
var plugin = string.Empty;
|
||||
@@ -57,7 +58,10 @@ public class ShadowsocksFmt : BaseFmt
|
||||
{
|
||||
pluginArgs += "mode=websocket;";
|
||||
pluginArgs += $"host={item.RequestHost};";
|
||||
pluginArgs += $"path={item.Path};";
|
||||
// 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))
|
||||
{
|
||||
@@ -75,8 +79,6 @@ public class ShadowsocksFmt : BaseFmt
|
||||
|
||||
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
|
||||
|
||||
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||
base64Content = base64Content.Replace("=", "\\=");
|
||||
|
||||
pluginArgs += $"certRaw={base64Content};";
|
||||
@@ -85,6 +87,7 @@ public class ShadowsocksFmt : BaseFmt
|
||||
if (pluginArgs.Length > 0)
|
||||
{
|
||||
plugin = "v2ray-plugin";
|
||||
pluginArgs += "mux=0;";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +137,8 @@ public class ShadowsocksFmt : BaseFmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.Security = details.Groups["method"].Value;
|
||||
item.Id = details.Groups["password"].Value;
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = details.Groups["method"].Value });
|
||||
item.Password = details.Groups["password"].Value;
|
||||
item.Address = details.Groups["hostname"].Value;
|
||||
item.Port = details.Groups["port"].Value.ToInt();
|
||||
return item;
|
||||
@@ -164,8 +167,8 @@ public class ShadowsocksFmt : BaseFmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.Security = userInfoParts.First();
|
||||
item.Id = Utils.UrlDecode(userInfoParts.Last());
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
|
||||
item.Password = Utils.UrlDecode(userInfoParts.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -176,8 +179,8 @@ public class ShadowsocksFmt : BaseFmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.Security = userInfoParts.First();
|
||||
item.Id = userInfoParts.Last();
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
|
||||
item.Password = userInfoParts.Last();
|
||||
}
|
||||
|
||||
var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
|
||||
@@ -222,6 +225,7 @@ public class ShadowsocksFmt : BaseFmt
|
||||
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")
|
||||
@@ -230,10 +234,13 @@ public class ShadowsocksFmt : BaseFmt
|
||||
if (!host.IsNullOrEmpty())
|
||||
{
|
||||
item.RequestHost = host.Replace("host=", "");
|
||||
item.Sni = item.RequestHost;
|
||||
}
|
||||
if (!path.IsNullOrEmpty())
|
||||
{
|
||||
item.Path = path.Replace("path=", "");
|
||||
var pathValue = path.Replace("path=", "");
|
||||
pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
|
||||
item.Path = pathValue;
|
||||
}
|
||||
}
|
||||
else if (modeValue == "quic")
|
||||
@@ -257,9 +264,18 @@ public class ShadowsocksFmt : BaseFmt
|
||||
item.Cert = certPem;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mux.IsNullOrEmpty())
|
||||
{
|
||||
var muxValue = mux.Replace("mux=", "");
|
||||
var muxCount = muxValue.ToInt();
|
||||
if (muxCount > 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -284,11 +300,11 @@ public class ShadowsocksFmt : BaseFmt
|
||||
var ssItem = new ProfileItem()
|
||||
{
|
||||
Remarks = it.remarks,
|
||||
Security = it.method,
|
||||
Id = it.password,
|
||||
Password = it.password,
|
||||
Address = it.server,
|
||||
Port = it.server_port.ToInt()
|
||||
};
|
||||
ssItem.SetProtocolExtra(new ProtocolExtraItem() { SsMethod = it.method });
|
||||
lst.Add(ssItem);
|
||||
}
|
||||
return lst;
|
||||
|
||||
@@ -33,7 +33,7 @@ public class SocksFmt : BaseFmt
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
//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);
|
||||
}
|
||||
|
||||
@@ -78,9 +78,8 @@ public class SocksFmt : BaseFmt
|
||||
}
|
||||
item.Address = arr1[1][..indexPort];
|
||||
item.Port = arr1[1][(indexPort + 1)..].ToInt();
|
||||
item.Security = arr21.First();
|
||||
item.Id = arr21[1];
|
||||
|
||||
item.Username = arr21.First();
|
||||
item.Password = arr21[1];
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -98,15 +97,14 @@ public class SocksFmt : BaseFmt
|
||||
Address = parsedUrl.IdnHost,
|
||||
Port = parsedUrl.Port,
|
||||
};
|
||||
|
||||
// parse base64 UserInfo
|
||||
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
|
||||
var userInfo = Utils.Base64Decode(rawUserInfo);
|
||||
var userInfoParts = userInfo.Split([':'], 2);
|
||||
if (userInfoParts.Length == 2)
|
||||
{
|
||||
item.Security = userInfoParts.First();
|
||||
item.Id = userInfoParts[1];
|
||||
item.Username = userInfoParts.First();
|
||||
item.Password = userInfoParts[1];
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
@@ -20,9 +20,10 @@ public class TrojanFmt : BaseFmt
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
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);
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with { Flow = GetQueryValue(query, "flow") });
|
||||
ResolveUriQuery(query, ref item);
|
||||
|
||||
return item;
|
||||
@@ -40,8 +41,12 @@ public class TrojanFmt : BaseFmt
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
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,8 +24,8 @@ public class TuicFmt : BaseFmt
|
||||
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
|
||||
if (userInfoParts.Length == 2)
|
||||
{
|
||||
item.Id = userInfoParts.First();
|
||||
item.Security = userInfoParts.Last();
|
||||
item.Username = userInfoParts.First();
|
||||
item.Password = userInfoParts.Last();
|
||||
}
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
@@ -53,6 +53,6 @@ public class TuicFmt : BaseFmt
|
||||
|
||||
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()
|
||||
{
|
||||
ConfigType = EConfigType.VLESS,
|
||||
Security = Global.None
|
||||
};
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
@@ -21,10 +20,14 @@ public class VLESSFmt : BaseFmt
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
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);
|
||||
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");
|
||||
ResolveUriQuery(query, ref item);
|
||||
|
||||
@@ -44,16 +47,14 @@ public class VLESSFmt : BaseFmt
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
dicQuery.Add("encryption", Global.None);
|
||||
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
|
||||
}
|
||||
ToUriQuery(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;
|
||||
}
|
||||
|
||||
var vmessQRCode = new VmessQRCode
|
||||
{
|
||||
v = item.ConfigVersion,
|
||||
v = 2,
|
||||
ps = item.Remarks.TrimEx(),
|
||||
add = item.Address,
|
||||
port = item.Port,
|
||||
id = item.Id,
|
||||
aid = item.AlterId,
|
||||
scy = item.Security,
|
||||
id = item.Password,
|
||||
aid = int.TryParse(item.GetProtocolExtra()?.AlterId, out var result) ? result : 0,
|
||||
scy = item.GetProtocolExtra().VmessSecurity ?? "",
|
||||
net = item.Network,
|
||||
type = item.HeaderType,
|
||||
host = item.RequestHost,
|
||||
@@ -71,15 +72,16 @@ public class VmessFmt : BaseFmt
|
||||
item.Network = Global.DefaultNetwork;
|
||||
item.HeaderType = Global.None;
|
||||
|
||||
item.ConfigVersion = vmessQRCode.v;
|
||||
//item.ConfigVersion = vmessQRCode.v;
|
||||
item.Remarks = Utils.ToString(vmessQRCode.ps);
|
||||
item.Address = Utils.ToString(vmessQRCode.add);
|
||||
item.Port = vmessQRCode.port;
|
||||
item.Id = Utils.ToString(vmessQRCode.id);
|
||||
item.AlterId = vmessQRCode.aid;
|
||||
item.Security = Utils.ToString(vmessQRCode.scy);
|
||||
|
||||
item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity;
|
||||
item.Password = Utils.ToString(vmessQRCode.id);
|
||||
item.SetProtocolExtra(new ProtocolExtraItem
|
||||
{
|
||||
AlterId = vmessQRCode.aid.ToString(),
|
||||
VmessSecurity = vmessQRCode.scy.IsNullOrEmpty() ? Global.DefaultSecurity : vmessQRCode.scy,
|
||||
});
|
||||
if (vmessQRCode.net.IsNotEmpty())
|
||||
{
|
||||
item.Network = vmessQRCode.net;
|
||||
@@ -105,7 +107,6 @@ public class VmessFmt : BaseFmt
|
||||
var item = new ProfileItem
|
||||
{
|
||||
ConfigType = EConfigType.VMess,
|
||||
Security = "auto"
|
||||
};
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
@@ -117,7 +118,12 @@ public class VmessFmt : BaseFmt
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
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);
|
||||
ResolveUriQuery(query, ref item);
|
||||
|
||||
@@ -20,14 +20,17 @@ public class WireguardFmt : BaseFmt
|
||||
item.Address = url.IdnHost;
|
||||
item.Port = url.Port;
|
||||
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);
|
||||
|
||||
item.PublicKey = GetQueryDecoded(query, "publickey");
|
||||
item.Path = GetQueryDecoded(query, "reserved");
|
||||
item.RequestHost = GetQueryDecoded(query, "address");
|
||||
item.ShortId = GetQueryDecoded(query, "mtu");
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
WgPublicKey = GetQueryDecoded(query, "publickey"),
|
||||
WgReserved = GetQueryDecoded(query, "reserved"),
|
||||
WgInterfaceAddress = GetQueryDecoded(query, "address"),
|
||||
WgMtu = int.TryParse(GetQueryDecoded(query, "mtu"), out var mtuVal) ? mtuVal : 1280,
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -46,22 +49,19 @@ public class WireguardFmt : BaseFmt
|
||||
}
|
||||
|
||||
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.ShortId));
|
||||
}
|
||||
return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,13 @@ public class DownloaderHelper
|
||||
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
Timeout = timeout * 1000,
|
||||
BlockTimeout = timeout * 1000,
|
||||
MaxTryAgainOnFailure = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Headers = headers,
|
||||
UserAgent = userAgent,
|
||||
Timeout = timeout * 1000,
|
||||
ConnectTimeout = timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
};
|
||||
@@ -62,37 +62,34 @@ public class DownloaderHelper
|
||||
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
Timeout = timeout * 1000,
|
||||
BlockTimeout = timeout * 1000,
|
||||
MaxTryAgainOnFailure = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Timeout= timeout * 1000,
|
||||
ConnectTimeout= timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
};
|
||||
|
||||
var totalDatetime = DateTime.Now;
|
||||
var totalSecond = 0;
|
||||
var lastUpdateTime = DateTime.Now;
|
||||
var hasValue = false;
|
||||
double maxSpeed = 0;
|
||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||
//downloader.DownloadStarted += (sender, value) =>
|
||||
//{
|
||||
// if (progress != null)
|
||||
// {
|
||||
// progress.Report("Start download data...");
|
||||
// }
|
||||
//};
|
||||
|
||||
downloader.DownloadProgressChanged += (sender, value) =>
|
||||
{
|
||||
var ts = DateTime.Now - totalDatetime;
|
||||
if (progress != null && ts.Seconds > totalSecond)
|
||||
if (progress != null && value.BytesPerSecondSpeed > 0)
|
||||
{
|
||||
hasValue = true;
|
||||
totalSecond = ts.Seconds;
|
||||
if (value.BytesPerSecondSpeed > maxSpeed)
|
||||
{
|
||||
maxSpeed = value.BytesPerSecondSpeed;
|
||||
}
|
||||
|
||||
var ts = DateTime.Now - lastUpdateTime;
|
||||
if (ts.TotalMilliseconds >= 1000)
|
||||
{
|
||||
lastUpdateTime = DateTime.Now;
|
||||
var speed = (maxSpeed / 1000 / 1000).ToString("#0.0");
|
||||
progress.Report(speed);
|
||||
}
|
||||
@@ -102,10 +99,19 @@ public class DownloaderHelper
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
progress.Report("0");
|
||||
}
|
||||
}
|
||||
};
|
||||
//progress.Report("......");
|
||||
@@ -133,11 +139,11 @@ public class DownloaderHelper
|
||||
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
Timeout = timeout * 1000,
|
||||
BlockTimeout = timeout * 1000,
|
||||
MaxTryAgainOnFailure = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Timeout= timeout * 1000,
|
||||
ConnectTimeout= timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,323 +0,0 @@
|
||||
namespace ServiceLib.Manager;
|
||||
|
||||
/// <summary>
|
||||
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
|
||||
/// </summary>
|
||||
public class ActionPrecheckManager(Config config)
|
||||
{
|
||||
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
|
||||
public static ActionPrecheckManager Instance => _instance.Value;
|
||||
|
||||
private readonly Config _config = config;
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (indexId.IsNullOrEmpty())
|
||||
{
|
||||
return [ResUI.PleaseSelectServer];
|
||||
}
|
||||
|
||||
var item = await AppManager.Instance.GetProfileItem(indexId);
|
||||
if (item is null)
|
||||
{
|
||||
return [ResUI.PleaseSelectServer];
|
||||
}
|
||||
|
||||
return await Check(item);
|
||||
}
|
||||
|
||||
public async Task<List<string>> Check(ProfileItem? item)
|
||||
{
|
||||
if (item is null)
|
||||
{
|
||||
return [ResUI.PleaseSelectServer];
|
||||
}
|
||||
|
||||
var errors = new List<string>();
|
||||
|
||||
errors.AddRange(await ValidateCurrentNodeAndCoreSupport(item));
|
||||
errors.AddRange(await ValidateRelatedNodesExistAndValid(item));
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private async Task<List<string>> ValidateCurrentNodeAndCoreSupport(ProfileItem item)
|
||||
{
|
||||
if (item.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||
return await ValidateNodeAndCoreSupport(item, coreType);
|
||||
}
|
||||
|
||||
private async Task<List<string>> ValidateNodeAndCoreSupport(ProfileItem item, ECoreType? coreType = null)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
coreType ??= AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||
|
||||
if (item.ConfigType is EConfigType.Custom)
|
||||
{
|
||||
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (!item.IsComplex())
|
||||
{
|
||||
if (item.Address.IsNullOrEmpty())
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (item.Port is <= 0 or >= 65536)
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
|
||||
return errors;
|
||||
}
|
||||
|
||||
switch (item.ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
||||
}
|
||||
|
||||
if (!Global.Flows.Contains(item.Flow))
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
if (item.Id.IsNullOrEmpty())
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
|
||||
&& item.StreamSecurity == Global.StreamSecurityReality
|
||||
&& item.PublicKey.IsNullOrEmpty())
|
||||
{
|
||||
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
|
||||
}
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.ConfigType.IsGroupType())
|
||||
{
|
||||
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
|
||||
if (group is null || group.NotHasChild())
|
||||
{
|
||||
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
|
||||
return errors;
|
||||
}
|
||||
|
||||
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
|
||||
if (hasCycle)
|
||||
{
|
||||
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
|
||||
return errors;
|
||||
}
|
||||
|
||||
var childIds = Utils.String2List(group.ChildItems) ?? [];
|
||||
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
|
||||
childIds.AddRange(subItems.Select(p => p.IndexId));
|
||||
|
||||
foreach (var child in childIds)
|
||||
{
|
||||
var childErrors = new List<string>();
|
||||
if (child.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var childItem = await AppManager.Instance.GetProfileItem(child);
|
||||
if (childItem is null)
|
||||
{
|
||||
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
|
||||
{
|
||||
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
|
||||
continue;
|
||||
}
|
||||
|
||||
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
|
||||
errors.AddRange(childErrors);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
var net = item.GetNetwork();
|
||||
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
var transportError = ValidateSingboxTransport(item.ConfigType, net);
|
||||
if (transportError != null)
|
||||
{
|
||||
errors.Add(transportError);
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
else if (coreType is ECoreType.Xray)
|
||||
{
|
||||
// Xray core does not support these protocols
|
||||
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
|
||||
&& !item.IsComplex())
|
||||
{
|
||||
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private static string? ValidateSingboxTransport(EConfigType configType, string net)
|
||||
{
|
||||
// sing-box does not support xhttp / kcp transports
|
||||
if (SingboxUnsupportedTransports.Contains(net))
|
||||
{
|
||||
return string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net);
|
||||
}
|
||||
|
||||
// sing-box does not support non-tcp transports for protocols other than vmess/trojan/vless/shadowsocks
|
||||
if (!SingboxTransportSupportedProtocols.Contains(configType) && net != nameof(ETransport.tcp))
|
||||
{
|
||||
return string.Format(ResUI.CoreNotSupportProtocolTransport,
|
||||
nameof(ECoreType.sing_box), configType.ToString(), net);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
errors.AddRange(await ValidateProxyChainedNodeExistAndValid(item));
|
||||
errors.AddRange(await ValidateRoutingNodeExistAndValid(item));
|
||||
return errors;
|
||||
}
|
||||
|
||||
private async Task<List<string>> ValidateProxyChainedNodeExistAndValid(ProfileItem? item)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
if (item is null)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
// prev node and next node
|
||||
var subItem = await AppManager.Instance.GetSubItem(item.Subid);
|
||||
if (subItem is null)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||
|
||||
await CollectProxyChainedNodeValidation(prevNode, subItem.PrevProfile, coreType, errors);
|
||||
await CollectProxyChainedNodeValidation(nextNode, subItem.NextProfile, coreType, errors);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private async Task CollectProxyChainedNodeValidation(ProfileItem? node, string tag, ECoreType coreType, List<string> errors)
|
||||
{
|
||||
if (node is not null)
|
||||
{
|
||||
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
|
||||
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
|
||||
}
|
||||
else if (tag.IsNotEmpty())
|
||||
{
|
||||
errors.Add(ResUI.ProxyChainedPrefix + string.Format(ResUI.NodeTagNotExist, tag));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<string>> ValidateRoutingNodeExistAndValid(ProfileItem? item)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
if (item is null)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
if (routing == null)
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||
foreach (var ruleItem in rules ?? [])
|
||||
{
|
||||
if (!ruleItem.Enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var outboundTag = ruleItem.OutboundTag;
|
||||
if (outboundTag.IsNullOrEmpty() || Global.OutboundTags.Contains(outboundTag))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var tagItem = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||
if (tagItem is null)
|
||||
{
|
||||
errors.Add(ResUI.RoutingRuleOutboundPrefix + string.Format(ResUI.NodeTagNotExist, outboundTag));
|
||||
continue;
|
||||
}
|
||||
|
||||
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
|
||||
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,23 @@ public sealed class AppManager
|
||||
|
||||
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
|
||||
|
||||
#region App
|
||||
@@ -64,7 +81,9 @@ public sealed class AppManager
|
||||
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
||||
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
||||
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
|
||||
#pragma warning disable CS0618
|
||||
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
|
||||
#pragma warning restore CS0618
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -77,6 +96,11 @@ public sealed class AppManager
|
||||
_ = StatePort;
|
||||
_ = StatePort2;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await MigrateProfileExtra();
|
||||
}).Wait();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -167,10 +191,17 @@ public sealed class AppManager
|
||||
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.*
|
||||
,b.remarks subRemarks
|
||||
var sql = @$"select a.IndexId
|
||||
,a.ConfigType
|
||||
,a.Remarks
|
||||
,a.Address
|
||||
,a.Port
|
||||
,a.Network
|
||||
,a.StreamSecurity
|
||||
,a.Subid
|
||||
,b.remarks as subRemarks
|
||||
from ProfileItem a
|
||||
left join SubItem b on a.subid = b.id
|
||||
where 1=1 ";
|
||||
@@ -199,6 +230,42 @@ public sealed class AppManager
|
||||
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<Dictionary<string, ProfileItem>> GetProfileItemsByIndexIdsAsMap(IEnumerable<string> indexIds)
|
||||
{
|
||||
var items = await GetProfileItemsByIndexIds(indexIds);
|
||||
return items.ToDictionary(it => it.IndexId);
|
||||
}
|
||||
|
||||
public async Task<List<ProfileItem>> GetProfileItemsOrderedByIndexIds(IEnumerable<string> indexIds)
|
||||
{
|
||||
var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
||||
if (idList.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var items = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => idList.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
var itemMap = items.ToDictionary(it => it.IndexId);
|
||||
|
||||
return idList.Select(id => itemMap.GetValueOrDefault(id))
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
|
||||
{
|
||||
if (remarks.IsNullOrEmpty())
|
||||
@@ -208,15 +275,6 @@ public sealed class AppManager
|
||||
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()
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();
|
||||
@@ -247,6 +305,205 @@ public sealed class AppManager
|
||||
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
|
||||
|
||||
#region Core Type
|
||||
|
||||
@@ -197,6 +197,7 @@ public class CertPemManager
|
||||
"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>
|
||||
@@ -214,7 +215,7 @@ public class CertPemManager
|
||||
using var client = new TcpClient();
|
||||
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
|
||||
|
||||
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
|
||||
await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
|
||||
|
||||
var sslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
@@ -261,7 +262,7 @@ public class CertPemManager
|
||||
using var client = new TcpClient();
|
||||
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
|
||||
|
||||
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
|
||||
await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
|
||||
|
||||
var sslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
@@ -279,11 +280,7 @@ public class CertPemManager
|
||||
var chain = new X509Chain();
|
||||
chain.Build(certChain);
|
||||
|
||||
foreach (var element in chain.ChainElements)
|
||||
{
|
||||
var pem = ExportCertToPem(element.Certificate);
|
||||
pemList.Add(pem);
|
||||
}
|
||||
pemList.AddRange(chain.ChainElements.Select(element => ExportCertToPem(element.Certificate)));
|
||||
|
||||
return (pemList, null);
|
||||
}
|
||||
@@ -416,4 +413,22 @@ public class CertPemManager
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ public sealed class CoreInfoManager
|
||||
new CoreInfo
|
||||
{
|
||||
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(),
|
||||
Url = GetCoreUrl(ECoreType.mihomo),
|
||||
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||
@@ -248,4 +248,34 @@ public sealed class CoreInfoManager
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,16 +57,19 @@ public class CoreManager
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadCore(ProfileItem? node)
|
||||
/// <param name="mainContext">Resolved main context (with pre-socks ports already merged if applicable).</param>
|
||||
/// <param name="preContext">Optional pre-socks context passed to <see cref="CoreStartPreService"/>.</param>
|
||||
public async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
|
||||
{
|
||||
if (node == null)
|
||||
if (mainContext == null)
|
||||
{
|
||||
await UpdateFunc(false, ResUI.CheckServerSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
var node = mainContext.Node;
|
||||
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(mainContext, fileName);
|
||||
if (result.Success != true)
|
||||
{
|
||||
await UpdateFunc(true, result.Msg);
|
||||
@@ -85,8 +88,8 @@ public class CoreManager
|
||||
await WindowsUtils.RemoveTunDevice();
|
||||
}
|
||||
|
||||
await CoreStart(node);
|
||||
await CoreStartPreService(node);
|
||||
await CoreStart(mainContext);
|
||||
await CoreStartPreService(preContext);
|
||||
if (_processService != null)
|
||||
{
|
||||
await UpdateFunc(true, $"{node.GetSummary()}");
|
||||
@@ -95,7 +98,7 @@ public class CoreManager
|
||||
|
||||
public async Task<ProcessService?> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
|
||||
{
|
||||
var coreType = selecteds.Any(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)) ? ECoreType.sing_box : ECoreType.Xray;
|
||||
var coreType = selecteds.FirstOrDefault()?.CoreType == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray;
|
||||
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
||||
var configPath = Utils.GetBinConfigPath(fileName);
|
||||
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
|
||||
@@ -122,13 +125,14 @@ public class CoreManager
|
||||
|
||||
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
||||
var configPath = Utils.GetBinConfigPath(fileName);
|
||||
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
|
||||
var (context, _) = await CoreConfigContextBuilder.Build(_config, node);
|
||||
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath);
|
||||
if (result.Success != true)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||
var coreType = context.RunCoreType;
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||
return await RunProcess(coreInfo, fileName, true, false);
|
||||
}
|
||||
@@ -165,9 +169,10 @@ public class CoreManager
|
||||
|
||||
#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 displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
||||
@@ -179,27 +184,22 @@ public class CoreManager
|
||||
_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 itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
|
||||
if (itemSocks != null)
|
||||
var preCoreType = preContext?.Node?.CoreType ?? ECoreType.sing_box;
|
||||
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(preContext, fileName);
|
||||
if (result.Success)
|
||||
{
|
||||
var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box;
|
||||
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
|
||||
if (result.Success)
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
|
||||
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
|
||||
if (proc is null)
|
||||
{
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
|
||||
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
|
||||
if (proc is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_processPreService = proc;
|
||||
return;
|
||||
}
|
||||
_processPreService = proc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,7 +226,7 @@ public class CoreManager
|
||||
{
|
||||
if (mayNeedSudo
|
||||
&& _config.TunModeItem.EnableTun
|
||||
&& coreInfo.CoreType == ECoreType.sing_box
|
||||
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo)
|
||||
&& Utils.IsNonWindows())
|
||||
{
|
||||
_linuxSudo = true;
|
||||
|
||||
151
v2rayN/ServiceLib/Manager/GroupProfileManager.cs
Normal file
151
v2rayN/ServiceLib/Manager/GroupProfileManager.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
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;
|
||||
}
|
||||
|
||||
var childItems = await AppManager.Instance.GetProfileItemsByIndexIds(childIds);
|
||||
foreach (var childItem in childItems)
|
||||
{
|
||||
if (await HasCycle(childItem.IndexId, 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 ordered = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childProfileIds);
|
||||
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<Dictionary<string, ProfileItem>> GetAllChildProfileItems(ProfileItem profileItem)
|
||||
{
|
||||
var itemMap = new Dictionary<string, ProfileItem>();
|
||||
var visited = new HashSet<string>();
|
||||
|
||||
await CollectChildItems(profileItem, itemMap, visited);
|
||||
|
||||
return itemMap;
|
||||
}
|
||||
|
||||
private static async Task CollectChildItems(ProfileItem profileItem, Dictionary<string, ProfileItem> itemMap,
|
||||
HashSet<string> visited)
|
||||
{
|
||||
var (childItems, _) = await GetChildProfileItems(profileItem);
|
||||
foreach (var child in childItems.Where(child => visited.Add(child.IndexId)))
|
||||
{
|
||||
itemMap[child.IndexId] = child;
|
||||
|
||||
if (child.ConfigType.IsGroupType())
|
||||
{
|
||||
await CollectChildItems(child, itemMap, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,4 +38,25 @@ public class NoticeManager
|
||||
Enqueue(msg);
|
||||
SendMessage(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends each error and warning in <paramref name="validatorResult"/> to the message panel
|
||||
/// and enqueues a summary snack notification (capped at 10 messages).
|
||||
/// Returns <c>true</c> when there were any messages so the caller can decide on early-return
|
||||
/// based on <see cref="NodeValidatorResult.Success"/>.
|
||||
/// </summary>
|
||||
public bool NotifyValidatorResult(NodeValidatorResult validatorResult)
|
||||
{
|
||||
var msgs = new List<string>([.. validatorResult.Errors, .. validatorResult.Warnings]);
|
||||
if (msgs.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var msg in msgs)
|
||||
{
|
||||
SendMessage(msg);
|
||||
}
|
||||
Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,320 +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.NotHasChild())
|
||||
{
|
||||
return (new List<ProfileItem>(), profileGroupItem);
|
||||
}
|
||||
var items = await GetChildProfileItems(profileGroupItem);
|
||||
var subItems = await GetSubChildProfileItems(profileGroupItem);
|
||||
items.AddRange(subItems);
|
||||
|
||||
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<List<ProfileItem>> GetSubChildProfileItems(ProfileGroupItem? group)
|
||||
{
|
||||
if (group == null || group.SubChildItems.IsNullOrEmpty())
|
||||
{
|
||||
return new();
|
||||
}
|
||||
var childProfiles = await AppManager.Instance.ProfileItems(group.SubChildItems);
|
||||
|
||||
return childProfiles.Where(p =>
|
||||
p != null &&
|
||||
p.IsValid() &&
|
||||
!p.ConfigType.IsComplexType() &&
|
||||
(group.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, group.Filter))
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
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 == null)
|
||||
{
|
||||
return childAddresses;
|
||||
}
|
||||
|
||||
if (groupItem.SubChildItems.IsNotEmpty())
|
||||
{
|
||||
var subItems = await GetSubChildProfileItems(groupItem);
|
||||
subItems.ForEach(p => childAddresses.Add(p.Address));
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -43,9 +43,12 @@ public sealed class WebDavManager
|
||||
_webDir = _config.WebDavItem.DirName.TrimEx();
|
||||
}
|
||||
|
||||
// Ensure BaseAddress URL ends with a trailing slash
|
||||
var baseUrl = _config.WebDavItem.Url.Trim().TrimEnd('/') + "/";
|
||||
|
||||
var clientParams = new WebDavClientParams
|
||||
{
|
||||
BaseAddress = new Uri(_config.WebDavItem.Url),
|
||||
BaseAddress = new Uri(baseUrl),
|
||||
Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password)
|
||||
};
|
||||
_client = new WebDavClient(clientParams);
|
||||
|
||||
@@ -8,21 +8,6 @@ public class Config
|
||||
public string IndexId { 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
|
||||
|
||||
#region other entities
|
||||
|
||||
@@ -100,7 +100,6 @@ public class UIItem
|
||||
public bool DoubleClick2Activate { get; set; }
|
||||
public bool AutoHideStartup { get; set; }
|
||||
public bool Hide2TrayWhenClose { get; set; }
|
||||
public bool ShowInTaskbar { get; set; }
|
||||
public bool MacOSShowInDock { get; set; }
|
||||
public List<ColumnItem> MainColumnItem { get; set; }
|
||||
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
||||
@@ -145,7 +144,6 @@ public class TunModeItem
|
||||
public bool StrictRoute { get; set; } = true;
|
||||
public string Stack { get; set; }
|
||||
public int Mtu { get; set; }
|
||||
public bool EnableExInbound { get; set; }
|
||||
public bool EnableIPv6Address { get; set; }
|
||||
}
|
||||
|
||||
@@ -210,6 +208,7 @@ public class ClashUIItem
|
||||
public int ProxiesAutoDelayTestInterval { get; set; } = 10;
|
||||
public bool ConnectionsAutoRefresh { get; set; }
|
||||
public int ConnectionsRefreshInterval { get; set; } = 2;
|
||||
public List<ColumnItem> ConnectionsColumnItem { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -266,9 +265,10 @@ public class SimpleDNSItem
|
||||
public string? DirectDNS { get; set; }
|
||||
public string? RemoteDNS { get; set; }
|
||||
public string? BootstrapDNS { get; set; }
|
||||
public string? RayStrategy4Freedom { get; set; }
|
||||
public string? SingboxStrategy4Direct { get; set; }
|
||||
public string? SingboxStrategy4Proxy { get; set; }
|
||||
public string? Strategy4Freedom { get; set; }
|
||||
public string? Strategy4Proxy { get; set; }
|
||||
public bool? ServeStale { get; set; }
|
||||
public bool? ParallelQuery { get; set; }
|
||||
public string? Hosts { get; set; }
|
||||
public string? DirectExpectedIPs { get; set; }
|
||||
}
|
||||
|
||||
25
v2rayN/ServiceLib/Models/CoreConfigContext.cs
Normal file
25
v2rayN/ServiceLib/Models/CoreConfigContext.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace ServiceLib.Models;
|
||||
|
||||
public record CoreConfigContext
|
||||
{
|
||||
public required ProfileItem Node { get; init; }
|
||||
public required ECoreType RunCoreType { 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;
|
||||
|
||||
[Obsolete("Use ProtocolExtraItem instead.")]
|
||||
[Serializable]
|
||||
public class ProfileGroupItem
|
||||
{
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
namespace ServiceLib.Models;
|
||||
|
||||
[Serializable]
|
||||
public class ProfileItem : ReactiveObject
|
||||
public class ProfileItem
|
||||
{
|
||||
private ProtocolExtraItem? _protocolExtraCache;
|
||||
|
||||
public ProfileItem()
|
||||
{
|
||||
IndexId = string.Empty;
|
||||
ConfigType = EConfigType.VMess;
|
||||
ConfigVersion = 2;
|
||||
ConfigVersion = 3;
|
||||
Subid = string.Empty;
|
||||
Address = string.Empty;
|
||||
Port = 0;
|
||||
Id = string.Empty;
|
||||
AlterId = 0;
|
||||
Security = string.Empty;
|
||||
Password = string.Empty;
|
||||
Username = string.Empty;
|
||||
Network = string.Empty;
|
||||
Remarks = string.Empty;
|
||||
HeaderType = string.Empty;
|
||||
@@ -20,8 +22,6 @@ public class ProfileItem : ReactiveObject
|
||||
Path = string.Empty;
|
||||
StreamSecurity = string.Empty;
|
||||
AllowInsecure = string.Empty;
|
||||
Subid = string.Empty;
|
||||
Flow = string.Empty;
|
||||
}
|
||||
|
||||
#region function
|
||||
@@ -81,7 +81,7 @@ public class ProfileItem : ReactiveObject
|
||||
switch (ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id))
|
||||
if (Password.IsNullOrEmpty() || !Utils.IsGuidByParse(Password))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -89,12 +89,12 @@ public class ProfileItem : ReactiveObject
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30))
|
||||
if (Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(Password) && Password.Length > 30))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Global.Flows.Contains(Flow))
|
||||
if (!Global.Flows.Contains(GetProtocolExtra().Flow ?? string.Empty))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -102,12 +102,13 @@ public class ProfileItem : ReactiveObject
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
if (Id.IsNullOrEmpty())
|
||||
if (Password.IsNullOrEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security))
|
||||
if (string.IsNullOrEmpty(GetProtocolExtra().SsMethod)
|
||||
|| !Global.SsSecuritiesInSingbox.Contains(GetProtocolExtra().SsMethod))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -125,35 +126,48 @@ public class ProfileItem : ReactiveObject
|
||||
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
|
||||
|
||||
[PrimaryKey]
|
||||
public string IndexId { get; set; }
|
||||
|
||||
public EConfigType ConfigType { get; set; }
|
||||
public ECoreType? CoreType { 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 int Port { get; set; }
|
||||
public string Ports { get; set; }
|
||||
public string Id { get; set; }
|
||||
public int AlterId { get; set; }
|
||||
public string Security { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Network { get; set; }
|
||||
public string Remarks { get; set; }
|
||||
public string HeaderType { get; set; }
|
||||
public string RequestHost { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string StreamSecurity { 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 Alpn { get; set; } = string.Empty;
|
||||
public ECoreType? CoreType { get; set; }
|
||||
public int? PreSocksPort { get; set; }
|
||||
public string Fingerprint { get; set; }
|
||||
public bool DisplayLog { get; set; } = true;
|
||||
public string PublicKey { get; set; }
|
||||
public string ShortId { get; set; }
|
||||
public string SpiderX { get; set; }
|
||||
@@ -161,4 +175,25 @@ public class ProfileItem : ReactiveObject
|
||||
public string Extra { 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;
|
||||
|
||||
[Serializable]
|
||||
public class ProfileItemModel : ProfileItem
|
||||
public class ProfileItemModel : ReactiveObject
|
||||
{
|
||||
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 int Sort { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int Delay { get; set; }
|
||||
|
||||
public decimal Speed { get; set; }
|
||||
public int Sort { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string DelayVal { get; set; }
|
||||
@@ -29,4 +37,15 @@ public class ProfileItemModel : ProfileItem
|
||||
|
||||
[Reactive]
|
||||
public string TotalDown { get; set; }
|
||||
|
||||
public string GetSummary()
|
||||
{
|
||||
var summary = $"[{ConfigType}] {Remarks}";
|
||||
if (!ConfigType.IsComplexType())
|
||||
{
|
||||
summary += $"({Address}:{Port})";
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
|
||||
38
v2rayN/ServiceLib/Models/ProtocolExtraItem.cs
Normal file
38
v2rayN/ServiceLib/Models/ProtocolExtraItem.cs
Normal file
@@ -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; }
|
||||
}
|
||||
@@ -9,4 +9,6 @@ public class ServerTestItem
|
||||
public EConfigType ConfigType { get; set; }
|
||||
public bool AllowTest { get; set; }
|
||||
public int QueueNum { get; set; }
|
||||
public ProfileItem Profile { get; set; }
|
||||
public ECoreType CoreType { get; set; }
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public class Dns4Sbox
|
||||
public bool? disable_cache { get; set; }
|
||||
public bool? disable_expire { get; set; }
|
||||
public bool? independent_cache { get; set; }
|
||||
public int? cache_capacity { get; set; }
|
||||
public bool? reverse_mapping { get; set; }
|
||||
public string? client_subnet { get; set; }
|
||||
}
|
||||
@@ -68,6 +69,7 @@ public class Rule4Sbox
|
||||
public List<string>? ip_cidr { get; set; }
|
||||
public List<string>? source_ip_cidr { get; set; }
|
||||
public List<string>? process_name { get; set; }
|
||||
public List<string>? process_path { get; set; }
|
||||
public List<string>? rule_set { get; set; }
|
||||
public List<Rule4Sbox>? rules { get; set; }
|
||||
public string? action { get; set; }
|
||||
@@ -182,6 +184,14 @@ public class Tls4Sbox
|
||||
public string? fragment_fallback_delay { 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
|
||||
@@ -246,7 +256,7 @@ public class Server4Sbox : BaseServer4Sbox
|
||||
// public List<string>? path { get; set; } // hosts
|
||||
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_resolver { get; set; }
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace ServiceLib.Models;
|
||||
public class V2rayConfig
|
||||
{
|
||||
public Log4Ray log { get; set; }
|
||||
public Dns4Ray dns { get; set; }
|
||||
public object dns { get; set; }
|
||||
public List<Inbounds4Ray> inbounds { get; set; }
|
||||
public List<Outbounds4Ray> outbounds { get; set; }
|
||||
public Routing4Ray routing { get; set; }
|
||||
@@ -105,6 +105,8 @@ public class Outbounds4Ray
|
||||
|
||||
public string protocol { get; set; }
|
||||
|
||||
public string? targetStrategy { get; set; }
|
||||
|
||||
public Outboundsettings4Ray settings { get; set; }
|
||||
|
||||
public StreamSettings4Ray streamSettings { get; set; }
|
||||
@@ -128,7 +130,8 @@ public class Outboundsettings4Ray
|
||||
|
||||
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; }
|
||||
|
||||
@@ -139,6 +142,8 @@ public class Outboundsettings4Ray
|
||||
public List<int>? reserved { get; set; }
|
||||
|
||||
public int? workers { get; set; }
|
||||
|
||||
public int? version { get; set; }
|
||||
}
|
||||
|
||||
public class WireguardPeer4Ray
|
||||
@@ -203,12 +208,8 @@ public class Dns4Ray
|
||||
{
|
||||
public Dictionary<string, object>? hosts { get; set; }
|
||||
public List<object> servers { get; set; }
|
||||
public string? clientIp { get; set; }
|
||||
public string? queryStrategy { get; set; }
|
||||
public bool? disableCache { get; set; }
|
||||
public bool? disableFallback { get; set; }
|
||||
public bool? disableFallbackIfMatch { get; set; }
|
||||
public bool? useSystemHosts { get; set; }
|
||||
public bool? serveStale { get; set; }
|
||||
public bool? enableParallelQuery { get; set; }
|
||||
public string? tag { get; set; }
|
||||
}
|
||||
|
||||
@@ -219,12 +220,6 @@ public class DnsServer4Ray
|
||||
public List<string>? domains { get; set; }
|
||||
public bool? skipFallback { get; set; }
|
||||
public List<string>? expectedIPs { get; set; }
|
||||
public List<string>? unexpectedIPs { get; set; }
|
||||
public string? clientIp { get; set; }
|
||||
public string? queryStrategy { get; set; }
|
||||
public int? timeoutMs { get; set; }
|
||||
public bool? disableCache { get; set; }
|
||||
public bool? finalQuery { get; set; }
|
||||
public string? tag { get; set; }
|
||||
}
|
||||
|
||||
@@ -256,6 +251,8 @@ public class RulesItem4Ray
|
||||
public List<string>? domain { get; set; }
|
||||
|
||||
public List<string>? protocol { get; set; }
|
||||
|
||||
public List<string>? process { get; set; }
|
||||
}
|
||||
|
||||
public class BalancersItem4Ray
|
||||
@@ -336,6 +333,10 @@ public class StreamSettings4Ray
|
||||
|
||||
public GrpcSettings4Ray? grpcSettings { get; set; }
|
||||
|
||||
public HysteriaSettings4Ray? hysteriaSettings { get; set; }
|
||||
|
||||
public Finalmask4Ray? finalmask { get; set; }
|
||||
|
||||
public Sockopt4Ray? sockopt { get; set; }
|
||||
}
|
||||
|
||||
@@ -355,7 +356,11 @@ public class TlsSettings4Ray
|
||||
public string? spiderX { 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 Sockopt4Ray? echSockopt { get; set; }
|
||||
}
|
||||
|
||||
public class CertificateSettings4Ray
|
||||
@@ -376,8 +381,6 @@ public class Header4Ray
|
||||
public object request { get; set; }
|
||||
|
||||
public object response { get; set; }
|
||||
|
||||
public string? domain { get; set; }
|
||||
}
|
||||
|
||||
public class KcpSettings4Ray
|
||||
@@ -395,10 +398,6 @@ public class KcpSettings4Ray
|
||||
public int readBufferSize { get; set; }
|
||||
|
||||
public int writeBufferSize { get; set; }
|
||||
|
||||
public Header4Ray header { get; set; }
|
||||
|
||||
public string seed { get; set; }
|
||||
}
|
||||
|
||||
public class WsSettings4Ray
|
||||
@@ -457,6 +456,39 @@ public class GrpcSettings4Ray
|
||||
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 string user { get; set; }
|
||||
|
||||
519
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
519
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -19,7 +19,7 @@ namespace ServiceLib.Resx {
|
||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||
// (以 /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.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class ResUI {
|
||||
@@ -132,33 +132,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Core '{0}' does not support network type '{1}'. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string CoreNotSupportNetwork {
|
||||
get {
|
||||
return ResourceManager.GetString("CoreNotSupportNetwork", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Core '{0}' does not support protocol '{1}'. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string CoreNotSupportProtocol {
|
||||
get {
|
||||
return ResourceManager.GetString("CoreNotSupportProtocol", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Core '{0}' does not support protocol '{1}' when using transport '{2}'. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string CoreNotSupportProtocolTransport {
|
||||
get {
|
||||
return ResourceManager.GetString("CoreNotSupportProtocolTransport", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -312,24 +285,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Group '{0}' is empty. Please add at least one node. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string GroupEmpty {
|
||||
get {
|
||||
return ResourceManager.GetString("GroupEmpty", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 {0} Group cannot reference itself or have a circular reference 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string GroupSelfReference {
|
||||
get {
|
||||
return ResourceManager.GetString("GroupSelfReference", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 This is not the correct configuration, please check 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -357,15 +312,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 The {0} property is invalid, please check. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string InvalidProperty {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidProperty", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Invalid address (URL) 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -888,6 +834,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 All configurations 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuAllServers {
|
||||
get {
|
||||
return ResourceManager.GetString("menuAllServers", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Backup and Restore 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1060,74 +1015,20 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Generate Policy Group from Multiple Profiles 的本地化字符串。
|
||||
/// 查找类似 Generate Policy Group 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServer {
|
||||
public static string menuGenGroupServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServer", resourceCulture);
|
||||
return ResourceManager.GetString("menuGenGroupServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fallback by sing-box 的本地化字符串。
|
||||
/// 查找类似 Group by Region 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerSingBoxFallback {
|
||||
public static string menuGenRegionGroup {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 LeastPing by sing-box 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerSingBoxLeastPing {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxLeastPing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fallback by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayFallback {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 LeastLoad by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayLeastLoad {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastLoad", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 LeastPing by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayLeastPing {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastPing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Random by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayRandom {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRandom", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 RoundRobin by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayRoundRobin {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRoundRobin", resourceCulture);
|
||||
return ResourceManager.GetString("menuGenRegionGroup", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1663,7 +1564,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Configuration List 的本地化字符串。
|
||||
/// 查找类似 Configuration item 1, Auto add from subscription group 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuServerList {
|
||||
get {
|
||||
@@ -1671,6 +1572,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 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>
|
||||
@@ -1896,6 +1815,33 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Core '{0}' does not support network type '{1}' 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgCoreNotSupportNetwork {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgCoreNotSupportNetwork", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Core '{0}' does not support protocol '{1}' 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgCoreNotSupportProtocol {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgCoreNotSupportProtocol", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Core '{0}' does not support protocol '{1}' when using transport '{2}' 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgCoreNotSupportProtocolTransport {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgCoreNotSupportProtocolTransport", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Downloaded GeoFile: {0} successfully 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1941,6 +1887,60 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Group {0} child group node {1} error: {2}. Skipping this node. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgGroupChildGroupNodeError {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgGroupChildGroupNodeError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Group {0} child group node {1} warning: {2} 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgGroupChildGroupNodeWarning {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgGroupChildGroupNodeWarning", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Group {0} child node {1} error: {2}. Skipping this node. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgGroupChildNodeError {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgGroupChildNodeError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Group {0} child node {1} warning: {2} 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgGroupChildNodeWarning {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgGroupChildNodeWarning", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Group {0} has a cycle dependency on child node {1}. Skipping this node. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgGroupCycleDependency {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgGroupCycleDependency", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Group {0} has no valid child node. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgGroupNoValidChildNode {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgGroupNoValidChildNode", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Information 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1950,6 +1950,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 The {0} property is invalid, please check 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgInvalidProperty {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgInvalidProperty", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please enter the URL 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1959,6 +1968,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Not support protocol '{0}' 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgNotSupportProtocol {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgNotSupportProtocol", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 No valid subscriptions set 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1977,6 +1995,42 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Routing rule {0} has an empty outbound tag. Fallback to proxy node only. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgRoutingRuleEmptyOutboundTag {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgRoutingRuleEmptyOutboundTag", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgRoutingRuleOutboundNodeError {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgRoutingRuleOutboundNodeError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Routing rule {0} outbound node {1} not found. Fallback to proxy node only. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgRoutingRuleOutboundNodeNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgRoutingRuleOutboundNodeNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Routing rule {0} outbound node {1} warning: {2} 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgRoutingRuleOutboundNodeWarning {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgRoutingRuleOutboundNodeWarning", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Filter, press Enter to execute 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2031,6 +2085,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Subscription next proxy {0} not found. Skipping. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgSubscriptionNextProfileNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgSubscriptionNextProfileNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Subscription previous proxy {0} not found. Skipping. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgSubscriptionPrevProfileNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgSubscriptionPrevProfileNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Unpacking... 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2085,15 +2157,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Node alias '{0}' does not exist. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string NodeTagNotExist {
|
||||
get {
|
||||
return ResourceManager.GetString("NodeTagNotExist", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Non-VMess or SS protocol 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2121,15 +2184,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Not support protocol '{0}'. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string NotSupportProtocol {
|
||||
get {
|
||||
return ResourceManager.GetString("NotSupportProtocol", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Scan completed, no valid QR code found 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2211,24 +2265,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Policy group: 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string PolicyGroupPrefix {
|
||||
get {
|
||||
return ResourceManager.GetString("PolicyGroupPrefix", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Proxy chained: 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string ProxyChainedPrefix {
|
||||
get {
|
||||
return ResourceManager.GetString("ProxyChainedPrefix", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Global hotkey {0} registration failed, reason: {1} 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2292,15 +2328,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Routing rule outbound: 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string RoutingRuleOutboundPrefix {
|
||||
get {
|
||||
return ResourceManager.GetString("RoutingRuleOutboundPrefix", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Run as Admin 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2608,7 +2635,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Server Certificate (PEM format, optional)
|
||||
/// 查找类似 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. 的本地化字符串。
|
||||
@@ -2619,6 +2646,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Certificate fingerprint (SHA-256) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbCertSha256Tips {
|
||||
get {
|
||||
return ResourceManager.GetString("TbCertSha256Tips", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Clear system proxy 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2709,6 +2745,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>
|
||||
/// 查找类似 Display GUI 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2781,6 +2835,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>
|
||||
/// 查找类似 Edit 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2844,6 +2925,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Finalmask 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbFinalmask {
|
||||
get {
|
||||
return ResourceManager.GetString("TbFinalmask", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fingerprint 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2862,6 +2952,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Full certificate (chain), PEM format 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbFullCertTips {
|
||||
get {
|
||||
return ResourceManager.GetString("TbFullCertTips", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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. 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2925,6 +3024,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Port hopping interval 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbHopInt7 {
|
||||
get {
|
||||
return ResourceManager.GetString("TbHopInt7", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 UUID(id) 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3015,6 +3123,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Parallel Query 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbParallelQuery {
|
||||
get {
|
||||
return ResourceManager.GetString("TbParallelQuery", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Path 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3169,7 +3286,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Via proxy — please ensure remote availability 的本地化字符串。
|
||||
/// 查找类似 By default, invoked only during routing for resolution; ensure the remote server can reach this DNS 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbRemoteDNSTips {
|
||||
get {
|
||||
@@ -3177,6 +3294,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>
|
||||
/// 查找类似 Camouflage domain(host) 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3241,7 +3376,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Full process name (Tun mode) 的本地化字符串。
|
||||
/// 查找类似 Process (Tun mode) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbRoutingRuleProcess {
|
||||
get {
|
||||
@@ -3312,15 +3447,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSBDirectResolveStrategy {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSBDirectResolveStrategy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 sing-box Full Config Template 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3339,15 +3465,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 sing-box Remote Resolution Strategy 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSBRemoteResolveStrategy {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSBRemoteResolveStrategy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Encryption method (security) 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3393,6 +3510,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Serve Stale 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbServeStale {
|
||||
get {
|
||||
return ResourceManager.GetString("TbServeStale", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Set system proxy 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3663,15 +3789,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Enable additional Inbound 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsEnableExInbound {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsEnableExInbound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Enable fragment 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3681,15 +3798,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 which conflicts with the group previous proxy 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsEnableFragmentTips {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsEnableFragmentTips", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Enable hardware acceleration (requires restart) 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -4366,7 +4474,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static string TbValidateDirectExpectedIPsDesc {
|
||||
get {
|
||||
@@ -4374,15 +4482,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 xray Freedom Resolution Strategy 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbXrayFreedomStrategy {
|
||||
get {
|
||||
return ResourceManager.GetString("TbXrayFreedomStrategy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 The delay: {0} ms, {1} 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -1027,7 +1027,7 @@
|
||||
<value>پروتکل sing-box Mux</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||
<value>نام کامل فرانید (حالت Tun)</value>
|
||||
<value>Process (Tun mode)</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||
<value>IP or IP CIDR</value>
|
||||
@@ -1071,9 +1071,6 @@
|
||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||
<value>MTU</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
<value>فعال سازی additional Inbound</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||
<value>فعال سازی آدرس IPv6</value>
|
||||
</data>
|
||||
@@ -1113,9 +1110,6 @@
|
||||
<data name="menuAddHttpServer" xml:space="preserve">
|
||||
<value>افزودن سرور [HTTP]</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
||||
<value>which conflicts with the group previous proxy</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>فعال کردن فرگمنت</value>
|
||||
</data>
|
||||
@@ -1377,24 +1371,6 @@
|
||||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>مخفی و پورت می شود، با کاما (،) جدا می شود</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Generate Policy Group from Multiple Profiles</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>چند سرور تصادفی توسط Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>چند سرور RoundRobin توسط Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>چند سرور LeastPing توسط Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>چند سرور LeastLoad توسط Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>LeastPing چند سرور توسط sing-box</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>صادر کردن سرور</value>
|
||||
</data>
|
||||
@@ -1419,17 +1395,11 @@
|
||||
<data name="TbDomesticDNS" xml:space="preserve">
|
||||
<value>Domestic DNS</value>
|
||||
</data>
|
||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||
<value>Via proxy — please ensure remote availability</value>
|
||||
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||
<value>Direct Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
||||
<value>xray Freedom 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 name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>Proxy Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||
<value>Add Common DNS Hosts</value>
|
||||
@@ -1453,7 +1423,7 @@
|
||||
<value>Validate Regional Domain IPs</value>
|
||||
</data>
|
||||
<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 name="TbCustomDNSEnable" xml:space="preserve">
|
||||
<value>Enable Custom DNS</value>
|
||||
@@ -1540,49 +1510,25 @@
|
||||
<value>Remove Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Configuration List</value>
|
||||
<value>Configuration item 1, Auto add from subscription group</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Fallback</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||
<value>Multi-Configuration Fallback by sing-box</value>
|
||||
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Core '{0}' does not support network type '{1}'</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||
<value>Multi-Configuration Fallback by Xray</value>
|
||||
<data name="MsgCoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Core '{0}' does not support network type '{1}'.</value>
|
||||
<data name="MsgCoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
|
||||
<data name="MsgInvalidProperty" xml:space="preserve">
|
||||
<value>The {0} property is invalid, please check</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}'.</value>
|
||||
</data>
|
||||
<data name="ProxyChainedPrefix" xml:space="preserve">
|
||||
<value>Proxy chained: </value>
|
||||
</data>
|
||||
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
|
||||
<value>Routing rule outbound: </value>
|
||||
</data>
|
||||
<data name="PolicyGroupPrefix" xml:space="preserve">
|
||||
<value>Policy group: </value>
|
||||
</data>
|
||||
<data name="NodeTagNotExist" xml:space="preserve">
|
||||
<value>Node alias '{0}' does not exist.</value>
|
||||
</data>
|
||||
<data name="GroupEmpty" xml:space="preserve">
|
||||
<value>Group '{0}' is empty. Please add at least one node.</value>
|
||||
</data>
|
||||
<data name="InvalidProperty" xml:space="preserve">
|
||||
<value>The {0} property is invalid, please check.</value>
|
||||
</data>
|
||||
<data name="GroupSelfReference" xml:space="preserve">
|
||||
<value>{0} Group cannot reference itself or have a circular reference</value>
|
||||
</data>
|
||||
<data name="NotSupportProtocol" xml:space="preserve">
|
||||
<value>Not support protocol '{0}'.</value>
|
||||
<data name="MsgNotSupportProtocol" xml:space="preserve">
|
||||
<value>Not support protocol '{0}'</value>
|
||||
</data>
|
||||
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
||||
<value>If the system does not have a tray function, please do not enable it</value>
|
||||
@@ -1638,4 +1584,91 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
|
||||
<value>macOS displays this in the Dock (requires restart)</value>
|
||||
</data>
|
||||
</root>
|
||||
<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>
|
||||
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgGroupCycleDependency" xml:space="preserve">
|
||||
<value>Group {0} has a cycle dependency on child node {1}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeError" xml:space="preserve">
|
||||
<value>Group {0} child node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
|
||||
<value>Group {0} has no valid child node.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
|
||||
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription previous proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription next proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="menuGenGroupServer" xml:space="preserve">
|
||||
<value>Generate Policy Group</value>
|
||||
</data>
|
||||
<data name="menuAllServers" xml:space="preserve">
|
||||
<value>All configurations</value>
|
||||
</data>
|
||||
<data name="menuGenRegionGroup" xml:space="preserve">
|
||||
<value>Group by Region</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1024,7 +1024,7 @@
|
||||
<value>Protocole de multiplexage Mux (sing-box)</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||
<value>Nom complet du processus (mode Tun)</value>
|
||||
<value>Process (Tun mode)</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||
<value>IP ou IP CIDR</value>
|
||||
@@ -1068,9 +1068,6 @@
|
||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||
<value>MTU</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
<value>Activer un port d’écoute supplémentaire</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||
<value>Activer IPv6</value>
|
||||
</data>
|
||||
@@ -1110,9 +1107,6 @@
|
||||
<data name="menuAddHttpServer" xml:space="preserve">
|
||||
<value>Ajouter [HTTP]</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
||||
<value>En conflit avec le proxy amont de groupe</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Activer le fragmentation (Fragment)</value>
|
||||
</data>
|
||||
@@ -1374,24 +1368,6 @@
|
||||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>Écrase le port ; pour plusieurs groupes, séparer par virgules (,)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Générer un groupe de stratégie depuis plusieurs profils</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>Xray aléatoire (multi-sélection)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>Xray équilibrage (tourniquet) multi-sélection</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>Xray latence minimale (multi-sélection)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>Xray le plus stable (multi-sélection)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>sing-box latence minimale (multi-sélection)</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>Exporter</value>
|
||||
</data>
|
||||
@@ -1416,17 +1392,11 @@
|
||||
<data name="TbDomesticDNS" xml:space="preserve">
|
||||
<value>DNS direct</value>
|
||||
</data>
|
||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||
<value>Via le proxy ; assurez-vous que le serveur distant est disponible</value>
|
||||
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||
<value>Direct Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
||||
<value>Stratégie de résolution xray freedom</value>
|
||||
</data>
|
||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
||||
<value>Stratégie de résolution directe sing-box</value>
|
||||
</data>
|
||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>Stratégie de résolution distante sing-box</value>
|
||||
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>Proxy Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||
<value>Ajouter des hôtes DNS courants</value>
|
||||
@@ -1450,7 +1420,7 @@
|
||||
<value>Valider les IP des domaines de la région concernée</value>
|
||||
</data>
|
||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||
<value>Après config, les IP renvoyées des domaines régionaux (ex. geosite:cn) seront vérifiées ; seules les IP attendues seront retournées.</value>
|
||||
<value>Après config, les IP renvoyées des domaines régionaux (ex. geosite:cn - geoip:cn) seront vérifiées ; seules les IP attendues seront retournées.</value>
|
||||
</data>
|
||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||
<value>Activer le DNS personnalisé</value>
|
||||
@@ -1537,49 +1507,25 @@
|
||||
<value>Supprimer une sous-configuration</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Liste des configurations</value>
|
||||
<value>Configuration item 1, Auto add from subscription group</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Basculement (failover)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||
<value>sing-box basculement (multi-sélection)</value>
|
||||
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Le cœur « {0} » ne prend pas en charge le type de réseau « {1} »</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||
<value>Xray basculement (multi-sélection)</value>
|
||||
<data name="MsgCoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Le cœur « {0} » ne prend pas en charge le protocole « {1} » avec le mode de transport « {2} »</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Le cœur « {0} » ne prend pas en charge le type de réseau « {1} ».</value>
|
||||
<data name="MsgCoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Le cœur « {0} » ne prend pas en charge le protocole « {1} »</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Le cœur « {0} » ne prend pas en charge le protocole « {1} » avec le mode de transport « {2} ».</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Le cœur « {0} » ne prend pas en charge le protocole « {1} ».</value>
|
||||
</data>
|
||||
<data name="ProxyChainedPrefix" xml:space="preserve">
|
||||
<value>Chaîne de proxy : </value>
|
||||
</data>
|
||||
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
|
||||
<value>Règle de routage sortante : </value>
|
||||
</data>
|
||||
<data name="PolicyGroupPrefix" xml:space="preserve">
|
||||
<value>Groupe de stratégie : </value>
|
||||
</data>
|
||||
<data name="NodeTagNotExist" xml:space="preserve">
|
||||
<value>L’alias « {0} » n’existe pas.</value>
|
||||
</data>
|
||||
<data name="GroupEmpty" xml:space="preserve">
|
||||
<value>Le groupe « {0} » est vide. Veuillez ajouter au moins une configuration.</value>
|
||||
</data>
|
||||
<data name="InvalidProperty" xml:space="preserve">
|
||||
<data name="MsgInvalidProperty" xml:space="preserve">
|
||||
<value>La propriété {0} est invalide, veuillez vérifier</value>
|
||||
</data>
|
||||
<data name="GroupSelfReference" xml:space="preserve">
|
||||
<value>Le groupe {0} ne peut pas se référencer lui-même ni créer de référence circulaire</value>
|
||||
</data>
|
||||
<data name="NotSupportProtocol" xml:space="preserve">
|
||||
<value>Protocole « {0} » non pris en charge.</value>
|
||||
<data name="MsgNotSupportProtocol" xml:space="preserve">
|
||||
<value>Protocole « {0} » non pris en charge</value>
|
||||
</data>
|
||||
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
||||
<value>Si le système n’a pas de zone de notif., n’activez pas cette option</value>
|
||||
@@ -1606,10 +1552,10 @@
|
||||
<value>Certificate Pinning</value>
|
||||
</data>
|
||||
<data name="TbCertPinningTips" xml:space="preserve">
|
||||
<value>Certificat serveur (format PEM, facultatif)
|
||||
Si le certificat est défini, il est fixé et l’option « Ignorer la vérification » est désactivée.
|
||||
<value>Pinned certificate (fill in either one)
|
||||
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
|
||||
|
||||
Si un certificat auto-signé est utilisé ou si le système contient une CA non fiable ou malveillante, l’action « Obtenir le certificat » peut échouer.</value>
|
||||
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>Obtenir le certificat</value>
|
||||
@@ -1635,4 +1581,91 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
|
||||
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
|
||||
<value>Afficher dans le Dock de macOS (redém. requis)</value>
|
||||
</data>
|
||||
<data name="menuServerList2" xml:space="preserve">
|
||||
<value>Élément de config 2 : choisir et ajouter depuis self-hosted</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>Certificat complet (chaîne), format PEM</value>
|
||||
</data>
|
||||
<data name="TbCertSha256Tips" xml:space="preserve">
|
||||
<value>Empreinte du certificat (SHA-256)</value>
|
||||
</data>
|
||||
<data name="TbServeStale" xml:space="preserve">
|
||||
<value>Cache optimiste</value>
|
||||
</data>
|
||||
<data name="TbParallelQuery" xml:space="preserve">
|
||||
<value>Requête parallèle</value>
|
||||
</data>
|
||||
<data name="TbDomesticDNSTips" xml:space="preserve">
|
||||
<value>Par défaut, utilisé uniquement lors du routage pour la résolution.</value>
|
||||
</data>
|
||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||
<value>Par défaut, invoqué uniquement au routage pour la résolution. Vérifiez que le serveur distant peut joindre ce DNS.</value>
|
||||
</data>
|
||||
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
||||
<value>Si non défini ou « AsIs », le DNS système est utilisé ; sinon, le module DNS interne est utilisé.</value>
|
||||
</data>
|
||||
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
||||
<value>Si non défini ou « AsIs », la résolution DNS est assurée par le serveur distant ; sinon, le module DNS interne est utilisé.</value>
|
||||
</data>
|
||||
<data name="TbHopInt7" xml:space="preserve">
|
||||
<value>Intervalle de saut de port</value>
|
||||
</data>
|
||||
<data name="menuServerListPreview" xml:space="preserve">
|
||||
<value>Aperçu des sous-config</value>
|
||||
</data>
|
||||
<data name="TbFinalmask" xml:space="preserve">
|
||||
<value>Finalmask</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgGroupCycleDependency" xml:space="preserve">
|
||||
<value>Group {0} has a cycle dependency on child node {1}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeError" xml:space="preserve">
|
||||
<value>Group {0} child node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
|
||||
<value>Group {0} has no valid child node.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
|
||||
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription previous proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription next proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="menuGenGroupServer" xml:space="preserve">
|
||||
<value>Generate Policy Group</value>
|
||||
</data>
|
||||
<data name="menuAllServers" xml:space="preserve">
|
||||
<value>All configurations</value>
|
||||
</data>
|
||||
<data name="menuGenRegionGroup" xml:space="preserve">
|
||||
<value>Group by Region</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1027,7 +1027,7 @@
|
||||
<value>sing-box Mux protokoll</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||
<value>Teljes folyamatnév (Tun mód)</value>
|
||||
<value>Process (Tun mode)</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||
<value>IP vagy IP CIDR</value>
|
||||
@@ -1071,9 +1071,6 @@
|
||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||
<value>MTU</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
<value>További bejövő engedélyezése</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||
<value>IPv6 cím engedélyezése</value>
|
||||
</data>
|
||||
@@ -1113,9 +1110,6 @@
|
||||
<data name="menuAddHttpServer" xml:space="preserve">
|
||||
<value>HTTP konfiguráció hozzáadása</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
||||
<value>which conflicts with the group previous proxy</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Fragment engedélyezése</value>
|
||||
</data>
|
||||
@@ -1377,24 +1371,6 @@
|
||||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>A portot lefedi, vesszővel (,) elválasztva</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Generate Policy Group from Multiple Profiles</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>Több konfiguráció véletlenszerűen Xray szerint</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>Több konfiguráció RoundRobin Xray szerint</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>Több konfiguráció legkisebb pinggel Xray szerint</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>Több konfiguráció legkisebb terheléssel Xray szerint</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>Több konfiguráció legkisebb pinggel sing-box szerint</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>Konfiguráció exportálása</value>
|
||||
</data>
|
||||
@@ -1419,17 +1395,11 @@
|
||||
<data name="TbDomesticDNS" xml:space="preserve">
|
||||
<value>Domestic DNS</value>
|
||||
</data>
|
||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||
<value>Via proxy — please ensure remote availability</value>
|
||||
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||
<value>Direct Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
||||
<value>xray Freedom 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 name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>Proxy Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||
<value>Add Common DNS Hosts</value>
|
||||
@@ -1453,7 +1423,7 @@
|
||||
<value>Validate Regional Domain IPs</value>
|
||||
</data>
|
||||
<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 name="TbCustomDNSEnable" xml:space="preserve">
|
||||
<value>Enable Custom DNS</value>
|
||||
@@ -1540,49 +1510,25 @@
|
||||
<value>Remove Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Configuration List</value>
|
||||
<value>Configuration item 1, Auto add from subscription group</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Fallback</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||
<value>Multi-Configuration Fallback by sing-box</value>
|
||||
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Core '{0}' does not support network type '{1}'</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||
<value>Multi-Configuration Fallback by Xray</value>
|
||||
<data name="MsgCoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Core '{0}' does not support network type '{1}'.</value>
|
||||
<data name="MsgCoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
|
||||
<data name="MsgInvalidProperty" xml:space="preserve">
|
||||
<value>The {0} property is invalid, please check</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}'.</value>
|
||||
</data>
|
||||
<data name="ProxyChainedPrefix" xml:space="preserve">
|
||||
<value>Proxy chained: </value>
|
||||
</data>
|
||||
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
|
||||
<value>Routing rule outbound: </value>
|
||||
</data>
|
||||
<data name="PolicyGroupPrefix" xml:space="preserve">
|
||||
<value>Policy group: </value>
|
||||
</data>
|
||||
<data name="NodeTagNotExist" xml:space="preserve">
|
||||
<value>Node alias '{0}' does not exist.</value>
|
||||
</data>
|
||||
<data name="GroupEmpty" xml:space="preserve">
|
||||
<value>Group '{0}' is empty. Please add at least one node.</value>
|
||||
</data>
|
||||
<data name="InvalidProperty" xml:space="preserve">
|
||||
<value>The {0} property is invalid, please check.</value>
|
||||
</data>
|
||||
<data name="GroupSelfReference" xml:space="preserve">
|
||||
<value>{0} Group cannot reference itself or have a circular reference</value>
|
||||
</data>
|
||||
<data name="NotSupportProtocol" xml:space="preserve">
|
||||
<value>Not support protocol '{0}'.</value>
|
||||
<data name="MsgNotSupportProtocol" xml:space="preserve">
|
||||
<value>Not support protocol '{0}'</value>
|
||||
</data>
|
||||
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
||||
<value>If the system does not have a tray function, please do not enable it</value>
|
||||
@@ -1609,7 +1555,7 @@
|
||||
<value>Certificate Pinning</value>
|
||||
</data>
|
||||
<data name="TbCertPinningTips" xml:space="preserve">
|
||||
<value>Server Certificate (PEM format, optional)
|
||||
<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>
|
||||
@@ -1638,4 +1584,91 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<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>
|
||||
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgGroupCycleDependency" xml:space="preserve">
|
||||
<value>Group {0} has a cycle dependency on child node {1}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeError" xml:space="preserve">
|
||||
<value>Group {0} child node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
|
||||
<value>Group {0} has no valid child node.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
|
||||
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription previous proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription next proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="menuGenGroupServer" xml:space="preserve">
|
||||
<value>Generate Policy Group</value>
|
||||
</data>
|
||||
<data name="menuAllServers" xml:space="preserve">
|
||||
<value>All configurations</value>
|
||||
</data>
|
||||
<data name="menuGenRegionGroup" xml:space="preserve">
|
||||
<value>Group by Region</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1027,7 +1027,7 @@
|
||||
<value>sing-box Mux Protocol</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||
<value>Full process name (Tun mode)</value>
|
||||
<value>Process (Tun mode)</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||
<value>IP or IP CIDR</value>
|
||||
@@ -1071,9 +1071,6 @@
|
||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||
<value>MTU</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
<value>Enable additional Inbound</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||
<value>Enable IPv6 Address</value>
|
||||
</data>
|
||||
@@ -1113,9 +1110,6 @@
|
||||
<data name="menuAddHttpServer" xml:space="preserve">
|
||||
<value>Add [HTTP]</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
||||
<value>which conflicts with the group previous proxy</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Enable fragment</value>
|
||||
</data>
|
||||
@@ -1377,24 +1371,6 @@
|
||||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>Will cover the port, separate with commas (,)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Generate Policy Group from Multiple Profiles</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>Random by Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>RoundRobin by Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>LeastPing by Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>LeastLoad by Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>LeastPing by sing-box</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>Export</value>
|
||||
</data>
|
||||
@@ -1419,17 +1395,11 @@
|
||||
<data name="TbDomesticDNS" xml:space="preserve">
|
||||
<value>Domestic DNS</value>
|
||||
</data>
|
||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||
<value>Via proxy — please ensure remote availability</value>
|
||||
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||
<value>Direct Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
||||
<value>xray Freedom 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 name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>Proxy Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||
<value>Add Common DNS Hosts</value>
|
||||
@@ -1453,7 +1423,7 @@
|
||||
<value>Validate Regional Domain IPs</value>
|
||||
</data>
|
||||
<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 name="TbCustomDNSEnable" xml:space="preserve">
|
||||
<value>Enable Custom DNS</value>
|
||||
@@ -1540,49 +1510,25 @@
|
||||
<value>Remove Child </value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Configuration List</value>
|
||||
<value>Configuration item 1, Auto add from subscription group</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Fallback</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||
<value>Fallback by sing-box</value>
|
||||
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Core '{0}' does not support network type '{1}'</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||
<value>Fallback by Xray</value>
|
||||
<data name="MsgCoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Core '{0}' does not support network type '{1}'.</value>
|
||||
<data name="MsgCoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
|
||||
<data name="MsgInvalidProperty" xml:space="preserve">
|
||||
<value>The {0} property is invalid, please check</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}'.</value>
|
||||
</data>
|
||||
<data name="ProxyChainedPrefix" xml:space="preserve">
|
||||
<value>Proxy chained: </value>
|
||||
</data>
|
||||
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
|
||||
<value>Routing rule outbound: </value>
|
||||
</data>
|
||||
<data name="PolicyGroupPrefix" xml:space="preserve">
|
||||
<value>Policy group: </value>
|
||||
</data>
|
||||
<data name="NodeTagNotExist" xml:space="preserve">
|
||||
<value>Node alias '{0}' does not exist.</value>
|
||||
</data>
|
||||
<data name="GroupEmpty" xml:space="preserve">
|
||||
<value>Group '{0}' is empty. Please add at least one node.</value>
|
||||
</data>
|
||||
<data name="InvalidProperty" xml:space="preserve">
|
||||
<value>The {0} property is invalid, please check.</value>
|
||||
</data>
|
||||
<data name="GroupSelfReference" xml:space="preserve">
|
||||
<value>{0} Group cannot reference itself or have a circular reference</value>
|
||||
</data>
|
||||
<data name="NotSupportProtocol" xml:space="preserve">
|
||||
<value>Not support protocol '{0}'.</value>
|
||||
<data name="MsgNotSupportProtocol" xml:space="preserve">
|
||||
<value>Not support protocol '{0}'</value>
|
||||
</data>
|
||||
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
||||
<value>If the system does not have a tray function, please do not enable it</value>
|
||||
@@ -1609,7 +1555,7 @@
|
||||
<value>Certificate Pinning</value>
|
||||
</data>
|
||||
<data name="TbCertPinningTips" xml:space="preserve">
|
||||
<value>Server Certificate (PEM format, optional)
|
||||
<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>
|
||||
@@ -1638,4 +1584,91 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<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>
|
||||
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgGroupCycleDependency" xml:space="preserve">
|
||||
<value>Group {0} has a cycle dependency on child node {1}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeError" xml:space="preserve">
|
||||
<value>Group {0} child node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
|
||||
<value>Group {0} has no valid child node.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
|
||||
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription previous proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription next proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="menuGenGroupServer" xml:space="preserve">
|
||||
<value>Generate Policy Group</value>
|
||||
</data>
|
||||
<data name="menuAllServers" xml:space="preserve">
|
||||
<value>All configurations</value>
|
||||
</data>
|
||||
<data name="menuGenRegionGroup" xml:space="preserve">
|
||||
<value>Group by Region</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1027,7 +1027,7 @@
|
||||
<value>Протокол Mux для sing-box</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||
<value>Полное имя процесса (режим TUN)</value>
|
||||
<value>Process (Tun mode)</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||
<value>IP-адрес или сеть CIDR</value>
|
||||
@@ -1071,9 +1071,6 @@
|
||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||
<value>MTU</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
<value>Включить дополнительный входящий канал</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||
<value>Включить IPv6 адреса</value>
|
||||
</data>
|
||||
@@ -1113,9 +1110,6 @@
|
||||
<data name="menuAddHttpServer" xml:space="preserve">
|
||||
<value>Добавить сервер [HTTP]</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
||||
<value>что конфликтует с предыдущим прокси группы</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Включить фрагментацию (Fragment)</value>
|
||||
</data>
|
||||
@@ -1377,24 +1371,6 @@
|
||||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>Заменит указанный порт, перечисляйте через запятую (,)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Generate Policy Group from Multiple Profiles</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>Случайный (Xray)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>Круговой (Xray)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>Минимальное RTT (минимальное время туда-обратно) (Xray)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>Минимальная нагрузка (Xray)</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>Минимальное RTT (минимальное время туда-обратно) (sing-box)</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>Экспортировать конфигурацию</value>
|
||||
</data>
|
||||
@@ -1419,17 +1395,11 @@
|
||||
<data name="TbDomesticDNS" xml:space="preserve">
|
||||
<value>Внутренний DNS</value>
|
||||
</data>
|
||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||
<value>Via proxy — please ensure remote availability</value>
|
||||
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||
<value>Direct Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
||||
<value>Стратегия резолвинга Freedom (Xray)</value>
|
||||
</data>
|
||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
||||
<value>Стратегия прямого резолвинга (sing-box)</value>
|
||||
</data>
|
||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>Стратегия удалённого резолвинга (sing-box)</value>
|
||||
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>Proxy Target Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||
<value>Добавить стандартные записи hosts (DNS)</value>
|
||||
@@ -1453,7 +1423,7 @@
|
||||
<value>Проверять IP-адреса региональных доменов</value>
|
||||
</data>
|
||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||
<value>При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn), и оставляет только ожидаемые IP-адреса</value>
|
||||
<value>При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn - geoip:cn), и оставляет только ожидаемые IP-адреса</value>
|
||||
</data>
|
||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||
<value>Включить пользовательский DNS</value>
|
||||
@@ -1540,49 +1510,25 @@
|
||||
<value>Remove Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Configuration List</value>
|
||||
<value>Configuration item 1, Auto add from subscription group</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Fallback</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||
<value>Multi-Configuration Fallback by sing-box</value>
|
||||
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Core '{0}' does not support network type '{1}'</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||
<value>Multi-Configuration Fallback by Xray</value>
|
||||
<data name="MsgCoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>Core '{0}' does not support network type '{1}'.</value>
|
||||
<data name="MsgCoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
|
||||
<data name="MsgInvalidProperty" xml:space="preserve">
|
||||
<value>The {0} property is invalid, please check</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>Core '{0}' does not support protocol '{1}'.</value>
|
||||
</data>
|
||||
<data name="ProxyChainedPrefix" xml:space="preserve">
|
||||
<value>Proxy chained: </value>
|
||||
</data>
|
||||
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
|
||||
<value>Routing rule outbound: </value>
|
||||
</data>
|
||||
<data name="PolicyGroupPrefix" xml:space="preserve">
|
||||
<value>Policy group: </value>
|
||||
</data>
|
||||
<data name="NodeTagNotExist" xml:space="preserve">
|
||||
<value>Node alias '{0}' does not exist.</value>
|
||||
</data>
|
||||
<data name="GroupEmpty" xml:space="preserve">
|
||||
<value>Group '{0}' is empty. Please add at least one node.</value>
|
||||
</data>
|
||||
<data name="InvalidProperty" xml:space="preserve">
|
||||
<value>The {0} property is invalid, please check.</value>
|
||||
</data>
|
||||
<data name="GroupSelfReference" xml:space="preserve">
|
||||
<value>{0} Group cannot reference itself or have a circular reference</value>
|
||||
</data>
|
||||
<data name="NotSupportProtocol" xml:space="preserve">
|
||||
<value>Not support protocol '{0}'.</value>
|
||||
<data name="MsgNotSupportProtocol" xml:space="preserve">
|
||||
<value>Not support protocol '{0}'</value>
|
||||
</data>
|
||||
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
||||
<value>If the system does not have a tray function, please do not enable it</value>
|
||||
@@ -1609,7 +1555,7 @@
|
||||
<value>Certificate Pinning</value>
|
||||
</data>
|
||||
<data name="TbCertPinningTips" xml:space="preserve">
|
||||
<value>Server Certificate (PEM format, optional)
|
||||
<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>
|
||||
@@ -1638,4 +1584,91 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<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>
|
||||
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgGroupCycleDependency" xml:space="preserve">
|
||||
<value>Group {0} has a cycle dependency on child node {1}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeError" xml:space="preserve">
|
||||
<value>Group {0} child node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} warning: {2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
|
||||
<value>Group {0} child group node {1} error: {2}. Skipping this node.</value>
|
||||
</data>
|
||||
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
|
||||
<value>Group {0} has no valid child node.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
|
||||
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
|
||||
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription previous proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
|
||||
<value>Subscription next proxy {0} not found. Skipping.</value>
|
||||
</data>
|
||||
<data name="menuGenGroupServer" xml:space="preserve">
|
||||
<value>Generate Policy Group</value>
|
||||
</data>
|
||||
<data name="menuAllServers" xml:space="preserve">
|
||||
<value>All configurations</value>
|
||||
</data>
|
||||
<data name="menuGenRegionGroup" xml:space="preserve">
|
||||
<value>Group by Region</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1024,7 +1024,7 @@
|
||||
<value>sing-box Mux 多路复用协议</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||
<value>进程名全称 (Tun 模式)</value>
|
||||
<value>进程 (Tun 模式)</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||
<value>IP 或 IP CIDR</value>
|
||||
@@ -1068,9 +1068,6 @@
|
||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||
<value>MTU</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
<value>启用额外监听端口</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||
<value>启用 IPv6</value>
|
||||
</data>
|
||||
@@ -1110,9 +1107,6 @@
|
||||
<data name="menuAddHttpServer" xml:space="preserve">
|
||||
<value>添加 [HTTP] </value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
||||
<value>和分组前置代理冲突</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>启用分片 (Fragment)</value>
|
||||
</data>
|
||||
@@ -1374,24 +1368,6 @@
|
||||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>会覆盖端口,多组时用逗号 (,) 隔开</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>多选生成策略组</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>多选随机 Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>多选负载均衡 Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>多选最低延迟 Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>多选最稳定 Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>多选最低延迟 sing-box</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>导出</value>
|
||||
</data>
|
||||
@@ -1416,17 +1392,11 @@
|
||||
<data name="TbDomesticDNS" xml:space="preserve">
|
||||
<value>直连 DNS</value>
|
||||
</data>
|
||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||
<value>通过代理,请确保远程可用</value>
|
||||
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||
<value>直连目标解析策略</value>
|
||||
</data>
|
||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
||||
<value>xray freedom 解析策略</value>
|
||||
</data>
|
||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
||||
<value>sing-box 直连解析策略</value>
|
||||
</data>
|
||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>sing-box 远程解析策略</value>
|
||||
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>代理目标解析策略</value>
|
||||
</data>
|
||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||
<value>添加常用 DNS Hosts</value>
|
||||
@@ -1450,7 +1420,7 @@
|
||||
<value>校验相应地区域名 IP</value>
|
||||
</data>
|
||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||
<value>配置后,会对相应地区域名(如 geosite:cn)的返回 IP 进行校验,仅返回期望 IP</value>
|
||||
<value>配置后,会对相应地区域名(如 geosite:cn - geoip:cn)的返回 IP 进行校验,仅返回期望 IP</value>
|
||||
</data>
|
||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||
<value>启用自定义 DNS</value>
|
||||
@@ -1537,49 +1507,25 @@
|
||||
<value>删除子配置</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>子配置项</value>
|
||||
<value>子配置项一,从订阅分组中自动添加</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>故障转移</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||
<value>多选故障转移 sing-box</value>
|
||||
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>核心 '{0}' 不支持网络类型 '{1}'</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||
<value>多选故障转移 Xray</value>
|
||||
<data name="MsgCoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>核心 '{0}' 在使用传输方式 '{2}' 时不支持协议 '{1}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>核心 '{0}' 不支持网络类型 '{1}'。</value>
|
||||
<data name="MsgCoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>核心 '{0}' 不支持协议 '{1}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>核心 '{0}' 在使用传输方式 '{2}' 时不支持协议 '{1}'。</value>
|
||||
<data name="MsgInvalidProperty" xml:space="preserve">
|
||||
<value>{0} 属性无效,请检查</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>核心 '{0}' 不支持协议 '{1}'。</value>
|
||||
</data>
|
||||
<data name="ProxyChainedPrefix" xml:space="preserve">
|
||||
<value>代理链: </value>
|
||||
</data>
|
||||
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
|
||||
<value>路由规则出站: </value>
|
||||
</data>
|
||||
<data name="PolicyGroupPrefix" xml:space="preserve">
|
||||
<value>策略组: </value>
|
||||
</data>
|
||||
<data name="NodeTagNotExist" xml:space="preserve">
|
||||
<value>别名 '{0}' 不存在。</value>
|
||||
</data>
|
||||
<data name="GroupEmpty" xml:space="preserve">
|
||||
<value>组“{0}”为空。请至少添加一个配置。</value>
|
||||
</data>
|
||||
<data name="InvalidProperty" xml:space="preserve">
|
||||
<value>{0}属性无效,请检查</value>
|
||||
</data>
|
||||
<data name="GroupSelfReference" xml:space="preserve">
|
||||
<value>{0} 分组不能引用自身或循环引用</value>
|
||||
</data>
|
||||
<data name="NotSupportProtocol" xml:space="preserve">
|
||||
<value>不支持协议 '{0}'。</value>
|
||||
<data name="MsgNotSupportProtocol" xml:space="preserve">
|
||||
<value>不支持协议 '{0}'</value>
|
||||
</data>
|
||||
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
||||
<value>如果系统没有托盘功能,请不要开启</value>
|
||||
@@ -1606,10 +1552,10 @@
|
||||
<value>固定证书</value>
|
||||
</data>
|
||||
<data name="TbCertPinningTips" xml:space="preserve">
|
||||
<value>服务器证书(PEM 格式,可选)
|
||||
<value>固定证书(二选一填写即可)
|
||||
当指定此证书后,将固定该证书,并禁用“跳过证书验证”选项。
|
||||
|
||||
“获取证书”操作可能失败,原因可能是使用了自签证书,或系统中存在不受信任或恶意的 CA。</value>
|
||||
“获取证书”操作可能失败,原因包括使用了自签名证书,或系统中存在不受信任甚至恶意的 CA。</value>
|
||||
</data>
|
||||
<data name="TbFetchCert" xml:space="preserve">
|
||||
<value>获取证书</value>
|
||||
@@ -1635,4 +1581,91 @@
|
||||
<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>
|
||||
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
|
||||
<value>路由规则 {0} 出站节点 {1} 警告:{2}</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
|
||||
<value>路由规则 {0} 出站节点 {1} 错误:{2}。已回退为仅使用代理节点。</value>
|
||||
</data>
|
||||
<data name="MsgGroupCycleDependency" xml:space="preserve">
|
||||
<value>节点组 {0} 与子节点 {1} 存在循环依赖,已跳过该节点。</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
|
||||
<value>节点组 {0} 子节点 {1} 警告:{2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeError" xml:space="preserve">
|
||||
<value>节点组 {0} 子节点 {1} 错误:{2}。已跳过该节点。</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
|
||||
<value>节点组 {0} 子节点组 {1} 警告:{2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
|
||||
<value>节点组 {0} 子节点组 {1} 错误:{2}。已跳过该节点。</value>
|
||||
</data>
|
||||
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
|
||||
<value>节点组 {0} 下没有有效的子节点。</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
|
||||
<value>路由规则 {0} 的出站标签为空,已回退为仅使用代理节点。</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
|
||||
<value>路由规则 {0} 的出站节点 {1} 未找到,已回退为仅使用代理节点。</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
|
||||
<value>订阅前置节点 {0} 未找到,已跳过。</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
|
||||
<value>订阅后置节点 {0} 未找到,已跳过。</value>
|
||||
</data>
|
||||
<data name="menuGenGroupServer" xml:space="preserve">
|
||||
<value>一键生成策略组</value>
|
||||
</data>
|
||||
<data name="menuAllServers" xml:space="preserve">
|
||||
<value>全部配置项</value>
|
||||
</data>
|
||||
<data name="menuGenRegionGroup" xml:space="preserve">
|
||||
<value>按地区分组</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -127,7 +127,7 @@
|
||||
<value>設定格式不正確</value>
|
||||
</data>
|
||||
<data name="CustomServerTips" xml:space="preserve">
|
||||
<value>注意,自訂設定完全依賴您自己的設定,不能使用所有設定功能。如需使用系統代理請手動修改偵聽埠。</value>
|
||||
<value>注意,自訂設定完全依賴您自行輸入的內容,部分功能可能無法使用。如需啟用系統代理,請手動調整監聽埠。</value>
|
||||
</data>
|
||||
<data name="Downloading" xml:space="preserve">
|
||||
<value>下載開始...</value>
|
||||
@@ -139,7 +139,7 @@
|
||||
<value>生成預設設定檔失敗</value>
|
||||
</data>
|
||||
<data name="FailedGetDefaultConfiguration" xml:space="preserve">
|
||||
<value>獲取預設設定失敗</value>
|
||||
<value>取得預設設定失敗</value>
|
||||
</data>
|
||||
<data name="FailedImportedCustomServer" xml:space="preserve">
|
||||
<value>匯入自訂設定失敗</value>
|
||||
@@ -148,7 +148,7 @@
|
||||
<value>讀取設定失敗</value>
|
||||
</data>
|
||||
<data name="FillCorrectServerPort" xml:space="preserve">
|
||||
<value>請填寫正確格式的埠</value>
|
||||
<value>請填寫有效的埠號</value>
|
||||
</data>
|
||||
<data name="FillLocalListeningPort" xml:space="preserve">
|
||||
<value>請填寫本機偵聽埠</value>
|
||||
@@ -247,7 +247,7 @@
|
||||
<value>非 VMess 或 SS 協定</value>
|
||||
</data>
|
||||
<data name="NotFoundCore" xml:space="preserve">
|
||||
<value>在資料夾 ({0}) 下未找到 Core 檔案 (檔案名: {1}),請下載後放入資料夾,下載網址: {2}</value>
|
||||
<value>在資料夾 ({0}) 中找不到 Core 檔案(檔名:{1})。請下載後放入該資料夾。下載網址:{2}</value>
|
||||
</data>
|
||||
<data name="NoValidQRcodeFound" xml:space="preserve">
|
||||
<value>掃描完成,未發現有效二維碼</value>
|
||||
@@ -304,7 +304,7 @@
|
||||
<value>是否確定移除規則?</value>
|
||||
</data>
|
||||
<data name="RoutingRuleDetailRequiredTips" xml:space="preserve">
|
||||
<value>{0},必填其中一項.</value>
|
||||
<value>{0},至少需填寫其中一項。</value>
|
||||
</data>
|
||||
<data name="LvRemarks" xml:space="preserve">
|
||||
<value>別名</value>
|
||||
@@ -385,7 +385,7 @@
|
||||
<value>所有</value>
|
||||
</data>
|
||||
<data name="FillServerAddressCustom" xml:space="preserve">
|
||||
<value>請瀏覽匯入設定</value>
|
||||
<value>請選擇要匯入的設定檔</value>
|
||||
</data>
|
||||
<data name="Speedtesting" xml:space="preserve">
|
||||
<value>測試中...</value>
|
||||
@@ -472,7 +472,7 @@
|
||||
<value>語言 (需重啟)</value>
|
||||
</data>
|
||||
<data name="menuAddServerViaClipboard" xml:space="preserve">
|
||||
<value>從剪貼簿導入分享連結</value>
|
||||
<value>從剪貼簿匯入分享連結</value>
|
||||
</data>
|
||||
<data name="menuAddServerViaScan" xml:space="preserve">
|
||||
<value>掃描螢幕上的二維碼</value>
|
||||
@@ -616,10 +616,10 @@
|
||||
<value>SNI</value>
|
||||
</data>
|
||||
<data name="TbStreamSecurity" xml:space="preserve">
|
||||
<value>傳輸層安全 (TLS)</value>
|
||||
<value>傳輸層安全性 (TLS)</value>
|
||||
</data>
|
||||
<data name="TipNetwork" xml:space="preserve">
|
||||
<value>*預設 TCP,選錯會無法連接</value>
|
||||
<value>*預設 TCP,選錯會無法連線</value>
|
||||
</data>
|
||||
<data name="TbCoreType" xml:space="preserve">
|
||||
<value>Core 類型</value>
|
||||
@@ -652,7 +652,7 @@
|
||||
<value>SOCKS 埠</value>
|
||||
</data>
|
||||
<data name="TipPreSocksPort" xml:space="preserve">
|
||||
<value>*自訂設定的 Socks 埠值,可不設定;當設定此值後,將使用 Xray/sing-box (Tun) 額外啟動一個前置 Socks 服務,提供分流和速度顯示等功能</value>
|
||||
<value>*自訂設定的 Socks 埠值,可留空;當設定此值後,將使用 Xray/sing-box (Tun) 額外啟動一個前置 Socks 服務,提供分流和速度顯示等功能</value>
|
||||
</data>
|
||||
<data name="TbBrowse" xml:space="preserve">
|
||||
<value>瀏覽</value>
|
||||
@@ -1024,7 +1024,7 @@
|
||||
<value>sing-box Mux 多路復用協定</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleProcess" xml:space="preserve">
|
||||
<value>行程名全稱 (Tun 模式)</value>
|
||||
<value>行程 (Tun 模式)</value>
|
||||
</data>
|
||||
<data name="TbRoutingRuleIP" xml:space="preserve">
|
||||
<value>IP 或 IP CIDR</value>
|
||||
@@ -1068,9 +1068,6 @@
|
||||
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||
<value>MTU</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||
<value>啟用額外偵聽連接埠</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
|
||||
<value>啟用 IPv6</value>
|
||||
</data>
|
||||
@@ -1110,9 +1107,6 @@
|
||||
<data name="menuAddHttpServer" xml:space="preserve">
|
||||
<value>新增 [HTTP] 節點</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
|
||||
<value>和分組前置代理衝突</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>啟用分片(Fragment)</value>
|
||||
</data>
|
||||
@@ -1315,7 +1309,7 @@
|
||||
<value>安裝字體到系統中,選擇或填入字體名稱,重新啟動後生效</value>
|
||||
</data>
|
||||
<data name="menuExitTips" xml:space="preserve">
|
||||
<value>是否確定退出?</value>
|
||||
<value>確定要退出嗎?</value>
|
||||
</data>
|
||||
<data name="LvMemo" xml:space="preserve">
|
||||
<value>備註備忘</value>
|
||||
@@ -1374,24 +1368,6 @@
|
||||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>多選生成策略組</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>多選隨機 Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>多選負載平衡 Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>多選最低延遲 Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>多選最穩定 Xray</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>多選最低延遲 sing-box</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
<value>匯出</value>
|
||||
</data>
|
||||
@@ -1416,17 +1392,11 @@
|
||||
<data name="TbDomesticDNS" xml:space="preserve">
|
||||
<value>直連 DNS</value>
|
||||
</data>
|
||||
<data name="TbRemoteDNSTips" xml:space="preserve">
|
||||
<value>通过代理,请确保远程可用</value>
|
||||
<data name="TbDirectResolveStrategy" xml:space="preserve">
|
||||
<value>直連目標解析策略</value>
|
||||
</data>
|
||||
<data name="TbXrayFreedomStrategy" xml:space="preserve">
|
||||
<value>xray freedom 解析策略</value>
|
||||
</data>
|
||||
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
|
||||
<value>sing-box 直連解析策略</value>
|
||||
</data>
|
||||
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>sing-box 遠程解析策略</value>
|
||||
<data name="TbRemoteResolveStrategy" xml:space="preserve">
|
||||
<value>代理目標解析策略</value>
|
||||
</data>
|
||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||
<value>新增常用 DNS Hosts</value>
|
||||
@@ -1450,7 +1420,7 @@
|
||||
<value>校驗相應地區域名 IP</value>
|
||||
</data>
|
||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||
<value>配置後,會對相應地區域名(如 geosite:cn)的返回 IP 進行校驗,僅返回期望 IP</value>
|
||||
<value>配置後,會對相應地區域名(如 geosite:cn - geoip:cn)的返回 IP 進行校驗,僅返回期望 IP</value>
|
||||
</data>
|
||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||
<value>啟用自訂 DNS</value>
|
||||
@@ -1525,61 +1495,37 @@
|
||||
<value>策略組類型</value>
|
||||
</data>
|
||||
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
||||
<value>添加策略組</value>
|
||||
<value>新增策略組</value>
|
||||
</data>
|
||||
<data name="menuAddProxyChainServer" xml:space="preserve">
|
||||
<value>添加鏈式代理</value>
|
||||
<value>新增鏈式代理</value>
|
||||
</data>
|
||||
<data name="menuAddChildServer" xml:space="preserve">
|
||||
<value>添加子配置</value>
|
||||
<value>新增子配置</value>
|
||||
</data>
|
||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||
<value>刪除子配置</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>子配置項</value>
|
||||
<value>子配置項目一,從訂閱分組中自動新增</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>容錯移轉</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||
<value>多選容錯移轉 sing-box</value>
|
||||
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>核心 '{0}' 不支援網路類型 '{1}'</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||
<value>多選容錯移轉 Xray</value>
|
||||
<data name="MsgCoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>核心 '{0}' 在使用傳輸方式 '{2}' 時不支援協定 '{1}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportNetwork" xml:space="preserve">
|
||||
<value>核心 '{0}' 不支援網路類型 '{1}'.</value>
|
||||
<data name="MsgCoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>核心 '{0}' 不支援協定 '{1}'</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
|
||||
<value>核心 '{0}' 在使用傳輸方式 '{2}' 時不支援協定 '{1}'.</value>
|
||||
<data name="MsgInvalidProperty" xml:space="preserve">
|
||||
<value>{0} 屬性無效,請檢查</value>
|
||||
</data>
|
||||
<data name="CoreNotSupportProtocol" xml:space="preserve">
|
||||
<value>核心 '{0}' 不支援協定 '{1}'.</value>
|
||||
</data>
|
||||
<data name="ProxyChainedPrefix" xml:space="preserve">
|
||||
<value>代理鏈: </value>
|
||||
</data>
|
||||
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
|
||||
<value>路由規則出站: </value>
|
||||
</data>
|
||||
<data name="PolicyGroupPrefix" xml:space="preserve">
|
||||
<value>策略組: </value>
|
||||
</data>
|
||||
<data name="NodeTagNotExist" xml:space="preserve">
|
||||
<value>別名 '{0}' 不存在。</value>
|
||||
</data>
|
||||
<data name="GroupEmpty" xml:space="preserve">
|
||||
<value>組“{0}”為空.請至少添加一個配置。</value>
|
||||
</data>
|
||||
<data name="InvalidProperty" xml:space="preserve">
|
||||
<value>{0}屬性無效,請檢查</value>
|
||||
</data>
|
||||
<data name="GroupSelfReference" xml:space="preserve">
|
||||
<value>{0} 分組不能引用自身或循環引用</value>
|
||||
</data>
|
||||
<data name="NotSupportProtocol" xml:space="preserve">
|
||||
<value>不支援協定 '{0}'.</value>
|
||||
<data name="MsgNotSupportProtocol" xml:space="preserve">
|
||||
<value>不支援協定 '{0}'</value>
|
||||
</data>
|
||||
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
|
||||
<value>如果系統沒有託盤功能,請不要開啟</value>
|
||||
@@ -1606,7 +1552,7 @@
|
||||
<value>憑證綁定</value>
|
||||
</data>
|
||||
<data name="TbCertPinningTips" xml:space="preserve">
|
||||
<value>伺服器憑證(PEM 格式,可選)
|
||||
<value>固定憑證(二選一填寫即可)
|
||||
若已指定,憑證將會被綁定,並且「跳過憑證驗證」將被停用。
|
||||
|
||||
若使用自簽憑證,或系統中存在不受信任或惡意的 CA,「取得憑證」動作可能會失敗。</value>
|
||||
@@ -1635,4 +1581,91 @@
|
||||
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
|
||||
<value>macOS 在 Dock 欄顯示 (需重啟)</value>
|
||||
</data>
|
||||
</root>
|
||||
<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>提供過期快取(Serve Stale)</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>
|
||||
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
|
||||
<value>路由規則 {0} 的出站節點 {1} 發出警告:{2}</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
|
||||
<value>路由規則 {0} 的出站節點 {1} 發生錯誤:{2}。已回退為僅使用代理節點。</value>
|
||||
</data>
|
||||
<data name="MsgGroupCycleDependency" xml:space="preserve">
|
||||
<value>節點組 {0} 與子節點 {1} 存在循環依賴。已跳過此節點。</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
|
||||
<value>節點組 {0} 的子節點 {1} 發出警告:{2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildNodeError" xml:space="preserve">
|
||||
<value>節點組 {0} 的子節點 {1} 發生錯誤:{2}。已跳過此節點。</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
|
||||
<value>節點組 {0} 的子節點組 {1} 發出警告:{2}</value>
|
||||
</data>
|
||||
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
|
||||
<value>節點組 {0} 的子節點組 {1} 發生錯誤:{2}。已跳過此節點。</value>
|
||||
</data>
|
||||
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
|
||||
<value>節點組 {0} 沒有可用的有效子節點。</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
|
||||
<value>路由規則 {0} 的出站標籤為空。已回退為僅使用代理節點。</value>
|
||||
</data>
|
||||
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
|
||||
<value>找不到路由規則 {0} 的出站節點 {1}。已回退為僅使用代理節點。</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
|
||||
<value>找不到訂閱的前一個代理 {0}。已跳過。</value>
|
||||
</data>
|
||||
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
|
||||
<value>找不到訂閱的下一個代理 {0}。已跳過。</value>
|
||||
</data>
|
||||
<data name="menuGenGroupServer" xml:space="preserve">
|
||||
<value>生成策略組</value>
|
||||
</data>
|
||||
<data name="menuAllServers" xml:space="preserve">
|
||||
<value>所有配置項</value>
|
||||
</data>
|
||||
<data name="menuGenRegionGroup" xml:space="preserve">
|
||||
<value>按區域分組</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"log": {
|
||||
"access": "Vaccess.log",
|
||||
"error": "Verror.log",
|
||||
@@ -6,34 +6,6 @@
|
||||
},
|
||||
"inbounds": [],
|
||||
"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",
|
||||
"tag": "direct"
|
||||
|
||||
@@ -5,19 +5,12 @@
|
||||
},
|
||||
"inbounds": [],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "proxy",
|
||||
"server": "",
|
||||
"server_port": 443
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
]
|
||||
"rules": []
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,34 @@
|
||||
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 readonly Config _config = context.AppConfig;
|
||||
private readonly ProfileItem _node = context.Node;
|
||||
|
||||
private SingboxConfig _coreConfig = new();
|
||||
|
||||
#region public gen function
|
||||
|
||||
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
|
||||
public RetResult GenerateClientConfigContent()
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (node == null
|
||||
|| !node.IsValid())
|
||||
if (_node == null
|
||||
|| !_node.IsValid())
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
@@ -45,44 +36,76 @@ public partial class CoreConfigSingboxService(Config config)
|
||||
return ret;
|
||||
}
|
||||
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (singboxConfig == null)
|
||||
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (_coreConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenLog(singboxConfig);
|
||||
GenLog();
|
||||
|
||||
await GenInbounds(singboxConfig);
|
||||
GenInbounds();
|
||||
|
||||
if (node.ConfigType == EConfigType.WireGuard)
|
||||
{
|
||||
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());
|
||||
}
|
||||
GenOutbounds();
|
||||
|
||||
await GenMoreOutbounds(node, singboxConfig);
|
||||
GenRouting();
|
||||
|
||||
await GenRouting(singboxConfig);
|
||||
GenDns();
|
||||
|
||||
await GenDns(node, singboxConfig);
|
||||
GenExperimental();
|
||||
|
||||
await GenExperimental(singboxConfig);
|
||||
|
||||
await ConvertGeo2Ruleset(singboxConfig);
|
||||
ConvertGeo2Ruleset();
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
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;
|
||||
}
|
||||
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();
|
||||
try
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||
@@ -114,44 +131,35 @@ public partial class CoreConfigSingboxService(Config config)
|
||||
return ret;
|
||||
}
|
||||
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (singboxConfig == null)
|
||||
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (_coreConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
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);
|
||||
//GenDns(new(), singboxConfig);
|
||||
singboxConfig.inbounds.Clear();
|
||||
singboxConfig.outbounds.RemoveAt(0);
|
||||
var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo();
|
||||
|
||||
GenLog();
|
||||
GenMinimizedDns();
|
||||
_coreConfig.inbounds.Clear();
|
||||
_coreConfig.outbounds.RemoveAt(0);
|
||||
|
||||
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
|
||||
if (!(Global.SingboxSupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.Port <= 0)
|
||||
if (!it.ConfigType.IsComplexType() && it.Port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
if (item is null || item.IsComplex() || !item.IsValid())
|
||||
var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId);
|
||||
var item = context.AllProxiesMap.GetValueOrDefault(actIndexId);
|
||||
if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -190,26 +198,11 @@ public partial class CoreConfigSingboxService(Config config)
|
||||
type = EInboundProtocol.mixed.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();
|
||||
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);
|
||||
}
|
||||
var serverList = new CoreConfigSingboxService(context with { Node = item }).BuildAllProxyOutbounds(tag);
|
||||
FillRangeProxy(serverList, _coreConfig, false);
|
||||
|
||||
//rule
|
||||
Rule4Sbox rule = new()
|
||||
@@ -217,25 +210,11 @@ public partial class CoreConfigSingboxService(Config config)
|
||||
inbound = new List<string> { inbound.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.Data = JsonUtils.Serialize(singboxConfig);
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
return ret;
|
||||
}
|
||||
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();
|
||||
try
|
||||
{
|
||||
if (node == null
|
||||
|| !node.IsValid())
|
||||
if (_node == null
|
||||
|| !_node.IsValid())
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -272,44 +251,20 @@ public partial class CoreConfigSingboxService(Config config)
|
||||
return ret;
|
||||
}
|
||||
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (singboxConfig == null)
|
||||
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (_coreConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenLog(singboxConfig);
|
||||
if (node.ConfigType == EConfigType.WireGuard)
|
||||
{
|
||||
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,
|
||||
};
|
||||
GenLog();
|
||||
GenOutbounds();
|
||||
GenMinimizedDns();
|
||||
|
||||
singboxConfig.route.rules.Clear();
|
||||
singboxConfig.inbounds.Clear();
|
||||
singboxConfig.inbounds.Add(new()
|
||||
_coreConfig.route.rules.Clear();
|
||||
_coreConfig.inbounds.Clear();
|
||||
_coreConfig.inbounds.Add(new()
|
||||
{
|
||||
tag = $"{EInboundProtocol.mixed}{port}",
|
||||
listen = Global.Loopback,
|
||||
@@ -319,202 +274,7 @@ public partial class CoreConfigSingboxService(Config config)
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||
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;
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -2,29 +2,29 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig)
|
||||
private string ApplyFullConfigTemplate()
|
||||
{
|
||||
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
|
||||
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled)
|
||||
var fullConfigTemplate = context.FullConfigTemplate;
|
||||
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())
|
||||
{
|
||||
return JsonUtils.Serialize(singboxConfig);
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
}
|
||||
|
||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||
if (fullConfigTemplateNode == null)
|
||||
{
|
||||
return JsonUtils.Serialize(singboxConfig);
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
}
|
||||
|
||||
// Process outbounds
|
||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
|
||||
foreach (var outbound in singboxConfig.outbounds)
|
||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : [];
|
||||
foreach (var outbound in _coreConfig.outbounds)
|
||||
{
|
||||
if (outbound.type.ToLower() is "direct" or "block")
|
||||
{
|
||||
@@ -42,10 +42,10 @@ public partial class CoreConfigSingboxService
|
||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||
|
||||
// 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();
|
||||
foreach (var endpoint in singboxConfig.endpoints)
|
||||
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : [];
|
||||
foreach (var endpoint in _coreConfig.endpoints)
|
||||
{
|
||||
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
||||
{
|
||||
@@ -56,6 +56,6 @@ public partial class CoreConfigSingboxService
|
||||
fullConfigTemplateNode["endpoints"] = customEndpointsNode;
|
||||
}
|
||||
|
||||
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
|
||||
return JsonUtils.Serialize(fullConfigTemplateNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,65 +2,68 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
|
||||
private void GenDns()
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
if (item != null && item.Enabled == true)
|
||||
var item = context.RawDnsItem;
|
||||
if (item is { Enabled: true })
|
||||
{
|
||||
return await GenDnsCompatible(node, singboxConfig);
|
||||
GenDnsCustom();
|
||||
return;
|
||||
}
|
||||
|
||||
var simpleDNSItem = _config.SimpleDNSItem;
|
||||
await GenDnsServers(singboxConfig, simpleDNSItem);
|
||||
await GenDnsRules(singboxConfig, simpleDNSItem);
|
||||
GenDnsServers();
|
||||
GenDnsRules();
|
||||
|
||||
singboxConfig.dns ??= new Dns4Sbox();
|
||||
singboxConfig.dns.independent_cache = true;
|
||||
_coreConfig.dns ??= new Dns4Sbox();
|
||||
_coreConfig.dns.independent_cache = true;
|
||||
|
||||
// final dns
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
var routing = context.RoutingItem;
|
||||
var useDirectDns = false;
|
||||
if (routing != null)
|
||||
{
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
|
||||
|
||||
useDirectDns = rules?.LastOrDefault() is { } lastRule &&
|
||||
lastRule.OutboundTag == Global.DirectTag &&
|
||||
(lastRule.Port == "0-65535" ||
|
||||
lastRule.Network == "tcp,udp" ||
|
||||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
||||
if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag)
|
||||
{
|
||||
var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0;
|
||||
var noProcess = lastRule.Process == null || lastRule.Process.Count == 0;
|
||||
var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0");
|
||||
var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535";
|
||||
var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp";
|
||||
useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork;
|
||||
}
|
||||
}
|
||||
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
|
||||
if ((!useDirectDns) && simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
|
||||
_coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
|
||||
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,
|
||||
query_type = new List<int> { 1, 28 }, // A and AAAA
|
||||
rewrite_ttl = 1,
|
||||
});
|
||||
}
|
||||
|
||||
await GenOutboundDnsRule(node, singboxConfig);
|
||||
}
|
||||
catch (Exception 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.domain_resolver = Global.SingboxLocalDNSTag;
|
||||
|
||||
var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS);
|
||||
var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First());
|
||||
remoteDns.tag = Global.SingboxRemoteDNSTag;
|
||||
remoteDns.detour = Global.ProxyTag;
|
||||
remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
|
||||
@@ -71,12 +74,12 @@ public partial class CoreConfigSingboxService
|
||||
type = "hosts",
|
||||
predefined = new(),
|
||||
};
|
||||
if (simpleDNSItem.AddCommonHosts == true)
|
||||
if (simpleDnsItem.AddCommonHosts == true)
|
||||
{
|
||||
hostsDns.predefined = Global.PredefinedHosts;
|
||||
}
|
||||
|
||||
if (simpleDNSItem.UseSystemHosts == true)
|
||||
if (simpleDnsItem.UseSystemHosts == true)
|
||||
{
|
||||
var systemHosts = Utils.GetSystemHosts();
|
||||
if (systemHosts != null && systemHosts.Count > 0)
|
||||
@@ -88,13 +91,24 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
|
||||
{
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||
|
||||
foreach (var kvp in userHostsMap)
|
||||
// only allow full match
|
||||
// like example.com and full:example.com,
|
||||
// but not domain:example.com, keyword:example.com or regex:example.com etc.
|
||||
var testRule = new Rule4Sbox();
|
||||
if (!ParseV2Domain(kvp.Key, testRule))
|
||||
{
|
||||
hostsDns.predefined[kvp.Key] = kvp.Value;
|
||||
continue;
|
||||
}
|
||||
if (testRule.domain_keyword?.Count > 0 && !kvp.Key.Contains(':'))
|
||||
{
|
||||
testRule.domain = testRule.domain_keyword;
|
||||
testRule.domain_keyword = null;
|
||||
}
|
||||
if (testRule.domain?.Count == 1)
|
||||
{
|
||||
hostsDns.predefined[testRule.domain.First()] = kvp.Value.Where(Utils.IsIpAddress).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,14 +128,14 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
singboxConfig.dns ??= new Dns4Sbox();
|
||||
singboxConfig.dns.servers ??= new List<Server4Sbox>();
|
||||
singboxConfig.dns.servers.Add(remoteDns);
|
||||
singboxConfig.dns.servers.Add(directDns);
|
||||
singboxConfig.dns.servers.Add(hostsDns);
|
||||
_coreConfig.dns ??= new Dns4Sbox();
|
||||
_coreConfig.dns.servers ??= [];
|
||||
_coreConfig.dns.servers.Add(remoteDns);
|
||||
_coreConfig.dns.servers.Add(directDns);
|
||||
_coreConfig.dns.servers.Add(hostsDns);
|
||||
|
||||
// fake ip
|
||||
if (simpleDNSItem.FakeIP == true)
|
||||
if (simpleDnsItem.FakeIP == true)
|
||||
{
|
||||
var fakeip = new Server4Sbox
|
||||
{
|
||||
@@ -130,55 +144,129 @@ public partial class CoreConfigSingboxService
|
||||
inet4_range = "198.18.0.0/15",
|
||||
inet6_range = "fc00::/18",
|
||||
};
|
||||
singboxConfig.dns.servers.Add(fakeip);
|
||||
_coreConfig.dns.servers.Add(fakeip);
|
||||
}
|
||||
}
|
||||
|
||||
private Server4Sbox GenBootstrapDns()
|
||||
{
|
||||
var finalDns = ParseDnsAddress(context.SimpleDnsItem?.BootstrapDNS ?? Global.DomainPureIPDNSAddress.First());
|
||||
finalDns.tag = Global.SingboxLocalDNSTag;
|
||||
_coreConfig.dns ??= new Dns4Sbox();
|
||||
_coreConfig.dns.servers ??= [];
|
||||
_coreConfig.dns.servers.Add(finalDns);
|
||||
return finalDns;
|
||||
}
|
||||
|
||||
private void GenDnsRules()
|
||||
{
|
||||
var simpleDnsItem = context.SimpleDnsItem;
|
||||
_coreConfig.dns ??= new Dns4Sbox();
|
||||
_coreConfig.dns.rules ??= [];
|
||||
|
||||
_coreConfig.dns.rules.Add(new() { ip_accept_any = true, server = Global.SingboxHostsDNSTag });
|
||||
|
||||
if (context.ProtectDomainList.Count > 0)
|
||||
{
|
||||
_coreConfig.dns.rules.Add(new()
|
||||
{
|
||||
server = Global.SingboxDirectDNSTag,
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||
domain = context.ProtectDomainList.ToList(),
|
||||
});
|
||||
}
|
||||
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
|
||||
{
|
||||
var finalDns = ParseDnsAddress(simpleDNSItem.BootstrapDNS);
|
||||
finalDns.tag = Global.SingboxLocalDNSTag;
|
||||
singboxConfig.dns ??= new Dns4Sbox();
|
||||
singboxConfig.dns.servers ??= new List<Server4Sbox>();
|
||||
singboxConfig.dns.servers.Add(finalDns);
|
||||
return await Task.FromResult(finalDns);
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
|
||||
{
|
||||
singboxConfig.dns ??= new Dns4Sbox();
|
||||
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
||||
|
||||
singboxConfig.dns.rules.AddRange(new[]
|
||||
{
|
||||
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
|
||||
_coreConfig.dns.rules.AddRange(new[]
|
||||
{
|
||||
new Rule4Sbox
|
||||
{
|
||||
server = Global.SingboxRemoteDNSTag,
|
||||
strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy,
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy),
|
||||
clash_mode = ERuleMode.Global.ToString()
|
||||
},
|
||||
new Rule4Sbox
|
||||
{
|
||||
server = Global.SingboxDirectDNSTag,
|
||||
strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct,
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||
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())
|
||||
{
|
||||
query_type = new List<int> { 64, 65 },
|
||||
continue;
|
||||
}
|
||||
var rule = new Rule4Sbox()
|
||||
{
|
||||
query_type = [1, 5, 28], // A, CNAME and AAAA
|
||||
action = "predefined",
|
||||
rcode = "NOTIMP"
|
||||
rcode = "NOERROR",
|
||||
};
|
||||
if (!ParseV2Domain(kvp.Key, rule))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// see: https://xtls.github.io/en/config/dns.html#dnsobject
|
||||
// The matching format (domain:, full:, etc.) is the same as the domain
|
||||
// in the commonly used Routing System. The difference is that without a prefix,
|
||||
// it defaults to using the full: prefix (similar to the common hosts file syntax).
|
||||
if (rule.domain_keyword?.Count > 0 && !kvp.Key.Contains(':'))
|
||||
{
|
||||
rule.domain = rule.domain_keyword;
|
||||
rule.domain_keyword = null;
|
||||
}
|
||||
// example.com #0 -> example.com with NOERROR
|
||||
if (predefined.StartsWith('#') && int.TryParse(predefined.AsSpan(1), out var rcode))
|
||||
{
|
||||
rule.rcode = rcode switch
|
||||
{
|
||||
0 => "NOERROR",
|
||||
1 => "FORMERR",
|
||||
2 => "SERVFAIL",
|
||||
3 => "NXDOMAIN",
|
||||
4 => "NOTIMP",
|
||||
5 => "REFUSED",
|
||||
_ => "NOERROR",
|
||||
};
|
||||
}
|
||||
else if (Utils.IsDomain(predefined))
|
||||
{
|
||||
// example.com CNAME target.com -> example.com with CNAME target.com
|
||||
rule.answer = new List<string> { $"*. IN CNAME {predefined}." };
|
||||
}
|
||||
else if (Utils.IsIpAddress(predefined) && (rule.domain?.Count ?? 0) == 0)
|
||||
{
|
||||
// not full match, but an IP address, treat it as predefined answer
|
||||
if (Utils.IsIpv6(predefined))
|
||||
{
|
||||
rule.answer = new List<string> { $"*. IN AAAA {predefined}" };
|
||||
}
|
||||
else
|
||||
{
|
||||
rule.answer = new List<string> { $"*. IN A {predefined}" };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_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));
|
||||
fakeipFilterRule.invert = true;
|
||||
@@ -188,22 +276,23 @@ public partial class CoreConfigSingboxService
|
||||
type = "logical",
|
||||
mode = "and",
|
||||
rewrite_ttl = 1,
|
||||
rules = new List<Rule4Sbox>
|
||||
{
|
||||
new() {
|
||||
query_type = new List<int> { 1, 28 }, // A and AAAA
|
||||
rules =
|
||||
[
|
||||
new()
|
||||
{
|
||||
query_type = [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)
|
||||
{
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
|
||||
@@ -211,9 +300,9 @@ public partial class CoreConfigSingboxService
|
||||
var expectedIPsRegions = new List<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)
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
@@ -261,7 +350,7 @@ public partial class CoreConfigSingboxService
|
||||
if (item.OutboundTag == Global.DirectTag)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -286,31 +375,46 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
else
|
||||
{
|
||||
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
|
||||
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
|
||||
{
|
||||
var rule4Fake = JsonUtils.DeepCopy(rule);
|
||||
rule4Fake.server = Global.SingboxFakeDNSTag;
|
||||
rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA
|
||||
rule4Fake.rewrite_ttl = 1;
|
||||
singboxConfig.dns.rules.Add(rule4Fake);
|
||||
_coreConfig.dns.rules.Add(rule4Fake);
|
||||
}
|
||||
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 async Task<int> GenDnsCompatible(ProfileItem? node, SingboxConfig singboxConfig)
|
||||
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 void GenDnsCustom()
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
var item = context.RawDnsItem;
|
||||
var strDNS = string.Empty;
|
||||
if (_config.TunModeItem.EnableTun)
|
||||
if (context.IsTunEnabled)
|
||||
{
|
||||
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
|
||||
}
|
||||
@@ -322,61 +426,33 @@ public partial class CoreConfigSingboxService
|
||||
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
|
||||
if (dns4Sbox is null)
|
||||
{
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
singboxConfig.dns = dns4Sbox;
|
||||
|
||||
if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty())
|
||||
_coreConfig.dns = dns4Sbox;
|
||||
if (dns4Sbox.servers?.Count > 0 &&
|
||||
dns4Sbox.servers.First().address.IsNullOrEmpty())
|
||||
{
|
||||
await GenDnsDomainsCompatible(singboxConfig, item);
|
||||
GenDnsProtectCustom();
|
||||
}
|
||||
else
|
||||
{
|
||||
await GenDnsDomainsLegacyCompatible(singboxConfig, item);
|
||||
GenDnsProtectCustomLegacy();
|
||||
}
|
||||
|
||||
await GenOutboundDnsRule(node, singboxConfig);
|
||||
}
|
||||
catch (Exception 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.rules ??= [];
|
||||
|
||||
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()
|
||||
{
|
||||
server = tag,
|
||||
@@ -388,56 +464,49 @@ public partial class CoreConfigSingboxService
|
||||
clash_mode = ERuleMode.Global.ToString()
|
||||
});
|
||||
|
||||
var lstDomain = singboxConfig.outbounds
|
||||
.Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server))
|
||||
.Select(t => t.server)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
if (lstDomain != null && lstDomain.Count > 0)
|
||||
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
|
||||
|
||||
var localDnsServer = ParseDnsAddress(finalDnsAddress);
|
||||
localDnsServer.tag = tag;
|
||||
|
||||
dns4Sbox.servers.Add(localDnsServer);
|
||||
var protectDomainRule = BuildProtectDomainRule();
|
||||
if (protectDomainRule != null)
|
||||
{
|
||||
dns4Sbox.rules.Insert(0, new()
|
||||
{
|
||||
server = tag,
|
||||
domain = lstDomain
|
||||
});
|
||||
dns4Sbox.rules.Insert(0, protectDomainRule);
|
||||
}
|
||||
|
||||
singboxConfig.dns = dns4Sbox;
|
||||
return await Task.FromResult(0);
|
||||
_coreConfig.dns = dns4Sbox;
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundDnsRule(ProfileItem? node, SingboxConfig singboxConfig)
|
||||
private void GenDnsProtectCustomLegacy()
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
GenDnsProtectCustom();
|
||||
|
||||
List<string> domain = new();
|
||||
if (Utils.IsDomain(node.Address)) // normal outbound
|
||||
_coreConfig.dns?.servers?.RemoveAll(s => s.tag == Global.SingboxLocalDNSTag);
|
||||
var dnsItem = context.RawDnsItem;
|
||||
var localDnsServer = new Server4Sbox()
|
||||
{
|
||||
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;
|
||||
}
|
||||
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress)
|
||||
? Global.DomainPureIPDNSAddress.FirstOrDefault()
|
||||
: dnsItem?.DomainDNSAddress,
|
||||
tag = Global.SingboxLocalDNSTag,
|
||||
detour = Global.DirectTag,
|
||||
};
|
||||
_coreConfig.dns?.servers?.Add(localDnsServer);
|
||||
}
|
||||
|
||||
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
||||
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
|
||||
private Rule4Sbox? BuildProtectDomainRule()
|
||||
{
|
||||
if (context.ProtectDomainList.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new()
|
||||
{
|
||||
server = Global.SingboxLocalDNSTag,
|
||||
domain = domain,
|
||||
});
|
||||
|
||||
return await Task.FromResult(0);
|
||||
domain = context.ProtectDomainList.ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
private static Server4Sbox? ParseDnsAddress(string address)
|
||||
|
||||
@@ -2,15 +2,16 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> GenInbounds(SingboxConfig singboxConfig)
|
||||
private void GenInbounds()
|
||||
{
|
||||
try
|
||||
{
|
||||
var listen = "0.0.0.0";
|
||||
singboxConfig.inbounds = [];
|
||||
var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
_coreConfig.inbounds = [];
|
||||
|
||||
if (!_config.TunModeItem.EnableTun
|
||||
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box))
|
||||
if (!context.IsTunEnabled
|
||||
|| (context.IsTunEnabled && _node.Port != listenPort))
|
||||
{
|
||||
var inbound = new Inbound4Sbox()
|
||||
{
|
||||
@@ -18,23 +19,23 @@ public partial class CoreConfigSingboxService
|
||||
tag = EInboundProtocol.socks.ToString(),
|
||||
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)
|
||||
{
|
||||
var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true);
|
||||
singboxConfig.inbounds.Add(inbound2);
|
||||
var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true);
|
||||
_coreConfig.inbounds.Add(inbound2);
|
||||
}
|
||||
|
||||
if (_config.Inbound.First().AllowLANConn)
|
||||
{
|
||||
if (_config.Inbound.First().NewPort4LAN)
|
||||
{
|
||||
var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true);
|
||||
var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true);
|
||||
inbound3.listen = listen;
|
||||
singboxConfig.inbounds.Add(inbound3);
|
||||
_coreConfig.inbounds.Add(inbound3);
|
||||
|
||||
//auth
|
||||
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)
|
||||
{
|
||||
@@ -71,17 +72,16 @@ public partial class CoreConfigSingboxService
|
||||
tunInbound.address = ["172.18.0.1/30"];
|
||||
}
|
||||
|
||||
singboxConfig.inbounds.Add(tunInbound);
|
||||
_coreConfig.inbounds.Add(tunInbound);
|
||||
}
|
||||
}
|
||||
catch (Exception 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);
|
||||
inbound.tag = protocol.ToString();
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> GenLog(SingboxConfig singboxConfig)
|
||||
private void GenLog()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -11,11 +11,11 @@ public partial class CoreConfigSingboxService
|
||||
case "debug":
|
||||
case "info":
|
||||
case "error":
|
||||
singboxConfig.log.level = _config.CoreBasicItem.Loglevel;
|
||||
_coreConfig.log.level = _config.CoreBasicItem.Loglevel;
|
||||
break;
|
||||
|
||||
case "warning":
|
||||
singboxConfig.log.level = "warn";
|
||||
_coreConfig.log.level = "warn";
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -23,18 +23,17 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
if (_config.CoreBasicItem.Loglevel == Global.None)
|
||||
{
|
||||
singboxConfig.log.disabled = true;
|
||||
_coreConfig.log.disabled = true;
|
||||
}
|
||||
if (_config.CoreBasicItem.LogEnabled)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
private async Task<int> GenRouting(SingboxConfig singboxConfig)
|
||||
private void GenRouting()
|
||||
{
|
||||
try
|
||||
{
|
||||
singboxConfig.route.final = Global.ProxyTag;
|
||||
var item = _config.SimpleDNSItem;
|
||||
_coreConfig.route.final = Global.ProxyTag;
|
||||
var simpleDnsItem = context.SimpleDnsItem;
|
||||
|
||||
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);
|
||||
if (rawDNSItem != null && rawDNSItem.Enabled == true)
|
||||
var rawDNSItem = context.RawDnsItem;
|
||||
if (rawDNSItem is { Enabled: true })
|
||||
{
|
||||
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,
|
||||
strategy = directDNSStrategy
|
||||
strategy = directDnsStrategy
|
||||
};
|
||||
|
||||
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));
|
||||
if (tunRules != null)
|
||||
{
|
||||
singboxConfig.route.rules.AddRange(tunRules);
|
||||
_coreConfig.route.rules.AddRange(tunRules);
|
||||
}
|
||||
|
||||
GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe);
|
||||
singboxConfig.route.rules.Add(new()
|
||||
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
port = new() { 53 },
|
||||
port = [53],
|
||||
action = "hijack-dns",
|
||||
process_name = lstDnsExe
|
||||
});
|
||||
|
||||
singboxConfig.route.rules.Add(new()
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
process_name = lstDirectExe
|
||||
@@ -51,73 +51,109 @@ public partial class CoreConfigSingboxService
|
||||
|
||||
if (_config.Inbound.First().SniffingEnabled)
|
||||
{
|
||||
singboxConfig.route.rules.Add(new()
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
action = "sniff"
|
||||
});
|
||||
singboxConfig.route.rules.Add(new()
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
protocol = new() { "dns" },
|
||||
protocol = ["dns"],
|
||||
action = "hijack-dns"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
singboxConfig.route.rules.Add(new()
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
port = new() { 53 },
|
||||
network = new() { "udp" },
|
||||
port = [53],
|
||||
network = ["udp"],
|
||||
action = "hijack-dns"
|
||||
});
|
||||
}
|
||||
|
||||
var hostsDomains = new List<string>();
|
||||
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
if (dnsItem == null || dnsItem.Enabled == false)
|
||||
if (rawDNSItem is not { Enabled: true })
|
||||
{
|
||||
var simpleDNSItem = _config.SimpleDNSItem;
|
||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
{
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
hostsDomains.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
if (simpleDNSItem.UseSystemHosts == true)
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDnsItem.Hosts);
|
||||
hostsDomains.AddRange(userHostsMap.Select(kvp => kvp.Key));
|
||||
if (simpleDnsItem.UseSystemHosts == true)
|
||||
{
|
||||
var systemHostsMap = Utils.GetSystemHosts();
|
||||
foreach (var kvp in systemHostsMap)
|
||||
{
|
||||
hostsDomains.Add(kvp.Key);
|
||||
}
|
||||
hostsDomains.AddRange(systemHostsMap.Select(kvp => kvp.Key));
|
||||
}
|
||||
}
|
||||
if (hostsDomains.Count > 0)
|
||||
{
|
||||
singboxConfig.route.rules.Add(new()
|
||||
var hostsResolveRule = new Rule4Sbox
|
||||
{
|
||||
action = "resolve",
|
||||
domain = hostsDomains,
|
||||
});
|
||||
};
|
||||
var hostsCounter = 0;
|
||||
foreach (var host in hostsDomains)
|
||||
{
|
||||
var domainRule = new Rule4Sbox();
|
||||
if (!ParseV2Domain(host, domainRule))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (domainRule.domain_keyword?.Count > 0 && !host.Contains(':'))
|
||||
{
|
||||
domainRule.domain = domainRule.domain_keyword;
|
||||
domainRule.domain_keyword = null;
|
||||
}
|
||||
if (domainRule.domain?.Count > 0)
|
||||
{
|
||||
hostsResolveRule.domain ??= [];
|
||||
hostsResolveRule.domain.AddRange(domainRule.domain);
|
||||
hostsCounter++;
|
||||
}
|
||||
else if (domainRule.domain_keyword?.Count > 0)
|
||||
{
|
||||
hostsResolveRule.domain_keyword ??= [];
|
||||
hostsResolveRule.domain_keyword.AddRange(domainRule.domain_keyword);
|
||||
hostsCounter++;
|
||||
}
|
||||
else if (domainRule.domain_suffix?.Count > 0)
|
||||
{
|
||||
hostsResolveRule.domain_suffix ??= [];
|
||||
hostsResolveRule.domain_suffix.AddRange(domainRule.domain_suffix);
|
||||
hostsCounter++;
|
||||
}
|
||||
else if (domainRule.domain_regex?.Count > 0)
|
||||
{
|
||||
hostsResolveRule.domain_regex ??= [];
|
||||
hostsResolveRule.domain_regex.AddRange(domainRule.domain_regex);
|
||||
hostsCounter++;
|
||||
}
|
||||
else if (domainRule.geosite?.Count > 0)
|
||||
{
|
||||
hostsResolveRule.geosite ??= [];
|
||||
hostsResolveRule.geosite.AddRange(domainRule.geosite);
|
||||
hostsCounter++;
|
||||
}
|
||||
}
|
||||
if (hostsCounter > 0)
|
||||
{
|
||||
_coreConfig.route.rules.Add(hostsResolveRule);
|
||||
}
|
||||
}
|
||||
|
||||
singboxConfig.route.rules.Add(new()
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
clash_mode = ERuleMode.Direct.ToString()
|
||||
});
|
||||
singboxConfig.route.rules.Add(new()
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.ProxyTag,
|
||||
clash_mode = ERuleMode.Global.ToString()
|
||||
});
|
||||
|
||||
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
|
||||
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
|
||||
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
|
||||
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty();
|
||||
var routing = context.RoutingItem;
|
||||
if (routing.DomainStrategy4Singbox.IsNotEmpty())
|
||||
{
|
||||
domainStrategy = defaultRouting.DomainStrategy4Singbox;
|
||||
domainStrategy = routing.DomainStrategy4Singbox;
|
||||
}
|
||||
var resolveRule = new Rule4Sbox
|
||||
{
|
||||
@@ -126,10 +162,9 @@ public partial class CoreConfigSingboxService
|
||||
};
|
||||
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>();
|
||||
if (routing != null)
|
||||
{
|
||||
@@ -146,7 +181,7 @@ public partial class CoreConfigSingboxService
|
||||
continue;
|
||||
}
|
||||
|
||||
await GenRoutingUserRule(item1, singboxConfig);
|
||||
GenRoutingUserRule(item1);
|
||||
|
||||
if (item1.Ip?.Count > 0)
|
||||
{
|
||||
@@ -156,10 +191,10 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch)
|
||||
{
|
||||
singboxConfig.route.rules.Add(resolveRule);
|
||||
_coreConfig.route.rules.Add(resolveRule);
|
||||
foreach (var item2 in ipRules)
|
||||
{
|
||||
await GenRoutingUserRule(item2, singboxConfig);
|
||||
GenRoutingUserRule(item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,10 +202,9 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
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 directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -194,20 +228,22 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
lstDnsExe = new List<string>(dnsExeSet);
|
||||
lstDirectExe = new List<string>(directExeSet);
|
||||
var lstDnsExe = new List<string>(dnsExeSet);
|
||||
var lstDirectExe = new List<string>(directExeSet);
|
||||
|
||||
return (lstDnsExe, lstDirectExe);
|
||||
}
|
||||
|
||||
private async Task<int> GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig)
|
||||
private void GenRoutingUserRule(RulesItem? item)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
|
||||
var rules = singboxConfig.route.rules;
|
||||
item.OutboundTag = GenRoutingUserRuleOutbound(item.OutboundTag ?? Global.ProxyTag);
|
||||
var rules = _coreConfig.route.rules;
|
||||
|
||||
var rule = new Rule4Sbox();
|
||||
if (item.OutboundTag == "block")
|
||||
@@ -278,11 +314,49 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
if (_config.TunModeItem.EnableTun && item.Process?.Count > 0)
|
||||
if (item.Process?.Count > 0)
|
||||
{
|
||||
rule3.process_name = item.Process;
|
||||
rules.Add(rule3);
|
||||
hasDomainIp = true;
|
||||
var ruleProcName = JsonUtils.DeepCopy(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;
|
||||
}
|
||||
|
||||
if (ruleProcPath.process_path.Count > 0)
|
||||
{
|
||||
rules.Add(ruleProcPath);
|
||||
hasDomainIp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDomainIp
|
||||
@@ -295,10 +369,9 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
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:"))
|
||||
{
|
||||
@@ -329,6 +402,11 @@ public partial class CoreConfigSingboxService
|
||||
rule.domain_keyword ??= [];
|
||||
rule.domain_keyword?.Add(domain.Substring(8));
|
||||
}
|
||||
else if (domain.StartsWith("dotless:"))
|
||||
{
|
||||
rule.domain_keyword ??= [];
|
||||
rule.domain_keyword?.Add(domain.Substring(8));
|
||||
}
|
||||
else
|
||||
{
|
||||
rule.domain_keyword ??= [];
|
||||
@@ -337,7 +415,7 @@ public partial class CoreConfigSingboxService
|
||||
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:"))
|
||||
{
|
||||
@@ -370,14 +448,14 @@ public partial class CoreConfigSingboxService
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig)
|
||||
private string GenRoutingUserRuleOutbound(string outboundTag)
|
||||
{
|
||||
if (Global.OutboundTags.Contains(outboundTag))
|
||||
{
|
||||
return outboundTag;
|
||||
}
|
||||
|
||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||
var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
|
||||
|
||||
if (node == null
|
||||
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
|
||||
@@ -387,39 +465,15 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
|
||||
var tag = $"{node.IndexId}-{Global.ProxyTag}";
|
||||
if (singboxConfig.outbounds.Any(o => o.tag == tag)
|
||||
|| (singboxConfig.endpoints != null && singboxConfig.endpoints.Any(e => e.tag == tag)))
|
||||
if (_coreConfig.outbounds.Any(o => o.tag.StartsWith(tag))
|
||||
|| (_coreConfig.endpoints != null && _coreConfig.endpoints.Any(e => e.tag.StartsWith(tag))))
|
||||
{
|
||||
return tag;
|
||||
}
|
||||
|
||||
if (node.ConfigType.IsGroupType())
|
||||
{
|
||||
var ret = await GenGroupOutbound(node, singboxConfig, tag);
|
||||
if (ret == 0)
|
||||
{
|
||||
return tag;
|
||||
}
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
var proxyOutbounds = new CoreConfigSingboxService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
|
||||
FillRangeProxy(proxyOutbounds, _coreConfig, false);
|
||||
|
||||
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;
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> ConvertGeo2Ruleset(SingboxConfig singboxConfig)
|
||||
private void ConvertGeo2Ruleset()
|
||||
{
|
||||
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
|
||||
{
|
||||
@@ -16,14 +16,14 @@ public partial class CoreConfigSingboxService
|
||||
var ruleSets = new List<string>();
|
||||
|
||||
//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.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
||||
rule.geosite = null;
|
||||
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.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
||||
@@ -32,24 +32,24 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
|
||||
//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.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
||||
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.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
||||
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);
|
||||
}
|
||||
//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 ?? [])
|
||||
{
|
||||
@@ -60,7 +60,7 @@ public partial class CoreConfigSingboxService
|
||||
//load custom ruleset file
|
||||
List<Ruleset4Sbox> customRulesets = [];
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
var routing = context.RoutingItem;
|
||||
if (routing.CustomRulesetPath4Singbox.IsNotEmpty())
|
||||
{
|
||||
var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox);
|
||||
@@ -78,7 +78,7 @@ public partial class CoreConfigSingboxService
|
||||
var localSrss = Utils.GetBinPath("srss");
|
||||
|
||||
//Add ruleset srs
|
||||
singboxConfig.route.rule_set = [];
|
||||
_coreConfig.route.rule_set = [];
|
||||
foreach (var item in new HashSet<string>(ruleSets))
|
||||
{
|
||||
if (item.IsNullOrEmpty())
|
||||
@@ -113,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
|
||||
{
|
||||
private async Task<int> GenExperimental(SingboxConfig singboxConfig)
|
||||
private void GenExperimental()
|
||||
{
|
||||
//if (_config.guiItem.enableStatistics)
|
||||
{
|
||||
singboxConfig.experimental ??= new Experimental4Sbox();
|
||||
singboxConfig.experimental.clash_api = new Clash_Api4Sbox()
|
||||
_coreConfig.experimental ??= new Experimental4Sbox();
|
||||
_coreConfig.experimental.clash_api = new Clash_Api4Sbox()
|
||||
{
|
||||
external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}",
|
||||
};
|
||||
@@ -15,15 +15,13 @@ public partial class CoreConfigSingboxService
|
||||
|
||||
if (_config.CoreBasicItem.EnableCacheFile4Sbox)
|
||||
{
|
||||
singboxConfig.experimental ??= new Experimental4Sbox();
|
||||
singboxConfig.experimental.cache_file = new CacheFile4Sbox()
|
||||
_coreConfig.experimental ??= new Experimental4Sbox();
|
||||
_coreConfig.experimental.cache_file = new CacheFile4Sbox()
|
||||
{
|
||||
enabled = true,
|
||||
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;
|
||||
|
||||
public partial class CoreConfigV2rayService(Config config)
|
||||
public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
{
|
||||
private readonly Config _config = config;
|
||||
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
|
||||
|
||||
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
|
||||
public RetResult GenerateClientConfigContent()
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (node == null
|
||||
|| !node.IsValid())
|
||||
if (context.IsTunEnabled && context.TunProtectSsPort > 0 && context.ProxyRelaySsPort > 0)
|
||||
{
|
||||
return GenerateClientProxyRelayConfig();
|
||||
}
|
||||
if (_node == null
|
||||
|| !_node.IsValid())
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
@@ -46,30 +41,34 @@ public partial class CoreConfigV2rayService(Config config)
|
||||
return ret;
|
||||
}
|
||||
|
||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (v2rayConfig == null)
|
||||
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (_coreConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
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);
|
||||
var finalRule = BuildFinalRule();
|
||||
if (!string.IsNullOrEmpty(finalRule?.balancerTag))
|
||||
{
|
||||
_coreConfig.routing.rules.Add(finalRule);
|
||||
}
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
|
||||
ret.Data = ApplyFullConfigTemplate();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -80,18 +79,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;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||
@@ -102,208 +94,35 @@ public partial class CoreConfigV2rayService(Config config)
|
||||
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)
|
||||
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (_coreConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenRouting(v2rayConfig);
|
||||
await GenDns(null, v2rayConfig);
|
||||
await GenStatistic(v2rayConfig);
|
||||
var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo();
|
||||
|
||||
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;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||
var 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();
|
||||
try
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||
var 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;
|
||||
}
|
||||
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);
|
||||
v2rayConfig.inbounds.Clear();
|
||||
v2rayConfig.outbounds.Clear();
|
||||
v2rayConfig.routing.rules.Clear();
|
||||
GenLog();
|
||||
_coreConfig.inbounds.Clear();
|
||||
_coreConfig.outbounds.Clear();
|
||||
_coreConfig.routing.rules.Clear();
|
||||
|
||||
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
|
||||
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.Port <= 0)
|
||||
if (!it.ConfigType.IsComplexType() && it.Port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
if (item is null || item.IsComplex() || !item.IsValid())
|
||||
var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId);
|
||||
var item = context.AllProxiesMap.GetValueOrDefault(actIndexId);
|
||||
if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -342,27 +161,40 @@ public partial class CoreConfigV2rayService(Config config)
|
||||
protocol = EInboundProtocol.mixed.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
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(item, outbound);
|
||||
outbound.tag = Global.ProxyTag + inbound.port.ToString();
|
||||
v2rayConfig.outbounds.Add(outbound);
|
||||
var proxyOutbounds =
|
||||
new CoreConfigV2rayService(context with { Node = item }).BuildAllProxyOutbounds(tag);
|
||||
_coreConfig.outbounds.AddRange(proxyOutbounds);
|
||||
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
|
||||
RulesItem4Ray rule = new()
|
||||
{
|
||||
inboundTag = new List<string> { inbound.tag },
|
||||
outboundTag = outbound.tag,
|
||||
inboundTag = [inbound.tag],
|
||||
outboundTag = tag,
|
||||
type = "field"
|
||||
};
|
||||
v2rayConfig.routing.rules.Add(rule);
|
||||
if (isBalancer)
|
||||
{
|
||||
rule.balancerTag = tag + Global.BalancerTagSuffix;
|
||||
rule.outboundTag = null;
|
||||
}
|
||||
_coreConfig.routing.rules.Add(rule);
|
||||
}
|
||||
|
||||
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -373,21 +205,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();
|
||||
try
|
||||
{
|
||||
if (node == null
|
||||
|| !node.IsValid())
|
||||
if (_node == null
|
||||
|| !_node.IsValid())
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -398,20 +230,20 @@ public partial class CoreConfigV2rayService(Config config)
|
||||
return ret;
|
||||
}
|
||||
|
||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (v2rayConfig == null)
|
||||
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (_coreConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenLog(v2rayConfig);
|
||||
await GenOutbound(node, v2rayConfig.outbounds.First());
|
||||
await GenMoreOutbounds(node, v2rayConfig);
|
||||
GenLog();
|
||||
GenOutbounds();
|
||||
|
||||
v2rayConfig.routing.rules.Clear();
|
||||
v2rayConfig.inbounds.Clear();
|
||||
v2rayConfig.inbounds.Add(new()
|
||||
_coreConfig.routing.domainStrategy = Global.AsIs;
|
||||
_coreConfig.routing.rules.Clear();
|
||||
_coreConfig.inbounds.Clear();
|
||||
_coreConfig.inbounds.Add(new()
|
||||
{
|
||||
tag = $"{EInboundProtocol.socks}{port}",
|
||||
listen = Global.Loopback,
|
||||
@@ -419,9 +251,120 @@ public partial class CoreConfigV2rayService(Config config)
|
||||
protocol = EInboundProtocol.mixed.ToString(),
|
||||
});
|
||||
|
||||
_coreConfig.routing.rules.Add(BuildFinalRule());
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
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();
|
||||
GenStatistic();
|
||||
|
||||
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(o => o.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
|
||||
{
|
||||
outbound.streamSettings ??= new();
|
||||
outbound.streamSettings.sockopt ??= new();
|
||||
outbound.streamSettings.sockopt.dialerProxy = "tun-protect-ss";
|
||||
}
|
||||
// ech protected
|
||||
foreach (var outbound in _coreConfig.outbounds
|
||||
.Where(outbound => outbound.streamSettings?.tlsSettings?.echConfigList?.IsNullOrEmpty() == false))
|
||||
{
|
||||
outbound.streamSettings!.tlsSettings!.echSockopt ??= new();
|
||||
outbound.streamSettings.tlsSettings.echSockopt.dialerProxy = "tun-protect-ss";
|
||||
}
|
||||
_coreConfig.outbounds.Add(new CoreConfigV2rayService(context with
|
||||
{
|
||||
Node = protectNode,
|
||||
}).BuildProxyOutbound("tun-protect-ss"));
|
||||
|
||||
_coreConfig.routing.rules ??= [];
|
||||
var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 };
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
inboundTag = ["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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -2,17 +2,17 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
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
|
||||
var subjectSelectors = new List<string>();
|
||||
subjectSelectors.AddRange(v2rayConfig.burstObservatory?.subjectSelector ?? []);
|
||||
subjectSelectors.AddRange(v2rayConfig.observatory?.subjectSelector ?? []);
|
||||
subjectSelectors.AddRange(_coreConfig.burstObservatory?.subjectSelector ?? []);
|
||||
subjectSelectors.AddRange(_coreConfig.observatory?.subjectSelector ?? []);
|
||||
|
||||
// Case 1: exact match already exists -> nothing to do
|
||||
if (subjectSelectors.Any(baseTagName.StartsWith))
|
||||
{
|
||||
return await Task.FromResult(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Case 2: prefix match exists -> reuse it and move to the first position
|
||||
@@ -21,28 +21,28 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
baseTagName = matched;
|
||||
|
||||
if (v2rayConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true)
|
||||
if (_coreConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true)
|
||||
{
|
||||
v2rayConfig.burstObservatory.subjectSelector.Remove(baseTagName);
|
||||
v2rayConfig.burstObservatory.subjectSelector.Insert(0, baseTagName);
|
||||
_coreConfig.burstObservatory.subjectSelector.Remove(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);
|
||||
v2rayConfig.observatory.subjectSelector.Insert(0, baseTagName);
|
||||
_coreConfig.observatory.subjectSelector.Remove(baseTagName);
|
||||
_coreConfig.observatory.subjectSelector.Insert(0, baseTagName);
|
||||
}
|
||||
|
||||
return await Task.FromResult(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Case 3: need to create or insert based on multipleLoad type
|
||||
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
|
||||
v2rayConfig.burstObservatory = new BurstObservatory4Ray
|
||||
_coreConfig.burstObservatory = new BurstObservatory4Ray
|
||||
{
|
||||
subjectSelector = [baseTagName],
|
||||
pingConfig = new()
|
||||
@@ -56,16 +56,16 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
else
|
||||
{
|
||||
v2rayConfig.burstObservatory.subjectSelector ??= new();
|
||||
v2rayConfig.burstObservatory.subjectSelector.Add(baseTagName);
|
||||
_coreConfig.burstObservatory.subjectSelector ??= new();
|
||||
_coreConfig.burstObservatory.subjectSelector.Add(baseTagName);
|
||||
}
|
||||
}
|
||||
else if (multipleLoad is EMultipleLoad.LeastPing)
|
||||
{
|
||||
if (v2rayConfig.observatory is null)
|
||||
if (_coreConfig.observatory is null)
|
||||
{
|
||||
// Create new observatory with default probe config
|
||||
v2rayConfig.observatory = new Observatory4Ray
|
||||
_coreConfig.observatory = new Observatory4Ray
|
||||
{
|
||||
subjectSelector = [baseTagName],
|
||||
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
|
||||
@@ -75,15 +75,13 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
else
|
||||
{
|
||||
v2rayConfig.observatory.subjectSelector ??= new();
|
||||
v2rayConfig.observatory.subjectSelector.Add(baseTagName);
|
||||
_coreConfig.observatory.subjectSelector ??= new();
|
||||
_coreConfig.observatory.subjectSelector.Add(baseTagName);
|
||||
}
|
||||
}
|
||||
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<string> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
|
||||
private void GenBalancer(EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
|
||||
{
|
||||
var strategyType = multipleLoad switch
|
||||
{
|
||||
@@ -107,8 +105,7 @@ public partial class CoreConfigV2rayService
|
||||
},
|
||||
tag = balancerTag,
|
||||
};
|
||||
v2rayConfig.routing.balancers ??= new();
|
||||
v2rayConfig.routing.balancers.Add(balancer);
|
||||
return await Task.FromResult(balancerTag);
|
||||
_coreConfig.routing.balancers ??= new();
|
||||
_coreConfig.routing.balancers.Add(balancer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,35 +2,39 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
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())
|
||||
{
|
||||
return JsonUtils.Serialize(v2rayConfig);
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
}
|
||||
|
||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config);
|
||||
if (fullConfigTemplateNode == null)
|
||||
{
|
||||
return JsonUtils.Serialize(v2rayConfig);
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
}
|
||||
|
||||
// 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
|
||||
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
|
||||
if (rulesNode != null)
|
||||
if (balancer != null)
|
||||
{
|
||||
foreach (var rule in rulesNode.AsArray())
|
||||
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
|
||||
if (rulesNode != null)
|
||||
{
|
||||
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
|
||||
foreach (var rule in rulesNode.AsArray())
|
||||
{
|
||||
rule.AsObject().Remove("outboundTag");
|
||||
rule["balancerTag"] = balancer.tag;
|
||||
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
|
||||
{
|
||||
rule.AsObject().Remove("outboundTag");
|
||||
rule["balancerTag"] = balancer.tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +48,7 @@ public partial class CoreConfigV2rayService
|
||||
// Handle balancers - append instead of override
|
||||
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)
|
||||
{
|
||||
@@ -54,33 +58,33 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
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)
|
||||
{
|
||||
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.observatory));
|
||||
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.observatory));
|
||||
}
|
||||
else
|
||||
{
|
||||
var subjectSelector = v2rayConfig.observatory.subjectSelector;
|
||||
var subjectSelector = _coreConfig.observatory.subjectSelector;
|
||||
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||
}
|
||||
}
|
||||
|
||||
if (v2rayConfig.burstObservatory != null)
|
||||
if (_coreConfig.burstObservatory != null)
|
||||
{
|
||||
if (fullConfigTemplateNode["burstObservatory"] == null)
|
||||
{
|
||||
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.burstObservatory));
|
||||
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.burstObservatory));
|
||||
}
|
||||
else
|
||||
{
|
||||
var subjectSelector = v2rayConfig.burstObservatory.subjectSelector;
|
||||
var subjectSelector = _coreConfig.burstObservatory.subjectSelector;
|
||||
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||
}
|
||||
@@ -88,7 +92,7 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
var customOutboundsNode = new JsonArray();
|
||||
|
||||
foreach (var outbound in v2rayConfig.outbounds)
|
||||
foreach (var outbound in _coreConfig.outbounds)
|
||||
{
|
||||
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
|
||||
{
|
||||
@@ -97,8 +101,8 @@ public partial class CoreConfigV2rayService
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if ((!fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
||||
&& ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true) == true))
|
||||
else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()
|
||||
&& (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true))
|
||||
{
|
||||
var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address
|
||||
?? outbound.settings?.vnext?.FirstOrDefault()?.address
|
||||
@@ -123,6 +127,6 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||
|
||||
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
|
||||
return JsonUtils.Serialize(fullConfigTemplateNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,112 +2,118 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenDns(ProfileItem? node, V2rayConfig v2rayConfig)
|
||||
private void GenDns()
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
if (item != null && item.Enabled == true)
|
||||
var item = context.RawDnsItem;
|
||||
if (item is { Enabled: true })
|
||||
{
|
||||
var result = await GenDnsCompatible(node, v2rayConfig);
|
||||
GenDnsCustom();
|
||||
|
||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
||||
if (_coreConfig.routing.domainStrategy != Global.IPIfNonMatch)
|
||||
{
|
||||
// DNS routing
|
||||
v2rayConfig.dns.tag = Global.DnsTag;
|
||||
v2rayConfig.routing.rules.Add(new RulesItem4Ray
|
||||
{
|
||||
type = "field",
|
||||
inboundTag = new List<string> { Global.DnsTag },
|
||||
outboundTag = Global.ProxyTag,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
var simpleDNSItem = _config.SimpleDNSItem;
|
||||
var domainStrategy4Freedom = simpleDNSItem?.RayStrategy4Freedom;
|
||||
|
||||
//Outbound Freedom domainStrategy
|
||||
if (domainStrategy4Freedom.IsNotEmpty())
|
||||
{
|
||||
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new()
|
||||
{
|
||||
domainStrategy = domainStrategy4Freedom,
|
||||
userLevel = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await GenDnsServers(node, v2rayConfig, simpleDNSItem);
|
||||
await GenDnsHosts(v2rayConfig, simpleDNSItem);
|
||||
|
||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
||||
{
|
||||
// DNS routing
|
||||
v2rayConfig.dns.tag = Global.DnsTag;
|
||||
v2rayConfig.routing.rules.Add(new RulesItem4Ray
|
||||
var dnsObj = JsonUtils.SerializeToNode(_coreConfig.dns);
|
||||
if (dnsObj == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dnsObj["tag"] = Global.DnsTag;
|
||||
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(dnsObj));
|
||||
_coreConfig.routing.rules.Add(new RulesItem4Ray
|
||||
{
|
||||
type = "field",
|
||||
inboundTag = new List<string> { Global.DnsTag },
|
||||
outboundTag = Global.ProxyTag,
|
||||
});
|
||||
return;
|
||||
}
|
||||
var simpleDnsItem = context.SimpleDnsItem;
|
||||
var dnsItem = _coreConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray();
|
||||
|
||||
var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs;
|
||||
//Outbound Freedom domainStrategy
|
||||
if (strategy4Freedom.IsNotEmpty() && strategy4Freedom != Global.AsIs)
|
||||
{
|
||||
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new()
|
||||
{
|
||||
domainStrategy = strategy4Freedom,
|
||||
userLevel = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var strategy4Proxy = simpleDnsItem?.Strategy4Proxy ?? Global.AsIs;
|
||||
//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);
|
||||
}
|
||||
|
||||
FillDnsServers(dnsItem);
|
||||
FillDnsHosts(dnsItem);
|
||||
|
||||
dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null;
|
||||
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
|
||||
|
||||
// DNS routing
|
||||
var directDnsTags = dnsItem.servers
|
||||
.Select(server =>
|
||||
{
|
||||
var tagNode = (server as JsonObject)?["tag"];
|
||||
return tagNode is JsonValue value && value.TryGetValue<string>(out var tag) ? tag : null;
|
||||
})
|
||||
.Where(tag => tag is not null && tag.StartsWith(Global.DirectDnsTag, StringComparison.Ordinal))
|
||||
.Select(tag => tag!)
|
||||
.ToList();
|
||||
if (directDnsTags.Count > 0)
|
||||
{
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
type = "field",
|
||||
inboundTag = directDnsTags,
|
||||
outboundTag = Global.DirectTag,
|
||||
});
|
||||
}
|
||||
|
||||
var finalRule = BuildFinalRule();
|
||||
dnsItem.tag = Global.DnsTag;
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
type = "field",
|
||||
inboundTag = [Global.DnsTag],
|
||||
outboundTag = finalRule.outboundTag,
|
||||
balancerTag = finalRule.balancerTag,
|
||||
});
|
||||
|
||||
_coreConfig.dns = dnsItem;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
|
||||
private void FillDnsServers(Dns4Ray dnsItem)
|
||||
{
|
||||
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
|
||||
{
|
||||
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
|
||||
.Select(addr => addr.Trim())
|
||||
.Where(addr => !string.IsNullOrEmpty(addr))
|
||||
.Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr)
|
||||
.Distinct()
|
||||
.ToList() ?? new List<string> { defaultAddress };
|
||||
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
|
||||
}
|
||||
var simpleDNSItem = context.SimpleDnsItem;
|
||||
|
||||
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
|
||||
{
|
||||
address = domainFinal,
|
||||
port = portFinal,
|
||||
skipFallback = true,
|
||||
domains = domains.Count > 0 ? domains : null,
|
||||
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
|
||||
};
|
||||
return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
}
|
||||
|
||||
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.FirstOrDefault());
|
||||
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.FirstOrDefault());
|
||||
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.First());
|
||||
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.First());
|
||||
|
||||
var directDomainList = new List<string>();
|
||||
var directGeositeList = new List<string>();
|
||||
@@ -117,7 +123,7 @@ public partial class CoreConfigV2rayService
|
||||
var expectedIPs = new List<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>();
|
||||
|
||||
foreach (var dns in directDNSAddress)
|
||||
@@ -169,128 +175,171 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
var routing = context.RoutingItem;
|
||||
List<RulesItem>? rules = null;
|
||||
if (routing != null)
|
||||
rules = JsonUtils.Deserialize<List<RulesItem>>(routing?.RuleSet) ?? [];
|
||||
foreach (var item in rules)
|
||||
{
|
||||
rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
|
||||
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)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.RuleType == ERuleType.Routing)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var domain in item.Domain)
|
||||
{
|
||||
if (domain.StartsWith('#'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.RuleType == ERuleType.Routing)
|
||||
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
|
||||
|
||||
if (item.OutboundTag == Global.DirectTag)
|
||||
{
|
||||
continue;
|
||||
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
|
||||
{
|
||||
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
|
||||
}
|
||||
else
|
||||
{
|
||||
directDomainList.Add(normalizedDomain);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var domain in item.Domain)
|
||||
else if (item.OutboundTag != Global.BlockTag)
|
||||
{
|
||||
if (domain.StartsWith('#'))
|
||||
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
|
||||
{
|
||||
continue;
|
||||
proxyGeositeList.Add(normalizedDomain);
|
||||
}
|
||||
|
||||
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
|
||||
|
||||
if (item.OutboundTag == Global.DirectTag)
|
||||
else
|
||||
{
|
||||
if (normalizedDomain.StartsWith("geosite:"))
|
||||
{
|
||||
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
|
||||
}
|
||||
else
|
||||
{
|
||||
directDomainList.Add(normalizedDomain);
|
||||
}
|
||||
}
|
||||
else if (item.OutboundTag != Global.BlockTag)
|
||||
{
|
||||
if (normalizedDomain.StartsWith("geosite:"))
|
||||
{
|
||||
proxyGeositeList.Add(normalizedDomain);
|
||||
}
|
||||
else
|
||||
{
|
||||
proxyDomainList.Add(normalizedDomain);
|
||||
}
|
||||
proxyDomainList.Add(normalizedDomain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Utils.IsDomain(node?.Address))
|
||||
if (context.ProtectDomainList.Count > 0)
|
||||
{
|
||||
directDomainList.Add(node.Address);
|
||||
directDomainList.AddRange(context.ProtectDomainList);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dnsItem.servers ??= [];
|
||||
|
||||
v2rayConfig.dns ??= new Dns4Ray();
|
||||
v2rayConfig.dns.servers ??= new List<object>();
|
||||
|
||||
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
|
||||
{
|
||||
if (domains.Count > 0)
|
||||
{
|
||||
foreach (var dnsAddress in dnsAddresses)
|
||||
{
|
||||
v2rayConfig.dns.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
|
||||
}
|
||||
}
|
||||
}
|
||||
var directDnsTagIndex = 1;
|
||||
|
||||
AddDnsServers(remoteDNSAddress, proxyDomainList);
|
||||
AddDnsServers(directDNSAddress, directDomainList);
|
||||
AddDnsServers(directDNSAddress, directDomainList, true);
|
||||
AddDnsServers(remoteDNSAddress, proxyGeositeList);
|
||||
AddDnsServers(directDNSAddress, directGeositeList);
|
||||
AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs);
|
||||
AddDnsServers(directDNSAddress, directGeositeList, true);
|
||||
AddDnsServers(directDNSAddress, expectedDomainList, true, expectedIPs);
|
||||
if (dnsServerDomains.Count > 0)
|
||||
{
|
||||
AddDnsServers(bootstrapDNSAddress, dnsServerDomains);
|
||||
}
|
||||
|
||||
var useDirectDns = rules?.LastOrDefault() is { } lastRule
|
||||
&& lastRule.OutboundTag == Global.DirectTag
|
||||
&& (lastRule.Port == "0-65535"
|
||||
|| lastRule.Network == "tcp,udp"
|
||||
|| lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
||||
var useDirectDns = false;
|
||||
|
||||
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
|
||||
v2rayConfig.dns.servers.AddRange(defaultDnsServers);
|
||||
if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag)
|
||||
{
|
||||
var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0;
|
||||
var noProcess = lastRule.Process == null || lastRule.Process.Count == 0;
|
||||
var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0");
|
||||
var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535";
|
||||
var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp";
|
||||
useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork;
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (!useDirectDns)
|
||||
{
|
||||
dnsItem.servers.AddRange(remoteDNSAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var dns in directDNSAddress)
|
||||
{
|
||||
var dnsServer = CreateDnsServer(dns, []);
|
||||
dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}";
|
||||
dnsServer.skipFallback = false;
|
||||
dnsItem.servers.Add(JsonUtils.SerializeToNode(dnsServer,
|
||||
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }));
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
|
||||
{
|
||||
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
|
||||
.Select(addr => addr.Trim())
|
||||
.Where(addr => !string.IsNullOrEmpty(addr))
|
||||
.Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr)
|
||||
.Distinct()
|
||||
.ToList() ?? [defaultAddress];
|
||||
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
|
||||
}
|
||||
|
||||
static DnsServer4Ray 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
|
||||
{
|
||||
address = domainFinal,
|
||||
port = portFinal,
|
||||
skipFallback = true,
|
||||
domains = domains.Count > 0 ? domains : null,
|
||||
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
|
||||
};
|
||||
return dnsServer;
|
||||
}
|
||||
|
||||
void AddDnsServers(List<string> dnsAddresses, List<string> domains, bool isDirectDns = false, List<string>? expectedIPs = null)
|
||||
{
|
||||
if (domains.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var dnsAddress in dnsAddresses)
|
||||
{
|
||||
var dnsServer = CreateDnsServer(dnsAddress, domains, expectedIPs);
|
||||
if (isDirectDns)
|
||||
{
|
||||
dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}";
|
||||
}
|
||||
var dnsServerNode = JsonUtils.SerializeToNode(dnsServer,
|
||||
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
|
||||
dnsItem.servers.Add(dnsServerNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
return await Task.FromResult(0);
|
||||
return;
|
||||
}
|
||||
v2rayConfig.dns ??= new Dns4Ray();
|
||||
v2rayConfig.dns.hosts ??= new Dictionary<string, object>();
|
||||
dnsItem.hosts ??= new Dictionary<string, object>();
|
||||
if (simpleDNSItem.AddCommonHosts == true)
|
||||
{
|
||||
v2rayConfig.dns.hosts = Global.PredefinedHosts.ToDictionary(
|
||||
dnsItem.hosts = Global.PredefinedHosts.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => (object)kvp.Value
|
||||
);
|
||||
@@ -299,7 +348,7 @@ public partial class CoreConfigV2rayService
|
||||
if (simpleDNSItem.UseSystemHosts == true)
|
||||
{
|
||||
var systemHosts = Utils.GetSystemHosts();
|
||||
var normalHost = v2rayConfig?.dns?.hosts;
|
||||
var normalHost = dnsItem.hosts;
|
||||
|
||||
if (normalHost != null && systemHosts?.Count > 0)
|
||||
{
|
||||
@@ -310,23 +359,17 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
|
||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts))
|
||||
{
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
v2rayConfig.dns.hosts[kvp.Key] = kvp.Value;
|
||||
}
|
||||
dnsItem.hosts[kvp.Key] = kvp.Value;
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig)
|
||||
private void GenDnsCustom()
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
var item = context.RawDnsItem;
|
||||
var normalDNS = item?.NormalDNS;
|
||||
var domainStrategy4Freedom = item?.DomainStrategy4Freedom;
|
||||
if (normalDNS.IsNullOrEmpty())
|
||||
@@ -337,7 +380,7 @@ public partial class CoreConfigV2rayService
|
||||
//Outbound Freedom domainStrategy
|
||||
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)
|
||||
{
|
||||
outbound.settings = new();
|
||||
@@ -392,63 +435,37 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
|
||||
await GenDnsDomainsCompatible(node, obj, item);
|
||||
FillDnsDomainsCustom(obj);
|
||||
|
||||
v2rayConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
|
||||
_coreConfig.dns = obj;
|
||||
}
|
||||
catch (Exception 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"];
|
||||
if (servers != null)
|
||||
if (servers == null)
|
||||
{
|
||||
var domainList = new List<string>();
|
||||
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 nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||
if (nextNode is not null
|
||||
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)
|
||||
&& Utils.IsDomain(nextNode.Address))
|
||||
{
|
||||
domainList.Add(nextNode.Address);
|
||||
}
|
||||
}
|
||||
if (domainList.Count > 0)
|
||||
{
|
||||
var dnsServer = new DnsServer4Ray()
|
||||
{
|
||||
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
|
||||
skipFallback = true,
|
||||
domains = domainList
|
||||
};
|
||||
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
||||
}
|
||||
return;
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
|
||||
var domainList = context.ProtectDomainList;
|
||||
if (domainList.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dnsItem = context.RawDnsItem;
|
||||
var dnsServer = new DnsServer4Ray()
|
||||
{
|
||||
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
|
||||
skipFallback = true,
|
||||
domains = domainList.ToList(),
|
||||
};
|
||||
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,35 +2,35 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenInbounds(V2rayConfig v2rayConfig)
|
||||
private void GenInbounds()
|
||||
{
|
||||
try
|
||||
{
|
||||
var listen = "0.0.0.0";
|
||||
v2rayConfig.inbounds = [];
|
||||
_coreConfig.inbounds = [];
|
||||
|
||||
var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
|
||||
v2rayConfig.inbounds.Add(inbound);
|
||||
var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
|
||||
_coreConfig.inbounds.Add(inbound);
|
||||
|
||||
if (_config.Inbound.First().SecondLocalPortEnabled)
|
||||
{
|
||||
var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
|
||||
v2rayConfig.inbounds.Add(inbound2);
|
||||
var inbound2 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
|
||||
_coreConfig.inbounds.Add(inbound2);
|
||||
}
|
||||
|
||||
if (_config.Inbound.First().AllowLANConn)
|
||||
{
|
||||
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;
|
||||
v2rayConfig.inbounds.Add(inbound3);
|
||||
_coreConfig.inbounds.Add(inbound3);
|
||||
|
||||
//auth
|
||||
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
||||
{
|
||||
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
|
||||
@@ -43,10 +43,9 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
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)
|
||||
{
|
||||
var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
|
||||
if (result.IsNullOrEmpty())
|
||||
|
||||
@@ -2,28 +2,27 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenLog(V2rayConfig v2rayConfig)
|
||||
private void GenLog()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_config.CoreBasicItem.LogEnabled)
|
||||
{
|
||||
var dtNow = DateTime.Now;
|
||||
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||
v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
|
||||
v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
|
||||
_coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||
_coreConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
|
||||
_coreConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||
v2rayConfig.log.access = null;
|
||||
v2rayConfig.log.error = null;
|
||||
_coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||
_coreConfig.log.access = null;
|
||||
_coreConfig.log.error = null;
|
||||
}
|
||||
}
|
||||
catch (Exception 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
|
||||
{
|
||||
private async Task<int> GenRouting(V2rayConfig v2rayConfig)
|
||||
private void GenRouting()
|
||||
{
|
||||
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.DomainStrategy.IsNotEmpty())
|
||||
{
|
||||
v2rayConfig.routing.domainStrategy = routing.DomainStrategy;
|
||||
_coreConfig.routing.domainStrategy = routing.DomainStrategy;
|
||||
}
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||
foreach (var item in rules)
|
||||
@@ -31,7 +31,18 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig)
|
||||
private void GenRoutingUserRule(RulesItem4Ray? userRule)
|
||||
{
|
||||
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;
|
||||
if (rule.domain?.Count > 0)
|
||||
if (userRule.domain?.Count > 0)
|
||||
{
|
||||
var it = JsonUtils.DeepCopy(rule);
|
||||
var it = JsonUtils.DeepCopy(userRule);
|
||||
it.ip = null;
|
||||
it.process = null;
|
||||
it.type = "field";
|
||||
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[k] = it.domain[k].Replace(Global.RoutingRuleComma, ",");
|
||||
}
|
||||
v2rayConfig.routing.rules.Add(it);
|
||||
_coreConfig.routing.rules.Add(it);
|
||||
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.process = null;
|
||||
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;
|
||||
}
|
||||
if (!hasDomainIp)
|
||||
{
|
||||
if (rule.port.IsNotEmpty()
|
||||
|| rule.protocol?.Count > 0
|
||||
|| rule.inboundTag?.Count > 0
|
||||
|| rule.network != null
|
||||
if (userRule.port.IsNotEmpty()
|
||||
|| userRule.protocol?.Count > 0
|
||||
|| userRule.inboundTag?.Count > 0
|
||||
|| userRule.network != null
|
||||
)
|
||||
{
|
||||
var it = JsonUtils.DeepCopy(rule);
|
||||
var it = JsonUtils.DeepCopy(userRule);
|
||||
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);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig)
|
||||
private string GenRoutingUserRuleOutbound(string outboundTag)
|
||||
{
|
||||
if (Global.OutboundTags.Contains(outboundTag))
|
||||
{
|
||||
return outboundTag;
|
||||
}
|
||||
|
||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||
var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
|
||||
|
||||
if (node == null
|
||||
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
|
||||
@@ -141,27 +165,44 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (node.ConfigType.IsGroupType())
|
||||
var proxyOutbounds = new CoreConfigV2rayService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
|
||||
_coreConfig.outbounds.AddRange(proxyOutbounds);
|
||||
if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1)
|
||||
{
|
||||
var ret = await GenGroupOutbound(node, v2rayConfig, tag);
|
||||
if (ret == 0)
|
||||
{
|
||||
return tag;
|
||||
}
|
||||
return Global.ProxyTag;
|
||||
var multipleLoad = node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
|
||||
GenObservatory(multipleLoad, tag);
|
||||
GenBalancer(multipleLoad, tag);
|
||||
}
|
||||
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(node, outbound);
|
||||
outbound.tag = tag;
|
||||
v2rayConfig.outbounds.Add(outbound);
|
||||
return tag;
|
||||
}
|
||||
|
||||
return outbound.tag;
|
||||
private RulesItem4Ray BuildFinalRule()
|
||||
{
|
||||
var finalRule = new RulesItem4Ray()
|
||||
{
|
||||
type = "field",
|
||||
network = "tcp,udp",
|
||||
outboundTag = Global.ProxyTag,
|
||||
};
|
||||
var balancer =
|
||||
_coreConfig?.routing?.balancers?.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null);
|
||||
var domainStrategy = _coreConfig.routing?.domainStrategy ?? Global.AsIs;
|
||||
if (balancer is not null)
|
||||
{
|
||||
finalRule.outboundTag = null;
|
||||
finalRule.balancerTag = balancer.tag;
|
||||
}
|
||||
if (domainStrategy == Global.IPIfNonMatch)
|
||||
{
|
||||
finalRule.network = null;
|
||||
finalRule.ip = ["0.0.0.0/0", "::/0"];
|
||||
}
|
||||
return finalRule;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenStatistic(V2rayConfig v2rayConfig)
|
||||
private void GenStatistic()
|
||||
{
|
||||
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
||||
{
|
||||
@@ -11,17 +11,17 @@ public partial class CoreConfigV2rayService
|
||||
Policy4Ray policyObj = new();
|
||||
SystemPolicy4Ray policySystemSetting = new();
|
||||
|
||||
v2rayConfig.stats = new Stats4Ray();
|
||||
_coreConfig.stats = new Stats4Ray();
|
||||
|
||||
apiObj.tag = tag;
|
||||
v2rayConfig.metrics = apiObj;
|
||||
_coreConfig.metrics = apiObj;
|
||||
|
||||
policySystemSetting.statsOutboundDownlink = true;
|
||||
policySystemSetting.statsOutboundUplink = true;
|
||||
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();
|
||||
Inboundsettings4Ray apiInboundSettings = new();
|
||||
@@ -31,10 +31,10 @@ public partial class CoreConfigV2rayService
|
||||
apiInbound.protocol = Global.InboundAPIProtocol;
|
||||
apiInboundSettings.address = Global.Loopback;
|
||||
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()
|
||||
{
|
||||
@@ -43,9 +43,8 @@ public partial class CoreConfigV2rayService
|
||||
type = "field"
|
||||
};
|
||||
|
||||
v2rayConfig.routing.rules.Add(apiRoutingRule);
|
||||
_coreConfig.routing.rules.Add(apiRoutingRule);
|
||||
}
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,26 +61,36 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
|
||||
private async Task<List<ServerTestItem>> GetClearItem(ESpeedActionType actionType, List<ProfileItem> selecteds)
|
||||
{
|
||||
var lstSelected = new List<ServerTestItem>();
|
||||
foreach (var it in selecteds)
|
||||
var lstSelected = new List<ServerTestItem>(selecteds.Count);
|
||||
var ids = selecteds.Where(it => !it.IndexId.IsNullOrEmpty()
|
||||
&& it.ConfigType != EConfigType.Custom
|
||||
&& (it.ConfigType.IsComplexType() || it.Port > 0))
|
||||
.Select(it => it.IndexId)
|
||||
.ToList();
|
||||
var profileMap = await AppManager.Instance.GetProfileItemsByIndexIdsAsMap(ids);
|
||||
for (var i = 0; i < selecteds.Count; i++)
|
||||
{
|
||||
if (it.ConfigType.IsComplexType())
|
||||
var it = selecteds[i];
|
||||
if (it.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (it.Port <= 0)
|
||||
if (!it.ConfigType.IsComplexType() && it.Port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var profile = profileMap.GetValueOrDefault(it.IndexId, it);
|
||||
lstSelected.Add(new ServerTestItem()
|
||||
{
|
||||
IndexId = it.IndexId,
|
||||
Address = it.Address,
|
||||
Port = it.Port,
|
||||
ConfigType = it.ConfigType,
|
||||
QueueNum = selecteds.IndexOf(it)
|
||||
QueueNum = i,
|
||||
Profile = profile,
|
||||
CoreType = AppManager.Instance.GetCoreType(profile, it.ConfigType),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -198,6 +208,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
{
|
||||
if (!it.AllowTest)
|
||||
{
|
||||
await UpdateFunc(it.IndexId, ResUI.SpeedtestingSkip);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -323,31 +334,28 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
{
|
||||
var responseTime = -1;
|
||||
|
||||
if (!IPAddress.TryParse(url, out var ipAddress))
|
||||
{
|
||||
var ipHostInfo = await Dns.GetHostEntryAsync(url);
|
||||
ipAddress = ipHostInfo.AddressList.First();
|
||||
}
|
||||
|
||||
IPEndPoint endPoint = new(ipAddress, port);
|
||||
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
if (!IPAddress.TryParse(url, out var ipAddress))
|
||||
{
|
||||
var ipHostInfo = await Dns.GetHostEntryAsync(url);
|
||||
ipAddress = ipHostInfo.AddressList.First();
|
||||
}
|
||||
|
||||
IPEndPoint endPoint = new(ipAddress, port);
|
||||
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
var result = clientSocket.BeginConnect(endPoint, null, null);
|
||||
if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
throw new TimeoutException("connect timeout (5s): " + url);
|
||||
}
|
||||
timer.Stop();
|
||||
responseTime = (int)timer.Elapsed.TotalMilliseconds;
|
||||
|
||||
clientSocket.EndConnect(result);
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
await clientSocket.ConnectAsync(endPoint, cts.Token).ConfigureAwait(false);
|
||||
responseTime = (int)timer.ElapsedMilliseconds;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
return responseTime;
|
||||
}
|
||||
@@ -355,8 +363,8 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
|
||||
{
|
||||
List<List<ServerTestItem>> lstTest = new();
|
||||
var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
|
||||
var lst2 = lstSelected.Where(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)).ToList();
|
||||
var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList();
|
||||
var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList();
|
||||
|
||||
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
|
||||
{
|
||||
|
||||
@@ -61,7 +61,7 @@ public class StatisticsSingboxService
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
if (!_config.IsRunningCore(ECoreType.sing_box))
|
||||
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class StatisticsXrayService
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
if (_config.RunningCoreType != ECoreType.Xray)
|
||||
if (AppManager.Instance.RunningCoreType != ECoreType.Xray)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -291,13 +291,6 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
||||
return url;
|
||||
}
|
||||
|
||||
//Check for standalone windows .Net version
|
||||
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "wpfgfx_cor3.dll"))
|
||||
&& File.Exists(Path.Combine(Utils.GetBaseDirectory(), "D3DCompiler_47_cor3.dll")))
|
||||
{
|
||||
return url?.Replace(".zip", "-SelfContained.zip");
|
||||
}
|
||||
|
||||
//Check for avalonia desktop windows version
|
||||
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll")))
|
||||
{
|
||||
@@ -370,44 +363,36 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
||||
var geoipFiles = new List<string>();
|
||||
var geoSiteFiles = new List<string>();
|
||||
|
||||
//Collect used files list
|
||||
// Collect from routing rules
|
||||
var routingItems = await AppManager.Instance.RoutingItems();
|
||||
foreach (var routing in routingItems)
|
||||
{
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||
foreach (var item in rules ?? [])
|
||||
{
|
||||
foreach (var ip in item.Ip ?? [])
|
||||
{
|
||||
var prefix = "geoip:";
|
||||
if (ip.StartsWith(prefix))
|
||||
{
|
||||
geoipFiles.Add(ip.Substring(prefix.Length));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var domain in item.Domain ?? [])
|
||||
{
|
||||
var prefix = "geosite:";
|
||||
if (domain.StartsWith(prefix))
|
||||
{
|
||||
geoSiteFiles.Add(domain.Substring(prefix.Length));
|
||||
}
|
||||
}
|
||||
AddPrefixedItems(item.Ip, "geoip:", geoipFiles);
|
||||
AddPrefixedItems(item.Domain, "geosite:", geoSiteFiles);
|
||||
}
|
||||
}
|
||||
|
||||
//append dns items TODO
|
||||
geoSiteFiles.Add("google");
|
||||
geoSiteFiles.Add("cn");
|
||||
geoSiteFiles.Add("geolocation-cn");
|
||||
geoSiteFiles.Add("category-ads-all");
|
||||
// Collect from DNS configuration
|
||||
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
if (dnsItem != null)
|
||||
{
|
||||
ExtractDnsRuleSets(dnsItem.NormalDNS, geoipFiles, geoSiteFiles);
|
||||
ExtractDnsRuleSets(dnsItem.TunDNS, geoipFiles, geoSiteFiles);
|
||||
}
|
||||
|
||||
// Append default items
|
||||
geoSiteFiles.AddRange(["google", "cn", "geolocation-cn", "category-ads-all"]);
|
||||
|
||||
// Download files
|
||||
var path = Utils.GetBinPath("srss");
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
foreach (var item in geoipFiles.Distinct())
|
||||
{
|
||||
await UpdateSrsFile("geoip", item);
|
||||
@@ -419,6 +404,63 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPrefixedItems(List<string>? items, string prefix, List<string> output)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.StartsWith(prefix))
|
||||
{
|
||||
output.Add(item.Substring(prefix.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractDnsRuleSets(string? dnsJson, List<string> geoipFiles, List<string> geoSiteFiles)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dnsJson))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var dns = JsonUtils.Deserialize<Dns4Sbox>(dnsJson);
|
||||
if (dns?.rules != null)
|
||||
{
|
||||
foreach (var rule in dns.rules)
|
||||
{
|
||||
ExtractSrsRuleSets(rule, geoipFiles, geoSiteFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void ExtractSrsRuleSets(Rule4Sbox? rule, List<string> geoipFiles, List<string> geoSiteFiles)
|
||||
{
|
||||
if (rule == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddPrefixedItems(rule.rule_set, "geosite-", geoSiteFiles);
|
||||
AddPrefixedItems(rule.rule_set, "geoip-", geoipFiles);
|
||||
|
||||
// Handle nested rules recursively
|
||||
if (rule.rules != null)
|
||||
{
|
||||
foreach (var nestedRule in rule.rules)
|
||||
{
|
||||
ExtractSrsRuleSets(nestedRule, geoipFiles, geoSiteFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateSrsFile(string type, string srsName)
|
||||
{
|
||||
var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl)
|
||||
|
||||
@@ -27,6 +27,8 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
|
||||
public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
||||
|
||||
public IObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
||||
|
||||
//public ReactiveCommand<Unit, Unit> AddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoveCmd { get; }
|
||||
|
||||
@@ -79,8 +81,8 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
ProfileGroupItemManager.Instance.TryGet(SelectedSource.IndexId, out var profileGroup);
|
||||
PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
|
||||
var protocolExtra = SelectedSource.GetProtocolExtra();
|
||||
PolicyGroupType = (protocolExtra?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
|
||||
{
|
||||
EMultipleLoad.LeastPing => ResUI.TbLeastPing,
|
||||
EMultipleLoad.Fallback => ResUI.TbFallback,
|
||||
@@ -93,23 +95,12 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
var subs = await AppManager.Instance.SubItems();
|
||||
subs.Add(new SubItem());
|
||||
SubItems.AddRange(subs);
|
||||
SelectedSubItem = SubItems.Where(s => s.Id == profileGroup?.SubChildItems).FirstOrDefault();
|
||||
Filter = profileGroup?.Filter;
|
||||
SelectedSubItem = SubItems.FirstOrDefault(s => s.Id == protocolExtra?.SubChildItems);
|
||||
Filter = protocolExtra?.Filter;
|
||||
|
||||
var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId);
|
||||
if (childItemMulti != null)
|
||||
{
|
||||
var childIndexIds = Utils.String2List(childItemMulti.ChildItems) ?? [];
|
||||
foreach (var item in childIndexIds)
|
||||
{
|
||||
var child = await AppManager.Instance.GetProfileItem(item);
|
||||
if (child == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ChildItemsObs.Add(child);
|
||||
}
|
||||
}
|
||||
var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? [];
|
||||
var childItemList = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childIndexIds);
|
||||
ChildItemsObs.AddRange(childItemList);
|
||||
}
|
||||
|
||||
public async Task ChildRemoveAsync()
|
||||
@@ -186,6 +177,32 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private ProtocolExtraItem GetUpdatedProtocolExtra()
|
||||
{
|
||||
return SelectedSource.GetProtocolExtra() with
|
||||
{
|
||||
ChildItems =
|
||||
Utils.List2String(ChildItemsObs.Where(s => !s.IndexId.IsNullOrEmpty()).Select(s => s.IndexId).ToList()),
|
||||
MultipleLoad = PolicyGroupType switch
|
||||
{
|
||||
var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing,
|
||||
var s when s == ResUI.TbFallback => EMultipleLoad.Fallback,
|
||||
var s when s == ResUI.TbRandom => EMultipleLoad.Random,
|
||||
var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin,
|
||||
var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad,
|
||||
_ => EMultipleLoad.LeastPing,
|
||||
},
|
||||
SubChildItems = SelectedSubItem?.Id,
|
||||
Filter = Filter,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task UpdatePreviewList()
|
||||
{
|
||||
AllProfilePreviewItemsObs.Clear();
|
||||
AllProfilePreviewItemsObs.AddRange(await GroupProfileManager.GetChildProfileItemsByProtocolExtra(GetUpdatedProtocolExtra()));
|
||||
}
|
||||
|
||||
private async Task SaveServerAsync()
|
||||
{
|
||||
var remarks = SelectedSource.Remarks;
|
||||
@@ -205,38 +222,12 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
{
|
||||
return;
|
||||
}
|
||||
var childIndexIds = new List<string>();
|
||||
foreach (var item in ChildItemsObs)
|
||||
{
|
||||
if (item.IndexId.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
childIndexIds.Add(item.IndexId);
|
||||
}
|
||||
var profileGroup = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource.IndexId);
|
||||
profileGroup.ChildItems = Utils.List2String(childIndexIds);
|
||||
profileGroup.MultipleLoad = PolicyGroupType switch
|
||||
{
|
||||
var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing,
|
||||
var s when s == ResUI.TbFallback => EMultipleLoad.Fallback,
|
||||
var s when s == ResUI.TbRandom => EMultipleLoad.Random,
|
||||
var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin,
|
||||
var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad,
|
||||
_ => EMultipleLoad.LeastPing,
|
||||
};
|
||||
|
||||
profileGroup.SubChildItems = SelectedSubItem?.Id;
|
||||
profileGroup.Filter = Filter;
|
||||
var protocolExtra = GetUpdatedProtocolExtra();
|
||||
|
||||
var hasCycle = ProfileGroupItemManager.HasCycle(profileGroup.IndexId);
|
||||
if (hasCycle)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.GroupSelfReference, remarks));
|
||||
return;
|
||||
}
|
||||
SelectedSource.SetProtocolExtra(protocolExtra);
|
||||
|
||||
if (await ConfigHandler.AddGroupServerCommon(_config, SelectedSource, profileGroup, true) == 0)
|
||||
if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
|
||||
@@ -14,6 +14,53 @@ public class AddServerViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public string CertTip { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CertSha { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SalamanderPass { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int AlterId { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Ports { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? UpMbps { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? DownMbps { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string HopInterval { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Flow { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string VmessSecurity { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string VlessEncryption { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SsMethod { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgPublicKey { get; set; }
|
||||
|
||||
//[Reactive]
|
||||
//public string WgPresharedKey { get; set; }
|
||||
[Reactive]
|
||||
public string WgInterfaceAddress { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgReserved { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int WgMtu { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
@@ -39,6 +86,12 @@ public class AddServerViewModel : MyReactiveObject
|
||||
this.WhenAnyValue(x => x.Cert)
|
||||
.Subscribe(_ => UpdateCertTip());
|
||||
|
||||
this.WhenAnyValue(x => x.CertSha)
|
||||
.Subscribe(_ => UpdateCertTip());
|
||||
|
||||
this.WhenAnyValue(x => x.Cert)
|
||||
.Subscribe(_ => UpdateCertSha());
|
||||
|
||||
if (profileItem.IndexId.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.Network = Global.DefaultNetwork;
|
||||
@@ -53,6 +106,23 @@ public class AddServerViewModel : MyReactiveObject
|
||||
}
|
||||
CoreType = SelectedSource?.CoreType?.ToString();
|
||||
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty;
|
||||
CertSha = SelectedSource?.CertSha?.ToString() ?? string.Empty;
|
||||
|
||||
var protocolExtra = SelectedSource?.GetProtocolExtra();
|
||||
Ports = protocolExtra?.Ports ?? string.Empty;
|
||||
AlterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0;
|
||||
Flow = protocolExtra?.Flow ?? string.Empty;
|
||||
SalamanderPass = protocolExtra?.SalamanderPass ?? string.Empty;
|
||||
UpMbps = protocolExtra?.UpMbps;
|
||||
DownMbps = protocolExtra?.DownMbps;
|
||||
HopInterval = protocolExtra?.HopInterval.IsNullOrEmpty() ?? true ? Global.Hysteria2DefaultHopInt.ToString() : protocolExtra.HopInterval;
|
||||
VmessSecurity = protocolExtra?.VmessSecurity?.IsNullOrEmpty() == false ? protocolExtra.VmessSecurity : Global.DefaultSecurity;
|
||||
VlessEncryption = protocolExtra?.VlessEncryption.IsNullOrEmpty() == false ? protocolExtra.VlessEncryption : Global.None;
|
||||
SsMethod = protocolExtra?.SsMethod ?? string.Empty;
|
||||
WgPublicKey = protocolExtra?.WgPublicKey ?? string.Empty;
|
||||
WgInterfaceAddress = protocolExtra?.WgInterfaceAddress ?? string.Empty;
|
||||
WgReserved = protocolExtra?.WgReserved ?? string.Empty;
|
||||
WgMtu = protocolExtra?.WgMtu ?? 1280;
|
||||
}
|
||||
|
||||
private async Task SaveServerAsync()
|
||||
@@ -77,12 +147,12 @@ public class AddServerViewModel : MyReactiveObject
|
||||
}
|
||||
if (SelectedSource.ConfigType == EConfigType.Shadowsocks)
|
||||
{
|
||||
if (SelectedSource.Id.IsNullOrEmpty())
|
||||
if (SelectedSource.Password.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillPassword);
|
||||
return;
|
||||
}
|
||||
if (SelectedSource.Security.IsNullOrEmpty())
|
||||
if (SsMethod.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectEncryption);
|
||||
return;
|
||||
@@ -90,14 +160,32 @@ public class AddServerViewModel : MyReactiveObject
|
||||
}
|
||||
if (SelectedSource.ConfigType is not EConfigType.SOCKS and not EConfigType.HTTP)
|
||||
{
|
||||
if (SelectedSource.Id.IsNullOrEmpty())
|
||||
if (SelectedSource.Password.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillUUID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
|
||||
SelectedSource.Cert = Cert.IsNullOrEmpty() ? null : Cert;
|
||||
SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert;
|
||||
SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha;
|
||||
SelectedSource.SetProtocolExtra(SelectedSource.GetProtocolExtra() with
|
||||
{
|
||||
Ports = Ports.NullIfEmpty(),
|
||||
AlterId = AlterId > 0 ? AlterId.ToString() : null,
|
||||
Flow = Flow.NullIfEmpty(),
|
||||
SalamanderPass = SalamanderPass.NullIfEmpty(),
|
||||
UpMbps = UpMbps,
|
||||
DownMbps = DownMbps,
|
||||
HopInterval = HopInterval.NullIfEmpty(),
|
||||
VmessSecurity = VmessSecurity.NullIfEmpty(),
|
||||
VlessEncryption = VlessEncryption.NullIfEmpty(),
|
||||
SsMethod = SsMethod.NullIfEmpty(),
|
||||
WgPublicKey = WgPublicKey.NullIfEmpty(),
|
||||
WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(),
|
||||
WgReserved = WgReserved.NullIfEmpty(),
|
||||
WgMtu = WgMtu >= 576 ? WgMtu : null,
|
||||
});
|
||||
|
||||
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
@@ -113,10 +201,36 @@ public class AddServerViewModel : MyReactiveObject
|
||||
private void UpdateCertTip(string? errorMessage = null)
|
||||
{
|
||||
CertTip = errorMessage.IsNullOrEmpty()
|
||||
? (Cert.IsNullOrEmpty() ? ResUI.CertNotSet : ResUI.CertSet)
|
||||
? ((Cert.IsNullOrEmpty() && CertSha.IsNullOrEmpty()) ? ResUI.CertNotSet : ResUI.CertSet)
|
||||
: errorMessage;
|
||||
}
|
||||
|
||||
private void UpdateCertSha()
|
||||
{
|
||||
if (Cert.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var certList = CertPemManager.ParsePemChain(Cert);
|
||||
if (certList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> shaList = [];
|
||||
foreach (var cert in certList)
|
||||
{
|
||||
var sha = CertPemManager.GetCertSha256Thumbprint(cert);
|
||||
if (sha.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
shaList.Add(sha);
|
||||
}
|
||||
CertSha = string.Join(',', shaList);
|
||||
}
|
||||
|
||||
private async Task FetchCert()
|
||||
{
|
||||
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
|
||||
@@ -133,17 +247,12 @@ public class AddServerViewModel : MyReactiveObject
|
||||
{
|
||||
serverName = SelectedSource.Address;
|
||||
}
|
||||
if (!Utils.IsDomain(serverName))
|
||||
{
|
||||
UpdateCertTip(ResUI.ServerNameMustBeValidDomain);
|
||||
return;
|
||||
}
|
||||
if (SelectedSource.Port > 0)
|
||||
{
|
||||
domain += $":{SelectedSource.Port}";
|
||||
}
|
||||
string certError;
|
||||
(Cert, certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
|
||||
|
||||
(Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
|
||||
UpdateCertTip(certError);
|
||||
}
|
||||
|
||||
@@ -163,17 +272,12 @@ public class AddServerViewModel : MyReactiveObject
|
||||
{
|
||||
serverName = SelectedSource.Address;
|
||||
}
|
||||
if (!Utils.IsDomain(serverName))
|
||||
{
|
||||
UpdateCertTip(ResUI.ServerNameMustBeValidDomain);
|
||||
return;
|
||||
}
|
||||
if (SelectedSource.Port > 0)
|
||||
{
|
||||
domain += $":{SelectedSource.Port}";
|
||||
}
|
||||
string certError;
|
||||
(var certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
|
||||
|
||||
var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
|
||||
Cert = CertPemManager.ConcatenatePemChain(certs);
|
||||
UpdateCertTip(certError);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
||||
{
|
||||
await Task.Delay(1000 * 5);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
{
|
||||
await Task.Delay(1000 * 60);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
[Reactive] public string? DirectDNS { get; set; }
|
||||
[Reactive] public string? RemoteDNS { get; set; }
|
||||
[Reactive] public string? BootstrapDNS { get; set; }
|
||||
[Reactive] public string? RayStrategy4Freedom { get; set; }
|
||||
[Reactive] public string? SingboxStrategy4Direct { get; set; }
|
||||
[Reactive] public string? SingboxStrategy4Proxy { get; set; }
|
||||
[Reactive] public string? Strategy4Freedom { get; set; }
|
||||
[Reactive] public string? Strategy4Proxy { get; set; }
|
||||
[Reactive] public string? Hosts { get; set; }
|
||||
[Reactive] public string? DirectExpectedIPs { get; set; }
|
||||
[Reactive] public bool? ParallelQuery { get; set; }
|
||||
[Reactive] public bool? ServeStale { get; set; }
|
||||
|
||||
[Reactive] public bool UseSystemHostsCompatible { get; set; }
|
||||
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; }
|
||||
@@ -70,11 +71,12 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
DirectDNS = item.DirectDNS;
|
||||
RemoteDNS = item.RemoteDNS;
|
||||
BootstrapDNS = item.BootstrapDNS;
|
||||
RayStrategy4Freedom = item.RayStrategy4Freedom;
|
||||
SingboxStrategy4Direct = item.SingboxStrategy4Direct;
|
||||
SingboxStrategy4Proxy = item.SingboxStrategy4Proxy;
|
||||
Strategy4Freedom = item.Strategy4Freedom;
|
||||
Strategy4Proxy = item.Strategy4Proxy;
|
||||
Hosts = item.Hosts;
|
||||
DirectExpectedIPs = item.DirectExpectedIPs;
|
||||
ParallelQuery = item.ParallelQuery;
|
||||
ServeStale = item.ServeStale;
|
||||
|
||||
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
RayCustomDNSEnableCompatible = item1.Enabled;
|
||||
@@ -100,11 +102,12 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
_config.SimpleDNSItem.DirectDNS = DirectDNS;
|
||||
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
|
||||
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
|
||||
_config.SimpleDNSItem.RayStrategy4Freedom = RayStrategy4Freedom;
|
||||
_config.SimpleDNSItem.SingboxStrategy4Direct = SingboxStrategy4Direct;
|
||||
_config.SimpleDNSItem.SingboxStrategy4Proxy = SingboxStrategy4Proxy;
|
||||
_config.SimpleDNSItem.Strategy4Freedom = Strategy4Freedom;
|
||||
_config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy;
|
||||
_config.SimpleDNSItem.Hosts = Hosts;
|
||||
_config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs;
|
||||
_config.SimpleDNSItem.ParallelQuery = ParallelQuery;
|
||||
_config.SimpleDNSItem.ServeStale = ServeStale;
|
||||
|
||||
if (NormalDNSCompatible.IsNotEmpty())
|
||||
{
|
||||
|
||||
@@ -253,13 +253,12 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
_config.UiItem.ShowInTaskbar = true;
|
||||
AppManager.Instance.ShowInTaskbar = true;
|
||||
|
||||
//await ConfigHandler.InitBuiltinRouting(_config);
|
||||
await ConfigHandler.InitBuiltinDNS(_config);
|
||||
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
|
||||
await ProfileExManager.Instance.Init();
|
||||
await ProfileGroupItemManager.Instance.Init();
|
||||
await CoreManager.Instance.Init(_config, UpdateHandler);
|
||||
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);
|
||||
|
||||
@@ -306,7 +305,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task UpdateStatisticsHandler(ServerSpeedItem update)
|
||||
{
|
||||
if (!_config.UiItem.ShowInTaskbar)
|
||||
if (!AppManager.Instance.ShowInTaskbar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -541,26 +540,27 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
SetReloadEnabled(false);
|
||||
|
||||
var msgs = await ActionPrecheckManager.Instance.Check(_config.IndexId);
|
||||
if (msgs.Count > 0)
|
||||
var profileItem = await ConfigHandler.GetDefaultServer(_config);
|
||||
if (profileItem == null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings);
|
||||
return;
|
||||
}
|
||||
var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem);
|
||||
if (NoticeManager.Instance.NotifyValidatorResult(allResult.CombinedValidatorResult) && !allResult.Success)
|
||||
{
|
||||
foreach (var msg in msgs)
|
||||
{
|
||||
NoticeManager.Instance.SendMessage(msg);
|
||||
}
|
||||
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
await LoadCore();
|
||||
await LoadCore(allResult.ResolvedMainContext, allResult.PreSocksResult?.Context);
|
||||
await SysProxyHandler.UpdateSysProxy(_config, false);
|
||||
await Task.Delay(1000);
|
||||
});
|
||||
AppEvents.TestServerRequested.Publish();
|
||||
|
||||
var showClashUI = _config.IsRunningCore(ECoreType.sing_box);
|
||||
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box);
|
||||
if (showClashUI)
|
||||
{
|
||||
AppEvents.ProxiesReloadRequested.Publish();
|
||||
@@ -595,10 +595,9 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled);
|
||||
}
|
||||
|
||||
private async Task LoadCore()
|
||||
private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
|
||||
{
|
||||
var node = await ConfigHandler.GetDefaultServer(_config);
|
||||
await CoreManager.Instance.LoadCore(node);
|
||||
await CoreManager.Instance.LoadCore(mainContext, preContext);
|
||||
}
|
||||
|
||||
#endregion core job
|
||||
|
||||
@@ -44,7 +44,7 @@ public class MsgViewModel : MyReactiveObject
|
||||
|
||||
EnqueueQueueMsg(msg);
|
||||
|
||||
if (!_config.UiItem.ShowInTaskbar)
|
||||
if (!AppManager.Instance.ShowInTaskbar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
[Reactive] public string defUserAgent { get; set; }
|
||||
[Reactive] public string mux4SboxProtocol { get; set; }
|
||||
[Reactive] public bool enableCacheFile4Sbox { get; set; }
|
||||
[Reactive] public int hyUpMbps { get; set; }
|
||||
[Reactive] public int hyDownMbps { get; set; }
|
||||
[Reactive] public int? hyUpMbps { get; set; }
|
||||
[Reactive] public int? hyDownMbps { get; set; }
|
||||
[Reactive] public bool enableFragment { get; set; }
|
||||
|
||||
#endregion Core
|
||||
@@ -95,7 +95,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
[Reactive] public bool TunStrictRoute { get; set; }
|
||||
[Reactive] public string TunStack { get; set; }
|
||||
[Reactive] public int TunMtu { get; set; }
|
||||
[Reactive] public bool TunEnableExInbound { get; set; }
|
||||
[Reactive] public bool TunEnableIPv6Address { get; set; }
|
||||
|
||||
#endregion Tun mode
|
||||
@@ -108,6 +107,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
[Reactive] public string CoreType4 { get; set; }
|
||||
[Reactive] public string CoreType5 { get; set; }
|
||||
[Reactive] public string CoreType6 { get; set; }
|
||||
[Reactive] public string CoreType7 { get; set; }
|
||||
[Reactive] public string CoreType9 { get; set; }
|
||||
|
||||
#endregion CoreType
|
||||
@@ -219,7 +219,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
TunStrictRoute = _config.TunModeItem.StrictRoute;
|
||||
TunStack = _config.TunModeItem.Stack;
|
||||
TunMtu = _config.TunModeItem.Mtu;
|
||||
TunEnableExInbound = _config.TunModeItem.EnableExInbound;
|
||||
TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address;
|
||||
|
||||
#endregion Tun mode
|
||||
@@ -276,6 +275,10 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
CoreType6 = type;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
CoreType7 = type;
|
||||
break;
|
||||
|
||||
case 9:
|
||||
CoreType9 = type;
|
||||
break;
|
||||
@@ -333,8 +336,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.CoreBasicItem.DefUserAgent = defUserAgent;
|
||||
_config.Mux4SboxItem.Protocol = mux4SboxProtocol;
|
||||
_config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox;
|
||||
_config.HysteriaItem.UpMbps = hyUpMbps;
|
||||
_config.HysteriaItem.DownMbps = hyDownMbps;
|
||||
_config.HysteriaItem.UpMbps = hyUpMbps ?? 0;
|
||||
_config.HysteriaItem.DownMbps = hyDownMbps ?? 0;
|
||||
_config.CoreBasicItem.EnableFragment = enableFragment;
|
||||
|
||||
_config.GuiItem.AutoRun = AutoRun;
|
||||
@@ -375,7 +378,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.TunModeItem.StrictRoute = TunStrictRoute;
|
||||
_config.TunModeItem.Stack = TunStack;
|
||||
_config.TunModeItem.Mtu = TunMtu;
|
||||
_config.TunModeItem.EnableExInbound = TunEnableExInbound;
|
||||
_config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address;
|
||||
|
||||
//coreType
|
||||
@@ -427,6 +429,10 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
type = CoreType6;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
type = CoreType7;
|
||||
break;
|
||||
|
||||
case 9:
|
||||
type = CoreType9;
|
||||
break;
|
||||
|
||||
@@ -200,7 +200,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
|
||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileItems(_subIndexId, filter);
|
||||
var lstModel = await AppManager.Instance.ProfileModels(_subIndexId, filter);
|
||||
lstModel = (from t in lstModel
|
||||
select new ProfileItemModel
|
||||
{
|
||||
@@ -209,7 +209,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
Remarks = t.Remarks,
|
||||
Address = t.Address,
|
||||
Port = t.Port,
|
||||
Security = t.Security,
|
||||
//Security = t.Security,
|
||||
Network = t.Network,
|
||||
StreamSecurity = t.StreamSecurity,
|
||||
Subid = t.Subid,
|
||||
@@ -255,19 +255,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var lst = new List<ProfileItem>();
|
||||
foreach (var sp in SelectedProfiles)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sp?.IndexId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(sp.IndexId);
|
||||
if (item != null)
|
||||
{
|
||||
lst.Add(item);
|
||||
}
|
||||
}
|
||||
var lst = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(SelectedProfiles.Select(sp => sp?.IndexId));
|
||||
if (lst.Count == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
|
||||
@@ -8,6 +8,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
private string _serverFilter = string.Empty;
|
||||
private Dictionary<string, bool> _dicHeaderSort = new();
|
||||
private SpeedtestService? _speedtestService;
|
||||
private string? _pendingSelectIndexId;
|
||||
|
||||
#endregion private prop
|
||||
|
||||
@@ -43,13 +44,8 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
public ReactiveCommand<Unit, Unit> CopyServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ShareServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRandomCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRoundRobinCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastPingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastLoadCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayFallbackCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerSingBoxLeastPingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerSingBoxFallbackCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupAllServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupRegionServerCmd { get; }
|
||||
|
||||
//servers move
|
||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
||||
@@ -57,6 +53,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
||||
public ReactiveCommand<SubItem, Unit> MoveToGroupCmd { get; }
|
||||
|
||||
//servers ping
|
||||
public ReactiveCommand<Unit, Unit> MixedTestServerCmd { get; }
|
||||
@@ -77,6 +74,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
public ReactiveCommand<Unit, Unit> AddSubCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> EditSubCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> DeleteSubCmd { get; }
|
||||
|
||||
#endregion Menu
|
||||
|
||||
@@ -132,33 +130,13 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
{
|
||||
await ShareServerAsync();
|
||||
}, canEditRemove);
|
||||
GenGroupMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
GenGroupAllServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Random);
|
||||
await GenGroupAllServer();
|
||||
}, canEditRemove);
|
||||
GenGroupMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
GenGroupRegionServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin);
|
||||
}, canEditRemove);
|
||||
GenGroupMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing);
|
||||
}, canEditRemove);
|
||||
GenGroupMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad);
|
||||
}, canEditRemove);
|
||||
GenGroupMultipleServerXrayFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Fallback);
|
||||
}, canEditRemove);
|
||||
GenGroupMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing);
|
||||
}, canEditRemove);
|
||||
GenGroupMultipleServerSingBoxFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.Fallback);
|
||||
await GenGroupRegionServer();
|
||||
}, canEditRemove);
|
||||
|
||||
//servers move
|
||||
@@ -178,6 +156,10 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
{
|
||||
await MoveServer(EMove.Bottom);
|
||||
}, canEditRemove);
|
||||
MoveToGroupCmd = ReactiveCommand.CreateFromTask<SubItem>(async sub =>
|
||||
{
|
||||
SelectedMoveToGroup = sub;
|
||||
});
|
||||
|
||||
//servers ping
|
||||
FastRealPingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
@@ -235,6 +217,10 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
{
|
||||
await EditSubAsync(false);
|
||||
});
|
||||
DeleteSubCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await DeleteSubAsync();
|
||||
});
|
||||
|
||||
#endregion WhenAnyValue && ReactiveCommand
|
||||
|
||||
@@ -382,15 +368,14 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
ProfileItems.AddRange(lstModel);
|
||||
if (lstModel.Count > 0)
|
||||
{
|
||||
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
||||
if (selected != null)
|
||||
ProfileItemModel? selected = null;
|
||||
if (!_pendingSelectIndexId.IsNullOrEmpty())
|
||||
{
|
||||
SelectedProfile = selected;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedProfile = lstModel.First();
|
||||
selected = lstModel.FirstOrDefault(t => t.IndexId == _pendingSelectIndexId);
|
||||
_pendingSelectIndexId = null;
|
||||
}
|
||||
selected ??= lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
||||
SelectedProfile = selected ?? lstModel.First();
|
||||
}
|
||||
|
||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||
@@ -418,7 +403,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, filter);
|
||||
var lstModel = await AppManager.Instance.ProfileModels(_config.SubIndexId, filter);
|
||||
|
||||
await ConfigHandler.SetDefaultServer(_config, lstModel);
|
||||
|
||||
@@ -436,7 +421,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
Remarks = t.Remarks,
|
||||
Address = t.Address,
|
||||
Port = t.Port,
|
||||
Security = t.Security,
|
||||
//Security = t.Security,
|
||||
Network = t.Network,
|
||||
StreamSecurity = t.StreamSecurity,
|
||||
Subid = t.Subid,
|
||||
@@ -471,14 +456,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort);
|
||||
if (latest)
|
||||
{
|
||||
foreach (var profile in orderProfiles)
|
||||
{
|
||||
var item = await AppManager.Instance.GetProfileItem(profile.IndexId);
|
||||
if (item is not null)
|
||||
{
|
||||
lstSelected.Add(item);
|
||||
}
|
||||
}
|
||||
lstSelected.AddRange(await AppManager.Instance.GetProfileItemsOrderedByIndexIds(orderProfiles.Select(sp => sp?.IndexId)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -631,29 +609,29 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
await _updateView?.Invoke(EViewAction.ShareServer, url);
|
||||
}
|
||||
|
||||
private async Task GenGroupMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad)
|
||||
private async Task GenGroupAllServer()
|
||||
{
|
||||
var lstSelected = await GetProfileItems(true);
|
||||
if (lstSelected == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ret = await ConfigHandler.AddGroupServer4Multiple(_config, lstSelected, coreType, multipleLoad, SelectedSub?.Id);
|
||||
var ret = await ConfigHandler.AddGroupAllServer(_config, SelectedSub);
|
||||
if (ret.Success != true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
if (ret?.Data?.ToString() == _config.IndexId)
|
||||
_pendingSelectIndexId = ret.Data?.ToString();
|
||||
await RefreshServers();
|
||||
}
|
||||
|
||||
private async Task GenGroupRegionServer()
|
||||
{
|
||||
var ret = await ConfigHandler.AddGroupRegionServer(_config, SelectedSub);
|
||||
if (ret.Success != true)
|
||||
{
|
||||
await RefreshServers();
|
||||
Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetDefaultServer(ret?.Data?.ToString());
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
var indexIdList = ret.Data as List<string>;
|
||||
_pendingSelectIndexId = indexIdList?.FirstOrDefault();
|
||||
await RefreshServers();
|
||||
}
|
||||
|
||||
public async Task SortServer(string colName)
|
||||
@@ -778,20 +756,15 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
|
||||
var msgs = await ActionPrecheckManager.Instance.Check(item);
|
||||
if (msgs.Count > 0)
|
||||
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
|
||||
if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
|
||||
{
|
||||
foreach (var msg in msgs)
|
||||
{
|
||||
NoticeManager.Instance.SendMessage(msg);
|
||||
}
|
||||
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
|
||||
return;
|
||||
}
|
||||
|
||||
if (blClipboard)
|
||||
{
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(item, null);
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(context, null);
|
||||
if (result.Success != true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(result.Msg);
|
||||
@@ -814,7 +787,12 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
{
|
||||
return;
|
||||
}
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(item, fileName);
|
||||
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
|
||||
if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
|
||||
if (result.Success != true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(result.Msg);
|
||||
@@ -884,5 +862,23 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteSubAsync()
|
||||
{
|
||||
var item = await AppManager.Instance.GetSubItem(_config.SubIndexId);
|
||||
if (item is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await ConfigHandler.DeleteSubItem(_config, item.Id);
|
||||
|
||||
await RefreshSubscriptions();
|
||||
await SubSelectedChangedAsync(true);
|
||||
}
|
||||
|
||||
#endregion Subscription
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
Network = item.Network,
|
||||
Protocols = Utils.List2String(item.Protocol),
|
||||
InboundTags = Utils.List2String(item.InboundTag),
|
||||
Domains = Utils.List2String((item.Domain ?? []).Concat(item.Ip ?? []).ToList()),
|
||||
Domains = Utils.List2String((item.Domain ?? []).Concat(item.Ip ?? []).ToList().Concat(item.Process ?? []).ToList()),
|
||||
Enabled = item.Enabled,
|
||||
Remarks = item.Remarks,
|
||||
};
|
||||
|
||||
@@ -303,7 +303,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
private async Task RefreshServersMenu()
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, "");
|
||||
var lstModel = await AppManager.Instance.ProfileModels(_config.SubIndexId, "");
|
||||
|
||||
Servers.Clear();
|
||||
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
|
||||
@@ -315,7 +315,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
BlServers = true;
|
||||
for (var k = 0; k < lstModel.Count; k++)
|
||||
{
|
||||
ProfileItem it = lstModel[k];
|
||||
var it = lstModel[k];
|
||||
var name = it.GetSummary();
|
||||
|
||||
var item = new ComboItem() { ID = it.IndexId, Text = name };
|
||||
@@ -549,7 +549,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
try
|
||||
{
|
||||
if (_config.IsRunningCore(ECoreType.sing_box))
|
||||
if (AppManager.Instance.IsRunningCore(ECoreType.sing_box))
|
||||
{
|
||||
SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown));
|
||||
SpeedDirectDisplay = string.Empty;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace v2rayN.Desktop.Common;
|
||||
|
||||
public static class AppBuilderExtension
|
||||
@@ -11,7 +5,7 @@ public static class AppBuilderExtension
|
||||
public static AppBuilder WithFontByDefault(this AppBuilder appBuilder)
|
||||
{
|
||||
var fallbacks = new List<FontFallback>();
|
||||
|
||||
|
||||
var notoSansSc = new FontFamily(Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC"));
|
||||
fallbacks.Add(new FontFallback { FontFamily = notoSansSc });
|
||||
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
</StackPanel>
|
||||
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
ColumnDefinitions="300,Auto,Auto"
|
||||
DockPanel.Dock="Top"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
@@ -75,7 +74,7 @@
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
ColumnDefinitions="180,Auto,Auto">
|
||||
ColumnDefinitions="300,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
@@ -89,11 +88,14 @@
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TabControl DockPanel.Dock="Top">
|
||||
<TabControl
|
||||
x:Name="tabControl"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
DockPanel.Dock="Top">
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
|
||||
<Grid
|
||||
Margin="{StaticResource Margin8}"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
ColumnDefinitions="Auto,Auto,Auto"
|
||||
RowDefinitions="Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
@@ -106,7 +108,7 @@
|
||||
x:Name="cmbSubChildItems"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Width="600"
|
||||
Margin="{StaticResource Margin4}"
|
||||
DisplayMemberBinding="{Binding Remarks}"
|
||||
ItemsSource="{Binding SubItems}" />
|
||||
@@ -115,7 +117,8 @@
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" />
|
||||
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
@@ -123,21 +126,20 @@
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.LvFilter}" />
|
||||
<TextBox
|
||||
x:Name="txtFilter"
|
||||
<ComboBox
|
||||
x:Name="cmbFilter"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="600"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center" />
|
||||
VerticalAlignment="Center"
|
||||
IsEditable="True" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<TabControl>
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList2}">
|
||||
<DataGrid
|
||||
x:Name="lstChild"
|
||||
Grid.Row="1"
|
||||
AutoGenerateColumns="False"
|
||||
Background="Transparent"
|
||||
BorderThickness="1"
|
||||
@@ -185,11 +187,53 @@
|
||||
Binding="{Binding ConfigType}"
|
||||
Header="{x:Static resx:ResUI.LvServiceType}" />
|
||||
<DataGridTextColumn
|
||||
Width="150"
|
||||
Width="200"
|
||||
Binding="{Binding Remarks}"
|
||||
Header="{x:Static resx:ResUI.LvRemarks}" />
|
||||
<DataGridTextColumn
|
||||
Width="120"
|
||||
Width="200"
|
||||
Binding="{Binding Address}"
|
||||
Header="{x:Static resx:ResUI.LvAddress}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding Port}"
|
||||
Header="{x:Static resx:ResUI.LvPort}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding Network}"
|
||||
Header="{x:Static resx:ResUI.LvTransportProtocol}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding StreamSecurity}"
|
||||
Header="{x:Static resx:ResUI.LvTLS}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerListPreview}">
|
||||
<DataGrid
|
||||
x:Name="lstPreviewChild"
|
||||
AutoGenerateColumns="False"
|
||||
Background="Transparent"
|
||||
BorderThickness="1"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
GridLinesVisibility="All"
|
||||
HeadersVisibility="Column"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding AllProfilePreviewItemsObs}"
|
||||
SelectionMode="Extended">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="150"
|
||||
Binding="{Binding ConfigType}"
|
||||
Header="{x:Static resx:ResUI.LvServiceType}" />
|
||||
<DataGridTextColumn
|
||||
Width="200"
|
||||
Binding="{Binding Remarks}"
|
||||
Header="{x:Static resx:ResUI.LvRemarks}" />
|
||||
<DataGridTextColumn
|
||||
Width="200"
|
||||
Binding="{Binding Address}"
|
||||
Header="{x:Static resx:ResUI.LvAddress}" />
|
||||
<DataGridTextColumn
|
||||
|
||||
@@ -16,6 +16,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
Loaded += Window_Loaded;
|
||||
btnCancel.Click += (s, e) => Close();
|
||||
lstChild.SelectionChanged += LstChild_SelectionChanged;
|
||||
tabControl.SelectionChanged += TabControl_SelectionChanged;
|
||||
|
||||
ViewModel = new AddGroupServerViewModel(profileItem, UpdateViewHandler);
|
||||
|
||||
@@ -28,6 +29,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
ResUI.TbRoundRobin,
|
||||
ResUI.TbLeastLoad,
|
||||
};
|
||||
cmbFilter.ItemsSource = Global.PolicyGroupDefaultFilterList;
|
||||
|
||||
switch (profileItem.ConfigType)
|
||||
{
|
||||
@@ -38,6 +40,10 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
case EConfigType.ProxyChain:
|
||||
Title = ResUI.TbConfigTypeProxyChain;
|
||||
gridPolicyGroup.IsVisible = false;
|
||||
if (tabControl.Items.Count > 0)
|
||||
{
|
||||
tabControl.Items.RemoveAt(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -48,9 +54,8 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables);
|
||||
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.RemoveCmd, v => v.menuRemoveChildServer).DisposeWith(disposables);
|
||||
@@ -143,14 +148,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
private async void MenuAddChild_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectWindow = new ProfilesSelectWindow();
|
||||
if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup)
|
||||
{
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
|
||||
}
|
||||
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
|
||||
selectWindow.AllowMultiSelect(true);
|
||||
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||
if (result == true)
|
||||
@@ -167,4 +165,29 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
ViewModel.SelectedChildren = lstChild.SelectedItems.Cast<ProfileItem>().ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private async void TabControl_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.Source is not TabControl tc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!(tc.SelectedIndex == tc.Items.Count - 1 && tc.Items.Count > 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (ViewModel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ViewModel.UpdatePreviewList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@
|
||||
IsCancel="True" />
|
||||
</StackPanel>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
ColumnDefinitions="300,Auto,Auto"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
@@ -50,7 +50,7 @@
|
||||
Orientation="Horizontal">
|
||||
<ComboBox
|
||||
x:Name="cmbCoreType"
|
||||
Width="100"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.TbCoreType}" />
|
||||
</StackPanel>
|
||||
@@ -101,7 +101,7 @@
|
||||
<Grid
|
||||
x:Name="gridVMess"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
ColumnDefinitions="300,Auto,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
<Grid
|
||||
x:Name="gridSs"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto"
|
||||
ColumnDefinitions="300,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
<Grid
|
||||
x:Name="gridSocks"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto"
|
||||
ColumnDefinitions="300,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
|
||||
@@ -246,7 +246,7 @@
|
||||
<Grid
|
||||
x:Name="gridVLESS"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
ColumnDefinitions="300,Auto,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
<Grid
|
||||
x:Name="gridTrojan"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto"
|
||||
ColumnDefinitions="300,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
@@ -358,9 +358,9 @@
|
||||
<Grid
|
||||
x:Name="gridHysteria2"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
ColumnDefinitions="300,Auto,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
@@ -407,11 +407,46 @@
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPorts7Tips}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbHopInt7}" />
|
||||
<TextBox
|
||||
x:Name="txtHopInt7"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsHysteriaBandwidth}" />
|
||||
<StackPanel
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal">
|
||||
<TextBox
|
||||
x:Name="txtUpMbps7"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="Up" />
|
||||
<TextBox
|
||||
x:Name="txtDownMbps7"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="Down" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridTuic"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto"
|
||||
ColumnDefinitions="300,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
|
||||
@@ -457,7 +492,7 @@
|
||||
<Grid
|
||||
x:Name="gridWireguard"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto"
|
||||
ColumnDefinitions="300,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
@@ -534,7 +569,7 @@
|
||||
<Grid
|
||||
x:Name="gridAnytls"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="180,Auto"
|
||||
ColumnDefinitions="300,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto">
|
||||
|
||||
@@ -560,7 +595,7 @@
|
||||
<Grid
|
||||
x:Name="gridTransport"
|
||||
Grid.Row="4"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
ColumnDefinitions="300,Auto,Auto"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
@@ -687,12 +722,52 @@
|
||||
Text="{x:Static resx:ResUI.TbPath}" />
|
||||
</Grid>
|
||||
|
||||
<Separator Grid.Row="5" Margin="{StaticResource MarginTb8}" />
|
||||
<Grid
|
||||
x:Name="gridFinalmask"
|
||||
Grid.Row="5"
|
||||
ColumnDefinitions="300,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbFinalmask}" />
|
||||
<Button
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource MarginLr4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Classes="IconButton">
|
||||
<Button.Content>
|
||||
<PathIcon Data="{StaticResource SemiIconMore}">
|
||||
<PathIcon.RenderTransform>
|
||||
<RotateTransform Angle="90" />
|
||||
</PathIcon.RenderTransform>
|
||||
</PathIcon>
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel>
|
||||
<TextBox
|
||||
x:Name="txtFinalmask"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
AcceptsReturn="True"
|
||||
Classes="TextArea"
|
||||
TextWrapping="NoWrap" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Separator Grid.Row="6" Margin="{StaticResource MarginTb8}" />
|
||||
|
||||
<Grid
|
||||
x:Name="gridTls"
|
||||
Grid.Row="6"
|
||||
ColumnDefinitions="180,Auto"
|
||||
Grid.Row="7"
|
||||
ColumnDefinitions="300,Auto"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
@@ -710,10 +785,10 @@
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridTlsMore"
|
||||
Grid.Row="7"
|
||||
ColumnDefinitions="180,Auto"
|
||||
Grid.Row="8"
|
||||
ColumnDefinitions="300,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
@@ -768,15 +843,41 @@
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbEchConfigList}" />
|
||||
<TextBox
|
||||
x:Name="txtEchConfigList"
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbEchForceQuery}" />
|
||||
<ComboBox
|
||||
x:Name="cmbEchForceQuery"
|
||||
Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbCertPinning}" />
|
||||
<StackPanel
|
||||
Grid.Row="5"
|
||||
Grid.Row="7"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
@@ -815,6 +916,23 @@
|
||||
Margin="{StaticResource Margin4}"
|
||||
Content="{x:Static resx:ResUI.TbFetchCertChain}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbCertSha256Tips}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
x:Name="txtCertSha256Pinning"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbFullCertTips}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
x:Name="txtCert"
|
||||
Width="400"
|
||||
@@ -830,8 +948,8 @@
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridRealityMore"
|
||||
Grid.Row="7"
|
||||
ColumnDefinitions="180,Auto"
|
||||
Grid.Row="8"
|
||||
ColumnDefinitions="300,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
@@ -918,7 +1036,7 @@
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
<Separator Grid.Row="8" Margin="{StaticResource MarginTb8}" />
|
||||
<Separator Grid.Row="9" Margin="{StaticResource MarginTb8}" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
|
||||
@@ -28,6 +28,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
cmbFingerprint2.ItemsSource = Global.Fingerprints;
|
||||
cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
|
||||
cmbAlpn.ItemsSource = Global.Alpns;
|
||||
cmbEchForceQuery.ItemsSource = Global.EchForceQuerys;
|
||||
|
||||
var lstStreamSecurity = new List<string>();
|
||||
lstStreamSecurity.Add(string.Empty);
|
||||
@@ -38,10 +39,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
case EConfigType.VMess:
|
||||
gridVMess.IsVisible = true;
|
||||
cmbSecurity.ItemsSource = Global.VmessSecurities;
|
||||
if (profileItem.Security.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.Security = Global.DefaultSecurity;
|
||||
}
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
@@ -58,10 +55,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
gridVLESS.IsVisible = true;
|
||||
lstStreamSecurity.Add(Global.StreamSecurityReality);
|
||||
cmbFlow5.ItemsSource = Global.Flows;
|
||||
if (profileItem.Security.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.Security = Global.None;
|
||||
}
|
||||
break;
|
||||
|
||||
case EConfigType.Trojan:
|
||||
@@ -74,7 +67,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
gridHysteria2.IsVisible = true;
|
||||
sepa2.IsVisible = false;
|
||||
gridTransport.IsVisible = false;
|
||||
cmbCoreType.IsEnabled = false;
|
||||
cmbFingerprint.IsEnabled = false;
|
||||
cmbFingerprint.SelectedValue = string.Empty;
|
||||
break;
|
||||
@@ -86,6 +78,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
cmbCoreType.IsEnabled = false;
|
||||
cmbFingerprint.IsEnabled = false;
|
||||
cmbFingerprint.SelectedValue = string.Empty;
|
||||
gridFinalmask.IsVisible = false;
|
||||
|
||||
cmbHeaderType8.ItemsSource = Global.TuicCongestionControls;
|
||||
break;
|
||||
@@ -103,6 +96,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
gridAnytls.IsVisible = true;
|
||||
lstStreamSecurity.Add(Global.StreamSecurityReality);
|
||||
cmbCoreType.IsEnabled = false;
|
||||
gridFinalmask.IsVisible = false;
|
||||
break;
|
||||
}
|
||||
cmbStreamSecurity.ItemsSource = lstStreamSecurity;
|
||||
@@ -119,59 +113,62 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
switch (profileItem.ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId3.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.SOCKS:
|
||||
case EConfigType.HTTP:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId4.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity4.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId4.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtSecurity4.Text).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity5.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Trojan:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Hysteria2:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SalamanderPass, v => v.txtPath7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.TUIC:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId8.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity8.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.WireGuard:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.PublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Anytls:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId10.Text).DisposeWith(disposables);
|
||||
break;
|
||||
}
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
|
||||
@@ -185,8 +182,12 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.SelectedValue).DisposeWith(disposables);
|
||||
|
||||
//reality
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);
|
||||
@@ -195,6 +196,8 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
|
||||
@@ -74,23 +74,28 @@
|
||||
<DataGridTextColumn
|
||||
Width="300"
|
||||
Binding="{Binding Host}"
|
||||
Header="{x:Static resx:ResUI.TbSortingHost}" />
|
||||
Header="{x:Static resx:ResUI.TbSortingHost}"
|
||||
Tag="Host" />
|
||||
<DataGridTextColumn
|
||||
Width="500"
|
||||
Binding="{Binding Chain}"
|
||||
Header="{x:Static resx:ResUI.TbSortingChain}" />
|
||||
Header="{x:Static resx:ResUI.TbSortingChain}"
|
||||
Tag="Chain" />
|
||||
<DataGridTextColumn
|
||||
Width="80"
|
||||
Binding="{Binding Network}"
|
||||
Header="{x:Static resx:ResUI.TbSortingNetwork}" />
|
||||
Header="{x:Static resx:ResUI.TbSortingNetwork}"
|
||||
Tag="Network" />
|
||||
<DataGridTextColumn
|
||||
Width="160"
|
||||
Binding="{Binding Type}"
|
||||
Header="{x:Static resx:ResUI.TbSortingType}" />
|
||||
Header="{x:Static resx:ResUI.TbSortingType}"
|
||||
Tag="Type" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding Elapsed}"
|
||||
Header="{x:Static resx:ResUI.TbSortingTime}" />
|
||||
Header="{x:Static resx:ResUI.TbSortingTime}"
|
||||
Tag="Elapsed" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DockPanel>
|
||||
|
||||
@@ -2,9 +2,15 @@ namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class ClashConnectionsView : ReactiveUserControl<ClashConnectionsViewModel>
|
||||
{
|
||||
private static Config _config;
|
||||
private static readonly string _tag = "ClashConnectionsView";
|
||||
|
||||
public ClashConnectionsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_config = AppManager.Instance.Config;
|
||||
|
||||
ViewModel = new ClashConnectionsViewModel(UpdateViewHandler);
|
||||
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
|
||||
|
||||
@@ -19,7 +25,15 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
|
||||
this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
||||
|
||||
AppEvents.AppExitRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(_ => StorageUI())
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
RestoreUI();
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
@@ -51,4 +65,74 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
|
||||
{
|
||||
ViewModel?.ClashConnectionClose(false);
|
||||
}
|
||||
|
||||
#region UI
|
||||
|
||||
private void RestoreUI()
|
||||
{
|
||||
try
|
||||
{
|
||||
var lvColumnItem = _config.ClashUIItem?.ConnectionsColumnItem?.OrderBy(t => t.Index).ToList();
|
||||
if (lvColumnItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var displayIndex = 0;
|
||||
foreach (var item in lvColumnItem)
|
||||
{
|
||||
foreach (var item2 in lstConnections.Columns)
|
||||
{
|
||||
if (item2.Tag == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item2.Tag.Equals(item.Name))
|
||||
{
|
||||
if (item.Width < 0)
|
||||
{
|
||||
item2.IsVisible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
|
||||
item2.DisplayIndex = displayIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void StorageUI()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<ColumnItem> lvColumnItem = new();
|
||||
foreach (var item2 in lstConnections.Columns)
|
||||
{
|
||||
if (item2.Tag == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
lvColumnItem.Add(new()
|
||||
{
|
||||
Name = (string)item2.Tag,
|
||||
Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1),
|
||||
Index = item2.DisplayIndex
|
||||
});
|
||||
}
|
||||
_config.ClashUIItem.ConnectionsColumnItem = lvColumnItem;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion UI
|
||||
}
|
||||
|
||||
@@ -61,7 +61,15 @@
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
IsEditable="True" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbDomesticDNSTips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
@@ -75,6 +83,7 @@
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
IsEditable="True" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
@@ -83,7 +92,7 @@
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbRemoteDNSTips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
@@ -96,6 +105,7 @@
|
||||
Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
IsEditable="True" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
@@ -106,59 +116,76 @@
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbXrayFreedomStrategy}" />
|
||||
Text="{x:Static resx:ResUI.TbDirectResolveStrategy}" />
|
||||
<ComboBox
|
||||
x:Name="cmbRayFreedomDNSStrategy"
|
||||
Grid.Row="4"
|
||||
x:Name="cmbDirectDNSStrategy"
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderText="Default" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSBDirectResolveStrategy}" />
|
||||
<ComboBox
|
||||
x:Name="cmbSBDirectDNSStrategy"
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
PlaceholderText="Default" />
|
||||
Text="{x:Static resx:ResUI.TbDirectResolveStrategyTips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSBRemoteResolveStrategy}" />
|
||||
Text="{x:Static resx:ResUI.TbRemoteResolveStrategy}" />
|
||||
<ComboBox
|
||||
x:Name="cmbSBRemoteDNSStrategy"
|
||||
x:Name="cmbRemoteDNSStrategy"
|
||||
Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderText="Default" />
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbRemoteResolveStrategyTips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
|
||||
Text="{x:Static resx:ResUI.TbParallelQuery}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togAddCommonHosts"
|
||||
x:Name="togParallelQuery"
|
||||
Grid.Row="7"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="8"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbServeStale}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togServeStale"
|
||||
Grid.Row="8"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
@@ -169,7 +196,7 @@
|
||||
x:Name="gridAdvancedDNSSettings"
|
||||
Margin="{StaticResource Margin8}"
|
||||
ColumnDefinitions="Auto,Auto,*"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,*">
|
||||
|
||||
<TextBlock
|
||||
x:Name="txtAdvancedDNSSettingsInvalid"
|
||||
@@ -190,22 +217,38 @@
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbFakeIP}" />
|
||||
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togFakeIP"
|
||||
x:Name="togAddCommonHosts"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbFakeIP}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togFakeIP"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
@@ -213,19 +256,20 @@
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbBlockSVCBHTTPSQueries}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togBlockBindingQuery"
|
||||
Grid.Row="3"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Row="4"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
@@ -233,20 +277,21 @@
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
|
||||
<ComboBox
|
||||
x:Name="cmbDirectExpectedIPs"
|
||||
Grid.Row="4"
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
IsEditable="True" />
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Row="5"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
@@ -254,7 +299,7 @@
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="{StaticResource Margin4}"
|
||||
@@ -263,7 +308,7 @@
|
||||
|
||||
<TextBox
|
||||
x:Name="txtHosts"
|
||||
Grid.Row="6"
|
||||
Grid.Row="7"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="{StaticResource Margin4}"
|
||||
|
||||
@@ -15,16 +15,15 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
|
||||
btnCancel.Click += (s, e) => Close();
|
||||
ViewModel = new DNSSettingViewModel(UpdateViewHandler);
|
||||
|
||||
cmbRayFreedomDNSStrategy.ItemsSource = Global.DomainStrategy4Freedoms;
|
||||
cmbSBDirectDNSStrategy.ItemsSource = Global.SingboxDomainStrategy4Out;
|
||||
cmbSBRemoteDNSStrategy.ItemsSource = Global.SingboxDomainStrategy4Out;
|
||||
cmbDirectDNSStrategy.ItemsSource = Global.DomainStrategy;
|
||||
cmbRemoteDNSStrategy.ItemsSource = Global.DomainStrategy;
|
||||
cmbDirectDNS.ItemsSource = Global.DomainDirectDNSAddress;
|
||||
cmbRemoteDNS.ItemsSource = Global.DomainRemoteDNSAddress;
|
||||
cmbBootstrapDNS.ItemsSource = Global.DomainPureIPDNSAddress;
|
||||
cmbDirectExpectedIPs.ItemsSource = Global.ExpectedIPs;
|
||||
|
||||
cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy4Freedoms;
|
||||
cmbdomainStrategy4OutCompatible.ItemsSource = Global.SingboxDomainStrategy4Out;
|
||||
cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy;
|
||||
cmbdomainStrategy4OutCompatible.ItemsSource = Global.DomainStrategies4Sbox;
|
||||
cmbdomainDNSAddressCompatible.ItemsSource = Global.DomainPureIPDNSAddress;
|
||||
cmbdomainDNSAddress2Compatible.ItemsSource = Global.DomainPureIPDNSAddress;
|
||||
|
||||
@@ -37,11 +36,12 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
|
||||
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.RayStrategy4Freedom, v => v.cmbRayFreedomDNSStrategy.SelectedItem).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SingboxStrategy4Direct, v => v.cmbSBDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SingboxStrategy4Proxy, v => v.cmbSBRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Strategy4Freedom, v => v.cmbDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
Title="v2rayN"
|
||||
Width="1200"
|
||||
Height="800"
|
||||
MinWidth="900"
|
||||
MinWidth="600"
|
||||
x:DataType="vms:MainWindowViewModel"
|
||||
Icon="/Assets/NotifyIcon1.ico"
|
||||
ShowInTaskbar="True"
|
||||
@@ -43,11 +43,11 @@
|
||||
<MenuItem x:Name="menuAddVlessServer" Header="{x:Static resx:ResUI.menuAddVlessServer}" />
|
||||
<MenuItem x:Name="menuAddShadowsocksServer" Header="{x:Static resx:ResUI.menuAddShadowsocksServer}" />
|
||||
<MenuItem x:Name="menuAddTrojanServer" Header="{x:Static resx:ResUI.menuAddTrojanServer}" />
|
||||
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
|
||||
<MenuItem x:Name="menuAddWireguardServer" Header="{x:Static resx:ResUI.menuAddWireguardServer}" />
|
||||
<MenuItem x:Name="menuAddSocksServer" Header="{x:Static resx:ResUI.menuAddSocksServer}" />
|
||||
<MenuItem x:Name="menuAddHttpServer" Header="{x:Static resx:ResUI.menuAddHttpServer}" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
|
||||
<MenuItem x:Name="menuAddTuicServer" Header="{x:Static resx:ResUI.menuAddTuicServer}" />
|
||||
<MenuItem x:Name="menuAddAnytlsServer" Header="{x:Static resx:ResUI.menuAddAnytlsServer}" />
|
||||
</MenuItem>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user