Compare commits

..

64 Commits

Author SHA1 Message Date
DHR60
03b62b3d78 Fix (#8658) 2026-01-17 19:35:56 +08:00
DHR60
9f9b90cb97 Add hysteria2 uri cert sha pinning support (#8657) 2026-01-17 16:22:26 +08:00
DHR60
c42dcd2876 Add process matching rules support (#8643)
* Add process matching rules support

* Fix
2026-01-17 16:08:36 +08:00
2dust
2fefafdd37 Add support for CoreType7 (Hysteria2) in option settings 2026-01-17 16:06:29 +08:00
DHR60
2c9a90c878 Add xray hysteria2 outbound support (#8630) 2026-01-17 15:49:44 +08:00
DHR60
4e5f1838a2 Add Cert SHA-256 pinning support (#8613) 2026-01-17 15:42:40 +08:00
2dust
a45a1dc982 Ensure WebDAV base URL ends with trailing slash 2026-01-17 15:08:08 +08:00
2dust
fe183798b6 Refactor child item aggregation in managers 2026-01-13 20:24:52 +08:00
2dust
947c84cf10 Refactor 'Move to Group' menu in ProfilesView 2026-01-10 15:14:58 +08:00
2dust
9c74b51d74 up 7.17.0 2026-01-09 18:44:28 +08:00
2dust
abd962ab31 Update Global.cs 2026-01-08 17:15:59 +08:00
DHR60
f3b894015e Add sing-box ech support (#8603)
* Add sing-box ech support

* Support group config type

* Simplified code
2026-01-08 13:56:45 +08:00
2dust
4562d4cf00 Add ECH config support to profile and UI
Introduces EchConfigList and EchForceQuery fields to ProfileItem and V2rayConfig models, updates related handlers and services to process these fields, and extends the AddServerWindow UI to allow user input for ECH configuration. Also adds localization entries for the new fields and updates extension methods for string handling.
2026-01-07 11:34:13 +08:00
JieXu
bc36cf8a47 Code Clean (#8586) 2026-01-05 09:56:43 +08:00
Kazuto Iris
cbdfe2e15a fix: Fix failure to follow system theme changes (#8584)
Fix the issue where the application failed to sync with system dark/light mode changes in specific scenarios such as triggering system theme switching via scheduled tasks while waking from hibernation, caused by the unreliable HWND hook implementation that missed critical events.
2026-01-05 09:56:33 +08:00
2dust
68583e20bc Update package versions in Directory.Packages.props 2026-01-03 19:06:17 +08:00
DHR60
6d6459b009 Fix edge cases (#8564) 2026-01-03 10:20:27 +08:00
2dust
807562b69e Set all .NET publish tasks to self-contained 2025-12-28 14:10:00 +08:00
2dust
654d7d83d0 up 7.16.9 2025-12-25 18:34:10 +08:00
2dust
027252e687 Move ShowInTaskbar and RunningCoreType to AppManager 2025-12-24 16:01:28 +08:00
2dust
5478c90180 Bug fix
https://github.com/2dust/v2rayN/issues/8515
2025-12-24 14:19:36 +08:00
DHR60
28f30d7e97 Revert "Add TLS ALPN check for WS (#8469)" (#8517)
This reverts commit 6e27dca6cd.
2025-12-24 13:38:08 +08:00
2dust
ae7d54c2e5 up 7.16.8 2025-12-22 19:04:36 +08:00
2dust
56d0d65b06 Reduce minimum width of MainWindow 2025-12-22 19:03:47 +08:00
2dust
5e8e189c27 Increase MenuItemHeight to 32 in App.xaml 2025-12-21 18:53:09 +08:00
2dust
3fee86d44a Add context menu to subscription DataGrid 2025-12-21 18:53:00 +08:00
2dust
dd77eb79c6 Remove .NET self-contained zip check in UpdateService 2025-12-20 14:47:40 +08:00
2dust
d26a2559a6 up 7.16.7 2025-12-20 14:12:41 +08:00
2dust
e5ba1759aa Update Directory.Packages.props 2025-12-20 14:12:16 +08:00
dependabot[bot]
bfdee37cc1 Bump actions/upload-artifact from 5.0.0 to 6.0.0 (#8493)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 09:19:09 +08:00
dependabot[bot]
cf89cfcd95 Bump actions/download-artifact from 6 to 7 (#8492)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 09:19:00 +08:00
2dust
39a988c704 Update Directory.Packages.props 2025-12-13 15:18:52 +08:00
JieXu
2b28254fbc Update ResUI.fr.resx (#8472) 2025-12-10 17:56:19 +08:00
DHR60
6e27dca6cd Add TLS ALPN check for WS (#8469) 2025-12-09 20:22:13 +08:00
DHR60
7cee98887b Refactor Node Precheck (#8464) 2025-12-09 20:03:07 +08:00
DHR60
3885ff8b31 Fix Shadowsocks Fmt (#8462) 2025-12-08 19:55:27 +08:00
2dust
12abf383e9 up 7.16.6 2025-12-07 15:32:45 +08:00
2dust
5bef02bd6d Code clean 2025-12-07 15:32:03 +08:00
2dust
592f1260b5 Remove Cloudflare IP API URL from IPAPIUrls
https://github.com/2dust/v2rayN/issues/8441
2025-12-07 15:24:54 +08:00
2dust
18303688d7 Refactor AddGroupServerWindow tab controls layout 2025-12-07 15:22:40 +08:00
2dust
5c4b7f6636 Update Directory.Packages.props 2025-12-07 15:22:19 +08:00
tt2563
37cce2fa35 「desktop版本-啟用連線資訊測試位址自訂輸入」 (#8456) 2025-12-07 15:21:11 +08:00
dependabot[bot]
6f8b65c75b Bump actions/checkout from 6.0.0 to 6.0.1 (#8437)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.0...v6.0.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-04 18:24:14 +08:00
2dust
83c63b914a up 7.16.5 2025-11-29 19:59:48 +08:00
DHR60
1ca2485d2a Fix (#8407) 2025-11-29 19:58:51 +08:00
2dust
cc4154bb0d Increase UI grid column widths and font size options 2025-11-28 20:31:40 +08:00
2dust
d7f77f220c Improve group text description 2025-11-27 19:55:33 +08:00
JieXu
86f45d103d Update build-linux.yml to use Red Hat UBI image (#8392) 2025-11-27 15:01:44 +08:00
2dust
0077751f75 Add subscription delete functionality to ProfilesView 2025-11-26 20:11:14 +08:00
dependabot[bot]
fa2b4b3dc9 Bump actions/setup-dotnet from 5.0.0 to 5.0.1 (#8387)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-26 14:31:34 +08:00
DHR60
23cacb8339 Format imported xhttp extra (#8390) 2025-11-26 14:31:14 +08:00
DHR60
9ffa6a4eb6 Remove formatted spaces from extra JSON before URL encoding (#8385) 2025-11-25 17:40:41 +08:00
2dust
386209b835 Fix 2025-11-24 19:12:49 +08:00
jiuqianyuan
830dc89c32 Fix: tcping high latency and speedtest displayed 0 (#8374)
* Fix: High latency in tcping test due to thread blocking

* Fix: download to fast, speed displayed as 0.

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2025-11-24 19:01:04 +08:00
2dust
693afe3560 up 7.16.4 2025-11-23 14:33:01 +08:00
2dust
95361e8b65 Simplify configuration-related resource strings 2025-11-23 14:31:29 +08:00
2dust
3ff7299aca Refactor update result handling and model 2025-11-23 14:06:34 +08:00
DHR60
34fc4de0c2 Avoid xray warning (#8369) 2025-11-22 19:17:18 +08:00
DHR60
91536d3923 Fix sing-box ws (#8367)
* Fix sing-box ws

* Parse eh
2025-11-22 16:40:07 +08:00
2dust
6b87c09a96 Add confirmation before removing duplicate servers
https://github.com/2dust/v2rayN/issues/8365
2025-11-22 10:20:16 +08:00
2dust
ecaac2ac61 Fix
https://github.com/2dust/v2rayN/discussions/8366
2025-11-22 10:16:53 +08:00
2dust
ad74b1584d Refactor dialog layouts 2025-11-21 20:30:41 +08:00
dependabot[bot]
514d76953a Bump actions/checkout from 5.0.1 to 6.0.0 (#8359)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6.0.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-21 15:57:37 +08:00
DHR60
5b82f17995 Fix (#8363)
* Fix

* AI-optimized code
2025-11-21 15:56:42 +08:00
84 changed files with 1793 additions and 934 deletions

View File

@@ -31,13 +31,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5.0.1
uses: actions/checkout@v6.0.1
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup .NET
uses: actions/setup-dotnet@v5.0.0
uses: actions/setup-dotnet@v5.0.1
with:
dotnet-version: '8.0.x'
@@ -50,7 +50,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-linux
path: |
@@ -97,26 +97,26 @@ jobs:
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
runs-on: ubuntu-24.04
container:
image: quay.io/almalinuxorg/10-base:latest
options: --platform=linux/amd64/v2
image: registry.access.redhat.com/ubi10/ubi
env:
RELEASE_TAG: ${{ github.event.inputs.release_tag != '' && github.event.inputs.release_tag || github.ref_name }}
steps:
- name: Prepare tools (Red Hat)
run: |
dnf repolist all
dnf -y makecache
dnf -y install epel-release
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@v5.0.1
uses: actions/checkout@v6.0.1
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Restore build artifacts
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: v2rayN-linux
path: ${{ github.workspace }}/v2rayN/Release
@@ -137,7 +137,7 @@ jobs:
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
- name: Upload RPM artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-rpm
path: dist/rpm/**/*.rpm

View File

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

View File

@@ -26,13 +26,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5.0.1
uses: actions/checkout@v6.0.1
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v5.0.0
uses: actions/setup-dotnet@v5.0.1
with:
dotnet-version: '8.0.x'
@@ -45,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-windows-desktop
path: |

View File

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

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
# ====== Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS ======
if [[ -r /etc/os-release ]]; then
. /etc/os-release
case "$ID" in
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
rhel|rocky|almalinux|fedora|centos)
echo "[OK] Detected supported system: $NAME $VERSION_ID"
;;
*)
@@ -30,7 +30,7 @@ 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+)."
echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+)."
exit 1
fi
@@ -80,7 +80,6 @@ host_arch="$(uname -m)"
install_ok=0
case "$ID" in
# ------------------------------ RHEL family (UNCHANGED) ------------------------------
rhel|rocky|almalinux|centos)
if command -v dnf >/dev/null 2>&1; then
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
@@ -92,58 +91,7 @@ case "$ID" in
install_ok=1
fi
;;
# ------------------------------ Ubuntu ----------------------------------------------
ubuntu)
sudo apt-get update
# Ensure 'universe' (Ubuntu) to get 'rpm'
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
sudo apt-get -y install software-properties-common || true
sudo add-apt-repository -y universe || true
sudo apt-get update
fi
# Base tools + rpm (provides rpmbuild)
sudo apt-get -y install curl unzip tar rsync rpm || true
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
# rpmbuild presence check
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
exit 1
fi
# .NET SDK 8 (best effort via apt)
if ! command -v dotnet >/dev/null 2>&1; then
sudo apt-get -y install dotnet-sdk-8.0 || true
sudo apt-get -y install dotnet-sdk-8 || true
sudo apt-get -y install dotnet-sdk || true
fi
install_ok=1
;;
# ------------------------------ Debian (KEEP, with local dotnet install) ------------
debian)
sudo apt-get update
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip
sudo apt-get -y install curl unzip tar rsync rpm binutils || true
# rpmbuild presence check
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
echo " Please ensure 'rpm' is available from Debian repos."
exit 1
fi
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet
if ! command -v dotnet >/dev/null 2>&1; then
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..."
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh"
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet"
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
export DOTNET_ROOT="$HOME/.dotnet"
if ! command -v dotnet >/dev/null 2>&1; then
echo "[ERROR] dotnet installation failed."
exit 1
fi
fi
install_ok=1
*)
;;
esac
@@ -154,7 +102,7 @@ fi
command -v curl >/dev/null
# Root directory = the script's location
# Root directory
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
@@ -164,14 +112,14 @@ if [[ -f .gitmodules ]]; then
git submodule update --init --recursive || true
fi
# ===== Locate project ================================================================
# Locate project
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
if [[ ! -f "$PROJECT" ]]; then
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
fi
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
# ===== Resolve GUI version & auto checkout ============================================
# Resolve GUI version & auto checkout
VERSION=""
choose_channel() {
@@ -391,22 +339,6 @@ download_singbox() {
install -Dm755 "$bin" "$outdir/sing-box"
}
# ---- NEW: download_mihomo (REQUIRED in --netcore mode) ----
download_mihomo() {
# Download mihomo into outroot/bin/mihomo/mihomo
local outroot="$1"
local url=""
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64/bin/mihomo/mihomo"
else
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo"
fi
echo "[+] Download mihomo: $url"
mkdir -p "$outroot/bin/mihomo"
curl -fL "$url" -o "$outroot/bin/mihomo/mihomo"
chmod +x "$outroot/bin/mihomo/mihomo" || true
}
# Move geo files to a unified path: outroot/bin
unify_geo_layout() {
local outroot="$1"
@@ -491,8 +423,7 @@ download_v2rayn_bundle() {
fi
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
# keep mihomo
# find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
local nested_dir
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
@@ -603,7 +534,7 @@ build_for_arch() {
fi
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
# download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
fi
# Tarball

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.16.3</Version>
<Version>7.17.0</Version>
</PropertyGroup>
<PropertyGroup>

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="Avalonia.Controls.DataGrid" Version="11.3.10" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.10" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.10" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" />
<PackageVersion Include="CliWrap" Version="3.10.0" />
<PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
<PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="ReactiveUI" Version="22.2.1" />
<PackageVersion Include="ReactiveUI" Version="22.3.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="22.2.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.1" />
<PackageVersion Include="ReactiveUI.WPF" Version="22.3.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.2" />
<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.2" />
<PackageVersion Include="NLog" Version="6.0.7" />
<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

@@ -6,17 +6,17 @@ public static class Extension
{
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
{
return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value);
}
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
{
return string.IsNullOrWhiteSpace(value);
return string.IsNullOrWhiteSpace(value) || string.IsNullOrEmpty(value);
}
public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
{
return !string.IsNullOrEmpty(value);
return !string.IsNullOrWhiteSpace(value);
}
public static string? NullIfEmpty(this string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value;
}
public static bool BeginWithAny(this string s, IEnumerable<char> chars)

View File

@@ -73,6 +73,7 @@ public class Global
public const string GrpcMultiMode = "multi";
public const int MaxPort = 65536;
public const int MinFontSize = 8;
public const int MinFontSizeCount = 13;
public const string RebootAs = "rebootas";
public const string AvaAssets = "avares://v2rayN/Assets/";
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2";
@@ -87,232 +88,236 @@ public class Global
public const string SingboxLocalDNSTag = "local_local";
public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns";
public const string SingboxEchDNSTag = "ech_dns";
public static readonly List<string> IEProxyProtocols =
[
"{ip}:{http_port}",
"socks={ip}:{socks_port}",
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
""
"socks={ip}:{socks_port}",
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
""
];
public static readonly List<string> SubConvertUrls =
[
@"https://sub.xeton.dev/sub?url={0}",
@"https://api.dler.io/sub?url={0}",
@"http://127.0.0.1:25500/sub?url={0}",
""
@"https://api.dler.io/sub?url={0}",
@"http://127.0.0.1:25500/sub?url={0}",
""
];
public static readonly List<string> SubConvertConfig =
[@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"];
[
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
];
public static readonly List<string> SubConvertTargets =
[
"",
"mixed",
"v2ray",
"clash",
"ss"
"mixed",
"v2ray",
"clash",
"ss"
];
public static readonly List<string> SpeedTestUrls =
[
@"https://cachefly.cachefly.net/50mb.test",
@"https://speed.cloudflare.com/__down?bytes=10000000",
@"https://speed.cloudflare.com/__down?bytes=50000000",
@"https://speed.cloudflare.com/__down?bytes=100000000",
];
@"https://speed.cloudflare.com/__down?bytes=10000000",
@"https://speed.cloudflare.com/__down?bytes=50000000",
@"https://speed.cloudflare.com/__down?bytes=100000000",
];
public static readonly List<string> SpeedPingTestUrls =
[
@"https://www.google.com/generate_204",
@"https://www.gstatic.com/generate_204",
@"https://www.apple.com/library/test/success.html",
@"http://www.msftconnecttest.com/connecttest.txt"
@"https://www.gstatic.com/generate_204",
@"https://www.apple.com/library/test/success.html",
@"http://www.msftconnecttest.com/connecttest.txt"
];
public static readonly List<string> GeoFilesSources =
[
"",
@"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat",
@"https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/{0}.dat"
@"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat",
@"https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/{0}.dat"
];
public static readonly List<string> SingboxRulesetSources =
[
"",
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
];
[
"",
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
];
public static readonly List<string> RoutingRulesSources =
[
"",
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/template.json",
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/template.json"
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/template.json",
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/template.json"
];
public static readonly List<string> DNSTemplateSources =
[
"",
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/",
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/"
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/",
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/"
];
public static readonly Dictionary<string, string> UserAgentTexts = new()
{
{"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" },
{"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" },
{"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" },
{"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" },
{"none",""}
};
{
{"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" },
{"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" },
{"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" },
{"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" },
{"none",""}
};
public const string Hysteria2ProtocolShare = "hy2://";
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
{
{ EConfigType.VMess, "vmess://" },
{ EConfigType.Shadowsocks, "ss://" },
{ EConfigType.SOCKS, "socks://" },
{ EConfigType.VLESS, "vless://" },
{ EConfigType.Trojan, "trojan://" },
{ EConfigType.Hysteria2, "hysteria2://" },
{ EConfigType.TUIC, "tuic://" },
{ EConfigType.WireGuard, "wireguard://" },
{ EConfigType.Anytls, "anytls://" }
};
{
{ EConfigType.VMess, "vmess://" },
{ EConfigType.Shadowsocks, "ss://" },
{ EConfigType.SOCKS, "socks://" },
{ EConfigType.VLESS, "vless://" },
{ EConfigType.Trojan, "trojan://" },
{ EConfigType.Hysteria2, "hysteria2://" },
{ EConfigType.TUIC, "tuic://" },
{ EConfigType.WireGuard, "wireguard://" },
{ EConfigType.Anytls, "anytls://" }
};
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
{
{ EConfigType.VMess, "vmess" },
{ EConfigType.Shadowsocks, "shadowsocks" },
{ EConfigType.SOCKS, "socks" },
{ EConfigType.HTTP, "http" },
{ EConfigType.VLESS, "vless" },
{ EConfigType.Trojan, "trojan" },
{ EConfigType.Hysteria2, "hysteria2" },
{ EConfigType.TUIC, "tuic" },
{ EConfigType.WireGuard, "wireguard" },
{ EConfigType.Anytls, "anytls" }
};
{
{ EConfigType.VMess, "vmess" },
{ EConfigType.Shadowsocks, "shadowsocks" },
{ EConfigType.SOCKS, "socks" },
{ EConfigType.HTTP, "http" },
{ EConfigType.VLESS, "vless" },
{ EConfigType.Trojan, "trojan" },
{ EConfigType.Hysteria2, "hysteria2" },
{ EConfigType.TUIC, "tuic" },
{ EConfigType.WireGuard, "wireguard" },
{ EConfigType.Anytls, "anytls" }
};
public static readonly List<string> VmessSecurities =
[
"aes-128-gcm",
"chacha20-poly1305",
"auto",
"none",
"zero"
"chacha20-poly1305",
"auto",
"none",
"zero"
];
public static readonly List<string> SsSecurities =
[
"aes-256-gcm",
"aes-128-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"none",
"plain"
"aes-128-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"none",
"plain"
];
public static readonly List<string> SsSecuritiesInXray =
[
"aes-256-gcm",
"aes-128-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"xchacha20-poly1305",
"xchacha20-ietf-poly1305",
"none",
"plain",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305"
"aes-128-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"xchacha20-poly1305",
"xchacha20-ietf-poly1305",
"none",
"plain",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305"
];
public static readonly List<string> SsSecuritiesInSingbox =
[
"aes-256-gcm",
"aes-192-gcm",
"aes-128-gcm",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305",
"none",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305",
"aes-128-ctr",
"aes-192-ctr",
"aes-256-ctr",
"aes-128-cfb",
"aes-192-cfb",
"aes-256-cfb",
"rc4-md5",
"chacha20-ietf",
"xchacha20"
"aes-192-gcm",
"aes-128-gcm",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305",
"none",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305",
"aes-128-ctr",
"aes-192-ctr",
"aes-256-ctr",
"aes-128-cfb",
"aes-192-cfb",
"aes-256-cfb",
"rc4-md5",
"chacha20-ietf",
"xchacha20"
];
public static readonly List<string> Flows =
[
"",
"xtls-rprx-vision",
"xtls-rprx-vision-udp443"
"xtls-rprx-vision",
"xtls-rprx-vision-udp443"
];
public static readonly List<string> Networks =
[
"tcp",
"kcp",
"ws",
"httpupgrade",
"xhttp",
"h2",
"quic",
"grpc"
"kcp",
"ws",
"httpupgrade",
"xhttp",
"h2",
"quic",
"grpc"
];
public static readonly List<string> KcpHeaderTypes =
[
"srtp",
"utp",
"wechat-video",
"dtls",
"wireguard",
"dns"
"utp",
"wechat-video",
"dtls",
"wireguard",
"dns"
];
public static readonly List<string> CoreTypes =
[
"Xray",
"sing_box"
"sing_box"
];
public static readonly HashSet<EConfigType> XraySupportConfigType =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.Hysteria2,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
];
public static readonly HashSet<EConfigType> SingboxSupportConfigType =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.Hysteria2,
EConfigType.TUIC,
EConfigType.Anytls,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.Hysteria2,
EConfigType.TUIC,
EConfigType.Anytls,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
];
public static readonly HashSet<EConfigType> SingboxOnlyConfigType = SingboxSupportConfigType.Except(XraySupportConfigType).ToHashSet();
@@ -327,129 +332,129 @@ public class Global
public static readonly List<string> DomainStrategies4Singbox =
[
"ipv4_only",
"ipv6_only",
"prefer_ipv4",
"prefer_ipv6",
""
"ipv6_only",
"prefer_ipv4",
"prefer_ipv6",
""
];
public static readonly List<string> Fingerprints =
[
"chrome",
"firefox",
"safari",
"ios",
"android",
"edge",
"360",
"qq",
"random",
"randomized",
""
"firefox",
"safari",
"ios",
"android",
"edge",
"360",
"qq",
"random",
"randomized",
""
];
public static readonly List<string> UserAgent =
[
"chrome",
"firefox",
"safari",
"edge",
"none"
"firefox",
"safari",
"edge",
"none"
];
public static readonly List<string> XhttpMode =
[
"auto",
"packet-up",
"stream-up",
"stream-one"
"packet-up",
"stream-up",
"stream-one"
];
public static readonly List<string> AllowInsecure =
[
"true",
"false",
""
"false",
""
];
public static readonly List<string> DomainStrategy4Freedoms =
[
"AsIs",
"UseIP",
"UseIPv4",
"UseIPv6",
""
"UseIP",
"UseIPv4",
"UseIPv6",
""
];
public static readonly List<string> SingboxDomainStrategy4Out =
[
"",
"ipv4_only",
"prefer_ipv4",
"prefer_ipv6",
"ipv6_only"
"ipv4_only",
"prefer_ipv4",
"prefer_ipv6",
"ipv6_only"
];
public static readonly List<string> DomainDirectDNSAddress =
[
"https://dns.alidns.com/dns-query",
"https://doh.pub/dns-query",
"223.5.5.5",
"119.29.29.29",
"localhost"
"https://doh.pub/dns-query",
"223.5.5.5",
"119.29.29.29",
"localhost"
];
public static readonly List<string> DomainRemoteDNSAddress =
[
"https://cloudflare-dns.com/dns-query",
"https://dns.cloudflare.com/dns-query",
"https://dns.google/dns-query",
"https://doh.dns.sb/dns-query",
"https://doh.opendns.com/dns-query",
"https://common.dot.dns.yandex.net",
"8.8.8.8",
"1.1.1.1",
"185.222.222.222",
"208.67.222.222",
"77.88.8.8"
"https://dns.cloudflare.com/dns-query",
"https://dns.google/dns-query",
"https://doh.dns.sb/dns-query",
"https://doh.opendns.com/dns-query",
"https://common.dot.dns.yandex.net",
"8.8.8.8",
"1.1.1.1",
"185.222.222.222",
"208.67.222.222",
"77.88.8.8"
];
public static readonly List<string> DomainPureIPDNSAddress =
[
"223.5.5.5",
"119.29.29.29",
"localhost"
"119.29.29.29",
"localhost"
];
public static readonly List<string> Languages =
[
"zh-Hans",
"zh-Hant",
"en",
"fa-Ir",
"fr",
"ru",
"hu"
"zh-Hant",
"en",
"fa-Ir",
"fr",
"ru",
"hu"
];
public static readonly List<string> Alpns =
[
"h3",
"h2",
"http/1.1",
"h3,h2",
"h2,http/1.1",
"h3,h2,http/1.1",
""
"h2",
"http/1.1",
"h3,h2",
"h2,http/1.1",
"h3,h2,http/1.1",
""
];
public static readonly List<string> LogLevels =
[
"debug",
"info",
"warning",
"error",
"none"
"info",
"warning",
"error",
"none"
];
public static readonly Dictionary<string, string> LogLevelColors = new()
@@ -463,32 +468,32 @@ public class Global
public static readonly List<string> InboundTags =
[
"socks",
"socks2",
"socks3"
"socks2",
"socks3"
];
public static readonly List<string> RuleProtocols =
[
"http",
"tls",
"bittorrent"
"tls",
"bittorrent"
];
public static readonly List<string> RuleNetworks =
[
"",
"tcp",
"udp",
"tcp,udp"
"tcp",
"udp",
"tcp,udp"
];
public static readonly List<string> destOverrideProtocols =
[
"http",
"tls",
"quic",
"fakedns",
"fakedns+others"
"tls",
"quic",
"fakedns",
"fakedns+others"
];
public static readonly List<int> TunMtus =
@@ -504,88 +509,87 @@ public class Global
public static readonly List<string> TunStacks =
[
"gvisor",
"system",
"mixed"
"system",
"mixed"
];
public static readonly List<string> PresetMsgFilters =
[
"proxy",
"direct",
"block",
""
"direct",
"block",
""
];
public static readonly List<string> SingboxMuxs =
[
"h2mux",
"smux",
"yamux",
""
"smux",
"yamux",
""
];
public static readonly List<string> TuicCongestionControls =
[
"cubic",
"new_reno",
"bbr"
"new_reno",
"bbr"
];
public static readonly List<string> allowSelectType =
[
"selector",
"urltest",
"loadbalance",
"fallback"
"urltest",
"loadbalance",
"fallback"
];
public static readonly List<string> notAllowTestType =
[
"selector",
"urltest",
"direct",
"reject",
"compatible",
"pass",
"loadbalance",
"fallback"
"urltest",
"direct",
"reject",
"compatible",
"pass",
"loadbalance",
"fallback"
];
public static readonly List<string> proxyVehicleType =
[
"file",
"http"
"http"
];
public static readonly Dictionary<ECoreType, string> CoreUrls = new()
{
{ ECoreType.v2fly, "v2fly/v2ray-core" },
{ ECoreType.v2fly_v5, "v2fly/v2ray-core" },
{ ECoreType.Xray, "XTLS/Xray-core" },
{ ECoreType.sing_box, "SagerNet/sing-box" },
{ ECoreType.mihomo, "MetaCubeX/mihomo" },
{ ECoreType.hysteria, "apernet/hysteria" },
{ ECoreType.hysteria2, "apernet/hysteria" },
{ ECoreType.naiveproxy, "klzgrad/naiveproxy" },
{ ECoreType.tuic, "EAimTY/tuic" },
{ ECoreType.juicity, "juicity/juicity" },
{ ECoreType.brook, "txthinking/brook" },
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
{ ECoreType.mieru, "enfein/mieru" },
{ ECoreType.v2rayN, "2dust/v2rayN" },
};
{
{ ECoreType.v2fly, "v2fly/v2ray-core" },
{ ECoreType.v2fly_v5, "v2fly/v2ray-core" },
{ ECoreType.Xray, "XTLS/Xray-core" },
{ ECoreType.sing_box, "SagerNet/sing-box" },
{ ECoreType.mihomo, "MetaCubeX/mihomo" },
{ ECoreType.hysteria, "apernet/hysteria" },
{ ECoreType.hysteria2, "apernet/hysteria" },
{ ECoreType.naiveproxy, "klzgrad/naiveproxy" },
{ ECoreType.tuic, "EAimTY/tuic" },
{ ECoreType.juicity, "juicity/juicity" },
{ ECoreType.brook, "txthinking/brook" },
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
{ ECoreType.mieru, "enfein/mieru" },
{ ECoreType.v2rayN, "2dust/v2rayN" },
};
public static readonly List<string> OtherGeoUrls =
[
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat",
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
@"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
@"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"
];
public static readonly List<string> IPAPIUrls =
[
@"https://speed.cloudflare.com/meta",
@"https://api.ip.sb/geoip",
@"https://api-ipv4.ip.sb/geoip",
@"https://api-ipv6.ip.sb/geoip",
@@ -601,29 +605,37 @@ public class Global
];
public static readonly Dictionary<string, List<string>> PredefinedHosts = new()
{
{ "dns.google", new List<string> { "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844" } },
{ "dns.alidns.com", new List<string> { "223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1" } },
{ "one.one.one.one", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
{ "1dot1dot1dot1.cloudflare-dns.com", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
{ "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } },
{ "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } },
{ "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
{ "doh.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
{ "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } },
{ "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } },
{ "dns.sb", new List<string> { "185.222.222.222", "2a09::" } },
{ "dns.umbrella.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
{ "dns.sse.cisco.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
{ "engage.cloudflareclient.com", new List<string> { "162.159.192.1", "2606:4700:d0::a29f:c001" } }
};
{
{ "dns.google", new List<string> { "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844" } },
{ "dns.alidns.com", new List<string> { "223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1" } },
{ "one.one.one.one", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
{ "1dot1dot1dot1.cloudflare-dns.com", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
{ "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } },
{ "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } },
{ "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
{ "doh.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
{ "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } },
{ "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } },
{ "dns.sb", new List<string> { "185.222.222.222", "2a09::" } },
{ "dns.umbrella.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
{ "dns.sse.cisco.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
{ "engage.cloudflareclient.com", new List<string> { "162.159.192.1", "2606:4700:d0::a29f:c001" } }
};
public static readonly List<string> ExpectedIPs =
[
"geoip:cn",
"geoip:ir",
"geoip:ru",
""
"geoip:ir",
"geoip:ru",
""
];
public static readonly List<string> EchForceQuerys =
[
"none",
"half",
"full",
""
];
#endregion const

View File

@@ -253,6 +253,9 @@ public static class ConfigHandler
item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled;
item.Cert = profileItem.Cert;
item.CertSha = profileItem.CertSha;
item.EchConfigList = profileItem.EchConfigList;
item.EchForceQuery = profileItem.EchForceQuery;
}
var ret = item.ConfigType switch
@@ -700,7 +703,7 @@ public static class ConfigHandler
public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Hysteria2;
profileItem.CoreType = ECoreType.sing_box;
//profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
@@ -1273,7 +1276,7 @@ public static class ConfigHandler
}
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)
{
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
var preCoreType = AppManager.Instance.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem()
{
CoreType = preCoreType,

View File

@@ -70,6 +70,14 @@ public class BaseFmt
}
ToUriQueryAllowInsecure(item, ref dicQuery);
}
if (item.EchConfigList.IsNotEmpty())
{
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
}
if (item.CertSha.IsNotEmpty())
{
dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha));
}
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
@@ -118,7 +126,16 @@ public class BaseFmt
}
if (item.Extra.IsNotEmpty())
{
dicQuery.Add("extra", Utils.UrlEncode(item.Extra));
var node = JsonUtils.ParseJson(item.Extra);
var extra = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
: item.Extra;
dicQuery.Add("extra", Utils.UrlEncode(extra));
}
break;
@@ -200,6 +217,8 @@ public class BaseFmt
item.ShortId = GetQueryDecoded(query, "sid");
item.SpiderX = GetQueryDecoded(query, "spx");
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
item.EchConfigList = GetQueryDecoded(query, "ech");
item.CertSha = GetQueryDecoded(query, "pcs");
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
{
@@ -237,7 +256,21 @@ public class BaseFmt
item.RequestHost = GetQueryDecoded(query, "host");
item.Path = GetQueryDecoded(query, "path", "/");
item.HeaderType = GetQueryDecoded(query, "mode");
item.Extra = GetQueryDecoded(query, "extra");
var extraDecoded = GetQueryDecoded(query, "extra");
if (extraDecoded.IsNotEmpty())
{
var node = JsonUtils.ParseJson(extraDecoded);
if (node != null)
{
extraDecoded = JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
}
}
item.Extra = extraDecoded;
break;
case nameof(ETransport.http):

View File

@@ -25,6 +25,10 @@ public class Hysteria2Fmt : BaseFmt
ResolveUriQuery(query, ref item);
item.Path = GetQueryDecoded(query, "obfs-password");
item.Ports = GetQueryDecoded(query, "mport");
if (item.CertSha.IsNullOrEmpty())
{
item.CertSha = GetQueryDecoded(query, "pinSHA256");
}
return item;
}
@@ -55,6 +59,16 @@ public class Hysteria2Fmt : BaseFmt
{
dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-')));
}
if (!item.CertSha.IsNullOrEmpty())
{
var sha = item.CertSha;
var idx = sha.IndexOf('~');
if (idx > 0)
{
sha = sha[..idx];
}
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
}
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark);
}

View File

@@ -57,7 +57,10 @@ public class ShadowsocksFmt : BaseFmt
{
pluginArgs += "mode=websocket;";
pluginArgs += $"host={item.RequestHost};";
pluginArgs += $"path={item.Path};";
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
// Equal signs and commas [and backslashes] must be escaped with a backslash.
var path = item.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
pluginArgs += $"path={path};";
}
else if (item.Network == nameof(ETransport.quic))
{
@@ -73,13 +76,9 @@ public class ShadowsocksFmt : BaseFmt
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
const string endMarker = "\n-----END CERTIFICATE-----";
var base64Start = beginMarker.Length;
var endIndex = cert.IndexOf(endMarker, base64Start, StringComparison.Ordinal);
var base64Content = cert.Substring(base64Start, endIndex - base64Start);
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("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
base64Content = base64Content.Replace("=", "\\=");
pluginArgs += $"certRaw={base64Content};";
}
@@ -87,6 +86,7 @@ public class ShadowsocksFmt : BaseFmt
if (pluginArgs.Length > 0)
{
plugin = "v2ray-plugin";
pluginArgs += "mux=0;";
}
}
@@ -224,6 +224,7 @@ public class ShadowsocksFmt : BaseFmt
var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
var hasTls = pluginParts.Any(t => t == "tls");
var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
var mux = pluginParts.FirstOrDefault(t => t.StartsWith("mux="));
var modeValue = mode.Replace("mode=", "");
if (modeValue == "websocket")
@@ -232,10 +233,13 @@ public class ShadowsocksFmt : BaseFmt
if (!host.IsNullOrEmpty())
{
item.RequestHost = host.Replace("host=", "");
item.Sni = item.RequestHost;
}
if (!path.IsNullOrEmpty())
{
item.Path = path.Replace("path=", "");
var pathValue = path.Replace("path=", "");
pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
item.Path = pathValue;
}
}
else if (modeValue == "quic")
@@ -251,7 +255,7 @@ public class ShadowsocksFmt : BaseFmt
{
var certBase64 = certRaw.Replace("certRaw=", "");
certBase64 = certBase64.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
certBase64 = certBase64.Replace("\\=", "=");
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
const string endMarker = "\n-----END CERTIFICATE-----";
@@ -259,6 +263,16 @@ public class ShadowsocksFmt : BaseFmt
item.Cert = certPem;
}
}
if (!mux.IsNullOrEmpty())
{
var muxValue = mux.Replace("mux=", "");
var muxCount = muxValue.ToInt();
if (muxCount > 0)
{
return null;
}
}
}
}

View File

@@ -71,28 +71,25 @@ public class DownloaderHelper
}
};
var totalDatetime = DateTime.Now;
var totalSecond = 0;
var lastUpdateTime = DateTime.Now;
var hasValue = false;
double maxSpeed = 0;
await using var downloader = new Downloader.DownloadService(downloadOpt);
//downloader.DownloadStarted += (sender, value) =>
//{
// if (progress != null)
// {
// progress.Report("Start download data...");
// }
//};
downloader.DownloadProgressChanged += (sender, value) =>
{
var ts = DateTime.Now - totalDatetime;
if (progress != null && ts.Seconds > totalSecond)
if (progress != null && value.BytesPerSecondSpeed > 0)
{
hasValue = true;
totalSecond = ts.Seconds;
if (value.BytesPerSecondSpeed > maxSpeed)
{
maxSpeed = value.BytesPerSecondSpeed;
}
var ts = DateTime.Now - lastUpdateTime;
if (ts.TotalMilliseconds >= 1000)
{
lastUpdateTime = DateTime.Now;
var speed = (maxSpeed / 1000 / 1000).ToString("#0.0");
progress.Report(speed);
}
@@ -102,10 +99,19 @@ public class DownloaderHelper
{
if (progress != null)
{
if (!hasValue && value.Error != null)
if (hasValue && maxSpeed > 0)
{
var finalSpeed = (maxSpeed / 1000 / 1000).ToString("#0.0");
progress.Report(finalSpeed);
}
else if (value.Error != null)
{
progress.Report(value.Error?.Message);
}
else
{
progress.Report("0");
}
}
};
//progress.Report("......");

View File

@@ -3,12 +3,19 @@ namespace ServiceLib.Manager;
/// <summary>
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
/// </summary>
public class ActionPrecheckManager(Config config)
public class ActionPrecheckManager
{
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
private static readonly Lazy<ActionPrecheckManager> _instance = new();
public static ActionPrecheckManager Instance => _instance.Value;
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)
{
@@ -47,6 +54,7 @@ public class ActionPrecheckManager(Config config)
{
return [];
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
return await ValidateNodeAndCoreSupport(item, coreType);
}
@@ -62,153 +70,209 @@ public class ActionPrecheckManager(Config config)
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
return errors;
}
if (!item.IsComplex())
else if (item.ConfigType.IsGroupType())
{
if (item.Address.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
return errors;
}
if (item.Port is <= 0 or >= 65536)
{
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
return errors;
}
switch (item.ConfigType)
{
case EConfigType.VMess:
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
break;
case EConfigType.VLESS:
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (!Global.Flows.Contains(item.Flow))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
break;
case EConfigType.Shadowsocks:
if (item.Id.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
}
break;
}
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
&& item.StreamSecurity == Global.StreamSecurityReality
&& item.PublicKey.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
}
if (errors.Count > 0)
{
return errors;
}
var groupErrors = await ValidateGroupNode(item, coreType);
errors.AddRange(groupErrors);
return errors;
}
if (item.ConfigType.IsGroupType())
else if (!item.IsComplex())
{
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
}
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
if (hasCycle)
{
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
return errors;
}
var childIds = Utils.String2List(group.ChildItems) ?? [];
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRange(subItems.Select(p => p.IndexId));
foreach (var child in childIds)
{
var childErrors = new List<string>();
if (child.IsNullOrEmpty())
{
continue;
}
var childItem = await AppManager.Instance.GetProfileItem(child);
if (childItem is null)
{
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
continue;
}
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
{
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
continue;
}
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
errors.AddRange(childErrors);
}
var normalErrors = await ValidateNormalNode(item, coreType);
errors.AddRange(normalErrors);
return errors;
}
var net = item.GetNetwork() ?? item.Network;
return errors;
}
private async Task<List<string>> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
if (item.Address.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
return errors;
}
if (item.Port is <= 0 or > 65535)
{
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
return errors;
}
var net = item.GetNetwork();
if (coreType == ECoreType.sing_box)
{
// sing-box does not support xhttp / kcp
// sing-box does not support transports like ws/http/httpupgrade/etc. when the node is not vmess/trojan/vless
if (net is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
var transportError = ValidateSingboxTransport(item.ConfigType, net);
if (transportError != null)
{
errors.Add(string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net));
return errors;
errors.Add(transportError);
}
if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan))
if (!Global.SingboxSupportConfigType.Contains(item.ConfigType))
{
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;
}
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
nameof(ECoreType.sing_box), item.ConfigType.ToString()));
}
}
else if (coreType is ECoreType.Xray)
{
// Xray core does not support these protocols
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
&& !item.IsComplex())
if (!Global.XraySupportConfigType.Contains(item.ConfigType))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
return errors;
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
nameof(ECoreType.Xray), item.ConfigType.ToString()));
}
}
switch (item.ConfigType)
{
case EConfigType.VMess:
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
break;
case EConfigType.VLESS:
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (!Global.Flows.Contains(item.Flow))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
break;
case EConfigType.Shadowsocks:
if (item.Id.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
}
break;
}
if (item.StreamSecurity == Global.StreamSecurity)
{
// check certificate validity
if (!item.Cert.IsNullOrEmpty()
&& (CertPemManager.ParsePemChain(item.Cert).Count == 0)
&& !item.CertSha.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate"));
}
}
if (item.StreamSecurity == Global.StreamSecurityReality)
{
if (item.PublicKey.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
}
}
if (item.Network == nameof(ETransport.xhttp)
&& !item.Extra.IsNullOrEmpty())
{
// check xhttp extra json validity
var xhttpExtra = JsonUtils.ParseJson(item.Extra);
if (xhttpExtra is null)
{
errors.Add(string.Format(ResUI.InvalidProperty, "XHTTP Extra"));
}
}
return errors;
}
private async Task<List<string>> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
}
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
if (hasCycle)
{
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
return errors;
}
var childIds = new List<string>();
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRange(subItems.Select(p => p.IndexId));
childIds.AddRange(Utils.String2List(group.ChildItems));
foreach (var child in childIds)
{
var childErrors = new List<string>();
if (child.IsNullOrEmpty())
{
continue;
}
var childItem = await AppManager.Instance.GetProfileItem(child);
if (childItem is null)
{
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
continue;
}
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
{
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
continue;
}
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: ")));
}
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>();
@@ -247,7 +311,7 @@ public class ActionPrecheckManager(Config config)
if (node is not null)
{
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + $"{node.Remarks}: " + s));
}
else if (tag.IsNotEmpty())
{
@@ -265,7 +329,7 @@ public class ActionPrecheckManager(Config config)
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = await ConfigHandler.GetDefaultRouting(AppManager.Instance.Config);
if (routing == null)
{
return errors;
@@ -293,7 +357,7 @@ public class ActionPrecheckManager(Config config)
}
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + $"{tagItem.Remarks}: " + s));
}
return errors;

View File

@@ -31,6 +31,23 @@ public sealed class AppManager
public string LinuxSudoPwd { get; set; }
public bool ShowInTaskbar { get; set; }
public ECoreType RunningCoreType { get; set; }
public bool IsRunningCore(ECoreType type)
{
switch (type)
{
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
return true;
default:
return false;
}
}
#endregion Property
#region App

View File

@@ -416,4 +416,22 @@ public class CertPemManager
return string.Concat(pemList);
}
public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = false)
{
try
{
var cert = X509Certificate2.CreateFromPem(pemCert);
var thumbprint = cert.GetCertHashString(HashAlgorithmName.SHA256);
if (includeColon)
{
return string.Join(":", thumbprint.Chunk(2).Select(c => new string(c)));
}
return thumbprint;
}
catch
{
return string.Empty;
}
}
}

View File

@@ -125,7 +125,7 @@ public sealed class CoreInfoManager
new CoreInfo
{
CoreType = ECoreType.mihomo,
CoreExes = ["mihomo-windows-amd64-v1", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
CoreExes = GetMihomoCoreExes(),
Arguments = "-f {0}" + PortableMode(),
Url = GetCoreUrl(ECoreType.mihomo),
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
@@ -248,4 +248,34 @@ public sealed class CoreInfoManager
{
return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases";
}
private static List<string>? GetMihomoCoreExes()
{
var names = new List<string>();
if (Utils.IsWindows())
{
names.Add("mihomo-windows-amd64-v1");
names.Add("mihomo-windows-amd64-compatible");
names.Add("mihomo-windows-amd64");
names.Add("mihomo-windows-arm64");
}
else if (Utils.IsLinux())
{
names.Add("mihomo-linux-amd64-v1");
names.Add("mihomo-linux-amd64");
names.Add("mihomo-linux-arm64");
}
else if (Utils.IsMacOS())
{
names.Add("mihomo-darwin-amd64-v1");
names.Add("mihomo-darwin-amd64");
names.Add("mihomo-darwin-arm64");
}
names.Add("clash");
names.Add("mihomo");
return names;
}
}

View File

@@ -167,7 +167,7 @@ public class CoreManager
private async Task CoreStart(ProfileItem node)
{
var coreType = _config.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;

View File

@@ -230,9 +230,10 @@ public class ProfileGroupItemManager
{
return (new List<ProfileItem>(), profileGroupItem);
}
var items = await GetChildProfileItems(profileGroupItem);
var subItems = await GetSubChildProfileItems(profileGroupItem);
items.AddRange(subItems);
var items = new List<ProfileItem>();
items.AddRange(await GetSubChildProfileItems(profileGroupItem));
items.AddRange(await GetChildProfileItems(profileGroupItem));
return (items, profileGroupItem);
}
@@ -316,5 +317,72 @@ public class ProfileGroupItemManager
return childAddresses;
}
public static async Task<HashSet<string>> GetAllChildEchQuerySni(string indexId)
{
// include grand children
var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
{
return childAddresses;
}
if (groupItem.SubChildItems.IsNotEmpty())
{
var subItems = await GetSubChildProfileItems(groupItem);
foreach (var childNode in subItems)
{
if (childNode.EchConfigList.IsNullOrEmpty())
{
continue;
}
if (childNode.StreamSecurity == Global.StreamSecurity
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
}
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() && !childNode.EchConfigList.IsNullOrEmpty())
{
if (childNode.StreamSecurity == Global.StreamSecurity
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
#endregion Helper
}

View File

@@ -43,9 +43,12 @@ public sealed class WebDavManager
_webDir = _config.WebDavItem.DirName.TrimEx();
}
// Ensure BaseAddress URL ends with a trailing slash
var baseUrl = _config.WebDavItem.Url.Trim().TrimEnd('/') + "/";
var clientParams = new WebDavClientParams
{
BaseAddress = new Uri(_config.WebDavItem.Url),
BaseAddress = new Uri(baseUrl),
Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password)
};
_client = new WebDavClient(clientParams);

View File

@@ -8,21 +8,6 @@ public class Config
public string IndexId { get; set; }
public string SubIndexId { get; set; }
public ECoreType RunningCoreType { get; set; }
public bool IsRunningCore(ECoreType type)
{
switch (type)
{
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
return true;
default:
return false;
}
}
#endregion property
#region other entities

View File

@@ -99,8 +99,7 @@ public class UIItem
public bool EnableDragDropSort { get; set; }
public bool DoubleClick2Activate { get; set; }
public bool AutoHideStartup { get; set; }
public bool Hide2TrayWhenClose { get; set; }
public bool ShowInTaskbar { get; set; }
public bool Hide2TrayWhenClose { get; set; }
public bool MacOSShowInDock { get; set; }
public List<ColumnItem> MainColumnItem { get; set; }
public List<WindowSizeItem> WindowSizeItem { get; set; }

View File

@@ -161,4 +161,7 @@ public class ProfileItem : ReactiveObject
public string Extra { get; set; }
public bool? MuxEnabled { get; set; }
public string Cert { get; set; }
public string CertSha { get; set; }
public string EchConfigList { get; set; }
public string EchForceQuery { get; set; }
}

View File

@@ -68,6 +68,7 @@ public class Rule4Sbox
public List<string>? ip_cidr { get; set; }
public List<string>? source_ip_cidr { get; set; }
public List<string>? process_name { get; set; }
public List<string>? process_path { get; set; }
public List<string>? rule_set { get; set; }
public List<Rule4Sbox>? rules { get; set; }
public string? action { get; set; }
@@ -182,6 +183,14 @@ public class Tls4Sbox
public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; }
public List<string>? certificate { get; set; }
public Ech4Sbox? ech { get; set; }
}
public class Ech4Sbox
{
public bool enabled { get; set; }
public List<string>? config { get; set; }
public string? query_server_name { get; set; }
}
public class Multiplex4Sbox
@@ -216,6 +225,8 @@ 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

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

View File

@@ -128,7 +128,8 @@ public class Outboundsettings4Ray
public string? secretKey { get; set; }
public List<string>? address { get; set; }
public Object? address { get; set; }
public int? port { get; set; }
public List<WireguardPeer4Ray>? peers { get; set; }
@@ -139,6 +140,8 @@ public class Outboundsettings4Ray
public List<int>? reserved { get; set; }
public int? workers { get; set; }
public int? version { get; set; }
}
public class WireguardPeer4Ray
@@ -256,6 +259,8 @@ public class RulesItem4Ray
public List<string>? domain { get; set; }
public List<string>? protocol { get; set; }
public List<string>? process { get; set; }
}
public class BalancersItem4Ray
@@ -336,6 +341,10 @@ public class StreamSettings4Ray
public GrpcSettings4Ray? grpcSettings { get; set; }
public HysteriaSettings4Ray? hysteriaSettings { get; set; }
public List<UdpMasks4Ray>? udpmasks { get; set; }
public Sockopt4Ray? sockopt { get; set; }
}
@@ -355,7 +364,10 @@ public class TlsSettings4Ray
public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; }
public List<CertificateSettings4Ray>? certificates { get; set; }
public string? pinnedPeerCertSha256 { get; set; }
public bool? disableSystemRoot { get; set; }
public string? echConfigList { get; set; }
public string? echForceQuery { get; set; }
}
public class CertificateSettings4Ray
@@ -411,8 +423,6 @@ public class WsSettings4Ray
public class Headers4Ray
{
public string Host { get; set; }
[JsonPropertyName("User-Agent")]
public string UserAgent { get; set; }
}
@@ -459,6 +469,32 @@ public class GrpcSettings4Ray
public int? initial_windows_size { get; set; }
}
public class HysteriaSettings4Ray
{
public int version { get; set; }
public string? auth { get; set; }
public string? up { get; set; }
public string? down { get; set; }
public HysteriaUdpHop4Ray? udphop { get; set; }
}
public class HysteriaUdpHop4Ray
{
public string? ports { get; set; }
public int? interval { get; set; }
}
public class UdpMasks4Ray
{
public string type { get; set; }
public UdpMasksSettings4Ray? settings { get; set; }
}
public class UdpMasksSettings4Ray
{
public string? password { get; set; }
}
public class AccountsItem4Ray
{
public string user { get; set; }

View File

@@ -529,7 +529,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Next proxy Configuration remarks 的本地化字符串。
/// 查找类似 Next proxy remarks 的本地化字符串。
/// </summary>
public static string LvNextProfile {
get {
@@ -547,7 +547,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Previous proxy Configuration remarks 的本地化字符串。
/// 查找类似 Previous proxy remarks 的本地化字符串。
/// </summary>
public static string LvPrevProfile {
get {
@@ -736,7 +736,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Anytls] Configuration 的本地化字符串。
/// 查找类似 Add [Anytls] 的本地化字符串。
/// </summary>
public static string menuAddAnytlsServer {
get {
@@ -745,7 +745,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add Child Configuration 的本地化字符串。
/// 查找类似 Add Child 的本地化字符串。
/// </summary>
public static string menuAddChildServer {
get {
@@ -754,7 +754,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。
/// 查找类似 Add a custom configuration 的本地化字符串。
/// </summary>
public static string menuAddCustomServer {
get {
@@ -763,7 +763,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [HTTP] Configuration 的本地化字符串。
/// 查找类似 Add [HTTP] 的本地化字符串。
/// </summary>
public static string menuAddHttpServer {
get {
@@ -772,7 +772,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Hysteria2] Configuration 的本地化字符串。
/// 查找类似 Add [Hysteria2] 的本地化字符串。
/// </summary>
public static string menuAddHysteria2Server {
get {
@@ -781,7 +781,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add Policy Group Configuration 的本地化字符串。
/// 查找类似 Add Policy Group 的本地化字符串。
/// </summary>
public static string menuAddPolicyGroupServer {
get {
@@ -790,7 +790,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add Proxy Chain Configuration 的本地化字符串。
/// 查找类似 Add Proxy Chain 的本地化字符串。
/// </summary>
public static string menuAddProxyChainServer {
get {
@@ -826,7 +826,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。
/// 查找类似 Add [Shadowsocks] 的本地化字符串。
/// </summary>
public static string menuAddShadowsocksServer {
get {
@@ -835,7 +835,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [SOCKS] Configuration 的本地化字符串。
/// 查找类似 Add [SOCKS] 的本地化字符串。
/// </summary>
public static string menuAddSocksServer {
get {
@@ -844,7 +844,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Trojan] Configuration 的本地化字符串。
/// 查找类似 Add [Trojan] 的本地化字符串。
/// </summary>
public static string menuAddTrojanServer {
get {
@@ -853,7 +853,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [TUIC] Configuration 的本地化字符串。
/// 查找类似 Add [TUIC] 的本地化字符串。
/// </summary>
public static string menuAddTuicServer {
get {
@@ -862,7 +862,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [VLESS] Configuration 的本地化字符串。
/// 查找类似 Add [VLESS] 的本地化字符串。
/// </summary>
public static string menuAddVlessServer {
get {
@@ -871,7 +871,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [VMess] Configuration 的本地化字符串。
/// 查找类似 Add [VMess] 的本地化字符串。
/// </summary>
public static string menuAddVmessServer {
get {
@@ -880,7 +880,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [WireGuard] Configuration 的本地化字符串。
/// 查找类似 Add [WireGuard] 的本地化字符串。
/// </summary>
public static string menuAddWireguardServer {
get {
@@ -952,7 +952,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Clone selected Configuration 的本地化字符串。
/// 查找类似 Clone selected 的本地化字符串。
/// </summary>
public static string menuCopyServer {
get {
@@ -970,7 +970,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Edit Configuration 的本地化字符串。
/// 查找类似 Edit 的本地化字符串。
/// </summary>
public static string menuEditServer {
get {
@@ -997,7 +997,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export selected Configuration for complete configuration 的本地化字符串。
/// 查找类似 Export selected for complete configuration 的本地化字符串。
/// </summary>
public static string menuExport2ClientConfig {
get {
@@ -1006,7 +1006,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export selected Configuration for complete configuration to clipboard 的本地化字符串。
/// 查找类似 Export selected for complete configuration to clipboard 的本地化字符串。
/// </summary>
public static string menuExport2ClientConfigClipboard {
get {
@@ -1033,7 +1033,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export Configuration 的本地化字符串。
/// 查找类似 Export 的本地化字符串。
/// </summary>
public static string menuExportConfig {
get {
@@ -1069,7 +1069,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration Fallback by sing-box 的本地化字符串。
/// 查找类似 Fallback by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxFallback {
get {
@@ -1078,7 +1078,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
/// 查找类似 LeastPing by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxLeastPing {
get {
@@ -1087,7 +1087,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration Fallback by Xray 的本地化字符串。
/// 查找类似 Fallback by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayFallback {
get {
@@ -1096,7 +1096,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
/// 查找类似 LeastLoad by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastLoad {
get {
@@ -1105,7 +1105,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
/// 查找类似 LeastPing by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastPing {
get {
@@ -1114,7 +1114,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
/// 查找类似 Random by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRandom {
get {
@@ -1123,7 +1123,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
/// 查找类似 RoundRobin by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRoundRobin {
get {
@@ -1411,7 +1411,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Test Configurations real delay 的本地化字符串。
/// 查找类似 Test real delay 的本地化字符串。
/// </summary>
public static string menuRealPingServer {
get {
@@ -1501,7 +1501,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove Child Configuration 的本地化字符串。
/// 查找类似 Remove Child 的本地化字符串。
/// </summary>
public static string menuRemoveChildServer {
get {
@@ -1510,7 +1510,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove duplicate Configurations 的本地化字符串。
/// 查找类似 Remove duplicate 的本地化字符串。
/// </summary>
public static string menuRemoveDuplicateServer {
get {
@@ -1528,7 +1528,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove selected Configurations 的本地化字符串。
/// 查找类似 Remove selected 的本地化字符串。
/// </summary>
public static string menuRemoveServer {
get {
@@ -1663,7 +1663,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Configuration List 的本地化字符串。
/// 查找类似 Configuration item 1, Auto add from subscription group 的本地化字符串。
/// </summary>
public static string menuServerList {
get {
@@ -1672,7 +1672,16 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Configurations 的本地化字符串。
/// 查找类似 Configuration Item 2, Select and add from self-built 的本地化字符串。
/// </summary>
public static string menuServerList2 {
get {
return ResourceManager.GetString("menuServerList2", resourceCulture);
}
}
/// <summary>
/// 查找类似 Configuration 的本地化字符串。
/// </summary>
public static string menuServers {
get {
@@ -1681,7 +1690,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Set as active Configuration 的本地化字符串。
/// 查找类似 Set as active 的本地化字符串。
/// </summary>
public static string menuSetDefaultServer {
get {
@@ -1699,7 +1708,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Share Configuration 的本地化字符串。
/// 查找类似 Share 的本地化字符串。
/// </summary>
public static string menuShareServer {
get {
@@ -1726,7 +1735,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Test Configurations download speed 的本地化字符串。
/// 查找类似 Test download speed 的本地化字符串。
/// </summary>
public static string menuSpeedServer {
get {
@@ -1870,7 +1879,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Test Configurations with tcping 的本地化字符串。
/// 查找类似 Test tcping 的本地化字符串。
/// </summary>
public static string menuTcpingServer {
get {
@@ -1978,7 +1987,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Configuration filter, press Enter to execute 的本地化字符串。
/// 查找类似 Filter, press Enter to execute 的本地化字符串。
/// </summary>
public static string MsgServerTitle {
get {
@@ -2275,7 +2284,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Are you sure you want to remove the Configuration? 的本地化字符串。
/// 查找类似 Are you sure you want to remove? 的本地化字符串。
/// </summary>
public static string RemoveServer {
get {
@@ -2608,7 +2617,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Server Certificate (PEM format, optional)
/// 查找类似 Pinned certificate (fill in either one)
///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. 的本地化字符串。
@@ -2619,6 +2628,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Certificate fingerprint (SHA-256) 的本地化字符串。
/// </summary>
public static string TbCertSha256Tips {
get {
return ResourceManager.GetString("TbCertSha256Tips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Clear system proxy 的本地化字符串。
/// </summary>
@@ -2781,6 +2799,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 EchConfigList 的本地化字符串。
/// </summary>
public static string TbEchConfigList {
get {
return ResourceManager.GetString("TbEchConfigList", resourceCulture);
}
}
/// <summary>
/// 查找类似 EchForceQuery 的本地化字符串。
/// </summary>
public static string TbEchForceQuery {
get {
return ResourceManager.GetString("TbEchForceQuery", resourceCulture);
}
}
/// <summary>
/// 查找类似 Edit 的本地化字符串。
/// </summary>
@@ -2862,6 +2898,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Full certificate (chain), PEM format 的本地化字符串。
/// </summary>
public static string TbFullCertTips {
get {
return ResourceManager.GetString("TbFullCertTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core&apos;s basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. 的本地化字符串。
/// </summary>
@@ -3241,7 +3286,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Full process name (Tun mode) 的本地化字符串。
/// 查找类似 Process (Tun mode) 的本地化字符串。
/// </summary>
public static string TbRoutingRuleProcess {
get {
@@ -4105,7 +4150,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Tray right-click menu Configurations display limit 的本地化字符串。
/// 查找类似 Tray right-click menu display limit 的本地化字符串。
/// </summary>
public static string TbSettingsTrayMenuServersLimit {
get {

View File

@@ -1027,7 +1027,7 @@
<value>پروتکل sing-box Mux</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>نام کامل فرانید (حالت Tun)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP or IP CIDR</value>
@@ -1540,7 +1540,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1638,4 +1638,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
</root>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root>

View File

@@ -1024,7 +1024,7 @@
<value>Protocole de multiplexage Mux (sing-box)</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Nom complet du processus (mode Tun)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP ou IP CIDR</value>
@@ -1537,7 +1537,7 @@
<value>Supprimer une sous-configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Liste des configurations</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Basculement (failover)</value>
@@ -1606,10 +1606,10 @@
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Certificat serveur (format PEM, facultatif)
Si le certificat est défini, il est fixé et loption « Ignorer la vérification » est désactivée.
<value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
Si un certificat auto-signé est utilisé ou si le système contient une CA non fiable ou malveillante, laction « Obtenir le certificat » peut échouer.</value>
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Obtenir le certificat</value>
@@ -1635,4 +1635,19 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>Afficher dans le Dock de macOS (redém. requis)</value>
</data>
</root>
<data name="menuServerList2" xml:space="preserve">
<value>Élément de config 2 : choisir et ajouter depuis self-hosted</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root>

View File

@@ -1027,7 +1027,7 @@
<value>sing-box Mux protokoll</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Teljes folyamatnév (Tun mód)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP vagy IP CIDR</value>
@@ -1540,7 +1540,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1609,7 +1609,7 @@
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
<value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
@@ -1638,4 +1638,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</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 the Configuration?</value>
<value>Are you sure you want to remove?</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>Configuration filter, press Enter to execute</value>
<value>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>Configurations</value>
<value>Configuration</value>
</data>
<data name="menuSetting" xml:space="preserve">
<value>Settings</value>
@@ -478,55 +478,55 @@
<value>Scan QR code on the screen</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>Clone selected Configuration</value>
<value>Clone selected</value>
</data>
<data name="menuRemoveDuplicateServer" xml:space="preserve">
<value>Remove duplicate Configurations</value>
<value>Remove duplicate</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>Remove selected Configurations</value>
<value>Remove selected</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>Set as active Configuration</value>
<value>Set as active</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>Clear all service statistics</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>Test Configurations real delay</value>
<value>Test real delay</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>Sort by test result</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>Test Configurations download speed</value>
<value>Test download speed</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>Test Configurations with tcping</value>
<value>Test tcping</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>Export selected Configuration for complete configuration</value>
<value>Export selected for complete configuration</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>Export Share Link to Clipboard</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>Add a custom configuration Configuration</value>
<value>Add a custom configuration</value>
</data>
<data name="menuAddShadowsocksServer" xml:space="preserve">
<value>Add [Shadowsocks] Configuration</value>
<value>Add [Shadowsocks] </value>
</data>
<data name="menuAddSocksServer" xml:space="preserve">
<value>Add [SOCKS] Configuration</value>
<value>Add [SOCKS] </value>
</data>
<data name="menuAddTrojanServer" xml:space="preserve">
<value>Add [Trojan] Configuration</value>
<value>Add [Trojan] </value>
</data>
<data name="menuAddVlessServer" xml:space="preserve">
<value>Add [VLESS] Configuration</value>
<value>Add [VLESS] </value>
</data>
<data name="menuAddVmessServer" xml:space="preserve">
<value>Add [VMess] Configuration</value>
<value>Add [VMess] </value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>Select all</value>
@@ -748,7 +748,7 @@
<value>System proxy settings</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>Tray right-click menu Configurations display limit</value>
<value>Tray right-click menu display limit</value>
</data>
<data name="TbSettingsUdpEnabled" xml:space="preserve">
<value>Enable UDP</value>
@@ -781,7 +781,7 @@
<value>PAC mode</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>Share Configuration</value>
<value>Share</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>Routing</value>
@@ -922,7 +922,7 @@
<value>Skip test</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>Edit Configuration</value>
<value>Edit </value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>Double-clicking Configuration makes it active</value>
@@ -1027,7 +1027,7 @@
<value>sing-box Mux Protocol</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Full process name (Tun mode)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP or IP CIDR</value>
@@ -1036,7 +1036,7 @@
<value>Domain</value>
</data>
<data name="menuAddHysteria2Server" xml:space="preserve">
<value>Add [Hysteria2] Configuration</value>
<value>Add [Hysteria2] </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] Configuration</value>
<value>Add [TUIC] </value>
</data>
<data name="TbHeaderType8" xml:space="preserve">
<value>Congestion control</value>
</data>
<data name="LvPrevProfile" xml:space="preserve">
<value>Previous proxy Configuration remarks</value>
<value>Previous proxy remarks</value>
</data>
<data name="LvNextProfile" xml:space="preserve">
<value>Next proxy Configuration remarks</value>
<value>Next proxy 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] Configuration</value>
<value>Add [WireGuard] </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] Configuration</value>
<value>Add [HTTP]</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>which conflicts with the group previous proxy</value>
@@ -1225,7 +1225,7 @@
<value>Export Base64-encoded Share Links to Clipboard</value>
</data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>Export selected Configuration for complete configuration to clipboard</value>
<value>Export selected for complete configuration to clipboard</value>
</data>
<data 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>Multi-Configuration Random by Xray</value>
<value>Random by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Multi-Configuration RoundRobin by Xray</value>
<value>RoundRobin by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by Xray</value>
<value>LeastPing by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Multi-Configuration LeastLoad by Xray</value>
<value>LeastLoad by Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by sing-box</value>
<value>LeastPing by sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>Export Configuration</value>
<value>Export</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] Configuration</value>
<value>Add [Anytls]</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 Configuration</value>
<value>Add Policy Group </value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
<value>Add Proxy Chain</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
<value>Add Child </value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
<value>Remove Child </value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
<value>Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
<value>Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
@@ -1609,7 +1609,7 @@
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
<value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
@@ -1638,4 +1638,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root>

View File

@@ -1027,7 +1027,7 @@
<value>Протокол Mux для sing-box</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Полное имя процесса (режим TUN)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP-адрес или сеть CIDR</value>
@@ -1540,7 +1540,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1609,7 +1609,7 @@
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
<value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
@@ -1638,4 +1638,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root>

View File

@@ -1024,7 +1024,7 @@
<value>sing-box Mux 多路复用协议</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>进程名全称 (Tun 模式)</value>
<value>进程 (Tun 模式)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP 或 IP CIDR</value>
@@ -1537,7 +1537,7 @@
<value>删除子配置</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>子配置项</value>
<value>子配置项一,从订阅分组中自动添加</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>故障转移</value>
@@ -1606,10 +1606,10 @@
<value>固定证书</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>服务器证书PEM 格式,可选
<value>固定证书(二选一填写即可
当指定此证书后,将固定该证书,并禁用“跳过证书验证”选项。
“获取证书”操作可能失败,原因可能是使用了自签证书,或系统中存在不受信任恶意的 CA。</value>
“获取证书”操作可能失败,原因包括使用了自签证书,或系统中存在不受信任甚至恶意的 CA。</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>获取证书</value>
@@ -1635,4 +1635,19 @@
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS 在 Dock 栏中显示 (需重启)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>子配置项二,从自建中选择添加</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>完整证书PEM 格式</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>证书指纹SHA-256</value>
</data>
</root>

View File

@@ -1024,7 +1024,7 @@
<value>sing-box Mux 多路復用協定</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>行程名全稱 (Tun 模式)</value>
<value>行程 (Tun 模式)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP 或 IP CIDR</value>
@@ -1537,7 +1537,7 @@
<value>刪除子配置</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>子配置項</value>
<value>子配置項目一,從訂閱分組中自動新增</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>容錯移轉</value>
@@ -1606,7 +1606,7 @@
<value>憑證綁定</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>伺服器憑證PEM 格式,可選
<value>固定憑證(二選一填寫即可
若已指定,憑證將會被綁定,並且「跳過憑證驗證」將被停用。
若使用自簽憑證,或系統中存在不受信任或惡意的 CA「取得憑證」動作可能會失敗。</value>
@@ -1635,4 +1635,19 @@
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS 在 Dock 欄顯示 (需重啟)</value>
</data>
</root>
<data name="menuServerList2" xml:space="preserve">
<value>子配置項二,從自建中選擇新增</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
</root>

View File

@@ -371,7 +371,7 @@ public partial class CoreConfigSingboxService(Config config)
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
await GenDns(null, singboxConfig);
await GenDns(parentNode, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true;
@@ -428,7 +428,7 @@ public partial class CoreConfigSingboxService(Config config)
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
await GenDns(null, singboxConfig);
await GenDns(parentNode, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true;

View File

@@ -13,8 +13,8 @@ public partial class CoreConfigSingboxService
}
var simpleDNSItem = _config.SimpleDNSItem;
await GenDnsServers(singboxConfig, simpleDNSItem);
await GenDnsRules(singboxConfig, simpleDNSItem);
await GenDnsServers(node, singboxConfig, simpleDNSItem);
await GenDnsRules(node, singboxConfig, simpleDNSItem);
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.independent_cache = true;
@@ -52,7 +52,7 @@ public partial class CoreConfigSingboxService
return 0;
}
private async Task<int> GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
private async Task<int> GenDnsServers(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
{
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
@@ -133,6 +133,29 @@ public partial class CoreConfigSingboxService
singboxConfig.dns.servers.Add(fakeip);
}
// ech
var (_, dnsServer) = ParseEchParam(node?.EchConfigList);
if (dnsServer is not null)
{
dnsServer.tag = Global.SingboxEchDNSTag;
if (dnsServer.server is not null
&& hostsDns.predefined.ContainsKey(dnsServer.server))
{
dnsServer.domain_resolver = Global.SingboxHostsDNSTag;
}
else
{
dnsServer.domain_resolver = Global.SingboxLocalDNSTag;
}
singboxConfig.dns.servers.Add(dnsServer);
}
else if (node?.ConfigType.IsGroupType() == true)
{
var echDnsObject = JsonUtils.DeepCopy(directDns);
echDnsObject.tag = Global.SingboxEchDNSTag;
singboxConfig.dns.servers.Add(echDnsObject);
}
return await Task.FromResult(0);
}
@@ -146,7 +169,7 @@ public partial class CoreConfigSingboxService
return await Task.FromResult(finalDns);
}
private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
private async Task<int> GenDnsRules(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
{
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
@@ -157,17 +180,42 @@ public partial class CoreConfigSingboxService
new Rule4Sbox
{
server = Global.SingboxRemoteDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy,
strategy = simpleDNSItem.SingboxStrategy4Proxy.NullIfEmpty(),
clash_mode = ERuleMode.Global.ToString()
},
new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct,
strategy = simpleDNSItem.SingboxStrategy4Direct.NullIfEmpty(),
clash_mode = ERuleMode.Direct.ToString()
}
});
var (ech, _) = ParseEchParam(node?.EchConfigList);
if (ech is not null)
{
var echDomain = ech.query_server_name ?? node?.Sni;
singboxConfig.dns.rules.Add(new()
{
query_type = new List<int> { 64, 65 },
server = Global.SingboxEchDNSTag,
domain = echDomain is not null ? new List<string> { echDomain } : null,
});
}
else if (node?.ConfigType.IsGroupType() == true)
{
var queryServerNames = (await ProfileGroupItemManager.GetAllChildEchQuerySni(node.IndexId)).ToList();
if (queryServerNames.Count > 0)
{
singboxConfig.dns.rules.Add(new()
{
query_type = new List<int> { 64, 65 },
server = Global.SingboxEchDNSTag,
domain = queryServerNames,
});
}
}
if (simpleDNSItem.BlockBindingQuery == true)
{
singboxConfig.dns.rules.Add(new()

View File

@@ -10,7 +10,7 @@ public partial class CoreConfigSingboxService
singboxConfig.inbounds = [];
if (!_config.TunModeItem.EnableTun
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box))
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && AppManager.Instance.RunningCoreType == ECoreType.sing_box))
{
var inbound = new Inbound4Sbox()
{

View File

@@ -46,7 +46,10 @@ public partial class CoreConfigSingboxService
{
pluginArgs += "mode=websocket;";
pluginArgs += $"host={node.RequestHost};";
pluginArgs += $"path={node.Path};";
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
// Equal signs and commas [and backslashes] must be escaped with a backslash.
var path = node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
pluginArgs += $"path={path};";
}
else if (node.Network == nameof(ETransport.quic))
{
@@ -62,13 +65,9 @@ public partial class CoreConfigSingboxService
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
const string endMarker = "\n-----END CERTIFICATE-----";
var base64Start = beginMarker.Length;
var endIndex = cert.IndexOf(endMarker, base64Start, StringComparison.Ordinal);
var base64Content = cert.Substring(base64Start, endIndex - base64Start);
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("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
base64Content = base64Content.Replace("=", "\\=");
pluginArgs += $"certRaw={base64Content};";
}
@@ -76,6 +75,9 @@ public partial class CoreConfigSingboxService
if (pluginArgs.Length > 0)
{
outbound.plugin = "v2ray-plugin";
pluginArgs += "mux=0;";
// pluginStr remove last ';'
pluginArgs = pluginArgs[..^1];
outbound.plugin_opts = pluginArgs;
}
}
@@ -332,6 +334,11 @@ public partial class CoreConfigSingboxService
};
tls.insecure = false;
}
var (ech, _) = ParseEchParam(node.EchConfigList);
if (ech is not null)
{
tls.ech = ech;
}
outbound.tls = tls;
}
catch (Exception ex)
@@ -352,7 +359,7 @@ public partial class CoreConfigSingboxService
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;
transport.path = node.Path.NullIfEmpty();
break;
case nameof(ETransport.tcp): //http
@@ -360,13 +367,41 @@ public partial class CoreConfigSingboxService
{
transport.type = nameof(ETransport.http);
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
transport.path = node.Path.NullIfEmpty();
}
break;
case nameof(ETransport.ws):
transport.type = nameof(ETransport.ws);
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
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.NullIfEmpty();
if (node.RequestHost.IsNotEmpty())
{
transport.headers = new()
@@ -378,8 +413,8 @@ public partial class CoreConfigSingboxService
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;
transport.path = node.Path.NullIfEmpty();
transport.host = node.RequestHost.NullIfEmpty();
break;
@@ -874,4 +909,31 @@ public partial class CoreConfigSingboxService
}
return await Task.FromResult(0);
}
private (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig)
{
if (echConfig.IsNullOrEmpty())
{
return (null, null);
}
if (!echConfig.Contains("://"))
{
return (new Ech4Sbox()
{
enabled = true,
config = [$"-----BEGIN ECH CONFIGS-----\n" +
$"{echConfig}\n" +
$"-----END ECH CONFIGS-----"],
}, null);
}
var idx = echConfig.IndexOf('+');
// NOTE: query_server_name, since sing-box 1.13.0
//var queryServerName = idx > 0 ? echConfig[..idx] : null;
var echDnsServer = idx > 0 ? echConfig[(idx + 1)..] : echConfig;
return (new Ech4Sbox()
{
enabled = true,
query_server_name = null,
}, ParseDnsAddress(echDnsServer));
}
}

View File

@@ -113,7 +113,7 @@ public partial class CoreConfigSingboxService
clash_mode = ERuleMode.Global.ToString()
});
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty();
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
{
@@ -280,9 +280,49 @@ public partial class CoreConfigSingboxService
if (_config.TunModeItem.EnableTun && item.Process?.Count > 0)
{
rule3.process_name = item.Process;
rules.Add(rule3);
hasDomainIp = true;
var ruleProcName = JsonUtils.DeepCopy(rule3);
var ruleProcPath = JsonUtils.DeepCopy(rule3);
foreach (var process in item.Process)
{
// sing-box doesn't support this, fall back to process name match
if (process is "self/" or "xray/")
{
ruleProcName.process_name.Add(Utils.GetExeName("sing-box"));
continue;
}
if (process.Contains('/') || process.Contains('\\'))
{
var procPath = process;
if (Utils.IsWindows())
{
procPath = procPath.Replace('/', '\\');
}
ruleProcPath.process_path.Add(procPath);
continue;
}
// sing-box strictly matches the exe suffix on Windows
var procName = process;
if (Utils.IsWindows() && !procName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
{
procName += ".exe";
}
ruleProcName.process_name.Add(procName);
}
if (ruleProcName.process_name.Count > 0)
{
rules.Add(ruleProcName);
hasDomainIp = true;
}
if (ruleProcPath.process_path.Count > 0)
{
rules.Add(ruleProcPath);
hasDomainIp = true;
}
}
if (!hasDomainIp

View File

@@ -197,7 +197,7 @@ public partial class CoreConfigV2rayService
if (item.OutboundTag == Global.DirectTag)
{
if (normalizedDomain.StartsWith("geosite:"))
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
{
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
}
@@ -208,7 +208,7 @@ public partial class CoreConfigV2rayService
}
else if (item.OutboundTag != Global.BlockTag)
{
if (normalizedDomain.StartsWith("geosite:"))
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
{
proxyGeositeList.Add(normalizedDomain);
}

View File

@@ -178,12 +178,29 @@ public partial class CoreConfigV2rayService
outbound.settings.vnext = null;
break;
}
case EConfigType.Hysteria2:
{
outbound.settings = new()
{
version = 2,
address = node.Address,
port = node.Port,
};
outbound.settings.vnext = null;
outbound.settings.servers = null;
break;
}
case EConfigType.WireGuard:
{
var address = node.Address;
if (Utils.IsIpv6(address))
{
address = $"[{address}]";
}
var peer = new WireguardPeer4Ray
{
publicKey = node.PublicKey,
endpoint = node.Address + ":" + node.Port.ToString()
endpoint = address + ":" + node.Port.ToString()
};
var setting = new Outboundsettings4Ray
{
@@ -201,6 +218,10 @@ public partial class CoreConfigV2rayService
}
outbound.protocol = Global.ProtocolTypes[node.ConfigType];
if (node.ConfigType == EConfigType.Hysteria2)
{
outbound.protocol = "hysteria";
}
await GenBoundStreamSettings(node, outbound);
}
catch (Exception ex)
@@ -241,7 +262,12 @@ public partial class CoreConfigV2rayService
try
{
var streamSettings = outbound.streamSettings;
streamSettings.network = node.GetNetwork();
var network = node.GetNetwork();
if (node.ConfigType == EConfigType.Hysteria2)
{
network = "hysteria";
}
streamSettings.network = network;
var host = node.RequestHost.TrimEx();
var path = node.Path.TrimEx();
var sni = node.Sni.TrimEx();
@@ -267,7 +293,9 @@ public partial class CoreConfigV2rayService
{
allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
alpn = node.GetAlpn(),
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint,
echConfigList = node.EchConfigList.NullIfEmpty(),
echForceQuery = node.EchForceQuery.NullIfEmpty()
};
if (sni.IsNotEmpty())
{
@@ -294,6 +322,10 @@ public partial class CoreConfigV2rayService
tlsSettings.disableSystemRoot = true;
tlsSettings.allowInsecure = false;
}
else if (!node.CertSha.IsNullOrEmpty())
{
tlsSettings.pinnedPeerCertSha256 = node.CertSha;
}
streamSettings.tlsSettings = tlsSettings;
}
@@ -317,7 +349,7 @@ public partial class CoreConfigV2rayService
}
//streamSettings
switch (node.GetNetwork())
switch (network)
{
case nameof(ETransport.kcp):
KcpSettings4Ray kcpSettings = new()
@@ -335,7 +367,7 @@ public partial class CoreConfigV2rayService
kcpSettings.header = new Header4Ray
{
type = node.HeaderType,
domain = host.IsNullOrEmpty() ? null : host
domain = host.NullIfEmpty()
};
if (path.IsNotEmpty())
{
@@ -351,7 +383,6 @@ public partial class CoreConfigV2rayService
if (host.IsNotEmpty())
{
wsSettings.host = host;
wsSettings.headers.Host = host;
}
if (path.IsNotEmpty())
{
@@ -446,7 +477,7 @@ public partial class CoreConfigV2rayService
case nameof(ETransport.grpc):
GrpcSettings4Ray grpcSettings = new()
{
authority = host.IsNullOrEmpty() ? null : host,
authority = host.NullIfEmpty(),
serviceName = path,
multiMode = node.HeaderType == Global.GrpcMultiMode,
idle_timeout = _config.GrpcItem.IdleTimeout,
@@ -457,6 +488,35 @@ public partial class CoreConfigV2rayService
streamSettings.grpcSettings = grpcSettings;
break;
case "hysteria":
HysteriaUdpHop4Ray? udpHop = null;
if (node.Ports.IsNotEmpty() &&
(node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(',')))
{
udpHop = new()
{
ports = node.Ports.Replace(':', '-'),
interval = _config.HysteriaItem.HopInterval > 0
? _config.HysteriaItem.HopInterval
: null,
};
}
HysteriaSettings4Ray hysteriaSettings = new()
{
version = 2,
auth = node.Id,
up = _config.HysteriaItem.UpMbps > 0 ? $"{_config.HysteriaItem.UpMbps}mbps" : null,
down = _config.HysteriaItem.DownMbps > 0 ? $"{_config.HysteriaItem.DownMbps}mbps" : null,
udphop = udpHop,
};
streamSettings.hysteriaSettings = hysteriaSettings;
if (node.Path.IsNotEmpty())
{
streamSettings.udpmasks =
[new() { type = "salamander", settings = new() { password = node.Path.TrimEx(), } }];
}
break;
default:
//tcp
if (node.HeaderType == Global.TcpHeaderHttp)
@@ -560,7 +620,7 @@ public partial class CoreConfigV2rayService
var fragmentOutbound = new Outbounds4Ray
{
protocol = "freedom",
tag = $"{Global.ProxyTag}3",
tag = $"frag-{Global.ProxyTag}",
settings = new()
{
fragment = new()

View File

@@ -77,12 +77,17 @@ public partial class CoreConfigV2rayService
{
rule.inboundTag = null;
}
if (rule.process?.Count == 0)
{
rule.process = null;
}
var hasDomainIp = false;
if (rule.domain?.Count > 0)
{
var it = JsonUtils.DeepCopy(rule);
it.ip = null;
it.process = null;
it.type = "field";
for (var k = it.domain.Count - 1; k >= 0; k--)
{
@@ -99,6 +104,16 @@ public partial class CoreConfigV2rayService
{
var it = JsonUtils.DeepCopy(rule);
it.domain = null;
it.process = null;
it.type = "field";
v2rayConfig.routing.rules.Add(it);
hasDomainIp = true;
}
if (_config.TunModeItem.EnableTun && rule.process?.Count > 0)
{
var it = JsonUtils.DeepCopy(rule);
it.domain = null;
it.ip = null;
it.type = "field";
v2rayConfig.routing.rules.Add(it);
hasDomainIp = true;

View File

@@ -7,7 +7,7 @@ namespace ServiceLib.Services;
/// </summary>
public class DownloadService
{
public event EventHandler<RetResult>? UpdateCompleted;
public event EventHandler<UpdateResult>? UpdateCompleted;
public event ErrorEventHandler? Error;
@@ -40,10 +40,10 @@ public class DownloadService
{
try
{
UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}"));
UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} {url}"));
var progress = new Progress<double>();
progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new RetResult(value > 100, $"...{value}%"));
progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new UpdateResult(value > 100, $"...{value}%"));
var webProxy = await GetWebProxy(blProxy);
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,

View File

@@ -198,6 +198,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{
if (!it.AllowTest)
{
await UpdateFunc(it.IndexId, ResUI.SpeedtestingSkip);
continue;
}
@@ -323,31 +324,28 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{
var responseTime = -1;
if (!IPAddress.TryParse(url, out var ipAddress))
{
var ipHostInfo = await Dns.GetHostEntryAsync(url);
ipAddress = ipHostInfo.AddressList.First();
}
IPEndPoint endPoint = new(ipAddress, port);
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
var timer = Stopwatch.StartNew();
try
{
if (!IPAddress.TryParse(url, out var ipAddress))
{
var ipHostInfo = await Dns.GetHostEntryAsync(url);
ipAddress = ipHostInfo.AddressList.First();
}
IPEndPoint endPoint = new(ipAddress, port);
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
var timer = Stopwatch.StartNew();
var result = clientSocket.BeginConnect(endPoint, null, null);
if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)))
{
throw new TimeoutException("connect timeout (5s): " + url);
}
timer.Stop();
responseTime = (int)timer.Elapsed.TotalMilliseconds;
clientSocket.EndConnect(result);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await clientSocket.ConnectAsync(endPoint, cts.Token).ConfigureAwait(false);
responseTime = (int)timer.ElapsedMilliseconds;
}
catch (Exception ex)
catch (OperationCanceledException)
{
Logging.SaveLog(_tag, ex);
}
finally
{
timer.Stop();
}
return responseTime;
}

View File

@@ -61,7 +61,7 @@ public class StatisticsSingboxService
await Task.Delay(1000);
try
{
if (!_config.IsRunningCore(ECoreType.sing_box))
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box))
{
continue;
}

View File

@@ -30,7 +30,7 @@ public class StatisticsXrayService
await Task.Delay(1000);
try
{
if (_config.RunningCoreType != ECoreType.Xray)
if (AppManager.Instance.RunningCoreType != ECoreType.Xray)
{
continue;
}

View File

@@ -37,7 +37,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
await UpdateFunc(false, result.Msg);
url = result.Data?.ToString();
url = result.Url.ToString();
fileName = Utils.GetTempPath(Utils.GetGuid());
await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout);
}
@@ -86,7 +86,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
await UpdateFunc(false, result.Msg);
url = result.Data?.ToString();
url = result.Url.ToString();
var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url);
fileName = Utils.GetTempPath(Utils.GetGuid() + ext);
await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout);
@@ -110,26 +110,26 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
#region CheckUpdate private
private async Task<RetResult> CheckUpdateAsync(DownloadService downloadHandle, ECoreType type, bool preRelease)
private async Task<UpdateResult> CheckUpdateAsync(DownloadService downloadHandle, ECoreType type, bool preRelease)
{
try
{
var result = await GetRemoteVersion(downloadHandle, type, preRelease);
if (!result.Success || result.Data is null)
if (!result.Success || result.Version is null)
{
return result;
}
return await ParseDownloadUrl(type, (SemanticVersion)result.Data);
return await ParseDownloadUrl(type, result);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
await UpdateFunc(false, ex.Message);
return new RetResult(false, ex.Message);
return new UpdateResult(false, ex.Message);
}
}
private async Task<RetResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease)
private async Task<UpdateResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease)
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
var tagName = string.Empty;
@@ -139,7 +139,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
var result = await downloadHandle.TryDownloadString(url, true, Global.AppName);
if (result.IsNullOrEmpty())
{
return new RetResult(false, "");
return new UpdateResult(false, "");
}
var gitHubReleases = JsonUtils.Deserialize<List<GitHubRelease>>(result);
@@ -153,12 +153,12 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
var lastUrl = await downloadHandle.UrlRedirectAsync(url, true);
if (lastUrl == null)
{
return new RetResult(false, "");
return new UpdateResult(false, "");
}
tagName = lastUrl?.Split("/tag/").LastOrDefault();
}
return new RetResult(true, "", new SemanticVersion(tagName));
return new UpdateResult(true, new SemanticVersion(tagName));
}
private async Task<SemanticVersion> GetCoreVersion(ECoreType type)
@@ -213,10 +213,11 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
}
}
private async Task<RetResult> ParseDownloadUrl(ECoreType type, SemanticVersion version)
private async Task<UpdateResult> ParseDownloadUrl(ECoreType type, UpdateResult result)
{
try
{
var version = result.Version ?? new SemanticVersion(0, 0, 0);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty;
SemanticVersion curVersion;
@@ -260,16 +261,17 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
if (curVersion >= version && version != new SemanticVersion(0, 0, 0))
{
return new RetResult(false, message);
return new UpdateResult(false, message);
}
return new RetResult(true, "", url);
result.Url = url;
return result;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
await UpdateFunc(false, ex.Message);
return new RetResult(false, ex.Message);
return new UpdateResult(false, ex.Message);
}
}
@@ -289,13 +291,6 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
return url;
}
//Check for standalone windows .Net version
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "wpfgfx_cor3.dll"))
&& File.Exists(Path.Combine(Utils.GetBaseDirectory(), "D3DCompiler_47_cor3.dll")))
{
return url?.Replace(".zip", "-SelfContained.zip");
}
//Check for avalonia desktop windows version
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll")))
{

View File

@@ -14,6 +14,9 @@ public class AddServerViewModel : MyReactiveObject
[Reactive]
public string CertTip { get; set; }
[Reactive]
public string CertSha { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
@@ -39,6 +42,12 @@ public class AddServerViewModel : MyReactiveObject
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertTip());
this.WhenAnyValue(x => x.CertSha)
.Subscribe(_ => UpdateCertTip());
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertSha());
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.Network = Global.DefaultNetwork;
@@ -53,6 +62,7 @@ public class AddServerViewModel : MyReactiveObject
}
CoreType = SelectedSource?.CoreType?.ToString();
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty;
CertSha = SelectedSource?.CertSha?.ToString() ?? string.Empty;
}
private async Task SaveServerAsync()
@@ -97,7 +107,8 @@ public class AddServerViewModel : MyReactiveObject
}
}
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
SelectedSource.Cert = Cert.IsNullOrEmpty() ? null : Cert;
SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert;
SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha;
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
{
@@ -113,10 +124,36 @@ public class AddServerViewModel : MyReactiveObject
private void UpdateCertTip(string? errorMessage = null)
{
CertTip = errorMessage.IsNullOrEmpty()
? (Cert.IsNullOrEmpty() ? ResUI.CertNotSet : ResUI.CertSet)
? ((Cert.IsNullOrEmpty() && CertSha.IsNullOrEmpty()) ? ResUI.CertNotSet : ResUI.CertSet)
: errorMessage;
}
private void UpdateCertSha()
{
if (Cert.IsNullOrEmpty())
{
return;
}
var certList = CertPemManager.ParsePemChain(Cert);
if (certList.Count == 0)
{
return;
}
List<string> shaList = new();
foreach (var cert in certList)
{
var sha = CertPemManager.GetCertSha256Thumbprint(cert);
if (sha.IsNullOrEmpty())
{
return;
}
shaList.Add(sha);
}
CertSha = string.Join('~', shaList);
}
private async Task FetchCert()
{
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
@@ -142,8 +179,8 @@ public class AddServerViewModel : MyReactiveObject
{
domain += $":{SelectedSource.Port}";
}
string certError;
(Cert, certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
(Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
UpdateCertTip(certError);
}
@@ -172,8 +209,8 @@ public class AddServerViewModel : MyReactiveObject
{
domain += $":{SelectedSource.Port}";
}
string certError;
(var certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
Cert = CertPemManager.ConcatenatePemChain(certs);
UpdateCertTip(certError);
}

View File

@@ -128,7 +128,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
{
await Task.Delay(1000 * 5);
numOfExecuted++;
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
{
continue;
}

View File

@@ -437,7 +437,7 @@ public class ClashProxiesViewModel : MyReactiveObject
{
await Task.Delay(1000 * 60);
numOfExecuted++;
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
{
continue;
}

View File

@@ -253,7 +253,7 @@ public class MainWindowViewModel : MyReactiveObject
private async Task Init()
{
_config.UiItem.ShowInTaskbar = true;
AppManager.Instance.ShowInTaskbar = true;
//await ConfigHandler.InitBuiltinRouting(_config);
await ConfigHandler.InitBuiltinDNS(_config);
@@ -306,7 +306,7 @@ public class MainWindowViewModel : MyReactiveObject
private async Task UpdateStatisticsHandler(ServerSpeedItem update)
{
if (!_config.UiItem.ShowInTaskbar)
if (!AppManager.Instance.ShowInTaskbar)
{
return;
}
@@ -560,7 +560,7 @@ public class MainWindowViewModel : MyReactiveObject
});
AppEvents.TestServerRequested.Publish();
var showClashUI = _config.IsRunningCore(ECoreType.sing_box);
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box);
if (showClashUI)
{
AppEvents.ProxiesReloadRequested.Publish();

View File

@@ -44,7 +44,7 @@ public class MsgViewModel : MyReactiveObject
EnqueueQueueMsg(msg);
if (!_config.UiItem.ShowInTaskbar)
if (!AppManager.Instance.ShowInTaskbar)
{
return;
}

View File

@@ -108,6 +108,7 @@ public class OptionSettingViewModel : MyReactiveObject
[Reactive] public string CoreType4 { get; set; }
[Reactive] public string CoreType5 { get; set; }
[Reactive] public string CoreType6 { get; set; }
[Reactive] public string CoreType7 { get; set; }
[Reactive] public string CoreType9 { get; set; }
#endregion CoreType
@@ -276,6 +277,10 @@ public class OptionSettingViewModel : MyReactiveObject
CoreType6 = type;
break;
case 7:
CoreType7 = type;
break;
case 9:
CoreType9 = type;
break;
@@ -427,6 +432,10 @@ public class OptionSettingViewModel : MyReactiveObject
type = CoreType6;
break;
case 7:
type = CoreType7;
break;
case 9:
type = CoreType9;
break;

View File

@@ -56,7 +56,8 @@ public class ProfilesViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
public ReactiveCommand<SubItem, Unit> MoveToGroupCmd { get; }
//servers ping
public ReactiveCommand<Unit, Unit> MixedTestServerCmd { get; }
@@ -77,6 +78,7 @@ public class ProfilesViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> AddSubCmd { get; }
public ReactiveCommand<Unit, Unit> EditSubCmd { get; }
public ReactiveCommand<Unit, Unit> DeleteSubCmd { get; }
#endregion Menu
@@ -178,6 +180,10 @@ public class ProfilesViewModel : MyReactiveObject
{
await MoveServer(EMove.Bottom);
}, canEditRemove);
MoveToGroupCmd = ReactiveCommand.CreateFromTask<SubItem>(async sub =>
{
SelectedMoveToGroup = sub;
});
//servers ping
FastRealPingCmd = ReactiveCommand.CreateFromTask(async () =>
@@ -235,6 +241,10 @@ public class ProfilesViewModel : MyReactiveObject
{
await EditSubAsync(false);
});
DeleteSubCmd = ReactiveCommand.CreateFromTask(async () =>
{
await DeleteSubAsync();
});
#endregion WhenAnyValue && ReactiveCommand
@@ -553,6 +563,11 @@ public class ProfilesViewModel : MyReactiveObject
private async Task RemoveDuplicateServer()
{
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
{
return;
}
var tuple = await ConfigHandler.DedupServerList(_config, _config.SubIndexId);
if (tuple.Item1 > 0 || tuple.Item2 > 0)
{
@@ -879,5 +894,23 @@ public class ProfilesViewModel : MyReactiveObject
}
}
private async Task DeleteSubAsync()
{
var item = await AppManager.Instance.GetSubItem(_config.SubIndexId);
if (item is null)
{
return;
}
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
{
return;
}
await ConfigHandler.DeleteSubItem(_config, item.Id);
await RefreshSubscriptions();
await SubSelectedChangedAsync(true);
}
#endregion Subscription
}

View File

@@ -549,7 +549,7 @@ public class StatusBarViewModel : MyReactiveObject
try
{
if (_config.IsRunningCore(ECoreType.sing_box))
if (AppManager.Instance.IsRunningCore(ECoreType.sing_box))
{
SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown));
SpeedDirectDisplay = string.Empty;

View File

@@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia;
using Avalonia.Media;
namespace v2rayN.Desktop.Common;
public static class AppBuilderExtension
@@ -11,7 +5,7 @@ public static class AppBuilderExtension
public static AppBuilder WithFontByDefault(this AppBuilder appBuilder)
{
var fallbacks = new List<FontFallback>();
var notoSansSc = new FontFamily(Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC"));
fallbacks.Add(new FontFallback { FontFamily = notoSansSc });

View File

@@ -34,8 +34,7 @@
</StackPanel>
<Grid
Grid.Row="0"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
DockPanel.Dock="Top"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock
@@ -75,7 +74,7 @@
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="3"
ColumnDefinitions="180,Auto,Auto">
ColumnDefinitions="300,Auto,Auto">
<TextBlock
Grid.Column="0"
Margin="{StaticResource Margin4}"
@@ -89,11 +88,11 @@
</Grid>
</Grid>
<TabControl DockPanel.Dock="Top">
<TabControl HorizontalContentAlignment="Stretch" DockPanel.Dock="Top">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid
Margin="{StaticResource Margin8}"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
RowDefinitions="Auto,Auto,Auto">
<TextBlock
@@ -131,10 +130,8 @@
VerticalAlignment="Center" />
</Grid>
</TabItem>
</TabControl>
<TabControl>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList2}">
<DataGrid
x:Name="lstChild"
Grid.Row="1"

View File

@@ -36,7 +36,7 @@
<Grid
Grid.Row="0"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock
@@ -50,7 +50,7 @@
Orientation="Horizontal">
<ComboBox
x:Name="cmbCoreType"
Width="100"
Width="200"
Margin="{StaticResource Margin4}"
ToolTip.Tip="{x:Static resx:ResUI.TbCoreType}" />
</StackPanel>
@@ -101,7 +101,7 @@
<Grid
x:Name="gridVMess"
Grid.Row="2"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
@@ -167,7 +167,7 @@
<Grid
x:Name="gridSs"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
@@ -213,7 +213,7 @@
<Grid
x:Name="gridSocks"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto">
@@ -246,7 +246,7 @@
<Grid
x:Name="gridVLESS"
Grid.Row="2"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
@@ -312,7 +312,7 @@
<Grid
x:Name="gridTrojan"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
@@ -358,7 +358,7 @@
<Grid
x:Name="gridHysteria2"
Grid.Row="2"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto">
@@ -411,7 +411,7 @@
<Grid
x:Name="gridTuic"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto">
@@ -457,7 +457,7 @@
<Grid
x:Name="gridWireguard"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
@@ -534,7 +534,7 @@
<Grid
x:Name="gridAnytls"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto">
@@ -560,7 +560,7 @@
<Grid
x:Name="gridTransport"
Grid.Row="4"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
@@ -692,7 +692,7 @@
<Grid
x:Name="gridTls"
Grid.Row="6"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock
@@ -711,9 +711,9 @@
<Grid
x:Name="gridTlsMore"
Grid.Row="7"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
@@ -768,15 +768,41 @@
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbEchConfigList}" />
<TextBox
x:Name="txtEchConfigList"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbEchForceQuery}" />
<ComboBox
x:Name="cmbEchForceQuery"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertPinning}" />
<StackPanel
Grid.Row="5"
Grid.Row="7"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Horizontal">
@@ -815,6 +841,23 @@
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}" />
</StackPanel>
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertSha256Tips}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtCertSha256Pinning"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFullCertTips}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtCert"
Width="400"
@@ -831,7 +874,7 @@
<Grid
x:Name="gridRealityMore"
Grid.Row="7"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">

View File

@@ -28,6 +28,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
cmbFingerprint2.ItemsSource = Global.Fingerprints;
cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
cmbAlpn.ItemsSource = Global.Alpns;
cmbEchForceQuery.ItemsSource = Global.EchForceQuerys;
var lstStreamSecurity = new List<string>();
lstStreamSecurity.Add(string.Empty);
@@ -74,7 +75,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridHysteria2.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
break;
@@ -185,8 +185,12 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.SelectedValue).DisposeWith(disposables);
//reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);

View File

@@ -9,9 +9,9 @@
xmlns:view="using:v2rayN.Desktop.Views"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="v2rayN"
Width="1000"
Height="600"
MinWidth="900"
Width="1200"
Height="800"
MinWidth="600"
x:DataType="vms:MainWindowViewModel"
Icon="/Assets/NotifyIcon1.ico"
ShowInTaskbar="True"
@@ -43,11 +43,11 @@
<MenuItem x:Name="menuAddVlessServer" Header="{x:Static resx:ResUI.menuAddVlessServer}" />
<MenuItem x:Name="menuAddShadowsocksServer" Header="{x:Static resx:ResUI.menuAddShadowsocksServer}" />
<MenuItem x:Name="menuAddTrojanServer" Header="{x:Static resx:ResUI.menuAddTrojanServer}" />
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem x:Name="menuAddWireguardServer" Header="{x:Static resx:ResUI.menuAddWireguardServer}" />
<MenuItem x:Name="menuAddSocksServer" Header="{x:Static resx:ResUI.menuAddSocksServer}" />
<MenuItem x:Name="menuAddHttpServer" Header="{x:Static resx:ResUI.menuAddHttpServer}" />
<Separator />
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem x:Name="menuAddTuicServer" Header="{x:Static resx:ResUI.menuAddTuicServer}" />
<MenuItem x:Name="menuAddAnytlsServer" Header="{x:Static resx:ResUI.menuAddAnytlsServer}" />
</MenuItem>
@@ -90,13 +90,13 @@
<MenuItem x:Name="menuOpenTheFileLocation" Header="{x:Static resx:ResUI.menuOpenTheFileLocation}" />
</MenuItem>
<MenuItem x:Name="menuReload" Header="{x:Static resx:ResUI.menuReload}" />
<MenuItem x:Name="menuHelp" Header="{x:Static resx:ResUI.menuHelp}">
<MenuItem x:Name="menuCheckUpdate" Header="{x:Static resx:ResUI.menuCheckUpdate}" />
<Separator />
</MenuItem>
<MenuItem x:Name="menuReload" Header="{x:Static resx:ResUI.menuReload}" />
<MenuItem x:Name="menuPromotion" Header="{x:Static resx:ResUI.menuPromotion}" />
<MenuItem x:Name="menuClose" Header="{x:Static resx:ResUI.menuExit}" />

View File

@@ -409,8 +409,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{
var bl = blShow ??
(Utils.IsLinux()
? (!_config.UiItem.ShowInTaskbar ^ (WindowState == WindowState.Minimized))
: !_config.UiItem.ShowInTaskbar);
? (!AppManager.Instance.ShowInTaskbar ^ (WindowState == WindowState.Minimized))
: !AppManager.Instance.ShowInTaskbar);
if (bl)
{
Show();
@@ -436,7 +436,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
Hide();
}
_config.UiItem.ShowInTaskbar = bl;
AppManager.Instance.ShowInTaskbar = bl;
}
protected override void OnLoaded(object? sender, RoutedEventArgs e)

View File

@@ -463,7 +463,7 @@
VerticalAlignment="Center"
IsVisible="{Binding BlIsLinux}"
Text="{x:Static resx:ResUI.TbSettingsHide2TrayWhenCloseTip}" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
@@ -477,7 +477,7 @@
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
IsVisible="{Binding BlIsIsMacOS}"/>
IsVisible="{Binding BlIsIsMacOS}" />
<TextBlock
Grid.Row="11"
@@ -592,7 +592,8 @@
Grid.Row="20"
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin4}" />
Margin="{StaticResource Margin4}"
IsEditable="True" />
<TextBlock
Grid.Row="21"
@@ -875,7 +876,7 @@
<Grid
Margin="{StaticResource Margin4}"
ColumnDefinitions="Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
@@ -959,10 +960,23 @@
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Hysteria2" />
<ComboBox
x:Name="cmbCoreType7"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Wireguard" />
<ComboBox
x:Name="cmbCoreType9"
Grid.Row="7"
Grid.Row="8"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />

View File

@@ -41,6 +41,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
cmbCoreType4.ItemsSource = Global.CoreTypes;
cmbCoreType5.ItemsSource = Global.CoreTypes;
cmbCoreType6.ItemsSource = Global.CoreTypes;
cmbCoreType7.ItemsSource = Global.CoreTypes;
cmbCoreType9.ItemsSource = Global.CoreTypes;
cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList();
@@ -101,7 +102,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);
@@ -122,6 +123,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.CoreType4, v => v.cmbCoreType4.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType5, v => v.cmbCoreType5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType7, v => v.cmbCoreType7.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.SelectedValue).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);

View File

@@ -7,6 +7,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
x:Name="Root"
d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="vms:ProfilesViewModel"
@@ -28,6 +29,14 @@
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuSubEdit" Header="{x:Static resx:ResUI.menuSubEdit}" />
<MenuItem x:Name="menuSubAdd" Header="{x:Static resx:ResUI.menuSubAdd}" />
<MenuItem x:Name="menuSubDelete" Header="{x:Static resx:ResUI.menuSubDelete}" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<Button
@@ -133,19 +142,18 @@
InputGesture="Ctrl+T" />
<MenuItem x:Name="menuSortServerResult" Header="{x:Static resx:ResUI.menuSortServerResult}" />
<Separator />
<MenuItem x:Name="menuMoveToGroup" Header="{x:Static resx:ResUI.menuMoveToGroup}">
<MenuItem>
<MenuItem.Header>
<DockPanel>
<ComboBox
x:Name="cmbMoveToGroup"
Width="200"
DisplayMemberBinding="{Binding Remarks}"
ItemsSource="{Binding SubItems}"
ToolTip.Tip="{x:Static resx:ResUI.menuSubscription}" />
</DockPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem
x:Name="menuMoveToGroup"
Header="{x:Static resx:ResUI.menuMoveToGroup}"
ItemsSource="{Binding DataContext.SubItems, ElementName=Root}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem
Command="{Binding DataContext.MoveToGroupCmd, ElementName=Root}"
CommandParameter="{Binding}"
Header="{Binding Remarks}" />
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
<MenuItem Header="{x:Static resx:ResUI.menuMoveTo}">
<MenuItem

View File

@@ -49,6 +49,9 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
this.Bind(ViewModel, vm => vm.ServerFilter, v => v.txtServerFilter.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddSubCmd, v => v.btnAddSub).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.EditSubCmd, v => v.btnEditSub).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.EditSubCmd, v => v.menuSubEdit).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddSubCmd, v => v.menuSubAdd).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.DeleteSubCmd, v => v.menuSubDelete).DisposeWith(disposables);
//servers delete
this.BindCommand(ViewModel, vm => vm.EditServerCmd, v => v.menuEditServer).DisposeWith(disposables);
@@ -66,7 +69,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
//servers move
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);

View File

@@ -40,6 +40,14 @@
<DataGrid.KeyBindings>
<KeyBinding Command="{Binding SubDeleteCmd}" Gesture="Delete" />
</DataGrid.KeyBindings>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuSubAdd2" Header="{x:Static resx:ResUI.menuSubAdd}" />
<MenuItem x:Name="menuSubDelete2" Header="{x:Static resx:ResUI.menuSubDelete}" />
<MenuItem x:Name="menuSubEdit2" Header="{x:Static resx:ResUI.menuSubEdit}" />
<MenuItem x:Name="menuSubShare2" Header="{x:Static resx:ResUI.menuSubShare}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn
Width="*"

View File

@@ -29,6 +29,11 @@ public partial class SubSettingWindow : WindowBase<SubSettingViewModel>
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubAddCmd, v => v.menuSubAdd2).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete2).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit2).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare2).DisposeWith(disposables);
});
}

View File

@@ -13,7 +13,7 @@ public partial class ThemeSettingView : ReactiveUserControl<ThemeSettingViewMode
ViewModel = new ThemeSettingViewModel();
cmbCurrentTheme.ItemsSource = Utils.GetEnumNames<ETheme>();
cmbCurrentFontSize.ItemsSource = Enumerable.Range(Global.MinFontSize, 11).ToList();
cmbCurrentFontSize.ItemsSource = Enumerable.Range(Global.MinFontSize, Global.MinFontSizeCount).ToList();
cmbCurrentLanguage.ItemsSource = Global.Languages;
this.WhenActivated(disposables =>

View File

@@ -15,7 +15,7 @@
SecondaryColor="Lime" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
</ResourceDictionary.MergedDictionaries>
<system:Double x:Key="MenuItemHeight">26</system:Double>
<system:Double x:Key="MenuItemHeight">32</system:Double>
<system:Double x:Key="StdFontSize">12</system:Double>
<system:Double x:Key="StdFontSize1">13</system:Double>
<system:Double x:Key="StdFontSize-1">11</system:Double>

View File

@@ -1,6 +1,7 @@
using MaterialDesignColors;
using MaterialDesignColors.ColorManipulation;
using MaterialDesignThemes.Wpf;
using Microsoft.Win32;
namespace v2rayN.ViewModels;
@@ -24,7 +25,7 @@ public class ThemeSettingViewModel : MyReactiveObject
{
_config = AppManager.Instance.Config;
RegisterSystemColorSet(_config, Application.Current.MainWindow, ModifyTheme);
RegisterSystemColorSet(_config, ModifyTheme);
BindingUI();
RestoreUI();
@@ -158,25 +159,15 @@ public class ThemeSettingViewModel : MyReactiveObject
_paletteHelper.SetTheme(theme);
}
public void RegisterSystemColorSet(Config config, Window window, Action updateFunc)
public static void RegisterSystemColorSet(Config config, Action updateFunc)
{
var helper = new WindowInteropHelper(window);
var hwndSource = HwndSource.FromHwnd(helper.EnsureHandle());
hwndSource.AddHook((IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) =>
SystemEvents.UserPreferenceChanged += (s, e) =>
{
if (config.UiItem.CurrentTheme == nameof(ETheme.FollowSystem))
if ((e.Category == UserPreferenceCategory.Color || e.Category == UserPreferenceCategory.General)
&& config.UiItem.CurrentTheme == nameof(ETheme.FollowSystem))
{
const int WM_SETTINGCHANGE = 0x001A;
if (msg == WM_SETTINGCHANGE)
{
if (wParam == IntPtr.Zero && Marshal.PtrToStringUni(lParam) == "ImmersiveColorSet")
{
updateFunc?.Invoke();
}
}
updateFunc?.Invoke();
}
return IntPtr.Zero;
});
};
}
}

View File

@@ -62,7 +62,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -111,7 +111,7 @@
Grid.Column="0"
Grid.ColumnSpan="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -134,7 +134,10 @@
</Grid>
</Grid>
<TabControl DockPanel.Dock="Top">
<TabControl
Margin="{StaticResource Margin8}"
HorizontalContentAlignment="Left"
DockPanel.Dock="Top">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid Margin="{StaticResource Margin8}">
<Grid.RowDefinitions>
@@ -143,7 +146,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -188,10 +191,8 @@
Style="{StaticResource DefTextBox}" />
</Grid>
</TabItem>
</TabControl>
<TabControl>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList2}">
<DataGrid
x:Name="lstChild"
AutoGenerateColumns="False"

View File

@@ -70,7 +70,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -87,7 +87,7 @@
Orientation="Horizontal">
<ComboBox
x:Name="cmbCoreType"
Width="100"
Width="200"
Margin="{StaticResource Margin4}"
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.TbCoreType}"
Style="{StaticResource DefComboBox}" />
@@ -157,7 +157,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -241,7 +241,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -300,7 +300,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -346,7 +346,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -430,7 +430,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -489,7 +489,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -559,7 +559,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -621,7 +621,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -715,7 +715,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -753,7 +753,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -899,7 +899,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -929,9 +929,11 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
@@ -1003,9 +1005,40 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbEchConfigList}" />
<TextBox
x:Name="txtEchConfigList"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbEchForceQuery}" />
<ComboBox
x:Name="cmbEchForceQuery"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertPinning}" />
<StackPanel
Grid.Row="5"
Grid.Row="7"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Horizontal">
@@ -1039,6 +1072,26 @@
Content="{x:Static resx:ResUI.TbFetchCertChain}"
Style="{StaticResource DefButton}" />
</StackPanel>
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertSha256Tips}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtCertSha256Pinning"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbFullCertTips}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtCert"
Width="400"
@@ -1067,7 +1120,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

View File

@@ -23,6 +23,7 @@ public partial class AddServerWindow
cmbFingerprint2.ItemsSource = Global.Fingerprints;
cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
cmbAlpn.ItemsSource = Global.Alpns;
cmbEchForceQuery.ItemsSource = Global.EchForceQuerys;
var lstStreamSecurity = new List<string>();
lstStreamSecurity.Add(string.Empty);
@@ -69,7 +70,6 @@ public partial class AddServerWindow
gridHysteria2.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed;
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
break;
@@ -180,8 +180,13 @@ public partial class AddServerWindow
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.Text).DisposeWith(disposables);
//reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables);

View File

@@ -1,4 +1,4 @@
<reactiveui:ReactiveUserControl
<reactiveui:ReactiveUserControl
x:Class="v2rayN.Views.BackupAndRestoreView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -23,15 +23,6 @@
</UserControl.Resources>
<DockPanel Margin="{StaticResource Margin8}">
<DockPanel Margin="{StaticResource Margin8}" DockPanel.Dock="Bottom">
<Button
Width="100"
Margin="{StaticResource Margin8}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
Content="{x:Static resx:ResUI.menuClose}"
DockPanel.Dock="Right"
IsCancel="True"
Style="{StaticResource DefButton}" />
<TextBlock
x:Name="txtMsg"
Margin="{StaticResource Margin8}"
@@ -252,4 +243,4 @@
</materialDesign:Card>
</StackPanel>
</DockPanel>
</reactiveui:ReactiveUserControl>
</reactiveui:ReactiveUserControl>

View File

@@ -1,4 +1,4 @@
<reactiveui:ReactiveUserControl
<reactiveui:ReactiveUserControl
x:Class="v2rayN.Views.CheckUpdateView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -39,15 +39,6 @@
Content="{x:Static resx:ResUI.menuCheckUpdate}"
IsDefault="True"
Style="{StaticResource DefButton}" />
<Button
Width="100"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Right"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
Content="{x:Static resx:ResUI.menuClose}"
IsCancel="True"
Style="{StaticResource DefButton}" />
</StackPanel>
<StackPanel>
@@ -99,4 +90,4 @@
</ListView>
</StackPanel>
</DockPanel>
</reactiveui:ReactiveUserControl>
</reactiveui:ReactiveUserControl>

View File

@@ -11,9 +11,9 @@
xmlns:view="clr-namespace:v2rayN.Views"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="v2rayN"
Width="900"
Height="700"
MinWidth="900"
Width="1200"
Height="800"
MinWidth="800"
x:TypeArguments="vms:MainWindowViewModel"
Icon="/Resources/v2rayN.ico"
ResizeMode="CanResizeWithGrip"
@@ -32,6 +32,7 @@
<materialDesign:DialogHost
materialDesign:TransitionAssist.DisableTransitions="True"
CloseOnClickAway="True"
Identifier="RootDialog"
SnackbarMessageQueue="{Binding ElementName=MainSnackbar, Path=MessageQueue}"
Style="{StaticResource MaterialDesignEmbeddedDialogHost}">
@@ -98,6 +99,10 @@
x:Name="menuAddTrojanServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddTrojanServer}" />
<MenuItem
x:Name="menuAddHysteria2Server"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem
x:Name="menuAddWireguardServer"
Height="{StaticResource MenuItemHeight}"
@@ -111,10 +116,6 @@
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddHttpServer}" />
<Separator Margin="-40,5" />
<MenuItem
x:Name="menuAddHysteria2Server"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem
x:Name="menuAddTuicServer"
Height="{StaticResource MenuItemHeight}"
@@ -231,23 +232,6 @@
</MenuItem>
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem
x:Name="menuReload"
Padding="{StaticResource MarginLeftRight8}"
AutomationProperties.Name="{x:Static resx:ResUI.menuReload}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
Margin="{StaticResource MarginRight8}"
VerticalAlignment="Center"
Kind="Reload" />
<TextBlock Text="{x:Static resx:ResUI.menuReload}" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem
x:Name="menuHelp"
@@ -267,6 +251,23 @@
</MenuItem>
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem
x:Name="menuReload"
Padding="{StaticResource MarginLeftRight8}"
AutomationProperties.Name="{x:Static resx:ResUI.menuReload}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
Margin="{StaticResource MarginRight8}"
VerticalAlignment="Center"
Kind="Reload" />
<TextBlock Text="{x:Static resx:ResUI.menuReload}" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem
x:Name="menuPromotion"

View File

@@ -376,7 +376,7 @@ public partial class MainWindow
public void ShowHideWindow(bool? blShow)
{
var bl = blShow ?? !_config.UiItem.ShowInTaskbar;
var bl = blShow ?? !AppManager.Instance.ShowInTaskbar;
if (bl)
{
this?.Show();
@@ -391,7 +391,7 @@ public partial class MainWindow
{
this?.Hide();
}
_config.UiItem.ShowInTaskbar = bl;
AppManager.Instance.ShowInTaskbar = bl;
}
protected override void OnLoaded(object? sender, RoutedEventArgs e)

View File

@@ -1138,6 +1138,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@@ -1239,10 +1240,25 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Hysteria2" />
<ComboBox
x:Name="cmbCoreType7"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Wireguard" />
<ComboBox
x:Name="cmbCoreType9"
Grid.Row="7"
Grid.Row="8"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"

View File

@@ -38,6 +38,7 @@ public partial class OptionSettingWindow
cmbCoreType4.ItemsSource = Global.CoreTypes;
cmbCoreType5.ItemsSource = Global.CoreTypes;
cmbCoreType6.ItemsSource = Global.CoreTypes;
cmbCoreType7.ItemsSource = Global.CoreTypes;
cmbCoreType9.ItemsSource = Global.CoreTypes;
cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList();
@@ -127,6 +128,7 @@ public partial class OptionSettingWindow
this.Bind(ViewModel, vm => vm.CoreType4, v => v.cmbCoreType4.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType5, v => v.cmbCoreType5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType7, v => v.cmbCoreType7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);

View File

@@ -15,18 +15,7 @@
<sys:Double x:Key="QrcodeWidth">400</sys:Double>
</UserControl.Resources>
<DockPanel Margin="{StaticResource Margin8}">
<StackPanel Margin="{StaticResource Margin8}" DockPanel.Dock="Bottom">
<Button
Width="100"
HorizontalAlignment="Right"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
Content="{x:Static resx:ResUI.menuClose}"
IsCancel="True"
IsDefault="True"
Style="{StaticResource DefButton}" />
</StackPanel>
<StackPanel Margin="{StaticResource Margin8}">
<Grid Margin="{StaticResource Margin8}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -51,5 +40,5 @@
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto" />
</Grid>
</DockPanel>
</StackPanel>
</UserControl>

View File

@@ -20,6 +20,7 @@
mc:Ignorable="d">
<materialDesign:DialogHost
materialDesign:TransitionAssist.DisableTransitions="True"
CloseOnClickAway="True"
Identifier="SubDialog"
Style="{StaticResource MaterialDesignEmbeddedDialogHost}">
<DockPanel>
@@ -92,6 +93,26 @@
HeadersVisibility="Column"
IsReadOnly="True"
Style="{StaticResource DefDataGrid}">
<DataGrid.ContextMenu>
<ContextMenu Style="{StaticResource DefContextMenu}">
<MenuItem
x:Name="menuSubAdd2"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuSubAdd}" />
<MenuItem
x:Name="menuSubDelete2"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuSubDelete}" />
<MenuItem
x:Name="menuSubEdit2"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuSubEdit}" />
<MenuItem
x:Name="menuSubShare2"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuSubShare}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn
Width="*"

View File

@@ -25,6 +25,11 @@ public partial class SubSettingWindow
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubAddCmd, v => v.menuSubAdd2).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete2).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit2).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare2).DisposeWith(disposables);
});
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
}

View File

@@ -13,7 +13,7 @@ public partial class ThemeSettingView
ViewModel = new ThemeSettingViewModel();
cmbCurrentTheme.ItemsSource = Utils.GetEnumNames<ETheme>().Take(3).ToList();
cmbCurrentFontSize.ItemsSource = Enumerable.Range(Global.MinFontSize, 11).ToList();
cmbCurrentFontSize.ItemsSource = Enumerable.Range(Global.MinFontSize, Global.MinFontSizeCount).ToList();
cmbCurrentLanguage.ItemsSource = Global.Languages;
this.WhenActivated(disposables =>