Compare commits

..

1 Commits

Author SHA1 Message Date
6b8b2d0b1b Make all build scripts executable 2025-10-24 13:36:52 +03:00
235 changed files with 3063 additions and 6468 deletions

View File

@@ -3,18 +3,6 @@ description: 在提出问题前请先自行排除服务器端问题和升级到
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
### 报告 Bug 前请务必确认以下事项:
> ** **
> **✓ 已自行排除服务器端问题。**
> **✓ 已升级到最新客户端版本。**
> **✓ 已通过搜索确认没有人提出过相同问题。**
> **✓ 已确认自己的电脑系统环境是受支持的。**
---
- type: input
id: "os-version"
attributes:
@@ -22,7 +10,6 @@ body:
description: "操作系统和版本"
validations:
required: true
- type: input
id: "expectation"
attributes:
@@ -30,7 +17,6 @@ body:
description: "描述你认为应该发生什么"
validations:
required: true
- type: textarea
id: "describe-the-bug"
attributes:
@@ -38,34 +24,22 @@ body:
description: "描述实际发生了什么"
validations:
required: true
- type: textarea
id: "reproduction-method"
attributes:
label: "复现方法"
description: "在BUG出现前执行了哪些操作"
placeholder: "标序号"
placeholder: 标序号
validations:
required: true
- type: textarea
id: "gui-log"
id: "log"
attributes:
label: "软件日志"
label: "日志信息"
description: "位置在软件当前目录下的guiLogs"
placeholder: "在日志开始和结束位置粘贴冒号后的内容到这:"
placeholder: 在日志开始和结束位置粘贴冒号后的内容```
validations:
required: true
- type: textarea
id: "core-log"
attributes:
label: "内核日志"
description: "位置在软件主界面的信息框内"
placeholder: "在信息框内鼠标右键复制全部信息粘贴在这:"
validations:
required: true
- type: textarea
id: "more"
attributes:
@@ -73,7 +47,6 @@ body:
description: "可选"
validations:
required: false
- type: checkboxes
id: "latest-version"
attributes:
@@ -82,7 +55,6 @@ body:
options:
- label:
required: true
- type: checkboxes
id: "issues"
attributes:
@@ -91,12 +63,3 @@ body:
options:
- label:
required: true
- type: checkboxes
id: "system-version"
attributes:
label: "我确认系统版本是受支持的"
description: "否则请切换后尝试"
options:
- label:
required: true

View File

@@ -1,9 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Discussions / 讨论区
url: https://github.com/2dust/v2rayN/discussions
about: 使用问题或需要帮助请前往 Discussions。
- name: Wiki / 使用说明
url: https://github.com/2dust/v2rayN/wiki
about: 查看常见问题和使用文档。

View File

@@ -9,12 +9,6 @@ on:
push:
branches:
- master
tags:
- 'v*'
- 'V*'
permissions:
contents: write
env:
OutputArch: "linux-64"
@@ -27,30 +21,31 @@ jobs:
strategy:
matrix:
configuration: [Release]
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6.0.0
uses: actions/checkout@v5.0.0
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup .NET
uses: actions/setup-dotnet@v5.0.1
- name: Setup
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: '8.0.x'
- name: Build
run: |
cd v2rayN
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64 -p:SelfContained=true -o "$OutputPath64"
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 -p:SelfContained=true -o "$OutputPathArm64"
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-x64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPath64"
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
cd v2rayN
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64 --self-contained=true -o $OutputPath64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 --self-contained=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-x64 --self-contained=true -p:PublishTrimmed=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v4.6.2
with:
name: v2rayN-linux
path: |
@@ -61,8 +56,8 @@ jobs:
if: github.event.inputs.release_tag != ''
run: |
chmod 755 package-debian.sh
./package-debian.sh "$OutputArch" "$OutputPath64" "${{ github.event.inputs.release_tag }}"
./package-debian.sh "$OutputArchArm" "$OutputPathArm64" "${{ github.event.inputs.release_tag }}"
./package-debian.sh $OutputArch $OutputPath64 ${{ github.event.inputs.release_tag }}
./package-debian.sh $OutputArchArm $OutputPathArm64 ${{ github.event.inputs.release_tag }}
- name: Upload deb to release
uses: svenstaro/upload-release-action@v2
@@ -73,13 +68,28 @@ jobs:
file_glob: true
prerelease: true
- name: Package AppImage
if: github.event.inputs.release_tag != ''
run: |
chmod a+x package-appimage.sh
./package-appimage.sh
- name: Upload AppImage to release
uses: svenstaro/upload-release-action@v2
if: github.event.inputs.release_tag != ''
with:
file: ${{ github.workspace }}/v2rayN*.AppImage
tag: ${{ github.event.inputs.release_tag }}
file_glob: true
prerelease: true
# release zip archive
- name: Package release zip archive
if: github.event.inputs.release_tag != ''
run: |
chmod 755 package-release-zip.sh
./package-release-zip.sh "$OutputArch" "$OutputPath64"
./package-release-zip.sh "$OutputArchArm" "$OutputPathArm64"
./package-release-zip.sh $OutputArch $OutputPath64
./package-release-zip.sh $OutputArchArm $OutputPathArm64
- name: Upload zip archive to release
uses: svenstaro/upload-release-action@v2
@@ -90,62 +100,36 @@ jobs:
file_glob: true
prerelease: true
rpm:
needs: build
if: |
(github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag != '') ||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
runs-on: ubuntu-24.04
container:
image: registry.access.redhat.com/ubi10/ubi
env:
RELEASE_TAG: ${{ github.event.inputs.release_tag != '' && github.event.inputs.release_tag || github.ref_name }}
steps:
- name: Prepare tools (Red Hat)
run: |
dnf repolist all
dnf -y makecache
dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
dnf -y install sudo git rpm-build rpmdevtools dnf-plugins-core rsync findutils tar gzip unzip which
- name: Checkout repo (for scripts)
uses: actions/checkout@v6.0.0
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Restore build artifacts
uses: actions/download-artifact@v6
with:
name: v2rayN-linux
path: ${{ github.workspace }}/v2rayN/Release
- name: Ensure script permissions
run: chmod 755 package-rhel.sh
# release RHEL package
- name: Package RPM (RHEL-family)
run: ./package-rhel.sh "${RELEASE_TAG}" --arch all
if: github.event.inputs.release_tag != ''
run: |
chmod 755 package-rhel.sh
# Build for both x86_64 and aarch64 in one go (explicit version passed; no --buildfrom)
./package-rhel.sh "${{ github.event.inputs.release_tag }}" --arch all
- name: Collect RPMs into workspace
if: github.event.inputs.release_tag != ''
run: |
mkdir -p "$GITHUB_WORKSPACE/dist/rpm"
rsync -av "$HOME/rpmbuild/RPMS/" "$GITHUB_WORKSPACE/dist/rpm/" || true
find "$GITHUB_WORKSPACE/dist/rpm" -name "v2rayN-*-1*.x86_64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm/v2rayN-linux-rhel-64.rpm" \; || true
find "$GITHUB_WORKSPACE/dist/rpm" -name "v2rayN-*-1*.aarch64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
echo "==== Dist tree ===="
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
mkdir -p "${{ github.workspace }}/dist/rpm"
rsync -av "$HOME/rpmbuild/RPMS/" "${{ github.workspace }}/dist/rpm/"
# Rename to requested filenames
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.x86_64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-x64.rpm" \; || true
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.aarch64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
- name: Upload RPM artifacts
uses: actions/upload-artifact@v5.0.0
if: github.event.inputs.release_tag != ''
uses: actions/upload-artifact@v4.6.2
with:
name: v2rayN-rpm
path: dist/rpm/**/*.rpm
path: |
${{ github.workspace }}/dist/rpm/**/*.rpm
- name: Upload RPMs to release
uses: svenstaro/upload-release-action@v2
if: github.event.inputs.release_tag != ''
with:
file: dist/rpm/**/*.rpm
tag: ${{ env.RELEASE_TAG }}
file: ${{ github.workspace }}/dist/rpm/**/*.rpm
tag: ${{ github.event.inputs.release_tag }}
file_glob: true
prerelease: true

View File

@@ -26,26 +26,26 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6.0.0
uses: actions/checkout@v5.0.0
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v5.0.1
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: '8.0.x'
- name: Build
run: |
cd v2rayN
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64 -p:SelfContained=true -o $OutputPath64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 -p:SelfContained=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-x64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64 --self-contained=true -o $OutputPath64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 --self-contained=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-x64 --self-contained=true -p:PublishTrimmed=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v4.6.2
with:
name: v2rayN-macos
path: |

View File

@@ -26,26 +26,26 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6.0.0
uses: actions/checkout@v5.0.0
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v5.0.1
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: '8.0.x'
- name: Build
run: |
cd v2rayN
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -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
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v4.6.2
with:
name: v2rayN-windows-desktop
path: |

View File

@@ -27,26 +27,26 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6.0.0
uses: actions/checkout@v5.0.0
- name: Setup
uses: actions/setup-dotnet@v5.0.1
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: '8.0.x'
- name: Build
run: |
cd v2rayN
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -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 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v4.6.2
with:
name: v2rayN-windows
path: |
@@ -68,4 +68,4 @@ jobs:
file: ${{ github.workspace }}/v2rayN*.zip
tag: ${{ github.event.inputs.release_tag }}
file_glob: true
prerelease: true
prerelease: true

67
package-appimage.sh Executable file
View File

@@ -0,0 +1,67 @@
#!/bin/bash
set -euo pipefail
# Install deps
sudo apt update -y
sudo apt install -y libfuse2 wget file
# Get tools
wget -qO appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool
# x86_64 AppDir
APPDIR_X64="AppDir-x86_64"
rm -rf "$APPDIR_X64"
mkdir -p "$APPDIR_X64/usr/lib/v2rayN" "$APPDIR_X64/usr/bin" "$APPDIR_X64/usr/share/applications" "$APPDIR_X64/usr/share/pixmaps"
cp -rf "$OutputPath64"/* "$APPDIR_X64/usr/lib/v2rayN" || true
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/usr/share/pixmaps/v2rayN.png" || true
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/v2rayN.png" || true
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_X64/AppRun"
chmod +x "$APPDIR_X64/AppRun"
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_X64/usr/bin/v2rayN"
cat > "$APPDIR_X64/v2rayN.desktop" <<EOF
[Desktop Entry]
Name=v2rayN
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
Exec=v2rayN
Icon=v2rayN
Terminal=false
Type=Application
Categories=Network;
EOF
install -Dm644 "$APPDIR_X64/v2rayN.desktop" "$APPDIR_X64/usr/share/applications/v2rayN.desktop"
ARCH=x86_64 ./appimagetool "$APPDIR_X64" "v2rayN-${OutputArch}.AppImage"
file "v2rayN-${OutputArch}.AppImage" | grep -q 'x86-64'
# aarch64 AppDir
APPDIR_ARM64="AppDir-aarch64"
rm -rf "$APPDIR_ARM64"
mkdir -p "$APPDIR_ARM64/usr/lib/v2rayN" "$APPDIR_ARM64/usr/bin" "$APPDIR_ARM64/usr/share/applications" "$APPDIR_ARM64/usr/share/pixmaps"
cp -rf "$OutputPathArm64"/* "$APPDIR_ARM64/usr/lib/v2rayN" || true
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/usr/share/pixmaps/v2rayN.png" || true
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/v2rayN.png" || true
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_ARM64/AppRun"
chmod +x "$APPDIR_ARM64/AppRun"
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_ARM64/usr/bin/v2rayN"
cat > "$APPDIR_ARM64/v2rayN.desktop" <<EOF
[Desktop Entry]
Name=v2rayN
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
Exec=v2rayN
Icon=v2rayN
Terminal=false
Type=Application
Categories=Network;
EOF
install -Dm644 "$APPDIR_ARM64/v2rayN.desktop" "$APPDIR_ARM64/usr/share/applications/v2rayN.desktop"
# aarch64 runtime
wget -qO runtime-aarch64 https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-aarch64
chmod +x runtime-aarch64
# build aarch64 AppImage
ARCH=aarch64 ./appimagetool --runtime-file ./runtime-aarch64 "$APPDIR_ARM64" "v2rayN-${OutputArchArm}.AppImage"
file "v2rayN-${OutputArchArm}.AppImage" | grep -q 'ARM aarch64'

2
package-debian.sh Normal file → Executable file
View File

@@ -28,7 +28,7 @@ Package: v2rayN
Version: $Version
Architecture: $Arch2
Maintainer: https://github.com/2dust/v2rayN
Depends: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1), libfreetype6 (>= 2.11)
Depends: desktop-file-utils, xdg-utils
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
EOF

View File

@@ -43,8 +43,6 @@ cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
<true/>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>12.7</string>
</dict>
</plist>
EOF
@@ -57,4 +55,4 @@ create-dmg \
--hide-extension "v2rayN.app" \
--app-drop-link 500 185 \
"v2rayN-${Arch}.dmg" \
"$PackagePath/v2rayN.app"
"$PackagePath/v2rayN.app"

0
package-release-zip.sh Normal file → Executable file
View File

29
package-rhel.sh Normal file → Executable file
View File

@@ -19,23 +19,6 @@ else
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 "[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 =========================================================
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
WITH_CORE="both" # Default: bundle both xray+sing-box
@@ -458,7 +441,7 @@ download_geo_assets() {
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
done
for f in \
geosite-cn.srs geosite-gfw.srs geosite-google.srs geosite-greatfire.srs \
geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
curl -fsSL -o "$srss_dir/$f" \
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
@@ -631,14 +614,8 @@ ExclusiveArch: aarch64 x86_64
Source0: __PKGROOT__.tar.gz
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
Requires: glibc >= 2.34
Requires: fontconfig >= 2.13.1
Requires: desktop-file-utils >= 0.26
Requires: xdg-utils >= 1.1.3
Requires: coreutils >= 8.32
Requires: bash >= 5.1
Requires: freetype >= 2.10
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL, xdg-utils
%description
v2rayN Linux for Red Hat Enterprise Linux

View File

@@ -1,14 +1,14 @@
<Project>
<PropertyGroup>
<Version>7.16.5</Version>
<Version>7.15.4</Version>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<NoWarn>CA1031;CS1591;NU1507;CA1416;IDE0058;IDE0053;IDE0200</NoWarn>
<NoWarn>CA1031;CS1591;NU1507;CA1416;IDE0058</NoWarn>
<Nullable>annotations</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Authors>2dust</Authors>

View File

@@ -6,23 +6,23 @@
</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="ReactiveUI.Avalonia" Version="11.3.8" />
<PackageVersion Include="CliWrap" Version="3.10.0" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.7" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.7" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.7" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.7" />
<PackageVersion Include="CliWrap" Version="3.9.0" />
<PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
<PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="ReactiveUI" Version="22.2.1" />
<PackageVersion Include="ReactiveUI" Version="20.4.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="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.7" />
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.1" />
<PackageVersion Include="NLog" Version="6.0.6" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7" />
<PackageVersion Include="NLog" Version="6.0.5" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" />

View File

@@ -1,3 +1,5 @@
using ReactiveUI;
namespace ServiceLib.Base;
public class MyReactiveObject : ReactiveObject

View File

@@ -1,3 +1,6 @@
using System.Collections.Concurrent;
using System.Reflection;
namespace ServiceLib.Common;
public static class EmbedUtils

View File

@@ -1,9 +1,10 @@
using System.Formats.Tar;
using System.IO.Compression;
using System.Text;
namespace ServiceLib.Common;
public static class FileUtils
public static class FileManager
{
private static readonly string _tag = "FileManager";

View File

@@ -1,3 +1,8 @@
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace ServiceLib.Common;
public class JsonUtils
@@ -35,13 +40,9 @@ public class JsonUtils
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static T? DeepCopy<T>(T? obj)
public static T DeepCopy<T>(T obj)
{
if (obj is null)
{
return default;
}
return Deserialize<T>(Serialize(obj, false));
return Deserialize<T>(Serialize(obj, false))!;
}
/// <summary>
@@ -71,7 +72,7 @@ public class JsonUtils
/// </summary>
/// <param name="strJson"></param>
/// <returns></returns>
public static JsonNode? ParseJson(string? strJson)
public static JsonNode? ParseJson(string strJson)
{
try
{
@@ -120,7 +121,7 @@ public class JsonUtils
/// <param name="obj"></param>
/// <param name="options"></param>
/// <returns></returns>
public static string Serialize(object? obj, JsonSerializerOptions? options)
public static string Serialize(object? obj, JsonSerializerOptions options)
{
var result = string.Empty;
try
@@ -129,7 +130,7 @@ public class JsonUtils
{
return result;
}
result = JsonSerializer.Serialize(obj, options ?? _defaultSerializeOptions);
result = JsonSerializer.Serialize(obj, options);
}
catch (Exception ex)
{

View File

@@ -1,3 +1,5 @@
using System.Diagnostics;
namespace ServiceLib.Common;
public static class ProcUtils

View File

@@ -1,4 +1,4 @@
namespace ServiceLib.Models;
namespace ServiceLib.Common;
public class SemanticVersion
{

View File

@@ -1,5 +1,13 @@
using System.Collections.Specialized;
using System.Diagnostics;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using CliWrap;
using CliWrap.Buffered;
@@ -9,7 +17,7 @@ public class Utils
{
private static readonly string _tag = "Utils";
#region Conversion Functions
#region
/// <summary>
/// Convert to comma-separated string
@@ -306,10 +314,7 @@ public class Utils
public static bool IsBase64String(string? plainText)
{
if (plainText.IsNullOrEmpty())
{
return false;
}
var buffer = new Span<byte>(new byte[plainText.Length]);
return Convert.TryFromBase64String(plainText, buffer, out var _);
}
@@ -358,113 +363,9 @@ public class Utils
return userHostsMap;
}
/// <summary>
/// Parse a possibly non-standard URL into scheme, domain, port, and path.
/// If parsing fails, the entire input is returned as domain, and others are empty or zero.
/// </summary>
/// <param name="url">Input URL or string</param>
/// <returns>(domain, scheme, port, path)</returns>
public static (string domain, string scheme, int port, string path) ParseUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return ("", "", 0, "");
}
#endregion
// 1. First, try to parse using the standard Uri class.
if (Uri.TryCreate(url, UriKind.Absolute, out var uri) && !string.IsNullOrEmpty(uri.Host))
{
var scheme = uri.Scheme;
var domain = uri.Host;
var port = uri.IsDefaultPort ? 0 : uri.Port;
var path = uri.PathAndQuery;
return (domain, scheme, port, path);
}
// 2. Try to handle more general cases with a regular expression, including non-standard schemes.
// This regex captures the scheme (optional), authority (host+port), and path (optional).
var match = Regex.Match(url, @"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):/{2,})?([^/?#]+)([^?#]*)?.*$");
if (match.Success)
{
var scheme = match.Groups[1].Value;
var authority = match.Groups[2].Value;
var path = match.Groups[3].Value;
// Remove userinfo from the authority part.
var atIndex = authority.LastIndexOf('@');
if (atIndex > 0)
{
authority = authority.Substring(atIndex + 1);
}
var (domain, port) = ParseAuthority(authority);
// If the parsed domain is empty, it means the authority part is malformed, so trigger the fallback.
if (!string.IsNullOrEmpty(domain))
{
return (domain, scheme, port, path);
}
}
// 3. If all of the above fails, execute the final fallback strategy.
return (url, "", 0, "");
}
/// <summary>
/// Helper function to parse domain and port from the authority part, with correct handling for IPv6.
/// </summary>
private static (string domain, int port) ParseAuthority(string authority)
{
if (string.IsNullOrEmpty(authority))
{
return ("", 0);
}
var port = 0;
var domain = authority;
// Handle IPv6 addresses, e.g., "[2001:db8::1]:443"
if (authority.StartsWith('[') && authority.Contains(']'))
{
var closingBracketIndex = authority.LastIndexOf(']');
if (closingBracketIndex < authority.Length - 1 && authority[closingBracketIndex + 1] == ':')
{
// Port exists
var portStr = authority.Substring(closingBracketIndex + 2);
if (int.TryParse(portStr, out var portNum))
{
port = portNum;
}
domain = authority.Substring(0, closingBracketIndex + 1);
}
else
{
// No port
domain = authority;
}
}
else // Handle IPv4 or domain names
{
var lastColonIndex = authority.LastIndexOf(':');
// Ensure there are digits after the colon and that this colon is not part of an IPv6 address.
if (lastColonIndex > 0 && lastColonIndex < authority.Length - 1 && authority.Substring(lastColonIndex + 1).All(char.IsDigit))
{
var portStr = authority.Substring(lastColonIndex + 1);
if (int.TryParse(portStr, out var portNum))
{
port = portNum;
domain = authority.Substring(0, lastColonIndex);
}
}
}
return (domain, port);
}
#endregion Conversion Functions
#region Data Checks
#region
/// <summary>
/// Determine if the input is a number
@@ -523,62 +424,40 @@ public class Utils
{
// Loopback address check (127.0.0.1 for IPv4, ::1 for IPv6)
if (IPAddress.IsLoopback(address))
{
return true;
}
var ipBytes = address.GetAddressBytes();
if (address.AddressFamily == AddressFamily.InterNetwork)
{
// IPv4 private address check
if (ipBytes[0] == 10)
{
return true;
}
if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31)
{
return true;
}
if (ipBytes[0] == 192 && ipBytes[1] == 168)
{
return true;
}
}
else if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
// IPv6 private address check
// Link-local address fe80::/10
if (ipBytes[0] == 0xfe && (ipBytes[1] & 0xc0) == 0x80)
{
return true;
}
// Unique local address fc00::/7 (typically fd00::/8)
if ((ipBytes[0] & 0xfe) == 0xfc)
{
return true;
}
// Private portion in IPv4-mapped addresses ::ffff:0:0/96
if (address.IsIPv4MappedToIPv6)
{
var ipv4Bytes = ipBytes.Skip(12).ToArray();
if (ipv4Bytes[0] == 10)
{
return true;
}
if (ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31)
{
return true;
}
if (ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168)
{
return true;
}
}
}
}
@@ -586,9 +465,9 @@ public class Utils
return false;
}
#endregion Data Checks
#endregion
#region Speed Test
#region
private static bool PortInUse(int port)
{
@@ -641,9 +520,9 @@ public class Utils
return 59090;
}
#endregion Speed Test
#endregion
#region Miscellaneous
#region
public static bool UpgradeAppExists(out string upgradeFileName)
{
@@ -733,16 +612,10 @@ public class Utils
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]);
}
}
@@ -793,7 +666,7 @@ public class Utils
return null;
}
#endregion Miscellaneous
#endregion
#region TempPath
@@ -994,13 +867,13 @@ public class Utils
#region Platform
public static bool IsWindows() => OperatingSystem.IsWindows();
public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static bool IsLinux() => OperatingSystem.IsLinux();
public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
public static bool IsMacOS() => OperatingSystem.IsMacOS();
public static bool IsOSX() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
public static bool IsNonWindows() => !OperatingSystem.IsWindows();
public static bool IsNonWindows() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static string GetExeName(string name)
{
@@ -1020,11 +893,16 @@ public class Utils
{
try
{
if (IsWindows() || IsMacOS())
if (IsWindows() || IsOSX())
{
return false;
}
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE")))
{
return true;
}
var exePath = GetExePath();
var baseDir = string.IsNullOrEmpty(exePath) ? StartupPath() : Path.GetDirectoryName(exePath) ?? "";
var p = baseDir.Replace('\\', '/');
@@ -1034,6 +912,11 @@ public class Utils
return false;
}
if (p.Contains("/.mount_", StringComparison.Ordinal))
{
return true;
}
if (p.StartsWith("/opt/v2rayN", StringComparison.OrdinalIgnoreCase))
{
return true;

View File

@@ -0,0 +1,180 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ServiceLib.Common;
/*
* See:
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
*/
public sealed class WindowsJob : IDisposable
{
private IntPtr handle = IntPtr.Zero;
public WindowsJob()
{
handle = CreateJobObject(IntPtr.Zero, null);
var extendedInfoPtr = IntPtr.Zero;
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
{
LimitFlags = 0x2000
};
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
BasicLimitInformation = info
};
try
{
var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
(uint)length))
{
throw new Exception(string.Format("Unable to set information. Error: {0}",
Marshal.GetLastWin32Error()));
}
}
finally
{
if (extendedInfoPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(extendedInfoPtr);
}
}
}
public bool AddProcess(IntPtr processHandle)
{
var succ = AssignProcessToJobObject(handle, processHandle);
if (!succ)
{
Logging.SaveLog("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error());
}
return succ;
}
public bool AddProcess(int processId)
{
return AddProcess(Process.GetProcessById(processId).Handle);
}
#region IDisposable
private bool disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposed)
{
return;
}
disposed = true;
if (disposing)
{
// no managed objects to free
}
if (handle != IntPtr.Zero)
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
}
~WindowsJob()
{
Dispose(false);
}
#endregion IDisposable
#region Interop
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr CreateJobObject(IntPtr a, string? lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
#endregion Interop
}
#region Helper classes
[StructLayout(LayoutKind.Sequential)]
internal struct IO_COUNTERS
{
public ulong ReadOperationCount;
public ulong WriteOperationCount;
public ulong OtherOperationCount;
public ulong ReadTransferCount;
public ulong WriteTransferCount;
public ulong OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public long PerProcessUserTimeLimit;
public long PerJobUserTimeLimit;
public uint LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public uint ActiveProcessLimit;
public UIntPtr Affinity;
public uint PriorityClass;
public uint SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
#endregion Helper classes

View File

@@ -1,3 +1,5 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.Win32;
namespace ServiceLib.Common;

View File

@@ -5,6 +5,5 @@ public enum ESpeedActionType
Tcping,
Realping,
Speedtest,
Mixedtest,
FastRealping
Mixedtest
}

View File

@@ -1,3 +1,5 @@
using System.Reactive;
namespace ServiceLib.Events;
public static class AppEvents

View File

@@ -1,3 +1,5 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace ServiceLib.Events;

View File

@@ -73,7 +73,6 @@ 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";
@@ -317,7 +316,7 @@ public class Global
];
public static readonly HashSet<EConfigType> SingboxOnlyConfigType = SingboxSupportConfigType.Except(XraySupportConfigType).ToHashSet();
public static readonly List<string> DomainStrategies =
[
AsIs,
@@ -428,7 +427,6 @@ public class Global
"zh-Hant",
"en",
"fa-Ir",
"fr",
"ru",
"hu"
];

View File

@@ -1,36 +1,14 @@
global using System.Collections.Concurrent;
global using System.Diagnostics;
global using System.Net;
global using System.Net.NetworkInformation;
global using System.Net.Sockets;
global using System.Reactive;
global using System.Reactive.Disposables;
global using System.Reactive.Linq;
global using System.Reflection;
global using System.Runtime.InteropServices;
global using System.Security.Cryptography;
global using System.Text;
global using System.Text.Encodings.Web;
global using System.Text.Json;
global using System.Text.Json.Nodes;
global using System.Text.Json.Serialization;
global using System.Text.RegularExpressions;
global using DynamicData;
global using DynamicData.Binding;
global using ReactiveUI;
global using ReactiveUI.Fody.Helpers;
global using ServiceLib.Base;
global using ServiceLib.Common;
global using ServiceLib.Enums;
global using ServiceLib.Events;
global using ServiceLib.Handler;
global using ServiceLib.Handler.Fmt;
global using ServiceLib.Handler.SysProxy;
global using ServiceLib.Helper;
global using ServiceLib.Manager;
global using ServiceLib.Handler.Fmt;
global using ServiceLib.Services;
global using ServiceLib.Services.Statistics;
global using ServiceLib.Services.CoreConfig;
global using ServiceLib.Models;
global using ServiceLib.Resx;
global using ServiceLib.Services;
global using ServiceLib.Services.CoreConfig;
global using ServiceLib.Services.Statistics;
global using SQLite;
global using ServiceLib.Handler.SysProxy;

View File

@@ -1,4 +1,5 @@
using System.Security.Principal;
using System.Text.RegularExpressions;
namespace ServiceLib.Handler;
@@ -26,7 +27,7 @@ public static class AutoStartupHandler
await SetTaskLinux();
}
}
else if (Utils.IsMacOS())
else if (Utils.IsOSX())
{
await ClearTaskOSX();

View File

@@ -1,4 +1,5 @@
using System.Data;
using System.Text.RegularExpressions;
namespace ServiceLib.Handler;
@@ -97,7 +98,7 @@ public static class ConfigHandler
config.UiItem ??= new UIItem()
{
EnableUpdateSubOnlyRemarksExist = true
EnableAutoAdjustMainLvColWidth = true
};
config.UiItem.MainColumnItem ??= new();
config.UiItem.WindowSizeItem ??= new();
@@ -112,8 +113,10 @@ public static class ConfigHandler
config.ConstItem ??= new ConstItem();
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
config.SimpleDNSItem.GlobalFakeIp ??= true;
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
if (config.SimpleDNSItem.GlobalFakeIp is null)
{
config.SimpleDNSItem.GlobalFakeIp = true;
}
config.SpeedTestItem ??= new();
if (config.SpeedTestItem.SpeedTestTimeout < 10)
@@ -252,7 +255,6 @@ public static class ConfigHandler
item.Mldsa65Verify = profileItem.Mldsa65Verify;
item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled;
item.Cert = profileItem.Cert;
}
var ret = item.ConfigType switch
@@ -448,13 +450,13 @@ public static class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> MoveServer(Config config, List<ProfileItem> lstProfile, int index, EMove eMove, int pos = -1)
{
var count = lstProfile.Count;
int count = lstProfile.Count;
if (index < 0 || index > lstProfile.Count - 1)
{
return -1;
}
for (var i = 0; i < lstProfile.Count; i++)
for (int i = 0; i < lstProfile.Count; i++)
{
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
}
@@ -528,7 +530,7 @@ public static class ConfigHandler
return -1;
}
var ext = Path.GetExtension(fileName);
var newFileName = $"{Utils.GetGuid()}{ext}";
string newFileName = $"{Utils.GetGuid()}{ext}";
//newFileName = Path.Combine(Utile.GetTempPath(), newFileName);
try
@@ -1222,7 +1224,6 @@ public static class ConfigHandler
CoreType = coreType,
ConfigType = EConfigType.PolicyGroup,
Remarks = remark,
IsSub = false
};
if (!subId.IsNullOrEmpty())
{
@@ -1357,7 +1358,7 @@ public static class ConfigHandler
}
continue;
}
var profileItem = FmtHandler.ResolveConfig(str, out var msg);
var profileItem = FmtHandler.ResolveConfig(str, out string msg);
if (profileItem is null)
{
continue;
@@ -1441,7 +1442,7 @@ public static class ConfigHandler
{
await RemoveServersViaSubid(config, subid, isSub);
}
var count = 0;
int count = 0;
foreach (var it in lstProfiles)
{
it.Subid = subid;
@@ -1483,7 +1484,7 @@ public static class ConfigHandler
if (profileItem is null)
{
profileItem = Hysteria2Fmt.ResolveFull2(strData, subRemarks);
}
}
if (profileItem is null || profileItem.Address.IsNullOrEmpty())
{
return -1;
@@ -1531,7 +1532,7 @@ public static class ConfigHandler
var lstSsServer = ShadowsocksFmt.ResolveSip008(strData);
if (lstSsServer?.Count > 0)
{
var counter = 0;
int counter = 0;
foreach (var ssItem in lstSsServer)
{
ssItem.Subid = subid;
@@ -1651,9 +1652,7 @@ public static class ConfigHandler
var uri = Utils.TryUri(url);
if (uri == null)
{
return -1;
}
//Do not allow http protocol
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{
@@ -1708,7 +1707,7 @@ public static class ConfigHandler
var maxSort = 0;
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
{
var lstSubs = await AppManager.Instance.SubItems();
var lstSubs = (await AppManager.Instance.SubItems());
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
}
item.Sort = maxSort + 1;
@@ -1870,7 +1869,7 @@ public static class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> MoveRoutingRule(List<RulesItem> rules, int index, EMove eMove, int pos = -1)
{
var count = rules.Count;
int count = rules.Count;
if (index < 0 || index > rules.Count - 1)
{
return -1;
@@ -2020,15 +2019,11 @@ public static class ConfigHandler
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, "");
if (templateContent.IsNullOrEmpty())
{
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
}
var template = JsonUtils.Deserialize<RoutingTemplate>(templateContent);
if (template == null)
{
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
}
var items = await AppManager.Instance.RoutingItems();
var maxSort = items.Count;
@@ -2041,18 +2036,14 @@ public static class ConfigHandler
var item = template.RoutingItems[i];
if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty())
{
continue;
}
var ruleSetsString = !item.RuleSet.IsNullOrEmpty()
? item.RuleSet
: await downloadHandle.TryDownloadString(item.Url, true, "");
if (ruleSetsString.IsNullOrEmpty())
{
continue;
}
item.Remarks = $"{template.Version}-{item.Remarks}";
item.Enabled = true;
@@ -2080,7 +2071,7 @@ public static class ConfigHandler
/// <returns>0 if successful</returns>
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
{
var ver = "V4-";
var ver = "V3-";
var items = await AppManager.Instance.RoutingItems();
//TODO Temporary code to be removed later
@@ -2091,7 +2082,7 @@ public static class ConfigHandler
items = await AppManager.Instance.RoutingItems();
}
if (!blImportAdvancedRules && items.Count(u => u.Remarks.StartsWith(ver)) > 0)
if (!blImportAdvancedRules && items.Count > 0)
{
//migrate
//TODO Temporary code to be removed later
@@ -2248,25 +2239,17 @@ public static class ConfigHandler
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (templateContent.IsNullOrEmpty())
{
return currentItem;
}
var template = JsonUtils.Deserialize<DNSItem>(templateContent);
if (template == null)
{
return currentItem;
}
if (!template.NormalDNS.IsNullOrEmpty())
{
template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, "");
}
if (!template.TunDNS.IsNullOrEmpty())
{
template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, "");
}
template.Id = currentItem.Id;
template.Enabled = currentItem.Enabled;
@@ -2291,7 +2274,6 @@ public static class ConfigHandler
BlockBindingQuery = true,
DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(),
BootstrapDNS = Global.DomainPureIPDNSAddress.FirstOrDefault(),
};
}
@@ -2300,16 +2282,10 @@ public static class ConfigHandler
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (templateContent.IsNullOrEmpty())
{
return null;
}
var template = JsonUtils.Deserialize<SimpleDNSItem>(templateContent);
if (template == null)
{
return null;
}
return template;
}

View File

@@ -1,3 +1,5 @@
using System.Net;
namespace ServiceLib.Handler;
public static class ConnectionHandler
@@ -6,7 +8,7 @@ public static class ConnectionHandler
public static async Task<string> RunAvailabilityCheck()
{
var time = await GetRealPingTimeInfo();
var time = await GetRealPingTime();
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
return string.Format(ResUI.TestMeOutput, time, ip);
@@ -39,7 +41,7 @@ public static class ConnectionHandler
return $"({country ?? "unknown"}) {ip}";
}
private static async Task<int> GetRealPingTimeInfo()
private static async Task<int> GetRealPingTime()
{
var responseTime = -1;
try
@@ -50,7 +52,7 @@ public static class ConnectionHandler
for (var i = 0; i < 2; i++)
{
responseTime = await GetRealPingTime(url, webProxy, 10);
responseTime = await HttpClientHelper.Instance.GetRealPingTime(url, webProxy, 10);
if (responseTime > 0)
{
break;
@@ -65,34 +67,4 @@ public static class ConnectionHandler
}
return responseTime;
}
public static async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
{
var responseTime = -1;
try
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
using var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null
});
List<int> oneTime = new();
for (var i = 0; i < 2; i++)
{
var timer = Stopwatch.StartNew();
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
timer.Stop();
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
await Task.Delay(100);
}
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
}
catch
{
}
return responseTime;
}
}

View File

@@ -58,7 +58,7 @@ public static class CoreConfigHandler
File.Delete(fileName);
}
var addressFileName = node.Address;
string addressFileName = node.Address;
if (!File.Exists(addressFileName))
{
addressFileName = Utils.GetConfigPath(addressFileName);

View File

@@ -23,7 +23,7 @@ public class AnytlsFmt : BaseFmt
item.Id = rawUserInfo;
var query = Utils.ParseQueryString(parsedUrl.Query);
ResolveUriQuery(query, ref item);
_ = ResolveStdTransport(query, ref item);
return item;
}
@@ -41,7 +41,7 @@ public class AnytlsFmt : BaseFmt
}
var pw = item.Id;
var dicQuery = new Dictionary<string, string>();
ToUriQuery(item, Global.None, ref dicQuery);
_ = GetStdTransport(item, Global.None, ref dicQuery);
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
}

View File

@@ -4,8 +4,6 @@ namespace ServiceLib.Handler.Fmt;
public class BaseFmt
{
private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure" };
protected static string GetIpv6(string address)
{
if (Utils.IsIpv6(address))
@@ -19,7 +17,7 @@ public class BaseFmt
}
}
protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
{
if (item.Flow.IsNotEmpty())
{
@@ -39,7 +37,11 @@ public class BaseFmt
}
if (item.Sni.IsNotEmpty())
{
dicQuery.Add("sni", Utils.UrlEncode(item.Sni));
dicQuery.Add("sni", item.Sni);
}
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
if (item.Fingerprint.IsNotEmpty())
{
@@ -61,14 +63,9 @@ public class BaseFmt
{
dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify));
}
if (item.StreamSecurity.Equals(Global.StreamSecurity))
if (item.AllowInsecure.Equals("true"))
{
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
ToUriQueryAllowInsecure(item, ref dicQuery);
dicQuery.Add("allowInsecure", "1");
}
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
@@ -118,16 +115,7 @@ public class BaseFmt
}
if (item.Extra.IsNotEmpty())
{
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));
dicQuery.Add("extra", Utils.UrlEncode(item.Extra));
}
break;
@@ -165,40 +153,7 @@ public class BaseFmt
return 0;
}
protected static int ToUriQueryLite(ProfileItem item, ref Dictionary<string, string> dicQuery)
{
if (item.Sni.IsNotEmpty())
{
dicQuery.Add("sni", Utils.UrlEncode(item.Sni));
}
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
ToUriQueryAllowInsecure(item, ref dicQuery);
return 0;
}
private static int ToUriQueryAllowInsecure(ProfileItem item, ref Dictionary<string, string> dicQuery)
{
if (item.AllowInsecure.Equals(Global.AllowInsecure.First()))
{
// Add two for compatibility
dicQuery.Add("insecure", "1");
dicQuery.Add("allowInsecure", "1");
}
else
{
dicQuery.Add("insecure", "0");
dicQuery.Add("allowInsecure", "0");
}
return 0;
}
protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item)
protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item)
{
item.Flow = GetQueryValue(query, "flow");
item.StreamSecurity = GetQueryValue(query, "security");
@@ -209,19 +164,7 @@ public class BaseFmt
item.ShortId = GetQueryDecoded(query, "sid");
item.SpiderX = GetQueryDecoded(query, "spx");
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
{
item.AllowInsecure = Global.AllowInsecure.First();
}
else if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "0"))
{
item.AllowInsecure = Global.AllowInsecure.Skip(1).First();
}
else
{
item.AllowInsecure = string.Empty;
}
item.AllowInsecure = new[] { "allowInsecure", "allow_insecure", "insecure" }.Any(k => (query[k] ?? "") == "1") ? "true" : "";
item.Network = GetQueryValue(query, "type", nameof(ETransport.tcp));
switch (item.Network)
@@ -246,21 +189,7 @@ public class BaseFmt
item.RequestHost = GetQueryDecoded(query, "host");
item.Path = GetQueryDecoded(query, "path", "/");
item.HeaderType = GetQueryDecoded(query, "mode");
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;
item.Extra = GetQueryDecoded(query, "extra");
break;
case nameof(ETransport.http):

View File

@@ -37,7 +37,7 @@ public class FmtHandler
try
{
var str = config.TrimEx();
string str = config.TrimEx();
if (str.IsNullOrEmpty())
{
msg = ResUI.FailedReadConfiguration;

View File

@@ -12,9 +12,7 @@ public class Hysteria2Fmt : BaseFmt
var url = Utils.TryUri(str);
if (url == null)
{
return null;
}
item.Address = url.IdnHost;
item.Port = url.Port;
@@ -22,8 +20,10 @@ public class Hysteria2Fmt : BaseFmt
item.Id = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item);
ResolveStdTransport(query, ref item);
item.Path = GetQueryDecoded(query, "obfs-password");
item.AllowInsecure = GetQueryValue(query, "insecure") == "1" ? "true" : "false";
item.Ports = GetQueryDecoded(query, "mport");
return item;
@@ -32,25 +32,29 @@ public class Hysteria2Fmt : BaseFmt
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
}
string url = string.Empty;
var url = string.Empty;
var remark = string.Empty;
string remark = string.Empty;
if (item.Remarks.IsNotEmpty())
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var dicQuery = new Dictionary<string, string>();
ToUriQueryLite(item, ref dicQuery);
if (item.Sni.IsNotEmpty())
{
dicQuery.Add("sni", item.Sni);
}
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
if (item.Path.IsNotEmpty())
{
dicQuery.Add("obfs", "salamander");
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path));
}
dicQuery.Add("insecure", item.AllowInsecure.ToLower() == "true" ? "1" : "0");
if (item.Ports.IsNotEmpty())
{
dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-')));

View File

@@ -1,3 +1,5 @@
using System.Text.RegularExpressions;
namespace ServiceLib.Handler.Fmt;
public class ShadowsocksFmt : BaseFmt
@@ -41,66 +43,7 @@ public class ShadowsocksFmt : BaseFmt
//url = Utile.Base64Encode(url);
//new Sip002
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
// plugin
var plugin = string.Empty;
var pluginArgs = string.Empty;
if (item.Network == nameof(ETransport.tcp) && item.HeaderType == Global.TcpHeaderHttp)
{
plugin = "obfs-local";
pluginArgs = $"obfs=http;obfs-host={item.RequestHost};";
}
else
{
if (item.Network == nameof(ETransport.ws))
{
pluginArgs += "mode=websocket;";
pluginArgs += $"host={item.RequestHost};";
pluginArgs += $"path={item.Path};";
}
else if (item.Network == nameof(ETransport.quic))
{
pluginArgs += "mode=quic;";
}
if (item.StreamSecurity == Global.StreamSecurity)
{
pluginArgs += "tls;";
var certs = CertPemManager.ParsePemChain(item.Cert);
if (certs.Count > 0)
{
var cert = certs.First();
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
const string endMarker = "\n-----END CERTIFICATE-----";
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
// 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};";
}
}
if (pluginArgs.Length > 0)
{
plugin = "v2ray-plugin";
}
}
var dicQuery = new Dictionary<string, string>();
if (plugin.IsNotEmpty())
{
var pluginStr = plugin + ";" + pluginArgs;
// pluginStr remove last ';' and url encode
if (pluginStr.EndsWith(';'))
{
pluginStr = pluginStr[..^1];
}
dicQuery["plugin"] = Utils.UrlEncode(pluginStr);
}
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, dicQuery, remark);
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark);
}
private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
@@ -183,82 +126,19 @@ public class ShadowsocksFmt : BaseFmt
var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
if (queryParameters["plugin"] != null)
{
var pluginStr = queryParameters["plugin"];
var pluginParts = pluginStr.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
if (pluginParts.Length == 0)
//obfs-host exists
var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host"));
if (queryParameters["plugin"].Contains("obfs=http") && obfsHost.IsNotEmpty())
{
obfsHost = obfsHost?.Replace("obfs-host=", "");
item.Network = Global.DefaultNetwork;
item.HeaderType = Global.TcpHeaderHttp;
item.RequestHost = obfsHost ?? "";
}
else
{
return null;
}
var pluginName = pluginParts[0];
// A typo in https://github.com/shadowsocks/shadowsocks-org/blob/6b1c064db4129de99c516294960e731934841c94/docs/doc/sip002.md?plain=1#L15
// "simple-obfs" should be "obfs-local"
if (pluginName == "simple-obfs")
{
pluginName = "obfs-local";
}
// Parse obfs-local plugin
if (pluginName == "obfs-local")
{
var obfsMode = pluginParts.FirstOrDefault(t => t.StartsWith("obfs="));
var obfsHost = pluginParts.FirstOrDefault(t => t.StartsWith("obfs-host="));
if ((!obfsMode.IsNullOrEmpty()) && obfsMode.Contains("obfs=http") && obfsHost.IsNotEmpty())
{
obfsHost = obfsHost.Replace("obfs-host=", "");
item.Network = Global.DefaultNetwork;
item.HeaderType = Global.TcpHeaderHttp;
item.RequestHost = obfsHost;
}
}
// Parse v2ray-plugin
else if (pluginName == "v2ray-plugin")
{
var mode = pluginParts.FirstOrDefault(t => t.StartsWith("mode="), "websocket");
var host = pluginParts.FirstOrDefault(t => t.StartsWith("host="));
var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
var hasTls = pluginParts.Any(t => t == "tls");
var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
var modeValue = mode.Replace("mode=", "");
if (modeValue == "websocket")
{
item.Network = nameof(ETransport.ws);
if (!host.IsNullOrEmpty())
{
item.RequestHost = host.Replace("host=", "");
item.Sni = item.RequestHost;
}
if (!path.IsNullOrEmpty())
{
item.Path = path.Replace("path=", "");
}
}
else if (modeValue == "quic")
{
item.Network = nameof(ETransport.quic);
}
if (hasTls)
{
item.StreamSecurity = Global.StreamSecurity;
if (!certRaw.IsNullOrEmpty())
{
var certBase64 = certRaw.Replace("certRaw=", "");
certBase64 = certBase64.Replace("\\=", "=");
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
const string endMarker = "\n-----END CERTIFICATE-----";
var certPem = beginMarker + certBase64 + endMarker;
item.Cert = certPem;
}
}
}
}
return item;

View File

@@ -45,18 +45,18 @@ public class SocksFmt : BaseFmt
};
result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..];
//remark
var indexRemark = result.IndexOf('#');
var indexRemark = result.IndexOf("#");
if (indexRemark > 0)
{
try
{
item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1));
item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1));
}
catch { }
result = result[..indexRemark];
}
//part decode
var indexS = result.IndexOf('@');
var indexS = result.IndexOf("@");
if (indexS > 0)
{
}

View File

@@ -23,7 +23,7 @@ public class TrojanFmt : BaseFmt
item.Id = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item);
_ = ResolveStdTransport(query, ref item);
return item;
}
@@ -40,7 +40,7 @@ public class TrojanFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var dicQuery = new Dictionary<string, string>();
ToUriQuery(item, null, ref dicQuery);
_ = GetStdTransport(item, null, ref dicQuery);
return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark);
}

View File

@@ -29,7 +29,7 @@ public class TuicFmt : BaseFmt
}
var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item);
ResolveStdTransport(query, ref item);
item.HeaderType = GetQueryValue(query, "congestion_control");
return item;
@@ -47,10 +47,15 @@ public class TuicFmt : BaseFmt
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var dicQuery = new Dictionary<string, string>();
ToUriQueryLite(item, ref dicQuery);
if (item.Sni.IsNotEmpty())
{
dicQuery.Add("sni", item.Sni);
}
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
dicQuery.Add("congestion_control", item.HeaderType);
return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark);

View File

@@ -26,7 +26,7 @@ public class VLESSFmt : BaseFmt
var query = Utils.ParseQueryString(url.Query);
item.Security = GetQueryValue(query, "encryption", Global.None);
item.StreamSecurity = GetQueryValue(query, "security");
ResolveUriQuery(query, ref item);
_ = ResolveStdTransport(query, ref item);
return item;
}
@@ -52,7 +52,7 @@ public class VLESSFmt : BaseFmt
{
dicQuery.Add("encryption", Global.None);
}
ToUriQuery(item, Global.None, ref dicQuery);
_ = GetStdTransport(item, Global.None, ref dicQuery);
return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark);
}

View File

@@ -39,8 +39,7 @@ public class VmessFmt : BaseFmt
tls = item.StreamSecurity,
sni = item.Sni,
alpn = item.Alpn,
fp = item.Fingerprint,
insecure = item.AllowInsecure.Equals(Global.AllowInsecure.First()) ? "1" : "0"
fp = item.Fingerprint
};
var url = JsonUtils.Serialize(vmessQRCode);
@@ -95,7 +94,6 @@ public class VmessFmt : BaseFmt
item.Sni = Utils.ToString(vmessQRCode.sni);
item.Alpn = Utils.ToString(vmessQRCode.alpn);
item.Fingerprint = Utils.ToString(vmessQRCode.fp);
item.AllowInsecure = vmessQRCode.insecure == "1" ? Global.AllowInsecure.First() : string.Empty;
return item;
}
@@ -120,7 +118,7 @@ public class VmessFmt : BaseFmt
item.Id = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item);
ResolveStdTransport(query, ref item);
return item;
}

View File

@@ -18,13 +18,7 @@ public static class ProxySettingLinux
private static async Task ExecCmd(List<string> args)
{
var customSystemProxyScriptPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyScriptPath;
var fileName = (customSystemProxyScriptPath.IsNotEmpty() && File.Exists(customSystemProxyScriptPath))
? customSystemProxyScriptPath
: await FileUtils.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false);
// TODO: temporarily notify which script is being used
NoticeManager.Instance.SendMessage(fileName);
var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false);
await Utils.GetCliWrapOutput(fileName, args);
}

View File

@@ -23,13 +23,7 @@ public static class ProxySettingOSX
private static async Task ExecCmd(List<string> args)
{
var customSystemProxyScriptPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyScriptPath;
var fileName = (customSystemProxyScriptPath.IsNotEmpty() && File.Exists(customSystemProxyScriptPath))
? customSystemProxyScriptPath
: await FileUtils.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false);
// TODO: temporarily notify which script is being used
NoticeManager.Instance.SendMessage(fileName);
var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false);
await Utils.GetCliWrapOutput(fileName, args);
}

View File

@@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using static ServiceLib.Handler.SysProxy.ProxySettingWindows.InternetConnectionOption;
namespace ServiceLib.Handler.SysProxy;

View File

@@ -33,7 +33,7 @@ public static class SysProxyHandler
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
break;
case ESysProxyType.ForcedChange when Utils.IsMacOS():
case ESysProxyType.ForcedChange when Utils.IsOSX():
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions);
break;
@@ -45,7 +45,7 @@ public static class SysProxyHandler
await ProxySettingLinux.UnsetProxy();
break;
case ESysProxyType.ForcedClear when Utils.IsMacOS():
case ESysProxyType.ForcedClear when Utils.IsOSX():
await ProxySettingOSX.UnsetProxy();
break;
@@ -91,7 +91,7 @@ public static class SysProxyHandler
private static async Task SetWindowsProxyPac(int port)
{
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
await PacManager.Instance.StartAsync(port, portPac);
await PacManager.Instance.StartAsync(Utils.GetConfigPath(), port, portPac);
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
ProxySettingWindows.SetProxy(strProxy, "", 4);
}

View File

@@ -1,3 +1,4 @@
using System.Net;
using Downloader;
namespace ServiceLib.Helper;
@@ -71,25 +72,28 @@ public class DownloaderHelper
}
};
var lastUpdateTime = DateTime.Now;
var totalDatetime = DateTime.Now;
var totalSecond = 0;
var hasValue = false;
double maxSpeed = 0;
await using var downloader = new Downloader.DownloadService(downloadOpt);
//downloader.DownloadStarted += (sender, value) =>
//{
// if (progress != null)
// {
// progress.Report("Start download data...");
// }
//};
downloader.DownloadProgressChanged += (sender, value) =>
{
if (progress != null && value.BytesPerSecondSpeed > 0)
var ts = DateTime.Now - totalDatetime;
if (progress != null && ts.Seconds > totalSecond)
{
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);
}
@@ -99,19 +103,10 @@ public class DownloaderHelper
{
if (progress != null)
{
if (hasValue && maxSpeed > 0)
{
var finalSpeed = (maxSpeed / 1000 / 1000).ToString("#0.0");
progress.Report(finalSpeed);
}
else if (value.Error != null)
if (!hasValue && value.Error != null)
{
progress.Report(value.Error?.Message);
}
else
{
progress.Report("0");
}
}
};
//progress.Report("......");

View File

@@ -1,5 +1,8 @@
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
namespace ServiceLib.Helper;
@@ -49,6 +52,15 @@ public class HttpClientHelper
return await httpClient.GetStringAsync(url);
}
public async Task<string?> GetAsync(HttpClient client, string url, CancellationToken token = default)
{
if (url.IsNullOrEmpty())
{
return null;
}
return await client.GetStringAsync(url, token);
}
public async Task PutAsync(string url, Dictionary<string, string> headers)
{
var jsonContent = JsonUtils.Serialize(headers);
@@ -71,4 +83,156 @@ public class HttpClientHelper
{
await httpClient.DeleteAsync(url);
}
public static async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress<double>? progress, CancellationToken token = default)
{
ArgumentNullException.ThrowIfNull(url);
ArgumentNullException.ThrowIfNull(fileName);
if (File.Exists(fileName))
{
File.Delete(fileName);
}
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
if (!response.IsSuccessStatusCode)
{
throw new Exception(response.StatusCode.ToString());
}
var total = response.Content.Headers.ContentLength ?? -1L;
var canReportProgress = total != -1 && progress != null;
await using var stream = await response.Content.ReadAsStreamAsync(token);
await using var file = File.Create(fileName);
var totalRead = 0L;
var buffer = new byte[1024 * 1024];
var progressPercentage = 0;
while (true)
{
token.ThrowIfCancellationRequested();
var read = await stream.ReadAsync(buffer, token);
totalRead += read;
if (read == 0)
{
break;
}
await file.WriteAsync(buffer.AsMemory(0, read), token);
if (canReportProgress)
{
var percent = (int)(100.0 * totalRead / total);
//if (progressPercentage != percent && percent % 10 == 0)
{
progressPercentage = percent;
progress?.Report(percent);
}
}
}
if (canReportProgress)
{
progress?.Report(101);
}
}
public async Task DownloadDataAsync4Speed(HttpClient client, string url, IProgress<string> progress, CancellationToken token = default)
{
if (url.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(url));
}
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
if (!response.IsSuccessStatusCode)
{
throw new Exception(response.StatusCode.ToString());
}
//var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
//var canReportProgress = total != -1 && progress != null;
await using var stream = await response.Content.ReadAsStreamAsync(token);
var totalRead = 0L;
var buffer = new byte[1024 * 64];
var isMoreToRead = true;
var progressSpeed = string.Empty;
var totalDatetime = DateTime.Now;
var totalSecond = 0;
do
{
if (token.IsCancellationRequested)
{
if (totalRead > 0)
{
return;
}
else
{
token.ThrowIfCancellationRequested();
}
}
var read = await stream.ReadAsync(buffer, token);
if (read == 0)
{
isMoreToRead = false;
}
else
{
var data = new byte[read];
buffer.ToList().CopyTo(0, data, 0, read);
totalRead += read;
var ts = DateTime.Now - totalDatetime;
if (progress != null && ts.Seconds > totalSecond)
{
totalSecond = ts.Seconds;
var speed = (totalRead * 1d / ts.TotalMilliseconds / 1000).ToString("#0.0");
if (progressSpeed != speed)
{
progressSpeed = speed;
progress.Report(speed);
}
}
}
} while (isMoreToRead);
}
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
{
var responseTime = -1;
try
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
using var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null
});
List<int> oneTime = new();
for (var i = 0; i < 2; i++)
{
var timer = Stopwatch.StartNew();
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
timer.Stop();
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
await Task.Delay(100);
}
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
}
catch //(Exception ex)
{
//Utile.SaveLog(ex.Message, ex);
}
return responseTime;
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections;
using SQLite;
namespace ServiceLib.Helper;

View File

@@ -10,13 +10,6 @@ public class ActionPrecheckManager(Config config)
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())
@@ -88,36 +81,21 @@ public class ActionPrecheckManager(Config config)
{
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))
{
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;
}
@@ -137,7 +115,7 @@ public class ActionPrecheckManager(Config config)
if (item.ConfigType.IsGroupType())
{
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild())
if (group is null || group.ChildItems.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
@@ -150,11 +128,7 @@ public class ActionPrecheckManager(Config config)
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)
foreach (var child in Utils.String2List(group.ChildItems))
{
var childErrors = new List<string>();
if (child.IsNullOrEmpty())
@@ -181,16 +155,26 @@ public class ActionPrecheckManager(Config config)
return errors;
}
var net = item.GetNetwork();
var net = item.GetNetwork() ?? item.Network;
if (coreType == ECoreType.sing_box)
{
var transportError = ValidateSingboxTransport(item.ConfigType, net);
if (transportError != null)
// sing-box does not support xhttp / kcp
// sing-box does not support transports like ws/http/httpupgrade/etc. when the node is not vmess/trojan/vless
if (net is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
errors.Add(transportError);
errors.Add(string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net));
return errors;
}
if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan))
{
if (net is nameof(ETransport.ws) or nameof(ETransport.http) or nameof(ETransport.h2) or nameof(ETransport.quic) or nameof(ETransport.httpupgrade))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), item.ConfigType.ToString(), net));
return errors;
}
}
}
else if (coreType is ECoreType.Xray)
{
@@ -206,31 +190,6 @@ public class ActionPrecheckManager(Config config)
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>();

View File

@@ -1,419 +0,0 @@
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace ServiceLib.Manager;
/// <summary>
/// Manager for certificate operations with CA pinning to prevent MITM attacks
/// </summary>
public class CertPemManager
{
private static readonly string _tag = "CertPemManager";
private static readonly Lazy<CertPemManager> _instance = new(() => new());
public static CertPemManager Instance => _instance.Value;
/// <summary>
/// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks
/// </summary>
private static readonly HashSet<string> TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase)
{
"EBD41040E4BB3EC742C9E381D31EF2A41A48B6685C96E7CEF3C1DF6CD4331C99", // GlobalSign Root CA
"6DC47172E01CBCB0BF62580D895FE2B8AC9AD4F873801E0C10B9C837D21EB177", // Entrust.net Premium 2048 Secure Server CA
"73C176434F1BC6D5ADF45B0E76E727287C8DE57616C1E6E6141A2B2CBC7D8E4C", // Entrust Root Certification Authority
"D8E0FEBC1DB2E38D00940F37D27D41344D993E734B99D5656D9778D4D8143624", // Certum Root CA
"D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root
"85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2
"18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3
"CECDDC905099D8DADFC5B1D209B737CBE2C18CFB2C10C0FF0BCF0D3286FC1AA2", // XRamp Global CA Root
"C3846BF24B9E93CA64274C0EC67C1ECC5E024FFCACD2D74019350E81FE546AE4", // Go Daddy Class 2 CA
"1465FA205397B876FAA6F0A9958E5590E40FCC7FAA4FB7C2C8677521FB5FB658", // Starfield Class 2 CA
"3E9099B5015E8F486C00BCEA9D111EE721FABA355A89BCF1DF69561E3DC6325C", // DigiCert Assured ID Root CA
"4348A0E9444C78CB265E058D5E8944B4D84F9662BD26DB257F8934A443C70161", // DigiCert Global Root CA
"7431E5F4C3C1CE4690774F0B61E05440883BA9A01ED00BA6ABD7806ED3B118CF", // DigiCert High Assurance EV Root CA
"62DD0BE9B9F50A163EA0F8E75C053B1ECA57EA55C8688F647C6881F2C8357B95", // SwissSign Gold CA - G2
"F1C1B50AE5A20DD8030EC9F6BC24823DD367B5255759B4E71B61FCE9F7375D73", // SecureTrust CA
"4200F5043AC8590EBB527D209ED1503029FBCBD41CA1B506EC27F15ADE7DAC69", // Secure Global CA
"0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66", // COMODO Certification Authority
"1793927A0614549789ADCE2F8F34F7F0B66D0F3AE3A3B84D21EC15DBBA4FADC7", // COMODO ECC Certification Authority
"41C923866AB4CAD6B7AD578081582E020797A6CBDF4FFF78CE8396B38937D7F5", // OISTE WISeKey Global Root GA CA
"E3B6A2DB2ED7CE48842F7AC53241C7B71D54144BFB40C11F3F1D0B42F5EEA12D", // Certigna
"C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5", // ePKI Root Certification Authority
"EAA962C4FA4A6BAFEBE415196D351CCD888D4F53F3FA8AE6D7C466A94E6042BB", // certSIGN ROOT CA
"6C61DAC3A2DEF031506BE036D2A6FE401994FBD13DF9C8D466599274C446EC98", // NetLock Arany (Class Gold) Főtanúsítvány
"3C5F81FEA5FAB82C64BFA2EAECAFCDE8E077FC8620A7CAE537163DF36EDBF378", // Microsec e-Szigno Root CA 2009
"CBB522D7B7F127AD6A0113865BDF1CD4102E7D0759AF635A7CF4720DC963C53B", // GlobalSign Root CA - R3
"2530CC8E98321502BAD96F9B1FBA1B099E2D299E0F4548BB914F363BC0D4531F", // Izenpe.com
"45140B3247EB9CC8C5B4F0D7B53091F73292089E6E5A63E2749DD3ACA9198EDA", // Go Daddy Root Certificate Authority - G2
"2CE1CB0BF9D2F9E102993FBE215152C3B2DD0CABDE1C68E5319B839154DBB7F5", // Starfield Root Certificate Authority - G2
"568D6905A2C88708A4B3025190EDCFEDB1974A606A13C6E5290FCB2AE63EDAB5", // Starfield Services Root Certificate Authority - G2
"0376AB1D54C5F9803CE4B2E201A0EE7EEF7B57B636E8A93C9B8D4860C96F5FA7", // AffirmTrust Commercial
"0A81EC5A929777F145904AF38D5D509F66B5E2C58FCDB531058B0E17F3F0B41B", // AffirmTrust Networking
"70A73F7F376B60074248904534B11482D5BF0E698ECC498DF52577EBF2E93B9A", // AffirmTrust Premium
"BD71FDF6DA97E4CF62D1647ADD2581B07D79ADF8397EB4ECBA9C5E8488821423", // AffirmTrust Premium ECC
"5C58468D55F58E497E743982D2B50010B6D165374ACF83A7D4A32DB768C4408E", // Certum Trusted Network CA
"BFD88FE1101C41AE3E801BF8BE56350EE9BAD1A6B9BD515EDC5C6D5B8711AC44", // TWCA Root Certification Authority
"513B2CECB810D4CDE5DD85391ADFC6C2DD60D87BB736D2B521484AA47A0EBEF6", // Security Communication RootCA2
"55926084EC963A64B96E2ABE01CE0BA86A64FBFEBCC7AAB5AFC155B37FD76066", // Actalis Authentication Root CA
"9A114025197C5BB95D94E63D55CD43790847B646B23CDF11ADA4A00EFF15FB48", // Buypass Class 2 Root CA
"EDF7EBBCA27A2A384D387B7D4010C666E2EDB4843E4C29B4AE1D5B9332E6B24D", // Buypass Class 3 Root CA
"FD73DAD31C644FF1B43BEF0CCDDA96710B9CD9875ECA7E31707AF3E96D522BBD", // T-TeleSec GlobalRoot Class 3
"49E7A442ACF0EA6287050054B52564B650E4F49E42E348D6AA38E039E957B1C1", // D-TRUST Root Class 3 CA 2 2009
"EEC5496B988CE98625B934092EEC2908BED0B0F316C2D4730C84EAF1F3D34881", // D-TRUST Root Class 3 CA 2 EV 2009
"E23D4A036D7B70E9F595B1422079D2B91EDFBB1FB651A0633EAA8A9DC5F80703", // CA Disig Root R2
"9A6EC012E1A7DA9DBE34194D478AD7C0DB1822FB071DF12981496ED104384113", // ACCVRAIZ1
"59769007F7685D0FCD50872F9F95D5755A5B2B457D81F3692B610A98672F0E1B", // TWCA Global Root CA
"DD6936FE21F8F077C123A1A521C12224F72255B73E03A7260693E8A24B0FA389", // TeliaSonera Root CA v1
"91E2F5788D5810EBA7BA58737DE1548A8ECACD014598BC0B143E041B17052552", // T-TeleSec GlobalRoot Class 2
"F356BEA244B7A91EB35D53CA9AD7864ACE018E2D35D5F8F96DDF68A6F41AA474", // Atos TrustedRoot 2011
"8A866FD1B276B57E578E921C65828A2BED58E9F2F288054134B7F1F4BFC9CC74", // QuoVadis Root CA 1 G3
"8FE4FB0AF93A4D0D67DB0BEBB23E37C71BF325DCBCDD240EA04DAF58B47E1840", // QuoVadis Root CA 2 G3
"88EF81DE202EB018452E43F864725CEA5FBD1FC2D9D205730709C5D8B8690F46", // QuoVadis Root CA 3 G3
"7D05EBB682339F8C9451EE094EEBFEFA7953A114EDB2F44949452FAB7D2FC185", // DigiCert Assured ID Root G2
"7E37CB8B4C47090CAB36551BA6F45DB840680FBA166A952DB100717F43053FC2", // DigiCert Assured ID Root G3
"CB3CCBB76031E5E0138F8DD39A23F9DE47FFC35E43C1144CEA27D46A5AB1CB5F", // DigiCert Global Root G2
"31AD6648F8104138C738F39EA4320133393E3A18CC02296EF97C2AC9EF6731D0", // DigiCert Global Root G3
"552F7BDCF1A7AF9E6CE672017F4F12ABF77240C78E761AC203D1D9D20AC89988", // DigiCert Trusted Root G4
"52F0E1C4E58EC629291B60317F074671B85D7EA80D5B07273463534B32B40234", // COMODO RSA Certification Authority
"E793C9B02FD8AA13E21C31228ACCB08119643B749C898964B1746D46C3D4CBD2", // USERTrust RSA Certification Authority
"4FF460D54B9C86DABFBCFC5712E0400D2BED3FBC4D4FBDAA86E06ADCD2A9AD7A", // USERTrust ECC Certification Authority
"179FBC148A3DD00FD24EA13458CC43BFA7F59C8182D783A513F6EBEC100C8924", // GlobalSign ECC Root CA - R5
"3C4FB0B95AB8B30032F432B86F535FE172C185D0FD39865837CF36187FA6F428", // Staat der Nederlanden Root CA - G3
"5D56499BE4D2E08BCFCAD08A3E38723D50503BDE706948E42F55603019E528AE", // IdenTrust Commercial Root CA 1
"30D0895A9A448A262091635522D1F52010B5867ACAE12C78EF958FD4F4389F2F", // IdenTrust Public Sector Root CA 1
"43DF5774B03E7FEF5FE40D931A7BEDF1BB2E6B42738C4E6D3841103D3AA7F339", // Entrust Root Certification Authority - G2
"02ED0EB28C14DA45165C566791700D6451D7FB56F0B2AB1D3B8EB070E56EDFF5", // Entrust Root Certification Authority - EC1
"5CC3D78E4E1D5E45547A04E6873E64F90CF9536D1CCC2EF800F355C4C5FD70FD", // CFCA EV ROOT
"6B9C08E86EB0F767CFAD65CD98B62149E5494A67F5845E7BD1ED019F27B86BD6", // OISTE WISeKey Global Root GB CA
"A1339D33281A0B56E557D3D32B1CE7F9367EB094BD5FA72A7E5004C8DED7CAFE", // SZAFIR ROOT CA2
"B676F2EDDAE8775CD36CB0F63CD1D4603961F49E6265BA013A2F0307B6D0B804", // Certum Trusted Network CA 2
"A040929A02CE53B4ACF4F2FFC6981CE4496F755E6D45FE0B2A692BCD52523F36", // Hellenic Academic and Research Institutions RootCA 2015
"44B545AA8A25E65A73CA15DC27FC36D24C1CB9953A066539B11582DC487B4833", // Hellenic Academic and Research Institutions ECC RootCA 2015
"96BCEC06264976F37460779ACF28C5A7CFE8A3C0AAE11A8FFCEE05C0BDDF08C6", // ISRG Root X1
"EBC5570C29018C4D67B1AA127BAF12F703B4611EBC17B7DAB5573894179B93FA", // AC RAIZ FNMT-RCM
"8ECDE6884F3D87B1125BA31AC3FCB13D7016DE7F57CC904FE1CB97C6AE98196E", // Amazon Root CA 1
"1BA5B2AA8C65401A82960118F80BEC4F62304D83CEC4713A19C39C011EA46DB4", // Amazon Root CA 2
"18CE6CFE7BF14E60B2E347B8DFE868CB31D02EBB3ADA271569F50343B46DB3A4", // Amazon Root CA 3
"E35D28419ED02025CFA69038CD623962458DA5C695FBDEA3C22B0BFB25897092", // Amazon Root CA 4
"A1A86D04121EB87F027C66F53303C28E5739F943FC84B38AD6AF009035DD9457", // D-TRUST Root CA 3 2013
"46EDC3689046D53A453FB3104AB80DCAEC658B2660EA1629DD7E867990648716", // TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1
"BFFF8FD04433487D6A8AA60C1A29767A9FC2BBB05E420F713A13B992891D3893", // GDCA TrustAUTH R5 ROOT
"85666A562EE0BE5CE925C1D8890A6F76A87EC16D4D7D5F29EA7419CF20123B69", // SSL.com Root Certification Authority RSA
"3417BB06CC6007DA1B961C920B8AB4CE3FAD820E4AA30B9ACBC4A74EBDCEBC65", // SSL.com Root Certification Authority ECC
"2E7BF16CC22485A7BBE2AA8696750761B0AE39BE3B2FE9D0CC6D4EF73491425C", // SSL.com EV Root Certification Authority RSA R2
"22A2C1F7BDED704CC1E701B5F408C310880FE956B5DE2A4A44F99C873A25A7C8", // SSL.com EV Root Certification Authority ECC
"2CABEAFE37D06CA22ABA7391C0033D25982952C453647349763A3AB5AD6CCF69", // GlobalSign Root CA - R6
"8560F91C3624DABA9570B5FEA0DBE36FF11A8323BE9486854FB3F34A5571198D", // OISTE WISeKey Global Root GC CA
"9BEA11C976FE014764C1BE56A6F914B5A560317ABD9988393382E5161AA0493C", // UCA Global G2 Root
"D43AF9B35473755C9684FC06D7D8CB70EE5C28E773FB294EB41EE71722924D24", // UCA Extended Validation Root
"D48D3D23EEDB50A459E55197601C27774B9D7B18C94D5A059511A10250B93168", // Certigna Root CA
"40F6AF0346A99AA1CD1D555A4E9CCE62C7F9634603EE406615833DC8C8D00367", // emSign Root CA - G1
"86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B", // emSign ECC Root CA - G3
"125609AA301DA0A249B97A8239CB6A34216F44DCAC9F3954B14292F2E8C8608F", // emSign Root CA - C1
"BC4D809B15189D78DB3E1D8CF4F9726A795DA1643CA5F1358E1DDB0EDC0D7EB3", // emSign ECC Root CA - C3
"5A2FC03F0C83B090BBFA40604B0988446C7636183DF9846E17101A447FB8EFD6", // Hongkong Post Root CA 3
"DB3517D1F6732A2D5AB97C533EC70779EE3270A62FB4AC4238372460E6F01E88", // Entrust Root Certification Authority - G4
"358DF39D764AF9E1B766E9C972DF352EE15CFAC227AF6AD1D70E8E4A6EDCBA02", // Microsoft ECC Root Certificate Authority 2017
"C741F70F4B2A8D88BF2E71C14122EF53EF10EBA0CFA5E64CFA20F418853073E0", // Microsoft RSA Root Certificate Authority 2017
"BEB00B30839B9BC32C32E4447905950641F26421B15ED089198B518AE2EA1B99", // e-Szigno Root CA 2017
"657CFE2FA73FAA38462571F332A2363A46FCE7020951710702CDFBB6EEDA3305", // certSIGN Root CA G2
"97552015F5DDFC3C8788C006944555408894450084F100867086BC1A2BB58DC8", // Trustwave Global Certification Authority
"945BBC825EA554F489D1FD51A73DDF2EA624AC7019A05205225C22A78CCFA8B4", // Trustwave Global ECC P256 Certification Authority
"55903859C8C0C3EBB8759ECE4E2557225FF5758BBD38EBD48276601E1BD58097", // Trustwave Global ECC P384 Certification Authority
"88F438DCF8FFD1FA8F429115FFE5F82AE1E06E0C70C375FAAD717B34A49E7265", // NAVER Global Root Certification Authority
"554153B13D2CF9DDB753BFBE1A4E0AE08D0AA4187058FE60A2B862B2E4B87BCB", // AC RAIZ FNMT-RCM SERVIDORES SEGUROS
"319AF0A7729E6F89269C131EA6A3A16FCD86389FDCAB3C47A4A675C161A3F974", // GlobalSign Secure Mail Root R45
"5CBF6FB81FD417EA4128CD6F8172A3C9402094F74AB2ED3A06B4405D04F30B19", // GlobalSign Secure Mail Root E45
"4FA3126D8D3A11D1C4855A4F807CBAD6CF919D3A5A88B03BEA2C6372D93C40C9", // GlobalSign Root R46
"CBB9C44D84B8043E1050EA31A69F514955D7BFD2E2C6B49301019AD61D9F5058", // GlobalSign Root E46
"9A296A5182D1D451A2E37F439B74DAAFA267523329F90F9A0D2007C334E23C9A", // GLOBALTRUST 2020
"FB8FEC759169B9106B1E511644C618C51304373F6C0643088D8BEFFD1B997599", // ANF Secure Server Root CA
"6B328085625318AA50D173C98D8BDA09D57E27413D114CF787A0F5D06C030CF6", // Certum EC-384 CA
"FE7696573855773E37A95E7AD4D9CC96C30157C15D31765BA9B15704E1AE78FD", // Certum Trusted Root CA
"2E44102AB58CB85419451C8E19D9ACF3662CAFBC614B6A53960A30F7D0E2EB41", // TunTrust Root CA
"D95D0E8EDA79525BF9BEB11B14D2100D3294985F0C62D9FABD9CD999ECCB7B1D", // HARICA TLS RSA Root CA 2021
"3F99CC474ACFCE4DFED58794665E478D1547739F2E780F1BB4CA9B133097D401", // HARICA TLS ECC Root CA 2021
"1BE7ABE30686B16348AFD1C61B6866A0EA7F4821E67D5E8AF937CF8011BC750D", // HARICA Client RSA Root CA 2021
"8DD4B5373CB0DE36769C12339280D82746B3AA6CD426E797A31BABE4279CF00B", // HARICA Client ECC Root CA 2021
"57DE0583EFD2B26E0361DA99DA9DF4648DEF7EE8441C3B728AFA9BCDE0F9B26A", // Autoridad de Certificacion Firmaprofesional CIF A62634068
"30FBBA2C32238E2A98547AF97931E550428B9B3F1C8EEB6633DCFA86C5B27DD3", // vTrus ECC Root CA
"8A71DE6559336F426C26E53880D00D88A18DA4C6A91F0DCB6194E206C5C96387", // vTrus Root CA
"69729B8E15A86EFC177A57AFB7171DFC64ADD28C2FCA8CF1507E34453CCB1470", // ISRG Root X2
"F015CE3CC239BFEF064BE9F1D2C417E1A0264A0A94BE1F0C8D121864EB6949CC", // HiPKI Root CA - G1
"B085D70B964F191A73E4AF0D54AE7A0E07AAFDAF9B71DD0862138AB7325A24A2", // GlobalSign ECC Root CA - R4
"D947432ABDE7B7FA90FC2E6B59101B1280E0E1C7E4E40FA3C6887FFF57A7F4CF", // GTS Root R1
"8D25CD97229DBF70356BDA4EB3CC734031E24CF00FAFCFD32DC76EB5841C7EA8", // GTS Root R2
"34D8A73EE208D9BCDB0D956520934B4E40E69482596E8B6F73C8426B010A6F48", // GTS Root R3
"349DFA4058C5E263123B398AE795573C4E1313C83FE68F93556CD5E8031B3C7D", // GTS Root R4
"242B69742FCB1E5B2ABF98898B94572187544E5B4D9911786573621F6A74B82C", // Telia Root CA v2
"E59AAA816009C22BFF5B25BAD37DF306F049797C1F81D85AB089E657BD8F0044", // D-TRUST BR Root CA 1 2020
"08170D1AA36453901A2F959245E347DB0C8D37ABAABC56B81AA100DC958970DB", // D-TRUST EV Root CA 1 2020
"018E13F0772532CF809BD1B17281867283FC48C6E13BE9C69812854A490C1B05", // DigiCert TLS ECC P384 Root G5
"371A00DC0533B3721A7EEB40E8419E70799D2B0A0F2C1D80693165F7CEC4AD75", // DigiCert TLS RSA4096 Root G5
"E8E8176536A60CC2C4E10187C3BEFCA20EF263497018F566D5BEA0F94D0C111B", // DigiCert SMIME ECC P384 Root G5
"90370D3EFA88BF58C30105BA25104A358460A7FA52DFC2011DF233A0F417912A", // DigiCert SMIME RSA4096 Root G5
"77B82CD8644C4305F7ACC5CB156B45675004033D51C60C6202A8E0C33467D3A0", // Certainly Root R1
"B4585F22E4AC756A4E8612A1361C5D9D031A93FD84FEBB778FA3068B0FC42DC2", // Certainly Root E1
"82BD5D851ACF7F6E1BA7BFCBC53030D0E7BC3C21DF772D858CAB41D199BDF595", // DIGITALSIGN GLOBAL ROOT RSA CA
"261D7114AE5F8FF2D8C7209A9DE4289E6AFC9D717023D85450909199F1857CFE", // DIGITALSIGN GLOBAL ROOT ECDSA CA
"E74FBDA55BD564C473A36B441AA799C8A68E077440E8288B9FA1E50E4BBACA11", // Security Communication ECC RootCA1
"F3896F88FE7C0A882766A7FA6AD2749FB57A7F3E98FB769C1FA7B09C2C44D5AE", // BJCA Global Root CA1
"574DF6931E278039667B720AFDC1600FC27EB66DD3092979FB73856487212882", // BJCA Global Root CA2
"48E1CF9E43B688A51044160F46D773B8277FE45BEAAD0E4DF90D1974382FEA99", // LAWtrust Root CA2 (4096)
"22D9599234D60F1D4BC7C7E96F43FA555B07301FD475175089DAFB8C25E477B3", // Sectigo Public Email Protection Root E46
"D5917A7791EB7CF20A2E57EB98284A67B28A57E89182DA53D546678C9FDE2B4F", // Sectigo Public Email Protection Root R46
"C90F26F0FB1B4018B22227519B5CA2B53E2CA5B3BE5CF18EFE1BEF47380C5383", // Sectigo Public Server Authentication Root E46
"7BB647A62AEEAC88BF257AA522D01FFEA395E0AB45C73F93F65654EC38F25A06", // Sectigo Public Server Authentication Root R46
"8FAF7D2E2CB4709BB8E0B33666BF75A5DD45B5DE480F8EA8D4BFE6BEBC17F2ED", // SSL.com TLS RSA Root CA 2022
"C32FFD9F46F936D16C3673990959434B9AD60AAFBB9E7CF33654F144CC1BA143", // SSL.com TLS ECC Root CA 2022
"AD7DD58D03AEDB22A30B5084394920CE12230C2D8017AD9B81AB04079BDD026B", // SSL.com Client ECC Root CA 2022
"1D4CA4A2AB21D0093659804FC0EB2175A617279B56A2475245C9517AFEB59153", // SSL.com Client RSA Root CA 2022
"E38655F4B0190C84D3B3893D840A687E190A256D98052F159E6D4A39F589A6EB", // Atos TrustedRoot Root CA ECC G2 2020
"78833A783BB2986C254B9370D3C20E5EBA8FA7840CBF63FE17297A0B0119685E", // Atos TrustedRoot Root CA RSA G2 2020
"B2FAE53E14CCD7AB9212064701AE279C1D8988FACB775FA8A008914E663988A8", // Atos TrustedRoot Root CA ECC TLS 2021
"81A9088EA59FB364C548A6F85559099B6F0405EFBF18E5324EC9F457BA00112F", // Atos TrustedRoot Root CA RSA TLS 2021
"E0D3226AEB1163C2E48FF9BE3B50B4C6431BE7BB1EACC5C36B5D5EC509039A08", // TrustAsia Global Root CA G3
"BE4B56CB5056C0136A526DF444508DAA36A0B54F42E4AC38F72AF470E479654C", // TrustAsia Global Root CA G4
"D92C171F5CF890BA428019292927FE22F3207FD2B54449CB6F675AF4922146E2", // D-Trust SBR Root CA 1 2022
"DBA84DD7EF622D485463A90137EA4D574DF8550928F6AFA03B4D8B1141E636CC", // D-Trust SBR Root CA 2 2022
"3AE6DF7E0D637A65A8C81612EC6F9A142F85A16834C10280D88E707028518755", // Telekom Security SMIME ECC Root 2021
"578AF4DED0853F4E5998DB4AEAF9CBEA8D945F60B620A38D1A3C13B2BC7BA8E1", // Telekom Security TLS ECC Root 2020
"78A656344F947E9CC0F734D9053D32F6742086B6B9CD2CAE4FAE1A2E4EFDE048", // Telekom Security SMIME RSA Root 2023
"EFC65CADBB59ADB6EFE84DA22311B35624B71B3B1EA0DA8B6655174EC8978646", // Telekom Security TLS RSA Root 2023
"BEF256DAF26E9C69BDEC1602359798F3CAF71821A03E018257C53C65617F3D4A", // FIRMAPROFESIONAL CA ROOT-A WEB
"3F63BB2814BE174EC8B6439CF08D6D56F0B7C405883A5648A334424D6B3EC558", // TWCA CYBER Root CA
"3A0072D49FFC04E996C59AEB75991D3C340F3615D6FD4DCE90AC0B3D88EAD4F4", // TWCA Global Root CA G2
"3F034BB5704D44B2D08545A02057DE93EBF3905FCE721ACBC730C06DDAEE904E", // SecureSign Root CA12
"4B009C1034494F9AB56BBA3BA1D62731FC4D20D8955ADCEC10A925607261E338", // SecureSign Root CA14
"E778F0F095FE843729CD1A0082179E5314A9C291442805E1FB1D8FB6B8886C3A", // SecureSign Root CA15
"0552E6F83FDF65E8FA9670E666DF28A4E21340B510CBE52566F97C4FB94B2BD1", // D-TRUST BR Root CA 2 2023
"436472C1009A325C54F1A5BBB5468A7BAEECCBE05DE5F099CB70D3FE41E13C16", // TrustAsia SMIME ECC Root CA
"C7796BEB62C101BB143D262A7C96A0C6168183223EF50D699632D86E03B8CC9B", // TrustAsia SMIME RSA Root CA
"C0076B9EF0531FB1A656D67C4EBE97CD5DBAA41EF44598ACC2489878C92D8711", // TrustAsia TLS ECC Root CA
"06C08D7DAFD876971EB1124FE67F847EC0C7A158D3EA53CBE940E2EA9791F4C3", // TrustAsia TLS RSA Root CA
"8E8221B2E7D4007836A1672F0DCC299C33BC07D316F132FA1A206D587150F1CE", // D-TRUST EV Root CA 2 2023
"9A12C392BFE57891A0C545309D4D9FD567E480CB613D6342278B195C79A7931F", // SwissSign RSA SMIME Root CA 2022 - 1
"193144F431E0FDDB740717D4DE926A571133884B4360D30E272913CBE660CE41", // SwissSign RSA TLS Root CA 2022 - 1
"D9A32485A8CCA85539CEF12FFFFF711378A17851D73DA2732AB4302D763BD62B", // OISTE Client Root ECC G1
"D02A0F994A868C66395F2E7A880DF509BD0C29C96DE16015A0FD501EDA4F96A9", // OISTE Client Root RSA G1
"EEC997C0C30F216F7E3B8B307D2BAE42412D753FC8219DAFD1520B2572850F49", // OISTE Server Root ECC G1
"9AE36232A5189FFDDB353DFD26520C015395D22777DAC59DB57B98C089A651E6", // OISTE Server Root RSA G1
};
/// <summary>
/// Get certificate in PEM format from a server with CA pinning validation
/// </summary>
public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4)
{
try
{
var (domain, _, port, _) = Utils.ParseUrl(target);
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
var sslOptions = new SslClientAuthenticationOptions
{
TargetHost = serverName,
RemoteCertificateValidationCallback = ValidateServerCertificate
};
await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
var remote = ssl.RemoteCertificate;
if (remote == null)
{
return (null, null);
}
var leaf = new X509Certificate2(remote);
return (ExportCertToPem(leaf), null);
}
catch (OperationCanceledException)
{
Logging.SaveLog(_tag, new TimeoutException($"Connection timeout after {timeout} seconds"));
return (null, $"Connection timeout after {timeout} seconds");
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return (null, ex.Message);
}
}
/// <summary>
/// Get certificate chain in PEM format from a server with CA pinning validation
/// </summary>
public async Task<(List<string>, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4)
{
var pemList = new List<string>();
try
{
var (domain, _, port, _) = Utils.ParseUrl(target);
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
var sslOptions = new SslClientAuthenticationOptions
{
TargetHost = serverName,
RemoteCertificateValidationCallback = ValidateServerCertificate
};
await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
if (ssl.RemoteCertificate is not X509Certificate2 certChain)
{
return (pemList, null);
}
var chain = new X509Chain();
chain.Build(certChain);
foreach (var element in chain.ChainElements)
{
var pem = ExportCertToPem(element.Certificate);
pemList.Add(pem);
}
return (pemList, null);
}
catch (OperationCanceledException)
{
Logging.SaveLog(_tag, new TimeoutException($"Connection timeout after {timeout} seconds"));
return (pemList, $"Connection timeout after {timeout} seconds");
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return (pemList, ex.Message);
}
}
/// <summary>
/// Validate server certificate with CA pinning
/// </summary>
private bool ValidateServerCertificate(
object sender,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
if (certificate == null)
{
return false;
}
// Check certificate name mismatch
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
{
return false;
}
// Build certificate chain
var cert2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate);
var certChain = chain ?? new X509Chain();
certChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
certChain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
certChain.ChainPolicy.VerificationTime = DateTime.Now;
certChain.Build(cert2);
// Find root CA
if (certChain.ChainElements.Count == 0)
{
return false;
}
var rootCert = certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate;
var rootThumbprint = rootCert.GetCertHashString(HashAlgorithmName.SHA256);
return TrustedCaThumbprints.Contains(rootThumbprint);
}
public static string ExportCertToPem(X509Certificate2 cert)
{
var der = cert.Export(X509ContentType.Cert);
var b64 = Convert.ToBase64String(der);
return $"-----BEGIN CERTIFICATE-----\n{b64}\n-----END CERTIFICATE-----\n";
}
/// <summary>
/// Parse concatenated PEM certificates string into a list of individual certificates
/// Normalizes format: removes line breaks from base64 content for better compatibility
/// </summary>
/// <param name="pemChain">Concatenated PEM certificates string (supports both \r\n and \n line endings)</param>
/// <returns>List of individual PEM certificate strings with normalized format</returns>
public static List<string> ParsePemChain(string pemChain)
{
var certs = new List<string>();
if (string.IsNullOrWhiteSpace(pemChain))
{
return certs;
}
// Normalize line endings (CRLF -> LF) at the beginning
pemChain = pemChain.Replace("\r\n", "\n").Replace("\r", "\n");
const string beginMarker = "-----BEGIN CERTIFICATE-----";
const string endMarker = "-----END CERTIFICATE-----";
var index = 0;
while (index < pemChain.Length)
{
var beginIndex = pemChain.IndexOf(beginMarker, index, StringComparison.Ordinal);
if (beginIndex == -1)
{
break;
}
var endIndex = pemChain.IndexOf(endMarker, beginIndex, StringComparison.Ordinal);
if (endIndex == -1)
{
break;
}
// Extract certificate content
var base64Start = beginIndex + beginMarker.Length;
var base64Content = pemChain.Substring(base64Start, endIndex - base64Start);
// Remove all whitespace from base64 content
base64Content = new string(base64Content.Where(c => !char.IsWhiteSpace(c)).ToArray());
// Reconstruct with clean format: BEGIN marker + base64 (no line breaks) + END marker
var normalizedCert = $"{beginMarker}\n{base64Content}\n{endMarker}\n";
certs.Add(normalizedCert);
// Move to next certificate
index = endIndex + endMarker.Length;
}
return certs;
}
/// <summary>
/// Concatenate a list of PEM certificates into a single string
/// </summary>
/// <param name="pemList">List of individual PEM certificate strings</param>
/// <returns>Concatenated PEM certificates string</returns>
public static string ConcatenatePemChain(IEnumerable<string> pemList)
{
if (pemList == null)
{
return string.Empty;
}
return string.Concat(pemList);
}
}

View File

@@ -1,3 +1,4 @@
using System.Text;
using CliWrap;
using CliWrap.Buffered;
@@ -34,8 +35,8 @@ public class CoreAdminManager
StringBuilder sb = new();
sb.AppendLine("#!/bin/bash");
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
sb.AppendLine($"exec sudo -S -- {cmdLine}");
var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
sb.AppendLine($"sudo -S {cmdLine}");
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
var procService = new ProcessService(
fileName: shFilePath,
@@ -67,8 +68,8 @@ public class CoreAdminManager
try
{
var shellFileName = Utils.IsMacOS() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName;
var shFilePath = await FileUtils.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
var shellFileName = Utils.IsOSX() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName;
var shFilePath = await FileManager.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
if (shFilePath.Contains(' '))
{
shFilePath = shFilePath.AppendQuotes();

View File

@@ -8,7 +8,7 @@ public class CoreManager
private static readonly Lazy<CoreManager> _instance = new(() => new());
public static CoreManager Instance => _instance.Value;
private Config _config;
private WindowsJobService? _processJob;
private WindowsJob? _processJob;
private ProcessService? _processService;
private ProcessService? _processPreService;
private bool _linuxSudo = false;
@@ -27,7 +27,7 @@ public class CoreManager
var toPath = Utils.GetBinPath("");
if (fromPath != toPath)
{
FileUtils.CopyDirectory(fromPath, toPath, true, false);
FileManager.CopyDirectory(fromPath, toPath, true, false);
}
}

View File

@@ -1,3 +1,6 @@
using System.Net.Sockets;
using System.Text;
namespace ServiceLib.Manager;
public class PacManager
@@ -5,6 +8,7 @@ public class PacManager
private static readonly Lazy<PacManager> _instance = new(() => new PacManager());
public static PacManager Instance => _instance.Value;
private string _configPath;
private int _httpPort;
private int _pacPort;
private TcpListener? _tcpListener;
@@ -12,10 +16,11 @@ public class PacManager
private bool _isRunning;
private bool _needRestart = true;
public async Task StartAsync(int httpPort, int pacPort)
public async Task StartAsync(string configPath, int httpPort, int pacPort)
{
_needRestart = httpPort != _httpPort || pacPort != _pacPort || !_isRunning;
_needRestart = configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning;
_configPath = configPath;
_httpPort = httpPort;
_pacPort = pacPort;
@@ -30,22 +35,22 @@ public class PacManager
private async Task InitText()
{
var customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyPacPath;
var fileName = (customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath))
? customSystemProxyPacPath
: Path.Combine(Utils.GetConfigPath(), "pac.txt");
var path = Path.Combine(_configPath, "pac.txt");
// TODO: temporarily notify which script is being used
NoticeManager.Instance.SendMessage(fileName);
if (!File.Exists(fileName))
// Delete the old pac file
if (File.Exists(path) && Utils.GetFileHash(path).Equals("b590c07280f058ef05d5394aa2f927fe"))
{
var pac = EmbedUtils.GetEmbedText(Global.PacFileName);
await File.AppendAllTextAsync(fileName, pac);
File.Delete(path);
}
var pacText = await File.ReadAllTextAsync(fileName);
pacText = pacText.Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;");
if (!File.Exists(path))
{
var pac = EmbedUtils.GetEmbedText(Global.PacFileName);
await File.AppendAllTextAsync(path, pac);
}
var pacText =
(await File.ReadAllTextAsync(path)).Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;");
var sb = new StringBuilder();
sb.AppendLine("HTTP/1.0 200 OK");

View File

@@ -1,3 +1,5 @@
using System.Collections.Concurrent;
//using System.Reactive.Linq;
namespace ServiceLib.Manager;

View File

@@ -1,3 +1,5 @@
using System.Collections.Concurrent;
namespace ServiceLib.Manager;
public class ProfileGroupItemManager
@@ -173,19 +175,13 @@ public class ProfileGroupItemManager
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);
@@ -226,14 +222,11 @@ public class ProfileGroupItemManager
public static async Task<(List<ProfileItem> Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId)
{
Instance.TryGet(indexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.NotHasChild())
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
{
return (new List<ProfileItem>(), profileGroupItem);
}
var items = await GetChildProfileItems(profileGroupItem);
var subItems = await GetSubChildProfileItems(profileGroupItem);
items.AddRange(subItems);
return (items, profileGroupItem);
}
@@ -257,47 +250,20 @@ public class ProfileGroupItemManager
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)
{
if (!Instance.TryGet(indexId, out var groupItem) || groupItem.ChildItems.IsNullOrEmpty())
return childAddresses;
}
if (groupItem.SubChildItems.IsNotEmpty())
{
var subItems = await GetSubChildProfileItems(groupItem);
subItems.ForEach(p => childAddresses.Add(p.Address));
}
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
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())
{

View File

@@ -26,29 +26,15 @@ public class TaskManager
await Task.Delay(1000 * 60);
//Execute once 1 minute
try
{
await UpdateTaskRunSubscription();
}
catch (Exception ex)
{
Logging.SaveLog("ScheduledTasks - UpdateTaskRunSubscription", ex);
}
await UpdateTaskRunSubscription();
//Execute once 20 minute
if (numOfExecuted % 20 == 0)
{
//Logging.SaveLog("Execute save config");
try
{
await ConfigHandler.SaveConfig(_config);
await ProfileExManager.Instance.SaveTo();
}
catch (Exception ex)
{
Logging.SaveLog("ScheduledTasks - SaveConfig", ex);
}
await ConfigHandler.SaveConfig(_config);
await ProfileExManager.Instance.SaveTo();
}
//Execute once 1 hour
@@ -56,18 +42,12 @@ public class TaskManager
{
//Logging.SaveLog("Execute delete expired files");
FileUtils.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
FileUtils.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
FileUtils.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
FileManager.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
FileManager.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
try
{
await UpdateTaskRunGeo(numOfExecuted / 60);
}
catch (Exception ex)
{
Logging.SaveLog("ScheduledTasks - UpdateTaskRunGeo", ex);
}
//Check once 1 hour
await UpdateTaskRunGeo(numOfExecuted / 60);
}
numOfExecuted++;
@@ -111,10 +91,11 @@ public class TaskManager
{
Logging.SaveLog("Execute update geo files");
await new UpdateService(_config, async (success, msg) =>
var updateHandle = new UpdateService();
await updateHandle.UpdateGeoFileAll(_config, async (success, msg) =>
{
await _updateFunc?.Invoke(false, msg);
}).UpdateGeoFileAll();
});
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Net;
using WebDav;
namespace ServiceLib.Manager;

View File

@@ -1,3 +1,6 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models;
public class CheckUpdateModel : ReactiveObject

View File

@@ -1,3 +1,6 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models;
[Serializable]

View File

@@ -71,6 +71,7 @@ public class GUIItem
public bool DisplayRealTimeSpeed { get; set; }
public bool KeepOlderDedupl { get; set; }
public int AutoUpdateInterval { get; set; }
public bool EnableSecurityProtocolTls13 { get; set; }
public int TrayMenuServersLimit { get; set; } = 20;
public bool EnableHWA { get; set; } = false;
public bool EnableLog { get; set; } = true;
@@ -219,8 +220,6 @@ public class SystemProxyItem
public string SystemProxyExceptions { get; set; }
public bool NotProxyLocalAddress { get; set; } = true;
public string SystemProxyAdvancedProtocol { get; set; }
public string? CustomSystemProxyPacPath { get; set; }
public string? CustomSystemProxyScriptPath { get; set; }
}
[Serializable]
@@ -265,7 +264,6 @@ public class SimpleDNSItem
public bool? BlockBindingQuery { get; set; }
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; }

View File

@@ -1,3 +1,5 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]

View File

@@ -1,3 +1,5 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]

View File

@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models;
public class GitHubReleaseAsset

View File

@@ -1,3 +1,5 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]

View File

@@ -1,3 +1,5 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]
@@ -8,14 +10,5 @@ public class ProfileGroupItem
public string ChildItems { get; set; }
public string? SubChildItems { get; set; }
public string? Filter { get; set; }
public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing;
public bool NotHasChild()
{
return string.IsNullOrWhiteSpace(ChildItems) && string.IsNullOrWhiteSpace(SubChildItems);
}
}

View File

@@ -1,3 +1,6 @@
using ReactiveUI;
using SQLite;
namespace ServiceLib.Models;
[Serializable]
@@ -28,7 +31,7 @@ public class ProfileItem : ReactiveObject
public string GetSummary()
{
var summary = $"[{ConfigType.ToString()}] ";
var summary = $"[{(ConfigType).ToString()}] ";
if (IsComplex())
{
summary += $"[{CoreType.ToString()}]{Remarks}";
@@ -69,49 +72,30 @@ public class ProfileItem : ReactiveObject
public bool IsValid()
{
if (IsComplex())
{
return true;
}
if (Address.IsNullOrEmpty() || Port is <= 0 or >= 65536)
{
return false;
}
switch (ConfigType)
{
case EConfigType.VMess:
if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id))
{
return false;
}
break;
case EConfigType.VLESS:
if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30))
{
return false;
}
if (!Global.Flows.Contains(Flow))
{
return false;
}
break;
case EConfigType.Shadowsocks:
if (Id.IsNullOrEmpty())
{
return false;
}
if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security))
{
return false;
}
break;
}
@@ -160,5 +144,4 @@ public class ProfileItem : ReactiveObject
public string Mldsa65Verify { get; set; }
public string Extra { get; set; }
public bool? MuxEnabled { get; set; }
public string Cert { get; set; }
}

View File

@@ -1,3 +1,5 @@
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models;
[Serializable]

View File

@@ -1,3 +1,5 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]

View File

@@ -1,3 +1,5 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]

View File

@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models;
public class SingboxConfig
@@ -181,7 +183,6 @@ public class Tls4Sbox
public bool? fragment { get; set; }
public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; }
public List<string>? certificate { get; set; }
}
public class Multiplex4Sbox
@@ -216,8 +217,6 @@ public class Transport4Sbox
public string? idle_timeout { get; set; }
public string? ping_timeout { get; set; }
public bool? permit_without_stream { get; set; }
public int? max_early_data { get; set; }
public string? early_data_header_name { get; set; }
}
public class Headers4Sbox

View File

@@ -1,3 +1,5 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]

View File

@@ -1,21 +0,0 @@
namespace ServiceLib.Models;
public class UpdateResult
{
public bool Success { get; set; }
public string? Msg { get; set; }
public SemanticVersion? Version { get; set; }
public string? Url { get; set; }
public UpdateResult(bool success, string? msg)
{
Success = success;
Msg = msg;
}
public UpdateResult(bool success, SemanticVersion? version)
{
Success = success;
Version = version;
}
}

View File

@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models;
public class V2rayConfig
@@ -215,7 +217,6 @@ public class Dns4Ray
public class DnsServer4Ray
{
public string? address { get; set; }
public int? port { get; set; }
public List<string>? domains { get; set; }
public bool? skipFallback { get; set; }
public List<string>? expectedIPs { get; set; }
@@ -354,14 +355,6 @@ public class TlsSettings4Ray
public string? shortId { get; set; }
public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; }
public List<CertificateSettings4Ray>? certificates { get; set; }
public bool? disableSystemRoot { get; set; }
}
public class CertificateSettings4Ray
{
public List<string>? certificate { get; set; }
public string? usage { get; set; }
}
public class TcpSettings4Ray
@@ -411,6 +404,8 @@ public class WsSettings4Ray
public class Headers4Ray
{
public string Host { get; set; }
[JsonPropertyName("User-Agent")]
public string UserAgent { get; set; }
}

View File

@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models;
/// <summary>
@@ -38,6 +40,4 @@ public class VmessQRCode
public string alpn { get; set; } = string.Empty;
public string fp { get; set; } = string.Empty;
public string insecure { get; set; } = string.Empty;
}

View File

@@ -87,24 +87,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Certificate not set 的本地化字符串。
/// </summary>
public static string CertNotSet {
get {
return ResourceManager.GetString("CertNotSet", resourceCulture);
}
}
/// <summary>
/// 查找类似 Certificate set 的本地化字符串。
/// </summary>
public static string CertSet {
get {
return ResourceManager.GetString("CertSet", resourceCulture);
}
}
/// <summary>
/// 查找类似 Please check the Configuration settings first. 的本地化字符串。
/// </summary>
@@ -529,7 +511,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Next proxy remarks 的本地化字符串。
/// 查找类似 Next proxy Configuration remarks 的本地化字符串。
/// </summary>
public static string LvNextProfile {
get {
@@ -547,7 +529,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Previous proxy remarks 的本地化字符串。
/// 查找类似 Previous proxy Configuration remarks 的本地化字符串。
/// </summary>
public static string LvPrevProfile {
get {
@@ -736,7 +718,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Anytls] 的本地化字符串。
/// 查找类似 Add [Anytls] Configuration 的本地化字符串。
/// </summary>
public static string menuAddAnytlsServer {
get {
@@ -745,7 +727,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add Child 的本地化字符串。
/// 查找类似 Add Child Configuration 的本地化字符串。
/// </summary>
public static string menuAddChildServer {
get {
@@ -754,7 +736,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add a custom configuration 的本地化字符串。
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。
/// </summary>
public static string menuAddCustomServer {
get {
@@ -763,7 +745,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [HTTP] 的本地化字符串。
/// 查找类似 Add [HTTP] Configuration 的本地化字符串。
/// </summary>
public static string menuAddHttpServer {
get {
@@ -772,7 +754,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Hysteria2] 的本地化字符串。
/// 查找类似 Add [Hysteria2] Configuration 的本地化字符串。
/// </summary>
public static string menuAddHysteria2Server {
get {
@@ -781,7 +763,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add Policy Group 的本地化字符串。
/// 查找类似 Add Policy Group Configuration 的本地化字符串。
/// </summary>
public static string menuAddPolicyGroupServer {
get {
@@ -790,7 +772,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add Proxy Chain 的本地化字符串。
/// 查找类似 Add Proxy Chain Configuration 的本地化字符串。
/// </summary>
public static string menuAddProxyChainServer {
get {
@@ -799,7 +781,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Import Share Links from clipboard 的本地化字符串。
/// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。
/// </summary>
public static string menuAddServerViaClipboard {
get {
@@ -817,7 +799,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Scan QR code on the screen 的本地化字符串。
/// 查找类似 Scan QR code on the screen (Ctrl+S) 的本地化字符串。
/// </summary>
public static string menuAddServerViaScan {
get {
@@ -826,7 +808,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Shadowsocks] 的本地化字符串。
/// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。
/// </summary>
public static string menuAddShadowsocksServer {
get {
@@ -835,7 +817,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [SOCKS] 的本地化字符串。
/// 查找类似 Add [SOCKS] Configuration 的本地化字符串。
/// </summary>
public static string menuAddSocksServer {
get {
@@ -844,7 +826,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Trojan] 的本地化字符串。
/// 查找类似 Add [Trojan] Configuration 的本地化字符串。
/// </summary>
public static string menuAddTrojanServer {
get {
@@ -853,7 +835,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [TUIC] 的本地化字符串。
/// 查找类似 Add [TUIC] Configuration 的本地化字符串。
/// </summary>
public static string menuAddTuicServer {
get {
@@ -862,7 +844,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [VLESS] 的本地化字符串。
/// 查找类似 Add [VLESS] Configuration 的本地化字符串。
/// </summary>
public static string menuAddVlessServer {
get {
@@ -871,7 +853,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [VMess] 的本地化字符串。
/// 查找类似 Add [VMess] Configuration 的本地化字符串。
/// </summary>
public static string menuAddVmessServer {
get {
@@ -880,7 +862,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [WireGuard] 的本地化字符串。
/// 查找类似 Add [WireGuard] Configuration 的本地化字符串。
/// </summary>
public static string menuAddWireguardServer {
get {
@@ -952,7 +934,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Clone selected 的本地化字符串。
/// 查找类似 Clone selected Configuration 的本地化字符串。
/// </summary>
public static string menuCopyServer {
get {
@@ -970,7 +952,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Edit 的本地化字符串。
/// 查找类似 Edit Configuration (Ctrl+D) 的本地化字符串。
/// </summary>
public static string menuEditServer {
get {
@@ -997,7 +979,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export selected for complete configuration 的本地化字符串。
/// 查找类似 Export selected Configuration for complete configuration 的本地化字符串。
/// </summary>
public static string menuExport2ClientConfig {
get {
@@ -1006,7 +988,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export selected for complete configuration to clipboard 的本地化字符串。
/// 查找类似 Export selected Configuration for complete configuration to clipboard 的本地化字符串。
/// </summary>
public static string menuExport2ClientConfigClipboard {
get {
@@ -1015,7 +997,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export Share Link to Clipboard 的本地化字符串。
/// 查找类似 Export Share Link to Clipboard (Ctrl+C) 的本地化字符串。
/// </summary>
public static string menuExport2ShareUrl {
get {
@@ -1033,7 +1015,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export 的本地化字符串。
/// 查找类似 Export Configuration 的本地化字符串。
/// </summary>
public static string menuExportConfig {
get {
@@ -1041,15 +1023,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Test real delay 的本地化字符串。
/// </summary>
public static string menuFastRealPing {
get {
return ResourceManager.GetString("menuFastRealPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Full Config Template Setting 的本地化字符串。
/// </summary>
@@ -1069,7 +1042,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Fallback by sing-box 的本地化字符串。
/// 查找类似 Multi-Configuration Fallback by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxFallback {
get {
@@ -1078,7 +1051,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 LeastPing by sing-box 的本地化字符串。
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxLeastPing {
get {
@@ -1087,7 +1060,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Fallback by Xray 的本地化字符串。
/// 查找类似 Multi-Configuration Fallback by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayFallback {
get {
@@ -1096,7 +1069,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 LeastLoad by Xray 的本地化字符串。
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastLoad {
get {
@@ -1105,7 +1078,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 LeastPing by Xray 的本地化字符串。
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastPing {
get {
@@ -1114,7 +1087,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Random by Xray 的本地化字符串。
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRandom {
get {
@@ -1123,7 +1096,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 RoundRobin by Xray 的本地化字符串。
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRoundRobin {
get {
@@ -1249,7 +1222,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Move to bottom 的本地化字符串。
/// 查找类似 Move to bottom (B) 的本地化字符串。
/// </summary>
public static string menuMoveBottom {
get {
@@ -1258,7 +1231,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Down 的本地化字符串。
/// 查找类似 Down (D) 的本地化字符串。
/// </summary>
public static string menuMoveDown {
get {
@@ -1285,7 +1258,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Move to top 的本地化字符串。
/// 查找类似 Move to top (T) 的本地化字符串。
/// </summary>
public static string menuMoveTop {
get {
@@ -1294,7 +1267,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Up 的本地化字符串。
/// 查找类似 Up (U) 的本地化字符串。
/// </summary>
public static string menuMoveUp {
get {
@@ -1312,7 +1285,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Copy 的本地化字符串。
/// 查找类似 Copy (Ctrl+C) 的本地化字符串。
/// </summary>
public static string menuMsgViewCopy {
get {
@@ -1330,7 +1303,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Select all 的本地化字符串。
/// 查找类似 Select all (Ctrl+A) 的本地化字符串。
/// </summary>
public static string menuMsgViewSelectAll {
get {
@@ -1402,7 +1375,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Select active node 的本地化字符串。
/// 查找类似 Select active node (Enter) 的本地化字符串。
/// </summary>
public static string menuProxiesSelectActivity {
get {
@@ -1411,7 +1384,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Test real delay 的本地化字符串。
/// 查找类似 Test Configurations real delay (Ctrl+R) 的本地化字符串。
/// </summary>
public static string menuRealPingServer {
get {
@@ -1501,7 +1474,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove Child 的本地化字符串。
/// 查找类似 Remove Child Configuration 的本地化字符串。
/// </summary>
public static string menuRemoveChildServer {
get {
@@ -1510,7 +1483,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove duplicate 的本地化字符串。
/// 查找类似 Remove duplicate Configurations 的本地化字符串。
/// </summary>
public static string menuRemoveDuplicateServer {
get {
@@ -1528,7 +1501,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove selected 的本地化字符串。
/// 查找类似 Remove selected Configurations (Delete) 的本地化字符串。
/// </summary>
public static string menuRemoveServer {
get {
@@ -1564,7 +1537,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove selected 的本地化字符串。
/// 查找类似 Remove selected (Delete) 的本地化字符串。
/// </summary>
public static string menuRoutingAdvancedRemove {
get {
@@ -1573,7 +1546,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Set as active rule 的本地化字符串。
/// 查找类似 Set as active rule (Enter) 的本地化字符串。
/// </summary>
public static string menuRoutingAdvancedSetDefault {
get {
@@ -1645,7 +1618,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove Rule 的本地化字符串。
/// 查找类似 Remove Rule (Delete) 的本地化字符串。
/// </summary>
public static string menuRuleRemove {
get {
@@ -1654,7 +1627,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Select all 的本地化字符串。
/// 查找类似 Select all (Ctrl+A) 的本地化字符串。
/// </summary>
public static string menuSelectAll {
get {
@@ -1663,7 +1636,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Configuration item 1, Auto add from subscription group 的本地化字符串。
/// 查找类似 Server List 的本地化字符串。
/// </summary>
public static string menuServerList {
get {
@@ -1672,16 +1645,7 @@ 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 的本地化字符串。
/// 查找类似 Configurations 的本地化字符串。
/// </summary>
public static string menuServers {
get {
@@ -1690,7 +1654,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Set as active 的本地化字符串。
/// 查找类似 Set as active Configuration (Enter) 的本地化字符串。
/// </summary>
public static string menuSetDefaultServer {
get {
@@ -1708,7 +1672,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Share 的本地化字符串。
/// 查找类似 Share Configuration (Ctrl+F) 的本地化字符串。
/// </summary>
public static string menuShareServer {
get {
@@ -1735,7 +1699,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Test download speed 的本地化字符串。
/// 查找类似 Test Configurations download speed (Ctrl+T) 的本地化字符串。
/// </summary>
public static string menuSpeedServer {
get {
@@ -1879,7 +1843,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Test tcping 的本地化字符串。
/// 查找类似 Test Configurations with tcping (Ctrl+O) 的本地化字符串。
/// </summary>
public static string menuTcpingServer {
get {
@@ -1987,7 +1951,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Filter, press Enter to execute 的本地化字符串。
/// 查找类似 Configuration filter, press Enter to execute 的本地化字符串。
/// </summary>
public static string MsgServerTitle {
get {
@@ -2284,7 +2248,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Are you sure you want to remove? 的本地化字符串。
/// 查找类似 Are you sure you want to remove the Configuration? 的本地化字符串。
/// </summary>
public static string RemoveServer {
get {
@@ -2328,15 +2292,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Please set a valid domain 的本地化字符串。
/// </summary>
public static string ServerNameMustBeValidDomain {
get {
return ResourceManager.GetString("ServerNameMustBeValidDomain", resourceCulture);
}
}
/// <summary>
/// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。
/// </summary>
@@ -2364,15 +2319,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Press ESC to terminate the test 的本地化字符串。
/// </summary>
public static string SpeedtestingPressEscToExit {
get {
return ResourceManager.GetString("SpeedtestingPressEscToExit", resourceCulture);
}
}
/// <summary>
/// 查找类似 Skip test 的本地化字符串。
/// </summary>
@@ -2401,7 +2347,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Waiting... 的本地化字符串。
/// 查找类似 Waiting for testing (press ESC to terminate)... 的本地化字符串。
/// </summary>
public static string SpeedtestingWait {
get {
@@ -2571,24 +2517,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Bootstrap DNS 的本地化字符串。
/// </summary>
public static string TbBootstrapDNS {
get {
return ResourceManager.GetString("TbBootstrapDNS", resourceCulture);
}
}
/// <summary>
/// 查找类似 Resolve DNS server domains, requires IP 的本地化字符串。
/// </summary>
public static string TbBootstrapDNSTips {
get {
return ResourceManager.GetString("TbBootstrapDNSTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Browse 的本地化字符串。
/// </summary>
@@ -2607,27 +2535,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Certificate Pinning 的本地化字符串。
/// </summary>
public static string TbCertPinning {
get {
return ResourceManager.GetString("TbCertPinning", resourceCulture);
}
}
/// <summary>
/// 查找类似 Server Certificate (PEM format, optional)
///When specified, the certificate will be pinned, and &quot;Allow Insecure&quot; will be disabled.
///
///The &quot;Get Certificate&quot; action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. 的本地化字符串。
/// </summary>
public static string TbCertPinningTips {
get {
return ResourceManager.GetString("TbCertPinningTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Clear system proxy 的本地化字符串。
/// </summary>
@@ -2835,24 +2742,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Fetch Certificate 的本地化字符串。
/// </summary>
public static string TbFetchCert {
get {
return ResourceManager.GetString("TbFetchCert", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fetch Certificate Chain 的本地化字符串。
/// </summary>
public static string TbFetchCertChain {
get {
return ResourceManager.GetString("TbFetchCertChain", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fingerprint 的本地化字符串。
/// </summary>
@@ -3042,15 +2931,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Auto add filtered configuration from subscription groups 的本地化字符串。
/// </summary>
public static string TbPolicyGroupSubChildTip {
get {
return ResourceManager.GetString("TbPolicyGroupSubChildTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 Policy Group Type 的本地化字符串。
/// </summary>
@@ -3528,24 +3408,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Custom PAC file path 的本地化字符串。
/// </summary>
public static string TbSettingsCustomSystemProxyPacPath {
get {
return ResourceManager.GetString("TbSettingsCustomSystemProxyPacPath", resourceCulture);
}
}
/// <summary>
/// 查找类似 Custom system proxy script file path 的本地化字符串。
/// </summary>
public static string TbSettingsCustomSystemProxyScriptPath {
get {
return ResourceManager.GetString("TbSettingsCustomSystemProxyScriptPath", resourceCulture);
}
}
/// <summary>
/// 查找类似 Allow Insecure 的本地化字符串。
/// </summary>
@@ -3870,15 +3732,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 macOS displays this in the Dock (requires restart) 的本地化字符串。
/// </summary>
public static string TbSettingsMacOSShowInDock {
get {
return ResourceManager.GetString("TbSettingsMacOSShowInDock", resourceCulture);
}
}
/// <summary>
/// 查找类似 Main layout orientation (requires restart) 的本地化字符串。
/// </summary>
@@ -4114,7 +3967,16 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Tray right-click menu display limit 的本地化字符串。
/// 查找类似 Enable Security Protocol TLS v1.3 (subscription/update) 的本地化字符串。
/// </summary>
public static string TbSettingsTLS13 {
get {
return ResourceManager.GetString("TbSettingsTLS13", resourceCulture);
}
}
/// <summary>
/// 查找类似 Tray right-click menu Configurations display limit 的本地化字符串。
/// </summary>
public static string TbSettingsTrayMenuServersLimit {
get {

View File

@@ -472,10 +472,10 @@
<value>زبان</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>وارد کردن URL انبوه از کلیپ بورد</value>
<value>وارد کردن URL انبوه از کلیپ بورد (Ctrl+V)</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>اسکن کد QR روی صفحه</value>
<value>اسکن کد QR روی صفحه (Ctrl+S)</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>سرور انتخاب شده را شبیه سازی کنید</value>
@@ -484,31 +484,31 @@
<value>سرورهای تکراری را حذف کنید</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>حذف سرورهای انتخابی</value>
<value>حذف سرورهای انتخابی (Delete)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>به عنوان سرور فعال تنظیم کنید</value>
<value>به عنوان سرور فعال تنظیم کنید (Enter)</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>تمام آمار خدمات را پاک کنید</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>آزمایش سرورها با تاخیر واقعی</value>
<value>آزمایش سرورها با تاخیر واقعی (Ctrl+R)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>مرتب سازی بر اساس نتیجه تست</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>تست سرعت دانلود سرورها</value>
<value>تست سرعت دانلود سرورها (Ctrl+T)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>تست سرورها با tcping</value>
<value>تست سرورها با tcping (Ctrl+O)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>سرور انتخابی را برای پیکربندی کلاینت صادر کنید</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>URL های اشتراک گذاری را به کلیپ بورد صادر کنید</value>
<value>URL های اشتراک گذاری را به کلیپ بورد صادر کنید (Ctrl+C)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>یک سرور پیکربندی سفارشی اضافه شود</value>
@@ -529,19 +529,19 @@
<value>سرور [VMess] را اضافه کنید</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>انتخاب همه</value>
<value>انتخاب همه (Ctrl+A)</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>همه را پاک کن</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>کپی</value>
<value>کپی (Ctrl+C)</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>کپی همه</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>انتخاب همه</value>
<value>انتخاب همه (Ctrl+A)</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>اضافه کردن</value>
@@ -747,6 +747,9 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>تنظیمات پراکسی سیستم</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>فعال کردن پروتکل امنیتی TLS نسخه 1.3 (اشتراک/به‌روزرسانی)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>محدودیت نمایش سرورهای منوی سینی کلیک راست</value>
</data>
@@ -796,13 +799,13 @@
<value>به پایین حرکت شود(B)</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>پایین</value>
<value>پایین (D)</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>حرکت به بالا</value>
<value>حرکت به بالا (T)</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>بالا</value>
<value>بالا (U)</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>فیلتر، از عبارات منظم پشتیبانی می کند</value>
@@ -922,7 +925,7 @@
<value>رد شدن از آزمون</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>ویرایش سرور</value>
<value>ویرایش سرور (Ctrl+D)</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>دوبار کلیک کردن سرور باعث فعال شدن آن می شود</value>
@@ -976,10 +979,7 @@
<value>فعال‌ سازی شتاب‌ دهنده سخت‌ افزاری (نیاز به راه‌اندازی مجدد)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>در انتظار آزمایش...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>برای پایان دادن به ESC فشار دهید</value>
<value>در انتظار آزمایش (برای پایان دادن به ESC فشار دهید)...</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>لطفاً در صورت قطع غیرعادی آن را خاموش کنید</value>
@@ -1207,7 +1207,7 @@
<value>تازه سازی پروکسی ها</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>انتخاب گره فعال</value>
<value>انتخاب گره فعال (Enter)</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>استراتژی دامنه پیش فرض برای خروجی</value>
@@ -1540,7 +1540,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration item 1, Auto add from subscription group</value>
<value>Server List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1593,52 +1593,4 @@
<data name="TbRuleTypeTips" xml:space="preserve">
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

View File

@@ -472,10 +472,10 @@
<value>Nyelv (Újraindítás)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>Megosztási linkek importálása vágólapról</value>
<value>Megosztási linkek importálása vágólapról (Ctrl+V)</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>QR kód beolvasása a képernyőről</value>
<value>QR kód beolvasása a képernyőről (Ctrl+S)</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>Kijelölt konfiguráció klónozása</value>
@@ -484,31 +484,31 @@
<value>Ismétlődő konfigurációk eltávolítása</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>Kijelölt konfigurációk eltávolítása</value>
<value>Kijelölt konfigurációk eltávolítása (Delete)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>Beállítás aktív konfigurációként</value>
<value>Beállítás aktív konfigurációként (Enter)</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>Összes szolgáltatás statisztika törlése</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>Konfigurációk valós késleltetésének tesztelése</value>
<value>Konfigurációk valós késleltetésének tesztelése (Ctrl+R)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>Rendezés teszteredmény szerint</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>Konfigurációk letöltési sebességének tesztelése</value>
<value>Konfigurációk letöltési sebességének tesztelése (Ctrl+T)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>Konfigurációk tesztelése tcpinggel</value>
<value>Konfigurációk tesztelése tcpinggel (Ctrl+O)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>Kijelölt konfiguráció exportálása teljes konfigurációként</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>Megosztási link exportálása vágólapra</value>
<value>Megosztási link exportálása vágólapra (Ctrl+C)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>Egyéni konfiguráció hozzáadása</value>
@@ -529,19 +529,19 @@
<value>[VMess] konfiguráció hozzáadása</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>Összes kijelölése</value>
<value>Összes kijelölése (Ctrl+A)</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>Összes törlése</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>Másolás</value>
<value>Másolás (Ctrl+C)</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>Összes másolása</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>Összes kijelölése</value>
<value>Összes kijelölése (Ctrl+A)</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>Hozzáadás</value>
@@ -747,6 +747,9 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>Rendszerproxy beállítások</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>Biztonsági protokoll TLS v1.3 engedélyezése (előfizetés/frissítés)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>Tálca jobb egérgombos menü konfigurációk megjelenítési limitje</value>
</data>
@@ -781,7 +784,7 @@
<value>PAC mód</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>Konfiguráció megosztása</value>
<value>Konfiguráció megosztása (Ctrl+F)</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>Útválasztás</value>
@@ -793,16 +796,16 @@
<value>Futtatás rendszergazdaként</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>Mozgatás alulra</value>
<value>Mozgatás alulra (B)</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>Le</value>
<value>Le (D)</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>Mozgatás felülre</value>
<value>Mozgatás felülre (T)</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>Fel</value>
<value>Fel (U)</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>Szűrő, támogatja a reguláris kifejezéseket</value>
@@ -817,10 +820,10 @@
<value>Szabályok importálása</value>
</data>
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
<value>Kijelölt eltávolítása</value>
<value>Kijelölt eltávolítása (Delete)</value>
</data>
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>Beállítás aktív szabályként</value>
<value>Beállítás aktív szabályként (Enter)</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>Tartomány stratégia</value>
@@ -853,7 +856,7 @@
<value>Szabálylista</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>Szabály eltávolítása</value>
<value>Szabály eltávolítása (Delete)</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>Útválasztási szabály részleteinek beállítása</value>
@@ -922,7 +925,7 @@
<value>Teszt kihagyása</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>Konfiguráció szerkesztése</value>
<value>Konfiguráció szerkesztése (Ctrl+D)</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>Dupla kattintás a konfigurációra aktiválja</value>
@@ -976,10 +979,7 @@
<value>Hardveres gyorsítás engedélyezése (újraindítást igényel)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>Tesztelésre vár...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>ESC megnyomásával megszakítható</value>
<value>Tesztelésre vár (ESC megnyomásával megszakítható)...</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>Kérjük, kapcsolja ki rendellenes megszakadás esetén</value>
@@ -1207,7 +1207,7 @@
<value>Proxyk frissítése</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>Aktív csomópont kiválasztása</value>
<value>Aktív csomópont kiválasztása (Enter)</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Alapértelmezett tartomány stratégia kimenő forgalomhoz</value>
@@ -1540,7 +1540,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration item 1, Auto add from subscription group</value>
<value>Server List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1593,52 +1593,4 @@
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
</root>

View File

@@ -271,7 +271,7 @@
<value>Configurations deduplication completed. Old: {0}, New: {1}.</value>
</data>
<data name="RemoveServer" xml:space="preserve">
<value>Are you sure you want to remove?</value>
<value>Are you sure you want to remove the Configuration?</value>
</data>
<data name="SaveClientConfigurationIn" xml:space="preserve">
<value>The client configuration file is saved at: {0}</value>
@@ -397,7 +397,7 @@
<value>Local</value>
</data>
<data name="MsgServerTitle" xml:space="preserve">
<value>Filter, press Enter to execute</value>
<value>Configuration filter, press Enter to execute</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve">
<value>Check Update</value>
@@ -427,7 +427,7 @@
<value>Routing Setting</value>
</data>
<data name="menuServers" xml:space="preserve">
<value>Configuration</value>
<value>Configurations</value>
</data>
<data name="menuSetting" xml:space="preserve">
<value>Settings</value>
@@ -472,76 +472,76 @@
<value>Language (Restart)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>Import Share Links from clipboard</value>
<value>Import Share Links from clipboard (Ctrl+V)</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>Scan QR code on the screen</value>
<value>Scan QR code on the screen (Ctrl+S)</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>Clone selected</value>
<value>Clone selected Configuration</value>
</data>
<data name="menuRemoveDuplicateServer" xml:space="preserve">
<value>Remove duplicate</value>
<value>Remove duplicate Configurations</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>Remove selected</value>
<value>Remove selected Configurations (Delete)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>Set as active</value>
<value>Set as active Configuration (Enter)</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>Clear all service statistics</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>Test real delay</value>
<value>Test Configurations real delay (Ctrl+R)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>Sort by test result</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>Test download speed</value>
<value>Test Configurations download speed (Ctrl+T)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>Test tcping</value>
<value>Test Configurations with tcping (Ctrl+O)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>Export selected for complete configuration</value>
<value>Export selected Configuration for complete configuration</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>Export Share Link to Clipboard</value>
<value>Export Share Link to Clipboard (Ctrl+C)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>Add a custom configuration</value>
<value>Add a custom configuration Configuration</value>
</data>
<data name="menuAddShadowsocksServer" xml:space="preserve">
<value>Add [Shadowsocks] </value>
<value>Add [Shadowsocks] Configuration</value>
</data>
<data name="menuAddSocksServer" xml:space="preserve">
<value>Add [SOCKS] </value>
<value>Add [SOCKS] Configuration</value>
</data>
<data name="menuAddTrojanServer" xml:space="preserve">
<value>Add [Trojan] </value>
<value>Add [Trojan] Configuration</value>
</data>
<data name="menuAddVlessServer" xml:space="preserve">
<value>Add [VLESS] </value>
<value>Add [VLESS] Configuration</value>
</data>
<data name="menuAddVmessServer" xml:space="preserve">
<value>Add [VMess] </value>
<value>Add [VMess] Configuration</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>Select all</value>
<value>Select all (Ctrl+A)</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>Clear all</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>Copy</value>
<value>Copy (Ctrl+C)</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>Copy all</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>Select all</value>
<value>Select all (Ctrl+A)</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>Add</value>
@@ -747,8 +747,11 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>System proxy settings</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>Enable Security Protocol TLS v1.3 (subscription/update)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>Tray right-click menu display limit</value>
<value>Tray right-click menu Configurations display limit</value>
</data>
<data name="TbSettingsUdpEnabled" xml:space="preserve">
<value>Enable UDP</value>
@@ -781,7 +784,7 @@
<value>PAC mode</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>Share</value>
<value>Share Configuration (Ctrl+F)</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>Routing</value>
@@ -793,16 +796,16 @@
<value>Run as Admin</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>Move to bottom</value>
<value>Move to bottom (B)</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>Down</value>
<value>Down (D)</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>Move to top</value>
<value>Move to top (T)</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>Up</value>
<value>Up (U)</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>Filter, supports regular expressions</value>
@@ -817,10 +820,10 @@
<value>Import Rules</value>
</data>
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
<value>Remove selected</value>
<value>Remove selected (Delete)</value>
</data>
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>Set as active rule</value>
<value>Set as active rule (Enter)</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>Domain strategy</value>
@@ -853,7 +856,7 @@
<value>Rule List</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>Remove Rule</value>
<value>Remove Rule (Delete)</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>Routing Rule Details Setting</value>
@@ -922,7 +925,7 @@
<value>Skip test</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>Edit </value>
<value>Edit Configuration (Ctrl+D)</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>Double-clicking Configuration makes it active</value>
@@ -976,10 +979,7 @@
<value>Enable hardware acceleration (requires restart)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>Waiting...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>Press ESC to terminate the test</value>
<value>Waiting for testing (press ESC to terminate)...</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>Please turn off when there is an abnormal disconnection</value>
@@ -1036,7 +1036,7 @@
<value>Domain</value>
</data>
<data name="menuAddHysteria2Server" xml:space="preserve">
<value>Add [Hysteria2] </value>
<value>Add [Hysteria2] Configuration</value>
</data>
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
<value>Hysteria Max bandwidth (Up/Down)</value>
@@ -1045,16 +1045,16 @@
<value>Use System Hosts</value>
</data>
<data name="menuAddTuicServer" xml:space="preserve">
<value>Add [TUIC] </value>
<value>Add [TUIC] Configuration</value>
</data>
<data name="TbHeaderType8" xml:space="preserve">
<value>Congestion control</value>
</data>
<data name="LvPrevProfile" xml:space="preserve">
<value>Previous proxy remarks</value>
<value>Previous proxy Configuration remarks</value>
</data>
<data name="LvNextProfile" xml:space="preserve">
<value>Next proxy remarks</value>
<value>Next proxy Configuration remarks</value>
</data>
<data name="LvPrevProfileTip" xml:space="preserve">
<value>Please make sure the Configuration remarks exist and are unique</value>
@@ -1078,7 +1078,7 @@
<value>Enable IPv6 Address</value>
</data>
<data name="menuAddWireguardServer" xml:space="preserve">
<value>Add [WireGuard] </value>
<value>Add [WireGuard] Configuration</value>
</data>
<data name="TbPrivateKey" xml:space="preserve">
<value>Private Key</value>
@@ -1111,7 +1111,7 @@
<value>*grpc Authority</value>
</data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>Add [HTTP]</value>
<value>Add [HTTP] Configuration</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>which conflicts with the group previous proxy</value>
@@ -1207,7 +1207,7 @@
<value>Refresh Proxies</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>Select active node</value>
<value>Select active node (Enter)</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Default domain strategy for outbound</value>
@@ -1225,7 +1225,7 @@
<value>Export Base64-encoded Share Links to Clipboard</value>
</data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>Export selected for complete configuration to clipboard</value>
<value>Export selected Configuration for complete configuration to clipboard</value>
</data>
<data name="menuShowOrHideMainWindow" xml:space="preserve">
<value>Show or hide the main window</value>
@@ -1381,22 +1381,22 @@
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Random by Xray</value>
<value>Multi-Configuration Random by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>RoundRobin by Xray</value>
<value>Multi-Configuration RoundRobin by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>LeastPing by Xray</value>
<value>Multi-Configuration LeastPing by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>LeastLoad by Xray</value>
<value>Multi-Configuration LeastLoad by Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>LeastPing by sing-box</value>
<value>Multi-Configuration LeastPing by sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>Export</value>
<value>Export Configuration</value>
</data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>Current connection info test URL</value>
@@ -1411,7 +1411,7 @@
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls]</value>
<value>Add [Anytls] Configuration</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>Remote DNS</value>
@@ -1528,28 +1528,28 @@
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group </value>
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain</value>
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child </value>
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child </value>
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration item 1, Auto add from subscription group</value>
<value>Server List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Fallback by sing-box</value>
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Fallback by Xray</value>
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
@@ -1593,52 +1593,4 @@
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
</root>

View File

@@ -472,10 +472,10 @@
<value>Язык (требуется перезапуск)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>Импорт массива URL из буфера обмена</value>
<value>Импорт массива URL из буфера обмена (Ctrl+V)</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>Сканировать QR-код с экрана</value>
<value>Сканировать QR-код с экрана (Ctrl+S)</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>Клонировать выбранный сервер</value>
@@ -484,31 +484,31 @@
<value>Удалить дубликаты серверов</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>Удалить выбранные серверы</value>
<value>Удалить выбранные серверы (Delete)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>Установить как активный сервер</value>
<value>Установить как активный сервер (Enter)</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>Очистить всю статистику</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>Тест на реальную задержку сервера</value>
<value>Тест на реальную задержку сервера (Ctrl+R)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>Сортировать по результату теста</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>Тест на скорость загрузки сервера</value>
<value>Тест на скорость загрузки сервера (Ctrl+T)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>Тест задержки с tcping</value>
<value>Тест задержки с tcping (Ctrl+O)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>Экспортировать выбранный сервер для клиента</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>Экспорт URL-адресов общего доступа в буфер обмена</value>
<value>Экспорт URL-адресов общего доступа в буфер обмена (Ctrl+C)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>Добавить сервер пользовательской конфигурации</value>
@@ -529,19 +529,19 @@
<value>Добавить сервер [VMess]</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>Выбрать все</value>
<value>Выбрать все (Ctrl+A)</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>Очистить все</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>Скопировать</value>
<value>Скопировать (Ctrl+C)</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>Скопировать все</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>Выбрать все</value>
<value>Выбрать все (Ctrl+A)</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>Добавить</value>
@@ -747,6 +747,9 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>Настройки системного прокси</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>Включить протокол безопасности TLS v1.3 (обновление подписки)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>Лимит серверов в меню трея</value>
</data>
@@ -781,7 +784,7 @@
<value>Режим PAC</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>Поделиться сервером</value>
<value>Поделиться сервером (Ctrl+F)</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>Маршрутизация</value>
@@ -793,16 +796,16 @@
<value>Администратор</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>Спуститься вниз</value>
<value>Спуститься вниз (B)</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>Вниз</value>
<value>Вниз (D)</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>Подняться наверх</value>
<value>Подняться наверх (T)</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>Вверх</value>
<value>Вверх (U)</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>Фильтр, поддерживает regex</value>
@@ -853,7 +856,7 @@
<value>Список правил</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>Удалить правила</value>
<value>Удалить правила (Delete)</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>Детальные настройки правил маршрутизации</value>
@@ -922,7 +925,7 @@
<value>Пропустить тест</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>Редактировать сервер</value>
<value>Редактировать сервер (Ctrl+D)</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>Двойной клик чтобы сделать сервер активным</value>
@@ -976,10 +979,7 @@
<value>Включить аппаратное ускорение (требуется перезагрузка)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>Ожидание тестирования…</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>нажмите ESC для отмены</value>
<value>Ожидание тестирования (нажмите ESC для отмены)…</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>Отключите при аномальном разрыве соединения</value>
@@ -1207,7 +1207,7 @@
<value>Обновить прокси</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>Сделать узел активным</value>
<value>Сделать узел активным (Enter)</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Стратегия домена по умолчанию для исходящих</value>
@@ -1540,7 +1540,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration item 1, Auto add from subscription group</value>
<value>Server List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1593,52 +1593,4 @@
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
</root>

View File

@@ -121,7 +121,7 @@
<value>导出分享链接至剪贴板成功</value>
</data>
<data name="CheckServerSettings" xml:space="preserve">
<value>请先检查设置</value>
<value>请先检查配置文件设置</value>
</data>
<data name="ConfigurationFormatIncorrect" xml:space="preserve">
<value>配置格式不正确</value>
@@ -133,7 +133,7 @@
<value>下载开始...</value>
</data>
<data name="FailedConversionConfiguration" xml:space="preserve">
<value>转换配置失败</value>
<value>转换配置文件失败</value>
</data>
<data name="FailedGenDefaultConfiguration" xml:space="preserve">
<value>生成默认配置文件失败</value>
@@ -142,10 +142,10 @@
<value>获取默认配置失败</value>
</data>
<data name="FailedImportedCustomServer" xml:space="preserve">
<value>导入自定义配置失败</value>
<value>导入自定义配置文件失败</value>
</data>
<data name="FailedReadConfiguration" xml:space="preserve">
<value>读取配置失败</value>
<value>读取配置文件失败</value>
</data>
<data name="FillCorrectServerPort" xml:space="preserve">
<value>请填写正确格式的端口</value>
@@ -265,13 +265,13 @@
<value>请选择协议</value>
</data>
<data name="PleaseSelectServer" xml:space="preserve">
<value>请先选择配置</value>
<value>请先选择配置文件</value>
</data>
<data name="RemoveDuplicateServerResult" xml:space="preserve">
<value>配置去重完成。原数量: {0},现数量: {1}。</value>
<value>配置文件去重完成。原数量: {0},现数量: {1}。</value>
</data>
<data name="RemoveServer" xml:space="preserve">
<value>是否确定移除?</value>
<value>是否确定移除配置文件</value>
</data>
<data name="SaveClientConfigurationIn" xml:space="preserve">
<value>客户端配置文件保存在:{0}</value>
@@ -283,10 +283,10 @@
<value>配置成功。 {0}</value>
</data>
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
<value>成功导入自定义配置</value>
<value>成功导入自定义配置文件</value>
</data>
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
<value>成功从剪贴板导入 {0} 个配置</value>
<value>成功从剪贴板导入 {0} 个配置文件</value>
</data>
<data name="SuccessfullyImportedServerViaScan" 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>
@@ -397,7 +397,7 @@
<value>本地</value>
</data>
<data name="MsgServerTitle" xml:space="preserve">
<value>过滤器,按回车执行</value>
<value>配置文件过滤器,按回车执行</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve">
<value>检查更新</value>
@@ -427,7 +427,7 @@
<value>路由设置</value>
</data>
<data name="menuServers" xml:space="preserve">
<value>配置</value>
<value>配置文件</value>
</data>
<data name="menuSetting" xml:space="preserve">
<value>设置</value>
@@ -472,76 +472,76 @@
<value>语言 (需重启)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>从剪贴板导入分享链接</value>
<value>从剪贴板导入分享链接 (Ctrl+V)</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>扫描屏幕上的二维码</value>
<value>扫描屏幕上的二维码 (Ctrl+S)</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>克隆所选</value>
<value>克隆所选配置文件</value>
</data>
<data name="menuRemoveDuplicateServer" xml:space="preserve">
<value>移除重复</value>
<value>移除重复的配置文件</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>移除所选 (多选)</value>
<value>移除所选配置文件 (多选) (Delete)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>设为活动</value>
<value>设为活动配置文件 (Enter)</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>清除所有服务统计数据</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>测试真连接延迟 (多选)</value>
<value>测试配置文件真连接延迟 (多选) (Ctrl+R)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>按测试结果排序</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>测试速度 (多选)</value>
<value>测试配置文件速度 (多选) (Ctrl+T)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>测试延迟 Tcping (多选)</value>
<value>测试配置文件延迟 Tcping (多选) (Ctrl+O)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>导出所选完整配置</value>
<value>导出所选配置文件完整配置</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>导出分享链接至剪贴板 (多选)</value>
<value>导出分享链接至剪贴板 (多选) (Ctrl+C)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>添加自定义配置</value>
<value>添加自定义配置文件</value>
</data>
<data name="menuAddShadowsocksServer" xml:space="preserve">
<value>添加 [Shadowsocks]</value>
<value>添加 [Shadowsocks] 配置文件</value>
</data>
<data name="menuAddSocksServer" xml:space="preserve">
<value>添加 [SOCKS] </value>
<value>添加 [SOCKS] 配置文件</value>
</data>
<data name="menuAddTrojanServer" xml:space="preserve">
<value>添加 [Trojan] </value>
<value>添加 [Trojan] 配置文件</value>
</data>
<data name="menuAddVlessServer" xml:space="preserve">
<value>添加 [VLESS] </value>
<value>添加 [VLESS] 配置文件</value>
</data>
<data name="menuAddVmessServer" xml:space="preserve">
<value>添加 [VMess] </value>
<value>添加 [VMess] 配置文件</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>全选</value>
<value>全选 (Ctrl+A)</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>清除所有</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>复制</value>
<value>复制 (Ctrl+C)</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>复制所有</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>全选</value>
<value>全选 (Ctrl+A)</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>添加</value>
@@ -691,7 +691,7 @@
<value>Outbound Freedom domainStrategy</value>
</data>
<data name="TbSettingsEnableAutoAdjustMainLvColWidth" xml:space="preserve">
<value>自动调整配置列宽在更新订阅后</value>
<value>自动调整配置文件列宽在更新订阅后</value>
</data>
<data name="TbSettingsEnableCheckPreReleaseUpdate" xml:space="preserve">
<value>检查 Pre-Release 更新 (请谨慎启用)</value>
@@ -700,7 +700,7 @@
<value>例外</value>
</data>
<data name="TbSettingsExceptionTip" xml:space="preserve">
<value>例外:对于下列字符开头的地址,不使用代理配置。使用分号 (;) 分隔。</value>
<value>例外:对于下列字符开头的地址,不使用代理配置文件。使用分号 (;) 分隔。</value>
</data>
<data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve">
<value>显示实时速度 (需重启)</value>
@@ -747,8 +747,11 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>系统代理设置</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>启用安全协议 TLS v1.3 (订阅/检查更新)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>托盘右键菜单配置展示数量限制</value>
<value>托盘右键菜单配置文件展示数量限制</value>
</data>
<data name="TbSettingsUdpEnabled" xml:space="preserve">
<value>开启 UDP</value>
@@ -781,7 +784,7 @@
<value>Pac 模式</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>分享</value>
<value>分享配置文件 (Ctrl+F)</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>路由</value>
@@ -793,16 +796,16 @@
<value>以管理员身份运行</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>下移至底</value>
<value>下移至底 (B)</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>下移</value>
<value>下移 (D)</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>上移至顶</value>
<value>上移至顶 (T)</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>上移</value>
<value>上移 (U)</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>过滤器 (支持正则)</value>
@@ -817,10 +820,10 @@
<value>一键导入规则集</value>
</data>
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
<value>移除所选规则</value>
<value>移除所选规则 (Delete)</value>
</data>
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>设为活动规则</value>
<value>设为活动规则 (Enter)</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>域名解析策略</value>
@@ -853,7 +856,7 @@
<value>规则列表</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>移除所选规则</value>
<value>移除所选规则 (Delete)</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>路由规则详情设置</value>
@@ -913,7 +916,7 @@
<value>移至订阅分组</value>
</data>
<data name="TbSettingsEnableDragDropSort" xml:space="preserve">
<value>启用配置拖放排序 (需重启)</value>
<value>启用配置文件拖放排序 (需重启)</value>
</data>
<data name="TbAutoRefresh" xml:space="preserve">
<value>自动刷新</value>
@@ -922,10 +925,10 @@
<value>跳过测试</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>编辑</value>
<value>编辑配置文件 (Ctrl+D)</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>主界面双击设为活动</value>
<value>主界面双击设为活动配置文件</value>
</data>
<data name="SpeedtestingCompleted" xml:space="preserve">
<value>测试完成</value>
@@ -976,10 +979,7 @@
<value>启用硬件加速 (需重启)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>等待测试...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>按 ESC 可终止测试</value>
<value>等待测试中 (按 ESC 终止)...</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>当有异常断流时请关闭</value>
@@ -1033,7 +1033,7 @@
<value>Domain</value>
</data>
<data name="menuAddHysteria2Server" xml:space="preserve">
<value>添加 [Hysteria2] </value>
<value>添加 [Hysteria2] 配置文件</value>
</data>
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
<value>Hysteria 最大带宽 (Up/Dw)</value>
@@ -1042,19 +1042,19 @@
<value>使用系统 hosts</value>
</data>
<data name="menuAddTuicServer" xml:space="preserve">
<value>添加 [TUIC] </value>
<value>添加 [TUIC] 配置文件</value>
</data>
<data name="TbHeaderType8" xml:space="preserve">
<value>拥塞控制算法</value>
</data>
<data name="LvPrevProfile" xml:space="preserve">
<value>前置代理配置别名</value>
<value>前置代理配置文件别名</value>
</data>
<data name="LvNextProfile" xml:space="preserve">
<value>落地代理配置別名</value>
<value>落地代理配置文件別名</value>
</data>
<data name="LvPrevProfileTip" xml:space="preserve">
<value>请确保配置别名存在并唯一</value>
<value>请确保配置文件别名存在并唯一</value>
</data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>自动路由</value>
@@ -1075,7 +1075,7 @@
<value>启用 IPv6</value>
</data>
<data name="menuAddWireguardServer" xml:space="preserve">
<value>添加 [WireGuard] </value>
<value>添加 [WireGuard] 配置文件</value>
</data>
<data name="TbPrivateKey" xml:space="preserve">
<value>PrivateKey</value>
@@ -1108,7 +1108,7 @@
<value>*grpc Authority</value>
</data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>添加 [HTTP] </value>
<value>添加 [HTTP] 配置文件</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>和分组前置代理冲突</value>
@@ -1198,13 +1198,13 @@
<value>延迟测试</value>
</data>
<data name="menuProxiesDelaytestPart" xml:space="preserve">
<value>当前部分延迟测试</value>
<value>当前部分节点延迟测试</value>
</data>
<data name="menuProxiesReload" xml:space="preserve">
<value>刷新</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>设为活动</value>
<value>设为活动节点 (Enter)</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Outbound 默认解析策略</value>
@@ -1222,7 +1222,7 @@
<value>导出分享链接至剪贴板 (多选) Base64 编码</value>
</data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>导出所选完整配置至剪贴板</value>
<value>导出所选配置文件完整配置至剪贴板</value>
</data>
<data name="menuShowOrHideMainWindow" xml:space="preserve">
<value>显示或隐藏主界面</value>
@@ -1339,7 +1339,7 @@
<value>多线程测试时的并发数量</value>
</data>
<data name="TbSettingsExceptionTip2" xml:space="preserve">
<value>例外:对于下列地址不使用代理配置。使用逗号 (,) 分隔。</value>
<value>例外:对于下列地址不使用代理配置文件。使用逗号 (,) 分隔。</value>
</data>
<data name="TbSettingsDestOverride" xml:space="preserve">
<value>流量探测类型</value>
@@ -1375,31 +1375,31 @@
<value>会覆盖端口,多组时用逗号 (,) 隔开</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多生成策略组</value>
<value>多配置文件生成策略组</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多随机 Xray</value>
<value>多配置文件随机 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多负载均衡 Xray</value>
<value>多配置文件负载均衡 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多最低延迟 Xray</value>
<value>多配置文件最低延迟 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多最稳定 Xray</value>
<value>多配置文件最稳定 Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多最低延迟 sing-box</value>
<value>多配置文件最低延迟 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>导出</value>
<value>导出配置文件</value>
</data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>当前连接信息测试地址</value>
</data>
<data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>可以填写配置别名,请确保存在并唯一</value>
<value>可以填写配置文件别名,请确保存在并唯一</value>
</data>
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>密码错误,请重试。</value>
@@ -1408,7 +1408,7 @@
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>添加 [Anytls] </value>
<value>添加 [Anytls] 配置文件</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>远程 DNS</value>
@@ -1495,13 +1495,13 @@
<value>开始解析和处理订阅内容</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>选择配置</value>
<value>选择配置文件</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>请至少添加一个配置</value>
<value>请至少添加一个配置文件</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>策略组</value>
@@ -1525,28 +1525,28 @@
<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>
<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>
<value>多配置文件故障转移 sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多故障转移 Xray</value>
<value>多配置文件故障转移 Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支持网络类型 '{1}'。</value>
@@ -1567,10 +1567,10 @@
<value>策略组: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>别名 '{0}' 不存在。</value>
<value>节点别名 '{0}' 不存在。</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>组“{0}”为空。请至少添加一个配置。</value>
<value>组“{0}”为空。请至少添加一个节点。</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>{0}属性无效,请检查</value>
@@ -1590,52 +1590,4 @@
<data name="TbRuleTypeTips" xml:space="preserve">
<value>可对 Routing 和 DNS 单独设定规则ALL 则都生效</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>解析 DNS 服务器域名,需指定为 IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>一键测试真连接延迟</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>自动从订阅分组添加过滤后的配置</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>固定证书</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>服务器证书PEM 格式,可选)
当指定此证书后,将固定该证书,并禁用“跳过证书验证”选项。
“获取证书”操作可能失败,原因可能是使用了自签证书,或系统中存在不受信任或恶意的 CA。</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>获取证书</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>获取证书链</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>请设置有效的域名</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>证书未设置</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>证书已设置</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>自定义 PAC 文件路径</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>自定义系统代理脚本文件路径</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS 在 Dock 栏中显示 (需重启)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>子配置项二,从自建中选择添加</value>
</data>
</root>

View File

@@ -121,7 +121,7 @@
<value>匯出分享連結至剪貼簿成功</value>
</data>
<data name="CheckServerSettings" xml:space="preserve">
<value>請先檢查設定</value>
<value>請先檢查設定檔設定</value>
</data>
<data name="ConfigurationFormatIncorrect" xml:space="preserve">
<value>設定格式不正確</value>
@@ -133,7 +133,7 @@
<value>下載開始...</value>
</data>
<data name="FailedConversionConfiguration" xml:space="preserve">
<value>轉換設定失敗</value>
<value>轉換設定失敗</value>
</data>
<data name="FailedGenDefaultConfiguration" xml:space="preserve">
<value>生成預設設定檔失敗</value>
@@ -142,10 +142,10 @@
<value>獲取預設設定失敗</value>
</data>
<data name="FailedImportedCustomServer" xml:space="preserve">
<value>匯入自訂設定失敗</value>
<value>匯入自訂設定設定檔失敗</value>
</data>
<data name="FailedReadConfiguration" xml:space="preserve">
<value>讀取設定失敗</value>
<value>讀取設定失敗</value>
</data>
<data name="FillCorrectServerPort" xml:space="preserve">
<value>請填寫正確格式的埠</value>
@@ -265,13 +265,13 @@
<value>請選擇協定</value>
</data>
<data name="PleaseSelectServer" xml:space="preserve">
<value>請先選擇設定</value>
<value>請先選擇設定</value>
</data>
<data name="RemoveDuplicateServerResult" xml:space="preserve">
<value>去重完成。原數量: {0},現數量: {1}。</value>
<value>設定檔去重完成。原數量: {0},現數量: {1}。</value>
</data>
<data name="RemoveServer" xml:space="preserve">
<value>是否確定移除?</value>
<value>是否確定移除設定檔</value>
</data>
<data name="SaveClientConfigurationIn" xml:space="preserve">
<value>用戶端設定檔儲存在:{0}</value>
@@ -283,10 +283,10 @@
<value>設定成功。{0}</value>
</data>
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
<value>成功匯入自訂節點</value>
<value>成功匯入自訂設定設定檔</value>
</data>
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
<value>成功從剪貼簿匯入 {0} 個節點</value>
<value>成功從剪貼簿匯入 {0} 個設定檔</value>
</data>
<data name="SuccessfullyImportedServerViaScan" 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>
@@ -397,7 +397,7 @@
<value>本機</value>
</data>
<data name="MsgServerTitle" xml:space="preserve">
<value>過濾,按 Enter 執行</value>
<value>設定檔過濾,按 Enter 執行</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve">
<value>檢查更新</value>
@@ -472,76 +472,76 @@
<value>語言 (需重啟)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>從剪貼簿導入分享連結</value>
<value>從剪貼簿導入分享連結 (Ctrl+V)</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>掃描螢幕上的二維碼</value>
<value>掃描螢幕上的二維碼 (Ctrl+S)</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>複製所選</value>
<value>複製所選設定檔</value>
</data>
<data name="menuRemoveDuplicateServer" xml:space="preserve">
<value>移除重複</value>
<value>移除重複的設定檔</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>移除所選 (多選)</value>
<value>移除所選設定檔 (多選) (Delete)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>設為活動</value>
<value>設為活動設定檔 (Enter)</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>清除所有服務統計資料</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>測試真連線延遲 (多選)</value>
<value>測試設定檔真連線延遲 (多選) (Ctrl+R)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>按測試結果排序</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>測試速度 (多選)</value>
<value>測試設定檔速度 (多選) (Ctrl+T)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>測試延遲 Tcping (多選)</value>
<value>測試設定檔延遲 Tcping (多選) (Ctrl+O)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>匯出所選完整設定</value>
<value>匯出所選設定檔完整設定</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>匯出分享連結至剪貼簿 (多選)</value>
<value>匯出分享連結至剪貼簿 (多選) (Ctrl+C)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>新增自訂節點</value>
<value>新增自訂設定設定檔</value>
</data>
<data name="menuAddShadowsocksServer" xml:space="preserve">
<value>新增 [Shadowsocks] 節點</value>
<value>新增 [Shadowsocks] 設定檔</value>
</data>
<data name="menuAddSocksServer" xml:space="preserve">
<value>新增 [SOCKS] 節點</value>
<value>新增 [SOCKS] 設定檔</value>
</data>
<data name="menuAddTrojanServer" xml:space="preserve">
<value>新增 [Trojan] 節點</value>
<value>新增 [Trojan] 設定檔</value>
</data>
<data name="menuAddVlessServer" xml:space="preserve">
<value>新增 [VLESS] 節點</value>
<value>新增 [VLESS] 設定檔</value>
</data>
<data name="menuAddVmessServer" xml:space="preserve">
<value>新增 [VMess] 節點</value>
<value>新增 [VMess] 設定檔</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>全選</value>
<value>全選 (Ctrl+A)</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>清除所有</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>複製</value>
<value>複製 (Ctrl+C)</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>複製所有</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>全選</value>
<value>全選 (Ctrl+A)</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>新增</value>
@@ -676,7 +676,7 @@
<value>Core: 基礎設定</value>
</data>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>v2ray 自訂 DNS</value>
<value>V2ray Custom DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Core: KCP 設定</value>
@@ -691,7 +691,7 @@
<value>Outbound Freedom domainStrategy</value>
</data>
<data name="TbSettingsEnableAutoAdjustMainLvColWidth" xml:space="preserve">
<value>在更新訂閱後自動調整列寬</value>
<value>在更新訂閱後自動調整設定檔列寬</value>
</data>
<data name="TbSettingsEnableCheckPreReleaseUpdate" xml:space="preserve">
<value>檢查 Pre-Release 更新 (請謹慎啟用)</value>
@@ -700,7 +700,7 @@
<value>例外</value>
</data>
<data name="TbSettingsExceptionTip" xml:space="preserve">
<value>例外:對於下列字元開頭的位址,不使用代理。使用分號 (;) 分隔。</value>
<value>例外:對於下列字元開頭的位址,不使用代理設定檔。使用分號 (;) 分隔。</value>
</data>
<data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve">
<value>顯示即時速度(需重啟)</value>
@@ -747,8 +747,11 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>系統代理設定</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>啟用安全協定 TLS v1.3 (訂閱/檢查更新)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>工具列右鍵選單設定展示數量限制</value>
<value>工具列右鍵選單設定展示數量限制</value>
</data>
<data name="TbSettingsUdpEnabled" xml:space="preserve">
<value>開啟 UDP</value>
@@ -781,7 +784,7 @@
<value>PAC 模式</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>分享</value>
<value>分享設定檔 (Ctrl+F)</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>路由</value>
@@ -793,16 +796,16 @@
<value>以管理員身份執行</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>下移至底部</value>
<value>下移至底部 (B)</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>下移</value>
<value>下移 (D)</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>上移至頂部</value>
<value>上移至頂部 (T)</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>上移</value>
<value>上移 (U)</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>過濾 (允許正則)</value>
@@ -817,10 +820,10 @@
<value>一鍵匯入規則集</value>
</data>
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
<value>移除所選規則</value>
<value>移除所選規則 (Delete)</value>
</data>
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>設為活動規則</value>
<value>設為活動規則 (Enter)</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>域名解析策略</value>
@@ -853,7 +856,7 @@
<value>規則列表</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>移除所選規則</value>
<value>移除所選規則 (Delete)</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>路由規則詳情設定</value>
@@ -883,7 +886,7 @@
<value>請勿將代理伺服器用於本機Intranet位址</value>
</data>
<data name="menuMixedTestServer" xml:space="preserve">
<value>一鍵延遲速度測試 (Ctrl+E)</value>
<value>一鍵多執行緒測試延遲速度 (Ctrl+E)</value>
</data>
<data name="LvTestDelay" xml:space="preserve">
<value>延遲 (ms)</value>
@@ -913,7 +916,7 @@
<value>移至訂閱分組</value>
</data>
<data name="TbSettingsEnableDragDropSort" xml:space="preserve">
<value>啟拖放排序 (需重啟)</value>
<value>啟動設定檔拖放排序 (需重啟)</value>
</data>
<data name="TbAutoRefresh" xml:space="preserve">
<value>自動重新整理</value>
@@ -922,10 +925,10 @@
<value>跳過測試</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>編輯</value>
<value>編輯設定檔 (Ctrl+D)</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>主介面輕按兩下設為活動</value>
<value>主介面輕按兩下設為活動設定檔</value>
</data>
<data name="SpeedtestingCompleted" xml:space="preserve">
<value>測試完成</value>
@@ -943,7 +946,7 @@
<value>目前字型 (需重啟)</value>
</data>
<data name="TbSettingsCurrentFontFamilyTip" xml:space="preserve">
<value>複製字型 TTF/TTC 檔案到目錄 guiFonts新啟動後生效</value>
<value>複製字型 TTF/TTC 檔案到目錄 guiFonts啟設定</value>
</data>
<data name="TbSettingsSocksPortTip" xml:space="preserve">
<value>Pac 連接埠 = +3Xray API 連接埠 = +4mihomo API 連接埠 = +5</value>
@@ -976,10 +979,7 @@
<value>啟用硬體加速 (需重啟)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>等待測試中...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>按 ECS 以終止測試</value>
<value>等待測試中(按 ESC 終止)...</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>當有異常斷流時請關閉</value>
@@ -1006,10 +1006,10 @@
<value>不需要轉換時請留空</value>
</data>
<data name="menuDNSSetting" xml:space="preserve">
<value>DNS設定</value>
<value>DNS 設定</value>
</data>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box 自訂 DNS</value>
<value>sing-box Custom DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>請填寫 DNS JSON 結構,點擊查看檔案</value>
@@ -1033,7 +1033,7 @@
<value>Domain</value>
</data>
<data name="menuAddHysteria2Server" xml:space="preserve">
<value>新增 [Hysteria2] 節點</value>
<value>添加 [Hysteria2] 設定檔</value>
</data>
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
<value>Hysteria 最大頻寬 (Up/Dw)</value>
@@ -1042,19 +1042,19 @@
<value>使用系統 hosts</value>
</data>
<data name="menuAddTuicServer" xml:space="preserve">
<value>新增 [TUIC] 節點</value>
<value>新增 [TUIC] 設定檔</value>
</data>
<data name="TbHeaderType8" xml:space="preserve">
<value>擁塞控制算法</value>
</data>
<data name="LvPrevProfile" xml:space="preserve">
<value>前置代理節點別名</value>
<value>前置代理設定檔別名</value>
</data>
<data name="LvNextProfile" xml:space="preserve">
<value>落地代理節點別名</value>
<value>落地代理設定檔別名</value>
</data>
<data name="LvPrevProfileTip" xml:space="preserve">
<value>請確保節點別名存在並且唯一</value>
<value>請確保設定檔別名存在並且唯一</value>
</data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>自動路由</value>
@@ -1075,7 +1075,7 @@
<value>啟用 IPv6</value>
</data>
<data name="menuAddWireguardServer" xml:space="preserve">
<value>新增 [WireGuard] 節點</value>
<value>添加 [WireGuard] 設定檔</value>
</data>
<data name="TbPrivateKey" xml:space="preserve">
<value>PrivateKey</value>
@@ -1108,7 +1108,7 @@
<value>*grpc Authority</value>
</data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>新增 [HTTP] 節點</value>
<value>新增 [HTTP] 設定檔</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>和分組前置代理衝突</value>
@@ -1204,7 +1204,7 @@
<value>重新整理</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>設為活動節點</value>
<value>設為活動節點 (Enter)</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Outbound 預設解析策略</value>
@@ -1222,13 +1222,13 @@
<value>匯出分享連結至剪貼簿 (多選) Base64 編碼</value>
</data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>匯出所選完整設定至剪貼簿</value>
<value>匯出所選設定檔完整設定至剪貼簿</value>
</data>
<data name="menuShowOrHideMainWindow" xml:space="preserve">
<value>顯示或隱藏主介面</value>
</data>
<data name="TbPreSocksPort4Sub" xml:space="preserve">
<value>自訂 Socks 連接埠</value>
<value>自訂設定的 Socks 連接埠</value>
</data>
<data name="menuBackupAndRestore" xml:space="preserve">
<value>備份和還原</value>
@@ -1312,7 +1312,7 @@
<value>請不要使用不安全的 HTTP 協定訂閱位址</value>
</data>
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
<value>安裝字體到系統中,選擇或填入字體名稱,重新啟動後生效</value>
<value>安裝字體到系統中,選擇或填入字體名稱,重新啟動設定</value>
</data>
<data name="menuExitTips" xml:space="preserve">
<value>是否確定退出?</value>
@@ -1339,7 +1339,7 @@
<value>多執行緒測試時的並發數量</value>
</data>
<data name="TbSettingsExceptionTip2" xml:space="preserve">
<value>例外:對於下列位址不使用代理,使用逗號 (,) 分隔。</value>
<value>例外:對於下列位址不使用代理設定檔,使用逗號 (,) 分隔。</value>
</data>
<data name="TbSettingsDestOverride" xml:space="preserve">
<value>流量探測類型</value>
@@ -1375,31 +1375,31 @@
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多選生成策略組</value>
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多隨機 Xray</value>
<value>多設定檔隨機 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多負載平衡 Xray</value>
<value>多設定檔負載平衡 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多最低延遲 Xray</value>
<value>多設定檔最低延遲 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多最穩定 Xray</value>
<value>多設定檔最穩定 Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多最低延遲 sing-box</value>
<value>多設定檔最低延遲 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>匯出</value>
<value>匯出設定檔</value>
</data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>目前連接資訊測試地址</value>
</data>
<data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>可以填寫節點別名,請確保存在並唯一</value>
<value>可以填寫設定檔別名,請確保存在並唯一</value>
</data>
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>密碼錯誤,請重試。</value>
@@ -1408,234 +1408,186 @@
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>新增 [Anytls] 節點</value>
<value>新增 [Anytls] 設定檔</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>遠程 DNS</value>
<value>Remote DNS</value>
</data>
<data name="TbDomesticDNS" xml:space="preserve">
<value>直連 DNS</value>
<value>Domestic DNS</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>通过代理,请确保远程可用</value>
<value>Via proxy — please ensure remote availability</value>
</data>
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray freedom 解析策略</value>
<value>xray Freedom Resolution Strategy</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>sing-box 直連解析策略</value>
<value>sing-box Direct Resolution Strategy</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>sing-box 遠程解析策略</value>
<value>sing-box Remote Resolution Strategy</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>新增常用 DNS Hosts</value>
<value>Add Common DNS Hosts</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
<value>阻止 SVCB HTTPS 查詢</value>
<value>Block SVCB and HTTPS Queries</value>
</data>
<data name="TbDNSHostsConfig" xml:space="preserve">
<value>DNS Hosts“網域名稱1 ip1 ip2” 一行一個)</value>
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
</data>
<data name="ThBasicDNSSettings" xml:space="preserve">
<value>DNS 基礎設定</value>
<value>Basic DNS Settings</value>
</data>
<data name="ThAdvancedDNSSettings" xml:space="preserve">
<value>DNS 進階設定</value>
<value>Advanced DNS Settings</value>
</data>
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
<value>校驗相應地區域名 IP</value>
<value>Validate Regional Domain IPs</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>配置後,會對相應地區域名(如 geosite:cn的返回 IP 進行校驗,僅返回期望 IP</value>
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>啟用自訂 DNS</value>
<value>Enable Custom DNS</value>
</data>
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
<value>自訂 DNS 已啟用,此頁面配置將無效</value>
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>開啟後將阻止 ECH HTTP/3 可用性查詢</value>
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>請填寫正確的配置範本</value>
<value>Please fill in the correct config template</value>
</data>
<data name="menuFullConfigTemplate" xml:space="preserve">
<value>完整配置範本設定</value>
<value>Full Config Template Setting</value>
</data>
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
<value>啟用完整配置範本</value>
<value>Enable Full Config Template</value>
</data>
<data name="TbRayFullConfigTemplate" xml:space="preserve">
<value>v2ray 完整配置範本</value>
<value>v2ray Full Config Template</value>
</data>
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
<value>僅添加出站配置,routing.balancers routing.rules.outboundTag,點擊查看文檔</value>
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>不添加非代理協定出站</value>
<value>Do Not Add Non-Proxy Protocol Outbound</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>設定上游代理 tag</value>
<value>Set Upstream Proxy Tag</value>
</data>
<data name="TbSBFullConfigTemplate" xml:space="preserve">
<value>sing-box 完整配置範本</value>
<value>sing-box Full Config Template</value>
</data>
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
<value>僅添加出站和端點配置,點擊查看文檔</value>
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
</data>
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
<value>此功能供高級用戶和有特殊需求的用戶使用。 啟用此功能後,將忽略 Core 的基礎設定DNS 設定 ,路由設定。你需要保證系統代理的埠和流量統計等功能的配置正確,一切都由你來設定。</value>
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
</data>
<data name="MsgStartParsingSubscription" xml:space="preserve">
<value>開始解析和處理訂閱內容</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>選擇節點</value>
<value>Select Profile</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>默認全域生效,內置 FakeIP 過濾,僅在 sing-box 中生效</value>
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>請至少添加一個節點</value>
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>策略組</value>
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>鏈式代理</value>
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>最低延遲</value>
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>隨機</value>
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>負載均衡</value>
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>最穩定</value>
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>策略組類型</value>
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>添加策略組</value>
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>添加鏈式代理</value>
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>添加子配置</value>
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>刪除子配置</value>
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>子配置項目一,從訂閱分組中自動新增</value>
<value>Server List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>容錯移轉</value>
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>多選容錯移轉 sing-box</value>
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多選容錯移轉 Xray</value>
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支援網路類型 '{1}'.</value>
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>核心 '{0}' 在使用傳輸方式 '{2}' 時不支援協定 '{1}'.</value>
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>核心 '{0}' 不支援協定 '{1}'.</value>
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>代理鏈: </value>
<value>Proxy chained: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>路由規則出站: </value>
<value>Routing rule outbound: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>策略組: </value>
<value>Policy group: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>別名 '{0}' 不存在。</value>
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>組“{0}”為空.請至少添加一個配置。</value>
<value>Group '{0}' is empty. Please add at least one node.</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>{0}屬性無效,請檢查</value>
<value>The {0} property is invalid, please check.</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} 分組不能引用自身或循環引用</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>不支援協定 '{0}'.</value>
<value>Not support protocol '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>如果系統沒有盤功能,請不要開啟</value>
<value>如果系統沒有盤功能,請不要開啟</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>規則類型</value>
<value>规则类型</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>可 Routing 和 DNS 單獨設定規則ALL 都生效</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>解析 DNS 伺服器網域名稱,需指定為 IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>一鍵測試真連線延遲</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>自動從訂閱分組新增過濾後的配置</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>憑證綁定</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>伺服器憑證PEM 格式,可選)
若已指定,憑證將會被綁定,並且「跳過憑證驗證」將被停用。
若使用自簽憑證,或系統中存在不受信任或惡意的 CA「取得憑證」動作可能會失敗。</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>獲取憑證</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>獲取憑證鏈</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>請設定有效的網域名稱</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>尚未設定憑證</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>已設定憑證</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>自訂 PAC 檔案路徑</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>自訂系統代理程式腳本檔案路徑</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS 在 Dock 欄顯示 (需重啟)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>子配置項二,從自建中選擇新增</value>
<value>可 Routing 和 DNS 单独设定规则ALL 都生效</value>
</data>
</root>

View File

@@ -13,19 +13,20 @@
"api.ip.sb"
]
},
{
"remarks": "Google cn",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "阻断udp443",
"outboundTag": "block",
"port": "443",
"network": "udp"
},
{
"remarks": "代理Google",
"outboundTag": "proxy",
"domain": [
"geosite:google"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",

View File

@@ -1,17 +1,18 @@
[
{
"remarks": "Google cn",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "阻断udp443",
"outboundTag": "block",
"port": "443",
"network": "udp"
},
{
"remarks": "代理Google",
"outboundTag": "proxy",
"domain": [
"geosite:google"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",

View File

@@ -14,8 +14,9 @@
],
"rules": [
{
"rule_set": [
"geosite-google"
"domain_suffix": [
"googleapis.cn",
"gstatic.com"
],
"server": "remote",
"strategy": "prefer_ipv4"

View File

@@ -8,7 +8,8 @@
"address": "1.1.1.1",
"skipFallback": true,
"domains": [
"geosite:google"
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{

View File

@@ -28,15 +28,15 @@ fi
kill_children() {
local parent=$1
local children=$(ps -o pid --no-headers --ppid "$parent")
# Output information about processes being terminated
echo "Processing children of PID: $parent..."
# Process each child
for child in $children; do
# Recursively find and kill child's children first
kill_children "$child"
# Force kill the child process
echo "Terminating child process: $child"
kill -9 "$child" 2>/dev/null || true
@@ -47,18 +47,6 @@ echo "============================================"
echo "Starting termination of process $PID and all its children"
echo "============================================"
# Try graceful termination first
echo "Attempting graceful termination (SIGTERM) of PID: $PID"
kill -15 "$PID" 2>/dev/null || true
sleep 1
# If still running, fall back to kill_children
if ps -p $PID > /dev/null; then
echo "Process $PID did not exit after SIGTERM; proceeding with forced termination of its children and itself"
else
echo "Process $PID exited cleanly after SIGTERM"
exit 0
fi
# Find and kill all child processes
kill_children "$PID"

View File

@@ -42,20 +42,6 @@ echo "============================================"
echo "Starting termination of process $PID and all its descendants"
echo "============================================"
# Try graceful termination first
echo "Attempting graceful termination (SIGTERM) of PID: $PID"
kill -15 "$PID" 2>/dev/null || true
sleep 1
# If still running, fall back to kill_descendants
# Use the macOS-native 'kill -0' check
if kill -0 $PID 2>/dev/null; then
echo "Process $PID did not exit after SIGTERM; proceeding with forced termination of its descendants and itself"
else
echo "Process $PID exited cleanly after SIGTERM"
exit 0
fi
# Find and kill all descendant processes
kill_descendants "$PID"

View File

@@ -14,8 +14,9 @@
],
"rules": [
{
"rule_set": [
"geosite-google"
"domain_suffix": [
"googleapis.cn",
"gstatic.com"
],
"server": "remote",
"strategy": "prefer_ipv4"

View File

@@ -57,9 +57,6 @@
<SubType>Designer</SubType>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resx\ResUI.fr.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resx\ResUI.hu.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
@@ -82,4 +79,4 @@
</EmbeddedResource>
</ItemGroup>
</Project>
</Project>

View File

@@ -1,3 +1,7 @@
using System.Net;
using System.Net.NetworkInformation;
using ServiceLib.Common;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService(Config config)

View File

@@ -1,3 +1,5 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService

View File

@@ -138,7 +138,12 @@ public partial class CoreConfigSingboxService
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
{
var finalDns = ParseDnsAddress(simpleDNSItem.BootstrapDNS);
var finalDnsAddress = "local";
if (_config.TunModeItem.EnableTun)
{
finalDnsAddress = "dhcp://auto";
}
var finalDns = ParseDnsAddress(finalDnsAddress);
finalDns.tag = Global.SingboxLocalDNSTag;
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= new List<Server4Sbox>();
@@ -202,9 +207,7 @@ public partial class CoreConfigSingboxService
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null)
{
return 0;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
var expectedIPCidr = new List<string>();
@@ -456,41 +459,79 @@ public partial class CoreConfigSingboxService
return server;
}
var (domain, scheme, port, path) = Utils.ParseUrl(addressFirst);
if (scheme.Equals("dhcp", StringComparison.OrdinalIgnoreCase))
if (addressFirst.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase))
{
var interface_name = addressFirst.Substring(7);
server.type = "dhcp";
if ((!domain.IsNullOrEmpty()) && domain != "auto")
{
server.server = domain;
}
server.Interface = interface_name == "auto" ? null : interface_name;
return server;
}
if (scheme.IsNullOrEmpty())
if (!addressFirst.Contains("://"))
{
// udp dns
server.type = "udp";
}
else
{
// server.type = scheme.ToLower();
// remove "+local" suffix
// TODO: "+local" suffix decide server.detour = "direct" ?
server.type = scheme.Replace("+local", "", StringComparison.OrdinalIgnoreCase).ToLower();
server.server = addressFirst;
return server;
}
server.server = domain;
if (port != 0)
try
{
server.server_port = port;
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal);
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
var uri = new Uri(addressFirst);
server.server = uri.Host;
if (!uri.IsDefaultPort)
{
server.server_port = uri.Port;
}
if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/")
{
server.path = uri.AbsolutePath;
}
}
if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(path) && path != "/")
catch (UriFormatException)
{
server.path = path;
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal);
if (protocolEndIndex > 0)
{
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
var remaining = addressFirst.Substring(protocolEndIndex + 3);
var portIndex = remaining.IndexOf(':');
var pathIndex = remaining.IndexOf('/');
if (portIndex > 0)
{
server.server = remaining.Substring(0, portIndex);
var portPart = pathIndex > portIndex
? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1)
: remaining.Substring(portIndex + 1);
if (int.TryParse(portPart, out var parsedPort))
{
server.server_port = parsedPort;
}
}
else if (pathIndex > 0)
{
server.server = remaining.Substring(0, pathIndex);
}
else
{
server.server = remaining;
}
if (pathIndex > 0 && (server.type == "https" || server.type == "h3"))
{
server.path = remaining.Substring(pathIndex);
}
}
}
return server;
}
}

View File

@@ -61,7 +61,7 @@ public partial class CoreConfigSingboxService
}
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
tunInbound.interface_name = Utils.IsMacOS() ? $"utun{new Random().Next(99)}" : "singbox_tun";
tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun";
tunInbound.mtu = _config.TunModeItem.Mtu;
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
tunInbound.strict_route = _config.TunModeItem.StrictRoute;

View File

@@ -26,7 +26,6 @@ public partial class CoreConfigSingboxService
}
await GenOutboundMux(node, outbound);
await GenOutboundTransport(node, outbound);
break;
}
case EConfigType.Shadowsocks:
@@ -34,50 +33,6 @@ public partial class CoreConfigSingboxService
outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None;
outbound.password = node.Id;
if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp)
{
outbound.plugin = "obfs-local";
outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};";
}
else
{
var pluginArgs = string.Empty;
if (node.Network == nameof(ETransport.ws))
{
pluginArgs += "mode=websocket;";
pluginArgs += $"host={node.RequestHost};";
pluginArgs += $"path={node.Path};";
}
else if (node.Network == nameof(ETransport.quic))
{
pluginArgs += "mode=quic;";
}
if (node.StreamSecurity == Global.StreamSecurity)
{
pluginArgs += "tls;";
var certs = CertPemManager.ParsePemChain(node.Cert);
if (certs.Count > 0)
{
var cert = certs.First();
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
const string endMarker = "\n-----END CERTIFICATE-----";
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
// 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};";
}
}
if (pluginArgs.Length > 0)
{
outbound.plugin = "v2ray-plugin";
outbound.plugin_opts = pluginArgs;
}
}
await GenOutboundMux(node, outbound);
break;
}
@@ -116,8 +71,6 @@ public partial class CoreConfigSingboxService
{
outbound.flow = node.Flow;
}
await GenOutboundTransport(node, outbound);
break;
}
case EConfigType.Trojan:
@@ -125,7 +78,6 @@ public partial class CoreConfigSingboxService
outbound.password = node.Id;
await GenOutboundMux(node, outbound);
await GenOutboundTransport(node, outbound);
break;
}
case EConfigType.Hysteria2:
@@ -175,6 +127,8 @@ public partial class CoreConfigSingboxService
}
await GenOutboundTls(node, outbound);
await GenOutboundTransport(node, outbound);
}
catch (Exception ex)
{
@@ -250,192 +204,6 @@ public partial class CoreConfigSingboxService
return await Task.FromResult<BaseServer4Sbox?>(null);
}
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
{
try
{
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty())
{
var mux = new Multiplex4Sbox()
{
enabled = true,
protocol = _config.Mux4SboxItem.Protocol,
max_connections = _config.Mux4SboxItem.MaxConnections,
padding = _config.Mux4SboxItem.Padding,
};
outbound.multiplex = mux;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenOutboundTls(ProfileItem node, Outbound4Sbox outbound)
{
try
{
if (node.StreamSecurity is not (Global.StreamSecurityReality or Global.StreamSecurity))
{
return await Task.FromResult(0);
}
if (node.ConfigType is EConfigType.Shadowsocks or EConfigType.SOCKS or EConfigType.WireGuard)
{
return await Task.FromResult(0);
}
var server_name = string.Empty;
if (node.Sni.IsNotEmpty())
{
server_name = node.Sni;
}
else if (node.RequestHost.IsNotEmpty())
{
server_name = Utils.String2List(node.RequestHost)?.First();
}
var tls = new Tls4Sbox()
{
enabled = true,
record_fragment = _config.CoreBasicItem.EnableFragment ? true : null,
server_name = server_name,
insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
alpn = node.GetAlpn(),
};
if (node.Fingerprint.IsNotEmpty())
{
tls.utls = new Utls4Sbox()
{
enabled = true,
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
};
}
if (node.StreamSecurity == Global.StreamSecurity)
{
var certs = CertPemManager.ParsePemChain(node.Cert);
if (certs.Count > 0)
{
tls.certificate = certs;
tls.insecure = false;
}
}
else if (node.StreamSecurity == Global.StreamSecurityReality)
{
tls.reality = new Reality4Sbox()
{
enabled = true,
public_key = node.PublicKey,
short_id = node.ShortId
};
tls.insecure = false;
}
outbound.tls = tls;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound)
{
try
{
var transport = new Transport4Sbox();
switch (node.GetNetwork())
{
case nameof(ETransport.h2):
transport.type = nameof(ETransport.http);
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
break;
case nameof(ETransport.tcp): //http
if (node.HeaderType == Global.TcpHeaderHttp)
{
transport.type = nameof(ETransport.http);
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
}
break;
case nameof(ETransport.ws):
transport.type = nameof(ETransport.ws);
var wsPath = node.Path;
// Parse eh and ed parameters from path using regex
if (!wsPath.IsNullOrEmpty())
{
var edRegex = new Regex(@"[?&]ed=(\d+)");
var edMatch = edRegex.Match(wsPath);
if (edMatch.Success && int.TryParse(edMatch.Groups[1].Value, out var edValue))
{
transport.max_early_data = edValue;
transport.early_data_header_name = "Sec-WebSocket-Protocol";
wsPath = edRegex.Replace(wsPath, "");
wsPath = wsPath.Replace("?&", "?");
if (wsPath.EndsWith('?'))
{
wsPath = wsPath.TrimEnd('?');
}
}
var ehRegex = new Regex(@"[?&]eh=([^&]+)");
var ehMatch = ehRegex.Match(wsPath);
if (ehMatch.Success)
{
transport.early_data_header_name = Uri.UnescapeDataString(ehMatch.Groups[1].Value);
}
}
transport.path = wsPath.IsNullOrEmpty() ? null : wsPath;
if (node.RequestHost.IsNotEmpty())
{
transport.headers = new()
{
Host = node.RequestHost
};
}
break;
case nameof(ETransport.httpupgrade):
transport.type = nameof(ETransport.httpupgrade);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
transport.host = node.RequestHost.IsNullOrEmpty() ? null : node.RequestHost;
break;
case nameof(ETransport.quic):
transport.type = nameof(ETransport.quic);
break;
case nameof(ETransport.grpc):
transport.type = nameof(ETransport.grpc);
transport.service_name = node.Path;
transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s");
transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s");
transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream;
break;
default:
break;
}
if (transport.type != null)
{
outbound.transport = transport;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
{
try
@@ -484,6 +252,158 @@ public partial class CoreConfigSingboxService
return await Task.FromResult(0);
}
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
{
try
{
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty())
{
var mux = new Multiplex4Sbox()
{
enabled = true,
protocol = _config.Mux4SboxItem.Protocol,
max_connections = _config.Mux4SboxItem.MaxConnections,
padding = _config.Mux4SboxItem.Padding,
};
outbound.multiplex = mux;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenOutboundTls(ProfileItem node, Outbound4Sbox outbound)
{
try
{
if (node.StreamSecurity == Global.StreamSecurityReality || node.StreamSecurity == Global.StreamSecurity)
{
var server_name = string.Empty;
if (node.Sni.IsNotEmpty())
{
server_name = node.Sni;
}
else if (node.RequestHost.IsNotEmpty())
{
server_name = Utils.String2List(node.RequestHost)?.First();
}
var tls = new Tls4Sbox()
{
enabled = true,
record_fragment = _config.CoreBasicItem.EnableFragment,
server_name = server_name,
insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
alpn = node.GetAlpn(),
};
if (node.Fingerprint.IsNotEmpty())
{
tls.utls = new Utls4Sbox()
{
enabled = true,
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
};
}
if (node.StreamSecurity == Global.StreamSecurityReality)
{
tls.reality = new Reality4Sbox()
{
enabled = true,
public_key = node.PublicKey,
short_id = node.ShortId
};
tls.insecure = false;
}
outbound.tls = tls;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound)
{
try
{
var transport = new Transport4Sbox();
switch (node.GetNetwork())
{
case nameof(ETransport.h2):
transport.type = nameof(ETransport.http);
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
break;
case nameof(ETransport.tcp): //http
if (node.HeaderType == Global.TcpHeaderHttp)
{
if (node.ConfigType == EConfigType.Shadowsocks)
{
outbound.plugin = "obfs-local";
outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};";
}
else
{
transport.type = nameof(ETransport.http);
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
}
}
break;
case nameof(ETransport.ws):
transport.type = nameof(ETransport.ws);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
if (node.RequestHost.IsNotEmpty())
{
transport.headers = new()
{
Host = node.RequestHost
};
}
break;
case nameof(ETransport.httpupgrade):
transport.type = nameof(ETransport.httpupgrade);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
transport.host = node.RequestHost.IsNullOrEmpty() ? null : node.RequestHost;
break;
case nameof(ETransport.quic):
transport.type = nameof(ETransport.quic);
break;
case nameof(ETransport.grpc):
transport.type = nameof(ETransport.grpc);
transport.service_name = node.Path;
transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s");
transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s");
transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream;
break;
default:
break;
}
if (transport.type != null)
{
outbound.transport = transport;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig)
{
if (node.Subid.IsNullOrEmpty())
@@ -748,10 +668,7 @@ public partial class CoreConfigSingboxService
{
var node = nodes[i];
if (node == null)
{
continue;
}
if (node.ConfigType.IsGroupType())
{
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);

View File

@@ -250,9 +250,7 @@ public partial class CoreConfigSingboxService
foreach (var it in item.Domain)
{
if (ParseV2Domain(it, rule1))
{
countDomain++;
}
}
if (countDomain > 0)
{
@@ -267,9 +265,7 @@ public partial class CoreConfigSingboxService
foreach (var it in item.Ip)
{
if (ParseV2Address(it, rule2))
{
countIp++;
}
}
if (countIp > 0)
{
@@ -300,7 +296,7 @@ public partial class CoreConfigSingboxService
private bool ParseV2Domain(string domain, Rule4Sbox rule)
{
if (domain.StartsWith('#') || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
if (domain.StartsWith("#") || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
{
return false;
}
@@ -316,8 +312,10 @@ public partial class CoreConfigSingboxService
}
else if (domain.StartsWith("domain:"))
{
rule.domain ??= [];
rule.domain_suffix ??= [];
rule.domain_suffix?.Add(domain.Substring(7));
rule.domain?.Add(domain.Substring(7));
rule.domain_suffix?.Add("." + domain.Substring(7));
}
else if (domain.StartsWith("full:"))
{

View File

@@ -7,9 +7,7 @@ public partial class CoreConfigSingboxService
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
{
if (rule_set != null)
{
ruleSets.AddRange(rule_set);
}
}
var geosite = "geosite";
var geoip = "geoip";

View File

@@ -1,3 +1,6 @@
using System.Net;
using System.Net.NetworkInformation;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService(Config config)
@@ -94,8 +97,8 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
@@ -137,9 +140,7 @@ public partial class CoreConfigV2rayService(Config config)
foreach (var rule in rules)
{
if (rule.outboundTag == null)
{
continue;
}
if (balancerTagSet.Contains(rule.outboundTag))
{
@@ -202,8 +203,8 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;

View File

@@ -11,9 +11,7 @@ public partial class CoreConfigV2rayService
// Case 1: exact match already exists -> nothing to do
if (subjectSelectors.Any(baseTagName.StartsWith))
{
return await Task.FromResult(0);
}
// Case 2: prefix match exists -> reuse it and move to the first position
var matched = subjectSelectors.FirstOrDefault(s => s.StartsWith(baseTagName));

View File

@@ -1,3 +1,5 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
@@ -86,8 +88,8 @@ public partial class CoreConfigV2rayService
}
}
var customOutboundsNode = new JsonArray();
// Handle outbounds - append instead of override
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
foreach (var outbound in v2rayConfig.outbounds)
{
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
@@ -97,30 +99,14 @@ public partial class CoreConfigV2rayService
continue;
}
}
else if ((!fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
&& ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true) == true))
else if ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() == true) && (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) && !(Utils.IsPrivateNetwork(outbound.settings?.servers?.FirstOrDefault()?.address ?? string.Empty) || Utils.IsPrivateNetwork(outbound.settings?.vnext?.FirstOrDefault()?.address ?? string.Empty)))
{
var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address
?? outbound.settings?.vnext?.FirstOrDefault()?.address
?? string.Empty;
if (!Utils.IsPrivateNetwork(outboundAddress))
{
outbound.streamSettings ??= new StreamSettings4Ray();
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
outbound.streamSettings.sockopt.dialerProxy = fullConfigTemplate.ProxyDetour;
}
outbound.streamSettings ??= new StreamSettings4Ray();
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
outbound.streamSettings.sockopt.dialerProxy = fullConfigTemplate.ProxyDetour;
}
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
}
if (fullConfigTemplateNode["outbounds"] is JsonArray templateOutbounds)
{
foreach (var outbound in templateOutbounds)
{
customOutboundsNode.Add(outbound?.DeepClone());
}
}
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));

Some files were not shown because too many files have changed in this diff Show More