mirror of
https://github.com/2dust/v2rayN.git
synced 2026-01-18 08:39:43 +05:00
Compare commits
64 Commits
20ce35bc30
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03b62b3d78 | ||
|
|
9f9b90cb97 | ||
|
|
c42dcd2876 | ||
|
|
2fefafdd37 | ||
|
|
2c9a90c878 | ||
|
|
4e5f1838a2 | ||
|
|
a45a1dc982 | ||
|
|
fe183798b6 | ||
|
|
947c84cf10 | ||
|
|
9c74b51d74 | ||
|
|
abd962ab31 | ||
|
|
f3b894015e | ||
|
|
4562d4cf00 | ||
|
|
bc36cf8a47 | ||
|
|
cbdfe2e15a | ||
|
|
68583e20bc | ||
|
|
6d6459b009 | ||
|
|
807562b69e | ||
|
|
654d7d83d0 | ||
|
|
027252e687 | ||
|
|
5478c90180 | ||
|
|
28f30d7e97 | ||
|
|
ae7d54c2e5 | ||
|
|
56d0d65b06 | ||
|
|
5e8e189c27 | ||
|
|
3fee86d44a | ||
|
|
dd77eb79c6 | ||
|
|
d26a2559a6 | ||
|
|
e5ba1759aa | ||
|
|
bfdee37cc1 | ||
|
|
cf89cfcd95 | ||
|
|
39a988c704 | ||
|
|
2b28254fbc | ||
|
|
6e27dca6cd | ||
|
|
7cee98887b | ||
|
|
3885ff8b31 | ||
|
|
12abf383e9 | ||
|
|
5bef02bd6d | ||
|
|
592f1260b5 | ||
|
|
18303688d7 | ||
|
|
5c4b7f6636 | ||
|
|
37cce2fa35 | ||
|
|
6f8b65c75b | ||
|
|
83c63b914a | ||
|
|
1ca2485d2a | ||
|
|
cc4154bb0d | ||
|
|
d7f77f220c | ||
|
|
86f45d103d | ||
|
|
0077751f75 | ||
|
|
fa2b4b3dc9 | ||
|
|
23cacb8339 | ||
|
|
9ffa6a4eb6 | ||
|
|
386209b835 | ||
|
|
830dc89c32 | ||
|
|
693afe3560 | ||
|
|
95361e8b65 | ||
|
|
3ff7299aca | ||
|
|
34fc4de0c2 | ||
|
|
91536d3923 | ||
|
|
6b87c09a96 | ||
|
|
ecaac2ac61 | ||
|
|
ad74b1584d | ||
|
|
514d76953a | ||
|
|
5b82f17995 |
18
.github/workflows/build-linux.yml
vendored
18
.github/workflows/build-linux.yml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/build-osx.yml
vendored
6
.github/workflows/build-osx.yml
vendored
@@ -26,13 +26,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@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: |
|
||||
|
||||
6
.github/workflows/build-windows-desktop.yml
vendored
6
.github/workflows/build-windows-desktop.yml
vendored
@@ -26,13 +26,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@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: |
|
||||
|
||||
18
.github/workflows/build-windows.yml
vendored
18
.github/workflows/build-windows.yml
vendored
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.16.3</Version>
|
||||
<Version>7.17.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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("......");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -43,9 +43,12 @@ public sealed class WebDavManager
|
||||
_webDir = _config.WebDavItem.DirName.TrimEx();
|
||||
}
|
||||
|
||||
// Ensure BaseAddress URL ends with a trailing slash
|
||||
var baseUrl = _config.WebDavItem.Url.Trim().TrimEnd('/') + "/";
|
||||
|
||||
var clientParams = new WebDavClientParams
|
||||
{
|
||||
BaseAddress = new Uri(_config.WebDavItem.Url),
|
||||
BaseAddress = new Uri(baseUrl),
|
||||
Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password)
|
||||
};
|
||||
_client = new WebDavClient(clientParams);
|
||||
|
||||
@@ -8,21 +8,6 @@ public class Config
|
||||
public string IndexId { get; set; }
|
||||
public string SubIndexId { get; set; }
|
||||
|
||||
public ECoreType RunningCoreType { get; set; }
|
||||
|
||||
public bool IsRunningCore(ECoreType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
|
||||
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion property
|
||||
|
||||
#region other entities
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
21
v2rayN/ServiceLib/Models/UpdateResult.cs
Normal file
21
v2rayN/ServiceLib/Models/UpdateResult.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
131
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
131
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -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 "Allow Insecure" will be disabled.
|
||||
///
|
||||
///The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. 的本地化字符串。
|
||||
@@ -2619,6 +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'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 {
|
||||
|
||||
@@ -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>
|
||||
@@ -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 l’option « Ignorer la vérification » est désactivée.
|
||||
<value>Pinned certificate (fill in either one)
|
||||
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
|
||||
|
||||
Si un certificat auto-signé est utilisé ou si le système contient une CA non fiable ou malveillante, l’action « Obtenir le certificat » peut échouer.</value>
|
||||
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
|
||||
</data>
|
||||
<data name="TbFetchCert" xml:space="preserve">
|
||||
<value>Obtenir le certificat</value>
|
||||
@@ -1635,4 +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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class StatisticsSingboxService
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
if (!_config.IsRunningCore(ECoreType.sing_box))
|
||||
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class StatisticsXrayService
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
if (_config.RunningCoreType != ECoreType.Xray)
|
||||
if (AppManager.Instance.RunningCoreType != ECoreType.Xray)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -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")))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
||||
{
|
||||
await Task.Delay(1000 * 5);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
{
|
||||
await Task.Delay(1000 * 60);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -44,7 +44,7 @@ public class MsgViewModel : MyReactiveObject
|
||||
|
||||
EnqueueQueueMsg(msg);
|
||||
|
||||
if (!_config.UiItem.ShowInTaskbar)
|
||||
if (!AppManager.Instance.ShowInTaskbar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -549,7 +549,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
try
|
||||
{
|
||||
if (_config.IsRunningCore(ECoreType.sing_box))
|
||||
if (AppManager.Instance.IsRunningCore(ECoreType.sing_box))
|
||||
{
|
||||
SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown));
|
||||
SpeedDirectDisplay = string.Empty;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace v2rayN.Desktop.Common;
|
||||
|
||||
public static class AppBuilderExtension
|
||||
@@ -11,7 +5,7 @@ public static class AppBuilderExtension
|
||||
public static AppBuilder WithFontByDefault(this AppBuilder appBuilder)
|
||||
{
|
||||
var fallbacks = new List<FontFallback>();
|
||||
|
||||
|
||||
var notoSansSc = new FontFamily(Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC"));
|
||||
fallbacks.Add(new FontFallback { FontFamily = notoSansSc });
|
||||
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
</StackPanel>
|
||||
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
ColumnDefinitions="300,Auto,Auto"
|
||||
DockPanel.Dock="Top"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
@@ -75,7 +74,7 @@
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
ColumnDefinitions="180,Auto,Auto">
|
||||
ColumnDefinitions="300,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
@@ -89,11 +88,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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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="*"
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="*"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
Reference in New Issue
Block a user