Compare commits

..

7 Commits

130 changed files with 1238 additions and 2803 deletions

View File

@@ -9,6 +9,10 @@ end_of_line = crlf
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
[*.sh]
end_of_line = lf
indent_size = 2
[*.{yml,yaml}] [*.{yml,yaml}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
@@ -157,14 +161,14 @@ dotnet_naming_rule.non_field_members_should_be_pascal.symbols = non_field_member
dotnet_naming_rule.non_field_members_should_be_pascal.style = pascal dotnet_naming_rule.non_field_members_should_be_pascal.style = pascal
dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = * dotnet_naming_symbols.interface.applicable_accessibilities = *
dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = * dotnet_naming_symbols.types.applicable_accessibilities = *
dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = * dotnet_naming_symbols.non_field_members.applicable_accessibilities = *
dotnet_naming_symbols.non_field_members.required_modifiers = dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_style.pascal.required_prefix = dotnet_naming_style.pascal.required_prefix =
dotnet_naming_style.pascal.required_suffix = dotnet_naming_style.pascal.required_suffix =
dotnet_naming_style.pascal.word_separator = dotnet_naming_style.pascal.word_separator =
dotnet_naming_style.pascal.capitalization = pascal_case dotnet_naming_style.pascal.capitalization = pascal_case

47
package-debian-repo.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
# Root directory = the script's location
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
Version="$1"
PackagePath="v2rayn-unofficial-repo"
mkdir -p "${PackagePath}/DEBIAN"
mkdir -p "${PackagePath}"/etc/apt/{keyrings,sources.list.d}
curl -fsSLo "${PackagePath}/etc/apt/keyrings/v2rayn-unofficial.asc" "https://git.vlyaii.ru/api/packages/voronin9032/debian/repository.key"
# basic
cat >"${PackagePath}/DEBIAN/control" <<-EOF
Package: v2rayn-unofficial-repo
Version: $Version
Maintainer: Vlyaii <voronin9032n3@gmail.com>
Homepage: https://git.vlyaii.ru/voronin9032/v2rayN
Architecture: all
Depends: ca-certificates
Description: v2rayn-unofficial repository configuration
EOF
cat >"${PackagePath}/etc/apt/sources.list.d/v2rayn-unofficial.sources" <<-EOF
Types: deb
URIs: https://git.vlyaii.ru/api/packages/voronin9032/debian
Suites: debian
Components: stable
Architectures: amd64 all
Signed-By: /etc/apt/keyrings/v2rayn-unofficial.asc
EOF
# Patch
# set owner to root:root
sudo chown -R root:root "${PackagePath}"
# set all directories to 755 (readable & traversable by all users)
sudo find "${PackagePath}/etc" -type d -exec chmod 755 {} +
# set all regular files to 644 (readable by all users)
sudo find "${PackagePath}/etc" -type f -exec chmod 644 {} +
# ensure main binaries are 755 (executable by all users)
# build deb package
sudo dpkg-deb -Zzstd -z19 --build "$PackagePath"
sudo mv "${PackagePath}.deb" "v2rayn-unofficial-repo_${Version}_all.deb"

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

@@ -1,38 +1,58 @@
#!/bin/bash #!/usr/bin/env bash
set -euo pipefail
Arch="$1" # Root directory = the script's location
OutputPath="$2" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
Version="$3" cd "$SCRIPT_DIR"
source ./utils.sh
FileName="v2rayN-${Arch}.zip" Arch="linux-64"
wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/$FileName" OutputPath="$(mktemp -d)"
7z x $FileName Version="$1"
cp -rf v2rayN-${Arch}/* $OutputPath
PackagePath="v2rayN-Package-${Arch}" PROJ="./v2rayN/v2rayN.Desktop/v2rayN.Desktop.csproj"
dotnet restore "$PROJ"
sudo rm -rf "$(dirname "$PROJ")/bin/Release/net8.0"
dotnet publish "${PROJ}" -c Release -r "linux-x64" --self-contained -p:StripSymbols=true -o "$OutputPath"
PROJ="./v2rayN/AmazTool/AmazTool.csproj"
dotnet restore "$PROJ"
sudo rm -rf "$(dirname "$PROJ")/bin/Release/net8.0"
dotnet publish "${PROJ}" -c Release -r "linux-x64" --self-contained -p:StripSymbols=true -p:PublishTrimmed=true -o "$OutputPath"
export RID_DIR="linux-x64"
download_xray "$OutputPath/bin/xray"
download_singbox "$OutputPath/bin/sing_box"
download_geo_assets "$OutputPath"
PackagePath="v2rayn-unofficial"
mkdir -p "${PackagePath}/DEBIAN" mkdir -p "${PackagePath}/DEBIAN"
mkdir -p "${PackagePath}/opt" mkdir -p "${PackagePath}/opt"
cp -rf $OutputPath "${PackagePath}/opt/v2rayN" cp -rf "$OutputPath" "${PackagePath}/opt/v2rayN"
echo "When this file exists, app will not store configs under this folder" > "${PackagePath}/opt/v2rayN/NotStoreConfigHere.txt" echo "When this file exists, app will not store configs under this folder" >"${PackagePath}/opt/v2rayN/NotStoreConfigHere.txt"
sudo find "${PackagePath}/opt/v2rayN" -type f -name "*.so" -exec strip {} +
if [ $Arch = "linux-64" ]; then if [ "$Arch" = "linux-64" ]; then
Arch2="amd64" Arch2="amd64"
else else
Arch2="arm64" Arch2="arm64"
fi fi
echo $Arch2
# basic # basic
cat >"${PackagePath}/DEBIAN/control" <<-EOF cat >"${PackagePath}/DEBIAN/control" <<-EOF
Package: v2rayN Package: v2rayn-unofficial
Version: $Version Version: $Version
Maintainer: Vlyaii <voronin9032n3@gmail.com>
Homepage: https://git.vlyaii.ru/voronin9032/v2rayN
Architecture: $Arch2 Architecture: $Arch2
Maintainer: https://github.com/2dust/v2rayN Replaces: v2rayn
Depends: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1) Depends: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1)
Breaks: v2rayn
Conflicts: v2rayn
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
EOF EOF
cat >"${PackagePath}/DEBIAN/postinst" <<-EOF cat >"${PackagePath}/DEBIAN/postinst" <<-EOF
#!/bin/sh
if [ ! -s /usr/share/applications/v2rayN.desktop ]; then if [ ! -s /usr/share/applications/v2rayN.desktop ]; then
cat >/usr/share/applications/v2rayN.desktop<<-END cat >/usr/share/applications/v2rayN.desktop<<-END
[Desktop Entry] [Desktop Entry]
@@ -65,5 +85,6 @@ sudo chmod 755 "${PackagePath}/opt/v2rayN/v2rayN" 2>/dev/null || true
sudo chmod 755 "${PackagePath}/opt/v2rayN/AmazTool" 2>/dev/null || true sudo chmod 755 "${PackagePath}/opt/v2rayN/AmazTool" 2>/dev/null || true
# build deb package # build deb package
sudo dpkg-deb -Zxz --build $PackagePath sudo dpkg-deb -Zzstd -z19 --build "$PackagePath"
sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb" sudo mv "${PackagePath}.deb" "v2rayn-unofficial_${Version}_${Arch2}.deb"
sudo rm -rf "$OutputPath"

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
Arch="$1" Arch="$1"
OutputPath="$2" OutputPath="$2"
@@ -13,7 +13,7 @@ PackagePath="v2rayN-Package-${Arch}"
mkdir -p "$PackagePath/v2rayN.app/Contents/Resources" mkdir -p "$PackagePath/v2rayN.app/Contents/Resources"
cp -rf "$OutputPath" "$PackagePath/v2rayN.app/Contents/MacOS" cp -rf "$OutputPath" "$PackagePath/v2rayN.app/Contents/MacOS"
cp -f "$PackagePath/v2rayN.app/Contents/MacOS/v2rayN.icns" "$PackagePath/v2rayN.app/Contents/Resources/AppIcon.icns" cp -f "$PackagePath/v2rayN.app/Contents/MacOS/v2rayN.icns" "$PackagePath/v2rayN.app/Contents/Resources/AppIcon.icns"
echo "When this file exists, app will not store configs under this folder" > "$PackagePath/v2rayN.app/Contents/MacOS/NotStoreConfigHere.txt" echo "When this file exists, app will not store configs under this folder" >"$PackagePath/v2rayN.app/Contents/MacOS/NotStoreConfigHere.txt"
chmod +x "$PackagePath/v2rayN.app/Contents/MacOS/v2rayN" chmod +x "$PackagePath/v2rayN.app/Contents/MacOS/v2rayN"
cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
@@ -48,11 +48,11 @@ cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
EOF EOF
create-dmg \ create-dmg \
--volname "v2rayN Installer" \ --volname "v2rayN Installer" \
--window-size 700 420 \ --window-size 700 420 \
--icon-size 100 \ --icon-size 100 \
--icon "v2rayN.app" 160 185 \ --icon "v2rayN.app" 160 185 \
--hide-extension "v2rayN.app" \ --hide-extension "v2rayN.app" \
--app-drop-link 500 185 \ --app-drop-link 500 185 \
"v2rayN-${Arch}.dmg" \ "v2rayN-${Arch}.dmg" \
"$PackagePath/v2rayN.app" "$PackagePath/v2rayN.app"

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

@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
Arch="$1" Arch="$1"
OutputPath="$2" OutputPath="$2"
@@ -12,4 +12,4 @@ ZipPath64="./$OutputArch"
mkdir $ZipPath64 mkdir $ZipPath64
cp -rf $OutputPath "$ZipPath64/$OutputArch" cp -rf $OutputPath "$ZipPath64/$OutputArch"
7z a -tZip $FileName "$ZipPath64/$OutputArch" -mx1 7z a -tZip $FileName "$ZipPath64/$OutputArch" -mx1

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

@@ -1,18 +1,18 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian == # == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu ==
if [[ -r /etc/os-release ]]; then if [[ -r /etc/os-release ]]; then
. /etc/os-release source /etc/os-release
case "$ID" in case "$ID" in
rhel|rocky|almalinux|fedora|centos|ubuntu|debian) rhel | rocky | almalinux | fedora | centos | ubuntu)
echo "[OK] Detected supported system: $NAME $VERSION_ID" echo "[OK] Detected supported system: $NAME $VERSION_ID"
;; ;;
*) *)
echo "[ERROR] Unsupported system: $NAME ($ID)." echo "[ERROR] Unsupported system: $NAME ($ID)."
echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS or Ubuntu/Debian." echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS or Ubuntu."
exit 1 exit 1
;; ;;
esac esac
else else
echo "[ERROR] Cannot detect system (missing /etc/os-release)." echo "[ERROR] Cannot detect system (missing /etc/os-release)."
@@ -37,12 +37,9 @@ fi
echo "[OK] Kernel version >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}." echo "[OK] Kernel version >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}."
# ===== Config & Parse arguments ========================================================= # ===== Config & Parse arguments =========================================================
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
WITH_CORE="both" # Default: bundle both xray+sing-box WITH_CORE="both" # Default: bundle both xray+sing-box
AUTOSTART=0 # 1 = enable system-wide autostart (/etc/xdg/autostart) ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target)
FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads
ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target)
BUILD_FROM="" # --buildfrom 1|2|3 to select channel non-interactively
# If the first argument starts with --, do not treat it as a version number # If the first argument starts with --, do not treat it as a version number
if [[ "${VERSION_ARG:-}" == --* ]]; then if [[ "${VERSION_ARG:-}" == --* ]]; then
@@ -54,97 +51,86 @@ if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi
# Parse remaining optional arguments # Parse remaining optional arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--with-core) WITH_CORE="${2:-both}"; shift 2;; --with-core)
--autostart) AUTOSTART=1; shift;; WITH_CORE="${2:-both}"
--xray-ver) XRAY_VER="${2:-}"; shift 2;; shift 2
--singbox-ver) SING_VER="${2:-}"; shift 2;; ;;
--netcore) FORCE_NETCORE=1; shift;; --xray-ver)
--arch) ARCH_OVERRIDE="${2:-}"; shift 2;; XRAY_VER="${2:-}"
--buildfrom) BUILD_FROM="${2:-}"; shift 2;; shift 2
*) ;;
if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi --singbox-ver)
shift;; SING_VER="${2:-}"
shift 2
;;
--arch)
ARCH_OVERRIDE="${2:-}"
shift 2
;;
--release)
RPM_RELEASE="${2:-}"
shift 2
;;
*)
if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi
shift
;;
esac esac
done done
# Conflict: version number AND --buildfrom cannot be used together if [[ -z "${RPM_RELEASE:-}" ]]; then
if [[ -n "${VERSION_ARG:-}" && -n "${BUILD_FROM:-}" ]]; then echo "--release is required"
echo "[ERROR] You cannot specify both an explicit version and --buildfrom at the same time."
echo " Provide either a version (e.g. 7.14.0) OR --buildfrom 1|2|3."
exit 1 exit 1
fi fi
# ===== Environment check + Dependencies ======================================== # ===== Environment check + Dependencies ========================================
host_arch="$(uname -m)" host_arch="$(uname -m)"
[[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; } if ! [[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]]; then
echo "Only supports aarch64 / x86_64"
exit 1
fi
install_ok=0 install_ok=0
case "$ID" in case "$ID" in
# ------------------------------ RHEL family (UNCHANGED) ------------------------------ # ------------------------------ RHEL family (UNCHANGED) ------------------------------
rhel|rocky|almalinux|centos) rhel | rocky | almalinux | centos)
if command -v dnf >/dev/null 2>&1; then if command -v dnf >/dev/null 2>&1; then
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \ sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync ||
sudo dnf -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync sudo dnf -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
install_ok=1 install_ok=1
elif command -v yum >/dev/null 2>&1; then elif command -v yum >/dev/null 2>&1; then
sudo yum -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \ sudo yum -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync ||
sudo yum -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync sudo yum -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
install_ok=1
fi
;;
# ------------------------------ Ubuntu ----------------------------------------------
ubuntu)
sudo apt-get update
# Ensure 'universe' (Ubuntu) to get 'rpm'
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
sudo apt-get -y install software-properties-common || true
sudo add-apt-repository -y universe || true
sudo apt-get update
fi
# Base tools + rpm (provides rpmbuild)
sudo apt-get -y install curl unzip tar rsync rpm || true
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
# rpmbuild presence check
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
exit 1
fi
# .NET SDK 8 (best effort via apt)
if ! command -v dotnet >/dev/null 2>&1; then
sudo apt-get -y install dotnet-sdk-8.0 || true
sudo apt-get -y install dotnet-sdk-8 || true
sudo apt-get -y install dotnet-sdk || true
fi
install_ok=1 install_ok=1
;; fi
# ------------------------------ Debian (KEEP, with local dotnet install) ------------ ;;
debian) # ------------------------------ 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 sudo apt-get update
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip fi
sudo apt-get -y install curl unzip tar rsync rpm binutils || true # Base tools + rpm (provides rpmbuild)
# rpmbuild presence check sudo apt-get -y install curl unzip tar rsync rpm || true
if ! command -v rpmbuild >/dev/null 2>&1; then # Cross-arch binutils so strip matches target arch + objdump for brp scripts
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'." sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
echo " Please ensure 'rpm' is available from Debian repos." # rpmbuild presence check
exit 1 if ! command -v rpmbuild >/dev/null 2>&1; then
fi echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
if ! command -v dotnet >/dev/null 2>&1; then exit 1
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..." fi
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN # .NET SDK 8 (best effort via apt)
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh" if ! command -v dotnet >/dev/null 2>&1; then
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet" sudo apt-get -y install dotnet-sdk-8.0 || true
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH" sudo apt-get -y install dotnet-sdk-8 || true
export DOTNET_ROOT="$HOME/.dotnet" sudo apt-get -y install dotnet-sdk || true
if ! command -v dotnet >/dev/null 2>&1; then fi
echo "[ERROR] dotnet installation failed." install_ok=1
exit 1 ;;
fi
fi
install_ok=1
;;
esac esac
if [[ "$install_ok" -ne 1 ]]; then if [[ "$install_ok" -ne 1 ]]; then
@@ -158,6 +144,8 @@ command -v curl >/dev/null
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
source ./utils.sh
# Git submodules (best effort) # Git submodules (best effort)
if [[ -f .gitmodules ]]; then if [[ -f .gitmodules ]]; then
git submodule sync --recursive || true git submodule sync --recursive || true
@@ -169,348 +157,16 @@ PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
if [[ ! -f "$PROJECT" ]]; then if [[ ! -f "$PROJECT" ]]; then
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)" PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
fi fi
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; } [[ -f "$PROJECT" ]] || {
echo "v2rayN.Desktop.csproj not found"
# ===== Resolve GUI version & auto checkout ============================================ exit 1
VERSION=""
choose_channel() {
# If --buildfrom provided, map it directly and skip interaction.
if [[ -n "${BUILD_FROM:-}" ]]; then
case "$BUILD_FROM" in
1) echo "latest"; return 0;;
2) echo "prerelease"; return 0;;
3) echo "keep"; return 0;;
*) echo "[ERROR] Invalid --buildfrom value: ${BUILD_FROM}. Use 1|2|3." >&2; exit 1;;
esac
fi
# Print menu to stderr and read from /dev/tty so stdout only carries the token.
local ch="latest" sel=""
if [[ -t 0 ]]; then
echo "[?] Choose v2rayN release channel:" >&2
echo " 1) Latest (stable) [default]" >&2
echo " 2) Pre-release (preview)" >&2
echo " 3) Keep current (do nothing)" >&2
printf "Enter 1, 2 or 3 [default 1]: " >&2
if read -r sel </dev/tty; then
case "${sel:-}" in
2) ch="prerelease" ;;
3) ch="keep" ;;
*) ch="latest" ;;
esac
else
ch="latest"
fi
else
ch="latest"
fi
echo "$ch"
} }
get_latest_tag_latest() { VERSION="$VERSION_ARG"
# Resolve /releases/latest → tag_name
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \
| grep -Eo '"tag_name":\s*"v?[^"]+"' \
| head -n1 \
| sed -E 's/.*"tag_name":\s*"v?([^"]+)".*/\1/'
}
get_latest_tag_prerelease() {
# Resolve newest prerelease=true tag; prefer jq, fallback to sed/grep (no awk)
local json tag
json="$(curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20")" || return 1
# 1) Use jq if present
if command -v jq >/dev/null 2>&1; then
tag="$(printf '%s' "$json" \
| jq -r '[.[] | select(.prerelease==true)][0].tag_name' 2>/dev/null \
| sed 's/^v//')" || true
fi
# 2) Fallback to sed/grep only
if [[ -z "${tag:-}" || "${tag:-}" == "null" ]]; then
tag="$(printf '%s' "$json" \
| tr '\n' ' ' \
| sed 's/},[[:space:]]*{/\n/g' \
| grep -m1 -E '"prerelease"[[:space:]]*:[[:space:]]*true' \
| grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"v?[^"]+"' \
| head -n1 \
| sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/')" || true
fi
[[ -n "${tag:-}" && "${tag:-}" != "null" ]] || return 1
printf '%s\n' "$tag"
}
git_try_checkout() {
# Try a series of refs and checkout when found.
local want="$1" ref=""
if git rev-parse --git-dir >/dev/null 2>&1; then
git fetch --tags --force --prune --depth=1 || true
if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then
ref="v${want}"
elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then
ref="${want}"
elif git rev-parse --verify "${want}" >/dev/null 2>&1; then
ref="${want}"
fi
if [[ -n "$ref" ]]; then
echo "[OK] Found ref '${ref}', checking out..."
git checkout -f "${ref}"
if [[ -f .gitmodules ]]; then
git submodule sync --recursive || true
git submodule update --init --recursive || true
fi
return 0
fi
fi
return 1
}
if git rev-parse --git-dir >/dev/null 2>&1; then
if [[ -n "${VERSION_ARG:-}" ]]; then
echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}"
if git_try_checkout "${VERSION_ARG#v}"; then
VERSION="${VERSION_ARG#v}"
else
echo "[WARN] Tag '${VERSION_ARG}' not found."
ch="$(choose_channel)"
if [[ "$ch" == "keep" ]]; then
echo "[*] Keep current repository state (no checkout)."
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
VERSION="$(git describe --tags --abbrev=0)"
else
VERSION="0.0.0+git"
fi
VERSION="${VERSION#v}"
else
echo "[*] Resolving ${ch} tag from GitHub releases..."
tag=""
if [[ "$ch" == "prerelease" ]]; then
tag="$(get_latest_tag_prerelease || true)"
if [[ -z "$tag" ]]; then
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
tag="$(get_latest_tag_latest || true)"
fi
else
tag="$(get_latest_tag_latest || true)"
fi
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
echo "[*] Latest tag for '${ch}': ${tag}"
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
VERSION="${tag#v}"
fi
fi
else
ch="$(choose_channel)"
if [[ "$ch" == "keep" ]]; then
echo "[*] Keep current repository state (no checkout)."
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
VERSION="$(git describe --tags --abbrev=0)"
else
VERSION="0.0.0+git"
fi
VERSION="${VERSION#v}"
else
echo "[*] Resolving ${ch} tag from GitHub releases..."
tag=""
if [[ "$ch" == "prerelease" ]]; then
tag="$(get_latest_tag_prerelease || true)"
if [[ -z "$tag" ]]; then
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
tag="$(get_latest_tag_latest || true)"
fi
else
tag="$(get_latest_tag_latest || true)"
fi
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
echo "[*] Latest tag for '${ch}': ${tag}"
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
VERSION="${tag#v}"
fi
fi
else
echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree."
VERSION="${VERSION_ARG:-}"
if [[ -z "$VERSION" ]]; then
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
VERSION="$(git describe --tags --abbrev=0)"
else
VERSION="0.0.0+git"
fi
fi
VERSION="${VERSION#v}"
fi
echo "[*] GUI version resolved as: ${VERSION}"
# ===== Helpers for core/rules download (use RID_DIR for arch sync) =====================
download_xray() {
# Download Xray core and install to outdir/xray
local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip"
mkdir -p "$outdir"
if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi
if [[ -z "$ver" ]]; then
ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
fi
[[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; }
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip"
else
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip"
fi
echo "[+] Download xray: $url"
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fL "$url" -o "$tmp/$zipname"
unzip -q "$tmp/$zipname" -d "$tmp"
install -Dm755 "$tmp/xray" "$outdir/xray"
}
download_singbox() {
# Download sing-box core and install to outdir/sing-box
local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin
mkdir -p "$outdir"
if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi
if [[ -z "$ver" ]]; then
ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
fi
[[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; }
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz"
else
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz"
fi
echo "[+] Download sing-box: $url"
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fL "$url" -o "$tmp/$tarname"
tar -C "$tmp" -xzf "$tmp/$tarname"
bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)"
[[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; return 1; }
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"
mkdir -p "$outroot/bin"
local names=( \
"geosite.dat" \
"geoip.dat" \
"geoip-only-cn-private.dat" \
"Country.mmdb" \
"geoip.metadb" \
)
for n in "${names[@]}"; do
# If file exists under bin/xray/, move it up to bin/
if [[ -f "$outroot/bin/xray/$n" ]]; then
mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n"
fi
# If file already in bin/, leave it as-is
if [[ -f "$outroot/bin/$n" ]]; then
:
fi
done
}
# Download geo/rule assets; then unify to bin/
download_geo_assets() {
local outroot="$1"
local bin_dir="$outroot/bin"
local srss_dir="$bin_dir/srss"
mkdir -p "$bin_dir" "$srss_dir"
echo "[+] Download Xray Geo to ${bin_dir}"
curl -fsSL -o "$bin_dir/geosite.dat" \
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat"
curl -fsSL -o "$bin_dir/geoip.dat" \
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat"
curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat"
curl -fsSL -o "$bin_dir/Country.mmdb" \
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
echo "[+] Download sing-box rule DB & rule-sets"
curl -fsSL -o "$bin_dir/geoip.metadb" \
"https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true
for f in \
geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \
geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do
curl -fsSL -o "$srss_dir/$f" \
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
done
for f in \
geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
curl -fsSL -o "$srss_dir/$f" \
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
done
# Unify to bin/
unify_geo_layout "$outroot"
}
# Prefer the prebuilt v2rayN core bundle; then unify geo layout
download_v2rayn_bundle() {
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.zip"
else
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip"
fi
echo "[+] Try v2rayN bundle archive: $url"
local tmp zipname
tmp="$(mktemp -d)"; zipname="$tmp/v2rayn.zip"
curl -fL "$url" -o "$zipname" || { echo "[!] Bundle download failed"; return 1; }
unzip -q "$zipname" -d "$tmp" || { echo "[!] Bundle unzip failed"; return 1; }
if [[ -d "$tmp/bin" ]]; then
mkdir -p "$outroot/bin"
rsync -a "$tmp/bin/" "$outroot/bin/"
else
rsync -a "$tmp/" "$outroot/"
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
local nested_dir
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
if [[ -n "${nested_dir:-}" && -d "$nested_dir/bin" ]]; then
mkdir -p "$outroot/bin"
rsync -a "$nested_dir/bin/" "$outroot/bin/"
rm -rf "$nested_dir"
fi
# Unify to bin/
unify_geo_layout "$outroot"
echo "[+] Bundle extracted to $outroot"
}
# ===== Build results collection for --arch all ======================================== # ===== Build results collection for --arch all ========================================
BUILT_RPMS=() # Will collect absolute paths of built RPMs BUILT_RPMS=() # Will collect absolute paths of built RPMs
BUILT_ALL=0 # Flag to know if we should print the final summary BUILT_ALL=0 # Flag to know if we should print the final summary
# ===== Build (single-arch) function ==================================================== # ===== Build (single-arch) function ====================================================
build_for_arch() { build_for_arch() {
@@ -518,29 +174,41 @@ build_for_arch() {
local short="$1" local short="$1"
local rid rpm_target archdir local rid rpm_target archdir
case "$short" in case "$short" in
x64) rid="linux-x64"; rpm_target="x86_64"; archdir="x86_64" ;; x64)
arm64) rid="linux-arm64"; rpm_target="aarch64"; archdir="aarch64" ;; rid="linux-x64"
*) echo "[ERROR] Unknown arch '$short' (use x64|arm64)"; return 1;; rpm_target="x86_64"
archdir="x86_64"
;;
arm64)
rid="linux-arm64"
rpm_target="aarch64"
archdir="aarch64"
;;
*)
echo "[ERROR] Unknown arch '$short' (use x64|arm64)"
return 1
;;
esac esac
echo "[*] Building for target: $short (RID=$rid, RPM --target $rpm_target)" echo "[*] Building for target: $short (RID=$rid, RPM --target $rpm_target)"
# .NET publish (self-contained) for this RID # .NET publish (self-contained) for this RID
dotnet clean "$PROJECT" -c Release
rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true
dotnet restore "$PROJECT" dotnet restore "$PROJECT"
rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true
dotnet publish "$PROJECT" \ dotnet publish "$PROJECT" \
-c Release -r "$rid" \ -c Release -r "$rid" \
--sc \
-p:PublishSingleFile=false \ -p:PublishSingleFile=false \
-p:SelfContained=true \ -p:SelfContained=true \
-p:IncludeNativeLibrariesForSelfExtract=true -p:IncludeNativeLibrariesForSelfExtract=true \
-p:StripSymbols=true
# Per-arch variables (scoped) # Per-arch variables (scoped)
local RID_DIR="$rid" local RID_DIR="$rid"
local PUBDIR local PUBDIR
PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish" PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish"
[[ -d "$PUBDIR" ]] [[ -d "$PUBDIR" ]]
sudo find "$PUBDIR" -type f -name "*.so" -exec strip {} +
# Make RID_DIR visible to download helpers (they read this var) # Make RID_DIR visible to download helpers (they read this var)
export RID_DIR export RID_DIR
@@ -580,31 +248,13 @@ build_for_arch() {
mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box" mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box"
# Bundle / cores per-arch # Bundle / cores per-arch
if [[ "$FORCE_NETCORE" -eq 0 ]]; then if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
echo "[*] Using v2rayN bundle archive."
else
echo "[*] Bundle failed, fallback to separate core + rules."
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
fi
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
fi
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
fi
else
echo "[*] --netcore specified: use separate core + rules."
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
fi
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
fi
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
fi fi
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
fi
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
# Tarball # Tarball
mkdir -p "$SOURCEDIR" mkdir -p "$SOURCEDIR"
@@ -613,19 +263,19 @@ build_for_arch() {
# SPEC # SPEC
local SPECFILE="$SPECDIR/v2rayN.spec" local SPECFILE="$SPECDIR/v2rayN.spec"
mkdir -p "$SPECDIR" mkdir -p "$SPECDIR"
cat > "$SPECFILE" <<'SPEC' cat >"$SPECFILE" <<'SPEC'
%global debug_package %{nil} %global debug_package %{nil}
%undefine _debuginfo_subpackages %undefine _debuginfo_subpackages
%undefine _debugsource_packages %undefine _debugsource_packages
# Ignore outdated LTTng dependencies incorrectly reported by the .NET runtime (to avoid installation failures) # Ignore outdated LTTng dependencies incorrectly reported by the .NET runtime (to avoid installation failures)
%global __requires_exclude ^liblttng-ust\.so\..*$ %global __requires_exclude ^liblttng-ust\.so\..*$
Name: v2rayN Name: v2rayn-unofficial
Version: __VERSION__ Version: __VERSION__
Release: 1%{?dist} Release: __RELEASE__
Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64) Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64)
License: GPL-3.0-only License: GPL-3.0-only
URL: https://github.com/2dust/v2rayN URL: https://git.vlyaii.ru/voronin9032/v2rayN
BugURL: https://github.com/2dust/v2rayN/issues BugURL: https://github.com/2dust/v2rayN/issues
ExclusiveArch: aarch64 x86_64 ExclusiveArch: aarch64 x86_64
Source0: __PKGROOT__.tar.gz Source0: __PKGROOT__.tar.gz
@@ -639,6 +289,9 @@ Requires: xdg-utils >= 1.1.3
Requires: coreutils >= 8.32 Requires: coreutils >= 8.32
Requires: bash >= 5.1 Requires: bash >= 5.1
Conflicts: v2rayN
Obsoletes: v2rayN
%description %description
v2rayN Linux for Red Hat Enterprise Linux v2rayN Linux for Red Hat Enterprise Linux
Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / Shadowsocks / tuic / WireGuard Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / Shadowsocks / tuic / WireGuard
@@ -711,41 +364,9 @@ fi
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png %{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
SPEC SPEC
# Autostart injection (inside %install) and %files entry
if [[ "$AUTOSTART" -eq 1 ]]; then
awk '
BEGIN{ins=0}
/^%post$/ && !ins {
print "# --- Autostart (.desktop) ---"
print "install -dm0755 %{buildroot}/etc/xdg/autostart"
print "cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << '\''EOF'\''"
print "[Desktop Entry]"
print "Type=Application"
print "Name=v2rayN (Autostart)"
print "Exec=v2rayn"
print "X-GNOME-Autostart-enabled=true"
print "NoDisplay=false"
print "EOF"
ins=1
}
{print}
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
awk '
BEGIN{infiles=0; done=0}
/^%files$/ {infiles=1}
infiles && done==0 && $0 ~ /%{_datadir}\/icons\/hicolor\/256x256\/apps\/v2rayn\.png/ {
print
print "%config(noreplace) /etc/xdg/autostart/v2rayn.desktop"
done=1
next
}
{print}
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
fi
# Replace placeholders # Replace placeholders
sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE" sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE"
sed -i "s/__RELEASE__/${RPM_RELEASE}/g" "$SPECFILE"
sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE" sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE"
# ----- Select proper 'strip' per target arch on Ubuntu only (cross-binutils) ----- # ----- Select proper 'strip' per target arch on Ubuntu only (cross-binutils) -----
@@ -760,7 +381,7 @@ SPEC
STRIP_BIN="/usr/bin/aarch64-linux-gnu-strip" STRIP_BIN="/usr/bin/aarch64-linux-gnu-strip"
fi fi
if [[ -x "$STRIP_BIN" ]]; then if [[ -x "$STRIP_BIN" ]]; then
STRIP_ARGS=( --define "__strip $STRIP_BIN" ) STRIP_ARGS=(--define "__strip $STRIP_BIN")
fi fi
fi fi
@@ -780,7 +401,7 @@ SPEC
echo "Build done for $short. RPM at:" echo "Build done for $short. RPM at:"
local f local f
for f in "${TOPDIR}/RPMS/${archdir}/v2rayN-${VERSION}-1"*.rpm; do for f in "${TOPDIR}/RPMS/${archdir}/v2rayN-${VERSION}-${RPM_RELEASE}"*.rpm; do
[[ -e "$f" ]] || continue [[ -e "$f" ]] || continue
echo " $f" echo " $f"
BUILT_RPMS+=("$f") BUILT_RPMS+=("$f")
@@ -789,30 +410,30 @@ SPEC
# ===== Arch selection and build orchestration ========================================= # ===== Arch selection and build orchestration =========================================
case "${ARCH_OVERRIDE:-}" in case "${ARCH_OVERRIDE:-}" in
"") "")
# No --arch: use host architecture # No --arch: use host architecture
if [[ "$host_arch" == "aarch64" ]]; then if [[ "$host_arch" == "aarch64" ]]; then
build_for_arch arm64
else
build_for_arch x64
fi
;;
x64|amd64)
build_for_arch x64
;;
arm64|aarch64)
build_for_arch arm64 build_for_arch arm64
;; else
all)
BUILT_ALL=1
# Build x64 and arm64 separately; each package contains its own arch-only binaries.
build_for_arch x64 build_for_arch x64
build_for_arch arm64 fi
;; ;;
*) x64 | amd64)
echo "[ERROR] Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all." build_for_arch x64
exit 1 ;;
;; arm64 | aarch64)
build_for_arch arm64
;;
all)
BUILT_ALL=1
# Build x64 and arm64 separately; each package contains its own arch-only binaries.
build_for_arch x64
build_for_arch arm64
;;
*)
echo "[ERROR] Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."
exit 1
;;
esac esac
# ===== Final summary if building both arches ========================================== # ===== Final summary if building both arches ==========================================

120
utils.sh Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env bash
download_xray() {
# Download Xray core and install to outdir/xray
local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip"
mkdir -p "$outdir"
if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi
if [[ -z "$ver" ]]; then
echo "Downloading latest xray"
ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest |
grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
fi
if [[ -z "$ver" ]]; then
echo "[xray] Failed to get version"
return 1
fi
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip"
else
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip"
fi
echo "[+] Download xray: $url"
tmp="$(mktemp -d)"
trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fL "$url" -o "$tmp/$zipname"
unzip -q "$tmp/$zipname" -d "$tmp"
install -Dm755 "$tmp/xray" "$outdir/xray"
}
download_singbox() {
# Download sing-box core and install to outdir/sing-box
local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin
mkdir -p "$outdir"
if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi
if [[ -z "$ver" ]]; then
echo "Downloading latest sing-box"
ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest |
grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
fi
if [[ -z "$ver" ]]; then
echo "[sing-box] Failed to get version"
return 1
fi
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz"
else
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz"
fi
echo "[+] Download sing-box: $url"
tmp="$(mktemp -d)"
trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fL "$url" -o "$tmp/$tarname"
tar -C "$tmp" -xzf "$tmp/$tarname"
bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)"
[[ -n "$bin" ]] || {
echo "[!] sing-box unpack failed"
return 1
}
install -Dm755 "$bin" "$outdir/sing-box"
}
# Move geo files to a unified path: outroot/bin
unify_geo_layout() {
local outroot="$1"
mkdir -p "$outroot/bin"
local names=(
"geosite.dat"
"geoip.dat"
"geoip-only-cn-private.dat"
"Country.mmdb"
"geoip.metadb"
)
for n in "${names[@]}"; do
# If file exists under bin/xray/, move it up to bin/
if [[ -f "$outroot/bin/xray/$n" ]]; then
mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n"
fi
# If file already in bin/, leave it as-is
if [[ -f "$outroot/bin/$n" ]]; then
:
fi
done
}
# Download geo/rule assets; then unify to bin/
download_geo_assets() {
local outroot="$1"
local bin_dir="$outroot/bin"
local srss_dir="$bin_dir/srss"
mkdir -p "$bin_dir" "$srss_dir"
echo "[+] Download Xray Geo to ${bin_dir}"
curl -fsSL -o "$bin_dir/geosite.dat" \
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat"
curl -fsSL -o "$bin_dir/geoip.dat" \
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat"
curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat"
curl -fsSL -o "$bin_dir/Country.mmdb" \
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
echo "[+] Download sing-box rule DB & rule-sets"
curl -fsSL -o "$bin_dir/geoip.metadb" \
"https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true
for f in \
geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \
geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do
curl -fsSL -o "$srss_dir/$f" \
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
done
for f in \
geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
curl -fsSL -o "$srss_dir/$f" \
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
done
# Unify to bin/
unify_geo_layout "$outroot"
}

View File

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

View File

@@ -14,7 +14,7 @@
<PackageVersion Include="Downloader" Version="4.0.3" /> <PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" /> <PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" /> <PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.0" /> <PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
<PackageVersion Include="QRCoder" Version="1.7.0" /> <PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="ReactiveUI" Version="22.2.1" /> <PackageVersion Include="ReactiveUI" Version="22.2.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" /> <PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />

View File

@@ -3,7 +3,7 @@ using System.IO.Compression;
namespace ServiceLib.Common; namespace ServiceLib.Common;
public static class FileUtils public static class FileManager
{ {
private static readonly string _tag = "FileManager"; private static readonly string _tag = "FileManager";

View File

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

View File

@@ -9,7 +9,7 @@ public class Utils
{ {
private static readonly string _tag = "Utils"; private static readonly string _tag = "Utils";
#region Conversion Functions #region
/// <summary> /// <summary>
/// Convert to comma-separated string /// Convert to comma-separated string
@@ -306,10 +306,7 @@ public class Utils
public static bool IsBase64String(string? plainText) public static bool IsBase64String(string? plainText)
{ {
if (plainText.IsNullOrEmpty()) if (plainText.IsNullOrEmpty())
{
return false; return false;
}
var buffer = new Span<byte>(new byte[plainText.Length]); var buffer = new Span<byte>(new byte[plainText.Length]);
return Convert.TryFromBase64String(plainText, buffer, out var _); return Convert.TryFromBase64String(plainText, buffer, out var _);
} }
@@ -427,7 +424,7 @@ public class Utils
// Handle IPv6 addresses, e.g., "[2001:db8::1]:443" // Handle IPv6 addresses, e.g., "[2001:db8::1]:443"
if (authority.StartsWith("[") && authority.Contains("]")) if (authority.StartsWith("[") && authority.Contains("]"))
{ {
var closingBracketIndex = authority.LastIndexOf(']'); int closingBracketIndex = authority.LastIndexOf(']');
if (closingBracketIndex < authority.Length - 1 && authority[closingBracketIndex + 1] == ':') if (closingBracketIndex < authority.Length - 1 && authority[closingBracketIndex + 1] == ':')
{ {
// Port exists // Port exists
@@ -462,9 +459,9 @@ public class Utils
return (domain, port); return (domain, port);
} }
#endregion Conversion Functions #endregion
#region Data Checks #region
/// <summary> /// <summary>
/// Determine if the input is a number /// Determine if the input is a number
@@ -523,62 +520,40 @@ public class Utils
{ {
// Loopback address check (127.0.0.1 for IPv4, ::1 for IPv6) // Loopback address check (127.0.0.1 for IPv4, ::1 for IPv6)
if (IPAddress.IsLoopback(address)) if (IPAddress.IsLoopback(address))
{
return true; return true;
}
var ipBytes = address.GetAddressBytes(); var ipBytes = address.GetAddressBytes();
if (address.AddressFamily == AddressFamily.InterNetwork) if (address.AddressFamily == AddressFamily.InterNetwork)
{ {
// IPv4 private address check // IPv4 private address check
if (ipBytes[0] == 10) if (ipBytes[0] == 10)
{
return true; return true;
}
if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31) if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31)
{
return true; return true;
}
if (ipBytes[0] == 192 && ipBytes[1] == 168) if (ipBytes[0] == 192 && ipBytes[1] == 168)
{
return true; return true;
}
} }
else if (address.AddressFamily == AddressFamily.InterNetworkV6) else if (address.AddressFamily == AddressFamily.InterNetworkV6)
{ {
// IPv6 private address check // IPv6 private address check
// Link-local address fe80::/10 // Link-local address fe80::/10
if (ipBytes[0] == 0xfe && (ipBytes[1] & 0xc0) == 0x80) if (ipBytes[0] == 0xfe && (ipBytes[1] & 0xc0) == 0x80)
{
return true; return true;
}
// Unique local address fc00::/7 (typically fd00::/8) // Unique local address fc00::/7 (typically fd00::/8)
if ((ipBytes[0] & 0xfe) == 0xfc) if ((ipBytes[0] & 0xfe) == 0xfc)
{
return true; return true;
}
// Private portion in IPv4-mapped addresses ::ffff:0:0/96 // Private portion in IPv4-mapped addresses ::ffff:0:0/96
if (address.IsIPv4MappedToIPv6) if (address.IsIPv4MappedToIPv6)
{ {
var ipv4Bytes = ipBytes.Skip(12).ToArray(); var ipv4Bytes = ipBytes.Skip(12).ToArray();
if (ipv4Bytes[0] == 10) if (ipv4Bytes[0] == 10)
{
return true; return true;
}
if (ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31) if (ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31)
{
return true; return true;
}
if (ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168) if (ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168)
{
return true; return true;
}
} }
} }
} }
@@ -586,9 +561,9 @@ public class Utils
return false; return false;
} }
#endregion Data Checks #endregion
#region Speed Test #region
private static bool PortInUse(int port) private static bool PortInUse(int port)
{ {
@@ -641,9 +616,9 @@ public class Utils
return 59090; return 59090;
} }
#endregion Speed Test #endregion
#region Miscellaneous #region
public static bool UpgradeAppExists(out string upgradeFileName) public static bool UpgradeAppExists(out string upgradeFileName)
{ {
@@ -733,16 +708,10 @@ public class Utils
foreach (var host in hostsList) foreach (var host in hostsList)
{ {
if (host.StartsWith("#")) if (host.StartsWith("#"))
{
continue; continue;
}
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (hostItem.Length < 2) if (hostItem.Length < 2)
{
continue; continue;
}
systemHosts.Add(hostItem[1], hostItem[0]); systemHosts.Add(hostItem[1], hostItem[0]);
} }
} }
@@ -793,7 +762,7 @@ public class Utils
return null; return null;
} }
#endregion Miscellaneous #endregion
#region TempPath #region TempPath

View File

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

View File

@@ -97,7 +97,7 @@ public static class ConfigHandler
config.UiItem ??= new UIItem() config.UiItem ??= new UIItem()
{ {
EnableUpdateSubOnlyRemarksExist = true EnableAutoAdjustMainLvColWidth = true
}; };
config.UiItem.MainColumnItem ??= new(); config.UiItem.MainColumnItem ??= new();
config.UiItem.WindowSizeItem ??= new(); config.UiItem.WindowSizeItem ??= new();
@@ -252,7 +252,6 @@ public static class ConfigHandler
item.Mldsa65Verify = profileItem.Mldsa65Verify; item.Mldsa65Verify = profileItem.Mldsa65Verify;
item.Extra = profileItem.Extra; item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled; item.MuxEnabled = profileItem.MuxEnabled;
item.Cert = profileItem.Cert;
} }
var ret = item.ConfigType switch var ret = item.ConfigType switch
@@ -448,13 +447,13 @@ public static class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns> /// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> MoveServer(Config config, List<ProfileItem> lstProfile, int index, EMove eMove, int pos = -1) public static async Task<int> MoveServer(Config config, List<ProfileItem> lstProfile, int index, EMove eMove, int pos = -1)
{ {
var count = lstProfile.Count; int count = lstProfile.Count;
if (index < 0 || index > lstProfile.Count - 1) if (index < 0 || index > lstProfile.Count - 1)
{ {
return -1; return -1;
} }
for (var i = 0; i < lstProfile.Count; i++) for (int i = 0; i < lstProfile.Count; i++)
{ {
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10); ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
} }
@@ -528,7 +527,7 @@ public static class ConfigHandler
return -1; return -1;
} }
var ext = Path.GetExtension(fileName); var ext = Path.GetExtension(fileName);
var newFileName = $"{Utils.GetGuid()}{ext}"; string newFileName = $"{Utils.GetGuid()}{ext}";
//newFileName = Path.Combine(Utile.GetTempPath(), newFileName); //newFileName = Path.Combine(Utile.GetTempPath(), newFileName);
try try
@@ -1357,7 +1356,7 @@ public static class ConfigHandler
} }
continue; continue;
} }
var profileItem = FmtHandler.ResolveConfig(str, out var msg); var profileItem = FmtHandler.ResolveConfig(str, out string msg);
if (profileItem is null) if (profileItem is null)
{ {
continue; continue;
@@ -1441,7 +1440,7 @@ public static class ConfigHandler
{ {
await RemoveServersViaSubid(config, subid, isSub); await RemoveServersViaSubid(config, subid, isSub);
} }
var count = 0; int count = 0;
foreach (var it in lstProfiles) foreach (var it in lstProfiles)
{ {
it.Subid = subid; it.Subid = subid;
@@ -1531,7 +1530,7 @@ public static class ConfigHandler
var lstSsServer = ShadowsocksFmt.ResolveSip008(strData); var lstSsServer = ShadowsocksFmt.ResolveSip008(strData);
if (lstSsServer?.Count > 0) if (lstSsServer?.Count > 0)
{ {
var counter = 0; int counter = 0;
foreach (var ssItem in lstSsServer) foreach (var ssItem in lstSsServer)
{ {
ssItem.Subid = subid; ssItem.Subid = subid;
@@ -1651,9 +1650,7 @@ public static class ConfigHandler
var uri = Utils.TryUri(url); var uri = Utils.TryUri(url);
if (uri == null) if (uri == null)
{
return -1; return -1;
}
//Do not allow http protocol //Do not allow http protocol
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{ {
@@ -1708,7 +1705,7 @@ public static class ConfigHandler
var maxSort = 0; var maxSort = 0;
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0) if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
{ {
var lstSubs = await AppManager.Instance.SubItems(); var lstSubs = (await AppManager.Instance.SubItems());
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0; maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
} }
item.Sort = maxSort + 1; item.Sort = maxSort + 1;
@@ -1870,7 +1867,7 @@ public static class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns> /// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> MoveRoutingRule(List<RulesItem> rules, int index, EMove eMove, int pos = -1) public static async Task<int> MoveRoutingRule(List<RulesItem> rules, int index, EMove eMove, int pos = -1)
{ {
var count = rules.Count; int count = rules.Count;
if (index < 0 || index > rules.Count - 1) if (index < 0 || index > rules.Count - 1)
{ {
return -1; return -1;
@@ -2020,15 +2017,11 @@ public static class ConfigHandler
var downloadHandle = new DownloadService(); var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, ""); var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, "");
if (templateContent.IsNullOrEmpty()) if (templateContent.IsNullOrEmpty())
{
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
}
var template = JsonUtils.Deserialize<RoutingTemplate>(templateContent); var template = JsonUtils.Deserialize<RoutingTemplate>(templateContent);
if (template == null) if (template == null)
{
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
}
var items = await AppManager.Instance.RoutingItems(); var items = await AppManager.Instance.RoutingItems();
var maxSort = items.Count; var maxSort = items.Count;
@@ -2041,18 +2034,14 @@ public static class ConfigHandler
var item = template.RoutingItems[i]; var item = template.RoutingItems[i];
if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty()) if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty())
{
continue; continue;
}
var ruleSetsString = !item.RuleSet.IsNullOrEmpty() var ruleSetsString = !item.RuleSet.IsNullOrEmpty()
? item.RuleSet ? item.RuleSet
: await downloadHandle.TryDownloadString(item.Url, true, ""); : await downloadHandle.TryDownloadString(item.Url, true, "");
if (ruleSetsString.IsNullOrEmpty()) if (ruleSetsString.IsNullOrEmpty())
{
continue; continue;
}
item.Remarks = $"{template.Version}-{item.Remarks}"; item.Remarks = $"{template.Version}-{item.Remarks}";
item.Enabled = true; item.Enabled = true;
@@ -2248,25 +2237,17 @@ public static class ConfigHandler
var downloadHandle = new DownloadService(); var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, ""); var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (templateContent.IsNullOrEmpty()) if (templateContent.IsNullOrEmpty())
{
return currentItem; return currentItem;
}
var template = JsonUtils.Deserialize<DNSItem>(templateContent); var template = JsonUtils.Deserialize<DNSItem>(templateContent);
if (template == null) if (template == null)
{
return currentItem; return currentItem;
}
if (!template.NormalDNS.IsNullOrEmpty()) if (!template.NormalDNS.IsNullOrEmpty())
{
template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, ""); template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, "");
}
if (!template.TunDNS.IsNullOrEmpty()) if (!template.TunDNS.IsNullOrEmpty())
{
template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, ""); template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, "");
}
template.Id = currentItem.Id; template.Id = currentItem.Id;
template.Enabled = currentItem.Enabled; template.Enabled = currentItem.Enabled;
@@ -2300,16 +2281,10 @@ public static class ConfigHandler
var downloadHandle = new DownloadService(); var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, ""); var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (templateContent.IsNullOrEmpty()) if (templateContent.IsNullOrEmpty())
{
return null; return null;
}
var template = JsonUtils.Deserialize<SimpleDNSItem>(templateContent); var template = JsonUtils.Deserialize<SimpleDNSItem>(templateContent);
if (template == null) if (template == null)
{
return null; return null;
}
return template; return template;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -91,7 +91,7 @@ public static class SysProxyHandler
private static async Task SetWindowsProxyPac(int port) private static async Task SetWindowsProxyPac(int port)
{ {
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac); var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
await PacManager.Instance.StartAsync(port, portPac); await PacManager.Instance.StartAsync(Utils.GetConfigPath(), port, portPac);
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}"; var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
ProxySettingWindows.SetProxy(strProxy, "", 4); ProxySettingWindows.SetProxy(strProxy, "", 4);
} }

View File

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

View File

@@ -81,36 +81,21 @@ public class ActionPrecheckManager(Config config)
{ {
case EConfigType.VMess: case EConfigType.VMess:
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id")); errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
break; break;
case EConfigType.VLESS: case EConfigType.VLESS:
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30)) if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id) && item.Id.Length > 30)
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id")); errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (!Global.Flows.Contains(item.Flow)) if (!Global.Flows.Contains(item.Flow))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow")); errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
if (item.Id.IsNullOrEmpty()) if (item.Id.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id")); errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security)) if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Security")); errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
}
break; break;
} }
@@ -130,7 +115,7 @@ public class ActionPrecheckManager(Config config)
if (item.ConfigType.IsGroupType()) if (item.ConfigType.IsGroupType())
{ {
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group); ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild()) if (group is null || group.ChildItems.IsNullOrEmpty())
{ {
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks)); errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors; return errors;
@@ -143,11 +128,7 @@ public class ActionPrecheckManager(Config config)
return errors; return errors;
} }
var childIds = Utils.String2List(group.ChildItems) ?? []; foreach (var child in 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>(); var childErrors = new List<string>();
if (child.IsNullOrEmpty()) if (child.IsNullOrEmpty())

View File

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

View File

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

View File

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

View File

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

View File

@@ -173,19 +173,13 @@ public class ProfileGroupItemManager
public static bool HasCycle(string? indexId, HashSet<string> visited, HashSet<string> stack) public static bool HasCycle(string? indexId, HashSet<string> visited, HashSet<string> stack)
{ {
if (indexId.IsNullOrEmpty()) if (indexId.IsNullOrEmpty())
{
return false; return false;
}
if (stack.Contains(indexId)) if (stack.Contains(indexId))
{
return true; return true;
}
if (visited.Contains(indexId)) if (visited.Contains(indexId))
{
return false; return false;
}
visited.Add(indexId); visited.Add(indexId);
stack.Add(indexId); stack.Add(indexId);
@@ -226,14 +220,11 @@ public class ProfileGroupItemManager
public static async Task<(List<ProfileItem> Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId) public static async Task<(List<ProfileItem> Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId)
{ {
Instance.TryGet(indexId, out var profileGroupItem); Instance.TryGet(indexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.NotHasChild()) if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
{ {
return (new List<ProfileItem>(), profileGroupItem); return (new List<ProfileItem>(), profileGroupItem);
} }
var items = await GetChildProfileItems(profileGroupItem); var items = await GetChildProfileItems(profileGroupItem);
var subItems = await GetSubChildProfileItems(profileGroupItem);
items.AddRange(subItems);
return (items, profileGroupItem); return (items, profileGroupItem);
} }
@@ -257,47 +248,20 @@ public class ProfileGroupItemManager
return childProfiles; return childProfiles;
} }
public static async Task<List<ProfileItem>> GetSubChildProfileItems(ProfileGroupItem? group)
{
if (group == null || group.SubChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = await AppManager.Instance.ProfileItems(group.SubChildItems);
return childProfiles.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(group.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, group.Filter))
)
.ToList();
}
public static async Task<HashSet<string>> GetAllChildDomainAddresses(string indexId) public static async Task<HashSet<string>> GetAllChildDomainAddresses(string indexId)
{ {
// include grand children // include grand children
var childAddresses = new HashSet<string>(); var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null) if (!Instance.TryGet(indexId, out var groupItem) || groupItem.ChildItems.IsNullOrEmpty())
{
return childAddresses; return childAddresses;
}
if (groupItem.SubChildItems.IsNotEmpty()) var childIds = Utils.String2List(groupItem.ChildItems);
{
var subItems = await GetSubChildProfileItems(groupItem);
subItems.ForEach(p => childAddresses.Add(p.Address));
}
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
foreach (var childId in childIds) foreach (var childId in childIds)
{ {
var childNode = await AppManager.Instance.GetProfileItem(childId); var childNode = await AppManager.Instance.GetProfileItem(childId);
if (childNode == null) if (childNode == null)
{
continue; continue;
}
if (!childNode.IsComplex()) if (!childNode.IsComplex())
{ {

View File

@@ -56,9 +56,9 @@ public class TaskManager
{ {
//Logging.SaveLog("Execute delete expired files"); //Logging.SaveLog("Execute delete expired files");
FileUtils.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1)); FileManager.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
FileUtils.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1)); FileManager.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
FileUtils.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1)); FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
try try
{ {
@@ -111,10 +111,11 @@ public class TaskManager
{ {
Logging.SaveLog("Execute update geo files"); Logging.SaveLog("Execute update geo files");
await new UpdateService(_config, async (success, msg) => var updateHandle = new UpdateService();
await updateHandle.UpdateGeoFileAll(_config, async (success, msg) =>
{ {
await _updateFunc?.Invoke(false, msg); await _updateFunc?.Invoke(false, msg);
}).UpdateGeoFileAll(); });
} }
} }
} }

View File

@@ -219,8 +219,6 @@ public class SystemProxyItem
public string SystemProxyExceptions { get; set; } public string SystemProxyExceptions { get; set; }
public bool NotProxyLocalAddress { get; set; } = true; public bool NotProxyLocalAddress { get; set; } = true;
public string SystemProxyAdvancedProtocol { get; set; } public string SystemProxyAdvancedProtocol { get; set; }
public string? CustomSystemProxyPacPath { get; set; }
public string? CustomSystemProxyScriptPath { get; set; }
} }
[Serializable] [Serializable]

View File

@@ -8,14 +8,5 @@ public class ProfileGroupItem
public string ChildItems { get; set; } public string ChildItems { get; set; }
public string? SubChildItems { get; set; }
public string? Filter { get; set; }
public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing; public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing;
public bool NotHasChild()
{
return string.IsNullOrWhiteSpace(ChildItems) && string.IsNullOrWhiteSpace(SubChildItems);
}
} }

View File

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

View File

@@ -181,7 +181,6 @@ public class Tls4Sbox
public bool? fragment { get; set; } public bool? fragment { get; set; }
public string? fragment_fallback_delay { get; set; } public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; } public bool? record_fragment { get; set; }
public List<string>? certificate { get; set; }
} }
public class Multiplex4Sbox public class Multiplex4Sbox

View File

@@ -354,14 +354,6 @@ public class TlsSettings4Ray
public string? shortId { get; set; } public string? shortId { get; set; }
public string? spiderX { get; set; } public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; } public string? mldsa65Verify { get; set; }
public List<CertificateSettings4Ray>? certificates { get; set; }
public bool? disableSystemRoot { get; set; }
}
public class CertificateSettings4Ray
{
public List<string>? certificate { get; set; }
public string? usage { get; set; }
} }
public class TcpSettings4Ray public class TcpSettings4Ray

View File

@@ -38,6 +38,4 @@ public class VmessQRCode
public string alpn { get; set; } = string.Empty; public string alpn { get; set; } = string.Empty;
public string fp { get; set; } = string.Empty; public string fp { get; set; } = string.Empty;
public string insecure { get; set; } = string.Empty;
} }

View File

@@ -19,7 +19,7 @@ namespace ServiceLib.Resx {
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。 // (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ResUI { public class ResUI {
@@ -87,24 +87,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Certificate not set 的本地化字符串。
/// </summary>
public static string CertNotSet {
get {
return ResourceManager.GetString("CertNotSet", resourceCulture);
}
}
/// <summary>
/// 查找类似 Certificate set 的本地化字符串。
/// </summary>
public static string CertSet {
get {
return ResourceManager.GetString("CertSet", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Please check the Configuration settings first. 的本地化字符串。 /// 查找类似 Please check the Configuration settings first. 的本地化字符串。
/// </summary> /// </summary>
@@ -1663,7 +1645,7 @@ namespace ServiceLib.Resx {
} }
/// <summary> /// <summary>
/// 查找类似 Configuration List 的本地化字符串。 /// 查找类似 Server List 的本地化字符串。
/// </summary> /// </summary>
public static string menuServerList { public static string menuServerList {
get { get {
@@ -2319,15 +2301,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Please set a valid domain 的本地化字符串。
/// </summary>
public static string ServerNameMustBeValidDomain {
get {
return ResourceManager.GetString("ServerNameMustBeValidDomain", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。 /// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。
/// </summary> /// </summary>
@@ -2589,25 +2562,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Certificate Pinning 的本地化字符串。
/// </summary>
public static string TbCertPinning {
get {
return ResourceManager.GetString("TbCertPinning", resourceCulture);
}
}
/// <summary>
/// 查找类似 Server certificate (PEM format, optional). Entering a certificate will pin it.
///Do not use the &quot;Fetch Certificate&quot; button when &quot;Allow Insecure&quot; is enabled. 的本地化字符串。
/// </summary>
public static string TbCertPinningTips {
get {
return ResourceManager.GetString("TbCertPinningTips", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Clear system proxy 的本地化字符串。 /// 查找类似 Clear system proxy 的本地化字符串。
/// </summary> /// </summary>
@@ -2815,24 +2769,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Fetch Certificate 的本地化字符串。
/// </summary>
public static string TbFetchCert {
get {
return ResourceManager.GetString("TbFetchCert", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fetch Certificate Chain 的本地化字符串。
/// </summary>
public static string TbFetchCertChain {
get {
return ResourceManager.GetString("TbFetchCertChain", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Fingerprint 的本地化字符串。 /// 查找类似 Fingerprint 的本地化字符串。
/// </summary> /// </summary>
@@ -3022,15 +2958,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Auto add filtered configuration from subscription groups 的本地化字符串。
/// </summary>
public static string TbPolicyGroupSubChildTip {
get {
return ResourceManager.GetString("TbPolicyGroupSubChildTip", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Policy Group Type 的本地化字符串。 /// 查找类似 Policy Group Type 的本地化字符串。
/// </summary> /// </summary>
@@ -3508,24 +3435,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Custom PAC file path 的本地化字符串。
/// </summary>
public static string TbSettingsCustomSystemProxyPacPath {
get {
return ResourceManager.GetString("TbSettingsCustomSystemProxyPacPath", resourceCulture);
}
}
/// <summary>
/// 查找类似 Custom system proxy script file path 的本地化字符串。
/// </summary>
public static string TbSettingsCustomSystemProxyScriptPath {
get {
return ResourceManager.GetString("TbSettingsCustomSystemProxyScriptPath", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Allow Insecure 的本地化字符串。 /// 查找类似 Allow Insecure 的本地化字符串。
/// </summary> /// </summary>

View File

@@ -1537,7 +1537,7 @@
<value>Remove Child Configuration</value> <value>Remove Child Configuration</value>
</data> </data>
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Configuration List</value> <value>Server List</value>
</data> </data>
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Fallback</value> <value>Fallback</value>
@@ -1599,35 +1599,4 @@
<data name="menuFastRealPing" xml:space="preserve"> <data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value> <value>Test real delay</value>
</data> </data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
</root> </root>

View File

@@ -1528,13 +1528,13 @@
<value>Ajouter une chaîne de proxy</value> <value>Ajouter une chaîne de proxy</value>
</data> </data>
<data name="menuAddChildServer" xml:space="preserve"> <data name="menuAddChildServer" xml:space="preserve">
<value>Ajouter une sous-configuration</value> <value>Ajouter un enfant</value>
</data> </data>
<data name="menuRemoveChildServer" xml:space="preserve"> <data name="menuRemoveChildServer" xml:space="preserve">
<value>Supprimer une sous-configuration</value> <value>Supprimer lenfant</value>
</data> </data>
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Liste des configurations</value> <value>Liste des enfants</value>
</data> </data>
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Basculement (failover)</value> <value>Basculement (failover)</value>
@@ -1596,35 +1596,4 @@
<data name="menuFastRealPing" xml:space="preserve"> <data name="menuFastRealPing" xml:space="preserve">
<value>Test 1-clic de latence réelle</value> <value>Test 1-clic de latence réelle</value>
</data> </data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Ajout auto des configs filtrées depuis les groupes dabonnement</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Certificat serveur (PEM, optionnel). Lajout dun certificat le fixe.
Ne pas utiliser « Obtenir le certificat » si « Autoriser non sécurisé » est activé.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Obtenir le certificat</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Obtenir la chaîne de certificats</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Veuillez définir un domaine valide</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificat non configuré</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificat configuré </value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Chemin fichier PAC personnalisé</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Chemin script proxy système personnalisé</value>
</data>
</root> </root>

View File

@@ -1537,7 +1537,7 @@
<value>Remove Child Configuration</value> <value>Remove Child Configuration</value>
</data> </data>
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Configuration List</value> <value>Server List</value>
</data> </data>
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Fallback</value> <value>Fallback</value>
@@ -1599,35 +1599,4 @@
<data name="menuFastRealPing" xml:space="preserve"> <data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value> <value>Test real delay</value>
</data> </data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
</root> </root>

View File

@@ -1537,7 +1537,7 @@
<value>Remove Child Configuration</value> <value>Remove Child Configuration</value>
</data> </data>
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Configuration List</value> <value>Server List</value>
</data> </data>
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Fallback</value> <value>Fallback</value>
@@ -1599,35 +1599,4 @@
<data name="menuFastRealPing" xml:space="preserve"> <data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value> <value>Test real delay</value>
</data> </data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
</root> </root>

View File

@@ -1537,7 +1537,7 @@
<value>Remove Child Configuration</value> <value>Remove Child Configuration</value>
</data> </data>
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Configuration List</value> <value>Server List</value>
</data> </data>
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Fallback</value> <value>Fallback</value>
@@ -1599,35 +1599,4 @@
<data name="menuFastRealPing" xml:space="preserve"> <data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value> <value>Test real delay</value>
</data> </data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
</root> </root>

View File

@@ -1528,13 +1528,13 @@
<value>添加链式代理</value> <value>添加链式代理</value>
</data> </data>
<data name="menuAddChildServer" xml:space="preserve"> <data name="menuAddChildServer" xml:space="preserve">
<value>添加子配置</value> <value>添加子</value>
</data> </data>
<data name="menuRemoveChildServer" xml:space="preserve"> <data name="menuRemoveChildServer" xml:space="preserve">
<value>删除子配置</value> <value>删除子</value>
</data> </data>
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>子配置项</value> <value>子项列表</value>
</data> </data>
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>故障转移</value> <value>故障转移</value>
@@ -1596,35 +1596,4 @@
<data name="menuFastRealPing" xml:space="preserve"> <data name="menuFastRealPing" xml:space="preserve">
<value>一键测试真连接延迟</value> <value>一键测试真连接延迟</value>
</data> </data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>自动从订阅分组添加过滤后的配置</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>固定证书</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>服务器证书PEM 格式,可选)。填入后将固定该证书。
启用“跳过证书验证”时,请勿使用 '获取证书'。</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>获取证书</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>获取证书链</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>请设置有效的域名</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>证书未设置</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>证书已设置</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>自定义 PAC 文件路径</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>自定义系统代理脚本文件路径</value>
</data>
</root> </root>

View File

@@ -1528,13 +1528,13 @@
<value>添加鏈式代理</value> <value>添加鏈式代理</value>
</data> </data>
<data name="menuAddChildServer" xml:space="preserve"> <data name="menuAddChildServer" xml:space="preserve">
<value>添加子配置</value> <value>添加子</value>
</data> </data>
<data name="menuRemoveChildServer" xml:space="preserve"> <data name="menuRemoveChildServer" xml:space="preserve">
<value>刪除子配置</value> <value>刪除子</value>
</data> </data>
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>子配置項</value> <value>子項清單</value>
</data> </data>
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>容錯移轉</value> <value>容錯移轉</value>
@@ -1596,35 +1596,4 @@
<data name="menuFastRealPing" xml:space="preserve"> <data name="menuFastRealPing" xml:space="preserve">
<value>一鍵測試真連線延遲</value> <value>一鍵測試真連線延遲</value>
</data> </data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>自動從訂閱分組新增過濾後的配置</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>自訂 PAC 檔案路徑</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>自訂系統代理程式腳本檔案路徑</value>
</data>
</root> </root>

View File

@@ -202,9 +202,7 @@ public partial class CoreConfigSingboxService
var routing = await ConfigHandler.GetDefaultRouting(_config); var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null) if (routing == null)
{
return 0; return 0;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? []; var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
var expectedIPCidr = new List<string>(); var expectedIPCidr = new List<string>();

View File

@@ -204,6 +204,54 @@ public partial class CoreConfigSingboxService
return await Task.FromResult<BaseServer4Sbox?>(null); return await Task.FromResult<BaseServer4Sbox?>(null);
} }
private async Task<int> GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
{
try
{
if (!node.ConfigType.IsGroupType())
{
return -1;
}
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId);
if (hasCycle)
{
return -1;
}
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
if (childProfiles.Count <= 0)
{
return -1;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
if (ignoreOriginChain)
{
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
else
{
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
break;
case EConfigType.ProxyChain:
await GenChainOutboundsList(childProfiles, singboxConfig, baseTagName);
break;
default:
break;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
{ {
try try
@@ -232,7 +280,7 @@ public partial class CoreConfigSingboxService
{ {
try try
{ {
if (node.StreamSecurity is Global.StreamSecurityReality or Global.StreamSecurity) if (node.StreamSecurity == Global.StreamSecurityReality || node.StreamSecurity == Global.StreamSecurity)
{ {
var server_name = string.Empty; var server_name = string.Empty;
if (node.Sni.IsNotEmpty()) if (node.Sni.IsNotEmpty())
@@ -259,22 +307,7 @@ public partial class CoreConfigSingboxService
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
}; };
} }
if (node.StreamSecurity == Global.StreamSecurity) if (node.StreamSecurity == Global.StreamSecurityReality)
{
var certs = node.Cert
?.Split("-----END CERTIFICATE-----", StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.TrimEx())
.Where(s => !s.IsNullOrEmpty())
.Select(s => s + "\n-----END CERTIFICATE-----")
.Select(s => s.Replace("\r\n", "\n"))
.ToList() ?? new();
if (certs.Count > 0)
{
tls.certificate = certs;
tls.insecure = false;
}
}
else if (node.StreamSecurity == Global.StreamSecurityReality)
{ {
tls.reality = new Reality4Sbox() tls.reality = new Reality4Sbox()
{ {
@@ -371,54 +404,6 @@ public partial class CoreConfigSingboxService
return await Task.FromResult(0); return await Task.FromResult(0);
} }
private async Task<int> GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
{
try
{
if (!node.ConfigType.IsGroupType())
{
return -1;
}
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId);
if (hasCycle)
{
return -1;
}
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
if (childProfiles.Count <= 0)
{
return -1;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
if (ignoreOriginChain)
{
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
else
{
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
break;
case EConfigType.ProxyChain:
await GenChainOutboundsList(childProfiles, singboxConfig, baseTagName);
break;
default:
break;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig) private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig)
{ {
if (node.Subid.IsNullOrEmpty()) if (node.Subid.IsNullOrEmpty())
@@ -683,10 +668,7 @@ public partial class CoreConfigSingboxService
{ {
var node = nodes[i]; var node = nodes[i];
if (node == null) if (node == null)
{
continue; continue;
}
if (node.ConfigType.IsGroupType()) if (node.ConfigType.IsGroupType())
{ {
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);

View File

@@ -250,9 +250,7 @@ public partial class CoreConfigSingboxService
foreach (var it in item.Domain) foreach (var it in item.Domain)
{ {
if (ParseV2Domain(it, rule1)) if (ParseV2Domain(it, rule1))
{
countDomain++; countDomain++;
}
} }
if (countDomain > 0) if (countDomain > 0)
{ {
@@ -267,9 +265,7 @@ public partial class CoreConfigSingboxService
foreach (var it in item.Ip) foreach (var it in item.Ip)
{ {
if (ParseV2Address(it, rule2)) if (ParseV2Address(it, rule2))
{
countIp++; countIp++;
}
} }
if (countIp > 0) if (countIp > 0)
{ {

View File

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

View File

@@ -94,8 +94,8 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = ResUI.InitialConfiguration; ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{ {
ret.Msg = ResUI.FailedGetDefaultConfiguration; ret.Msg = ResUI.FailedGetDefaultConfiguration;
@@ -137,9 +137,7 @@ public partial class CoreConfigV2rayService(Config config)
foreach (var rule in rules) foreach (var rule in rules)
{ {
if (rule.outboundTag == null) if (rule.outboundTag == null)
{
continue; continue;
}
if (balancerTagSet.Contains(rule.outboundTag)) if (balancerTagSet.Contains(rule.outboundTag))
{ {
@@ -202,8 +200,8 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = ResUI.InitialConfiguration; ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{ {
ret.Msg = ResUI.FailedGetDefaultConfiguration; ret.Msg = ResUI.FailedGetDefaultConfiguration;

View File

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

View File

@@ -87,7 +87,7 @@ public partial class CoreConfigV2rayService
} }
var customOutboundsNode = new JsonArray(); var customOutboundsNode = new JsonArray();
foreach (var outbound in v2rayConfig.outbounds) foreach (var outbound in v2rayConfig.outbounds)
{ {
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom") if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
@@ -112,7 +112,7 @@ public partial class CoreConfigV2rayService
} }
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound)); customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
} }
if (fullConfigTemplateNode["outbounds"] is JsonArray templateOutbounds) if (fullConfigTemplateNode["outbounds"] is JsonArray templateOutbounds)
{ {
foreach (var outbound in templateOutbounds) foreach (var outbound in templateOutbounds)
@@ -120,7 +120,7 @@ public partial class CoreConfigV2rayService
customOutboundsNode.Add(outbound?.DeepClone()); customOutboundsNode.Add(outbound?.DeepClone());
} }
} }
fullConfigTemplateNode["outbounds"] = customOutboundsNode; fullConfigTemplateNode["outbounds"] = customOutboundsNode;
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode)); return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));

View File

@@ -189,10 +189,7 @@ public partial class CoreConfigV2rayService
foreach (var domain in item.Domain) foreach (var domain in item.Domain)
{ {
if (domain.StartsWith('#')) if (domain.StartsWith('#'))
{
continue; continue;
}
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ","); var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
if (item.OutboundTag == Global.DirectTag) if (item.OutboundTag == Global.DirectTag)
@@ -350,8 +347,8 @@ public partial class CoreConfigV2rayService
if (obj is null) if (obj is null)
{ {
List<string> servers = []; List<string> servers = [];
var arrDNS = normalDNS.Split(','); string[] arrDNS = normalDNS.Split(',');
foreach (var str in arrDNS) foreach (string str in arrDNS)
{ {
servers.Add(str); servers.Add(str);
} }
@@ -371,10 +368,7 @@ public partial class CoreConfigV2rayService
foreach (var host in systemHosts) foreach (var host in systemHosts)
{ {
if (normalHost1[host.Key] != null) if (normalHost1[host.Key] != null)
{
continue; continue;
}
normalHost1[host.Key] = host.Value; normalHost1[host.Key] = host.Value;
} }
} }

View File

@@ -48,7 +48,7 @@ public partial class CoreConfigV2rayService
private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
{ {
var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound); string result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
if (result.IsNullOrEmpty()) if (result.IsNullOrEmpty())
{ {
return new(); return new();

View File

@@ -245,13 +245,6 @@ public partial class CoreConfigV2rayService
var host = node.RequestHost.TrimEx(); var host = node.RequestHost.TrimEx();
var path = node.Path.TrimEx(); var path = node.Path.TrimEx();
var sni = node.Sni.TrimEx(); var sni = node.Sni.TrimEx();
var certs = node.Cert
?.Split("-----END CERTIFICATE-----", StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.TrimEx())
.Where(s => !s.IsNullOrEmpty())
.Select(s => s + "\n-----END CERTIFICATE-----")
.Select(s => s.Replace("\r\n", "\n"))
.ToList() ?? new();
var useragent = ""; var useragent = "";
if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty()) if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty())
{ {
@@ -284,22 +277,6 @@ public partial class CoreConfigV2rayService
{ {
tlsSettings.serverName = Utils.String2List(host)?.First(); tlsSettings.serverName = Utils.String2List(host)?.First();
} }
if (certs.Count > 0)
{
var certsettings = new List<CertificateSettings4Ray>();
foreach (var cert in certs)
{
var certPerLine = cert.Split("\n").ToList();
certsettings.Add(new CertificateSettings4Ray
{
certificate = certPerLine,
usage = "verify",
});
}
tlsSettings.certificates = certsettings;
tlsSettings.disableSystemRoot = true;
tlsSettings.allowInsecure = false;
}
streamSettings.tlsSettings = tlsSettings; streamSettings.tlsSettings = tlsSettings;
} }
@@ -476,16 +453,16 @@ public partial class CoreConfigV2rayService
}; };
//request Host //request Host
var request = EmbedUtils.GetEmbedText(Global.V2raySampleHttpRequestFileName); string request = EmbedUtils.GetEmbedText(Global.V2raySampleHttpRequestFileName);
var arrHost = host.Split(','); string[] arrHost = host.Split(',');
var host2 = string.Join(",".AppendQuotes(), arrHost); string host2 = string.Join(",".AppendQuotes(), arrHost);
request = request.Replace("$requestHost$", $"{host2.AppendQuotes()}"); request = request.Replace("$requestHost$", $"{host2.AppendQuotes()}");
request = request.Replace("$requestUserAgent$", $"{useragent.AppendQuotes()}"); request = request.Replace("$requestUserAgent$", $"{useragent.AppendQuotes()}");
//Path //Path
var pathHttp = @"/"; string pathHttp = @"/";
if (path.IsNotEmpty()) if (path.IsNotEmpty())
{ {
var arrPath = path.Split(','); string[] arrPath = path.Split(',');
pathHttp = string.Join(",".AppendQuotes(), arrPath); pathHttp = string.Join(",".AppendQuotes(), arrPath);
} }
request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}"); request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}");
@@ -646,10 +623,10 @@ public partial class CoreConfigV2rayService
// Cache for chain proxies to avoid duplicate generation // Cache for chain proxies to avoid duplicate generation
var nextProxyCache = new Dictionary<string, Outbounds4Ray?>(); var nextProxyCache = new Dictionary<string, Outbounds4Ray?>();
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
var prevIndex = 0; // Index for prev outbounds int prevIndex = 0; // Index for prev outbounds
// Process nodes // Process nodes
var index = 0; int index = 0;
foreach (var node in nodes) foreach (var node in nodes)
{ {
index++; index++;
@@ -804,10 +781,7 @@ public partial class CoreConfigV2rayService
{ {
var node = nodes[i]; var node = nodes[i];
if (node == null) if (node == null)
{
continue; continue;
}
if (node.ConfigType.IsGroupType()) if (node.ConfigType.IsGroupType())
{ {
var (childProfiles, _) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); var (childProfiles, _) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);

View File

@@ -6,7 +6,7 @@ public partial class CoreConfigV2rayService
{ {
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
{ {
var tag = EInboundProtocol.api.ToString(); string tag = EInboundProtocol.api.ToString();
Metrics4Ray apiObj = new(); Metrics4Ray apiObj = new();
Policy4Ray policyObj = new(); Policy4Ray policyObj = new();
SystemPolicy4Ray policySystemSetting = new(); SystemPolicy4Ray policySystemSetting = new();

View File

@@ -71,7 +71,7 @@ public class DownloadService
AllowAutoRedirect = false, AllowAutoRedirect = false,
Proxy = await GetWebProxy(blProxy) Proxy = await GetWebProxy(blProxy)
}; };
var client = new HttpClient(webRequestHandler); HttpClient client = new(webRequestHandler);
var response = await client.GetAsync(url); var response = await client.GetAsync(url);
if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null) if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null)
@@ -156,7 +156,7 @@ public class DownloadService
} }
using var cts = new CancellationTokenSource(); using var cts = new CancellationTokenSource();
var result = await client.GetStringAsync(url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token); var result = await HttpClientHelper.Instance.GetAsync(client, url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
return result; return result;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -21,7 +21,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{ {
if (_lstExitLoop.Count > 0) if (_lstExitLoop.Count > 0)
{ {
_ = UpdateFunc("", ResUI.SpeedtestingStop); UpdateFunc("", ResUI.SpeedtestingStop);
_lstExitLoop.Clear(); _lstExitLoop.Clear();
} }
@@ -272,7 +272,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task<int> DoRealPing(ServerTestItem it) private async Task<int> DoRealPing(ServerTestItem it)
{ {
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
var responseTime = await ConnectionHandler.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10); var responseTime = await HttpClientHelper.Instance.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString()); await UpdateFunc(it.IndexId, responseTime.ToString());

View File

@@ -1,14 +1,14 @@
namespace ServiceLib.Services; namespace ServiceLib.Services;
public class UpdateService(Config config, Func<bool, string, Task> updateFunc) public class UpdateService
{ {
private readonly Config? _config = config; private Func<bool, string, Task>? _updateFunc;
private readonly Func<bool, string, Task>? _updateFunc = updateFunc;
private readonly int _timeout = 30; private readonly int _timeout = 30;
private static readonly string _tag = "UpdateService"; private static readonly string _tag = "UpdateService";
public async Task CheckUpdateGuiN(bool preRelease) public async Task CheckUpdateGuiN(Config config, Func<bool, string, Task> updateFunc, bool preRelease)
{ {
_updateFunc = updateFunc;
var url = string.Empty; var url = string.Empty;
var fileName = string.Empty; var fileName = string.Empty;
@@ -47,8 +47,9 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
} }
} }
public async Task CheckUpdateCore(ECoreType type, bool preRelease) public async Task CheckUpdateCore(ECoreType type, Config config, Func<bool, string, Task> updateFunc, bool preRelease)
{ {
_updateFunc = updateFunc;
var url = string.Empty; var url = string.Empty;
var fileName = string.Empty; var fileName = string.Empty;
@@ -100,11 +101,11 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
} }
} }
public async Task UpdateGeoFileAll() public async Task UpdateGeoFileAll(Config config, Func<bool, string, Task> updateFunc)
{ {
await UpdateGeoFiles(); await UpdateGeoFiles(config, updateFunc);
await UpdateOtherFiles(); await UpdateOtherFiles(config, updateFunc);
await UpdateSrsFileAll(); await UpdateSrsFileAll(config, updateFunc);
await UpdateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo")); await UpdateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
} }
@@ -166,7 +167,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
try try
{ {
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
var filePath = string.Empty; string filePath = string.Empty;
foreach (var name in coreInfo.CoreExes) foreach (var name in coreInfo.CoreExes)
{ {
var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString()); var vName = Utils.GetBinPath(Utils.GetExeName(name), coreInfo.CoreType.ToString());
@@ -179,14 +180,14 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
var msg = string.Format(ResUI.NotFoundCore, @"", "", ""); string msg = string.Format(ResUI.NotFoundCore, @"", "", "");
//ShowMsg(true, msg); //ShowMsg(true, msg);
return new SemanticVersion(""); return new SemanticVersion("");
} }
var result = await Utils.GetCliWrapOutput(filePath, coreInfo.VersionArg); var result = await Utils.GetCliWrapOutput(filePath, coreInfo.VersionArg);
var echo = result ?? ""; var echo = result ?? "";
var version = string.Empty; string version = string.Empty;
switch (type) switch (type)
{ {
case ECoreType.v2fly: case ECoreType.v2fly:
@@ -329,11 +330,13 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
#region Geo private #region Geo private
private async Task UpdateGeoFiles() private async Task UpdateGeoFiles(Config config, Func<bool, string, Task> updateFunc)
{ {
var geoUrl = string.IsNullOrEmpty(_config?.ConstItem.GeoSourceUrl) _updateFunc = updateFunc;
var geoUrl = string.IsNullOrEmpty(config?.ConstItem.GeoSourceUrl)
? Global.GeoUrl ? Global.GeoUrl
: _config.ConstItem.GeoSourceUrl; : config.ConstItem.GeoSourceUrl;
List<string> files = ["geosite", "geoip"]; List<string> files = ["geosite", "geoip"];
foreach (var geoName in files) foreach (var geoName in files)
@@ -342,29 +345,33 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
var targetPath = Utils.GetBinPath($"{fileName}"); var targetPath = Utils.GetBinPath($"{fileName}");
var url = string.Format(geoUrl, geoName); var url = string.Format(geoUrl, geoName);
await DownloadGeoFile(url, fileName, targetPath); await DownloadGeoFile(url, fileName, targetPath, updateFunc);
} }
} }
private async Task UpdateOtherFiles() private async Task UpdateOtherFiles(Config config, Func<bool, string, Task> updateFunc)
{ {
//If it is not in China area, no update is required //If it is not in China area, no update is required
if (_config.ConstItem.GeoSourceUrl.IsNotEmpty()) if (config.ConstItem.GeoSourceUrl.IsNotEmpty())
{ {
return; return;
} }
_updateFunc = updateFunc;
foreach (var url in Global.OtherGeoUrls) foreach (var url in Global.OtherGeoUrls)
{ {
var fileName = Path.GetFileName(url); var fileName = Path.GetFileName(url);
var targetPath = Utils.GetBinPath($"{fileName}"); var targetPath = Utils.GetBinPath($"{fileName}");
await DownloadGeoFile(url, fileName, targetPath); await DownloadGeoFile(url, fileName, targetPath, updateFunc);
} }
} }
private async Task UpdateSrsFileAll() private async Task UpdateSrsFileAll(Config config, Func<bool, string, Task> updateFunc)
{ {
_updateFunc = updateFunc;
var geoipFiles = new List<string>(); var geoipFiles = new List<string>();
var geoSiteFiles = new List<string>(); var geoSiteFiles = new List<string>();
@@ -407,29 +414,29 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
} }
foreach (var item in geoipFiles.Distinct()) foreach (var item in geoipFiles.Distinct())
{ {
await UpdateSrsFile("geoip", item); await UpdateSrsFile("geoip", item, config, updateFunc);
} }
foreach (var item in geoSiteFiles.Distinct()) foreach (var item in geoSiteFiles.Distinct())
{ {
await UpdateSrsFile("geosite", item); await UpdateSrsFile("geosite", item, config, updateFunc);
} }
} }
private async Task UpdateSrsFile(string type, string srsName) private async Task UpdateSrsFile(string type, string srsName, Config config, Func<bool, string, Task> updateFunc)
{ {
var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl) var srsUrl = string.IsNullOrEmpty(config.ConstItem.SrsSourceUrl)
? Global.SingboxRulesetUrl ? Global.SingboxRulesetUrl
: _config.ConstItem.SrsSourceUrl; : config.ConstItem.SrsSourceUrl;
var fileName = $"{type}-{srsName}.srs"; var fileName = $"{type}-{srsName}.srs";
var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName); var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName);
var url = string.Format(srsUrl, type, $"{type}-{srsName}", srsName); var url = string.Format(srsUrl, type, $"{type}-{srsName}", srsName);
await DownloadGeoFile(url, fileName, targetPath); await DownloadGeoFile(url, fileName, targetPath, updateFunc);
} }
private async Task DownloadGeoFile(string url, string fileName, string targetPath) private async Task DownloadGeoFile(string url, string fileName, string targetPath, Func<bool, string, Task> updateFunc)
{ {
var tmpFileName = Utils.GetTempPath(Utils.GetGuid()); var tmpFileName = Utils.GetTempPath(Utils.GetGuid());

View File

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

View File

@@ -17,14 +17,6 @@ public class AddGroupServerViewModel : MyReactiveObject
[Reactive] [Reactive]
public string? PolicyGroupType { get; set; } public string? PolicyGroupType { get; set; }
[Reactive]
public SubItem? SelectedSubItem { get; set; }
[Reactive]
public string? Filter { get; set; }
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>(); public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
//public ReactiveCommand<Unit, Unit> AddCmd { get; } //public ReactiveCommand<Unit, Unit> AddCmd { get; }
@@ -72,14 +64,10 @@ public class AddGroupServerViewModel : MyReactiveObject
}); });
SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem); SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem);
CoreType = (SelectedSource?.CoreType ?? ECoreType.Xray).ToString(); CoreType = (SelectedSource?.CoreType ?? ECoreType.Xray).ToString();
_ = Init(); ProfileGroupItemManager.Instance.TryGet(profileItem.IndexId, out var profileGroup);
}
public async Task Init()
{
ProfileGroupItemManager.Instance.TryGet(SelectedSource.IndexId, out var profileGroup);
PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
{ {
EMultipleLoad.LeastPing => ResUI.TbLeastPing, EMultipleLoad.LeastPing => ResUI.TbLeastPing,
@@ -90,16 +78,15 @@ public class AddGroupServerViewModel : MyReactiveObject
_ => ResUI.TbLeastPing, _ => ResUI.TbLeastPing,
}; };
var subs = await AppManager.Instance.SubItems(); _ = Init();
subs.Add(new SubItem()); }
SubItems.AddRange(subs);
SelectedSubItem = SubItems.Where(s => s.Id == profileGroup?.SubChildItems).FirstOrDefault();
Filter = profileGroup?.Filter;
public async Task Init()
{
var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId); var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId);
if (childItemMulti != null) if (childItemMulti != null)
{ {
var childIndexIds = Utils.String2List(childItemMulti.ChildItems) ?? []; var childIndexIds = childItemMulti.ChildItems.IsNullOrEmpty() ? new List<string>() : Utils.String2List(childItemMulti.ChildItems);
foreach (var item in childIndexIds) foreach (var item in childIndexIds)
{ {
var child = await AppManager.Instance.GetProfileItem(item); var child = await AppManager.Instance.GetProfileItem(item);
@@ -194,7 +181,7 @@ public class AddGroupServerViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks); NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
return; return;
} }
if (ChildItemsObs.Count == 0 && SelectedSubItem?.Id.IsNullOrEmpty() == true) if (ChildItemsObs.Count == 0)
{ {
NoticeManager.Instance.Enqueue(ResUI.PleaseAddAtLeastOneServer); NoticeManager.Instance.Enqueue(ResUI.PleaseAddAtLeastOneServer);
return; return;
@@ -226,9 +213,6 @@ public class AddGroupServerViewModel : MyReactiveObject
_ => EMultipleLoad.LeastPing, _ => EMultipleLoad.LeastPing,
}; };
profileGroup.SubChildItems = SelectedSubItem?.Id;
profileGroup.Filter = Filter;
var hasCycle = ProfileGroupItemManager.HasCycle(profileGroup.IndexId); var hasCycle = ProfileGroupItemManager.HasCycle(profileGroup.IndexId);
if (hasCycle) if (hasCycle)
{ {

View File

@@ -2,22 +2,12 @@ namespace ServiceLib.ViewModels;
public class AddServerViewModel : MyReactiveObject public class AddServerViewModel : MyReactiveObject
{ {
private string _certError = string.Empty;
[Reactive] [Reactive]
public ProfileItem SelectedSource { get; set; } public ProfileItem SelectedSource { get; set; }
[Reactive] [Reactive]
public string? CoreType { get; set; } public string? CoreType { get; set; }
[Reactive]
public string Cert { get; set; }
[Reactive]
public string CertTip { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; } public ReactiveCommand<Unit, Unit> SaveCmd { get; }
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView) public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
@@ -25,22 +15,11 @@ public class AddServerViewModel : MyReactiveObject
_config = AppManager.Instance.Config; _config = AppManager.Instance.Config;
_updateView = updateView; _updateView = updateView;
FetchCertCmd = ReactiveCommand.CreateFromTask(async () =>
{
await FetchCert();
});
FetchCertChainCmd = ReactiveCommand.CreateFromTask(async () =>
{
await FetchCertChain();
});
SaveCmd = ReactiveCommand.CreateFromTask(async () => SaveCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await SaveServerAsync(); await SaveServerAsync();
}); });
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertTip());
if (profileItem.IndexId.IsNullOrEmpty()) if (profileItem.IndexId.IsNullOrEmpty())
{ {
profileItem.Network = Global.DefaultNetwork; profileItem.Network = Global.DefaultNetwork;
@@ -54,7 +33,6 @@ public class AddServerViewModel : MyReactiveObject
SelectedSource = JsonUtils.DeepCopy(profileItem); SelectedSource = JsonUtils.DeepCopy(profileItem);
} }
CoreType = SelectedSource?.CoreType?.ToString(); CoreType = SelectedSource?.CoreType?.ToString();
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty;
} }
private async Task SaveServerAsync() private async Task SaveServerAsync()
@@ -99,7 +77,6 @@ public class AddServerViewModel : MyReactiveObject
} }
} }
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
SelectedSource.Cert = Cert.IsNullOrEmpty() ? null : Cert;
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
{ {
@@ -111,74 +88,4 @@ public class AddServerViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
} }
} }
private void UpdateCertTip()
{
CertTip = _certError.IsNullOrEmpty()
? (Cert.IsNullOrEmpty() ? ResUI.CertNotSet : ResUI.CertSet)
: _certError;
}
private async Task FetchCert()
{
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
{
return;
}
var domain = SelectedSource.Address;
var serverName = SelectedSource.Sni;
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.RequestHost;
}
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
_certError = ResUI.ServerNameMustBeValidDomain;
UpdateCertTip();
_certError = string.Empty;
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
}
(Cert, _certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
UpdateCertTip();
}
private async Task FetchCertChain()
{
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
{
return;
}
var domain = SelectedSource.Address;
var serverName = SelectedSource.Sni;
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.RequestHost;
}
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
_certError = ResUI.ServerNameMustBeValidDomain;
UpdateCertTip();
_certError = string.Empty;
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
}
(var certs, _certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
UpdateCertTip();
Cert = string.Join("\n", certs);
}
} }

View File

@@ -119,7 +119,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
return; return;
} }
//check //check
var lstFiles = FileUtils.GetFilesFromZip(fileName); var lstFiles = FileManager.GetFilesFromZip(fileName);
if (lstFiles is null || !lstFiles.Any(t => t.Contains(_guiConfigs))) if (lstFiles is null || !lstFiles.Any(t => t.Contains(_guiConfigs)))
{ {
DisplayOperationMsg(ResUI.LocalRestoreInvalidZipTips); DisplayOperationMsg(ResUI.LocalRestoreInvalidZipTips);
@@ -135,7 +135,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
await SQLiteHelper.Instance.DisposeDbConnectionAsync(); await SQLiteHelper.Instance.DisposeDbConnectionAsync();
var toPath = Utils.GetConfigPath(); var toPath = Utils.GetConfigPath();
FileUtils.ZipExtractToFile(fileName, toPath, ""); FileManager.ZipExtractToFile(fileName, toPath, "");
if (Utils.IsWindows()) if (Utils.IsWindows())
{ {
@@ -167,8 +167,8 @@ public class BackupAndRestoreViewModel : MyReactiveObject
var configDirZipTemp = Utils.GetTempPath($"v2rayN_{DateTime.Now:yyyyMMddHHmmss}"); var configDirZipTemp = Utils.GetTempPath($"v2rayN_{DateTime.Now:yyyyMMddHHmmss}");
var configDirTemp = Path.Combine(configDirZipTemp, _guiConfigs); var configDirTemp = Path.Combine(configDirZipTemp, _guiConfigs);
FileUtils.CopyDirectory(configDir, configDirTemp, false, true, ""); FileManager.CopyDirectory(configDir, configDirTemp, false, true, "");
var ret = FileUtils.CreateFromDirectory(configDirZipTemp, fileName); var ret = FileManager.CreateFromDirectory(configDirZipTemp, fileName);
Directory.Delete(configDirZipTemp, true); Directory.Delete(configDirZipTemp, true);
return await Task.FromResult(ret); return await Task.FromResult(ret);
} }

View File

@@ -148,7 +148,7 @@ public class CheckUpdateViewModel : MyReactiveObject
UpdatedPlusPlus(_geo, ""); UpdatedPlusPlus(_geo, "");
} }
} }
await new UpdateService(_config, _updateUI).UpdateGeoFileAll() await new UpdateService().UpdateGeoFileAll(_config, _updateUI)
.ContinueWith(t => UpdatedPlusPlus(_geo, "")); .ContinueWith(t => UpdatedPlusPlus(_geo, ""));
} }
@@ -163,7 +163,7 @@ public class CheckUpdateViewModel : MyReactiveObject
UpdatedPlusPlus(_v2rayN, msg); UpdatedPlusPlus(_v2rayN, msg);
} }
} }
await new UpdateService(_config, _updateUI).CheckUpdateGuiN(preRelease) await new UpdateService().CheckUpdateGuiN(_config, _updateUI, preRelease)
.ContinueWith(t => UpdatedPlusPlus(_v2rayN, "")); .ContinueWith(t => UpdatedPlusPlus(_v2rayN, ""));
} }
@@ -180,7 +180,7 @@ public class CheckUpdateViewModel : MyReactiveObject
} }
} }
var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType); var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType);
await new UpdateService(_config, _updateUI).CheckUpdateCore(type, preRelease) await new UpdateService().CheckUpdateCore(type, _config, _updateUI, preRelease)
.ContinueWith(t => UpdatedPlusPlus(model.CoreType, "")); .ContinueWith(t => UpdatedPlusPlus(model.CoreType, ""));
} }
@@ -209,7 +209,6 @@ public class CheckUpdateViewModel : MyReactiveObject
_ = UpdateFinishedResult(blReload); _ = UpdateFinishedResult(blReload);
return Disposable.Empty; return Disposable.Empty;
}); });
await Task.CompletedTask;
} }
public async Task UpdateFinishedResult(bool blReload) public async Task UpdateFinishedResult(bool blReload)
@@ -271,24 +270,24 @@ public class CheckUpdateViewModel : MyReactiveObject
if (fileName.Contains(".tar.gz")) if (fileName.Contains(".tar.gz"))
{ {
FileUtils.DecompressTarFile(fileName, toPath); FileManager.DecompressTarFile(fileName, toPath);
var dir = new DirectoryInfo(toPath); var dir = new DirectoryInfo(toPath);
if (dir.Exists) if (dir.Exists)
{ {
foreach (var subDir in dir.GetDirectories()) foreach (var subDir in dir.GetDirectories())
{ {
FileUtils.CopyDirectory(subDir.FullName, toPath, false, true); FileManager.CopyDirectory(subDir.FullName, toPath, false, true);
subDir.Delete(true); subDir.Delete(true);
} }
} }
} }
else if (fileName.Contains(".gz")) else if (fileName.Contains(".gz"))
{ {
FileUtils.DecompressFile(fileName, toPath, item.CoreType); FileManager.DecompressFile(fileName, toPath, item.CoreType);
} }
else else
{ {
FileUtils.ZipExtractToFile(fileName, toPath, "geo"); FileManager.ZipExtractToFile(fileName, toPath, "geo");
} }
if (Utils.IsNonWindows()) if (Utils.IsNonWindows())
@@ -322,7 +321,6 @@ public class CheckUpdateViewModel : MyReactiveObject
_ = UpdateViewResult(model); _ = UpdateViewResult(model);
return Disposable.Empty; return Disposable.Empty;
}); });
await Task.CompletedTask;
} }
public async Task UpdateViewResult(CheckUpdateModel model) public async Task UpdateViewResult(CheckUpdateModel model)
@@ -333,6 +331,5 @@ public class CheckUpdateViewModel : MyReactiveObject
return; return;
} }
found.Remarks = model.Remarks; found.Remarks = model.Remarks;
await Task.CompletedTask;
} }
} }

View File

@@ -96,7 +96,6 @@ public class ClashConnectionsViewModel : MyReactiveObject
} }
ConnectionItems.AddRange(lstModel); ConnectionItems.AddRange(lstModel);
await Task.CompletedTask;
} }
public async Task ClashConnectionClose(bool all) public async Task ClashConnectionClose(bool all)

View File

@@ -211,7 +211,7 @@ public class ClashProxiesViewModel : MyReactiveObject
} }
//from api //from api
foreach (var kv in _proxies) foreach (KeyValuePair<string, ProxiesItem> kv in _proxies)
{ {
if (!Global.allowSelectType.Contains(kv.Value.type.ToLower())) if (!Global.allowSelectType.Contains(kv.Value.type.ToLower()))
{ {
@@ -245,7 +245,6 @@ public class ClashProxiesViewModel : MyReactiveObject
{ {
SelectedGroup = new(); SelectedGroup = new();
} }
await Task.CompletedTask;
} }
private void RefreshProxyDetails(bool c) private void RefreshProxyDetails(bool c)
@@ -320,7 +319,7 @@ public class ClashProxiesViewModel : MyReactiveObject
//from providers //from providers
if (_providers != null) if (_providers != null)
{ {
foreach (var kv in _providers) foreach (KeyValuePair<string, ProvidersItem> kv in _providers)
{ {
if (Global.proxyVehicleType.Contains(kv.Value.vehicleType.ToLower())) if (Global.proxyVehicleType.Contains(kv.Value.vehicleType.ToLower()))
{ {
@@ -392,7 +391,6 @@ public class ClashProxiesViewModel : MyReactiveObject
_ = ProxiesDelayTestResult(model); _ = ProxiesDelayTestResult(model);
return Disposable.Empty; return Disposable.Empty;
}); });
await Task.CompletedTask;
}); });
await Task.CompletedTask; await Task.CompletedTask;
} }
@@ -421,7 +419,6 @@ public class ClashProxiesViewModel : MyReactiveObject
detail.Delay = _delayTimeout; detail.Delay = _delayTimeout;
detail.DelayName = string.Empty; detail.DelayName = string.Empty;
} }
await Task.CompletedTask;
} }
#endregion proxy function #endregion proxy function

View File

@@ -66,14 +66,10 @@ public class FullConfigTemplateViewModel : MyReactiveObject
private async Task SaveSettingAsync() private async Task SaveSettingAsync()
{ {
if (!await SaveXrayConfigAsync()) if (!await SaveXrayConfigAsync())
{
return; return;
}
if (!await SaveSingboxConfigAsync()) if (!await SaveSingboxConfigAsync())
{
return; return;
}
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_ = _updateView?.Invoke(EViewAction.CloseWindow, null); _ = _updateView?.Invoke(EViewAction.CloseWindow, null);

View File

@@ -78,55 +78,55 @@ public class MainWindowViewModel : MyReactiveObject
//servers //servers
AddVmessServerCmd = ReactiveCommand.CreateFromTask(async () => AddVmessServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.VMess); await AddServerAsync(true, EConfigType.VMess);
}); });
AddVlessServerCmd = ReactiveCommand.CreateFromTask(async () => AddVlessServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.VLESS); await AddServerAsync(true, EConfigType.VLESS);
}); });
AddShadowsocksServerCmd = ReactiveCommand.CreateFromTask(async () => AddShadowsocksServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.Shadowsocks); await AddServerAsync(true, EConfigType.Shadowsocks);
}); });
AddSocksServerCmd = ReactiveCommand.CreateFromTask(async () => AddSocksServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.SOCKS); await AddServerAsync(true, EConfigType.SOCKS);
}); });
AddHttpServerCmd = ReactiveCommand.CreateFromTask(async () => AddHttpServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.HTTP); await AddServerAsync(true, EConfigType.HTTP);
}); });
AddTrojanServerCmd = ReactiveCommand.CreateFromTask(async () => AddTrojanServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.Trojan); await AddServerAsync(true, EConfigType.Trojan);
}); });
AddHysteria2ServerCmd = ReactiveCommand.CreateFromTask(async () => AddHysteria2ServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.Hysteria2); await AddServerAsync(true, EConfigType.Hysteria2);
}); });
AddTuicServerCmd = ReactiveCommand.CreateFromTask(async () => AddTuicServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.TUIC); await AddServerAsync(true, EConfigType.TUIC);
}); });
AddWireguardServerCmd = ReactiveCommand.CreateFromTask(async () => AddWireguardServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.WireGuard); await AddServerAsync(true, EConfigType.WireGuard);
}); });
AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () => AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.Anytls); await AddServerAsync(true, EConfigType.Anytls);
}); });
AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.Custom); await AddServerAsync(true, EConfigType.Custom);
}); });
AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () => AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.PolicyGroup); await AddServerAsync(true, EConfigType.PolicyGroup);
}); });
AddProxyChainServerCmd = ReactiveCommand.CreateFromTask(async () => AddProxyChainServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.ProxyChain); await AddServerAsync(true, EConfigType.ProxyChain);
}); });
AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () => AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
@@ -268,7 +268,7 @@ public class MainWindowViewModel : MyReactiveObject
} }
await RefreshServers(); await RefreshServers();
SetReloadEnabled(true); BlReloadEnabled = true;
await Reload(); await Reload();
} }
@@ -283,7 +283,6 @@ public class MainWindowViewModel : MyReactiveObject
{ {
NoticeManager.Instance.Enqueue(msg); NoticeManager.Instance.Enqueue(msg);
} }
await Task.CompletedTask;
} }
private async Task UpdateTaskHandler(bool success, string msg) private async Task UpdateTaskHandler(bool success, string msg)
@@ -311,7 +310,6 @@ public class MainWindowViewModel : MyReactiveObject
return; return;
} }
AppEvents.DispatcherStatisticsRequested.Publish(update); AppEvents.DispatcherStatisticsRequested.Publish(update);
await Task.CompletedTask;
} }
#endregion Actions #endregion Actions
@@ -334,7 +332,7 @@ public class MainWindowViewModel : MyReactiveObject
#region Add Servers #region Add Servers
public async Task AddServerAsync(EConfigType eConfigType) public async Task AddServerAsync(bool blNew, EConfigType eConfigType)
{ {
ProfileItem item = new() ProfileItem item = new()
{ {
@@ -534,7 +532,7 @@ public class MainWindowViewModel : MyReactiveObject
return; return;
} }
SetReloadEnabled(false); BlReloadEnabled = false;
var msgs = await ActionPrecheckManager.Instance.Check(_config.IndexId); var msgs = await ActionPrecheckManager.Instance.Check(_config.IndexId);
if (msgs.Count > 0) if (msgs.Count > 0)
@@ -544,7 +542,7 @@ public class MainWindowViewModel : MyReactiveObject
NoticeManager.Instance.SendMessage(msg); NoticeManager.Instance.SendMessage(msg);
} }
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true)); NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
SetReloadEnabled(true); BlReloadEnabled = true;
return; return;
} }
@@ -562,8 +560,9 @@ public class MainWindowViewModel : MyReactiveObject
AppEvents.ProxiesReloadRequested.Publish(); AppEvents.ProxiesReloadRequested.Publish();
} }
ReloadResult(showClashUI); RxApp.MainThreadScheduler.Schedule(() => ReloadResult(showClashUI));
SetReloadEnabled(true);
BlReloadEnabled = true;
if (_hasNextReloadJob) if (_hasNextReloadJob)
{ {
_hasNextReloadJob = false; _hasNextReloadJob = false;
@@ -573,16 +572,9 @@ public class MainWindowViewModel : MyReactiveObject
private void ReloadResult(bool showClashUI) private void ReloadResult(bool showClashUI)
{ {
RxApp.MainThreadScheduler.Schedule(() => // BlReloadEnabled = true;
{ ShowClashUI = showClashUI;
ShowClashUI = showClashUI; TabMainSelectedIndex = showClashUI ? TabMainSelectedIndex : 0;
TabMainSelectedIndex = showClashUI ? TabMainSelectedIndex : 0;
});
}
private void SetReloadEnabled(bool enabled)
{
RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled);
} }
private async Task LoadCore() private async Task LoadCore()
@@ -602,7 +594,7 @@ public class MainWindowViewModel : MyReactiveObject
AppEvents.RoutingsMenuRefreshRequested.Publish(); AppEvents.RoutingsMenuRefreshRequested.Publish();
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
await new UpdateService(_config, UpdateTaskHandler).UpdateGeoFileAll(); await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler);
await Reload(); await Reload();
} }

View File

@@ -74,8 +74,6 @@ public class OptionSettingViewModel : MyReactiveObject
[Reactive] public bool notProxyLocalAddress { get; set; } [Reactive] public bool notProxyLocalAddress { get; set; }
[Reactive] public string systemProxyAdvancedProtocol { get; set; } [Reactive] public string systemProxyAdvancedProtocol { get; set; }
[Reactive] public string systemProxyExceptions { get; set; } [Reactive] public string systemProxyExceptions { get; set; }
[Reactive] public string CustomSystemProxyPacPath { get; set; }
[Reactive] public string CustomSystemProxyScriptPath { get; set; }
#endregion System proxy #endregion System proxy
@@ -193,8 +191,6 @@ public class OptionSettingViewModel : MyReactiveObject
notProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress; notProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress;
systemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol; systemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol;
systemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions; systemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions;
CustomSystemProxyPacPath = _config.SystemProxyItem.CustomSystemProxyPacPath;
CustomSystemProxyScriptPath = _config.SystemProxyItem.CustomSystemProxyScriptPath;
#endregion System proxy #endregion System proxy
@@ -277,12 +273,12 @@ public class OptionSettingViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort); NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
return; return;
} }
var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics var needReboot = (EnableStatistics != _config.GuiItem.EnableStatistics
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed || DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort || EnableDragDropSort != _config.UiItem.EnableDragDropSort
|| EnableHWA != _config.GuiItem.EnableHWA || EnableHWA != _config.GuiItem.EnableHWA
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily || CurrentFontFamily != _config.UiItem.CurrentFontFamily
|| MainGirdOrientation != (int)_config.UiItem.MainGirdOrientation; || MainGirdOrientation != (int)_config.UiItem.MainGirdOrientation);
//if (Utile.IsNullOrEmpty(Kcpmtu.ToString()) || !Utile.IsNumeric(Kcpmtu.ToString()) //if (Utile.IsNullOrEmpty(Kcpmtu.ToString()) || !Utile.IsNumeric(Kcpmtu.ToString())
// || Utile.IsNullOrEmpty(Kcptti.ToString()) || !Utile.IsNumeric(Kcptti.ToString()) // || Utile.IsNullOrEmpty(Kcptti.ToString()) || !Utile.IsNumeric(Kcptti.ToString())
@@ -351,8 +347,6 @@ public class OptionSettingViewModel : MyReactiveObject
_config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions; _config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions;
_config.SystemProxyItem.NotProxyLocalAddress = notProxyLocalAddress; _config.SystemProxyItem.NotProxyLocalAddress = notProxyLocalAddress;
_config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol; _config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol;
_config.SystemProxyItem.CustomSystemProxyPacPath = CustomSystemProxyPacPath;
_config.SystemProxyItem.CustomSystemProxyScriptPath = CustomSystemProxyScriptPath;
//tun mode //tun mode
_config.TunModeItem.AutoRoute = TunAutoRoute; _config.TunModeItem.AutoRoute = TunAutoRoute;
@@ -381,7 +375,7 @@ public class OptionSettingViewModel : MyReactiveObject
private async Task SaveCoreType() private async Task SaveCoreType()
{ {
for (var k = 1; k <= _config.CoreTypeItem.Count; k++) for (int k = 1; k <= _config.CoreTypeItem.Count; k++)
{ {
var item = _config.CoreTypeItem[k - 1]; var item = _config.CoreTypeItem[k - 1];
var type = string.Empty; var type = string.Empty;

View File

@@ -110,7 +110,7 @@ public class ProfilesViewModel : MyReactiveObject
//servers delete //servers delete
EditServerCmd = ReactiveCommand.CreateFromTask(async () => EditServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await EditServerAsync(); await EditServerAsync(EConfigType.Custom);
}, canEditRemove); }, canEditRemove);
RemoveServerCmd = ReactiveCommand.CreateFromTask(async () => RemoveServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
@@ -300,14 +300,14 @@ public class ProfilesViewModel : MyReactiveObject
if (result.Delay.IsNotEmpty()) if (result.Delay.IsNotEmpty())
{ {
item.Delay = result.Delay.ToInt(); int.TryParse(result.Delay, out var temp);
item.Delay = temp;
item.DelayVal = result.Delay ?? string.Empty; item.DelayVal = result.Delay ?? string.Empty;
} }
if (result.Speed.IsNotEmpty()) if (result.Speed.IsNotEmpty())
{ {
item.SpeedVal = result.Speed ?? string.Empty; item.SpeedVal = result.Speed ?? string.Empty;
} }
await Task.CompletedTask;
} }
public async Task UpdateStatistics(ServerSpeedItem update) public async Task UpdateStatistics(ServerSpeedItem update)
@@ -333,7 +333,6 @@ public class ProfilesViewModel : MyReactiveObject
catch catch
{ {
} }
await Task.CompletedTask;
} }
#endregion Actions #endregion Actions
@@ -488,7 +487,7 @@ public class ProfilesViewModel : MyReactiveObject
return lstSelected; return lstSelected;
} }
public async Task EditServerAsync() public async Task EditServerAsync(EConfigType eConfigType)
{ {
if (string.IsNullOrEmpty(SelectedProfile?.IndexId)) if (string.IsNullOrEmpty(SelectedProfile?.IndexId))
{ {
@@ -500,7 +499,7 @@ public class ProfilesViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
return; return;
} }
var eConfigType = item.ConfigType; eConfigType = item.ConfigType;
bool? ret = false; bool? ret = false;
if (eConfigType == EConfigType.Custom) if (eConfigType == EConfigType.Custom)
@@ -659,7 +658,7 @@ public class ProfilesViewModel : MyReactiveObject
} }
_dicHeaderSort.TryAdd(colName, true); _dicHeaderSort.TryAdd(colName, true);
_dicHeaderSort.TryGetValue(colName, out var asc); _dicHeaderSort.TryGetValue(colName, out bool asc);
if (await ConfigHandler.SortServers(_config, _config.SubIndexId, colName, asc) != 0) if (await ConfigHandler.SortServers(_config, _config.SubIndexId, colName, asc) != 0)
{ {
return; return;
@@ -754,7 +753,6 @@ public class ProfilesViewModel : MyReactiveObject
_ = SetSpeedTestResult(result); _ = SetSpeedTestResult(result);
return Disposable.Empty; return Disposable.Empty;
}); });
await Task.CompletedTask;
}); });
_speedtestService?.RunLoop(actionType, lstSelected); _speedtestService?.RunLoop(actionType, lstSelected);
} }

View File

@@ -215,7 +215,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
private async Task SaveRoutingAsync() private async Task SaveRoutingAsync()
{ {
var remarks = SelectedRouting.Remarks; string remarks = SelectedRouting.Remarks;
if (remarks.IsNullOrEmpty()) if (remarks.IsNullOrEmpty())
{ {
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks); NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
@@ -286,7 +286,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
return; return;
} }
var downloadHandle = new DownloadService(); DownloadService downloadHandle = new DownloadService();
var result = await downloadHandle.TryDownloadString(url, true, ""); var result = await downloadHandle.TryDownloadString(url, true, "");
var ret = await AddBatchRoutingRulesAsync(SelectedRouting, result); var ret = await AddBatchRoutingRulesAsync(SelectedRouting, result);
if (ret == 0) if (ret == 0)
@@ -298,7 +298,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
private async Task<int> AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData) private async Task<int> AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData)
{ {
var blReplace = false; bool blReplace = false;
if (await _updateView?.Invoke(EViewAction.AddBatchRoutingRulesYesNo, null) == false) if (await _updateView?.Invoke(EViewAction.AddBatchRoutingRulesYesNo, null) == false)
{ {
blReplace = true; blReplace = true;

View File

@@ -120,7 +120,7 @@ public class StatusBarViewModel : MyReactiveObject
this.WhenAnyValue( this.WhenAnyValue(
x => x.SelectedServer, x => x.SelectedServer,
y => y != null && !y.Text.IsNullOrEmpty()) y => y != null && !y.Text.IsNullOrEmpty())
.Subscribe(ServerSelectedChanged); .Subscribe(c => ServerSelectedChanged(c));
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
this.WhenAnyValue( this.WhenAnyValue(
@@ -313,10 +313,10 @@ public class StatusBarViewModel : MyReactiveObject
} }
BlServers = true; BlServers = true;
for (var k = 0; k < lstModel.Count; k++) for (int k = 0; k < lstModel.Count; k++)
{ {
ProfileItem it = lstModel[k]; ProfileItem it = lstModel[k];
var name = it.GetSummary(); string name = it.GetSummary();
var item = new ComboItem() { ID = it.IndexId, Text = name }; var item = new ComboItem() { ID = it.IndexId, Text = name };
Servers.Add(item); Servers.Add(item);
@@ -367,13 +367,11 @@ public class StatusBarViewModel : MyReactiveObject
_ = TestServerAvailabilityResult(msg); _ = TestServerAvailabilityResult(msg);
return Disposable.Empty; return Disposable.Empty;
}); });
await Task.CompletedTask;
} }
public async Task TestServerAvailabilityResult(string msg) public async Task TestServerAvailabilityResult(string msg)
{ {
RunningInfoDisplay = msg; RunningInfoDisplay = msg;
await Task.CompletedTask;
} }
#region System proxy and Routings #region System proxy and Routings
@@ -386,7 +384,7 @@ public class StatusBarViewModel : MyReactiveObject
} }
_config.SystemProxyItem.SysProxyType = type; _config.SystemProxyItem.SysProxyType = type;
await ChangeSystemProxyAsync(type, true); await ChangeSystemProxyAsync(type, true);
NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType}"); NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}");
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType; SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
@@ -396,10 +394,10 @@ public class StatusBarViewModel : MyReactiveObject
{ {
await SysProxyHandler.UpdateSysProxy(_config, false); await SysProxyHandler.UpdateSysProxy(_config, false);
BlSystemProxyClear = type == ESysProxyType.ForcedClear; BlSystemProxyClear = (type == ESysProxyType.ForcedClear);
BlSystemProxySet = type == ESysProxyType.ForcedChange; BlSystemProxySet = (type == ESysProxyType.ForcedChange);
BlSystemProxyNothing = type == ESysProxyType.Unchanged; BlSystemProxyNothing = (type == ESysProxyType.Unchanged);
BlSystemProxyPac = type == ESysProxyType.Pac; BlSystemProxyPac = (type == ESysProxyType.Pac);
if (blChange) if (blChange)
{ {
@@ -563,7 +561,6 @@ public class StatusBarViewModel : MyReactiveObject
catch catch
{ {
} }
await Task.CompletedTask;
} }
#endregion UI #endregion UI

View File

@@ -28,10 +28,7 @@ internal class AvaUtils
{ {
var clipboard = TopLevel.GetTopLevel(visual)?.Clipboard; var clipboard = TopLevel.GetTopLevel(visual)?.Clipboard;
if (clipboard == null) if (clipboard == null)
{
return; return;
}
await clipboard.SetTextAsync(strData); await clipboard.SetTextAsync(strData);
} }
catch catch

View File

@@ -6,7 +6,7 @@ public class DelayColorConverter : IValueConverter
{ {
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
var delay = value.ToString().ToInt(); _ = int.TryParse(value?.ToString(), out var delay);
return delay switch return delay switch
{ {

View File

@@ -17,13 +17,13 @@ global using Avalonia.Markup.Xaml;
global using Avalonia.Media; global using Avalonia.Media;
global using Avalonia.Media.Imaging; global using Avalonia.Media.Imaging;
global using Avalonia.Platform; global using Avalonia.Platform;
global using ReactiveUI.Avalonia;
global using Avalonia.Styling; global using Avalonia.Styling;
global using Avalonia.Threading; global using Avalonia.Threading;
global using ReactiveUI;
global using ReactiveUI.Fody.Helpers;
global using DynamicData; global using DynamicData;
global using MsBox.Avalonia.Enums; global using MsBox.Avalonia.Enums;
global using ReactiveUI;
global using ReactiveUI.Avalonia;
global using ReactiveUI.Fody.Helpers;
global using ServiceLib; global using ServiceLib;
global using ServiceLib.Base; global using ServiceLib.Base;
global using ServiceLib.Common; global using ServiceLib.Common;

View File

@@ -95,9 +95,7 @@ public class ThemeSettingViewModel : MyReactiveObject
{ {
double size = CurrentFontSize; double size = CurrentFontSize;
if (size < Global.MinFontSize) if (size < Global.MinFontSize)
{
return; return;
}
Style style = new(x => Selectors.Or( Style style = new(x => Selectors.Or(
x.OfType<Button>(), x.OfType<Button>(),

View File

@@ -33,106 +33,63 @@
IsCancel="True" /> IsCancel="True" />
</StackPanel> </StackPanel>
<Grid <Grid DockPanel.Dock="Top" RowDefinitions="Auto,*,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
Grid.Row="0"
ColumnDefinitions="180,Auto,Auto"
DockPanel.Dock="Top"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
Text="{x:Static resx:ResUI.menuServers}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRemarks}" />
<TextBox
x:Name="txtRemarks"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCoreType}" />
<ComboBox
x:Name="cmbCoreType"
Grid.Row="2"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<Grid <Grid
x:Name="gridPolicyGroup" Grid.Row="0"
Grid.Row="3" ColumnDefinitions="180,Auto,Auto"
Grid.Column="0" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
Grid.ColumnSpan="3"
ColumnDefinitions="180,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
Text="{x:Static resx:ResUI.menuServers}" />
<TextBlock
Grid.Row="1"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPolicyGroupType}" /> Text="{x:Static resx:ResUI.TbRemarks}" />
<TextBox
x:Name="txtRemarks"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCoreType}" />
<ComboBox <ComboBox
x:Name="cmbPolicyGroupType" x:Name="cmbCoreType"
Grid.Row="2"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
</Grid>
</Grid>
<TabControl DockPanel.Dock="Top">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid <Grid
Margin="{StaticResource Margin8}" x:Name="gridPolicyGroup"
ColumnDefinitions="180,Auto,Auto" Grid.Row="3"
RowDefinitions="Auto,Auto,Auto"> Grid.Column="0"
Grid.ColumnSpan="3"
ColumnDefinitions="180,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="0"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.menuSubscription}" /> Text="{x:Static resx:ResUI.TbPolicyGroupType}" />
<ComboBox <ComboBox
x:Name="cmbSubChildItems" x:Name="cmbPolicyGroupType"
Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}" />
DisplayMemberBinding="{Binding Remarks}"
ItemsSource="{Binding SubItems}" />
<TextBlock
Grid.Row="0"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.LvFilter}" />
<TextBox
x:Name="txtFilter"
Grid.Row="1"
Grid.Column="1"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center" />
</Grid> </Grid>
</TabItem> </Grid>
</TabControl> </Grid>
<TabControl> <TabControl>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}"> <TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<DataGrid <DataGrid

View File

@@ -13,8 +13,8 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
{ {
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded; this.Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
lstChild.SelectionChanged += LstChild_SelectionChanged; lstChild.SelectionChanged += LstChild_SelectionChanged;
ViewModel = new AddGroupServerViewModel(profileItem, UpdateViewHandler); ViewModel = new AddGroupServerViewModel(profileItem, UpdateViewHandler);
@@ -32,11 +32,11 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
switch (profileItem.ConfigType) switch (profileItem.ConfigType)
{ {
case EConfigType.PolicyGroup: case EConfigType.PolicyGroup:
Title = ResUI.TbConfigTypePolicyGroup; this.Title = ResUI.TbConfigTypePolicyGroup;
break; break;
case EConfigType.ProxyChain: case EConfigType.ProxyChain:
Title = ResUI.TbConfigTypeProxyChain; this.Title = ResUI.TbConfigTypeProxyChain;
gridPolicyGroup.IsVisible = false; gridPolicyGroup.IsVisible = false;
break; break;
} }
@@ -46,9 +46,6 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables);
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
@@ -67,7 +64,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
menuSelectAllChild.Click += (s, e) => lstChild.SelectAll(); menuSelectAllChild.Click += (s, e) => lstChild.SelectAll();
// Keyboard shortcuts when focus is within grid // Keyboard shortcuts when focus is within grid
AddHandler(KeyDownEvent, AddGroupServerWindow_KeyDown, RoutingStrategies.Tunnel); this.AddHandler(KeyDownEvent, AddGroupServerWindow_KeyDown, RoutingStrategies.Tunnel);
lstChild.LoadingRow += LstChild_LoadingRow; lstChild.LoadingRow += LstChild_LoadingRow;
} }
@@ -81,7 +78,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
} }
return await Task.FromResult(true); return await Task.FromResult(true);
@@ -95,9 +92,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
private void AddGroupServerWindow_KeyDown(object? sender, KeyEventArgs e) private void AddGroupServerWindow_KeyDown(object? sender, KeyEventArgs e)
{ {
if (!lstChild.IsKeyboardFocusWithin) if (!lstChild.IsKeyboardFocusWithin)
{
return; return;
}
if ((e.KeyModifiers & (KeyModifiers.Control | KeyModifiers.Meta)) != 0) if ((e.KeyModifiers & (KeyModifiers.Control | KeyModifiers.Meta)) != 0)
{ {

View File

@@ -14,8 +14,8 @@ public partial class AddServer2Window : WindowBase<AddServer2ViewModel>
{ {
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded; this.Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
ViewModel = new AddServer2ViewModel(profileItem, UpdateViewHandler); ViewModel = new AddServer2ViewModel(profileItem, UpdateViewHandler);
cmbCoreType.ItemsSource = Utils.GetEnumNames<ECoreType>().Where(t => t != ECoreType.v2rayN.ToString()).ToList().AppendEmpty(); cmbCoreType.ItemsSource = Utils.GetEnumNames<ECoreType>().Where(t => t != ECoreType.v2rayN.ToString()).ToList().AppendEmpty();
@@ -39,7 +39,7 @@ public partial class AddServer2Window : WindowBase<AddServer2ViewModel>
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
case EViewAction.BrowseServer: case EViewAction.BrowseServer:

View File

@@ -607,10 +607,10 @@
<Button <Button
x:Name="btnExtra" x:Name="btnExtra"
Margin="{StaticResource MarginLr8}" Classes="IconButton"
Classes="IconButton"> Margin="{StaticResource MarginLr8}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource SemiIconMore}"> <PathIcon Data="{StaticResource SemiIconMore}" >
<PathIcon.RenderTransform> <PathIcon.RenderTransform>
<RotateTransform Angle="90" /> <RotateTransform Angle="90" />
</PathIcon.RenderTransform> </PathIcon.RenderTransform>
@@ -713,7 +713,7 @@
Grid.Row="7" Grid.Row="7"
ColumnDefinitions="180,Auto" ColumnDefinitions="180,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@@ -767,67 +767,6 @@
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertPinning}" />
<StackPanel
Grid.Row="5"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
x:Name="labCertPinning"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center" />
<Button
Margin="{StaticResource MarginLr4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="IconButton">
<Button.Content>
<PathIcon Data="{StaticResource SemiIconMore}">
<PathIcon.RenderTransform>
<RotateTransform Angle="90" />
</PathIcon.RenderTransform>
</PathIcon>
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertPinningTips}" />
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Button
x:Name="btnFetchCert"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCert}" />
<Button
x:Name="btnFetchCertChain"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}" />
</StackPanel>
<TextBox
x:Name="txtCert"
Width="400"
MinHeight="100"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Classes="TextArea"
MinLines="6"
TextWrapping="Wrap" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
</Grid> </Grid>
<Grid <Grid
x:Name="gridRealityMore" x:Name="gridRealityMore"

View File

@@ -13,8 +13,8 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
{ {
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded; this.Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
cmbNetwork.SelectionChanged += CmbNetwork_SelectionChanged; cmbNetwork.SelectionChanged += CmbNetwork_SelectionChanged;
cmbStreamSecurity.SelectionChanged += CmbStreamSecurity_SelectionChanged; cmbStreamSecurity.SelectionChanged += CmbStreamSecurity_SelectionChanged;
btnGUID.Click += btnGUID_Click; btnGUID.Click += btnGUID_Click;
@@ -185,8 +185,6 @@ 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.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.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.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).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);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); 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); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);
@@ -195,12 +193,10 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
}); });
Title = $"{profileItem.ConfigType}"; this.Title = $"{profileItem.ConfigType}";
} }
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj) private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@@ -208,7 +204,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
} }
return await Task.FromResult(true); return await Task.FromResult(true);

View File

@@ -7,7 +7,7 @@ public partial class ClashProxiesView : ReactiveUserControl<ClashProxiesViewMode
InitializeComponent(); InitializeComponent();
ViewModel = new ClashProxiesViewModel(UpdateViewHandler); ViewModel = new ClashProxiesViewModel(UpdateViewHandler);
lstProxyDetails.DoubleTapped += LstProxyDetails_DoubleTapped; lstProxyDetails.DoubleTapped += LstProxyDetails_DoubleTapped;
KeyDown += ClashProxiesView_KeyDown; this.KeyDown += ClashProxiesView_KeyDown;
this.WhenActivated(disposables => this.WhenActivated(disposables =>
{ {

View File

@@ -12,7 +12,7 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
_config = AppManager.Instance.Config; _config = AppManager.Instance.Config;
Loaded += Window_Loaded; Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
ViewModel = new DNSSettingViewModel(UpdateViewHandler); ViewModel = new DNSSettingViewModel(UpdateViewHandler);
cmbRayFreedomDNSStrategy.ItemsSource = Global.DomainStrategy4Freedoms; cmbRayFreedomDNSStrategy.ItemsSource = Global.DomainStrategy4Freedoms;
@@ -77,7 +77,7 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
} }
return await Task.FromResult(true); return await Task.FromResult(true);

View File

@@ -12,7 +12,7 @@ public partial class FullConfigTemplateWindow : WindowBase<FullConfigTemplateVie
_config = AppManager.Instance.Config; _config = AppManager.Instance.Config;
Loaded += Window_Loaded; Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
ViewModel = new FullConfigTemplateViewModel(UpdateViewHandler); ViewModel = new FullConfigTemplateViewModel(UpdateViewHandler);
this.WhenActivated(disposables => this.WhenActivated(disposables =>
@@ -36,7 +36,7 @@ public partial class FullConfigTemplateWindow : WindowBase<FullConfigTemplateVie
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
} }
return await Task.FromResult(true); return await Task.FromResult(true);

View File

@@ -17,8 +17,8 @@ public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingV
HotkeyManager.Instance.IsPause = true; HotkeyManager.Instance.IsPause = true;
Loaded += Window_Loaded; Loaded += Window_Loaded;
Closing += (s, e) => HotkeyManager.Instance.IsPause = false; this.Closing += (s, e) => HotkeyManager.Instance.IsPause = false;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
this.WhenActivated(disposables => this.WhenActivated(disposables =>
{ {
@@ -34,7 +34,7 @@ public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingV
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
} }
return await Task.FromResult(true); return await Task.FromResult(true);

View File

@@ -21,9 +21,9 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
_config = AppManager.Instance.Config; _config = AppManager.Instance.Config;
_manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.TopRight }; _manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.TopRight };
KeyDown += MainWindow_KeyDown; this.KeyDown += MainWindow_KeyDown;
menuSettingsSetUWP.Click += MenuSettingsSetUWP_Click; menuSettingsSetUWP.Click += menuSettingsSetUWP_Click;
menuPromotion.Click += MenuPromotion_Click; menuPromotion.Click += menuPromotion_Click;
menuCheckUpdate.Click += MenuCheckUpdate_Click; menuCheckUpdate.Click += MenuCheckUpdate_Click;
menuBackupAndRestore.Click += MenuBackupAndRestore_Click; menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
menuClose.Click += MenuClose_Click; menuClose.Click += MenuClose_Click;
@@ -153,14 +153,14 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
if (Utils.IsWindows()) if (Utils.IsWindows())
{ {
Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}"; this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false); ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false);
HotkeyManager.Instance.Init(_config, OnHotkeyHandler); HotkeyManager.Instance.Init(_config, OnHotkeyHandler);
} }
else else
{ {
Title = $"{Utils.GetVersion()}"; this.Title = $"{Utils.GetVersion()}";
menuRebootAsAdmin.IsVisible = false; menuRebootAsAdmin.IsVisible = false;
menuSettingsSetUWP.IsVisible = false; menuSettingsSetUWP.IsVisible = false;
@@ -170,7 +170,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
if (_config.UiItem.AutoHideStartup && Utils.IsWindows()) if (_config.UiItem.AutoHideStartup && Utils.IsWindows())
{ {
WindowState = WindowState.Minimized; this.WindowState = WindowState.Minimized;
} }
AddHelpMenuItem(); AddHelpMenuItem();
@@ -188,7 +188,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
private async Task DelegateSnackMsg(string content) private async Task DelegateSnackMsg(string content)
{ {
_manager?.Show(new Notification(null, content, NotificationType.Information)); _manager?.Show(new Notification(null, content, NotificationType.Information));
await Task.CompletedTask;
} }
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj) private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@@ -197,26 +196,17 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{ {
case EViewAction.AddServerWindow: case EViewAction.AddServerWindow:
if (obj is null) if (obj is null)
{
return false; return false;
}
return await new AddServerWindow((ProfileItem)obj).ShowDialog<bool>(this); return await new AddServerWindow((ProfileItem)obj).ShowDialog<bool>(this);
case EViewAction.AddServer2Window: case EViewAction.AddServer2Window:
if (obj is null) if (obj is null)
{
return false; return false;
}
return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(this); return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(this);
case EViewAction.AddGroupServerWindow: case EViewAction.AddGroupServerWindow:
if (obj is null) if (obj is null)
{
return false; return false;
}
return await new AddGroupServerWindow((ProfileItem)obj).ShowDialog<bool>(this); return await new AddGroupServerWindow((ProfileItem)obj).ShowDialog<bool>(this);
case EViewAction.DNSSettingWindow: case EViewAction.DNSSettingWindow:
@@ -318,12 +308,12 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
} }
} }
private void MenuPromotion_Click(object? sender, RoutedEventArgs e) private void menuPromotion_Click(object? sender, RoutedEventArgs e)
{ {
ProcUtils.ProcessStart($"{Utils.Base64Decode(Global.PromotionUrl)}?t={DateTime.Now.Ticks}"); ProcUtils.ProcessStart($"{Utils.Base64Decode(Global.PromotionUrl)}?t={DateTime.Now.Ticks}");
} }
private void MenuSettingsSetUWP_Click(object? sender, RoutedEventArgs e) private void menuSettingsSetUWP_Click(object? sender, RoutedEventArgs e)
{ {
ProcUtils.ProcessStart(Utils.GetBinPath("EnableLoopback.exe")); ProcUtils.ProcessStart(Utils.GetBinPath("EnableLoopback.exe"));
} }
@@ -417,27 +407,27 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
: !_config.UiItem.ShowInTaskbar); : !_config.UiItem.ShowInTaskbar);
if (bl) if (bl)
{ {
Show(); this.Show();
if (WindowState == WindowState.Minimized) if (this.WindowState == WindowState.Minimized)
{ {
WindowState = WindowState.Normal; this.WindowState = WindowState.Normal;
} }
Activate(); this.Activate();
Focus(); this.Focus();
} }
else else
{ {
if (Utils.IsLinux() && _config.UiItem.Hide2TrayWhenClose == false) if (Utils.IsLinux() && _config.UiItem.Hide2TrayWhenClose == false)
{ {
WindowState = WindowState.Minimized; this.WindowState = WindowState.Minimized;
return; return;
} }
foreach (var ownedWindow in OwnedWindows) foreach (var ownedWindow in this.OwnedWindows)
{ {
ownedWindow.Close(); ownedWindow.Close();
} }
Hide(); this.Hide();
} }
_config.UiItem.ShowInTaskbar = bl; _config.UiItem.ShowInTaskbar = bl;
@@ -488,8 +478,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{ {
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(); var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
foreach (var it in coreInfo foreach (var it in coreInfo
.Where(t => t.CoreType is not ECoreType.v2fly .Where(t => t.CoreType != ECoreType.v2fly
and not ECoreType.hysteria)) && t.CoreType != ECoreType.hysteria))
{ {
var item = new MenuItem() var item = new MenuItem()
{ {

View File

@@ -30,9 +30,7 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
{ {
case EViewAction.DispatcherShowMsg: case EViewAction.DispatcherShowMsg:
if (obj is null) if (obj is null)
{
return false; return false;
}
Dispatcher.UIThread.Post(() => ShowMsg(obj), Dispatcher.UIThread.Post(() => ShowMsg(obj),
DispatcherPriority.ApplicationIdle); DispatcherPriority.ApplicationIdle);

View File

@@ -674,29 +674,6 @@
<TabItem Name="tabSystemproxy" Header="{x:Static resx:ResUI.TbSettingsSystemproxy}"> <TabItem Name="tabSystemproxy" Header="{x:Static resx:ResUI.TbSettingsSystemproxy}">
<DockPanel Margin="{StaticResource Margin8}"> <DockPanel Margin="{StaticResource Margin8}">
<StackPanel
Name="panSystemProxyUnix"
DockPanel.Dock="Bottom"
Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsCustomSystemProxyScriptPath}" />
<TextBox
x:Name="txtCustomSystemProxyScriptPath"
Width="600"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
TextWrapping="Wrap"
Watermark="proxy_set.sh"/>
<Button
x:Name="btnBrowseCustomSystemProxyScriptPath"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbBrowse}" />
</StackPanel>
</StackPanel>
<StackPanel <StackPanel
Name="panSystemProxyAdvanced" Name="panSystemProxyAdvanced"
DockPanel.Dock="Bottom" DockPanel.Dock="Bottom"
@@ -722,25 +699,6 @@
MinWidth="400" MinWidth="400"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsCustomSystemProxyPacPath}" />
<TextBox
x:Name="txtCustomSystemProxyPacPath"
Width="600"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
TextWrapping="Wrap"
Watermark="pac.txt"/>
<Button
x:Name="btnBrowseCustomSystemProxyPacPath"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbBrowse}" />
</StackPanel>
</StackPanel> </StackPanel>
<TextBlock <TextBlock

View File

@@ -1,5 +1,4 @@
using v2rayN.Desktop.Base; using v2rayN.Desktop.Base;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
@@ -12,15 +11,12 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded; Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
_config = AppManager.Instance.Config; _config = AppManager.Instance.Config;
ViewModel = new OptionSettingViewModel(UpdateViewHandler); ViewModel = new OptionSettingViewModel(UpdateViewHandler);
clbdestOverride.SelectionChanged += ClbdestOverride_SelectionChanged; clbdestOverride.SelectionChanged += ClbdestOverride_SelectionChanged;
btnBrowseCustomSystemProxyPacPath.Click += BtnBrowseCustomSystemProxyPacPath_Click;
btnBrowseCustomSystemProxyScriptPath.Click += BtnBrowseCustomSystemProxyScriptPath_Click;
clbdestOverride.ItemsSource = Global.destOverrideProtocols; clbdestOverride.ItemsSource = Global.destOverrideProtocols;
_config.Inbound.First().DestOverride?.ForEach(it => _config.Inbound.First().DestOverride?.ForEach(it =>
{ {
@@ -105,8 +101,6 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).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); this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomSystemProxyPacPath, v => v.txtCustomSystemProxyPacPath.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomSystemProxyScriptPath, v => v.txtCustomSystemProxyScriptPath.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables);
@@ -133,7 +127,6 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
labHide2TrayWhenClose.IsVisible = false; labHide2TrayWhenClose.IsVisible = false;
togHide2TrayWhenClose.IsVisible = false; togHide2TrayWhenClose.IsVisible = false;
labHide2TrayWhenCloseTip.IsVisible = false; labHide2TrayWhenCloseTip.IsVisible = false;
panSystemProxyUnix.IsVisible = false;
} }
else if (Utils.IsLinux()) else if (Utils.IsLinux())
{ {
@@ -160,7 +153,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
case EViewAction.InitSettingFont: case EViewAction.InitSettingFont:
@@ -219,28 +212,6 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
} }
} }
private async void BtnBrowseCustomSystemProxyPacPath_Click(object? sender, RoutedEventArgs e)
{
var fileName = await UI.OpenFileDialog(this, null);
if (fileName.IsNullOrEmpty())
{
return;
}
txtCustomSystemProxyPacPath.Text = fileName;
}
private async void BtnBrowseCustomSystemProxyScriptPath_Click(object? sender, RoutedEventArgs e)
{
var fileName = await UI.OpenFileDialog(this, null);
if (fileName.IsNullOrEmpty())
{
return;
}
txtCustomSystemProxyScriptPath.Text = fileName;
}
private void Window_Loaded(object? sender, RoutedEventArgs e) private void Window_Loaded(object? sender, RoutedEventArgs e)
{ {
btnCancel.Focus(); btnCancel.Focus();

View File

@@ -90,7 +90,7 @@ public partial class ProfilesSelectWindow : WindowBase<ProfilesSelectViewModel>
private void LstProfiles_DoubleTapped(object? sender, TappedEventArgs e) private void LstProfiles_DoubleTapped(object? sender, TappedEventArgs e)
{ {
// Ignore double-taps on the column header area // 忽略表头区域的双击
if (e.Source is Control src) if (e.Source is Control src)
{ {
if (src.FindAncestorOfType<DataGridColumnHeader>() != null) if (src.FindAncestorOfType<DataGridColumnHeader>() != null)
@@ -99,7 +99,7 @@ public partial class ProfilesSelectWindow : WindowBase<ProfilesSelectViewModel>
return; return;
} }
// Only trigger selection when double-tapped on a data row or its child element // 仅当在数据行或其子元素上双击时才触发选择
if (src.FindAncestorOfType<DataGridRow>() != null) if (src.FindAncestorOfType<DataGridRow>() != null)
{ {
ViewModel?.SelectFinish(); ViewModel?.SelectFinish();
@@ -110,7 +110,7 @@ public partial class ProfilesSelectWindow : WindowBase<ProfilesSelectViewModel>
private void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e) private void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
{ {
// Custom sort to prevent unintended default behavior // 自定义排序,防止默认行为导致误触发
e.Handled = true; e.Handled = true;
if (ViewModel != null && e.Column?.Tag?.ToString() != null) if (ViewModel != null && e.Column?.Tag?.ToString() != null)
{ {

View File

@@ -124,10 +124,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{ {
case EViewAction.SetClipboardData: case EViewAction.SetClipboardData:
if (obj is null) if (obj is null)
{
return false; return false;
}
await AvaUtils.SetClipboardData(this, (string)obj); await AvaUtils.SetClipboardData(this, (string)obj);
break; break;
@@ -144,10 +141,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
case EViewAction.SaveFileDialog: case EViewAction.SaveFileDialog:
if (obj is null) if (obj is null)
{
return false; return false;
}
var fileName = await UI.SaveFileDialog(_window, ""); var fileName = await UI.SaveFileDialog(_window, "");
if (fileName.IsNullOrEmpty()) if (fileName.IsNullOrEmpty())
{ {
@@ -158,43 +152,28 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
case EViewAction.AddServerWindow: case EViewAction.AddServerWindow:
if (obj is null) if (obj is null)
{
return false; return false;
}
return await new AddServerWindow((ProfileItem)obj).ShowDialog<bool>(_window); return await new AddServerWindow((ProfileItem)obj).ShowDialog<bool>(_window);
case EViewAction.AddServer2Window: case EViewAction.AddServer2Window:
if (obj is null) if (obj is null)
{
return false; return false;
}
return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(_window); return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(_window);
case EViewAction.AddGroupServerWindow: case EViewAction.AddGroupServerWindow:
if (obj is null) if (obj is null)
{
return false; return false;
}
return await new AddGroupServerWindow((ProfileItem)obj).ShowDialog<bool>(_window); return await new AddGroupServerWindow((ProfileItem)obj).ShowDialog<bool>(_window);
case EViewAction.ShareServer: case EViewAction.ShareServer:
if (obj is null) if (obj is null)
{
return false; return false;
}
await ShareServer((string)obj); await ShareServer((string)obj);
break; break;
case EViewAction.SubEditWindow: case EViewAction.SubEditWindow:
if (obj is null) if (obj is null)
{
return false; return false;
}
return await new SubEditWindow((SubItem)obj).ShowDialog<bool>(_window); return await new SubEditWindow((SubItem)obj).ShowDialog<bool>(_window);
case EViewAction.DispatcherRefreshServersBiz: case EViewAction.DispatcherRefreshServersBiz:
@@ -236,17 +215,14 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{ {
var source = e.Source as Border; var source = e.Source as Border;
if (source?.Name == "HeaderBackground") if (source?.Name == "HeaderBackground")
{
return; return;
}
if (_config.UiItem.DoubleClick2Activate) if (_config.UiItem.DoubleClick2Activate)
{ {
ViewModel?.SetDefaultServer(); ViewModel?.SetDefaultServer();
} }
else else
{ {
ViewModel?.EditServerAsync(); ViewModel?.EditServerAsync(EConfigType.Custom);
} }
} }
@@ -287,7 +263,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
break; break;
case Key.D: case Key.D:
ViewModel?.EditServerAsync(); ViewModel?.EditServerAsync(EConfigType.Custom);
break; break;
case Key.F: case Key.F:

View File

@@ -13,8 +13,8 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
{ {
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded; this.Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
clbProtocol.SelectionChanged += ClbProtocol_SelectionChanged; clbProtocol.SelectionChanged += ClbProtocol_SelectionChanged;
clbInboundTag.SelectionChanged += ClbInboundTag_SelectionChanged; clbInboundTag.SelectionChanged += ClbInboundTag_SelectionChanged;
@@ -61,7 +61,7 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
} }
return await Task.FromResult(true); return await Task.FromResult(true);

View File

@@ -14,9 +14,9 @@ public partial class RoutingRuleSettingWindow : WindowBase<RoutingRuleSettingVie
{ {
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded; this.Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
KeyDown += RoutingRuleSettingWindow_KeyDown; this.KeyDown += RoutingRuleSettingWindow_KeyDown;
lstRules.SelectionChanged += lstRules_SelectionChanged; lstRules.SelectionChanged += lstRules_SelectionChanged;
lstRules.DoubleTapped += LstRules_DoubleTapped; lstRules.DoubleTapped += LstRules_DoubleTapped;
menuRuleSelectAll.Click += menuRuleSelectAll_Click; menuRuleSelectAll.Click += menuRuleSelectAll_Click;
@@ -64,7 +64,7 @@ public partial class RoutingRuleSettingWindow : WindowBase<RoutingRuleSettingVie
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
case EViewAction.ShowYesNo: case EViewAction.ShowYesNo:
@@ -83,10 +83,7 @@ public partial class RoutingRuleSettingWindow : WindowBase<RoutingRuleSettingVie
case EViewAction.RoutingRuleDetailsWindow: case EViewAction.RoutingRuleDetailsWindow:
if (obj is null) if (obj is null)
{
return false; return false;
}
return await new RoutingRuleDetailsWindow((RulesItem)obj).ShowDialog<bool>(this); return await new RoutingRuleDetailsWindow((RulesItem)obj).ShowDialog<bool>(this);
case EViewAction.ImportRulesFromFile: case EViewAction.ImportRulesFromFile:

View File

@@ -12,9 +12,9 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded; Loaded += Window_Loaded;
Closing += RoutingSettingWindow_Closing; this.Closing += RoutingSettingWindow_Closing;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
KeyDown += RoutingSettingWindow_KeyDown; this.KeyDown += RoutingSettingWindow_KeyDown;
lstRoutings.SelectionChanged += lstRoutings_SelectionChanged; lstRoutings.SelectionChanged += lstRoutings_SelectionChanged;
lstRoutings.DoubleTapped += LstRoutings_DoubleTapped; lstRoutings.DoubleTapped += LstRoutings_DoubleTapped;
menuRoutingAdvancedSelectAll.Click += menuRoutingAdvancedSelectAll_Click; menuRoutingAdvancedSelectAll.Click += menuRoutingAdvancedSelectAll_Click;
@@ -48,7 +48,7 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
case EViewAction.ShowYesNo: case EViewAction.ShowYesNo:
@@ -60,10 +60,7 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
case EViewAction.RoutingRuleSettingWindow: case EViewAction.RoutingRuleSettingWindow:
if (obj is null) if (obj is null)
{
return false; return false;
}
return await new RoutingRuleSettingWindow((RoutingItem)obj).ShowDialog<bool>(this); return await new RoutingRuleSettingWindow((RoutingItem)obj).ShowDialog<bool>(this);
} }
return await Task.FromResult(true); return await Task.FromResult(true);
@@ -119,7 +116,7 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
private void btnCancel_Click(object? sender, RoutedEventArgs e) private void btnCancel_Click(object? sender, RoutedEventArgs e)
{ {
_manualClose = true; _manualClose = true;
Close(ViewModel?.IsModified); this.Close(ViewModel?.IsModified);
} }
private void RoutingSettingWindow_Closing(object? sender, WindowClosingEventArgs e) private void RoutingSettingWindow_Closing(object? sender, WindowClosingEventArgs e)

View File

@@ -56,10 +56,7 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
case EViewAction.SetClipboardData: case EViewAction.SetClipboardData:
if (obj is null) if (obj is null)
{
return false; return false;
}
await AvaUtils.SetClipboardData(this, (string)obj); await AvaUtils.SetClipboardData(this, (string)obj);
break; break;

View File

@@ -14,7 +14,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
InitializeComponent(); InitializeComponent();
Loaded += Window_Loaded; Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => this.Close();
ViewModel = new SubEditViewModel(subItem, UpdateViewHandler); ViewModel = new SubEditViewModel(subItem, UpdateViewHandler);
@@ -45,7 +45,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
switch (action) switch (action)
{ {
case EViewAction.CloseWindow: case EViewAction.CloseWindow:
Close(true); this.Close(true);
break; break;
} }
return await Task.FromResult(true); return await Task.FromResult(true);

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