merge upstream/master

This commit is contained in:
Koll Zhu 2025-11-12 07:37:11 +08:00
commit a50a0f8246
245 changed files with 9884 additions and 3795 deletions

View file

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

9
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

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

View file

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

View file

@ -45,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v4.6.2
uses: actions/upload-artifact@v5.0.0
with:
name: v2rayN-macos
path: |

View file

@ -45,7 +45,7 @@ jobs:
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v4.6.2
uses: actions/upload-artifact@v5.0.0
with:
name: v2rayN-windows-desktop
path: |

View file

@ -46,7 +46,7 @@ jobs:
- name: Upload build artifacts
uses: actions/upload-artifact@v4.6.2
uses: actions/upload-artifact@v5.0.0
with:
name: v2rayN-windows
path: |

View file

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

View file

@ -28,6 +28,7 @@ Package: v2rayN
Version: $Version
Architecture: $Arch2
Maintainer: https://github.com/cg3s/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)
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
EOF

View file

@ -43,6 +43,8 @@ cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
<true/>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>12.7</string>
</dict>
</plist>
EOF

View file

@ -19,6 +19,23 @@ else
exit 1
fi
# ======================== Kernel version check (require >= 6.11) =======================
MIN_KERNEL_MAJOR=6
MIN_KERNEL_MINOR=11
KERNEL_FULL=$(uname -r)
KERNEL_MAJOR=$(echo "$KERNEL_FULL" | cut -d. -f1)
KERNEL_MINOR=$(echo "$KERNEL_FULL" | cut -d. -f2)
echo "[INFO] Detected kernel version: $KERNEL_FULL"
if (( KERNEL_MAJOR < MIN_KERNEL_MAJOR )) || { (( KERNEL_MAJOR == MIN_KERNEL_MAJOR )) && (( KERNEL_MINOR < MIN_KERNEL_MINOR )); }; then
echo "[ERROR] Kernel $KERNEL_FULL is too old. Requires Linux >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}."
echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+, Debian 13+)."
exit 1
fi
echo "[OK] Kernel version >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}."
# ===== Config & Parse arguments =========================================================
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
WITH_CORE="both" # Default: bundle both xray+sing-box
@ -614,8 +631,13 @@ ExclusiveArch: aarch64 x86_64
Source0: __PKGROOT__.tar.gz
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL, xdg-utils
Requires: freetype, cairo, pango, openssl, mesa-libEGL, mesa-libGL
Requires: glibc >= 2.34
Requires: fontconfig >= 2.13.1
Requires: desktop-file-utils >= 0.26
Requires: xdg-utils >= 1.1.3
Requires: coreutils >= 8.32
Requires: bash >= 5.1
%description
v2rayN Linux for Red Hat Enterprise Linux

View file

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

View file

@ -5,22 +5,24 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.6" />
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.8" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.8" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.8" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" />
<PackageVersion Include="CliWrap" Version="3.9.0" />
<PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
<PackageVersion Include="MaterialDesignThemes" Version="5.2.1" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
<PackageVersion Include="QRCoder" Version="1.6.0" />
<PackageVersion Include="ReactiveUI" Version="20.4.1" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.0" />
<PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="ReactiveUI" Version="22.2.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
<PackageVersion Include="Splat.NLog" Version="16.2.1" />
<PackageVersion Include="ReactiveUI.WPF" Version="22.2.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.7" />
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7" />
<PackageVersion Include="NLog" Version="6.0.5" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" />

@ -1 +1 @@
Subproject commit 270f023fcca4ef9149480c575d502815efd83531
Subproject commit ffb2850df0991495d0918e13cc5701737f26175a

View file

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

View file

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

View file

@ -84,4 +84,14 @@ public static class Extension
{
return source.Concat(new[] { string.Empty }).ToList();
}
public static bool IsGroupType(this EConfigType configType)
{
return configType is EConfigType.PolicyGroup or EConfigType.ProxyChain;
}
public static bool IsComplexType(this EConfigType configType)
{
return configType is EConfigType.Custom or EConfigType.PolicyGroup or EConfigType.ProxyChain;
}
}

View file

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

View file

@ -1,180 +0,0 @@
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 Job : IDisposable
{
private IntPtr handle = IntPtr.Zero;
public Job()
{
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;
}
}
~Job()
{
Dispose(false);
}
#endregion IDisposable
#region Interop
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr CreateJobObject(IntPtr a, string? lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
#endregion Interop
}
#region Helper classes
[StructLayout(LayoutKind.Sequential)]
internal struct IO_COUNTERS
{
public ulong ReadOperationCount;
public ulong WriteOperationCount;
public ulong OtherOperationCount;
public ulong ReadTransferCount;
public ulong WriteTransferCount;
public ulong OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public long PerProcessUserTimeLimit;
public long PerJobUserTimeLimit;
public uint LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public uint ActiveProcessLimit;
public UIntPtr Affinity;
public uint PriorityClass;
public uint SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
#endregion Helper classes

View file

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

View file

@ -1,5 +1,3 @@
using System.Diagnostics;
namespace ServiceLib.Common;
public static class ProcUtils
@ -67,116 +65,4 @@ public static class ProcUtils
Logging.SaveLog(_tag, ex);
}
}
public static async Task ProcessKill(int pid)
{
try
{
await ProcessKill(Process.GetProcessById(pid), false);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
public static async Task ProcessKill(Process? proc, bool review)
{
if (proc is null)
{
return;
}
GetProcessKeyInfo(proc, review, out var procId, out var fileName, out var processName);
try
{
if (Utils.IsNonWindows())
{
proc?.Kill(true);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
try
{
proc?.Kill();
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
try
{
proc?.Close();
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
try
{
proc?.Dispose();
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
await Task.Delay(300);
await ProcessKillByKeyInfo(review, procId, fileName, processName);
}
private static void GetProcessKeyInfo(Process? proc, bool review, out int? procId, out string? fileName, out string? processName)
{
procId = null;
fileName = null;
processName = null;
if (!review)
{
return;
}
try
{
procId = proc?.Id;
fileName = proc?.MainModule?.FileName;
processName = proc?.ProcessName;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private static async Task ProcessKillByKeyInfo(bool review, int? procId, string? fileName, string? processName)
{
if (review && procId != null && fileName != null)
{
try
{
var lstProc = Process.GetProcessesByName(processName);
foreach (var proc2 in lstProc)
{
if (proc2.Id == procId)
{
Logging.SaveLog($"{_tag}, KillProcess not completing the job, procId");
await ProcessKill(proc2, false);
}
if (proc2.MainModule != null && proc2.MainModule?.FileName == fileName)
{
Logging.SaveLog($"{_tag}, KillProcess not completing the job, fileName");
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
}
}

View file

@ -1,4 +1,5 @@
using QRCoder;
using QRCoder.Exceptions;
using SkiaSharp;
using ZXing.SkiaSharp;
@ -8,10 +9,45 @@ public class QRCodeUtils
{
public static byte[]? GenQRCode(string? url)
{
if (url.IsNullOrEmpty())
{
return null;
}
using QRCodeGenerator qrGenerator = new();
using var qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q);
using PngByteQRCode qrCode = new(qrCodeData);
return qrCode.GetGraphic(20);
DataTooLongException? lastDtle = null;
var levels = new[]
{
QRCodeGenerator.ECCLevel.H,
QRCodeGenerator.ECCLevel.Q,
QRCodeGenerator.ECCLevel.M,
QRCodeGenerator.ECCLevel.L
};
foreach (var level in levels)
{
try
{
using var qrCodeData = qrGenerator.CreateQrCode(url, level);
using PngByteQRCode qrCode = new(qrCodeData);
return qrCode.GetGraphic(20);
}
catch (DataTooLongException ex)
{
lastDtle = ex;
continue;
}
catch
{
throw;
}
}
if (lastDtle != null)
{
throw lastDtle;
}
return null;
}
public static string? ParseBarcode(string? fileName)

View file

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

View file

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

View file

@ -12,5 +12,7 @@ public enum EConfigType
TUIC = 8,
WireGuard = 9,
HTTP = 10,
Anytls = 11
Anytls = 11,
PolicyGroup = 101,
ProxyChain = 102,
}

View file

@ -2,8 +2,9 @@ namespace ServiceLib.Enums;
public enum EMultipleLoad
{
LeastPing,
Fallback,
Random,
RoundRobin,
LeastPing,
LeastLoad
}

View file

@ -0,0 +1,8 @@
namespace ServiceLib.Enums;
public enum ERuleType
{
ALL = 0,
Routing = 1,
DNS = 2,
}

View file

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

View file

@ -12,7 +12,6 @@ public enum EViewAction
ProfilesFocus,
ShareSub,
ShareServer,
ShowHideWindow,
ScanScreenTask,
ScanImageTask,
BrowseServer,
@ -24,6 +23,7 @@ public enum EViewAction
RoutingRuleDetailsWindow,
AddServerWindow,
AddServer2Window,
AddGroupServerWindow,
DNSSettingWindow,
RoutingSettingWindow,
OptionSettingWindow,

View file

@ -0,0 +1,30 @@
namespace ServiceLib.Events;
public static class AppEvents
{
public static readonly EventChannel<Unit> ReloadRequested = new();
public static readonly EventChannel<bool?> ShowHideWindowRequested = new();
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
public static readonly EventChannel<Unit> ProxiesReloadRequested = new();
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
public static readonly EventChannel<string> SendSnackMsgRequested = new();
public static readonly EventChannel<string> SendMsgViewRequested = new();
public static readonly EventChannel<Unit> AppExitRequested = new();
public static readonly EventChannel<bool> ShutdownRequested = new();
public static readonly EventChannel<Unit> AdjustMainLvColWidthRequested = new();
public static readonly EventChannel<string> SetDefaultServerRequested = new();
public static readonly EventChannel<Unit> RoutingsMenuRefreshRequested = new();
public static readonly EventChannel<Unit> TestServerRequested = new();
public static readonly EventChannel<Unit> InboundDisplayRequested = new();
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
}

View file

@ -0,0 +1,27 @@
using System.Reactive.Subjects;
namespace ServiceLib.Events;
public sealed class EventChannel<T>
{
private readonly ISubject<T> _subject = Subject.Synchronize(new Subject<T>());
public IObservable<T> AsObservable()
{
return _subject.AsObservable();
}
public void Publish(T value)
{
_subject.OnNext(value);
}
public void Publish()
{
if (typeof(T) != typeof(Unit))
{
throw new InvalidOperationException("Publish() without value is only valid for EventChannel<Unit>.");
}
_subject.OnNext((T)(object)Unit.Default);
}
}

View file

@ -50,6 +50,7 @@ public class Global
public const string DirectTag = "direct";
public const string BlockTag = "block";
public const string DnsTag = "dns-module";
public const string BalancerTagSuffix = "-round";
public const string StreamSecurity = "tls";
public const string StreamSecurityReality = "reality";
public const string Loopback = "127.0.0.1";
@ -83,8 +84,7 @@ public class Global
public const string SingboxDirectDNSTag = "direct_dns";
public const string SingboxRemoteDNSTag = "remote_dns";
public const string SingboxOutboundResolverTag = "outbound_resolver";
public const string SingboxFinalResolverTag = "final_resolver";
public const string SingboxLocalDNSTag = "local_local";
public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns";
@ -315,6 +315,8 @@ public class Global
EConfigType.HTTP,
];
public static readonly HashSet<EConfigType> SingboxOnlyConfigType = SingboxSupportConfigType.Except(XraySupportConfigType).ToHashSet();
public static readonly List<string> DomainStrategies =
[
AsIs,
@ -425,6 +427,7 @@ public class Global
"zh-Hant",
"en",
"fa-Ir",
"fr",
"ru",
"hu"
];
@ -449,6 +452,14 @@ public class Global
"none"
];
public static readonly Dictionary<string, string> LogLevelColors = new()
{
{ "debug", "#6C757D" },
{ "info", "#2ECC71" },
{ "warning", "#FFA500" },
{ "error", "#E74C3C" },
};
public static readonly List<string> InboundTags =
[
"socks",
@ -598,6 +609,7 @@ public class Global
{ "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } },
{ "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } },
{ "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
{ "doh.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
{ "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } },
{ "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } },
{ "dns.sb", new List<string> { "185.222.222.222", "2a09::" } },

View file

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

View file

@ -1,21 +0,0 @@
using System.Reactive;
using System.Reactive.Subjects;
namespace ServiceLib.Handler;
public static class AppEvents
{
public static readonly Subject<Unit> ProfilesRefreshRequested = new();
public static readonly Subject<string> SendSnackMsgRequested = new();
public static readonly Subject<string> SendMsgViewRequested = new();
public static readonly Subject<Unit> AppExitRequested = new();
public static readonly Subject<bool> ShutdownRequested = new();
public static readonly Subject<Unit> AdjustMainLvColWidthRequested = new();
public static readonly Subject<ServerSpeedItem> DispatcherStatisticsRequested = new();
}

View file

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

View file

@ -1,5 +1,4 @@
using System.Data;
using System.Text.RegularExpressions;
namespace ServiceLib.Handler;
@ -98,7 +97,7 @@ public static class ConfigHandler
config.UiItem ??= new UIItem()
{
EnableAutoAdjustMainLvColWidth = true
EnableUpdateSubOnlyRemarksExist = true
};
config.UiItem.MainColumnItem ??= new();
config.UiItem.WindowSizeItem ??= new();
@ -113,10 +112,8 @@ public static class ConfigHandler
config.ConstItem ??= new ConstItem();
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
if (config.SimpleDNSItem.GlobalFakeIp is null)
{
config.SimpleDNSItem.GlobalFakeIp = true;
}
config.SimpleDNSItem.GlobalFakeIp ??= true;
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
config.SpeedTestItem ??= new();
if (config.SpeedTestItem.SpeedTestTimeout < 10)
@ -255,6 +252,7 @@ public static class ConfigHandler
item.Mldsa65Verify = profileItem.Mldsa65Verify;
item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled;
item.Cert = profileItem.Cert;
}
var ret = item.ConfigType switch
@ -357,6 +355,11 @@ public static class ConfigHandler
{
}
}
else if (profileItem.ConfigType.IsGroupType())
{
var profileGroupItem = await AppManager.Instance.GetProfileGroupItem(it.IndexId);
await AddGroupServerCommon(config, profileItem, profileGroupItem, true);
}
else
{
await AddServerCommon(config, profileItem, true);
@ -445,13 +448,13 @@ public static class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> MoveServer(Config config, List<ProfileItem> lstProfile, int index, EMove eMove, int pos = -1)
{
int count = lstProfile.Count;
var count = lstProfile.Count;
if (index < 0 || index > lstProfile.Count - 1)
{
return -1;
}
for (int i = 0; i < lstProfile.Count; i++)
for (var i = 0; i < lstProfile.Count; i++)
{
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
}
@ -525,7 +528,7 @@ public static class ConfigHandler
return -1;
}
var ext = Path.GetExtension(fileName);
string newFileName = $"{Utils.GetGuid()}{ext}";
var newFileName = $"{Utils.GetGuid()}{ext}";
//newFileName = Path.Combine(Utile.GetTempPath(), newFileName);
try
@ -1074,6 +1077,37 @@ public static class ConfigHandler
return 0;
}
public static async Task<int> AddGroupServerCommon(Config config, ProfileItem profileItem, ProfileGroupItem profileGroupItem, bool toFile = true)
{
var maxSort = -1;
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExManager.Instance.GetMaxSort();
}
var groupType = profileItem.ConfigType == EConfigType.ProxyChain ? EConfigType.ProxyChain.ToString() : profileGroupItem.MultipleLoad.ToString();
profileItem.Address = $"{profileItem.CoreType}-{groupType}";
if (maxSort > 0)
{
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
}
if (toFile)
{
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
if (profileGroupItem != null)
{
profileGroupItem.IndexId = profileItem.IndexId;
await ProfileGroupItemManager.Instance.SaveItemAsync(profileGroupItem);
}
else
{
ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(profileItem.IndexId);
await ProfileGroupItemManager.Instance.SaveTo();
}
}
return 0;
}
/// <summary>
/// Compare two profile items to determine if they represent the same server
/// Used for deduplication and server matching
@ -1145,7 +1179,7 @@ public static class ConfigHandler
}
/// <summary>
/// Create a custom server that combines multiple servers for load balancing
/// Create a group server that combines multiple servers for load balancing
/// Generates a configuration file that references multiple servers
/// </summary>
/// <param name="config">Current configuration</param>
@ -1153,45 +1187,55 @@ public static class ConfigHandler
/// <param name="coreType">Core type to use (Xray or sing_box)</param>
/// <param name="multipleLoad">Load balancing algorithm</param>
/// <returns>Result object with success state and data</returns>
public static async Task<RetResult> AddCustomServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad)
public static async Task<RetResult> AddGroupServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad, string? subId)
{
var indexId = Utils.GetMd5(Global.CoreMultipleLoadConfigFileName);
var configPath = Utils.GetConfigPath(Global.CoreMultipleLoadConfigFileName);
var result = new RetResult();
var result = await CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, coreType, multipleLoad);
if (result.Success != true)
{
return result;
}
var indexId = Utils.GetGuid(false);
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
if (!File.Exists(configPath))
{
return result;
}
var profileItem = await AppManager.Instance.GetProfileItem(indexId) ?? new();
profileItem.IndexId = indexId;
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId)).Remarks} ";
if (coreType == ECoreType.Xray)
{
profileItem.Remarks = multipleLoad switch
remark += multipleLoad switch
{
EMultipleLoad.Random => ResUI.menuSetDefaultMultipleServerXrayRandom,
EMultipleLoad.RoundRobin => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
EMultipleLoad.LeastPing => ResUI.menuSetDefaultMultipleServerXrayLeastPing,
EMultipleLoad.LeastLoad => ResUI.menuSetDefaultMultipleServerXrayLeastLoad,
_ => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback,
EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom,
EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad,
_ => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
};
}
else if (coreType == ECoreType.sing_box)
{
profileItem.Remarks = ResUI.menuSetDefaultMultipleServerSingBoxLeastPing;
remark += multipleLoad switch
{
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback,
_ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
};
}
profileItem.Address = Global.CoreMultipleLoadConfigFileName;
profileItem.ConfigType = EConfigType.Custom;
profileItem.CoreType = coreType;
await AddServerCommon(config, profileItem, true);
var profile = new ProfileItem
{
IndexId = indexId,
CoreType = coreType,
ConfigType = EConfigType.PolicyGroup,
Remarks = remark,
IsSub = false
};
if (!subId.IsNullOrEmpty())
{
profile.Subid = subId;
}
var profileGroup = new ProfileGroupItem
{
ChildItems = childProfileIndexId,
MultipleLoad = multipleLoad,
IndexId = indexId,
};
var ret = await AddGroupServerCommon(config, profile, profileGroup, true);
result.Success = ret == 0;
result.Data = indexId;
return result;
}
@ -1209,16 +1253,25 @@ public static class ConfigHandler
ProfileItem? itemSocks = null;
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
{
var tun2SocksAddress = node.Address;
if (node.ConfigType.IsGroupType())
{
var lstAddresses = (await ProfileGroupItemManager.GetAllChildDomainAddresses(node.IndexId)).ToList();
if (lstAddresses.Count > 0)
{
tun2SocksAddress = Utils.List2String(lstAddresses);
}
}
itemSocks = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.SOCKS,
Address = Global.Loopback,
Sni = node.Address, //Tun2SocksAddress
SpiderX = tun2SocksAddress, // Tun2SocksAddress
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
};
}
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)
{
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem()
@ -1304,7 +1357,7 @@ public static class ConfigHandler
}
continue;
}
var profileItem = FmtHandler.ResolveConfig(str, out string msg);
var profileItem = FmtHandler.ResolveConfig(str, out var msg);
if (profileItem is null)
{
continue;
@ -1388,7 +1441,7 @@ public static class ConfigHandler
{
await RemoveServersViaSubid(config, subid, isSub);
}
int count = 0;
var count = 0;
foreach (var it in lstProfiles)
{
it.Subid = subid;
@ -1431,15 +1484,6 @@ public static class ConfigHandler
{
profileItem = Hysteria2Fmt.ResolveFull2(strData, subRemarks);
}
if (profileItem is null)
{
profileItem = Hysteria2Fmt.ResolveFull(strData, subRemarks);
}
//Is naiveproxy configuration
if (profileItem is null)
{
profileItem = NaiveproxyFmt.ResolveFull(strData, subRemarks);
}
if (profileItem is null || profileItem.Address.IsNullOrEmpty())
{
return -1;
@ -1487,7 +1531,7 @@ public static class ConfigHandler
var lstSsServer = ShadowsocksFmt.ResolveSip008(strData);
if (lstSsServer?.Count > 0)
{
int counter = 0;
var counter = 0;
foreach (var ssItem in lstSsServer)
{
ssItem.Subid = subid;
@ -1607,7 +1651,9 @@ public static class ConfigHandler
var uri = Utils.TryUri(url);
if (uri == null)
{
return -1;
}
//Do not allow http protocol
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{
@ -1662,7 +1708,7 @@ public static class ConfigHandler
var maxSort = 0;
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
{
var lstSubs = (await AppManager.Instance.SubItems());
var lstSubs = await AppManager.Instance.SubItems();
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
}
item.Sort = maxSort + 1;
@ -1824,7 +1870,7 @@ public static class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> MoveRoutingRule(List<RulesItem> rules, int index, EMove eMove, int pos = -1)
{
int count = rules.Count;
var count = rules.Count;
if (index < 0 || index > rules.Count - 1)
{
return -1;
@ -1974,11 +2020,15 @@ public static class ConfigHandler
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, "");
if (templateContent.IsNullOrEmpty())
{
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
}
var template = JsonUtils.Deserialize<RoutingTemplate>(templateContent);
if (template == null)
{
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
}
var items = await AppManager.Instance.RoutingItems();
var maxSort = items.Count;
@ -1991,14 +2041,18 @@ public static class ConfigHandler
var item = template.RoutingItems[i];
if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty())
{
continue;
}
var ruleSetsString = !item.RuleSet.IsNullOrEmpty()
? item.RuleSet
: await downloadHandle.TryDownloadString(item.Url, true, "");
if (ruleSetsString.IsNullOrEmpty())
{
continue;
}
item.Remarks = $"{template.Version}-{item.Remarks}";
item.Enabled = true;
@ -2194,17 +2248,25 @@ public static class ConfigHandler
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (templateContent.IsNullOrEmpty())
{
return currentItem;
}
var template = JsonUtils.Deserialize<DNSItem>(templateContent);
if (template == null)
{
return currentItem;
}
if (!template.NormalDNS.IsNullOrEmpty())
{
template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, "");
}
if (!template.TunDNS.IsNullOrEmpty())
{
template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, "");
}
template.Id = currentItem.Id;
template.Enabled = currentItem.Enabled;
@ -2229,8 +2291,7 @@ public static class ConfigHandler
BlockBindingQuery = true,
DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(),
SingboxOutboundsResolveDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
SingboxFinalResolveDNS = Global.DomainPureIPDNSAddress.FirstOrDefault()
BootstrapDNS = Global.DomainPureIPDNSAddress.FirstOrDefault(),
};
}
@ -2239,10 +2300,16 @@ public static class ConfigHandler
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (templateContent.IsNullOrEmpty())
{
return null;
}
var template = JsonUtils.Deserialize<SimpleDNSItem>(templateContent);
if (template == null)
{
return null;
}
return template;
}

View file

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

View file

@ -58,7 +58,7 @@ public static class CoreConfigHandler
File.Delete(fileName);
}
string addressFileName = node.Address;
var addressFileName = node.Address;
if (!File.Exists(addressFileName))
{
addressFileName = Utils.GetConfigPath(addressFileName);
@ -132,24 +132,4 @@ public static class CoreConfigHandler
await File.WriteAllTextAsync(fileName, result.Data.ToString());
return result;
}
public static async Task<RetResult> GenerateClientMultipleLoadConfig(Config config, string fileName, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad)
{
var result = new RetResult();
if (coreType == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds);
}
else
{
result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds, multipleLoad);
}
if (result.Success != true)
{
return result;
}
await File.WriteAllTextAsync(fileName, result.Data.ToString());
return result;
}
}

View file

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

View file

@ -4,6 +4,8 @@ namespace ServiceLib.Handler.Fmt;
public class BaseFmt
{
private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure", "verify" };
protected static string GetIpv6(string address)
{
if (Utils.IsIpv6(address))
@ -17,7 +19,7 @@ public class BaseFmt
}
}
protected static int GetStdTransport(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
{
if (item.Flow.IsNotEmpty())
{
@ -37,11 +39,7 @@ public class BaseFmt
}
if (item.Sni.IsNotEmpty())
{
dicQuery.Add("sni", item.Sni);
}
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
dicQuery.Add("sni", Utils.UrlEncode(item.Sni));
}
if (item.Fingerprint.IsNotEmpty())
{
@ -63,9 +61,14 @@ public class BaseFmt
{
dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify));
}
if (item.AllowInsecure.Equals("true"))
if (item.StreamSecurity.Equals(Global.StreamSecurity))
{
dicQuery.Add("allowInsecure", "1");
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
ToUriQueryAllowInsecure(item, ref dicQuery);
}
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
@ -153,63 +156,107 @@ public class BaseFmt
return 0;
}
protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item)
protected static int ToUriQueryLite(ProfileItem item, ref Dictionary<string, string> dicQuery)
{
item.Flow = query["flow"] ?? "";
item.StreamSecurity = query["security"] ?? "";
item.Sni = query["sni"] ?? "";
item.Alpn = Utils.UrlDecode(query["alpn"] ?? "");
item.Fingerprint = Utils.UrlDecode(query["fp"] ?? "");
item.PublicKey = Utils.UrlDecode(query["pbk"] ?? "");
item.ShortId = Utils.UrlDecode(query["sid"] ?? "");
item.SpiderX = Utils.UrlDecode(query["spx"] ?? "");
item.Mldsa65Verify = Utils.UrlDecode(query["pqv"] ?? "");
item.AllowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : "";
if (item.Sni.IsNotEmpty())
{
dicQuery.Add("sni", Utils.UrlEncode(item.Sni));
}
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
item.Network = query["type"] ?? nameof(ETransport.tcp);
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.StreamSecurity = GetQueryValue(query, "security");
item.Sni = GetQueryValue(query, "sni");
item.Alpn = GetQueryDecoded(query, "alpn");
item.Fingerprint = GetQueryDecoded(query, "fp");
item.PublicKey = GetQueryDecoded(query, "pbk");
item.ShortId = GetQueryDecoded(query, "sid");
item.SpiderX = GetQueryDecoded(query, "spx");
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
{
item.AllowInsecure = Global.AllowInsecure.First();
}
else if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "0"))
{
item.AllowInsecure = Global.AllowInsecure.Skip(1).First();
}
else
{
item.AllowInsecure = string.Empty;
}
item.Network = GetQueryValue(query, "type", nameof(ETransport.tcp));
switch (item.Network)
{
case nameof(ETransport.tcp):
item.HeaderType = query["headerType"] ?? Global.None;
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
item.RequestHost = GetQueryDecoded(query, "host");
break;
case nameof(ETransport.kcp):
item.HeaderType = query["headerType"] ?? Global.None;
item.Path = Utils.UrlDecode(query["seed"] ?? "");
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
item.Path = GetQueryDecoded(query, "seed");
break;
case nameof(ETransport.ws):
case nameof(ETransport.httpupgrade):
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
item.Path = Utils.UrlDecode(query["path"] ?? "/");
item.RequestHost = GetQueryDecoded(query, "host");
item.Path = GetQueryDecoded(query, "path", "/");
break;
case nameof(ETransport.xhttp):
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
item.Path = Utils.UrlDecode(query["path"] ?? "/");
item.HeaderType = Utils.UrlDecode(query["mode"] ?? "");
item.Extra = Utils.UrlDecode(query["extra"] ?? "");
item.RequestHost = GetQueryDecoded(query, "host");
item.Path = GetQueryDecoded(query, "path", "/");
item.HeaderType = GetQueryDecoded(query, "mode");
item.Extra = GetQueryDecoded(query, "extra");
break;
case nameof(ETransport.http):
case nameof(ETransport.h2):
item.Network = nameof(ETransport.h2);
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
item.Path = Utils.UrlDecode(query["path"] ?? "/");
item.RequestHost = GetQueryDecoded(query, "host");
item.Path = GetQueryDecoded(query, "path", "/");
break;
case nameof(ETransport.quic):
item.HeaderType = query["headerType"] ?? Global.None;
item.RequestHost = query["quicSecurity"] ?? Global.None;
item.Path = Utils.UrlDecode(query["key"] ?? "");
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
item.RequestHost = GetQueryValue(query, "quicSecurity", Global.None);
item.Path = GetQueryDecoded(query, "key");
break;
case nameof(ETransport.grpc):
item.RequestHost = Utils.UrlDecode(query["authority"] ?? "");
item.Path = Utils.UrlDecode(query["serviceName"] ?? "");
item.HeaderType = Utils.UrlDecode(query["mode"] ?? Global.GrpcGunMode);
item.RequestHost = GetQueryDecoded(query, "authority");
item.Path = GetQueryDecoded(query, "serviceName");
item.HeaderType = GetQueryDecoded(query, "mode", Global.GrpcGunMode);
break;
default:
@ -239,4 +286,14 @@ public class BaseFmt
var url = $"{Utils.UrlEncode(userInfo)}@{GetIpv6(address)}:{port}";
return $"{Global.ProtocolShares[eConfigType]}{url}{query}{remark}";
}
protected static string GetQueryValue(NameValueCollection query, string key, string defaultValue = "")
{
return query[key] ?? defaultValue;
}
protected static string GetQueryDecoded(NameValueCollection query, string key, string defaultValue = "")
{
return Utils.UrlDecode(GetQueryValue(query, key, defaultValue));
}
}

View file

@ -4,7 +4,7 @@ public class ClashFmt : BaseFmt
{
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "external-controller", "-port", "proxies"))
if (Contains(strData, "rules", "-port", "proxies"))
{
var fileName = WriteAllText(strData, "yaml");

View file

@ -27,7 +27,7 @@ public class FmtHandler
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return "";
return string.Empty;
}
}
@ -37,7 +37,7 @@ public class FmtHandler
try
{
string str = config.TrimEx();
var str = config.TrimEx();
if (str.IsNullOrEmpty())
{
msg = ResUI.FailedReadConfiguration;

View file

@ -12,7 +12,9 @@ public class Hysteria2Fmt : BaseFmt
var url = Utils.TryUri(str);
if (url == null)
{
return null;
}
item.Address = url.IdnHost;
item.Port = url.Port;
@ -20,11 +22,9 @@ public class Hysteria2Fmt : BaseFmt
item.Id = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query);
ResolveStdTransport(query, ref item);
item.Path = Utils.UrlDecode(query["obfs-password"] ?? "");
item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false";
item.Ports = Utils.UrlDecode(query["mport"] ?? "");
ResolveUriQuery(query, ref item);
item.Path = GetQueryDecoded(query, "obfs-password");
item.Ports = GetQueryDecoded(query, "mport");
return item;
}
@ -32,29 +32,25 @@ public class Hysteria2Fmt : BaseFmt
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
string url = string.Empty;
}
string remark = string.Empty;
var url = string.Empty;
var remark = string.Empty;
if (item.Remarks.IsNotEmpty())
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var dicQuery = new Dictionary<string, string>();
if (item.Sni.IsNotEmpty())
{
dicQuery.Add("sni", item.Sni);
}
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
ToUriQueryLite(item, ref dicQuery);
if (item.Path.IsNotEmpty())
{
dicQuery.Add("obfs", "salamander");
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path));
}
dicQuery.Add("insecure", item.AllowInsecure.ToLower() == "true" ? "1" : "0");
if (item.Ports.IsNotEmpty())
{
dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-')));
@ -63,24 +59,6 @@ public class Hysteria2Fmt : BaseFmt
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark);
}
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "server", "up", "down", "listen", "<html>", "<body>"))
{
var fileName = WriteAllText(strData);
var profileItem = new ProfileItem
{
CoreType = ECoreType.hysteria,
Address = fileName,
Remarks = subRemarks ?? "hysteria_custom"
};
return profileItem;
}
return null;
}
public static ProfileItem? ResolveFull2(string strData, string? subRemarks)
{
if (Contains(strData, "server", "auth", "up", "down", "listen"))

View file

@ -1,22 +0,0 @@
namespace ServiceLib.Handler.Fmt;
public class NaiveproxyFmt : BaseFmt
{
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "listen", "proxy", "<html>", "<body>"))
{
var fileName = WriteAllText(strData);
var profileItem = new ProfileItem
{
CoreType = ECoreType.naiveproxy,
Address = fileName,
Remarks = subRemarks ?? "naiveproxy_custom"
};
return profileItem;
}
return null;
}
}

View file

@ -1,5 +1,3 @@
using System.Text.RegularExpressions;
namespace ServiceLib.Handler.Fmt;
public class ShadowsocksFmt : BaseFmt
@ -42,7 +40,7 @@ public class ShadowsocksFmt : BaseFmt
// item.port);
//url = Utile.Base64Encode(url);
//new Sip002
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}");
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark);
}

View file

@ -33,7 +33,7 @@ public class SocksFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks);
}
//new
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}");
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -24,10 +24,10 @@ public class WireguardFmt : BaseFmt
var query = Utils.ParseQueryString(url.Query);
item.PublicKey = Utils.UrlDecode(query["publickey"] ?? "");
item.Path = Utils.UrlDecode(query["reserved"] ?? "");
item.RequestHost = Utils.UrlDecode(query["address"] ?? "");
item.ShortId = Utils.UrlDecode(query["mtu"] ?? "");
item.PublicKey = GetQueryDecoded(query, "publickey");
item.Path = GetQueryDecoded(query, "reserved");
item.RequestHost = GetQueryDecoded(query, "address");
item.ShortId = GetQueryDecoded(query, "mtu");
return item;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,3 @@
using System.Net;
using Downloader;
namespace ServiceLib.Helper;

View file

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

View file

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

View file

@ -0,0 +1,301 @@
namespace ServiceLib.Manager;
/// <summary>
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
/// </summary>
public class ActionPrecheckManager(Config config)
{
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
public static ActionPrecheckManager Instance => _instance.Value;
private readonly Config _config = config;
public async Task<List<string>> Check(string? indexId)
{
if (indexId.IsNullOrEmpty())
{
return [ResUI.PleaseSelectServer];
}
var item = await AppManager.Instance.GetProfileItem(indexId);
if (item is null)
{
return [ResUI.PleaseSelectServer];
}
return await Check(item);
}
public async Task<List<string>> Check(ProfileItem? item)
{
if (item is null)
{
return [ResUI.PleaseSelectServer];
}
var errors = new List<string>();
errors.AddRange(await ValidateCurrentNodeAndCoreSupport(item));
errors.AddRange(await ValidateRelatedNodesExistAndValid(item));
return errors;
}
private async Task<List<string>> ValidateCurrentNodeAndCoreSupport(ProfileItem item)
{
if (item.ConfigType == EConfigType.Custom)
{
return [];
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
return await ValidateNodeAndCoreSupport(item, coreType);
}
private async Task<List<string>> ValidateNodeAndCoreSupport(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
coreType ??= AppManager.Instance.GetCoreType(item, item.ConfigType);
if (item.ConfigType is EConfigType.Custom)
{
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
return errors;
}
if (!item.IsComplex())
{
if (item.Address.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
return errors;
}
if (item.Port is <= 0 or >= 65536)
{
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
return errors;
}
switch (item.ConfigType)
{
case EConfigType.VMess:
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
break;
case EConfigType.VLESS:
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (!Global.Flows.Contains(item.Flow))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
break;
case EConfigType.Shadowsocks:
if (item.Id.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
}
break;
}
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
&& item.StreamSecurity == Global.StreamSecurityReality
&& item.PublicKey.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
}
if (errors.Count > 0)
{
return errors;
}
}
if (item.ConfigType.IsGroupType())
{
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
}
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
if (hasCycle)
{
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
return errors;
}
var childIds = Utils.String2List(group.ChildItems) ?? [];
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRange(subItems.Select(p => p.IndexId));
foreach (var child in childIds)
{
var childErrors = new List<string>();
if (child.IsNullOrEmpty())
{
continue;
}
var childItem = await AppManager.Instance.GetProfileItem(child);
if (childItem is null)
{
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
continue;
}
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
{
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
continue;
}
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
errors.AddRange(childErrors);
}
return errors;
}
var net = item.GetNetwork() ?? item.Network;
if (coreType == ECoreType.sing_box)
{
// sing-box does not support xhttp / kcp
// sing-box does not support transports like ws/http/httpupgrade/etc. when the node is not vmess/trojan/vless
if (net is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
errors.Add(string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net));
return errors;
}
if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan))
{
if (net is nameof(ETransport.ws) or nameof(ETransport.http) or nameof(ETransport.h2) or nameof(ETransport.quic) or nameof(ETransport.httpupgrade))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), item.ConfigType.ToString(), net));
return errors;
}
}
}
else if (coreType is ECoreType.Xray)
{
// Xray core does not support these protocols
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
&& !item.IsComplex())
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
return errors;
}
}
return errors;
}
private async Task<List<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
errors.AddRange(await ValidateProxyChainedNodeExistAndValid(item));
errors.AddRange(await ValidateRoutingNodeExistAndValid(item));
return errors;
}
private async Task<List<string>> ValidateProxyChainedNodeExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
if (item is null)
{
return errors;
}
// prev node and next node
var subItem = await AppManager.Instance.GetSubItem(item.Subid);
if (subItem is null)
{
return errors;
}
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
await CollectProxyChainedNodeValidation(prevNode, subItem.PrevProfile, coreType, errors);
await CollectProxyChainedNodeValidation(nextNode, subItem.NextProfile, coreType, errors);
return errors;
}
private async Task CollectProxyChainedNodeValidation(ProfileItem? node, string tag, ECoreType coreType, List<string> errors)
{
if (node is not null)
{
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
}
else if (tag.IsNotEmpty())
{
errors.Add(ResUI.ProxyChainedPrefix + string.Format(ResUI.NodeTagNotExist, tag));
}
}
private async Task<List<string>> ValidateRoutingNodeExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
if (item is null)
{
return errors;
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null)
{
return errors;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var ruleItem in rules ?? [])
{
if (!ruleItem.Enabled)
{
continue;
}
var outboundTag = ruleItem.OutboundTag;
if (outboundTag.IsNullOrEmpty() || Global.OutboundTags.Contains(outboundTag))
{
continue;
}
var tagItem = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (tagItem is null)
{
errors.Add(ResUI.RoutingRuleOutboundPrefix + string.Format(ResUI.NodeTagNotExist, outboundTag));
continue;
}
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
}
return errors;
}
}

View file

@ -1,5 +1,3 @@
using System.Reactive;
namespace ServiceLib.Manager;
public sealed class AppManager
@ -10,7 +8,6 @@ public sealed class AppManager
private Config _config;
private int? _statePort;
private int? _statePort2;
private Job? _processJob;
public static AppManager Instance => _instance.Value;
public Config Config => _config;
@ -67,6 +64,7 @@ public sealed class AppManager
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
SQLiteHelper.Instance.CreateTable<DNSItem>();
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
return true;
}
@ -96,7 +94,7 @@ public sealed class AppManager
Logging.SaveLog("AppExitAsync Begin");
await SysProxyHandler.UpdateSysProxy(_config, true);
AppEvents.AppExitRequested.OnNext(Unit.Default);
AppEvents.AppExitRequested.Publish();
await Task.Delay(50); //Wait for AppExitRequested to be processed
await ConfigHandler.SaveConfig(_config);
@ -119,7 +117,13 @@ public sealed class AppManager
public void Shutdown(bool byUser)
{
AppEvents.ShutdownRequested.OnNext(byUser);
AppEvents.ShutdownRequested.Publish(byUser);
}
public async Task RebootAsAdmin()
{
ProcUtils.RebootAsAdmin();
await AppManager.Instance.AppExitAsync(true);
}
#endregion App
@ -132,21 +136,6 @@ public sealed class AppManager
return localPort + (int)protocol;
}
public void AddProcess(nint processHandle)
{
if (Utils.IsWindows())
{
_processJob ??= new();
try
{
_processJob?.AddProcess(processHandle);
}
catch
{
}
}
}
#endregion Config
#region SqliteHelper
@ -219,6 +208,15 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks);
}
public async Task<ProfileGroupItem?> GetProfileGroupItem(string indexId)
{
if (indexId.IsNullOrEmpty())
{
return null;
}
return await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
}
public async Task<List<RoutingItem>?> RoutingItems()
{
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();

View file

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

View file

@ -1,5 +1,3 @@
using System.Diagnostics;
using System.Text;
using CliWrap;
using CliWrap.Buffered;
@ -31,58 +29,33 @@ public class CoreAdminManager
await _updateFunc?.Invoke(notify, msg);
}
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
public async Task<ProcessService?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
{
StringBuilder sb = new();
sb.AppendLine("#!/bin/bash");
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
sb.AppendLine($"sudo -S {cmdLine}");
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
sb.AppendLine($"exec sudo -S -- {cmdLine}");
var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
Process proc = new()
{
StartInfo = new()
{
FileName = shFilePath,
Arguments = "",
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
}
};
var procService = new ProcessService(
fileName: shFilePath,
arguments: "",
workingDirectory: Utils.GetBinConfigPath(),
displayLog: true,
redirectInput: true,
environmentVars: null,
updateFunc: _updateFunc
);
void dataHandler(object sender, DataReceivedEventArgs e)
{
if (e.Data.IsNotEmpty())
{
_ = UpdateFunc(false, e.Data + Environment.NewLine);
}
}
await procService.StartAsync(AppManager.Instance.LinuxSudoPwd);
proc.OutputDataReceived += dataHandler;
proc.ErrorDataReceived += dataHandler;
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(AppManager.Instance.LinuxSudoPwd);
await Task.Delay(100);
if (proc is null or { HasExited: true })
if (procService is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
_linuxSudoPid = procService.Id;
_linuxSudoPid = proc.Id;
return proc;
return procService;
}
public async Task KillProcessAsLinuxSudo()
@ -94,8 +67,8 @@ public class CoreAdminManager
try
{
var shellFileName = Utils.IsOSX() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName;
var shFilePath = await FileManager.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
var shellFileName = Utils.IsMacOS() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName;
var shFilePath = await FileUtils.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
if (shFilePath.Contains(' '))
{
shFilePath = shFilePath.AppendQuotes();

View file

@ -1,6 +1,3 @@
using System.Diagnostics;
using System.Text;
namespace ServiceLib.Manager;
/// <summary>
@ -11,8 +8,9 @@ public class CoreManager
private static readonly Lazy<CoreManager> _instance = new(() => new());
public static CoreManager Instance => _instance.Value;
private Config _config;
private Process? _process;
private Process? _processPre;
private WindowsJobService? _processJob;
private ProcessService? _processService;
private ProcessService? _processPreService;
private bool _linuxSudo = false;
private Func<bool, string, Task>? _updateFunc;
private const string _tag = "CoreHandler";
@ -29,7 +27,7 @@ public class CoreManager
var toPath = Utils.GetBinPath("");
if (fromPath != toPath)
{
FileManager.CopyDirectory(fromPath, toPath, true, false);
FileUtils.CopyDirectory(fromPath, toPath, true, false);
}
}
@ -89,43 +87,37 @@ public class CoreManager
await CoreStart(node);
await CoreStartPreService(node);
if (_process != null)
if (_processService != null)
{
await UpdateFunc(true, $"{node.GetSummary()}");
}
}
public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
public async Task<ProcessService?> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
{
var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? ECoreType.sing_box : ECoreType.Xray;
var coreType = selecteds.Any(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)) ? ECoreType.sing_box : ECoreType.Xray;
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
await UpdateFunc(false, result.Msg);
if (result.Success != true)
{
return -1;
return null;
}
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
await UpdateFunc(false, configPath);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null)
{
return -1;
}
return proc.Id;
return await RunProcess(coreInfo, fileName, true, false);
}
public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem)
public async Task<ProcessService?> LoadCoreConfigSpeedtest(ServerTestItem testItem)
{
var node = await AppManager.Instance.GetProfileItem(testItem.IndexId);
if (node is null)
{
return -1;
return null;
}
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
@ -133,18 +125,12 @@ public class CoreManager
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
if (result.Success != true)
{
return -1;
return null;
}
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null)
{
return -1;
}
return proc.Id;
return await RunProcess(coreInfo, fileName, true, false);
}
public async Task CoreStop()
@ -157,16 +143,18 @@ public class CoreManager
_linuxSudo = false;
}
if (_process != null)
if (_processService != null)
{
await ProcUtils.ProcessKill(_process, Utils.IsWindows());
_process = null;
await _processService.StopAsync();
_processService.Dispose();
_processService = null;
}
if (_processPre != null)
if (_processPreService != null)
{
await ProcUtils.ProcessKill(_processPre, Utils.IsWindows());
_processPre = null;
await _processPreService.StopAsync();
_processPreService.Dispose();
_processPreService = null;
}
}
catch (Exception ex)
@ -188,12 +176,12 @@ public class CoreManager
{
return;
}
_process = proc;
_processService = proc;
}
private async Task CoreStartPreService(ProfileItem node)
{
if (_process != null && !_process.HasExited)
if (_processService != null && !_processService.HasExited)
{
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
@ -210,7 +198,7 @@ public class CoreManager
{
return;
}
_processPre = proc;
_processPreService = proc;
}
}
}
@ -225,7 +213,7 @@ public class CoreManager
#region Process
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
private async Task<ProcessService?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
{
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
if (fileName.IsNullOrEmpty())
@ -256,55 +244,48 @@ public class CoreManager
}
}
private async Task<Process?> RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog)
private async Task<ProcessService?> RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog)
{
Process proc = new()
{
StartInfo = new()
{
FileName = fileName,
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardOutput = displayLog,
RedirectStandardError = displayLog,
CreateNoWindow = true,
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
}
};
var environmentVars = new Dictionary<string, string>();
foreach (var kv in coreInfo.Environment)
{
proc.StartInfo.Environment[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath);
environmentVars[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath);
}
if (displayLog)
{
void dataHandler(object sender, DataReceivedEventArgs e)
{
if (e.Data.IsNotEmpty())
{
_ = UpdateFunc(false, e.Data + Environment.NewLine);
}
}
proc.OutputDataReceived += dataHandler;
proc.ErrorDataReceived += dataHandler;
}
proc.Start();
var procService = new ProcessService(
fileName: fileName,
arguments: string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
workingDirectory: Utils.GetBinConfigPath(),
displayLog: displayLog,
redirectInput: false,
environmentVars: environmentVars,
updateFunc: _updateFunc
);
if (displayLog)
{
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
}
await procService.StartAsync();
await Task.Delay(100);
AppManager.Instance.AddProcess(proc.Handle);
if (proc is null or { HasExited: true })
if (procService is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
return proc;
AddProcessJob(procService.Handle);
return procService;
}
private void AddProcessJob(nint processHandle)
{
if (Utils.IsWindows())
{
_processJob ??= new();
try
{
_processJob?.AddProcess(processHandle);
}
catch { }
}
}
#endregion Process

View file

@ -11,7 +11,7 @@ public class NoticeManager
{
return;
}
AppEvents.SendSnackMsgRequested.OnNext(content);
AppEvents.SendSnackMsgRequested.Publish(content);
}
public void SendMessage(string? content)
@ -20,7 +20,7 @@ public class NoticeManager
{
return;
}
AppEvents.SendMsgViewRequested.OnNext(content);
AppEvents.SendMsgViewRequested.Publish(content);
}
public void SendMessageEx(string? content)

View file

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

View file

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

View file

@ -0,0 +1,320 @@
namespace ServiceLib.Manager;
public class ProfileGroupItemManager
{
private static readonly Lazy<ProfileGroupItemManager> _instance = new(() => new());
private ConcurrentDictionary<string, ProfileGroupItem> _items = new();
public static ProfileGroupItemManager Instance => _instance.Value;
private static readonly string _tag = "ProfileGroupItemManager";
private ProfileGroupItemManager()
{
}
public async Task Init()
{
await InitData();
}
// Read-only getters: do not create or mark dirty
public bool TryGet(string indexId, out ProfileGroupItem? item)
{
item = null;
if (string.IsNullOrWhiteSpace(indexId))
{
return false;
}
return _items.TryGetValue(indexId, out item);
}
public ProfileGroupItem? GetOrDefault(string indexId)
{
return string.IsNullOrWhiteSpace(indexId) ? null : (_items.TryGetValue(indexId, out var v) ? v : null);
}
private async Task InitData()
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem where IndexId not in ( select indexId from ProfileItem )");
var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
_items = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!));
}
private ProfileGroupItem AddProfileGroupItem(string indexId)
{
var profileGroupItem = new ProfileGroupItem()
{
IndexId = indexId,
ChildItems = string.Empty,
MultipleLoad = EMultipleLoad.LeastPing
};
_items[indexId] = profileGroupItem;
return profileGroupItem;
}
private ProfileGroupItem GetProfileGroupItem(string indexId)
{
if (string.IsNullOrEmpty(indexId))
{
indexId = Utils.GetGuid(false);
}
return _items.GetOrAdd(indexId, AddProfileGroupItem);
}
public async Task ClearAll()
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem ");
_items.Clear();
}
public async Task SaveTo()
{
try
{
var lstExists = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
var existsMap = lstExists.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!);
var lstInserts = new List<ProfileGroupItem>();
var lstUpdates = new List<ProfileGroupItem>();
foreach (var item in _items.Values)
{
if (string.IsNullOrEmpty(item.IndexId))
{
continue;
}
if (existsMap.ContainsKey(item.IndexId))
{
lstUpdates.Add(item);
}
else
{
lstInserts.Add(item);
}
}
try
{
if (lstInserts.Count > 0)
{
await SQLiteHelper.Instance.InsertAllAsync(lstInserts);
}
if (lstUpdates.Count > 0)
{
await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
public ProfileGroupItem GetOrCreateAndMarkDirty(string indexId)
{
return GetProfileGroupItem(indexId);
}
public async ValueTask DisposeAsync()
{
await SaveTo();
}
public async Task SaveItemAsync(ProfileGroupItem item)
{
if (item is null)
{
throw new ArgumentNullException(nameof(item));
}
if (string.IsNullOrWhiteSpace(item.IndexId))
{
throw new ArgumentException("IndexId required", nameof(item));
}
_items[item.IndexId] = item;
try
{
var lst = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().Where(t => t.IndexId == item.IndexId).ToListAsync();
if (lst != null && lst.Count > 0)
{
await SQLiteHelper.Instance.UpdateAllAsync(new List<ProfileGroupItem> { item });
}
else
{
await SQLiteHelper.Instance.InsertAllAsync(new List<ProfileGroupItem> { item });
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#region Helper
public static bool HasCycle(string? indexId)
{
return HasCycle(indexId, new HashSet<string>(), new HashSet<string>());
}
public static bool HasCycle(string? indexId, HashSet<string> visited, HashSet<string> stack)
{
if (indexId.IsNullOrEmpty())
{
return false;
}
if (stack.Contains(indexId))
{
return true;
}
if (visited.Contains(indexId))
{
return false;
}
visited.Add(indexId);
stack.Add(indexId);
try
{
Instance.TryGet(indexId, out var groupItem);
if (groupItem == null || groupItem.ChildItems.IsNullOrEmpty())
{
return false;
}
var childIds = Utils.String2List(groupItem.ChildItems)
.Where(p => !string.IsNullOrEmpty(p))
.ToList();
if (childIds == null)
{
return false;
}
foreach (var child in childIds)
{
if (HasCycle(child, visited, stack))
{
return true;
}
}
return false;
}
finally
{
stack.Remove(indexId);
}
}
public static async Task<(List<ProfileItem> Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId)
{
Instance.TryGet(indexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.NotHasChild())
{
return (new List<ProfileItem>(), profileGroupItem);
}
var items = await GetChildProfileItems(profileGroupItem);
var subItems = await GetSubChildProfileItems(profileGroupItem);
items.AddRange(subItems);
return (items, profileGroupItem);
}
public static async Task<List<ProfileItem>> GetChildProfileItems(ProfileGroupItem? group)
{
if (group == null || group.ChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(group.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
))
.Where(p =>
p != null &&
p.IsValid() &&
p.ConfigType != EConfigType.Custom
)
.ToList();
return childProfiles;
}
public static async Task<List<ProfileItem>> GetSubChildProfileItems(ProfileGroupItem? group)
{
if (group == null || group.SubChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = await AppManager.Instance.ProfileItems(group.SubChildItems);
return childProfiles.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(group.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, group.Filter))
)
.ToList();
}
public static async Task<HashSet<string>> GetAllChildDomainAddresses(string indexId)
{
// include grand children
var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
{
return childAddresses;
}
if (groupItem.SubChildItems.IsNotEmpty())
{
var subItems = await GetSubChildProfileItems(groupItem);
subItems.ForEach(p => childAddresses.Add(p.Address));
}
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
foreach (var childId in childIds)
{
var childNode = await AppManager.Instance.GetProfileItem(childId);
if (childNode == null)
{
continue;
}
if (!childNode.IsComplex())
{
childAddresses.Add(childNode.Address);
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
#endregion Helper
}

View file

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

View file

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

View file

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

View file

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

View file

@ -71,7 +71,6 @@ public class GUIItem
public bool DisplayRealTimeSpeed { get; set; } = true;
public bool KeepOlderDedupl { get; set; }
public int AutoUpdateInterval { get; set; }
public bool EnableSecurityProtocolTls13 { get; set; }
public int TrayMenuServersLimit { get; set; } = 20;
public bool EnableHWA { get; set; } = false;
public bool EnableLog { get; set; } = true;
@ -220,6 +219,8 @@ public class SystemProxyItem
public string SystemProxyExceptions { get; set; }
public bool NotProxyLocalAddress { get; set; } = true;
public string SystemProxyAdvancedProtocol { get; set; }
public string? CustomSystemProxyPacPath { get; set; }
public string? CustomSystemProxyScriptPath { get; set; }
}
[Serializable]
@ -264,8 +265,7 @@ public class SimpleDNSItem
public bool? BlockBindingQuery { get; set; }
public string? DirectDNS { get; set; }
public string? RemoteDNS { get; set; }
public string? SingboxOutboundsResolveDNS { get; set; }
public string? SingboxFinalResolveDNS { get; set; }
public string? BootstrapDNS { get; set; }
public string? RayStrategy4Freedom { get; set; }
public string? SingboxStrategy4Direct { get; set; }
public string? SingboxStrategy4Proxy { get; set; }

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,21 @@
namespace ServiceLib.Models;
[Serializable]
public class ProfileGroupItem
{
[PrimaryKey]
public string IndexId { 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 bool NotHasChild()
{
return string.IsNullOrWhiteSpace(ChildItems) && string.IsNullOrWhiteSpace(SubChildItems);
}
}

View file

@ -1,6 +1,3 @@
using ReactiveUI;
using SQLite;
namespace ServiceLib.Models;
[Serializable]
@ -31,19 +28,22 @@ public class ProfileItem : ReactiveObject
public string GetSummary()
{
var summary = $"[{(ConfigType).ToString()}] ";
var arrAddr = Address.Contains(':') ? Address.Split(':') : Address.Split('.');
var addr = arrAddr.Length switch
var summary = $"[{ConfigType.ToString()}] ";
if (IsComplex())
{
> 2 => $"{arrAddr.First()}***{arrAddr.Last()}",
> 1 => $"***{arrAddr.Last()}",
_ => Address
};
summary += ConfigType switch
summary += $"[{CoreType.ToString()}]{Remarks}";
}
else
{
EConfigType.Custom => $"[{CoreType.ToString()}]{Remarks}",
_ => $"{Remarks}({addr}:{Port})"
};
var arrAddr = Address.Contains(':') ? Address.Split(':') : Address.Split('.');
var addr = arrAddr.Length switch
{
> 2 => $"{arrAddr.First()}***{arrAddr.Last()}",
> 1 => $"***{arrAddr.Last()}",
_ => Address
};
summary += $"{Remarks}({addr}:{Port})";
}
return summary;
}
@ -61,6 +61,70 @@ public class ProfileItem : ReactiveObject
return Network.TrimEx();
}
public bool IsComplex()
{
return ConfigType.IsComplexType();
}
public bool IsValid()
{
if (IsComplex())
{
return true;
}
if (Address.IsNullOrEmpty() || Port is <= 0 or >= 65536)
{
return false;
}
switch (ConfigType)
{
case EConfigType.VMess:
if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id))
{
return false;
}
break;
case EConfigType.VLESS:
if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30))
{
return false;
}
if (!Global.Flows.Contains(Flow))
{
return false;
}
break;
case EConfigType.Shadowsocks:
if (Id.IsNullOrEmpty())
{
return false;
}
if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security))
{
return false;
}
break;
}
if ((ConfigType is EConfigType.VLESS or EConfigType.Trojan)
&& StreamSecurity == Global.StreamSecurityReality
&& PublicKey.IsNullOrEmpty())
{
return false;
}
return true;
}
#endregion function
[PrimaryKey]
@ -96,4 +160,5 @@ public class ProfileItem : ReactiveObject
public string Mldsa65Verify { get; set; }
public string Extra { get; set; }
public bool? MuxEnabled { get; set; }
public string Cert { get; set; }
}

View file

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

View file

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

View file

@ -15,4 +15,5 @@ public class RulesItem
public List<string>? Process { get; set; }
public bool Enabled { get; set; } = true;
public string? Remarks { get; set; }
public ERuleType? RuleType { get; set; }
}

View file

@ -7,4 +7,5 @@ public class RulesItemModel : RulesItem
public string Ips { get; set; }
public string Domains { get; set; }
public string Protocols { get; set; }
public string RuleTypeName { get; set; }
}

View file

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

View file

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

View file

@ -1,5 +1,3 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models;
public class SingboxConfig
@ -145,6 +143,7 @@ public class Outbound4Sbox : BaseServer4Sbox
public string? plugin_opts { get; set; }
public List<string>? outbounds { get; set; }
public bool? interrupt_exist_connections { get; set; }
public int? tolerance { get; set; }
}
public class Endpoints4Sbox : BaseServer4Sbox
@ -182,6 +181,7 @@ public class Tls4Sbox
public bool? fragment { get; set; }
public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; }
public List<string>? certificate { get; set; }
}
public class Multiplex4Sbox

View file

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

View file

@ -1,5 +1,3 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models;
public class V2rayConfig
@ -217,6 +215,7 @@ public class Dns4Ray
public class DnsServer4Ray
{
public string? address { get; set; }
public int? port { get; set; }
public List<string>? domains { get; set; }
public bool? skipFallback { get; set; }
public List<string>? expectedIPs { get; set; }
@ -355,6 +354,14 @@ public class TlsSettings4Ray
public string? shortId { get; set; }
public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; }
public List<CertificateSettings4Ray>? certificates { get; set; }
public bool? disableSystemRoot { get; set; }
}
public class CertificateSettings4Ray
{
public List<string>? certificate { get; set; }
public string? usage { get; set; }
}
public class TcpSettings4Ray

View file

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

View file

@ -87,6 +87,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Certificate not set 的本地化字符串。
/// </summary>
public static string CertNotSet {
get {
return ResourceManager.GetString("CertNotSet", resourceCulture);
}
}
/// <summary>
/// 查找类似 Certificate set 的本地化字符串。
/// </summary>
public static string CertSet {
get {
return ResourceManager.GetString("CertSet", resourceCulture);
}
}
/// <summary>
/// 查找类似 Please check the Configuration settings first. 的本地化字符串。
/// </summary>
@ -114,6 +132,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support network type &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string CoreNotSupportNetwork {
get {
return ResourceManager.GetString("CoreNotSupportNetwork", resourceCulture);
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support protocol &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string CoreNotSupportProtocol {
get {
return ResourceManager.GetString("CoreNotSupportProtocol", resourceCulture);
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support protocol &apos;{1}&apos; when using transport &apos;{2}&apos;. 的本地化字符串。
/// </summary>
public static string CoreNotSupportProtocolTransport {
get {
return ResourceManager.GetString("CoreNotSupportProtocolTransport", resourceCulture);
}
}
/// <summary>
/// 查找类似 Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. 的本地化字符串。
/// </summary>
@ -267,6 +312,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Group &apos;{0}&apos; is empty. Please add at least one node. 的本地化字符串。
/// </summary>
public static string GroupEmpty {
get {
return ResourceManager.GetString("GroupEmpty", resourceCulture);
}
}
/// <summary>
/// 查找类似 {0} Group cannot reference itself or have a circular reference 的本地化字符串。
/// </summary>
public static string GroupSelfReference {
get {
return ResourceManager.GetString("GroupSelfReference", resourceCulture);
}
}
/// <summary>
/// 查找类似 This is not the correct configuration, please check 的本地化字符串。
/// </summary>
@ -294,6 +357,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 The {0} property is invalid, please check. 的本地化字符串。
/// </summary>
public static string InvalidProperty {
get {
return ResourceManager.GetString("InvalidProperty", resourceCulture);
}
}
/// <summary>
/// 查找类似 Invalid address (URL) 的本地化字符串。
/// </summary>
@ -538,7 +610,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Speed (M/s) 的本地化字符串。
/// 查找类似 Speed (MB/s) 的本地化字符串。
/// </summary>
public static string LvTestSpeed {
get {
@ -672,6 +744,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Add Child Configuration 的本地化字符串。
/// </summary>
public static string menuAddChildServer {
get {
return ResourceManager.GetString("menuAddChildServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。
/// </summary>
@ -699,6 +780,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Add Policy Group Configuration 的本地化字符串。
/// </summary>
public static string menuAddPolicyGroupServer {
get {
return ResourceManager.GetString("menuAddPolicyGroupServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add Proxy Chain Configuration 的本地化字符串。
/// </summary>
public static string menuAddProxyChainServer {
get {
return ResourceManager.GetString("menuAddProxyChainServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。
/// </summary>
@ -942,6 +1041,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Test real delay 的本地化字符串。
/// </summary>
public static string menuFastRealPing {
get {
return ResourceManager.GetString("menuFastRealPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Full Config Template Setting 的本地化字符串。
/// </summary>
@ -951,6 +1059,78 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Generate Policy Group from Multiple Profiles 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServer {
get {
return ResourceManager.GetString("menuGenGroupMultipleServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration Fallback by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxFallback {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxLeastPing {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration Fallback by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayFallback {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastLoad {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastLoad", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastPing {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRandom {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRandom", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRoundRobin {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRoundRobin", resourceCulture);
}
}
/// <summary>
/// 查找类似 Global Hotkey Setting 的本地化字符串。
/// </summary>
@ -1311,6 +1491,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Remove Child Configuration 的本地化字符串。
/// </summary>
public static string menuRemoveChildServer {
get {
return ResourceManager.GetString("menuRemoveChildServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Remove duplicate Configurations 的本地化字符串。
/// </summary>
@ -1464,6 +1653,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Configuration List 的本地化字符串。
/// </summary>
public static string menuServerList {
get {
return ResourceManager.GetString("menuServerList", resourceCulture);
}
}
/// <summary>
/// 查找类似 Configurations 的本地化字符串。
/// </summary>
@ -1473,60 +1671,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Multi-Configuration to custom configuration 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServer {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerSingBoxLeastPing {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerSingBoxLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerXrayLeastLoad {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayLeastLoad", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerXrayLeastPing {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerXrayRandom {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayRandom", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerXrayRoundRobin {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayRoundRobin", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set as active Configuration (Enter) 的本地化字符串。
/// </summary>
@ -1932,6 +2076,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Node alias &apos;{0}&apos; does not exist. 的本地化字符串。
/// </summary>
public static string NodeTagNotExist {
get {
return ResourceManager.GetString("NodeTagNotExist", resourceCulture);
}
}
/// <summary>
/// 查找类似 Non-VMess or SS protocol 的本地化字符串。
/// </summary>
@ -1959,6 +2112,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Not support protocol &apos;{0}&apos;. 的本地化字符串。
/// </summary>
public static string NotSupportProtocol {
get {
return ResourceManager.GetString("NotSupportProtocol", resourceCulture);
}
}
/// <summary>
/// 查找类似 Scan completed, no valid QR code found 的本地化字符串。
/// </summary>
@ -1986,6 +2148,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Please Add At Least One Configuration 的本地化字符串。
/// </summary>
public static string PleaseAddAtLeastOneServer {
get {
return ResourceManager.GetString("PleaseAddAtLeastOneServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Please fill Remarks 的本地化字符串。
/// </summary>
@ -2031,6 +2202,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Policy group: 的本地化字符串。
/// </summary>
public static string PolicyGroupPrefix {
get {
return ResourceManager.GetString("PolicyGroupPrefix", resourceCulture);
}
}
/// <summary>
/// 查找类似 Proxy chained: 的本地化字符串。
/// </summary>
public static string ProxyChainedPrefix {
get {
return ResourceManager.GetString("ProxyChainedPrefix", resourceCulture);
}
}
/// <summary>
/// 查找类似 Global hotkey {0} registration failed, reason: {1} 的本地化字符串。
/// </summary>
@ -2094,6 +2283,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Routing rule outbound: 的本地化字符串。
/// </summary>
public static string RoutingRuleOutboundPrefix {
get {
return ResourceManager.GetString("RoutingRuleOutboundPrefix", resourceCulture);
}
}
/// <summary>
/// 查找类似 Run as Admin 的本地化字符串。
/// </summary>
@ -2112,6 +2310,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Please set a valid domain 的本地化字符串。
/// </summary>
public static string ServerNameMustBeValidDomain {
get {
return ResourceManager.GetString("ServerNameMustBeValidDomain", resourceCulture);
}
}
/// <summary>
/// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。
/// </summary>
@ -2329,7 +2536,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Prevent domain-based routing rules from failing 的本地化字符串。
/// 查找类似 Block ECH and HTTP/3 availability checks when enabled 的本地化字符串。
/// </summary>
public static string TbBlockSVCBHTTPSQueriesTips {
get {
@ -2337,6 +2544,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Bootstrap DNS 的本地化字符串。
/// </summary>
public static string TbBootstrapDNS {
get {
return ResourceManager.GetString("TbBootstrapDNS", resourceCulture);
}
}
/// <summary>
/// 查找类似 Resolve DNS server domains, requires IP 的本地化字符串。
/// </summary>
public static string TbBootstrapDNSTips {
get {
return ResourceManager.GetString("TbBootstrapDNSTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Browse 的本地化字符串。
/// </summary>
@ -2355,6 +2580,27 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Certificate Pinning 的本地化字符串。
/// </summary>
public static string TbCertPinning {
get {
return ResourceManager.GetString("TbCertPinning", resourceCulture);
}
}
/// <summary>
/// 查找类似 Server Certificate (PEM format, optional)
///When specified, the certificate will be pinned, and &quot;Allow Insecure&quot; will be disabled.
///
///The &quot;Get Certificate&quot; action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. 的本地化字符串。
/// </summary>
public static string TbCertPinningTips {
get {
return ResourceManager.GetString("TbCertPinningTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Clear system proxy 的本地化字符串。
/// </summary>
@ -2364,6 +2610,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Policy Group 的本地化字符串。
/// </summary>
public static string TbConfigTypePolicyGroup {
get {
return ResourceManager.GetString("TbConfigTypePolicyGroup", resourceCulture);
}
}
/// <summary>
/// 查找类似 Proxy Chain 的本地化字符串。
/// </summary>
public static string TbConfigTypeProxyChain {
get {
return ResourceManager.GetString("TbConfigTypeProxyChain", resourceCulture);
}
}
/// <summary>
/// 查找类似 Confirm 的本地化字符串。
/// </summary>
@ -2409,6 +2673,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 V2ray Custom DNS 的本地化字符串。
/// </summary>
public static string TbCustomDnsRay {
get {
return ResourceManager.GetString("TbCustomDnsRay", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Custom DNS 的本地化字符串。
/// </summary>
public static string TbCustomDnsSingbox {
get {
return ResourceManager.GetString("TbCustomDnsSingbox", resourceCulture);
}
}
/// <summary>
/// 查找类似 Display GUI 的本地化字符串。
/// </summary>
@ -2517,6 +2799,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Fallback 的本地化字符串。
/// </summary>
public static string TbFallback {
get {
return ResourceManager.GetString("TbFallback", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fetch Certificate 的本地化字符串。
/// </summary>
public static string TbFetchCert {
get {
return ResourceManager.GetString("TbFetchCert", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fetch Certificate Chain 的本地化字符串。
/// </summary>
public static string TbFetchCertChain {
get {
return ResourceManager.GetString("TbFetchCertChain", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fingerprint 的本地化字符串。
/// </summary>
@ -2634,6 +2943,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Most Stable 的本地化字符串。
/// </summary>
public static string TbLeastLoad {
get {
return ResourceManager.GetString("TbLeastLoad", resourceCulture);
}
}
/// <summary>
/// 查找类似 Lowest Latency 的本地化字符串。
/// </summary>
public static string TbLeastPing {
get {
return ResourceManager.GetString("TbLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Address (IPv4, IPv6) 的本地化字符串。
/// </summary>
@ -2688,6 +3015,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Auto add filtered configuration from subscription groups 的本地化字符串。
/// </summary>
public static string TbPolicyGroupSubChildTip {
get {
return ResourceManager.GetString("TbPolicyGroupSubChildTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 Policy Group Type 的本地化字符串。
/// </summary>
public static string TbPolicyGroupType {
get {
return ResourceManager.GetString("TbPolicyGroupType", resourceCulture);
}
}
/// <summary>
/// 查找类似 Port 的本地化字符串。
/// </summary>
@ -2760,6 +3105,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Random 的本地化字符串。
/// </summary>
public static string TbRandom {
get {
return ResourceManager.GetString("TbRandom", resourceCulture);
}
}
/// <summary>
/// 查找类似 v2ray Full Config Template 的本地化字符串。
/// </summary>
@ -2796,6 +3150,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Via proxy — please ensure remote availability 的本地化字符串。
/// </summary>
public static string TbRemoteDNSTips {
get {
return ResourceManager.GetString("TbRemoteDNSTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Camouflage domain(host) 的本地化字符串。
/// </summary>
@ -2823,6 +3186,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Round Robin 的本地化字符串。
/// </summary>
public static string TbRoundRobin {
get {
return ResourceManager.GetString("TbRoundRobin", resourceCulture);
}
}
/// <summary>
/// 查找类似 socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。
/// </summary>
@ -2904,6 +3276,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Rule Type 的本地化字符串。
/// </summary>
public static string TbRuleType {
get {
return ResourceManager.GetString("TbRuleType", resourceCulture);
}
}
/// <summary>
/// 查找类似 You can set separate rules for Routing and DNS, or select &quot;ALL&quot; to apply to both 的本地化字符串。
/// </summary>
public static string TbRuleTypeTips {
get {
return ResourceManager.GetString("TbRuleTypeTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。
/// </summary>
@ -2913,33 +3303,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 The sing-box DoH resolution server can be overwritten 的本地化字符串。
/// </summary>
public static string TbSBDoHOverride {
get {
return ResourceManager.GetString("TbSBDoHOverride", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box DoH Resolver Server 的本地化字符串。
/// </summary>
public static string TbSBDoHResolverServer {
get {
return ResourceManager.GetString("TbSBDoHResolverServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fallback DNS Resolution, Suggest IP 的本地化字符串。
/// </summary>
public static string TbSBFallbackDNSResolve {
get {
return ResourceManager.GetString("TbSBFallbackDNSResolve", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Full Config Template 的本地化字符串。
/// </summary>
@ -2958,24 +3321,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Resolve Outbound Domains 的本地化字符串。
/// </summary>
public static string TbSBOutboundDomainResolve {
get {
return ResourceManager.GetString("TbSBOutboundDomainResolve", resourceCulture);
}
}
/// <summary>
/// 查找类似 Outbound DNS Resolution (sing-box) 的本地化字符串。
/// </summary>
public static string TbSBOutboundsResolverDNS {
get {
return ResourceManager.GetString("TbSBOutboundsResolverDNS", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Remote Resolution Strategy 的本地化字符串。
/// </summary>
@ -3111,24 +3456,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 V2ray DNS settings 的本地化字符串。
/// </summary>
public static string TbSettingsCoreDns {
get {
return ResourceManager.GetString("TbSettingsCoreDns", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box DNS settings 的本地化字符串。
/// </summary>
public static string TbSettingsCoreDnsSingbox {
get {
return ResourceManager.GetString("TbSettingsCoreDnsSingbox", resourceCulture);
}
}
/// <summary>
/// 查找类似 Core: KCP settings 的本地化字符串。
/// </summary>
@ -3174,6 +3501,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Custom PAC file path 的本地化字符串。
/// </summary>
public static string TbSettingsCustomSystemProxyPacPath {
get {
return ResourceManager.GetString("TbSettingsCustomSystemProxyPacPath", resourceCulture);
}
}
/// <summary>
/// 查找类似 Custom system proxy script file path 的本地化字符串。
/// </summary>
public static string TbSettingsCustomSystemProxyScriptPath {
get {
return ResourceManager.GetString("TbSettingsCustomSystemProxyScriptPath", resourceCulture);
}
}
/// <summary>
/// 查找类似 Allow Insecure 的本地化字符串。
/// </summary>
@ -3408,6 +3753,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 If the system does not have a tray function, please do not enable it 的本地化字符串。
/// </summary>
public static string TbSettingsHide2TrayWhenCloseTip {
get {
return ResourceManager.GetString("TbSettingsHide2TrayWhenCloseTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 Hysteria Max bandwidth (Up/Down) 的本地化字符串。
/// </summary>
@ -3489,6 +3843,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 macOS displays this in the Dock (requires restart) 的本地化字符串。
/// </summary>
public static string TbSettingsMacOSShowInDock {
get {
return ResourceManager.GetString("TbSettingsMacOSShowInDock", resourceCulture);
}
}
/// <summary>
/// 查找类似 Main layout orientation (requires restart) 的本地化字符串。
/// </summary>
@ -3723,15 +4086,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Enable Security Protocol TLS v1.3 (subscription/update) 的本地化字符串。
/// </summary>
public static string TbSettingsTLS13 {
get {
return ResourceManager.GetString("TbSettingsTLS13", resourceCulture);
}
}
/// <summary>
/// 查找类似 Tray right-click menu Configurations display limit 的本地化字符串。
/// </summary>
@ -4005,9 +4359,9 @@ namespace ServiceLib.Resx {
/// <summary>
/// 查找类似 xray Freedom Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbXrayFreedomResolveStrategy {
public static string TbXrayFreedomStrategy {
get {
return ResourceManager.GetString("TbXrayFreedomResolveStrategy", resourceCulture);
return ResourceManager.GetString("TbXrayFreedomStrategy", resourceCulture);
}
}

View file

@ -672,8 +672,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>هسته: تنظیمات اولیه</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>تنظیمات V2ray DNS</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>V2ray Custom DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>هسته: تنظیمات KCP</value>
@ -744,9 +744,6 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>تنظیمات پراکسی سیستم</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>فعال کردن پروتکل امنیتی TLS نسخه 1.3 (اشتراک/به‌روزرسانی)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>محدودیت نمایش سرورهای منوی سینی کلیک راست</value>
</data>
@ -889,7 +886,7 @@
<value>تاخیر (میلی‌ثانیه)</value>
</data>
<data name="LvTestSpeed" xml:space="preserve">
<value>سرعت (M/s)</value>
<value>سرعت (MB/s)</value>
</data>
<data name="FailedToRunCore" xml:space="preserve">
<value>Core اجرا نشد، لطفاً گزارش را ببینید</value>
@ -1008,8 +1005,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>تنظیمات DNS</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>تنظیمات DNS sing-box</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box Custom DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>لطفا ساختار DNS را پر کنید، برای مشاهده سند کلیک کنید</value>
@ -1374,22 +1371,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>مخفی و پورت می شود، با کاما (،) جدا می شود</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>چند سرور به پیکربندی سفارشی</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>چند سرور تصادفی توسط Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>چند سرور RoundRobin توسط Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>چند سرور LeastPing توسط Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>چند سرور LeastLoad توسط Xray</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>LeastPing چند سرور توسط sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1416,19 +1413,10 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>Domestic DNS</value>
</data>
<data name="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>Outbound DNS Resolution (sing-box)</value>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>Via proxy — please ensure remote availability</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>Resolve Outbound Domains</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH Resolver Server</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>Fallback DNS Resolution, Suggest IP</value>
</data>
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray Freedom Resolution Strategy</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
@ -1440,9 +1428,6 @@
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
</data>
<data name="TbSBDoHOverride" xml:space="preserve">
<value>The sing-box DoH resolution server can be overwritten</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
@ -1471,7 +1456,7 @@
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Prevent domain-based routing rules from failing</value>
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Please fill in the correct config template</value>
@ -1512,4 +1497,139 @@
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>The {0} property is invalid, please check.</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} Group cannot reference itself or have a circular reference</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>Not support protocol '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>If the system does not have a tray function, please do not enable it</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
</root>

File diff suppressed because it is too large Load diff

View file

@ -672,8 +672,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>Core: alapbeállítások</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>V2ray DNS beállítások</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>V2ray Custom DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Core: KCP beállítások</value>
@ -744,9 +744,6 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>Rendszerproxy beállítások</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>Biztonsági protokoll TLS v1.3 engedélyezése (előfizetés/frissítés)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>Tálca jobb egérgombos menü konfigurációk megjelenítési limitje</value>
</data>
@ -889,7 +886,7 @@
<value>Késleltetés (ms)</value>
</data>
<data name="LvTestSpeed" xml:space="preserve">
<value>Sebesség (M/s)</value>
<value>Sebesség (MB/s)</value>
</data>
<data name="FailedToRunCore" xml:space="preserve">
<value>Nem sikerült futtatni a Core-t, kérjük, ellenőrizze a prompt információt</value>
@ -1008,8 +1005,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>DNS beállítások</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>sing-box DNS beállítások</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box Custom DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>Kérjük, töltse ki a DNS struktúrát, kattintson a dokumentum megtekintéséhez</value>
@ -1374,22 +1371,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>A portot lefedi, vesszővel (,) elválasztva</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>Több konfiguráció egyéni konfigurációra</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Több konfiguráció véletlenszerűen Xray szerint</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Több konfiguráció RoundRobin Xray szerint</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Több konfiguráció legkisebb pinggel Xray szerint</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Több konfiguráció legkisebb terheléssel Xray szerint</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Több konfiguráció legkisebb pinggel sing-box szerint</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1416,19 +1413,10 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>Domestic DNS</value>
</data>
<data name="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>Outbound DNS Resolution (sing-box)</value>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>Via proxy — please ensure remote availability</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>Resolve Outbound Domains</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH Resolver Server</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>Fallback DNS Resolution, Suggest IP</value>
</data>
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray Freedom Resolution Strategy</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
@ -1440,9 +1428,6 @@
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
</data>
<data name="TbSBDoHOverride" xml:space="preserve">
<value>The sing-box DoH resolution server can be overwritten</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
@ -1471,7 +1456,7 @@
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Prevent domain-based routing rules from failing</value>
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Please fill in the correct config template</value>
@ -1512,4 +1497,139 @@
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>The {0} property is invalid, please check.</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} Group cannot reference itself or have a circular reference</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>Not support protocol '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>If the system does not have a tray function, please do not enable it</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
</root>

View file

@ -672,8 +672,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>Core: basic settings</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>V2ray DNS settings</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>V2ray Custom DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Core: KCP settings</value>
@ -744,9 +744,6 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>System proxy settings</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>Enable Security Protocol TLS v1.3 (subscription/update)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>Tray right-click menu Configurations display limit</value>
</data>
@ -889,7 +886,7 @@
<value>Delay (ms)</value>
</data>
<data name="LvTestSpeed" xml:space="preserve">
<value>Speed (M/s)</value>
<value>Speed (MB/s)</value>
</data>
<data name="FailedToRunCore" xml:space="preserve">
<value>Failed to run Core, please check the prompt information</value>
@ -1008,8 +1005,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>DNS Settings</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>sing-box DNS settings</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box Custom DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>Please fill in DNS Structure, Click to view the document</value>
@ -1374,22 +1371,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>Multi-Configuration to custom configuration</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Multi-Configuration Random by Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Multi-Configuration RoundRobin by Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Multi-Configuration LeastLoad by Xray</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1416,19 +1413,10 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>Domestic DNS</value>
</data>
<data name="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>Outbound DNS Resolution (sing-box)</value>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>Via proxy — please ensure remote availability</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>Resolve Outbound Domains</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH Resolver Server</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>Fallback DNS Resolution, Suggest IP</value>
</data>
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray Freedom Resolution Strategy</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
@ -1440,9 +1428,6 @@
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
</data>
<data name="TbSBDoHOverride" xml:space="preserve">
<value>The sing-box DoH resolution server can be overwritten</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
@ -1471,7 +1456,7 @@
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Prevent domain-based routing rules from failing</value>
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Please fill in the correct config template</value>
@ -1512,4 +1497,139 @@
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>The {0} property is invalid, please check.</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} Group cannot reference itself or have a circular reference</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>Not support protocol '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>If the system does not have a tray function, please do not enable it</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
</root>

View file

@ -672,8 +672,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>Ядро: базовые настройки</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>Настройки DNS V2ray</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>V2ray Custom DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Ядро: настройки KCP</value>
@ -744,9 +744,6 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>Настройки системного прокси</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>Включить протокол безопасности TLS v1.3 (обновление подписки)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>Лимит серверов в меню трея</value>
</data>
@ -1008,8 +1005,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>Настройки DNS</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>Настройки DNS sing-box</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box Custom DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>Заполните структуру DNS, нажмите, чтобы открыть документ</value>
@ -1374,22 +1371,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>Заменит указанный порт, перечисляйте через запятую (,)</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>От мультиконфигурации к пользовательской конфигурации</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Случайный (Xray)</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Круговой (Xray)</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (Xray)</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Минимальная нагрузка (Xray)</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (sing-box)</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1416,19 +1413,10 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>Внутренний DNS</value>
</data>
<data name="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>Резолвер DNS для исходящих (sing-box)</value>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>Via proxy — please ensure remote availability</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>Разрешать домены для исходящих соединений</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>Сервер DoH-резолвера (sing-box)</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>Резервное DNS-разрешение (рекомендуется указывать IP)</value>
</data>
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>Стратегия резолвинга Freedom (Xray)</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
@ -1440,9 +1428,6 @@
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Добавить стандартные записи hosts (DNS)</value>
</data>
<data name="TbSBDoHOverride" xml:space="preserve">
<value>Сервер DoH-резолвера sing-box можно переопределить</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
@ -1471,7 +1456,7 @@
<value>Включён пользовательский DNS — настройки на этой странице не применяются</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Предотвращает сбои доменных правил маршрутизации</value>
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Пожалуйста, заполните корректный шаблон конфигурации</value>
@ -1512,4 +1497,139 @@
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>The {0} property is invalid, please check.</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} Group cannot reference itself or have a circular reference</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>Not support protocol '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>If the system does not have a tray function, please do not enable it</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Custom system proxy script file path</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
</root>

View file

@ -121,7 +121,7 @@
<value>导出分享链接至剪贴板成功</value>
</data>
<data name="CheckServerSettings" xml:space="preserve">
<value>请先检查配置文件设置</value>
<value>请先检查设置</value>
</data>
<data name="ConfigurationFormatIncorrect" xml:space="preserve">
<value>配置格式不正确</value>
@ -133,7 +133,7 @@
<value>下载开始...</value>
</data>
<data name="FailedConversionConfiguration" xml:space="preserve">
<value>转换配置文件失败</value>
<value>转换配置失败</value>
</data>
<data name="FailedGenDefaultConfiguration" xml:space="preserve">
<value>生成默认配置文件失败</value>
@ -142,10 +142,10 @@
<value>获取默认配置失败</value>
</data>
<data name="FailedImportedCustomServer" xml:space="preserve">
<value>导入自定义配置文件失败</value>
<value>导入自定义配置失败</value>
</data>
<data name="FailedReadConfiguration" xml:space="preserve">
<value>读取配置文件失败</value>
<value>读取配置失败</value>
</data>
<data name="FillCorrectServerPort" xml:space="preserve">
<value>请填写正确格式的端口</value>
@ -265,13 +265,13 @@
<value>请选择协议</value>
</data>
<data name="PleaseSelectServer" xml:space="preserve">
<value>请先选择配置文件</value>
<value>请先选择配置</value>
</data>
<data name="RemoveDuplicateServerResult" xml:space="preserve">
<value>配置文件去重完成。原数量: {0},现数量: {1}。</value>
<value>配置去重完成。原数量: {0},现数量: {1}。</value>
</data>
<data name="RemoveServer" xml:space="preserve">
<value>是否确定移除配置文件</value>
<value>是否确定移除?</value>
</data>
<data name="SaveClientConfigurationIn" xml:space="preserve">
<value>客户端配置文件保存在:{0}</value>
@ -283,10 +283,10 @@
<value>配置成功。 {0}</value>
</data>
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
<value>成功导入自定义配置文件</value>
<value>成功导入自定义配置</value>
</data>
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
<value>成功从剪贴板导入 {0} 个配置文件</value>
<value>成功从剪贴板导入 {0} 个配置</value>
</data>
<data name="SuccessfullyImportedServerViaScan" xml:space="preserve">
<value>扫描导入分享链接成功</value>
@ -385,7 +385,7 @@
<value>所有</value>
</data>
<data name="FillServerAddressCustom" xml:space="preserve">
<value>请浏览导入配置文件配置</value>
<value>请浏览导入配置</value>
</data>
<data name="Speedtesting" xml:space="preserve">
<value>测试中...</value>
@ -397,7 +397,7 @@
<value>本地</value>
</data>
<data name="MsgServerTitle" xml:space="preserve">
<value>配置文件过滤器,按回车执行</value>
<value>过滤器,按回车执行</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve">
<value>检查更新</value>
@ -424,7 +424,7 @@
<value>路由设置</value>
</data>
<data name="menuServers" xml:space="preserve">
<value>配置文件</value>
<value>配置</value>
</data>
<data name="menuSetting" xml:space="preserve">
<value>设置</value>
@ -475,55 +475,55 @@
<value>扫描屏幕上的二维码 (Ctrl+S)</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>克隆所选配置文件</value>
<value>克隆所选</value>
</data>
<data name="menuRemoveDuplicateServer" xml:space="preserve">
<value>移除重复的配置文件</value>
<value>移除重复</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>移除所选配置文件 (多选) (Delete)</value>
<value>移除所选 (多选) (Delete)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>设为活动配置文件 (Enter)</value>
<value>设为活动 (Enter)</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>清除所有服务统计数据</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>测试配置文件真连接延迟 (多选) (Ctrl+R)</value>
<value>测试真连接延迟 (多选) (Ctrl+R)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>按测试结果排序</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>测试配置文件速度 (多选) (Ctrl+T)</value>
<value>测试速度 (多选) (Ctrl+T)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>测试配置文件延迟 Tcping (多选) (Ctrl+O)</value>
<value>测试延迟 Tcping (多选) (Ctrl+O)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>导出所选配置文件完整配置</value>
<value>导出所选完整配置</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>导出分享链接至剪贴板 (多选) (Ctrl+C)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>添加自定义配置文件</value>
<value>添加自定义配置</value>
</data>
<data name="menuAddShadowsocksServer" xml:space="preserve">
<value>添加 [Shadowsocks] 配置文件</value>
<value>添加 [Shadowsocks]</value>
</data>
<data name="menuAddSocksServer" xml:space="preserve">
<value>添加 [SOCKS] 配置文件</value>
<value>添加 [SOCKS] </value>
</data>
<data name="menuAddTrojanServer" xml:space="preserve">
<value>添加 [Trojan] 配置文件</value>
<value>添加 [Trojan] </value>
</data>
<data name="menuAddVlessServer" xml:space="preserve">
<value>添加 [VLESS] 配置文件</value>
<value>添加 [VLESS] </value>
</data>
<data name="menuAddVmessServer" xml:space="preserve">
<value>添加 [VMess] 配置文件</value>
<value>添加 [VMess] </value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>全选 (Ctrl+A)</value>
@ -672,8 +672,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>Core: 基础设置</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>v2ray DNS 设置</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>v2ray 自定义 DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Core: KCP 设置</value>
@ -688,7 +688,7 @@
<value>Outbound Freedom domainStrategy</value>
</data>
<data name="TbSettingsEnableAutoAdjustMainLvColWidth" xml:space="preserve">
<value>自动调整配置文件列宽在更新订阅后</value>
<value>自动调整配置列宽在更新订阅后</value>
</data>
<data name="TbSettingsEnableCheckPreReleaseUpdate" xml:space="preserve">
<value>检查 Pre-Release 更新 (请谨慎启用)</value>
@ -697,7 +697,7 @@
<value>例外</value>
</data>
<data name="TbSettingsExceptionTip" xml:space="preserve">
<value>例外:对于下列字符开头的地址,不使用代理配置文件。使用分号 (;) 分隔。</value>
<value>例外:对于下列字符开头的地址,不使用代理配置。使用分号 (;) 分隔。</value>
</data>
<data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve">
<value>显示实时速度 (需重启)</value>
@ -744,11 +744,8 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>系统代理设置</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>启用安全协议 TLS v1.3 (订阅/检查更新)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>托盘右键菜单配置文件展示数量限制</value>
<value>托盘右键菜单配置展示数量限制</value>
</data>
<data name="TbSettingsUdpEnabled" xml:space="preserve">
<value>开启 UDP</value>
@ -781,7 +778,7 @@
<value>Pac 模式</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>分享配置文件 (Ctrl+F)</value>
<value>分享 (Ctrl+F)</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>路由</value>
@ -889,7 +886,7 @@
<value>延迟 (ms)</value>
</data>
<data name="LvTestSpeed" xml:space="preserve">
<value>速度 (M/s)</value>
<value>速度 (MB/s)</value>
</data>
<data name="FailedToRunCore" xml:space="preserve">
<value>运行 Core 失败,请查看提示信息</value>
@ -913,7 +910,7 @@
<value>移至订阅分组</value>
</data>
<data name="TbSettingsEnableDragDropSort" xml:space="preserve">
<value>启用配置文件拖放排序 (需重启)</value>
<value>启用配置拖放排序 (需重启)</value>
</data>
<data name="TbAutoRefresh" xml:space="preserve">
<value>自动刷新</value>
@ -922,10 +919,10 @@
<value>跳过测试</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>编辑配置文件 (Ctrl+D)</value>
<value>编辑 (Ctrl+D)</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>主界面双击设为活动配置文件</value>
<value>主界面双击设为活动</value>
</data>
<data name="SpeedtestingCompleted" xml:space="preserve">
<value>测试完成</value>
@ -1005,8 +1002,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>DNS 设置</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>sing-box DNS 设置</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box 自定义 DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>请填写 DNS JSON 结构,点击查看文档</value>
@ -1030,7 +1027,7 @@
<value>Domain</value>
</data>
<data name="menuAddHysteria2Server" xml:space="preserve">
<value>添加 [Hysteria2] 配置文件</value>
<value>添加 [Hysteria2] </value>
</data>
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
<value>Hysteria 最大带宽 (Up/Dw)</value>
@ -1039,19 +1036,19 @@
<value>使用系统 hosts</value>
</data>
<data name="menuAddTuicServer" xml:space="preserve">
<value>添加 [TUIC] 配置文件</value>
<value>添加 [TUIC] </value>
</data>
<data name="TbHeaderType8" xml:space="preserve">
<value>拥塞控制算法</value>
</data>
<data name="LvPrevProfile" xml:space="preserve">
<value>前置代理配置文件别名</value>
<value>前置代理配置别名</value>
</data>
<data name="LvNextProfile" xml:space="preserve">
<value>落地代理配置文件別名</value>
<value>落地代理配置別名</value>
</data>
<data name="LvPrevProfileTip" xml:space="preserve">
<value>请确保配置文件别名存在并唯一</value>
<value>请确保配置别名存在并唯一</value>
</data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>自动路由</value>
@ -1072,7 +1069,7 @@
<value>启用 IPv6</value>
</data>
<data name="menuAddWireguardServer" xml:space="preserve">
<value>添加 [WireGuard] 配置文件</value>
<value>添加 [WireGuard] </value>
</data>
<data name="TbPrivateKey" xml:space="preserve">
<value>PrivateKey</value>
@ -1105,7 +1102,7 @@
<value>*grpc Authority</value>
</data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>添加 [HTTP] 配置文件</value>
<value>添加 [HTTP] </value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>和分组前置代理冲突</value>
@ -1195,13 +1192,13 @@
<value>延迟测试</value>
</data>
<data name="menuProxiesDelaytestPart" xml:space="preserve">
<value>当前部分节点延迟测试</value>
<value>当前部分延迟测试</value>
</data>
<data name="menuProxiesReload" xml:space="preserve">
<value>刷新</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>设为活动节点 (Enter)</value>
<value>设为活动 (Enter)</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Outbound 默认解析策略</value>
@ -1219,7 +1216,7 @@
<value>导出分享链接至剪贴板 (多选) Base64 编码</value>
</data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>导出所选配置文件完整配置至剪贴板</value>
<value>导出所选完整配置至剪贴板</value>
</data>
<data name="menuShowOrHideMainWindow" xml:space="preserve">
<value>显示或隐藏主界面</value>
@ -1336,7 +1333,7 @@
<value>多线程测试时的并发数量</value>
</data>
<data name="TbSettingsExceptionTip2" xml:space="preserve">
<value>例外:对于下列地址不使用代理配置文件。使用逗号 (,) 分隔。</value>
<value>例外:对于下列地址不使用代理配置。使用逗号 (,) 分隔。</value>
</data>
<data name="TbSettingsDestOverride" xml:space="preserve">
<value>流量探测类型</value>
@ -1371,32 +1368,32 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>会覆盖端口,多组时用逗号 (,) 隔开</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>多配置文件产生自定义配置 (多选)</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多选生成策略组</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<value>多配置文件随机 Xray</value>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多随机 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多配置文件负载均衡 Xray</value>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多负载均衡 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>多配置文件最低延迟 Xray</value>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多最低延迟 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多配置文件最稳定 Xray</value>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多最稳定 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多配置文件最低延迟 sing-box</value>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多最低延迟 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>导出配置文件</value>
<value>导出</value>
</data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>当前连接信息测试地址</value>
</data>
<data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>可以填写配置文件别名,请确保存在并唯一</value>
<value>可以填写配置别名,请确保存在并唯一</value>
</data>
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>密码错误,请重试。</value>
@ -1405,7 +1402,7 @@
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>添加 [Anytls] 配置文件</value>
<value>添加 [Anytls] </value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>远程 DNS</value>
@ -1413,19 +1410,10 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>直连 DNS</value>
</data>
<data name="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>出站 DNS 解析sing-box</value>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>通过代理,请确保远程可用</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>解析出站域名</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH 解析服务器</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>兜底解析其他 DNS 域名,建议设为 ip</value>
</data>
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray freedom 解析策略</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
@ -1437,9 +1425,6 @@
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>添加常用 DNS Hosts</value>
</data>
<data name="TbSBDoHOverride" xml:space="preserve">
<value>开启后可覆盖 sing-box DoH 解析服务器</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
@ -1468,7 +1453,7 @@
<value>自定义 DNS 已启用,此页面配置将无效</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>避免域名分流规则失效</value>
<value>开启后将阻止 ECH 和 HTTP/3 可用性查询</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>请填写正确的配置模板</value>
@ -1504,9 +1489,144 @@
<value>开始解析和处理订阅内容</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>选择配置文件</value>
<value>选择配置</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>请至少添加一个配置</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>策略组</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>链式代理</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>最低延迟</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>随机</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>负载均衡</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>最稳定</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>策略组类型</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>添加策略组</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>添加链式代理</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>添加子配置</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>删除子配置</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>子配置项</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>故障转移</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>多选故障转移 sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多选故障转移 Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支持网络类型 '{1}'。</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>核心 '{0}' 在使用传输方式 '{2}' 时不支持协议 '{1}'。</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>核心 '{0}' 不支持协议 '{1}'。</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>代理链: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>路由规则出站: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>策略组: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>别名 '{0}' 不存在。</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>组“{0}”为空。请至少添加一个配置。</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>{0}属性无效,请检查</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} 分组不能引用自身或循环引用</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>不支持协议 '{0}'。</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>如果系统没有托盘功能,请不要开启</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>规则类型</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>可对 Routing 和 DNS 单独设定规则ALL 则都生效</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>解析 DNS 服务器域名,需指定为 IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>一键测试真连接延迟</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>自动从订阅分组添加过滤后的配置</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>固定证书</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>服务器证书PEM 格式,可选)
当指定此证书后,将固定该证书,并禁用“跳过证书验证”选项。
“获取证书”操作可能失败,原因可能是使用了自签证书,或系统中存在不受信任或恶意的 CA。</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>获取证书</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>获取证书链</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>请设置有效的域名</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>证书未设置</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>证书已设置</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>自定义 PAC 文件路径</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>自定义系统代理脚本文件路径</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS 在 Dock 栏中显示 (需重启)</value>
</data>
</root>

View file

@ -121,7 +121,7 @@
<value>匯出分享連結至剪貼簿成功</value>
</data>
<data name="CheckServerSettings" xml:space="preserve">
<value>請先檢查設定檔設定</value>
<value>請先檢查設定</value>
</data>
<data name="ConfigurationFormatIncorrect" xml:space="preserve">
<value>設定格式不正確</value>
@ -133,7 +133,7 @@
<value>下載開始...</value>
</data>
<data name="FailedConversionConfiguration" xml:space="preserve">
<value>轉換設定失敗</value>
<value>轉換設定失敗</value>
</data>
<data name="FailedGenDefaultConfiguration" xml:space="preserve">
<value>生成預設設定檔失敗</value>
@ -142,10 +142,10 @@
<value>獲取預設設定失敗</value>
</data>
<data name="FailedImportedCustomServer" xml:space="preserve">
<value>匯入自訂設定設定檔失敗</value>
<value>匯入自訂設定失敗</value>
</data>
<data name="FailedReadConfiguration" xml:space="preserve">
<value>讀取設定失敗</value>
<value>讀取設定失敗</value>
</data>
<data name="FillCorrectServerPort" xml:space="preserve">
<value>請填寫正確格式的埠</value>
@ -265,13 +265,13 @@
<value>請選擇協定</value>
</data>
<data name="PleaseSelectServer" xml:space="preserve">
<value>請先選擇設定</value>
<value>請先選擇設定</value>
</data>
<data name="RemoveDuplicateServerResult" xml:space="preserve">
<value>設定檔去重完成。原數量: {0},現數量: {1}。</value>
<value>去重完成。原數量: {0},現數量: {1}。</value>
</data>
<data name="RemoveServer" xml:space="preserve">
<value>是否確定移除設定檔</value>
<value>是否確定移除?</value>
</data>
<data name="SaveClientConfigurationIn" xml:space="preserve">
<value>用戶端設定檔儲存在:{0}</value>
@ -283,10 +283,10 @@
<value>設定成功。{0}</value>
</data>
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
<value>成功匯入自訂設定設定檔</value>
<value>成功匯入自訂節點</value>
</data>
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
<value>成功從剪貼簿匯入 {0} 個設定檔</value>
<value>成功從剪貼簿匯入 {0} 個節點</value>
</data>
<data name="SuccessfullyImportedServerViaScan" xml:space="preserve">
<value>掃描匯入分享連結成功</value>
@ -385,7 +385,7 @@
<value>所有</value>
</data>
<data name="FillServerAddressCustom" xml:space="preserve">
<value>請瀏覽匯入設定檔設定</value>
<value>請瀏覽匯入設定</value>
</data>
<data name="Speedtesting" xml:space="preserve">
<value>測試中...</value>
@ -397,7 +397,7 @@
<value>本機</value>
</data>
<data name="MsgServerTitle" xml:space="preserve">
<value>設定檔過濾,按 Enter 執行</value>
<value>過濾,按 Enter 執行</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve">
<value>檢查更新</value>
@ -475,55 +475,55 @@
<value>掃描螢幕上的二維碼 (Ctrl+S)</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>複製所選設定檔</value>
<value>複製所選</value>
</data>
<data name="menuRemoveDuplicateServer" xml:space="preserve">
<value>移除重複的設定檔</value>
<value>移除重複</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>移除所選設定檔 (多選) (Delete)</value>
<value>移除所選 (多選) (Delete)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>設為活動設定檔 (Enter)</value>
<value>設為活動 (Enter)</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>清除所有服務統計資料</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>測試設定檔真連線延遲 (多選) (Ctrl+R)</value>
<value>測試真連線延遲 (多選) (Ctrl+R)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>按測試結果排序</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>測試設定檔速度 (多選) (Ctrl+T)</value>
<value>測試速度 (多選) (Ctrl+T)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>測試設定檔延遲 Tcping (多選) (Ctrl+O)</value>
<value>測試延遲 Tcping (多選) (Ctrl+O)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>匯出所選設定檔完整設定</value>
<value>匯出所選完整設定</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>匯出分享連結至剪貼簿 (多選) (Ctrl+C)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>新增自訂設定設定檔</value>
<value>新增自訂節點</value>
</data>
<data name="menuAddShadowsocksServer" xml:space="preserve">
<value>新增 [Shadowsocks] 設定檔</value>
<value>新增 [Shadowsocks] 節點</value>
</data>
<data name="menuAddSocksServer" xml:space="preserve">
<value>新增 [SOCKS] 設定檔</value>
<value>新增 [SOCKS] 節點</value>
</data>
<data name="menuAddTrojanServer" xml:space="preserve">
<value>新增 [Trojan] 設定檔</value>
<value>新增 [Trojan] 節點</value>
</data>
<data name="menuAddVlessServer" xml:space="preserve">
<value>新增 [VLESS] 設定檔</value>
<value>新增 [VLESS] 節點</value>
</data>
<data name="menuAddVmessServer" xml:space="preserve">
<value>新增 [VMess] 設定檔</value>
<value>新增 [VMess] 節點</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>全選 (Ctrl+A)</value>
@ -672,8 +672,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>Core: 基礎設定</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>V2ray DNS 設定</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>v2ray 自訂 DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Core: KCP 設定</value>
@ -688,7 +688,7 @@
<value>Outbound Freedom domainStrategy</value>
</data>
<data name="TbSettingsEnableAutoAdjustMainLvColWidth" xml:space="preserve">
<value>在更新訂閱後自動調整設定檔列寬</value>
<value>在更新訂閱後自動調整列寬</value>
</data>
<data name="TbSettingsEnableCheckPreReleaseUpdate" xml:space="preserve">
<value>檢查 Pre-Release 更新 (請謹慎啟用)</value>
@ -697,7 +697,7 @@
<value>例外</value>
</data>
<data name="TbSettingsExceptionTip" xml:space="preserve">
<value>例外:對於下列字元開頭的位址,不使用代理設定檔。使用分號 (;) 分隔。</value>
<value>例外:對於下列字元開頭的位址,不使用代理。使用分號 (;) 分隔。</value>
</data>
<data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve">
<value>顯示即時速度(需重啟)</value>
@ -744,11 +744,8 @@
<data name="TbSettingsSystemproxy" xml:space="preserve">
<value>系統代理設定</value>
</data>
<data name="TbSettingsTLS13" xml:space="preserve">
<value>啟用安全協定 TLS v1.3 (訂閱/檢查更新)</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>工具列右鍵選單設定展示數量限制</value>
<value>工具列右鍵選單設定展示數量限制</value>
</data>
<data name="TbSettingsUdpEnabled" xml:space="preserve">
<value>開啟 UDP</value>
@ -781,7 +778,7 @@
<value>PAC 模式</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>分享設定檔 (Ctrl+F)</value>
<value>分享 (Ctrl+F)</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>路由</value>
@ -883,13 +880,13 @@
<value>請勿將代理伺服器用於本機Intranet位址</value>
</data>
<data name="menuMixedTestServer" xml:space="preserve">
<value>一鍵多執行緒測試延遲和速度 (Ctrl+E)</value>
<value>一鍵延遲與速度測試 (Ctrl+E)</value>
</data>
<data name="LvTestDelay" xml:space="preserve">
<value>延遲 (ms)</value>
</data>
<data name="LvTestSpeed" xml:space="preserve">
<value>速度 (M/s)</value>
<value>速度 (MB/s)</value>
</data>
<data name="FailedToRunCore" xml:space="preserve">
<value>執行 Core 失敗,請查看提示訊息</value>
@ -913,7 +910,7 @@
<value>移至訂閱分組</value>
</data>
<data name="TbSettingsEnableDragDropSort" xml:space="preserve">
<value>啟動設定檔拖放排序 (需重啟)</value>
<value>啟拖放排序 (需重啟)</value>
</data>
<data name="TbAutoRefresh" xml:space="preserve">
<value>自動重新整理</value>
@ -922,10 +919,10 @@
<value>跳過測試</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>編輯設定檔 (Ctrl+D)</value>
<value>編輯 (Ctrl+D)</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>主介面輕按兩下設為活動設定檔</value>
<value>主介面輕按兩下設為活動</value>
</data>
<data name="SpeedtestingCompleted" xml:space="preserve">
<value>測試完成</value>
@ -943,7 +940,7 @@
<value>目前字型 (需重啟)</value>
</data>
<data name="TbSettingsCurrentFontFamilyTip" xml:space="preserve">
<value>複製字型 TTF/TTC 檔案到目錄 guiFonts啟設定</value>
<value>複製字型 TTF/TTC 檔案到目錄 guiFonts新啟動後生效</value>
</data>
<data name="TbSettingsSocksPortTip" xml:space="preserve">
<value>Pac 連接埠 = +3Xray API 連接埠 = +4mihomo API 連接埠 = +5</value>
@ -1003,10 +1000,10 @@
<value>不需要轉換時請留空</value>
</data>
<data name="menuDNSSetting" xml:space="preserve">
<value>DNS 設定</value>
<value>DNS設定</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>sing-box DNS 設定</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box 自訂 DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>請填寫 DNS JSON 結構,點擊查看檔案</value>
@ -1030,7 +1027,7 @@
<value>Domain</value>
</data>
<data name="menuAddHysteria2Server" xml:space="preserve">
<value>添加 [Hysteria2] 設定檔</value>
<value>新增 [Hysteria2] 節點</value>
</data>
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
<value>Hysteria 最大頻寬 (Up/Dw)</value>
@ -1039,19 +1036,19 @@
<value>使用系統 hosts</value>
</data>
<data name="menuAddTuicServer" xml:space="preserve">
<value>新增 [TUIC] 設定檔</value>
<value>新增 [TUIC] 節點</value>
</data>
<data name="TbHeaderType8" xml:space="preserve">
<value>擁塞控制算法</value>
</data>
<data name="LvPrevProfile" xml:space="preserve">
<value>前置代理設定檔別名</value>
<value>前置代理節點別名</value>
</data>
<data name="LvNextProfile" xml:space="preserve">
<value>落地代理設定檔別名</value>
<value>落地代理節點別名</value>
</data>
<data name="LvPrevProfileTip" xml:space="preserve">
<value>請確保設定檔別名存在並且唯一</value>
<value>請確保節點別名存在並且唯一</value>
</data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>自動路由</value>
@ -1072,7 +1069,7 @@
<value>啟用 IPv6</value>
</data>
<data name="menuAddWireguardServer" xml:space="preserve">
<value>添加 [WireGuard] 設定檔</value>
<value>新增 [WireGuard] 節點</value>
</data>
<data name="TbPrivateKey" xml:space="preserve">
<value>PrivateKey</value>
@ -1105,7 +1102,7 @@
<value>*grpc Authority</value>
</data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>新增 [HTTP] 設定檔</value>
<value>新增 [HTTP] 節點</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>和分組前置代理衝突</value>
@ -1219,13 +1216,13 @@
<value>匯出分享連結至剪貼簿 (多選) Base64 編碼</value>
</data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>匯出所選設定檔完整設定至剪貼簿</value>
<value>匯出所選完整設定至剪貼簿</value>
</data>
<data name="menuShowOrHideMainWindow" xml:space="preserve">
<value>顯示或隱藏主介面</value>
</data>
<data name="TbPreSocksPort4Sub" xml:space="preserve">
<value>自訂設定的 Socks 連接埠</value>
<value>自訂 Socks 連接埠</value>
</data>
<data name="menuBackupAndRestore" xml:space="preserve">
<value>備份和還原</value>
@ -1309,7 +1306,7 @@
<value>請不要使用不安全的 HTTP 協定訂閱位址</value>
</data>
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
<value>安裝字體到系統中,選擇或填入字體名稱,重新啟動設定</value>
<value>安裝字體到系統中,選擇或填入字體名稱,重新啟動後生效</value>
</data>
<data name="menuExitTips" xml:space="preserve">
<value>是否確定退出?</value>
@ -1336,7 +1333,7 @@
<value>多執行緒測試時的並發數量</value>
</data>
<data name="TbSettingsExceptionTip2" xml:space="preserve">
<value>例外:對於下列位址不使用代理設定檔,使用逗號 (,) 分隔。</value>
<value>例外:對於下列位址不使用代理,使用逗號 (,) 分隔。</value>
</data>
<data name="TbSettingsDestOverride" xml:space="preserve">
<value>流量探測類型</value>
@ -1371,32 +1368,32 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>多設定檔產生自訂配置 (多選)</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多選生成策略組</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<value>多設定檔隨機 Xray</value>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多隨機 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多設定檔負載平衡 Xray</value>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多負載平衡 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>多設定檔最低延遲 Xray</value>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多最低延遲 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多設定檔最穩定 Xray</value>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多最穩定 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多設定檔最低延遲 sing-box</value>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多最低延遲 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>匯出設定檔</value>
<value>匯出</value>
</data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>目前連接資訊測試地址</value>
</data>
<data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>可以填寫設定檔別名,請確保存在並唯一</value>
<value>可以填寫節點別名,請確保存在並唯一</value>
</data>
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>密碼錯誤,請重試。</value>
@ -1405,108 +1402,231 @@
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>新增 [Anytls] 設定檔</value>
<value>新增 [Anytls] 節點</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>Remote DNS</value>
<value>遠程 DNS</value>
</data>
<data name="TbDomesticDNS" xml:space="preserve">
<value>Domestic DNS</value>
<value>直連 DNS</value>
</data>
<data name="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>Outbound DNS Resolution (sing-box)</value>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>通过代理,请确保远程可用</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>Resolve Outbound Domains</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH Resolver Server</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>Fallback DNS Resolution, Suggest IP</value>
</data>
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
<value>xray Freedom Resolution Strategy</value>
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray freedom 解析策略</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>sing-box Direct Resolution Strategy</value>
<value>sing-box 直連解析策略</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>sing-box Remote Resolution Strategy</value>
<value>sing-box 遠程解析策略</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
</data>
<data name="TbSBDoHOverride" xml:space="preserve">
<value>The sing-box DoH resolution server can be overwritten</value>
<value>新增常用 DNS Hosts</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
<value>Block SVCB and HTTPS Queries</value>
<value>阻止 SVCB 和 HTTPS 查詢</value>
</data>
<data name="TbDNSHostsConfig" xml:space="preserve">
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
<value>DNS Hosts“網域名稱1 ip1 ip2” 一行一個)</value>
</data>
<data name="ThBasicDNSSettings" xml:space="preserve">
<value>Basic DNS Settings</value>
<value>DNS 基礎設定</value>
</data>
<data name="ThAdvancedDNSSettings" xml:space="preserve">
<value>Advanced DNS Settings</value>
<value>DNS 進階設定</value>
</data>
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
<value>Validate Regional Domain IPs</value>
<value>校驗相應地區域名 IP</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
<value>配置後,會對相應地區域名(如 geosite:cn的返回 IP 進行校驗,僅返回期望 IP</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Enable Custom DNS</value>
<value>啟用自訂 DNS</value>
</data>
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
<value>自訂 DNS 已啟用,此頁面配置將無效</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Prevent domain-based routing rules from failing</value>
<value>開啟後將阻止 ECH 和 HTTP/3 可用性查詢</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Please fill in the correct config template</value>
<value>請填寫正確的配置範本</value>
</data>
<data name="menuFullConfigTemplate" xml:space="preserve">
<value>Full Config Template Setting</value>
<value>完整配置範本設定</value>
</data>
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
<value>Enable Full Config Template</value>
<value>啟用完整配置範本</value>
</data>
<data name="TbRayFullConfigTemplate" xml:space="preserve">
<value>v2ray Full Config Template</value>
<value>v2ray 完整配置範本</value>
</data>
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
<value>僅添加出站配置routing.balancers 和 routing.rules.outboundTag點擊查看文檔</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
<value>不添加非代理協定出站</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
<value>設定上游代理 tag</value>
</data>
<data name="TbSBFullConfigTemplate" xml:space="preserve">
<value>sing-box Full Config Template</value>
<value>sing-box 完整配置範本</value>
</data>
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
<value>僅添加出站和端點配置,點擊查看文檔</value>
</data>
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core's basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you.</value>
<value>此功能供高級用戶和有特殊需求的用戶使用。 啟用此功能後,將忽略 Core 的基礎設定DNS 設定 ,路由設定。你需要保證系統代理的埠和流量統計等功能的配置正確,一切都由你來設定。</value>
</data>
<data name="MsgStartParsingSubscription" xml:space="preserve">
<value>開始解析和處理訂閱內容</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
<value>選擇節點</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
<value>默認全域生效,內置 FakeIP 過濾,僅在 sing-box 中生效</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>請至少添加一個節點</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>策略組</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>鏈式代理</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>最低延遲</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>隨機</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>負載均衡</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>最穩定</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>策略組類型</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>添加策略組</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>添加鏈式代理</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>添加子配置</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>刪除子配置</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>子配置項</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>容錯移轉</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>多選容錯移轉 sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多選容錯移轉 Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支援網路類型 '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>核心 '{0}' 在使用傳輸方式 '{2}' 時不支援協定 '{1}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>核心 '{0}' 不支援協定 '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>代理鏈: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>路由規則出站: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>策略組: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>別名 '{0}' 不存在。</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>組“{0}”為空.請至少添加一個配置。</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>{0}屬性無效,請檢查</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} 分組不能引用自身或循環引用</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>不支援協定 '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>如果系統沒有託盤功能,請不要開啟</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>規則類型</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>可對 Routing 和 DNS 單獨設定規則ALL 則都生效</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>解析 DNS 伺服器網域名稱,需指定為 IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>一鍵測試真連線延遲</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>自動從訂閱分組新增過濾後的配置</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>憑證綁定</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>伺服器憑證PEM 格式,可選)
若已指定,憑證將會被綁定,並且「跳過憑證驗證」將被停用。
若使用自簽憑證,或系統中存在不受信任或惡意的 CA「取得憑證」動作可能會失敗。</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>獲取憑證</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>獲取憑證鏈</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>請設定有效的網域名稱</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>尚未設定憑證</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>已設定憑證</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>自訂 PAC 檔案路徑</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>自訂系統代理程式腳本檔案路徑</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS 在 Dock 欄顯示 (需重啟)</value>
</data>
</root>

View file

@ -47,6 +47,18 @@ echo "============================================"
echo "Starting termination of process $PID and all its children"
echo "============================================"
# Try graceful termination first
echo "Attempting graceful termination (SIGTERM) of PID: $PID"
kill -15 "$PID" 2>/dev/null || true
sleep 1
# If still running, fall back to kill_children
if ps -p $PID > /dev/null; then
echo "Process $PID did not exit after SIGTERM; proceeding with forced termination of its children and itself"
else
echo "Process $PID exited cleanly after SIGTERM"
exit 0
fi
# Find and kill all child processes
kill_children "$PID"

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
{
{
"domain": [
"amobile.music.tc.qq.com",
"api-jooxtt.sanook.com",
@ -28,6 +28,7 @@
"swdownload.apple.com",
"swquery.apple.com",
"swscan.apple.com",
"turn.cloudflare.com",
"trackercdn.kugou.com",
"xnotify.xboxlive.com"
],

View file

@ -11,7 +11,7 @@
</PackageReference>
<PackageReference Include="ReactiveUI.Fody" />
<PackageReference Include="sqlite-net-pcl" />
<PackageReference Include="Splat.NLog" />
<PackageReference Include="NLog" />
<PackageReference Include="WebDav.Client" />
<PackageReference Include="YamlDotNet" />
<PackageReference Include="QRCoder" />
@ -57,6 +57,9 @@
<SubType>Designer</SubType>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resx\ResUI.fr.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resx\ResUI.hu.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>

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