mirror of
https://github.com/2dust/v2rayN.git
synced 2026-08-09 00:32:04 +03:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5efff37acb | ||
|
|
e8cdd1cc02 | ||
|
|
d381d429e4 | ||
|
|
b27ae11d1e | ||
|
|
eff584597f | ||
|
|
ccf18ba0fb | ||
|
|
15ba2bdf93 | ||
|
|
e1e6c5ddb0 | ||
|
|
d924c6f557 | ||
|
|
635a04d74c | ||
|
|
2e1bb23da7 | ||
|
|
6f3bacd5b4 | ||
|
|
074ce5de04 | ||
|
|
02df430172 | ||
|
|
611ce0fd2c | ||
|
|
a846e74e8f | ||
|
|
92c3df45ae | ||
|
|
ee7e21268a | ||
|
|
dc216c2b02 | ||
|
|
beffa919d3 | ||
|
|
a654826231 | ||
|
|
b01476d147 | ||
|
|
cf8dd45abe | ||
|
|
9bba6f53f8 | ||
|
|
cd77f1d882 | ||
|
|
0427037638 | ||
|
|
e749b81ecf | ||
|
|
fd2c942231 | ||
|
|
fa42eb670f | ||
|
|
0d42996573 | ||
|
|
a37772f12b | ||
|
|
531e0e9443 | ||
|
|
984c77f0c4 | ||
|
|
b5e0be47a5 | ||
|
|
6df0e6de1b | ||
|
|
2b5328665f | ||
|
|
4584ecc5c9 | ||
|
|
d9dc2d7cf5 | ||
|
|
36f8565b7a | ||
|
|
f55d8b2565 | ||
|
|
09ea4890a7 | ||
|
|
5cc2aaba13 | ||
|
|
b8889bad86 | ||
|
|
5dd5b25869 | ||
|
|
fafdb4a0b6 | ||
|
|
223642dd67 | ||
|
|
165e0bf9e9 | ||
|
|
e615314582 | ||
|
|
ca9978b625 | ||
|
|
74ab7ad097 | ||
|
|
8f5cbad988 | ||
|
|
1ab9757498 | ||
|
|
50ab02e8a9 | ||
|
|
b9a1c129c4 | ||
|
|
996eb51f9b | ||
|
|
b9a58e7137 | ||
|
|
9664ee380e | ||
|
|
6f50206606 |
107
.github/scripts/UpdateCert.cs
vendored
107
.github/scripts/UpdateCert.cs
vendored
@@ -1,3 +1,49 @@
|
||||
/*
|
||||
==============================================================================
|
||||
THIRD-PARTY CA CERTIFICATE DATA ATTRIBUTION
|
||||
==============================================================================
|
||||
|
||||
This component includes CA certificate data obtained from third-party sources.
|
||||
|
||||
1. Common CA Database (CCADB)
|
||||
------------------------------------------------------------------------------
|
||||
Website:
|
||||
https://www.ccadb.org/
|
||||
|
||||
Data source:
|
||||
https://ccadb.my.salesforce-sites.com/mozilla/IncludedRootsPEMTxt?TrustBitsInclude=Websites
|
||||
|
||||
License:
|
||||
Community Data License Agreement – Permissive, Version 2.0 (CDLA-2.0 Permissive)
|
||||
|
||||
The names and trademarks of CCADB contributors may not be used to endorse or
|
||||
promote products derived from this data without prior written permission.
|
||||
|
||||
License text:
|
||||
https://cdla.dev/permissive-2-0/
|
||||
|
||||
|
||||
2. Chromium Root Store
|
||||
------------------------------------------------------------------------------
|
||||
Source:
|
||||
Chromium Project
|
||||
|
||||
Data source:
|
||||
https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/root_store.certs?format=TEXT
|
||||
|
||||
License:
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright:
|
||||
Copyright (c) The Chromium Authors
|
||||
|
||||
The Chromium Root Store data is distributed under the BSD 3-Clause License.
|
||||
|
||||
License text:
|
||||
https://opensource.org/licenses/BSD-3-Clause
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
@@ -27,68 +73,23 @@ Console.WriteLine("\nAll done!");
|
||||
|
||||
async Task ProcessMozillaAsync(string outputDir)
|
||||
{
|
||||
const string certdataUrl = "https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt";
|
||||
const string certdataUrl = "https://ccadb.my.salesforce-sites.com/mozilla/IncludedRootsPEMTxt?TrustBitsInclude=Websites";
|
||||
var outputFile = Path.Combine(outputDir, "mozilla_roots_pem");
|
||||
|
||||
Console.WriteLine("Downloading Mozilla certdata.txt...");
|
||||
var content = await client.GetStringAsync(certdataUrl);
|
||||
|
||||
Console.WriteLine("Parsing MULTILINE_OCTAL to PEM...");
|
||||
var pems = ParseMozillaCertData(content);
|
||||
var pemMatches = Regex.Matches(content, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
|
||||
var pems = new List<string>();
|
||||
foreach (Match m in pemMatches)
|
||||
{
|
||||
pems.Add(m.Groups[1].Value.Trim());
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(outputFile, string.Join("\n\n", pems));
|
||||
Console.WriteLine($"Mozilla roots saved to: {outputFile} ({pems.Count} certificates)");
|
||||
}
|
||||
|
||||
List<string> ParseMozillaCertData(string text)
|
||||
{
|
||||
var pems = new List<string>();
|
||||
|
||||
var matches = Regex.Matches(text,
|
||||
@"CKA_VALUE MULTILINE_OCTAL\s*([\s\S]*?)\s*END",
|
||||
RegexOptions.Multiline);
|
||||
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
var octalBlock = m.Groups[1].Value.Trim();
|
||||
var der = OctalToBytes(octalBlock);
|
||||
if (der.Length > 0)
|
||||
{
|
||||
var pem = ConvertToPem(der);
|
||||
pems.Add(pem);
|
||||
}
|
||||
}
|
||||
return pems;
|
||||
}
|
||||
|
||||
byte[] OctalToBytes(string octalBlock)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
var matches = Regex.Matches(octalBlock, @"\\(\d{1,3})");
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
bytes.Add((byte)Convert.ToInt32(m.Groups[1].Value, 8));
|
||||
}
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
string ConvertToPem(byte[] der)
|
||||
{
|
||||
var base64 = Convert.ToBase64String(der);
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("-----BEGIN CERTIFICATE-----\n");
|
||||
|
||||
for (int i = 0; i < base64.Length; i += 64)
|
||||
{
|
||||
var length = Math.Min(64, base64.Length - i);
|
||||
sb.Append(base64.Substring(i, length));
|
||||
sb.Append("\n");
|
||||
}
|
||||
|
||||
sb.Append("-----END CERTIFICATE-----\n");
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
async Task ProcessChromeAsync(string outputDir)
|
||||
{
|
||||
const string chromeCertsUrl = "https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/root_store.certs?format=TEXT";
|
||||
@@ -99,7 +100,7 @@ async Task ProcessChromeAsync(string outputDir)
|
||||
var decoded = Convert.FromBase64String(base64Content.Replace("\n", ""));
|
||||
|
||||
var text = Encoding.UTF8.GetString(decoded);
|
||||
|
||||
|
||||
var pemMatches = Regex.Matches(text, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
|
||||
var pems = new List<string>();
|
||||
foreach (Match m in pemMatches)
|
||||
|
||||
16
.github/workflows/build-linux.yml
vendored
16
.github/workflows/build-linux.yml
vendored
@@ -205,7 +205,7 @@ jobs:
|
||||
deb-loong64:
|
||||
name: build and release deb loong64
|
||||
if: (github.event_name == 'workflow_dispatch' && inputs.release_tag != '') || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-26.04-arm
|
||||
env:
|
||||
RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}
|
||||
QCOW2_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/debian13-loong64.qcow2
|
||||
@@ -234,10 +234,10 @@ jobs:
|
||||
- name: Download QEMU prebuild
|
||||
run: |
|
||||
set -euo pipefail
|
||||
wget -O qemu-linux-x64-v3.tar.gz "https://github.com/xujiegb/qemu-linux-prebuild/releases/download/${QEMU_VERSION}/qemu-linux-x64-v3.tar.gz"
|
||||
tar -xzf qemu-linux-x64-v3.tar.gz
|
||||
wget -O qemu-linux-arm64-v8.2.tar.gz "https://github.com/xujiegb/qemu-linux-prebuild/releases/download/${QEMU_VERSION}/qemu-linux-arm64-v8.2.tar.gz"
|
||||
tar -xzf qemu-linux-arm64-v8.2.tar.gz
|
||||
mkdir -p "$HOME/qemu-install"
|
||||
rsync -a qemu-linux-x64-v3/ "$HOME/qemu-install/"
|
||||
rsync -a qemu-linux-arm64-v8.2/ "$HOME/qemu-install/"
|
||||
"$HOME/qemu-install/bin/qemu-system-loongarch64" --version
|
||||
- name: Download loong64 qcow2 and EFI firmware
|
||||
shell: bash
|
||||
@@ -355,7 +355,7 @@ jobs:
|
||||
rpm-loong64:
|
||||
name: build and release rpm loong64
|
||||
if: (github.event_name == 'workflow_dispatch' && inputs.release_tag != '') || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-26.04-arm
|
||||
env:
|
||||
RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}
|
||||
QCOW2_URL: https://github.com/xujiegb/fedora-loong64-qcow2/releases/download/43/fedora43-loong64.qcow2
|
||||
@@ -384,10 +384,10 @@ jobs:
|
||||
- name: Download QEMU prebuild
|
||||
run: |
|
||||
set -euo pipefail
|
||||
wget -O qemu-linux-x64-v3.tar.gz "https://github.com/xujiegb/qemu-linux-prebuild/releases/download/${QEMU_VERSION}/qemu-linux-x64-v3.tar.gz"
|
||||
tar -xzf qemu-linux-x64-v3.tar.gz
|
||||
wget -O qemu-linux-arm64-v8.2.tar.gz "https://github.com/xujiegb/qemu-linux-prebuild/releases/download/${QEMU_VERSION}/qemu-linux-arm64-v8.2.tar.gz"
|
||||
tar -xzf qemu-linux-arm64-v8.2.tar.gz
|
||||
mkdir -p "$HOME/qemu-install"
|
||||
rsync -a qemu-linux-x64-v3/ "$HOME/qemu-install/"
|
||||
rsync -a qemu-linux-arm64-v8.2/ "$HOME/qemu-install/"
|
||||
"$HOME/qemu-install/bin/qemu-system-loongarch64" --version
|
||||
- name: Download loong64 qcow2 and EFI firmware
|
||||
shell: bash
|
||||
|
||||
4
.github/workflows/build-windows-x86.yml
vendored
4
.github/workflows/build-windows-x86.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||
|
||||
- name: Setup .NET 10.0.1xx
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
uses: actions/setup-dotnet@v6.0.0
|
||||
with:
|
||||
dotnet-version: 10.0.1xx
|
||||
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
$xrayTag = "v$xrayVer"
|
||||
$singTag = "v$singVer"
|
||||
|
||||
$xrayUrl = "https://github.com/autorepobot/Xray-core/releases/download/$xrayTag/Xray-windows-32.zip"
|
||||
$xrayUrl = "https://github.com/XTLS/Xray-core/releases/download/$xrayTag/Xray-windows-32.zip"
|
||||
$singUrl = "https://github.com/SagerNet/sing-box/releases/download/$singTag/sing-box-$singVer-windows-386.zip"
|
||||
|
||||
Write-Host "Bundled Xray version: $xrayVer"
|
||||
|
||||
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
uses: actions/setup-dotnet@v6.0.0
|
||||
with:
|
||||
dotnet-version: '10.0.1xx'
|
||||
|
||||
@@ -83,6 +83,11 @@ jobs:
|
||||
working-directory: ./v2rayN
|
||||
run: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r $RID -p:SelfContained=true -p:PublishTrimmed=true $ExtOpt -o $Output
|
||||
|
||||
- name: Remove Debug Symbols
|
||||
shell: bash
|
||||
run: |
|
||||
find ${{ matrix.arch }} -type f -name '*.pdb' -delete
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
uses: actions/setup-dotnet@v6.0.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
OUTPUT_DIR="${HOME}/debbuild"
|
||||
DOTNET_TFM="net10.0"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.109"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.110"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
||||
@@ -597,8 +597,8 @@ package_binary() {
|
||||
write_desktop_file "$stage"
|
||||
write_maintainer_scripts "$debian_dir"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.2.0), coreutils (>= 9.5), bash (>= 5.2.26), libfreetype6 (>= 2.13)"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 9.4), bash (>= 5.2.21), libfreetype6 (>= 2.13)"
|
||||
|
||||
mkdir -p "$workdir/debian"
|
||||
cat > "$workdir/debian/control" <<EOF
|
||||
Source: v2rayn
|
||||
|
||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
OUTPUT_DIR="${HOME}/debbuild"
|
||||
DOTNET_RISCV_VERSION="10.0.109"
|
||||
DOTNET_RISCV_VERSION="10.0.110"
|
||||
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
||||
@@ -595,8 +595,8 @@ package_binary() {
|
||||
write_desktop_file "$stage"
|
||||
write_maintainer_scripts "$debian_dir"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.2.0), coreutils (>= 9.5), bash (>= 5.2.26), libfreetype6 (>= 2.13)"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 9.4), bash (>= 5.2.21), libfreetype6 (>= 2.13)"
|
||||
|
||||
mkdir -p "$workdir/debian"
|
||||
cat > "$workdir/debian/control" <<EOF
|
||||
Source: v2rayn
|
||||
|
||||
@@ -609,7 +609,7 @@ package_binary() {
|
||||
write_desktop_file "$stage"
|
||||
write_maintainer_scripts "$debian_dir"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.2.0), coreutils (>= 9.5), bash (>= 5.2.26), libfreetype6 (>= 2.13)"
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 9.4), bash (>= 5.2.21), libfreetype6 (>= 2.13)"
|
||||
|
||||
mkdir -p "$workdir/debian"
|
||||
cat > "$workdir/debian/control" <<EOF
|
||||
|
||||
@@ -12,8 +12,8 @@ MIN_KERNEL="6.12"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.109"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.110"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
||||
@@ -513,9 +513,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.39
|
||||
Requires: fontconfig >= 2.15.0
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.2.0
|
||||
Requires: coreutils >= 9.5
|
||||
Requires: bash >= 5.2.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 9.4
|
||||
Requires: bash >= 5.2.21
|
||||
Requires: freetype >= 2.13
|
||||
|
||||
%description
|
||||
|
||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
||||
DOTNET_RISCV_VERSION="10.0.109"
|
||||
DOTNET_RISCV_VERSION="10.0.110"
|
||||
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
||||
@@ -512,9 +512,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.39
|
||||
Requires: fontconfig >= 2.15.0
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.2.0
|
||||
Requires: coreutils >= 9.5
|
||||
Requires: bash >= 5.2.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 9.4
|
||||
Requires: bash >= 5.2.21
|
||||
Requires: freetype >= 2.13
|
||||
|
||||
%description
|
||||
|
||||
@@ -501,9 +501,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.39
|
||||
Requires: fontconfig >= 2.15.0
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.2.0
|
||||
Requires: coreutils >= 9.5
|
||||
Requires: bash >= 5.2.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 9.4
|
||||
Requires: bash >= 5.2.21
|
||||
Requires: freetype >= 2.13
|
||||
|
||||
%description
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.23.4</Version>
|
||||
<Version>7.24.5</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -5,35 +5,35 @@
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.18" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.0" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="12.1.0" />
|
||||
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.5.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.3" />
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.13" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.0" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.2" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.5" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.14" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||
<PackageVersion Include="NLog" Version="6.1.3" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3" />
|
||||
<PackageVersion Include="ReactiveUI" Version="24.0.0" />
|
||||
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="3.1.0" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="24.0.0" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="12.1.0" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0" />
|
||||
<PackageVersion Include="NLog" Version="6.1.4" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3.10" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||
<PackageVersion Include="YamlDotNet" Version="18.0.0" />
|
||||
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
|
||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -59,7 +59,7 @@ internal static class CoreConfigTestFactory
|
||||
},
|
||||
WebDavItem = new WebDavItem(),
|
||||
CheckUpdateItem = new CheckUpdateItem(),
|
||||
Fragment4RayItem = new Fragment4RayItem { Packets = "tlshello", Length = "100-200", Interval = "10-20" },
|
||||
Fragment4RayItem = new Fragment4RayItem { Packets = "tlshello", Lengths = ["100-200"], Delays = ["10-20"] },
|
||||
Inbound =
|
||||
[
|
||||
new InItem
|
||||
@@ -84,6 +84,7 @@ internal static class CoreConfigTestFactory
|
||||
ParallelQuery = false,
|
||||
Strategy4Freedom = Global.AsIs,
|
||||
Strategy4Proxy = Global.AsIs,
|
||||
Strategy4ProxyDial = Global.AsIs,
|
||||
},
|
||||
IndexId = string.Empty,
|
||||
SubIndexId = string.Empty,
|
||||
@@ -149,6 +150,23 @@ internal static class CoreConfigTestFactory
|
||||
};
|
||||
}
|
||||
|
||||
public static ProfileItem CreateCustomOutboundNode(ECoreType coreType, string indexId = "node-custom-1",
|
||||
string remarks = "demo-custom-outbound", string address = "custom_outbound.json")
|
||||
{
|
||||
return new ProfileItem
|
||||
{
|
||||
IndexId = indexId,
|
||||
ConfigType = EConfigType.Outbound,
|
||||
CoreType = coreType,
|
||||
Remarks = remarks,
|
||||
Address = address,
|
||||
Port = 0,
|
||||
Network = nameof(ETransport.raw),
|
||||
StreamSecurity = string.Empty,
|
||||
Subid = string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
public static ProfileItem CreatePolicyGroupNode(ECoreType coreType, string indexId, string remarks,
|
||||
IEnumerable<string> childIndexIds)
|
||||
{
|
||||
|
||||
@@ -592,4 +592,74 @@ public class CoreConfigSingboxServiceTests
|
||||
proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478");
|
||||
proxy.server.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_TunSystemStackWithIpv6_ShouldUsePrefixWithPeerAddress()
|
||||
{
|
||||
// Regression test for #9820: sing-box fails with "need one more IPv6 address in
|
||||
// first prefix for system stack" when the TUN inbound uses a /128 IPv6 prefix.
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||
config.TunModeItem.EnableTun = true;
|
||||
config.TunModeItem.Stack = "system";
|
||||
config.TunModeItem.EnableIPv6Address = true;
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.sing_box);
|
||||
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
|
||||
{
|
||||
IsTunEnabled = true,
|
||||
};
|
||||
|
||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||
var tun = cfg.inbounds.First(i => i.type == "tun");
|
||||
|
||||
tun.address.Should().NotBeNullOrEmpty();
|
||||
foreach (var address in tun.address!)
|
||||
{
|
||||
var prefixLength = int.Parse(address[(address.LastIndexOf('/') + 1)..]);
|
||||
var isIpv6 = address.Contains(':');
|
||||
prefixLength.Should().BeLessThanOrEqualTo(isIpv6 ? 126 : 30,
|
||||
$"'{address}' must leave room for the peer address the system stack derives");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
||||
{
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var customNode = CoreConfigTestFactory.CreateCustomOutboundNode(ECoreType.sing_box, "n-custom", "custom-singbox");
|
||||
var customJsonContent = """
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"server": "1.2.3.4",
|
||||
"server_port": 8388,
|
||||
"method": "aes-128-gcm",
|
||||
"password": "custom_password"
|
||||
}
|
||||
""";
|
||||
|
||||
var context = CoreConfigTestFactory.CreateContext(config, customNode, ECoreType.sing_box);
|
||||
context.CustomOutboundContent[customNode.IndexId] = customJsonContent;
|
||||
|
||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||
result.Data.Should().NotBeNull();
|
||||
|
||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString());
|
||||
cfg.Should().NotBeNull();
|
||||
var proxyOutbound = cfg!.outbounds.FirstOrDefault(o => o.tag == Global.ProxyTag);
|
||||
proxyOutbound.Should().NotBeNull();
|
||||
proxyOutbound!.type.Should().Be("shadowsocks");
|
||||
proxyOutbound.server.Should().Be("1.2.3.4");
|
||||
proxyOutbound.server_port.Should().Be(8388);
|
||||
proxyOutbound.method.Should().Be("aes-128-gcm");
|
||||
proxyOutbound.password.Should().Be("custom_password");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,7 @@ public class CoreConfigV2rayServiceTests
|
||||
|
||||
var directOutbound = cfg.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag && o.protocol == "freedom");
|
||||
directOutbound.Should().NotBeNull();
|
||||
directOutbound!.settings.domainStrategy.Should().Be("UseIPv4");
|
||||
directOutbound!.streamSettings.sockopt!.domainStrategy.Should().Be("UseIPv4");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -592,4 +592,43 @@ public class CoreConfigV2rayServiceTests
|
||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.0/32");
|
||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.2/31");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
||||
{
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var customNode = CoreConfigTestFactory.CreateCustomOutboundNode(ECoreType.Xray, "n-custom", "custom-xray");
|
||||
var customJsonContent = """
|
||||
{
|
||||
"protocol": "shadowsocks",
|
||||
"settings": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "1.2.3.4",
|
||||
"port": 8388,
|
||||
"method": "aes-128-gcm",
|
||||
"password": "custom_password"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var context = CoreConfigTestFactory.CreateContext(config, customNode, ECoreType.Xray);
|
||||
context.CustomOutboundContent[customNode.IndexId] = customJsonContent;
|
||||
|
||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||
result.Data.Should().NotBeNull();
|
||||
|
||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString());
|
||||
cfg.Should().NotBeNull();
|
||||
var proxyOutbound = cfg!.outbounds.FirstOrDefault(o => o.tag == Global.ProxyTag);
|
||||
proxyOutbound.Should().NotBeNull();
|
||||
proxyOutbound!.protocol.Should().Be("shadowsocks");
|
||||
proxyOutbound.settings.servers.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@ 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;
|
||||
@@ -15,10 +12,7 @@ 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;
|
||||
|
||||
44
v2rayN/ServiceLib.Tests/Manager/CoreManagerTests.cs
Normal file
44
v2rayN/ServiceLib.Tests/Manager/CoreManagerTests.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using AwesomeAssertions;
|
||||
using ServiceLib.Enums;
|
||||
using ServiceLib.Manager;
|
||||
using Xunit;
|
||||
|
||||
namespace ServiceLib.Tests.Manager;
|
||||
|
||||
public class CoreManagerTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ECoreType.sing_box)]
|
||||
[InlineData(ECoreType.mihomo)]
|
||||
[InlineData(ECoreType.Xray)]
|
||||
public void ShouldRunAsSudo_TunLaunchOnNonWindows_RequiresElevation(ECoreType coreType)
|
||||
{
|
||||
CoreManager.ShouldRunAsSudo(isTunLaunch: true, coreType, isNonWindows: true).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRunAsSudo_NonTunLaunch_ShouldNotElevate()
|
||||
{
|
||||
// Regression guard for the macOS TUN failure: the elevation decision must follow
|
||||
// the context snapshot that generated the config. A launch whose snapshot has TUN
|
||||
// disabled must never elevate, and a launch whose snapshot has TUN enabled must
|
||||
// elevate regardless of later changes to the live config.
|
||||
CoreManager.ShouldRunAsSudo(isTunLaunch: false, ECoreType.sing_box, isNonWindows: true).Should().BeFalse();
|
||||
CoreManager.ShouldRunAsSudo(isTunLaunch: false, ECoreType.Xray, isNonWindows: true).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRunAsSudo_OnWindows_ShouldNotElevate()
|
||||
{
|
||||
CoreManager.ShouldRunAsSudo(isTunLaunch: true, ECoreType.sing_box, isNonWindows: false).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ECoreType.v2fly)]
|
||||
[InlineData(ECoreType.hysteria)]
|
||||
[InlineData(null)]
|
||||
public void ShouldRunAsSudo_UnsupportedCoreType_ShouldNotElevate(ECoreType? coreType)
|
||||
{
|
||||
CoreManager.ShouldRunAsSudo(isTunLaunch: true, coreType, isNonWindows: true).Should().BeFalse();
|
||||
}
|
||||
}
|
||||
66
v2rayN/ServiceLib/Base/BulkObservableCollection.cs
Normal file
66
v2rayN/ServiceLib/Base/BulkObservableCollection.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
namespace ServiceLib.Base;
|
||||
|
||||
public class BulkObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
private bool _suppressNotification = false;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (!_suppressNotification)
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
if (!_suppressNotification)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T>? collection)
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_suppressNotification = true;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressNotification = false;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Replace(T oldItem, T newItem)
|
||||
{
|
||||
var index = Items.IndexOf(oldItem);
|
||||
if (index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Items[index] = newItem;
|
||||
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace,
|
||||
newItem,
|
||||
oldItem,
|
||||
index));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
6
v2rayN/ServiceLib/Base/ICloseable.cs
Normal file
6
v2rayN/ServiceLib/Base/ICloseable.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace ServiceLib.Base;
|
||||
|
||||
public interface ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
}
|
||||
7
v2rayN/ServiceLib/Base/IWindowDialog.cs
Normal file
7
v2rayN/ServiceLib/Base/IWindowDialog.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ServiceLib.Base;
|
||||
|
||||
public interface IWindowDialog
|
||||
{
|
||||
public Task<bool> ShowDialogAsync<TViewModel>(TViewModel vm)
|
||||
where TViewModel : class;
|
||||
}
|
||||
@@ -3,5 +3,4 @@ namespace ServiceLib.Base;
|
||||
public class MyReactiveObject : ReactiveObject
|
||||
{
|
||||
protected static Config? _config;
|
||||
protected Func<EViewAction, object?, Task<bool>>? _updateView;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public static class Extension
|
||||
|
||||
public static bool IsComplexType(this EConfigType configType)
|
||||
{
|
||||
return configType is EConfigType.Custom or EConfigType.PolicyGroup or EConfigType.ProxyChain;
|
||||
return configType is EConfigType.Custom or EConfigType.Outbound or EConfigType.PolicyGroup or EConfigType.ProxyChain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -485,12 +485,17 @@ public class Utils
|
||||
|
||||
public static string? DomainStrategy4Sbox(string? strategy)
|
||||
{
|
||||
if (strategy is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return strategy switch
|
||||
{
|
||||
not null when strategy.StartsWith("UseIPv4") => "prefer_ipv4",
|
||||
not null when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
|
||||
not null when strategy.StartsWith("ForceIPv4") => "ipv4_only",
|
||||
not null when strategy.StartsWith("ForceIPv6") => "ipv6_only",
|
||||
_ when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
|
||||
_ when strategy.StartsWith("UseIP") => "prefer_ipv4",
|
||||
_ when strategy.StartsWith("ForceIPv6") => "ipv6_only",
|
||||
_ when strategy.StartsWith("ForceIP") => "ipv4_only",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public enum EConfigType
|
||||
HTTP = 10,
|
||||
Anytls = 11,
|
||||
Naive = 12,
|
||||
Outbound = 13,
|
||||
PolicyGroup = 101,
|
||||
ProxyChain = 102,
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum EViewAction
|
||||
{
|
||||
CloseWindow,
|
||||
ShowYesNo,
|
||||
SaveFileDialog,
|
||||
AddBatchRoutingRulesYesNo,
|
||||
SetClipboardData,
|
||||
AddServerViaClipboard,
|
||||
ImportRulesFromClipboard,
|
||||
ProfilesFocus,
|
||||
ShareSub,
|
||||
ShareServer,
|
||||
ScanScreenTask,
|
||||
ScanImageTask,
|
||||
BrowseServer,
|
||||
ImportRulesFromFile,
|
||||
InitSettingFont,
|
||||
PasswordInput,
|
||||
SubEditWindow,
|
||||
RoutingRuleSettingWindow,
|
||||
RoutingRuleDetailsWindow,
|
||||
AddServerWindow,
|
||||
AddServer2Window,
|
||||
AddGroupServerWindow,
|
||||
DNSSettingWindow,
|
||||
RoutingSettingWindow,
|
||||
OptionSettingWindow,
|
||||
FullConfigTemplateWindow,
|
||||
GlobalHotkeySettingWindow,
|
||||
SubSettingWindow,
|
||||
DispatcherRefreshServersBiz,
|
||||
DispatcherRefreshIcon,
|
||||
DispatcherShowMsg,
|
||||
}
|
||||
@@ -2,30 +2,16 @@ 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<RxVoid> AddServerViaClipboardRequested = new();
|
||||
public static readonly EventChannel<bool> HasUpdateNotified = 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<RxVoid> 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();
|
||||
}
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace ServiceLib.Events;
|
||||
|
||||
public sealed class EventChannel<T>
|
||||
{
|
||||
private readonly ISubject<T> _subject = Subject.Synchronize(new Subject<T>());
|
||||
private readonly Signal<T> _signal = new();
|
||||
private readonly Lock _gate = new();
|
||||
private readonly IObservable<T> _observable;
|
||||
public EventChannel()
|
||||
{
|
||||
_observable = _signal.Synchronize(_gate);
|
||||
}
|
||||
|
||||
public IObservable<T> AsObservable()
|
||||
{
|
||||
return _subject.AsObservable();
|
||||
return _observable;
|
||||
}
|
||||
|
||||
public void Publish(T value)
|
||||
{
|
||||
_subject.OnNext(value);
|
||||
lock (_gate)
|
||||
{
|
||||
_signal.OnNext(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Publish()
|
||||
{
|
||||
if (typeof(T) != typeof(Unit))
|
||||
if (typeof(T) != typeof(RxVoid))
|
||||
{
|
||||
throw new InvalidOperationException("Publish() without value is only valid for EventChannel<Unit>.");
|
||||
throw new InvalidOperationException("Publish() without value is only valid for EventChannel<RxVoid>.");
|
||||
}
|
||||
lock (_gate)
|
||||
{
|
||||
_signal.OnNext((T)(object)RxVoid.Default);
|
||||
}
|
||||
_subject.OnNext((T)(object)Unit.Default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ReactiveUI />
|
||||
</Weavers>
|
||||
@@ -2,8 +2,6 @@ namespace ServiceLib;
|
||||
|
||||
public class Global
|
||||
{
|
||||
#region const
|
||||
|
||||
public const string AppName = "v2rayN";
|
||||
public const string GithubUrl = "https://github.com";
|
||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||
@@ -106,9 +104,7 @@ public class Global
|
||||
public const string SingboxFakeDNSTag = "fake_dns";
|
||||
|
||||
public const int Hysteria2DefaultHopInt = 30;
|
||||
|
||||
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";
|
||||
|
||||
public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$";
|
||||
|
||||
public static readonly List<string> PolicyGroupDefaultFilterList =
|
||||
@@ -557,7 +553,6 @@ public class Global
|
||||
"http",
|
||||
"tls",
|
||||
"quic",
|
||||
"fakedns",
|
||||
];
|
||||
|
||||
public static readonly List<int> TunMtus =
|
||||
@@ -737,6 +732,12 @@ public class Global
|
||||
"reply",
|
||||
];
|
||||
|
||||
public static readonly List<string> FakeIPRanges =
|
||||
[
|
||||
"198.18.0.0/15",
|
||||
"11.0.0.0/8",
|
||||
];
|
||||
|
||||
public static readonly List<string> RootCertProviders =
|
||||
[
|
||||
"system",
|
||||
@@ -744,5 +745,29 @@ public class Global
|
||||
MozillaRootProvider,
|
||||
];
|
||||
|
||||
#endregion const
|
||||
public static readonly IReadOnlyList<string> TunIPv4Address =
|
||||
[
|
||||
"172.18.0.1/30",
|
||||
"172.31.0.1/30",
|
||||
"172.20.0.1/30",
|
||||
"172.16.0.1/30",
|
||||
"192.168.100.1/30",
|
||||
"10.10.14.1/30",
|
||||
"10.1.0.1/30",
|
||||
"10.0.0.1/30",
|
||||
];
|
||||
|
||||
// Prefixes must leave room for a peer address (max /126); the sing-box system
|
||||
// stack derives a gateway from the first prefix and rejects single-address prefixes.
|
||||
public static readonly IReadOnlyList<string> TunIPv6Address =
|
||||
[
|
||||
"fc00::172:18:0:1/126",
|
||||
"fc00::172:31:0:1/126",
|
||||
"fc00::172:20:0:1/126",
|
||||
"fc00::172:16:0:1/126",
|
||||
"fc00::192:168:100:1/126",
|
||||
"fc00::10:10:14:1/126",
|
||||
"fc00::10:1:0:1/126",
|
||||
"fc00::10:0:0:1/126",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
global using System.Collections.Concurrent;
|
||||
global using System.Collections.ObjectModel;
|
||||
global using System.Collections.Specialized;
|
||||
global using System.ComponentModel;
|
||||
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 ReactiveUI.Primitives;
|
||||
global using ReactiveUI.Primitives.Concurrency;
|
||||
global using ReactiveUI.Primitives.Disposables;
|
||||
global using ReactiveUI.Primitives.Signals;
|
||||
global using System.Reflection;
|
||||
global using System.Runtime.InteropServices;
|
||||
global using System.Runtime.Versioning;
|
||||
@@ -16,10 +20,8 @@ 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 ReactiveUI.SourceGenerators;
|
||||
global using ServiceLib.Base;
|
||||
global using ServiceLib.Common;
|
||||
global using ServiceLib.Enums;
|
||||
@@ -30,8 +32,8 @@ global using ServiceLib.Handler.Fmt;
|
||||
global using ServiceLib.Handler.SysProxy;
|
||||
global using ServiceLib.Helper;
|
||||
global using ServiceLib.Manager;
|
||||
global using ServiceLib.Models.CoreConfigs;
|
||||
global using ServiceLib.Models.Configs;
|
||||
global using ServiceLib.Models.CoreConfigs;
|
||||
global using ServiceLib.Models.Dto;
|
||||
global using ServiceLib.Models.Entities;
|
||||
global using ServiceLib.Resx;
|
||||
@@ -39,3 +41,5 @@ global using ServiceLib.Services;
|
||||
global using ServiceLib.Services.CoreConfig;
|
||||
global using ServiceLib.Services.Statistics;
|
||||
global using SQLite;
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ServiceLib.Handler.Builder;
|
||||
|
||||
public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult)
|
||||
@@ -49,6 +51,7 @@ public class CoreConfigContextBuilder
|
||||
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
|
||||
IsWindows = Utils.IsWindows(),
|
||||
IsMacOS = Utils.IsMacOS(),
|
||||
ProtectCoreTypeList = config.TunModeItem.EnableTun ? [ECoreType.Xray, ECoreType.sing_box] : []
|
||||
};
|
||||
var validatorResult = NodeValidatorResult.Empty();
|
||||
var (actNode, nodeValidatorResult) = await ResolveNodeAsync(context, node);
|
||||
@@ -94,6 +97,7 @@ public class CoreConfigContextBuilder
|
||||
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.IsTunEnabled && context.AppConfig.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||
{
|
||||
var appConfig = JsonUtils.DeepCopy(config);
|
||||
@@ -193,12 +197,16 @@ public class CoreConfigContextBuilder
|
||||
if (preSocksItem != null)
|
||||
{
|
||||
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
|
||||
|
||||
var protectCoreTypeList = new HashSet<ECoreType>(nodeContext.ProtectCoreTypeList) { nodeContext.RunCoreType };
|
||||
|
||||
return preSocksResult with
|
||||
{
|
||||
Context = preSocksResult.Context with
|
||||
{
|
||||
ProtectDomainList =
|
||||
[.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
|
||||
ProtectCoreTypeList = protectCoreTypeList,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -317,14 +325,14 @@ public class CoreConfigContextBuilder
|
||||
{
|
||||
return await RegisterGroupNodeAsync(context, node);
|
||||
}
|
||||
return RegisterSingleNodeAsync(context, node);
|
||||
return await RegisterSingleNodeAsync(context, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a single (non-group) node and, on success, adds it to the proxy map
|
||||
/// and records any domain addresses that should bypass the proxy.
|
||||
/// </summary>
|
||||
private static NodeValidatorResult RegisterSingleNodeAsync(CoreConfigContext context, ProfileItem node)
|
||||
private static async Task<NodeValidatorResult> RegisterSingleNodeAsync(CoreConfigContext context, ProfileItem node)
|
||||
{
|
||||
if (node.ConfigType.IsGroupType())
|
||||
{
|
||||
@@ -332,6 +340,29 @@ public class CoreConfigContextBuilder
|
||||
}
|
||||
|
||||
var nodeValidatorResult = NodeValidator.Validate(node, context.RunCoreType);
|
||||
|
||||
if (node.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
var addressFileName = node.Address;
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
addressFileName = Utils.GetConfigPath(addressFileName);
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
nodeValidatorResult.Errors.Add(string.Format(ResUI.MsgCustomOutboundFileNotFound, node.Remarks, addressFileName));
|
||||
}
|
||||
try
|
||||
{
|
||||
var fileContent = await File.ReadAllTextAsync(addressFileName);
|
||||
context.CustomOutboundContent[node.IndexId] = fileContent;
|
||||
}
|
||||
catch
|
||||
{
|
||||
nodeValidatorResult.Errors.Add(string.Format(ResUI.MsgCustomOutboundFileNotFound, node.Remarks, addressFileName));
|
||||
}
|
||||
}
|
||||
|
||||
var msgs = new List<string>([.. nodeValidatorResult.Errors, .. nodeValidatorResult.Warnings]);
|
||||
if (msgs.Count > 0)
|
||||
{
|
||||
@@ -433,7 +464,7 @@ public class CoreConfigContextBuilder
|
||||
|
||||
if (!childNode.ConfigType.IsGroupType())
|
||||
{
|
||||
var childNodeResult = RegisterSingleNodeAsync(context, childNode);
|
||||
var childNodeResult = await RegisterSingleNodeAsync(context, childNode);
|
||||
childNodeValidatorResult.Warnings.AddRange(childNodeResult.Warnings.Select(w =>
|
||||
string.Format(ResUI.MsgGroupChildNodeWarning, node.Remarks, childNode.Remarks, w)));
|
||||
childNodeValidatorResult.Errors.AddRange(childNodeResult.Errors.Select(e =>
|
||||
|
||||
@@ -36,6 +36,15 @@ public class NodeValidator
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.ConfigType is EConfigType.Outbound)
|
||||
{
|
||||
if (item.CoreType != coreType)
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgCoreNotSupportProtocol, coreType.ToString(), item.ConfigType));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.ConfigType.IsGroupType())
|
||||
{
|
||||
// Group logic is handled in ValidateGroupNode
|
||||
|
||||
@@ -92,7 +92,7 @@ public static class ConfigHandler
|
||||
EnableTun = false,
|
||||
Mtu = 9000,
|
||||
IcmpRouting = Global.TunIcmpRoutingPolicies.First(),
|
||||
EnableLegacyProtect = false,
|
||||
EnableLegacyProtect = true,
|
||||
};
|
||||
config.GuiItem ??= new();
|
||||
if (!Global.RootCertProviders.Contains(config.GuiItem.RootCertProvider))
|
||||
@@ -115,10 +115,12 @@ public static class ConfigHandler
|
||||
config.ConstItem ??= new ConstItem();
|
||||
|
||||
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
||||
config.SimpleDNSItem.FakeIPRange ??= Global.FakeIPRanges.FirstOrDefault();
|
||||
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
||||
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
||||
config.SimpleDNSItem.ServeStale ??= false;
|
||||
config.SimpleDNSItem.ParallelQuery ??= false;
|
||||
config.SimpleDNSItem.EnableHappyEyeballs ??= false;
|
||||
|
||||
config.SpeedTestItem ??= new();
|
||||
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
||||
@@ -168,10 +170,24 @@ public static class ConfigHandler
|
||||
config.Fragment4RayItem ??= new()
|
||||
{
|
||||
Packets = "tlshello",
|
||||
Length = "50-100",
|
||||
Interval = "10-20",
|
||||
MaxSplit = "0"
|
||||
};
|
||||
config.Fragment4RayItem.MaxSplit ??= "0";
|
||||
|
||||
config.HappyEyeballs4RayItem ??= new()
|
||||
{
|
||||
TryDelayMs = 250,
|
||||
PrioritizeIPv6 = false,
|
||||
Interleave = 1,
|
||||
MaxConcurrentTry = 4,
|
||||
};
|
||||
if ((config.Fragment4RayItem.Lengths ?? []).Count == 0)
|
||||
{
|
||||
config.Fragment4RayItem.Lengths = [config.Fragment4RayItem.Length ?? "50-100"];
|
||||
}
|
||||
if ((config.Fragment4RayItem.Delays ?? []).Count == 0)
|
||||
{
|
||||
config.Fragment4RayItem.Delays = [config.Fragment4RayItem.Interval ?? "10-20"];
|
||||
}
|
||||
config.GlobalHotkeys ??= [];
|
||||
|
||||
if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty())
|
||||
@@ -366,6 +382,13 @@ public static class ConfigHandler
|
||||
{
|
||||
}
|
||||
}
|
||||
else if (profileItem.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
profileItem.Address = Utils.GetConfigPath(profileItem.Address);
|
||||
if (await AddCustomOutboundServer(config, profileItem, false) == 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await AddServerCommon(config, profileItem, true);
|
||||
@@ -563,6 +586,44 @@ public static class ConfigHandler
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public static async Task<int> AddCustomOutboundServer(Config config, ProfileItem profileItem, bool blDelete, bool toFile = true)
|
||||
{
|
||||
var fileName = profileItem.Address;
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
var ext = Path.GetExtension(fileName);
|
||||
var newFileName = $"{Utils.GetGuid()}{ext}";
|
||||
//newFileName = Path.Combine(Utile.GetTempPath(), newFileName);
|
||||
|
||||
try
|
||||
{
|
||||
File.Copy(fileName, Utils.GetConfigPath(newFileName));
|
||||
if (blDelete)
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
profileItem.Address = newFileName;
|
||||
profileItem.ConfigType = EConfigType.Outbound;
|
||||
if (profileItem.Remarks.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.Remarks = $"import custom outbound@{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}";
|
||||
}
|
||||
|
||||
await AddServerCommon(config, profileItem, toFile);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit an existing custom server configuration
|
||||
/// Updates the server's properties without changing the file
|
||||
@@ -584,6 +645,8 @@ public static class ConfigHandler
|
||||
item.CoreType = profileItem.CoreType;
|
||||
item.DisplayLog = profileItem.DisplayLog;
|
||||
item.PreSocksPort = profileItem.PreSocksPort;
|
||||
|
||||
item.ProtoExtra = profileItem.ProtoExtra;
|
||||
}
|
||||
|
||||
if (await SQLiteHelper.Instance.UpdateAsync(item) > 0)
|
||||
@@ -1463,7 +1526,7 @@ public static class ConfigHandler
|
||||
var matchedChildProfiles = childProfiles?.Where(p =>
|
||||
p != null &&
|
||||
p.IsValid() &&
|
||||
!p.ConfigType.IsComplexType() &&
|
||||
(!p.ConfigType.IsComplexType() || p.ConfigType == EConfigType.Outbound) &&
|
||||
(extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter))
|
||||
)
|
||||
.ToList() ?? [];
|
||||
@@ -1511,7 +1574,8 @@ public static class ConfigHandler
|
||||
else if (node.ConfigType == EConfigType.Custom
|
||||
&& node.PreSocksPort is > 0 and <= 65535)
|
||||
{
|
||||
var preCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
||||
var customPreCoreType = AppManager.Instance.GetCoreType(null, EConfigType.Custom);
|
||||
var preCoreType = (enableLegacyProtect && config.TunModeItem.EnableTun) ? ECoreType.sing_box : customPreCoreType;
|
||||
itemSocks = new ProfileItem()
|
||||
{
|
||||
CoreType = preCoreType,
|
||||
@@ -1658,68 +1722,179 @@ public static class ConfigHandler
|
||||
}
|
||||
|
||||
var subItem = await AppManager.Instance.GetSubItem(subid);
|
||||
|
||||
if (subItem?.CustomCoreType is null)
|
||||
{
|
||||
return await AddBatchServersDefaultCustom(config, strData, subid, isSub, subItem);
|
||||
}
|
||||
|
||||
return await AddBatchServersSpecificCustom(config, strData, subid, isSub, subItem);
|
||||
}
|
||||
|
||||
private static async Task<int> AddBatchServersDefaultCustom(
|
||||
Config config,
|
||||
string strData,
|
||||
string subid,
|
||||
bool isSub,
|
||||
SubItem? subItem)
|
||||
{
|
||||
var subRemarks = subItem?.Remarks;
|
||||
var preSocksPort = subItem?.PreSocksPort;
|
||||
|
||||
List<ProfileItem>? lstProfiles = null;
|
||||
//Is sing-box array configuration
|
||||
if (lstProfiles is null || lstProfiles.Count <= 0)
|
||||
// Safe Mode: Only allow full configuration if it's not from a subscription
|
||||
var lstProfiles = V2rayFmt.ResolveToCustomOutbound(strData, subRemarks);
|
||||
if (lstProfiles.Count == 0)
|
||||
{
|
||||
lstProfiles = SingboxFmt.ResolveFullArray(strData, subRemarks);
|
||||
lstProfiles = SingboxFmt.ResolveToCustomOutbound(strData, subRemarks);
|
||||
}
|
||||
//Is v2ray array configuration
|
||||
if (lstProfiles is null || lstProfiles.Count <= 0)
|
||||
{
|
||||
lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks);
|
||||
}
|
||||
if (lstProfiles is { Count: > 0 })
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var it in lstProfiles)
|
||||
{
|
||||
it.Subid = subid;
|
||||
it.IsSub = isSub;
|
||||
it.PreSocksPort = preSocksPort;
|
||||
if (await AddCustomServer(config, it, true) == 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count > 0)
|
||||
{
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
ProfileItem? profileItem = null;
|
||||
//Is sing-box configuration
|
||||
profileItem ??= SingboxFmt.ResolveFull(strData, subRemarks);
|
||||
//Is v2ray configuration
|
||||
profileItem ??= V2rayFmt.ResolveFull(strData, subRemarks);
|
||||
//Is Html Page
|
||||
if (profileItem is null && HtmlPageFmt.IsHtmlPage(strData))
|
||||
if (lstProfiles.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
//Is Clash configuration
|
||||
profileItem ??= ClashFmt.ResolveFull(strData, subRemarks);
|
||||
//Is hysteria configuration
|
||||
profileItem ??= Hysteria2Fmt.ResolveFull2(strData, subRemarks);
|
||||
if (profileItem is null || profileItem.Address.IsNullOrEmpty())
|
||||
|
||||
var count = await AddCustomOutboundServers(config, lstProfiles, subid, isSub);
|
||||
if (count > 0)
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
if (HtmlPageFmt.IsHtmlPage(strData))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var profileItem = ClashFmt.ResolveFull(strData, subRemarks)
|
||||
?? Hysteria2Fmt.ResolveFull2(strData, subRemarks);
|
||||
|
||||
if (profileItem == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
profileItem.Subid = subid;
|
||||
profileItem.IsSub = isSub;
|
||||
profileItem.PreSocksPort = preSocksPort;
|
||||
if (await AddCustomServer(config, profileItem, true) == 0)
|
||||
profileItem.PreSocksPort = subItem?.PreSocksPort;
|
||||
|
||||
return await AddCustomServer(config, profileItem, true) == 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
private static async Task<int> AddBatchServersSpecificCustom(
|
||||
Config config,
|
||||
string strData,
|
||||
string subid,
|
||||
bool isSub,
|
||||
SubItem subItem)
|
||||
{
|
||||
var subRemarks = subItem.Remarks;
|
||||
var customCoreType = subItem.CustomCoreType!.Value;
|
||||
|
||||
List<ProfileItem>? lstProfiles = customCoreType switch
|
||||
{
|
||||
return 1;
|
||||
ECoreType.Xray => V2rayFmt.ResolveToCustom(strData, subRemarks),
|
||||
ECoreType.sing_box => SingboxFmt.ResolveToCustom(strData, subRemarks),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (lstProfiles is not null)
|
||||
{
|
||||
if (lstProfiles.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var count = await AddCustomOutboundServers(config, lstProfiles, subid, isSub);
|
||||
if (count > 0)
|
||||
{
|
||||
return count;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
return await SaveCustomRawFileServer(config, strData, subid, isSub, subItem, customCoreType);
|
||||
}
|
||||
|
||||
private static async Task<int> AddCustomOutboundServers(
|
||||
Config config,
|
||||
List<ProfileItem> lstProfiles,
|
||||
string subid,
|
||||
bool isSub)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var it in lstProfiles)
|
||||
{
|
||||
return -1;
|
||||
it.Subid = subid;
|
||||
it.IsSub = isSub;
|
||||
if (await AddCustomOutboundServer(config, it, true) == 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static async Task<int> SaveCustomRawFileServer(
|
||||
Config config,
|
||||
string strData,
|
||||
string subid,
|
||||
bool isSub,
|
||||
SubItem subItem,
|
||||
ECoreType customCoreType)
|
||||
{
|
||||
var ext = DetectFileExtension(strData);
|
||||
var fileName = Utils.GetTempPath($"{Utils.GetGuid(false)}{ext}");
|
||||
await File.WriteAllTextAsync(fileName, strData);
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
CoreType = customCoreType,
|
||||
ConfigType = EConfigType.Custom,
|
||||
Address = fileName,
|
||||
Remarks = subItem.Remarks ?? customCoreType.ToString(),
|
||||
Subid = subid,
|
||||
IsSub = isSub,
|
||||
PreSocksPort = subItem.PreSocksPort,
|
||||
};
|
||||
|
||||
return await AddCustomServer(config, profileItem, true) == 0 ? 1 : -1;
|
||||
|
||||
static string DetectFileExtension(string data)
|
||||
{
|
||||
var trimmed = data.AsSpan().TrimStart();
|
||||
if (trimmed.IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (trimmed[0] is '{' or '[')
|
||||
{
|
||||
return ".json";
|
||||
}
|
||||
|
||||
if (trimmed.StartsWith("---"))
|
||||
{
|
||||
return ".yaml";
|
||||
}
|
||||
|
||||
foreach (var line in trimmed.EnumerateLines())
|
||||
{
|
||||
var lineTrimmed = line.TrimStart();
|
||||
if (lineTrimmed.IsEmpty || lineTrimmed.StartsWith("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var colonIndex = lineTrimmed.IndexOf(':');
|
||||
if (colonIndex > 0)
|
||||
{
|
||||
var keySpan = lineTrimmed[..colonIndex];
|
||||
if (!keySpan.Contains(' ') && !keySpan.Contains('\t'))
|
||||
{
|
||||
if (colonIndex == lineTrimmed.Length - 1 || lineTrimmed[colonIndex + 1] is ' ' or '\t' or '\r' or '\n')
|
||||
{
|
||||
return ".yaml";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1820,6 +1995,7 @@ public static class ConfigHandler
|
||||
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
|
||||
EConfigType.Naive => await AddNaiveServer(config, profileItem, false),
|
||||
EConfigType.PolicyGroup or EConfigType.ProxyChain => await AddServerCommon(config, profileItem, false),
|
||||
EConfigType.Outbound => await AddCustomOutboundServer(config, profileItem, true, false),
|
||||
_ => -1,
|
||||
};
|
||||
if (addStatus == 0)
|
||||
@@ -2021,6 +2197,7 @@ public static class ConfigHandler
|
||||
item.NextProfile = subItem.NextProfile;
|
||||
item.PreSocksPort = subItem.PreSocksPort;
|
||||
item.Memo = subItem.Memo;
|
||||
item.CustomCoreType = subItem.CustomCoreType;
|
||||
}
|
||||
|
||||
if (item.Id.IsNullOrEmpty())
|
||||
@@ -2061,7 +2238,7 @@ public static class ConfigHandler
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
var customProfile = await SQLiteHelper.Instance.TableAsync<ProfileItem>().Where(t => t.Subid == subid && t.ConfigType == EConfigType.Custom).ToListAsync();
|
||||
var customProfile = await SQLiteHelper.Instance.TableAsync<ProfileItem>().Where(t => t.Subid == subid && (t.ConfigType == EConfigType.Custom || t.ConfigType == EConfigType.Outbound)).ToListAsync();
|
||||
if (isSub)
|
||||
{
|
||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where isSub = 1 and subid = '{subid}'");
|
||||
|
||||
@@ -17,7 +17,7 @@ public static class CoreConfigHandler
|
||||
{
|
||||
result = node.CoreType switch
|
||||
{
|
||||
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
|
||||
ECoreType.mihomo => await new CoreConfigClashService(config, context.IsTunEnabled).GenerateClientCustomConfig(node, fileName),
|
||||
_ => await GenerateClientCustomConfig(node, fileName)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ public class AnytlsFmt : BaseFmt
|
||||
var query = Utils.ParseQueryString(parsedUrl.Query);
|
||||
ResolveUriQuery(query, ref item);
|
||||
|
||||
if (GetQueryValue(query, "insecure") == "1")
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -41,6 +46,10 @@ public class AnytlsFmt : BaseFmt
|
||||
}
|
||||
var pw = item.Password;
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
dicQuery.Add("insecure", "1");
|
||||
}
|
||||
ToUriQuery(item, Global.None, ref dicQuery);
|
||||
|
||||
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
|
||||
|
||||
@@ -4,8 +4,6 @@ namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class BaseFmt
|
||||
{
|
||||
private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure" };
|
||||
|
||||
private static string UrlEncodeSafe(string? value) => Utils.UrlEncode(value ?? string.Empty);
|
||||
|
||||
protected static string GetIpv6(string address)
|
||||
@@ -67,7 +65,6 @@ public class BaseFmt
|
||||
{
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||
}
|
||||
ToUriQueryAllowInsecure(item, ref dicQuery);
|
||||
}
|
||||
if (item.EchConfigList.IsNotEmpty())
|
||||
{
|
||||
@@ -196,25 +193,6 @@ public class BaseFmt
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||
}
|
||||
|
||||
ToUriQueryAllowInsecure(item, ref dicQuery);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int ToUriQueryAllowInsecure(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
||||
{
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
// Add two for compatibility
|
||||
dicQuery.Add("insecure", "1");
|
||||
dicQuery.Add("allowInsecure", "1");
|
||||
}
|
||||
else
|
||||
{
|
||||
dicQuery.Add("insecure", "0");
|
||||
dicQuery.Add("allowInsecure", "0");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -252,19 +230,6 @@ public class BaseFmt
|
||||
item.Finalmask = string.Empty;
|
||||
}
|
||||
|
||||
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
else if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "0"))
|
||||
{
|
||||
item.AllowInsecure = Global.StringFalse;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.AllowInsecure = string.Empty;
|
||||
}
|
||||
|
||||
var net = GetQueryValue(query, "type", nameof(ETransport.raw));
|
||||
if (net == Global.RawNetworkAlias)
|
||||
{
|
||||
|
||||
@@ -162,6 +162,10 @@ public class Hysteria2Fmt : BaseFmt
|
||||
|
||||
private static void ResolveHy2UriQuery(NameValueCollection query, ref ProfileItem item)
|
||||
{
|
||||
if (GetQueryValue(query, "insecure") == "1")
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
if (item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||
@@ -198,6 +202,10 @@ public class Hysteria2Fmt : BaseFmt
|
||||
|
||||
private static void ToHy2UriQuery(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
||||
{
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
dicQuery.Add("insecure", "1");
|
||||
}
|
||||
if (!item.CertSha.IsNullOrEmpty()
|
||||
&& !item.CertSha.Contains(','))
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class InnerFmt
|
||||
public class InnerFmt : BaseFmt
|
||||
{
|
||||
private static readonly Lazy<string> SessionSalt = new(() => Utils.GetGuid(false));
|
||||
|
||||
@@ -50,19 +50,19 @@ public class InnerFmt
|
||||
var protocolExtra = item.GetProtocolExtra();
|
||||
// Only allow "self" as a special value for SubChildItems to avoid possible sources of attacks,
|
||||
// which means it will be replaced with the subid, otherwise set it to null
|
||||
//if (!protocolExtra.SubChildItems.IsNullOrEmpty())
|
||||
// if (!protocolExtra.SubChildItems.IsNullOrEmpty())
|
||||
if (protocolExtra.SubChildItems == "self")
|
||||
{
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
SubChildItems = subid
|
||||
SubChildItems = subid,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
SubChildItems = null
|
||||
SubChildItems = null,
|
||||
};
|
||||
}
|
||||
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
|
||||
@@ -73,14 +73,14 @@ public class InnerFmt
|
||||
.ToList();
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
ChildItems = Utils.List2String(newChildIndexIds)
|
||||
ChildItems = Utils.List2String(newChildIndexIds),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
ChildItems = null
|
||||
ChildItems = null,
|
||||
};
|
||||
}
|
||||
item.SetProtocolExtra(protocolExtra);
|
||||
@@ -120,7 +120,7 @@ public class InnerFmt
|
||||
{
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
SubChildItems = "self"
|
||||
SubChildItems = "self",
|
||||
};
|
||||
}
|
||||
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
|
||||
@@ -131,7 +131,7 @@ public class InnerFmt
|
||||
.ToList();
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
ChildItems = Utils.List2String(newChildIndexIds)
|
||||
ChildItems = Utils.List2String(newChildIndexIds),
|
||||
};
|
||||
}
|
||||
itemClone.SetProtocolExtra(protocolExtra);
|
||||
@@ -175,6 +175,19 @@ public class InnerFmt
|
||||
jsonObj["TransportExtra"] = JsonUtils.Serialize(transportExtraObj, false);
|
||||
jsonObj.Remove("TransportExtraObj");
|
||||
}
|
||||
var customOutboundFilePath = string.Empty;
|
||||
if (jsonObj.TryGetPropertyValue("CustomOutboundObj", out var customOutboundNode)
|
||||
&& customOutboundNode is JsonObject customOutboundObj)
|
||||
{
|
||||
var customOutboundContent = JsonUtils.Serialize(customOutboundObj, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
});
|
||||
customOutboundFilePath = WriteAllText(customOutboundContent);
|
||||
jsonObj.Remove("CustomOutboundObj");
|
||||
}
|
||||
var profileItem = JsonUtils.Deserialize<ProfileItem>(JsonUtils.Serialize(jsonObj, false));
|
||||
if (profileItem is null)
|
||||
{
|
||||
@@ -193,6 +206,14 @@ public class InnerFmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (profileItem.ConfigType is EConfigType.Outbound)
|
||||
{
|
||||
if (customOutboundFilePath.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
profileItem.Address = customOutboundFilePath;
|
||||
}
|
||||
var protocolExtra = profileItem.GetProtocolExtra();
|
||||
var multipleLoad = protocolExtra.MultipleLoad;
|
||||
if (multipleLoad is not null && !Enum.IsDefined(typeof(EMultipleLoad), multipleLoad))
|
||||
@@ -209,6 +230,26 @@ public class InnerFmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (item.ConfigType is EConfigType.Outbound)
|
||||
{
|
||||
var customOutboundFilePath = item.Address;
|
||||
if (!File.Exists(customOutboundFilePath))
|
||||
{
|
||||
customOutboundFilePath = Utils.GetConfigPath(customOutboundFilePath);
|
||||
}
|
||||
if (!File.Exists(customOutboundFilePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!customOutboundFilePath.IsNullOrEmpty()
|
||||
&& File.Exists(customOutboundFilePath)
|
||||
&& File.ReadAllText(customOutboundFilePath) is { Length: > 0 } customOutboundContent
|
||||
&& JsonUtils.ParseJson(customOutboundContent) is JsonObject customOutboundObj)
|
||||
{
|
||||
jsonObj["CustomOutboundObj"] = customOutboundObj;
|
||||
jsonObj.Remove("Address");
|
||||
}
|
||||
}
|
||||
// unflatten
|
||||
// move jsonObj.ProtoExtra (string) to jsonObj.ProtoExtraObj
|
||||
// move jsonObj.TransportExtra (string) to jsonObj.TransportExtraObj
|
||||
@@ -296,7 +337,7 @@ public class InnerFmt
|
||||
JsonValue value when value.TryGetValue<string>(out var str) => string.IsNullOrEmpty(str),
|
||||
JsonObject obj => obj.Count == 0,
|
||||
JsonArray arr => arr.Count == 0,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,102 @@ namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class SingboxFmt : BaseFmt
|
||||
{
|
||||
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
||||
public static List<ProfileItem> ResolveToCustom(string strData, string? subRemarks)
|
||||
{
|
||||
var configObjects = JsonUtils.Deserialize<object[]>(strData);
|
||||
if (configObjects is not { Length: > 0 })
|
||||
var jsonNode = JsonUtils.ParseJson(strData);
|
||||
return ResolveCommon(jsonNode, subRemarks, false);
|
||||
}
|
||||
|
||||
public static List<ProfileItem> ResolveToCustomOutbound(string strData, string? subRemarks)
|
||||
{
|
||||
var jsonNode = JsonUtils.ParseJson(strData);
|
||||
return ResolveCommon(jsonNode, subRemarks, true);
|
||||
}
|
||||
|
||||
private static List<ProfileItem> ResolveCommon(JsonNode? jsonNode, string? subRemarks, bool isOutbound)
|
||||
{
|
||||
if (jsonNode is JsonArray jsonArray)
|
||||
{
|
||||
return
|
||||
[
|
||||
.. jsonArray.Select(item => ResolveCommon(item, subRemarks, isOutbound))
|
||||
.Where(list => list is { Count: > 0 })
|
||||
.SelectMany(list => list),
|
||||
];
|
||||
}
|
||||
if (jsonNode is not JsonObject jsonObject)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
// Process the individual JSON object
|
||||
var profileList = new List<ProfileItem>();
|
||||
if (!isOutbound)
|
||||
{
|
||||
var fullProfile = ResolveFull(jsonObject, subRemarks);
|
||||
profileList.Add(fullProfile);
|
||||
if (fullProfile is not null)
|
||||
{
|
||||
return profileList;
|
||||
}
|
||||
}
|
||||
profileList.AddRange(ResolveFullToOutbound(jsonObject, subRemarks));
|
||||
if (profileList.Count != 0)
|
||||
{
|
||||
return profileList;
|
||||
}
|
||||
var outboundProfile = ResolveOutbound(jsonObject, subRemarks);
|
||||
if (outboundProfile is not null)
|
||||
{
|
||||
profileList.Add(outboundProfile);
|
||||
}
|
||||
return profileList;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveFull(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (jsonObject?["inbounds"] == null
|
||||
|| jsonObject["outbounds"] == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var configObject in configObjects)
|
||||
if (jsonObject["outbounds"] is JsonArray outboundsArray)
|
||||
{
|
||||
var objectString = JsonUtils.Serialize(configObject);
|
||||
var profileIt = ResolveFull(objectString, subRemarks);
|
||||
if (!outboundsArray.Any(IsValidSingboxOutbound))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
CoreType = ECoreType.sing_box,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "singbox_custom",
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
private static List<ProfileItem> ResolveFullToOutbound(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (jsonObject?["outbounds"] is not JsonArray outboundsArray)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var outbound in outboundsArray)
|
||||
{
|
||||
if (outbound is not JsonObject outboundObj)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var profileIt = ResolveOutbound(outboundObj, subRemarks);
|
||||
if (profileIt != null)
|
||||
{
|
||||
lstResult.Add(profileIt);
|
||||
@@ -23,25 +106,58 @@ public class SingboxFmt : BaseFmt
|
||||
return lstResult;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
private static ProfileItem? ResolveOutbound(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
var config = JsonUtils.ParseJson(strData);
|
||||
if (config?["inbounds"] == null
|
||||
|| config["outbounds"] == null
|
||||
|| config["route"] == null
|
||||
|| config["dns"] == null)
|
||||
if (!IsValidSingboxOutbound(jsonObject))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = WriteAllText(strData);
|
||||
var type = jsonObject["type"]?.ToString();
|
||||
if (type is null or "direct" or "block" or "dns" or "selector" or "urltest")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var tag = jsonObject["tag"]?.ToString();
|
||||
var remarks = $"{type}_{tag}";
|
||||
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
ConfigType = EConfigType.Outbound,
|
||||
CoreType = ECoreType.sing_box,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "singbox_custom"
|
||||
Remarks = remarks,
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
private static bool IsValidSingboxOutbound(JsonNode? jsonNode)
|
||||
{
|
||||
if (jsonNode is not JsonObject jsonObject)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var matchedCounter = 0;
|
||||
if (string.IsNullOrEmpty(jsonObject["type"]?.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
matchedCounter += 1;
|
||||
if (!string.IsNullOrEmpty(jsonObject["tag"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["server"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["server_port"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["tls"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
return matchedCounter >= 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class TrojanFmt : BaseFmt
|
||||
{
|
||||
private static readonly List<string> _insecureQueryKeys = new() { "allowInsecure", "insecure" };
|
||||
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
@@ -23,6 +25,10 @@ public class TrojanFmt : BaseFmt
|
||||
item.Password = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
if (_insecureQueryKeys.Any(q => GetQueryValue(query, q) == "1"))
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with { Flow = GetQueryValue(query, "flow") });
|
||||
ResolveUriQuery(query, ref item);
|
||||
|
||||
@@ -41,6 +47,10 @@ public class TrojanFmt : BaseFmt
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
_insecureQueryKeys.ForEach(q => dicQuery.Add(q, "1"));
|
||||
}
|
||||
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
|
||||
|
||||
@@ -30,6 +30,10 @@ public class TuicFmt : BaseFmt
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveUriQuery(query, ref item);
|
||||
if (GetQueryValue(query, "allow_insecure") == "1")
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
CongestionControl = GetQueryValue(query, "congestion_control")
|
||||
@@ -53,7 +57,10 @@ public class TuicFmt : BaseFmt
|
||||
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
ToUriQueryLite(item, ref dicQuery);
|
||||
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
dicQuery.Add("allow_insecure", "1");
|
||||
}
|
||||
if (!item.GetProtocolExtra().CongestionControl.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("congestion_control", item.GetProtocolExtra().CongestionControl);
|
||||
|
||||
@@ -2,47 +2,165 @@ namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class V2rayFmt : BaseFmt
|
||||
{
|
||||
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
||||
public static List<ProfileItem> ResolveToCustom(string strData, string? subRemarks)
|
||||
{
|
||||
var configObjects = JsonUtils.Deserialize<object[]>(strData);
|
||||
if (configObjects is not { Length: > 0 })
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var configObject in configObjects)
|
||||
{
|
||||
var objectString = JsonUtils.Serialize(configObject);
|
||||
var profileIt = ResolveFull(objectString, subRemarks);
|
||||
if (profileIt != null)
|
||||
{
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
}
|
||||
|
||||
return lstResult;
|
||||
var jsonNode = JsonUtils.ParseJson(strData);
|
||||
return ResolveCommon(jsonNode, subRemarks, false);
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
public static List<ProfileItem> ResolveToCustomOutbound(string strData, string? subRemarks)
|
||||
{
|
||||
var config = JsonUtils.ParseJson(strData);
|
||||
if (config?["inbounds"] == null
|
||||
|| config["outbounds"] == null
|
||||
|| config["routing"] == null)
|
||||
var jsonNode = JsonUtils.ParseJson(strData);
|
||||
return ResolveCommon(jsonNode, subRemarks, true);
|
||||
}
|
||||
|
||||
private static List<ProfileItem> ResolveCommon(JsonNode? jsonNode, string? subRemarks, bool isOutbound)
|
||||
{
|
||||
if (jsonNode is JsonArray jsonArray)
|
||||
{
|
||||
return
|
||||
[
|
||||
.. jsonArray.Select(item => ResolveCommon(item, subRemarks, isOutbound))
|
||||
.Where(list => list is { Count: > 0 })
|
||||
.SelectMany(list => list),
|
||||
];
|
||||
}
|
||||
if (jsonNode is not JsonObject jsonObject)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
// Process the individual JSON object
|
||||
var profileList = new List<ProfileItem>();
|
||||
if (!isOutbound)
|
||||
{
|
||||
var fullProfile = ResolveFull(jsonObject, subRemarks);
|
||||
profileList.Add(fullProfile);
|
||||
if (fullProfile is not null)
|
||||
{
|
||||
return profileList;
|
||||
}
|
||||
}
|
||||
profileList.AddRange(ResolveFullToOutbound(jsonObject, subRemarks));
|
||||
if (profileList.Count != 0)
|
||||
{
|
||||
return profileList;
|
||||
}
|
||||
var outboundProfile = ResolveOutbound(jsonObject, subRemarks);
|
||||
if (outboundProfile is not null)
|
||||
{
|
||||
profileList.Add(outboundProfile);
|
||||
}
|
||||
return profileList;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveFull(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (jsonObject?["inbounds"] == null
|
||||
|| jsonObject["outbounds"] == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = WriteAllText(strData);
|
||||
if (jsonObject["outbounds"] is JsonArray outboundsArray)
|
||||
{
|
||||
if (!outboundsArray.Any(IsValidV2rayOutbound))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
CoreType = ECoreType.Xray,
|
||||
Address = fileName,
|
||||
Remarks = config?["remarks"]?.ToString() ?? subRemarks ?? "v2ray_custom"
|
||||
Remarks = jsonObject["remarks"]?.ToString() ?? subRemarks ?? "v2ray_custom",
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
public static List<ProfileItem> ResolveFullToOutbound(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (jsonObject["outbounds"] is not JsonArray outboundsArray)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var outbound in outboundsArray)
|
||||
{
|
||||
if (outbound is not JsonObject outboundObj)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var profileIt = ResolveOutbound(outboundObj, subRemarks);
|
||||
if (profileIt != null)
|
||||
{
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
}
|
||||
return lstResult;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveOutbound(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (!IsValidV2rayOutbound(jsonObject))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var protocol = jsonObject["protocol"]?.ToString();
|
||||
if (protocol is null or "freedom" or "blackhole" or "dns" or "loopback")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var tag = jsonObject["tag"]?.ToString();
|
||||
var remarks = $"{protocol}_{tag}";
|
||||
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
ConfigType = EConfigType.Outbound,
|
||||
CoreType = ECoreType.Xray,
|
||||
Address = fileName,
|
||||
Remarks = remarks,
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
private static bool IsValidV2rayOutbound(JsonNode? jsonNode)
|
||||
{
|
||||
if (jsonNode is not JsonObject jsonObject)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var matchedCounter = 0;
|
||||
if (string.IsNullOrEmpty(jsonObject["protocol"]?.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
matchedCounter += 1;
|
||||
if (!string.IsNullOrEmpty(jsonObject["settings"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["streamSettings"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["tag"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["mux"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
|
||||
return matchedCounter >= 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ public static class SysProxyHandler
|
||||
try
|
||||
{
|
||||
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
if (port <= 0)
|
||||
{
|
||||
return false;
|
||||
@@ -24,17 +23,18 @@ public static class SysProxyHandler
|
||||
switch (type)
|
||||
{
|
||||
case ESysProxyType.ForcedChange when Utils.IsWindows():
|
||||
{
|
||||
GetWindowsProxyString(config, port, out var strProxy, out var strExceptions);
|
||||
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
|
||||
break;
|
||||
}
|
||||
var (strProxy, strExceptions) = GetWindowsProxyString(config, port);
|
||||
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedChange when Utils.IsLinux():
|
||||
var exceptions = SanitizeExceptions(config);
|
||||
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedChange when Utils.IsMacOS():
|
||||
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions);
|
||||
var exceptions2 = SanitizeExceptions(config);
|
||||
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions2);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedClear when Utils.IsWindows():
|
||||
@@ -66,15 +66,31 @@ public static class SysProxyHandler
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void GetWindowsProxyString(Config config, int port, out string strProxy, out string strExceptions)
|
||||
private static string SanitizeExceptions(Config config)
|
||||
{
|
||||
strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions;
|
||||
if (exceptions.IsNullOrEmpty())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var items = exceptions
|
||||
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(item => item.Replace(" ", string.Empty))
|
||||
.Where(item => item.Length > 0);
|
||||
|
||||
return string.Join(',', items);
|
||||
}
|
||||
|
||||
private static (string strProxy, string strExceptions) GetWindowsProxyString(Config config, int port)
|
||||
{
|
||||
var strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
if (config.SystemProxyItem.NotProxyLocalAddress)
|
||||
{
|
||||
strExceptions = $"<local>;{strExceptions}";
|
||||
}
|
||||
|
||||
strProxy = string.Empty;
|
||||
var strProxy = string.Empty;
|
||||
if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty())
|
||||
{
|
||||
strProxy = $"{Global.Loopback}:{port}";
|
||||
@@ -86,6 +102,8 @@ public static class SysProxyHandler
|
||||
.Replace("{http_port}", port.ToString())
|
||||
.Replace("{socks_port}", port.ToString());
|
||||
}
|
||||
|
||||
return (strProxy, strExceptions);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
|
||||
@@ -10,6 +10,7 @@ public sealed class AppManager
|
||||
private int? _statePort2;
|
||||
public static AppManager Instance => _instance.Value;
|
||||
public Config Config => _config;
|
||||
public IWindowDialog WindowDialog { get; set; } = null!;
|
||||
|
||||
public int StatePort
|
||||
{
|
||||
@@ -662,7 +663,7 @@ public sealed class AppManager
|
||||
return Global.SsSecuritiesInSingbox;
|
||||
}
|
||||
|
||||
public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType)
|
||||
public ECoreType GetCoreType(ProfileItem? profileItem, EConfigType eConfigType)
|
||||
{
|
||||
if (profileItem?.CoreType != null)
|
||||
{
|
||||
|
||||
@@ -34,7 +34,18 @@ public class CoreAdminManager
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine("#!/bin/bash");
|
||||
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
|
||||
sb.AppendLine($"exec sudo -S -- {cmdLine}");
|
||||
|
||||
// Passing environment variables to the sudo command, here it only xray or sing-box.
|
||||
if (coreInfo.Environment.Count > 0)
|
||||
{
|
||||
var envArgs = string.Join(" ", coreInfo.Environment.Where(kv => kv.Value.IsNotEmpty()).Select(kv => $"{kv.Key}={kv.Value.AppendQuotes()}"));
|
||||
sb.AppendLine($"exec sudo -S -- env {envArgs} {cmdLine}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"exec sudo -S -- {cmdLine}");
|
||||
}
|
||||
|
||||
var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
|
||||
|
||||
var procService = new ProcessService(
|
||||
@@ -80,6 +91,8 @@ public class CoreAdminManager
|
||||
.ExecuteBufferedAsync();
|
||||
|
||||
await UpdateFunc(false, result.StandardOutput.ToString());
|
||||
|
||||
await Task.Delay(1000); // Wait for a second to ensure the process is killed
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ public class CoreManager
|
||||
await CoreStop();
|
||||
await Task.Delay(100);
|
||||
|
||||
if (Utils.IsWindows() && _config.TunModeItem.EnableTun)
|
||||
if (Utils.IsWindows() && (mainContext?.IsTunEnabled == true || preContext?.IsTunEnabled == true))
|
||||
{
|
||||
await Task.Delay(100);
|
||||
await WindowsUtils.RemoveTunDevice();
|
||||
@@ -183,7 +183,7 @@ public class CoreManager
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||
|
||||
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
||||
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
|
||||
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true, context.IsTunEnabled);
|
||||
if (proc is null)
|
||||
{
|
||||
return;
|
||||
@@ -201,7 +201,7 @@ public class CoreManager
|
||||
if (result.Success)
|
||||
{
|
||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
|
||||
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
|
||||
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true, preContext.IsTunEnabled);
|
||||
if (proc is null)
|
||||
{
|
||||
return;
|
||||
@@ -222,7 +222,7 @@ public class CoreManager
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!preContext.AppConfig.TunModeItem.EnableTun)
|
||||
if (!preContext.IsTunEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -289,7 +289,20 @@ public class CoreManager
|
||||
|
||||
#region Process
|
||||
|
||||
private async Task<ProcessService?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
|
||||
/// <summary>
|
||||
/// Decides whether a core launch must be elevated on non-Windows platforms.
|
||||
/// The TUN state comes from the immutable <see cref="CoreConfigContext" /> snapshot that
|
||||
/// generated the config, never from the live mutable config: the generated config and the
|
||||
/// launch mode must always agree, even if TUN is toggled while a reload is in flight.
|
||||
/// </summary>
|
||||
public static bool ShouldRunAsSudo(bool isTunLaunch, ECoreType? coreType, bool isNonWindows)
|
||||
{
|
||||
return isTunLaunch
|
||||
&& coreType is ECoreType.sing_box or ECoreType.mihomo or ECoreType.Xray
|
||||
&& isNonWindows;
|
||||
}
|
||||
|
||||
private async Task<ProcessService?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo, bool isTunLaunch = false)
|
||||
{
|
||||
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
|
||||
if (fileName.IsNullOrEmpty())
|
||||
@@ -301,9 +314,7 @@ public class CoreManager
|
||||
try
|
||||
{
|
||||
if (mayNeedSudo
|
||||
&& _config.TunModeItem.EnableTun
|
||||
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo or ECoreType.Xray)
|
||||
&& Utils.IsNonWindows())
|
||||
&& ShouldRunAsSudo(isTunLaunch, coreInfo.CoreType, Utils.IsNonWindows()))
|
||||
{
|
||||
_linuxSudo = true;
|
||||
await CoreAdminManager.Instance.Init(_config, _updateFunc);
|
||||
|
||||
@@ -118,7 +118,7 @@ public class GroupProfileManager
|
||||
return childProfiles?.Where(p =>
|
||||
p != null &&
|
||||
p.IsValid() &&
|
||||
!p.ConfigType.IsComplexType() &&
|
||||
(!p.ConfigType.IsComplexType() || p.ConfigType == EConfigType.Outbound) &&
|
||||
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
|
||||
)
|
||||
.ToList() ?? [];
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//using System.Reactive.Linq;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
|
||||
public class ProfileExManager
|
||||
|
||||
@@ -34,6 +34,7 @@ public class Config
|
||||
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
||||
public List<CoreTypeItem> CoreTypeItem { get; set; }
|
||||
public SimpleDNSItem SimpleDNSItem { get; set; }
|
||||
public HappyEyeballs4RayItem HappyEyeballs4RayItem { get; set; }
|
||||
|
||||
#endregion other entities
|
||||
}
|
||||
|
||||
@@ -147,8 +147,10 @@ public class TunModeItem
|
||||
public int Mtu { get; set; }
|
||||
public bool EnableIPv6Address { get; set; }
|
||||
public string IcmpRouting { get; set; }
|
||||
public bool EnableLegacyProtect { get; set; }
|
||||
public bool EnableLegacyProtect { get; set; } = true;
|
||||
public List<string>? RouteExcludeAddress { get; set; }
|
||||
public string IPv4Address { get; set; }
|
||||
public string IPv6Address { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -249,9 +251,15 @@ public class CheckUpdateItem
|
||||
public class Fragment4RayItem
|
||||
{
|
||||
public string? Packets { get; set; }
|
||||
public string? Length { get; set; }
|
||||
public string? Interval { get; set; }
|
||||
public List<string>? Lengths { get; set; }
|
||||
public List<string>? Delays { get; set; }
|
||||
public string? MaxSplit { get; set; }
|
||||
|
||||
// For migration from old version, remove those properties in the future
|
||||
public string? Length { get; set; }
|
||||
|
||||
public string? Interval { get; set; }
|
||||
// migration end
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -269,14 +277,26 @@ public class SimpleDNSItem
|
||||
public bool? AddCommonHosts { get; set; }
|
||||
public bool? FakeIP { get; set; }
|
||||
public bool? GlobalFakeIp { get; set; }
|
||||
public string? FakeIPRange { get; set; }
|
||||
public bool? BlockBindingQuery { get; set; }
|
||||
public string? DirectDNS { get; set; }
|
||||
public string? RemoteDNS { get; set; }
|
||||
public string? BootstrapDNS { get; set; }
|
||||
public string? Strategy4Freedom { get; set; }
|
||||
public string? Strategy4Proxy { get; set; }
|
||||
public string? Strategy4ProxyDial { get; set; }
|
||||
public bool? ServeStale { get; set; }
|
||||
public bool? ParallelQuery { get; set; }
|
||||
public string? Hosts { get; set; }
|
||||
public string? DirectExpectedIPs { get; set; }
|
||||
public bool? EnableHappyEyeballs { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class HappyEyeballs4RayItem
|
||||
{
|
||||
public int? TryDelayMs { get; set; }
|
||||
public bool? PrioritizeIPv6 { get; set; }
|
||||
public int? Interleave { get; set; }
|
||||
public int? MaxConcurrentTry { get; set; }
|
||||
}
|
||||
|
||||
@@ -11,13 +11,20 @@ public record CoreConfigContext
|
||||
public Config AppConfig { get; init; } = new();
|
||||
public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new();
|
||||
|
||||
public Dictionary<string, string> CustomOutboundContent { get; init; } = new();
|
||||
|
||||
// Test ServerTestItem Map
|
||||
public Dictionary<string, string> ServerTestItemMap { get; init; } = new();
|
||||
|
||||
// TUN Compatibility
|
||||
public bool IsTunEnabled { get; init; } = false;
|
||||
public HashSet<string> ProtectDomainList { get; init; } = [];
|
||||
// Typically, it is the core of the outbound chain
|
||||
public HashSet<ECoreType> ProtectCoreTypeList { get; init; } = [];
|
||||
|
||||
public bool IsWindows { get; init; }
|
||||
public bool IsMacOS { get; init; }
|
||||
|
||||
// Generation Context
|
||||
public Dictionary<object, string> CustomOutboundMap { get; init; } = new();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ public class V2rayConfig
|
||||
{
|
||||
public Log4Ray log { get; set; }
|
||||
public object dns { get; set; }
|
||||
public FakeDns4Ray? fakedns { get; set; }
|
||||
public List<Inbounds4Ray> inbounds { get; set; }
|
||||
public List<Outbounds4Ray> outbounds { get; set; }
|
||||
public Routing4Ray routing { get; set; }
|
||||
@@ -43,6 +44,12 @@ public class Log4Ray
|
||||
public string? loglevel { get; set; }
|
||||
}
|
||||
|
||||
public class FakeDns4Ray
|
||||
{
|
||||
public string? ipPool { get; set; }
|
||||
public long? poolSize { get; set; }
|
||||
}
|
||||
|
||||
public class Inbounds4Ray
|
||||
{
|
||||
public string tag { get; set; }
|
||||
@@ -136,8 +143,6 @@ public class Outboundsettings4Ray
|
||||
|
||||
public Response4Ray? response { get; set; }
|
||||
|
||||
public string? domainStrategy { get; set; }
|
||||
|
||||
public int? userLevel { get; set; }
|
||||
|
||||
public string? secretKey { get; set; }
|
||||
@@ -359,8 +364,6 @@ public class StreamSettings4Ray
|
||||
|
||||
public class TlsSettings4Ray
|
||||
{
|
||||
public bool? allowInsecure { get; set; }
|
||||
|
||||
public string? serverName { get; set; }
|
||||
|
||||
public List<string>? alpn { get; set; }
|
||||
@@ -516,6 +519,8 @@ public class MaskSettings4Ray
|
||||
|
||||
public string? length { get; set; }
|
||||
public string? delay { get; set; }
|
||||
public List<string>? lengths { get; set; }
|
||||
public List<string>? delays { get; set; }
|
||||
public int? maxSplit { get; set; }
|
||||
|
||||
// noise
|
||||
@@ -547,15 +552,20 @@ public class AccountsItem4Ray
|
||||
|
||||
public class Sockopt4Ray
|
||||
{
|
||||
public string? domainStrategy { get; set; }
|
||||
|
||||
public string? dialerProxy { get; set; }
|
||||
|
||||
[JsonPropertyName("interface")]
|
||||
public string? Interface { get; set; }
|
||||
|
||||
public HappyEyeballs4Ray? happyEyeballs { get; set; }
|
||||
}
|
||||
|
||||
public class FragmentItem4Ray
|
||||
public class HappyEyeballs4Ray
|
||||
{
|
||||
public string? packets { get; set; }
|
||||
public string? length { get; set; }
|
||||
public string? interval { get; set; }
|
||||
public int? tryDelayMs { get; set; }
|
||||
public bool? prioritizeIPv6 { get; set; }
|
||||
public int? interleave { get; set; }
|
||||
public int? maxConcurrentTry { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
public class CheckUpdateModel : ReactiveObject
|
||||
public partial class CheckUpdateModel : ReactiveObject
|
||||
{
|
||||
public bool? IsSelected { get; set; }
|
||||
public ECoreType? CoreType { get; set; }
|
||||
[Reactive] public string? Remarks { get; set; }
|
||||
[Reactive] public partial string? Remarks { get; set; }
|
||||
public string? FileName { get; set; }
|
||||
public bool? IsFinished { get; set; }
|
||||
public bool IsGeoFile { get; set; }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
[Serializable]
|
||||
public class ClashProxyModel : ReactiveObject
|
||||
public partial class ClashProxyModel : ReactiveObject
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
|
||||
@@ -9,9 +9,9 @@ public class ClashProxyModel : ReactiveObject
|
||||
|
||||
public string? Now { get; set; }
|
||||
|
||||
[Reactive] public int Delay { get; set; }
|
||||
[Reactive] public partial int Delay { get; set; }
|
||||
|
||||
[Reactive] public string? DelayName { get; set; }
|
||||
[Reactive] public partial string? DelayName { get; set; }
|
||||
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
[Serializable]
|
||||
public class ProfileItemModel : ReactiveObject
|
||||
public partial class ProfileItemModel : ReactiveObject
|
||||
{
|
||||
public bool IsActive { get; set; }
|
||||
public string IndexId { get; set; }
|
||||
@@ -16,30 +16,30 @@ public class ProfileItemModel : ReactiveObject
|
||||
public int Sort { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int Delay { get; set; }
|
||||
public partial int Delay { get; set; }
|
||||
|
||||
public decimal Speed { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string DelayVal { get; set; }
|
||||
public partial string DelayVal { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SpeedVal { get; set; }
|
||||
public partial string SpeedVal { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string IpInfo { get; set; }
|
||||
public partial string IpInfo { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string TodayUp { get; set; }
|
||||
public partial string TodayUp { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string TodayDown { get; set; }
|
||||
public partial string TodayDown { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string TotalUp { get; set; }
|
||||
public partial string TotalUp { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string TotalDown { get; set; }
|
||||
public partial string TotalDown { get; set; }
|
||||
|
||||
public string GetSummary()
|
||||
{
|
||||
|
||||
@@ -66,7 +66,7 @@ public class ProfileItem
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
if (IsComplex())
|
||||
if (IsComplex() || ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,4 +51,7 @@ public record ProtocolExtraItem
|
||||
public string? SubChildItems { get; init; }
|
||||
public string? Filter { get; init; }
|
||||
public EMultipleLoad? MultipleLoad { get; init; }
|
||||
|
||||
// custom outbound
|
||||
public bool? IsSingboxEndpoint { get; init; }
|
||||
}
|
||||
|
||||
@@ -33,4 +33,6 @@ public class SubItem
|
||||
public int? PreSocksPort { get; set; }
|
||||
|
||||
public string? Memo { get; set; }
|
||||
|
||||
public ECoreType? CustomCoreType { get; set; }
|
||||
}
|
||||
|
||||
94
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
94
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -438,6 +438,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Custom config core 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string LvCustomCoreType {
|
||||
get {
|
||||
return ResourceManager.GetString("LvCustomCoreType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Custom icon 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -735,6 +744,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add a custom outbound 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuAddCustomOutboundServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuAddCustomOutboundServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add a custom configuration 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1933,7 +1951,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Warning: Xray will disable allowInsecure (skip certificate verification) in August 2026. Please switch to pinnedPeerCertSha256 (fixed certificate fingerprint) as soon as possible. allowInsecure will not be usable after its expiration. 的本地化字符串。
|
||||
/// 查找类似 The current node uses an unencrypted connection, meaning your communications could be directly monitored by network intermediaries controlled by authoritarian governments. For security reasons, nodes of this type cannot connect via Xray-core versions 26.2.6 or higher. If this is a self-built node, please enable TLS or other secure encryption, or pin the certificate using pinSHA256. If this is an airport/provider node, please contact your service provider for a technical upgrade. If the provider refuses to c [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgAllowInsecureDeprecated {
|
||||
get {
|
||||
@@ -1977,6 +1995,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Custom outbound {0} file not found: {1} 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgCustomOutboundFileNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgCustomOutboundFileNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Downloaded GeoFile: {0} successfully 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2925,6 +2952,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Only single outbound/endpoint supported for xray/sing-box 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbCustomOutboundTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbCustomOutboundTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Direct Target Resolution Strategy 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3060,6 +3096,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Enable Happy Eyeballs 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbEnableHappyEyeballs {
|
||||
get {
|
||||
return ResourceManager.GetString("TbEnableHappyEyeballs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Requires the UseIP Strategy. When enabled, it attempts IPv4 and IPv6 connections simultaneously and automatically selects the faster available path. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbEnableHappyEyeballsTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbEnableHappyEyeballsTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 DNS via Bridge 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3088,7 +3142,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Applies globally by default, with built-in FakeIP filtering (sing-box only). 的本地化字符串。
|
||||
/// 查找类似 Applies globally by default, and built-in FakeIP filtering is only built into sing-box. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbFakeIPTips {
|
||||
get {
|
||||
@@ -3330,6 +3384,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Ipv4 Address 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbIpv4Address {
|
||||
get {
|
||||
return ResourceManager.GetString("TbIpv4Address", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Ipv6 Address 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbIpv6Address {
|
||||
get {
|
||||
return ResourceManager.GetString("TbIpv6Address", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Most Stable 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3528,6 +3600,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Proxy Dial Resolution Strategy 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbProxyDialResolveStrategy {
|
||||
get {
|
||||
return ResourceManager.GetString("TbProxyDialResolveStrategy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Not recommended; may cause routing loops. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbProxyDialResolveStrategyTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbProxyDialResolveStrategyTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Public Key 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -1495,7 +1495,7 @@
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
|
||||
@@ -1492,7 +1492,7 @@
|
||||
<value>Choisir une config.</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Actif globalement par défaut, avec filtre FakeIP intégré ; ne fonctionne que dans sing-box</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Veuillez ajouter au moins une configuration</value>
|
||||
|
||||
@@ -1495,7 +1495,7 @@
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
|
||||
@@ -1495,7 +1495,7 @@
|
||||
<value>Pilih profil</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Berlaku secara global secara default, dengan filter FakeIP bawaan (hanya sing-box).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Silakan tambahkan setidaknya satu konfigurasi.</value>
|
||||
|
||||
@@ -1501,7 +1501,7 @@
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
@@ -1780,7 +1780,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<value>New Update</value>
|
||||
</data>
|
||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
||||
<value>Warning: Xray will disable allowInsecure (skip certificate verification) in August 2026. Please switch to pinnedPeerCertSha256 (fixed certificate fingerprint) as soon as possible. allowInsecure will not be usable after its expiration.</value>
|
||||
<value>The current node uses an unencrypted connection, meaning your communications could be directly monitored by network intermediaries controlled by authoritarian governments. For security reasons, nodes of this type cannot connect via Xray-core versions 26.2.6 or higher. If this is a self-built node, please enable TLS or other secure encryption, or pin the certificate using pinSHA256. If this is an airport/provider node, please contact your service provider for a technical upgrade. If the provider refuses to cooperate, it is recommended to switch to a service provider that prioritizes user security. For more information, please visit https://github.com/2dust/v2rayN/discussions/9460.</value>
|
||||
</data>
|
||||
<data name="TbRouteExcludeAddress" xml:space="preserve">
|
||||
<value>Route Exclude Address</value>
|
||||
@@ -1830,4 +1830,34 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>Only applies to the v2rayN GUI's downloads and network requests. Does not affect the core's certificate validation.</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
|
||||
<value>Proxy Dial Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
|
||||
<value>Not recommended; may cause routing loops.</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballs" xml:space="preserve">
|
||||
<value>Enable Happy Eyeballs</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
|
||||
<value>Requires the UseIP Strategy. When enabled, it attempts IPv4 and IPv6 connections simultaneously and automatically selects the faster available path.</value>
|
||||
</data>
|
||||
<data name="TbIpv6Address" xml:space="preserve">
|
||||
<value>Ipv6 Address</value>
|
||||
</data>
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 Address</value>
|
||||
</data>
|
||||
<data name="menuAddCustomOutboundServer" xml:space="preserve">
|
||||
<value>Add a custom outbound</value>
|
||||
</data>
|
||||
<data name="MsgCustomOutboundFileNotFound" xml:space="preserve">
|
||||
<value>Custom outbound {0} file not found: {1}</value>
|
||||
</data>
|
||||
<data name="TbCustomOutboundTip" xml:space="preserve">
|
||||
<value>Only single outbound/endpoint supported for xray/sing-box</value>
|
||||
</data>
|
||||
<data name="LvCustomCoreType" xml:space="preserve">
|
||||
<value>Custom config core</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1044,6 +1044,12 @@
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>Управление перегрузками</value>
|
||||
</data>
|
||||
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>HTTP-заголовки</value>
|
||||
</data>
|
||||
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>Пользовательские заголовки исходящего HTTP-запроса. Введите JSON-объект, значения которого — строка или массив строк.</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>Псевдоним предыдущего прокси</value>
|
||||
</data>
|
||||
@@ -1495,7 +1501,7 @@
|
||||
<value>Выбрать профиль</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>По умолчанию применяется глобально, со встроенной фильтрацией FakeIP (только sing-box).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Добавьте хотя бы одну конфигурацию</value>
|
||||
@@ -1614,6 +1620,9 @@
|
||||
<data name="TbEchConfigList" xml:space="preserve">
|
||||
<value>EchConfigList</value>
|
||||
</data>
|
||||
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
||||
<value>Проверять сертификат узла по имени</value>
|
||||
</data>
|
||||
<data name="TbFullCertTips" xml:space="preserve">
|
||||
<value>Полный сертификат (цепочка) в формате PEM</value>
|
||||
</data>
|
||||
@@ -1770,4 +1779,55 @@
|
||||
<data name="menuNewUpdate" xml:space="preserve">
|
||||
<value>Доступно обновление</value>
|
||||
</data>
|
||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
||||
<value>Предупреждение: 1 августа 2026 г. Xray отключит пропуск проверки сертификата (allowInsecure). Как можно скорее перейдите на привязанный отпечаток сертификата (pinnedPeerCertSha256). После этой даты allowInsecure использовать будет нельзя.</value>
|
||||
</data>
|
||||
<data name="TbRouteExcludeAddress" xml:space="preserve">
|
||||
<value>Адреса, исключаемые из маршрутизации</value>
|
||||
</data>
|
||||
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
|
||||
<value>Разделяйте запятыми (,).</value>
|
||||
</data>
|
||||
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
|
||||
<value>Недопустимый адрес в списке исключений маршрутизации TUN: {0}</value>
|
||||
</data>
|
||||
<data name="MsgOptionsConflict" xml:space="preserve">
|
||||
<value>Конфликт между {0} и {1}</value>
|
||||
</data>
|
||||
<data name="TbEnableFinalFragment" xml:space="preserve">
|
||||
<value>Включить финальную фрагментацию (Final Fragment)</value>
|
||||
</data>
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>Разбивать конец пакетов на более мелкие фрагменты при отправке. Это может влиять на пропускную способность и задержку.</value>
|
||||
</data>
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS через Bridge</value>
|
||||
</data>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>Обнаружена небезопасная конфигурация: AllowInsecure включён, но сертификат не предоставлен. Это может привести к атаке «человек посередине» (MITM).</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL</value>
|
||||
</data>
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>Некорректный Realm URL.</value>
|
||||
</data>
|
||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>Введите корректный JSON заголовков HTTP-запроса.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>Формат: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||
<value>Размер пакета Gecko (мин/макс)</value>
|
||||
</data>
|
||||
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||
<value>Если включено, используется sing-box TUN; иначе — xray TUN.</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||
<value>Поставщик корневых сертификатов</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>Применяется только к загрузкам и сетевым запросам графического интерфейса v2rayN. Не влияет на проверку сертификатов ядром.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1498,7 +1498,7 @@
|
||||
<value>选择配置</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
|
||||
<value>默认全局生效,仅在 sing-box 中内置 FakeIP 过滤。</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>请至少添加一个配置</value>
|
||||
@@ -1774,7 +1774,11 @@
|
||||
<value>有更新</value>
|
||||
</data>
|
||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
||||
<value>警告:Xray 将在 2026.8.1 禁用跳过证书验证 allowInsecure ,请尽快改用证书固定指纹 pinnedPeerCertSha256。到期后无法使用 </value>
|
||||
<value>当前节点使用未加密连接,您的通信可能会被威权政府掌控的网络中间设施直接查看
|
||||
为了安全,此类节点无法通过 26.2.6+ 版本的Xray核心连接
|
||||
如果是自建节点请启用 TLS 等安全加密,或固定证书 pinSHA256
|
||||
如果机场节点请联系服务商完成技术升级,如服务商拒绝配合,建议更换更重视用户安全的服务商
|
||||
更多的信息,请访问 https://github.com/2dust/v2rayN/discussions/9460</value>
|
||||
</data>
|
||||
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
||||
<value>Verify Peer Cert By Name</value>
|
||||
@@ -1827,4 +1831,34 @@
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>仅用于 v2rayN 界面程序的下载及网络请求,不影响核心的证书验证。</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
|
||||
<value>连接代理解析策略</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
|
||||
<value>不建议开启,特殊情况可能回环。</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballs" xml:space="preserve">
|
||||
<value>启用 Happy Eyeballs</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
|
||||
<value>需配合 UseIP 策略使用。启用后将同时尝试 IPv4 和 IPv6 连接,并自动选择更快可用的路径。</value>
|
||||
</data>
|
||||
<data name="TbIpv6Address" xml:space="preserve">
|
||||
<value>Ipv6 地址</value>
|
||||
</data>
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 地址</value>
|
||||
</data>
|
||||
<data name="menuAddCustomOutboundServer" xml:space="preserve">
|
||||
<value>添加自定义出站</value>
|
||||
</data>
|
||||
<data name="MsgCustomOutboundFileNotFound" xml:space="preserve">
|
||||
<value>自定义出站 {0} 的文件未找到:{1}</value>
|
||||
</data>
|
||||
<data name="TbCustomOutboundTip" xml:space="preserve">
|
||||
<value>仅支持 xray/sing-box 的单个 outbound/endpoin</value>
|
||||
</data>
|
||||
<data name="LvCustomCoreType" xml:space="preserve">
|
||||
<value>自定义配置核心</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1498,7 +1498,7 @@
|
||||
<value>選擇節點</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>默認全域生效,內置 FakeIP 過濾,僅在 sing-box 中生效</value>
|
||||
<value>默認全局生效,僅在 sing-box 中內置 FakeIP 過濾。</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>請至少添加一個節點</value>
|
||||
@@ -1774,7 +1774,11 @@
|
||||
<value>有更新</value>
|
||||
</data>
|
||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
||||
<value>警告:Xray 將在 2026.8.1 停用跳過憑證驗證 allowInsecure ,請盡快改用憑證固定指紋 pinnedPeerCertSha256。到期後無法使用 allowInsecure。</value>
|
||||
<value>目前節點使用未加密連接,您的通訊可能會被威權政府掌控的網路中間設施直接查看
|
||||
為了安全,此類節點無法透過 26.2.6+ 版本的Xray核心連接
|
||||
如果是自建節點請啟用 TLS 等安全加密,或固定憑證 pinSHA256
|
||||
若機場節點請聯絡服務商完成技術升級,如服務商拒絕配合,建議更換更重視用戶安全的服務商
|
||||
更多的信息,請訪問 https://github.com/2dust/v2rayN/discussions/9460</value>
|
||||
</data>
|
||||
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
||||
<value>Verify Peer Cert By Name</value>
|
||||
@@ -1800,10 +1804,49 @@
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS 透過 Bridge</value>
|
||||
</data>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>偵測到不安全的設定:已啟用 AllowInsecure,但未提供憑證。這可能導致中間人攻擊(MITM)</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL</value>
|
||||
</data>
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>無效的 Realm URL.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>格式: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||
<value>Gecko 封包大小 (min/max)</value>
|
||||
</data>
|
||||
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||
<value>啟用則使用 sing-box TUN ,否則使用 xray TUN</value>
|
||||
</data>
|
||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>請填寫合法的HTTP請求頭JSON。</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||
<value>根憑證提供者</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>僅適用於 v2rayN GUI 的下載與網路請求,不影響核心的憑證驗證</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
|
||||
<value>連線代理解析策略</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
|
||||
<value>不建議啟用,特殊情況可能造成連線循環</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballs" xml:space="preserve">
|
||||
<value>啟用 Happy Eyeballs</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
|
||||
<value>需搭配 UseIP 策略,啟用後會同時嘗試 IPv4 與 IPv6 連線,並自動選擇速度較快的路徑</value>
|
||||
</data>
|
||||
<data name="TbIpv6Address" xml:space="preserve">
|
||||
<value>Ipv6 位址</value>
|
||||
</data>
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 位址</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,27 +1,28 @@
|
||||
{
|
||||
"tag": "tun",
|
||||
"protocol": "tun",
|
||||
"settings": {
|
||||
"name": "xray_tun",
|
||||
"MTU": 9000,
|
||||
"gateway": [
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"dns": [
|
||||
"172.18.0.1"
|
||||
],
|
||||
"autoSystemRoutingTable": [
|
||||
"0.0.0.0/0",
|
||||
"::/0"
|
||||
],
|
||||
"autoOutboundsInterface": "auto"
|
||||
},
|
||||
"sniffing": {
|
||||
"enabled": true,
|
||||
"destOverride": [
|
||||
"http",
|
||||
"tls"
|
||||
]
|
||||
}
|
||||
}
|
||||
"tag": "tun",
|
||||
"protocol": "tun",
|
||||
"settings": {
|
||||
"name": "xray_tun",
|
||||
"MTU": 9000,
|
||||
"gateway": [
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"dns": [
|
||||
"1.1.1.1",
|
||||
"8.8.8.8"
|
||||
],
|
||||
"autoSystemRoutingTable": [
|
||||
"0.0.0.0/0",
|
||||
"::/0"
|
||||
],
|
||||
"autoOutboundsInterface": "auto"
|
||||
},
|
||||
"sniffing": {
|
||||
"enabled": true,
|
||||
"destOverride": [
|
||||
"http",
|
||||
"tls"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,11 @@ kill_children "$PID"
|
||||
echo "Terminating main process: $PID"
|
||||
kill -9 "$PID" 2>/dev/null || true
|
||||
|
||||
# Wait a little for process/port resources to be fully released
|
||||
FINAL_WAIT_SECONDS=1
|
||||
echo "Waiting ${FINAL_WAIT_SECONDS}s for resources to settle..."
|
||||
sleep "$FINAL_WAIT_SECONDS"
|
||||
|
||||
echo "============================================"
|
||||
echo "Process $PID and all its children have been terminated"
|
||||
echo "============================================"
|
||||
|
||||
@@ -63,6 +63,11 @@ kill_descendants "$PID"
|
||||
echo "Terminating main process: $PID"
|
||||
kill -9 "$PID" 2>/dev/null || true
|
||||
|
||||
# Wait a little for process/port resources to be fully released
|
||||
FINAL_WAIT_SECONDS=1
|
||||
echo "Waiting ${FINAL_WAIT_SECONDS}s for resources to settle..."
|
||||
sleep "$FINAL_WAIT_SECONDS"
|
||||
|
||||
echo "============================================"
|
||||
echo "Process $PID and all its descendants have been terminated"
|
||||
echo "============================================"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
||||
[
|
||||
{
|
||||
"network": "udp",
|
||||
"network": [
|
||||
"udp"
|
||||
],
|
||||
"port": [
|
||||
135,
|
||||
137,
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
<PackageReference Include="ReactiveUI">
|
||||
<TreatAsUsed>true</TreatAsUsed>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ReactiveUI.Fody" />
|
||||
<PackageReference Include="ReactiveUI.SourceGenerators">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="sqlite-net-e" />
|
||||
<PackageReference Include="Repobot.SQLite.Unofficial" />
|
||||
<PackageReference Include="NLog" />
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Core configuration file processing class
|
||||
/// Core configuration file processing class.
|
||||
/// The TUN state is taken as a snapshot so the generated config always agrees
|
||||
/// with the launch elevation decision (see CoreManager.ShouldRunAsSudo).
|
||||
/// </summary>
|
||||
public class CoreConfigClashService(Config config)
|
||||
public class CoreConfigClashService(Config config, bool isTunEnabled)
|
||||
{
|
||||
private static readonly string _tag = "CoreConfigClashService";
|
||||
|
||||
@@ -102,7 +104,7 @@ public class CoreConfigClashService(Config config)
|
||||
}
|
||||
|
||||
//enable tun mode
|
||||
if (config.TunModeItem.EnableTun)
|
||||
if (isTunEnabled)
|
||||
{
|
||||
var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml);
|
||||
if (tun.IsNotEmpty())
|
||||
@@ -171,7 +173,7 @@ public class CoreConfigClashService(Config config)
|
||||
}
|
||||
foreach (var item in mixinContent)
|
||||
{
|
||||
if (!config.TunModeItem.EnableTun && item.Key == "tun")
|
||||
if (!isTunEnabled && item.Key == "tun")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -57,13 +57,10 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||
|
||||
ConvertGeo2Ruleset();
|
||||
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
|
||||
ret.Data = ApplyFullConfigTemplate();
|
||||
ret.Data = ApplyFinalConfigModifiers();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -107,7 +104,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (!(Global.SingboxSupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
|
||||
if (!(Global.SingboxSupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType() || it.ConfigType is EConfigType.Outbound))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -174,7 +171,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
ret.Data = ApplyCustomOutboundReplace();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -236,7 +233,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
ret.Data = ApplyCustomOutboundReplace();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -2,54 +2,137 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private string ApplyFullConfigTemplate()
|
||||
private string ApplyFinalConfigModifiers()
|
||||
{
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
|
||||
var coreConfigContent = ApplyCustomOutboundReplace();
|
||||
|
||||
return ApplyFullConfigTemplate(coreConfigContent);
|
||||
}
|
||||
|
||||
private string ApplyCustomOutboundReplace()
|
||||
{
|
||||
var coreConfigContent = JsonUtils.Serialize(_coreConfig);
|
||||
if (context.CustomOutboundMap.Count == 0)
|
||||
{
|
||||
return coreConfigContent;
|
||||
}
|
||||
var coreConfigNode = JsonNode.Parse(coreConfigContent) as JsonObject;
|
||||
var coreConfigOutboundsNode = coreConfigNode?["outbounds"] as JsonArray ?? [];
|
||||
ReplaceCustomOutbounds(_coreConfig.outbounds, coreConfigOutboundsNode);
|
||||
coreConfigNode!["outbounds"] = coreConfigOutboundsNode;
|
||||
var coreConfigEndpointsNode = coreConfigNode?["endpoints"] as JsonArray ?? [];
|
||||
ReplaceCustomOutbounds(_coreConfig.endpoints, coreConfigEndpointsNode);
|
||||
if (coreConfigEndpointsNode.Count > 0)
|
||||
{
|
||||
coreConfigNode!["endpoints"] = coreConfigEndpointsNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
coreConfigNode?.Remove("endpoints");
|
||||
}
|
||||
return JsonUtils.Serialize(coreConfigNode);
|
||||
|
||||
void ReplaceCustomOutbounds(IReadOnlyList<BaseServer4Sbox>? source, JsonArray jsonArrayOutbounds)
|
||||
{
|
||||
foreach (var outbound in source ?? [])
|
||||
{
|
||||
if (!context.CustomOutboundMap.TryGetValue(outbound, out var customOutboundIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var outboundTag = outbound.tag;
|
||||
var outboundDetour = outbound.detour ?? string.Empty;
|
||||
var outboundBindInterface = outbound.bind_interface ?? string.Empty;
|
||||
var customOutboundContent = context.CustomOutboundContent[customOutboundIndex];
|
||||
var containTagPlaceholder = customOutboundContent.Contains("{{tag}}");
|
||||
var containDetourPlaceholder = customOutboundContent.Contains("{{detour}}");
|
||||
var containBindInterfacePlaceholder = customOutboundContent.Contains("{{interface}}");
|
||||
customOutboundContent = customOutboundContent.Replace("{{tag}}", outboundTag);
|
||||
customOutboundContent = customOutboundContent.Replace("{{detour}}", outboundDetour);
|
||||
customOutboundContent = customOutboundContent.Replace("{{interface}}", outboundBindInterface);
|
||||
var customOutboundObj = JsonUtils.ParseJson(customOutboundContent) as JsonObject;
|
||||
|
||||
if (!containTagPlaceholder)
|
||||
{
|
||||
customOutboundObj?["tag"] = outboundTag;
|
||||
}
|
||||
if (!containDetourPlaceholder && !outboundDetour.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj?["detour"] = outboundDetour;
|
||||
}
|
||||
else if (outboundDetour.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj?.Remove("detour");
|
||||
}
|
||||
if (!containBindInterfacePlaceholder && !outboundBindInterface.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj?["bind_interface"] = outboundBindInterface;
|
||||
}
|
||||
|
||||
var index = jsonArrayOutbounds
|
||||
.Select((node, idx) => new { node, idx })
|
||||
.FirstOrDefault(x => x.node?["tag"]?.ToString() == outboundTag)?.idx ?? -1;
|
||||
if (index != -1)
|
||||
{
|
||||
jsonArrayOutbounds[index] = customOutboundObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string ApplyFullConfigTemplate(string coreConfigContent)
|
||||
{
|
||||
var fullConfigTemplate = context.FullConfigTemplate;
|
||||
if (fullConfigTemplate is not { Enabled: true })
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
||||
if (fullConfigTemplateItem.IsNullOrEmpty())
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||
if (fullConfigTemplateNode == null)
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
// Process outbounds
|
||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] as JsonArray ?? [];
|
||||
foreach (var outbound in _coreConfig.outbounds)
|
||||
var coreConfigNode = JsonNode.Parse(coreConfigContent);
|
||||
var coreConfigOutboundsNode = coreConfigNode?["outbounds"] as JsonArray ?? [];
|
||||
foreach (var outbound in coreConfigOutboundsNode)
|
||||
{
|
||||
if (outbound.type.ToLower() is "direct" or "block")
|
||||
if (outbound["type"]?.ToString()?.ToLower() is "direct" or "block")
|
||||
{
|
||||
if (fullConfigTemplate.AddProxyOnly == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (outbound.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && !Utils.IsPrivateNetwork(outbound.server ?? string.Empty))
|
||||
if (outbound["detour"] is null && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && !Utils.IsPrivateNetwork(outbound["server"]?.ToString() ?? string.Empty))
|
||||
{
|
||||
outbound.detour = fullConfigTemplate.ProxyDetour;
|
||||
outbound["detour"] = fullConfigTemplate.ProxyDetour;
|
||||
}
|
||||
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||
}
|
||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||
|
||||
// Process endpoints
|
||||
if (_coreConfig.endpoints is { Count: > 0 })
|
||||
if (fullConfigTemplateNode["endpoints"] is JsonArray { Count: > 0 } coreConfigEndpointsNode)
|
||||
{
|
||||
var customEndpointsNode = fullConfigTemplateNode["endpoints"] as JsonArray ?? [];
|
||||
foreach (var endpoint in _coreConfig.endpoints)
|
||||
foreach (var endpoint in coreConfigEndpointsNode)
|
||||
{
|
||||
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
||||
if (endpoint["detour"] is null && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
||||
{
|
||||
endpoint.detour = fullConfigTemplate.ProxyDetour;
|
||||
endpoint["detour"] = fullConfigTemplate.ProxyDetour;
|
||||
}
|
||||
customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint));
|
||||
}
|
||||
|
||||
@@ -137,12 +137,12 @@ public partial class CoreConfigSingboxService
|
||||
// fake ip
|
||||
if (simpleDnsItem.FakeIP == true)
|
||||
{
|
||||
var fakeipRange = simpleDnsItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : simpleDnsItem.FakeIPRange;
|
||||
var fakeip = new Server4Sbox
|
||||
{
|
||||
tag = Global.SingboxFakeDNSTag,
|
||||
type = "fakeip",
|
||||
inet4_range = "198.18.0.0/15",
|
||||
inet6_range = "fc00::/18",
|
||||
inet4_range = fakeipRange,
|
||||
};
|
||||
_coreConfig.dns.servers.Add(fakeip);
|
||||
}
|
||||
@@ -171,7 +171,7 @@ public partial class CoreConfigSingboxService
|
||||
_coreConfig.dns.rules.Add(new()
|
||||
{
|
||||
server = Global.SingboxDirectDNSTag,
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial),
|
||||
domain = context.ProtectDomainList.ToList(),
|
||||
});
|
||||
}
|
||||
@@ -266,7 +266,7 @@ public partial class CoreConfigSingboxService
|
||||
});
|
||||
}
|
||||
|
||||
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true)
|
||||
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp != false)
|
||||
{
|
||||
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
|
||||
fakeipFilterRule.invert = true;
|
||||
|
||||
@@ -67,9 +67,13 @@ public partial class CoreConfigSingboxService
|
||||
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
||||
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
||||
tunInbound.stack = _config.TunModeItem.Stack;
|
||||
if (_config.TunModeItem.EnableIPv6Address == false)
|
||||
|
||||
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
|
||||
tunInbound.address = [address];
|
||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||
{
|
||||
tunInbound.address = ["172.18.0.1/30"];
|
||||
var address6 = _config.TunModeItem.IPv6Address.NullIfEmpty() ?? Global.TunIPv6Address.First();
|
||||
tunInbound.address.Add(address6);
|
||||
}
|
||||
tunInbound.route_exclude_address = _config.TunModeItem.RouteExcludeAddress;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ public partial class CoreConfigSingboxService
|
||||
private List<BaseServer4Sbox> BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag, bool withSelector = true)
|
||||
{
|
||||
var proxyOutboundList = new List<BaseServer4Sbox>();
|
||||
if (!_node.ConfigType.IsComplexType())
|
||||
if (!_node.ConfigType.IsGroupType())
|
||||
{
|
||||
var outbound = BuildProxyOutbound(baseTagName);
|
||||
proxyOutboundList.Add(outbound);
|
||||
@@ -35,6 +35,10 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
var outbound = BuildProxyServer();
|
||||
outbound.tag = baseTagName;
|
||||
if (_node.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
context.CustomOutboundMap[outbound] = _node.IndexId;
|
||||
}
|
||||
return outbound;
|
||||
}
|
||||
|
||||
@@ -59,6 +63,20 @@ public partial class CoreConfigSingboxService
|
||||
try
|
||||
{
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
if (_node.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
if (_node.GetProtocolExtra().IsSingboxEndpoint == true)
|
||||
{
|
||||
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
|
||||
return endpoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||
return outbound;
|
||||
}
|
||||
}
|
||||
|
||||
if (_node.ConfigType == EConfigType.WireGuard)
|
||||
{
|
||||
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
|
||||
@@ -417,11 +435,15 @@ public partial class CoreConfigSingboxService
|
||||
var tls = new Tls4Sbox()
|
||||
{
|
||||
enabled = true,
|
||||
record_fragment = _config.CoreBasicItem.EnableFragment ? true : null,
|
||||
server_name = serverName,
|
||||
insecure = _node.GetAllowInsecure(),
|
||||
alpn = _node.GetAlpn(),
|
||||
};
|
||||
if (_config.CoreBasicItem.EnableFragment == true)
|
||||
{
|
||||
tls.fragment = true;
|
||||
tls.record_fragment = true;
|
||||
}
|
||||
if (_node.Fingerprint.IsNotEmpty())
|
||||
{
|
||||
tls.utls = new Utls4Sbox()
|
||||
@@ -589,7 +611,7 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
type = "selector",
|
||||
tag = baseTagName,
|
||||
outbounds = JsonUtils.DeepCopy(proxyTags),
|
||||
outbounds = [.. proxyTags],
|
||||
interrupt_exist_connections = false,
|
||||
};
|
||||
outSelector.outbounds.Insert(0, outUrltest.tag);
|
||||
@@ -717,9 +739,9 @@ public partial class CoreConfigSingboxService
|
||||
return resultOutbounds;
|
||||
}
|
||||
|
||||
private static List<BaseServer4Sbox> CloneOutbounds(List<BaseServer4Sbox> source)
|
||||
private List<BaseServer4Sbox> CloneOutbounds(List<BaseServer4Sbox> source)
|
||||
{
|
||||
if (source is null || source.Count == 0)
|
||||
if (source is not { Count: > 0 })
|
||||
{
|
||||
return [];
|
||||
}
|
||||
@@ -736,9 +758,14 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
clone = JsonUtils.DeepCopy(endpoint);
|
||||
}
|
||||
if (clone is not null)
|
||||
if (clone is null)
|
||||
{
|
||||
result.Add(clone);
|
||||
continue;
|
||||
}
|
||||
result.Add(clone);
|
||||
if (context.CustomOutboundMap.ContainsKey(item))
|
||||
{
|
||||
context.CustomOutboundMap[clone] = context.CustomOutboundMap[item];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -10,18 +10,27 @@ public partial class CoreConfigSingboxService
|
||||
var simpleDnsItem = context.SimpleDnsItem;
|
||||
|
||||
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
|
||||
var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
|
||||
var dialDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial);
|
||||
|
||||
var rawDNSItem = context.RawDnsItem;
|
||||
if (rawDNSItem is { Enabled: true })
|
||||
{
|
||||
defaultDomainResolverTag = Global.SingboxLocalDNSTag;
|
||||
directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
|
||||
dialDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
|
||||
}
|
||||
else if (!simpleDnsItem.Strategy4Freedom.IsNullOrEmpty())
|
||||
{
|
||||
var directOutbound = _coreConfig.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag);
|
||||
directOutbound?.domain_resolver = new()
|
||||
{
|
||||
server = defaultDomainResolverTag,
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||
};
|
||||
}
|
||||
_coreConfig.route.default_domain_resolver = new()
|
||||
{
|
||||
server = defaultDomainResolverTag,
|
||||
strategy = directDnsStrategy
|
||||
strategy = dialDnsStrategy
|
||||
};
|
||||
|
||||
if (context.IsTunEnabled)
|
||||
@@ -34,19 +43,38 @@ public partial class CoreConfigSingboxService
|
||||
_coreConfig.route.rules.AddRange(tunRules);
|
||||
}
|
||||
|
||||
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
|
||||
_coreConfig.route.rules.Add(new()
|
||||
// Traffic addressed to the TUN interface's own addresses must never reach an
|
||||
// outbound. auto_route hijacks the default route, so `direct` writes such a
|
||||
// packet straight back into the TUN, which hands it to the outbound again -
|
||||
// an infinite loop that pins a CPU core. Drop instead of rejecting so no
|
||||
// ICMP unreachable is generated back towards the same addresses.
|
||||
var tunAddresses = _coreConfig.inbounds.FirstOrDefault(i => i.type == "tun")?.address;
|
||||
if (tunAddresses?.Count > 0)
|
||||
{
|
||||
port = [53],
|
||||
action = "hijack-dns",
|
||||
process_name = lstDnsExe
|
||||
});
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
ip_cidr = [.. tunAddresses],
|
||||
action = "reject",
|
||||
method = "drop",
|
||||
});
|
||||
}
|
||||
|
||||
_coreConfig.route.rules.Add(new()
|
||||
var lstDirectExe = BuildRoutingDirectExe();
|
||||
if (lstDirectExe.Count > 0)
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
process_name = lstDirectExe
|
||||
});
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
port = [53],
|
||||
action = "hijack-dns",
|
||||
process_path = lstDirectExe,
|
||||
});
|
||||
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
process_path = lstDirectExe,
|
||||
});
|
||||
}
|
||||
|
||||
// ICMP Routing
|
||||
var icmpRouting = _config.TunModeItem.IcmpRouting ?? "";
|
||||
@@ -256,34 +284,38 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
|
||||
private List<string> BuildRoutingDirectExe()
|
||||
{
|
||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
|
||||
var allCoreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||
|
||||
foreach (var coreConfig in coreInfoResult)
|
||||
foreach (var coreConfig in allCoreInfo)
|
||||
{
|
||||
if (!context.ProtectCoreTypeList.Contains(coreConfig.CoreType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (coreConfig.CoreExes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var baseExeName in coreConfig.CoreExes)
|
||||
{
|
||||
if (coreConfig.CoreType != ECoreType.sing_box)
|
||||
//directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
var exePath = CoreInfoManager.Instance.GetCoreExecFile(coreConfig, out _);
|
||||
if (!exePath.IsNullOrEmpty())
|
||||
{
|
||||
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
directExeSet.Add(exePath);
|
||||
}
|
||||
directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
}
|
||||
|
||||
var lstDnsExe = new List<string>(dnsExeSet);
|
||||
var lstDirectExe = new List<string>(directExeSet);
|
||||
|
||||
return (lstDnsExe, lstDirectExe);
|
||||
return directExeSet.ToList();
|
||||
}
|
||||
|
||||
private void GenRoutingUserRule(RulesItem? item)
|
||||
@@ -542,7 +574,8 @@ public partial class CoreConfigSingboxService
|
||||
|
||||
if (node == null
|
||||
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
|
||||
&& !node.ConfigType.IsGroupType()))
|
||||
&& !node.ConfigType.IsGroupType()
|
||||
&& node.ConfigType is not EConfigType.Outbound))
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
@@ -64,8 +64,6 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
{
|
||||
ApplyFinalFragment();
|
||||
}
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
|
||||
var finalRule = BuildFinalRule();
|
||||
if (!string.IsNullOrEmpty(finalRule?.balancerTag))
|
||||
@@ -75,7 +73,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = ApplyFullConfigTemplate();
|
||||
ret.Data = ApplyFinalConfigModifiers();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -119,7 +117,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
|
||||
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType() || it.ConfigType is EConfigType.Outbound))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -216,7 +214,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
ApplyOutboundSendThrough();
|
||||
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
ret.Data = ApplyCustomOutboundReplace();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -293,7 +291,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
ret.Data = ApplyCustomOutboundReplace();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -2,24 +2,105 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private string ApplyFullConfigTemplate()
|
||||
private string ApplyFinalConfigModifiers()
|
||||
{
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
|
||||
var coreConfigContent = ApplyCustomOutboundReplace();
|
||||
|
||||
return ApplyFullConfigTemplate(coreConfigContent);
|
||||
}
|
||||
|
||||
private string ApplyCustomOutboundReplace()
|
||||
{
|
||||
var coreConfigContent = JsonUtils.Serialize(_coreConfig);
|
||||
if (context.CustomOutboundMap.Count == 0)
|
||||
{
|
||||
return coreConfigContent;
|
||||
}
|
||||
var coreConfigNode = JsonNode.Parse(coreConfigContent) as JsonObject;
|
||||
var coreConfigOutboundsNode = coreConfigNode?["outbounds"] as JsonArray ?? [];
|
||||
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
if (!context.CustomOutboundMap.TryGetValue(outbound, out var customOutboundIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var outboundTag = outbound.tag;
|
||||
var outboundDetour = outbound.streamSettings?.sockopt?.dialerProxy ?? string.Empty;
|
||||
var outboundBindInterface = outbound.streamSettings?.sockopt?.Interface ?? string.Empty;
|
||||
var customOutboundContent = context.CustomOutboundContent[customOutboundIndex];
|
||||
var containTagPlaceholder = customOutboundContent.Contains("{{tag}}");
|
||||
var containDetourPlaceholder = customOutboundContent.Contains("{{detour}}");
|
||||
var containBindInterfacePlaceholder = customOutboundContent.Contains("{{interface}}");
|
||||
customOutboundContent = customOutboundContent.Replace("{{tag}}", outboundTag);
|
||||
customOutboundContent = customOutboundContent.Replace("{{detour}}", outboundDetour);
|
||||
customOutboundContent = customOutboundContent.Replace("{{interface}}", outboundBindInterface);
|
||||
var customOutboundObj = JsonUtils.ParseJson(customOutboundContent) as JsonObject;
|
||||
|
||||
if (!containTagPlaceholder)
|
||||
{
|
||||
customOutboundObj?["tag"] = outboundTag;
|
||||
}
|
||||
if (!containDetourPlaceholder && !outboundDetour.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj!["streamSettings"] ??= new JsonObject();
|
||||
customOutboundObj["streamSettings"]["sockopt"] ??= new JsonObject();
|
||||
customOutboundObj["streamSettings"]["sockopt"]["dialerProxy"] = outboundDetour;
|
||||
if (customOutboundObj["streamSettings"]?["xhttpSettings"]?["extra"]?["downloadSettings"] is JsonObject downloadSettings)
|
||||
{
|
||||
downloadSettings["sockopt"] ??= new JsonObject();
|
||||
downloadSettings["sockopt"]["dialerProxy"] = outboundDetour;
|
||||
}
|
||||
}
|
||||
else if (outboundDetour.IsNullOrEmpty())
|
||||
{
|
||||
(customOutboundObj?["streamSettings"]?["sockopt"] as JsonObject)?.Remove("dialerProxy");
|
||||
}
|
||||
if (!containBindInterfacePlaceholder && !outboundBindInterface.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj!["streamSettings"] ??= new JsonObject();
|
||||
customOutboundObj["streamSettings"]["sockopt"] ??= new JsonObject();
|
||||
customOutboundObj["streamSettings"]["sockopt"]["interface"] = outboundBindInterface;
|
||||
if (customOutboundObj["streamSettings"]?["xhttpSettings"]?["extra"]?["downloadSettings"] is JsonObject downloadSettings)
|
||||
{
|
||||
downloadSettings["sockopt"] ??= new JsonObject();
|
||||
downloadSettings["sockopt"]["interface"] = outboundBindInterface;
|
||||
}
|
||||
}
|
||||
|
||||
var index = coreConfigOutboundsNode
|
||||
.Select((node, idx) => new { node, idx })
|
||||
.FirstOrDefault(x => x.node?["tag"]?.ToString() == outboundTag)?.idx ?? -1;
|
||||
if (index != -1)
|
||||
{
|
||||
coreConfigOutboundsNode[index] = customOutboundObj;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonUtils.Serialize(coreConfigNode);
|
||||
}
|
||||
|
||||
private string ApplyFullConfigTemplate(string coreConfigContent)
|
||||
{
|
||||
var fullConfigTemplate = context.FullConfigTemplate;
|
||||
if (fullConfigTemplate is not { Enabled: true })
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
||||
if (fullConfigTemplateItem.IsNullOrEmpty())
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||
if (fullConfigTemplateNode == null)
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
// Handle balancer and rules modifications (for multiple load scenarios)
|
||||
@@ -74,8 +155,8 @@ public partial class CoreConfigV2rayService
|
||||
else
|
||||
{
|
||||
var subjectSelector = _coreConfig.observatory.subjectSelector;
|
||||
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||
subjectSelector?.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["observatory"]?["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector?.Distinct().ToList()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,16 +169,18 @@ public partial class CoreConfigV2rayService
|
||||
else
|
||||
{
|
||||
var subjectSelector = _coreConfig.burstObservatory.subjectSelector;
|
||||
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||
subjectSelector?.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["burstObservatory"]?["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector?.Distinct().ToList()));
|
||||
}
|
||||
}
|
||||
|
||||
var customOutboundsNode = new JsonArray();
|
||||
|
||||
foreach (var outbound in _coreConfig.outbounds)
|
||||
var coreConfigNode = JsonNode.Parse(coreConfigContent);
|
||||
var coreConfigOutboundsNode = coreConfigNode?["outbounds"] as JsonArray ?? [];
|
||||
foreach (var outbound in coreConfigOutboundsNode)
|
||||
{
|
||||
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
|
||||
if (outbound?["protocol"]?.ToString()?.ToLower() is "blackhole" or "dns" or "freedom")
|
||||
{
|
||||
if (fullConfigTemplate.AddProxyOnly == true)
|
||||
{
|
||||
@@ -105,14 +188,22 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()
|
||||
&& (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true))
|
||||
&& (outbound["streamSettings"]?["sockopt"]?["dialerProxy"].ToString().IsNullOrEmpty() ?? true))
|
||||
{
|
||||
var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address
|
||||
?? outbound.settings?.vnext?.FirstOrDefault()?.address
|
||||
var outboundAddress = outbound["settings"]?["servers"]?.AsArray()?.FirstOrDefault()?["address"]?.ToString()
|
||||
?? outbound["settings"]?["vnext"]?.AsArray()?.FirstOrDefault()?["address"]?.ToString()
|
||||
?? string.Empty;
|
||||
if (!Utils.IsPrivateNetwork(outboundAddress))
|
||||
{
|
||||
FillDialerProxy(outbound, fullConfigTemplate.ProxyDetour);
|
||||
//FillDialerProxy(outbound, fullConfigTemplate.ProxyDetour);
|
||||
outbound["streamSettings"] ??= new JsonObject();
|
||||
outbound["streamSettings"]["sockopt"] ??= new JsonObject();
|
||||
outbound["streamSettings"]["sockopt"]["dialerProxy"] = fullConfigTemplate.ProxyDetour;
|
||||
if (outbound["streamSettings"]?["xhttpSettings"]?["extra"]?["downloadSettings"] is JsonObject downloadSettings)
|
||||
{
|
||||
downloadSettings["sockopt"] ??= new JsonObject();
|
||||
downloadSettings["sockopt"]["dialerProxy"] = fullConfigTemplate.ProxyDetour;
|
||||
}
|
||||
}
|
||||
}
|
||||
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||
|
||||
@@ -28,7 +28,7 @@ public partial class CoreConfigV2rayService
|
||||
_coreConfig.routing.rules.Add(new RulesItem4Ray
|
||||
{
|
||||
type = "field",
|
||||
inboundTag = new List<string> { Global.DnsTag },
|
||||
inboundTag = [Global.DnsTag],
|
||||
outboundTag = Global.ProxyTag,
|
||||
});
|
||||
return;
|
||||
@@ -43,11 +43,7 @@ public partial class CoreConfigV2rayService
|
||||
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new()
|
||||
{
|
||||
domainStrategy = strategy4Freedom,
|
||||
userLevel = 0
|
||||
};
|
||||
FillSockoptDomainStrategy(outbound, strategy4Freedom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +60,22 @@ public partial class CoreConfigV2rayService
|
||||
.ForEach(outbound => outbound.targetStrategy = strategy4Proxy);
|
||||
}
|
||||
|
||||
var strategy4DialProxy = simpleDnsItem?.Strategy4ProxyDial ?? Global.AsIs;
|
||||
//Outbound DialProxy domainStrategy
|
||||
if (strategy4DialProxy.IsNotEmpty() && strategy4DialProxy != Global.AsIs)
|
||||
{
|
||||
var xraySupportConfigTypeNames = Global.XraySupportConfigType
|
||||
.Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x])
|
||||
.ToHashSet();
|
||||
_coreConfig.outbounds
|
||||
.Where(t => xraySupportConfigTypeNames.Contains(t.protocol))
|
||||
.ToList()
|
||||
.ForEach(outbound =>
|
||||
{
|
||||
FillSockoptDomainStrategy(outbound, strategy4DialProxy);
|
||||
});
|
||||
}
|
||||
|
||||
FillDnsServers(dnsItem);
|
||||
FillDnsHosts(dnsItem);
|
||||
|
||||
@@ -108,6 +120,33 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
|
||||
private void GenFakeDns()
|
||||
{
|
||||
var fakeipRange = _config.SimpleDNSItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : _config.SimpleDNSItem.FakeIPRange;
|
||||
var poolSize = 65535L;
|
||||
try
|
||||
{
|
||||
var fakeipNetwork = IPNetwork2.Parse(fakeipRange);
|
||||
var totalIPs = fakeipNetwork.Total;
|
||||
// see https://github.com/XTLS/Xray-core/blob/6e3322d219140a025285ded1114fe17a5edb74d8/app/dns/fakedns/fake.go#L88
|
||||
// if math.Log2(float64(lruSize)) >= float64(rooms) { return errors.New("LRU size is bigger than subnet size").AtError() }
|
||||
totalIPs -= 1;
|
||||
if (totalIPs > 0)
|
||||
{
|
||||
poolSize = (totalIPs >= long.MaxValue) ? long.MaxValue : (long)totalIPs;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
_coreConfig.fakedns = new()
|
||||
{
|
||||
ipPool = fakeipRange,
|
||||
poolSize = poolSize,
|
||||
};
|
||||
}
|
||||
|
||||
private void FillDnsServers(Dns4Ray dnsItem)
|
||||
{
|
||||
var simpleDNSItem = context.SimpleDnsItem;
|
||||
@@ -234,6 +273,23 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
var directDnsTagIndex = 1;
|
||||
|
||||
if (simpleDNSItem.FakeIP == true)
|
||||
{
|
||||
var fakeIPMatchDomain = new HashSet<string>(proxyDomainList);
|
||||
fakeIPMatchDomain.UnionWith(proxyGeositeList);
|
||||
if (simpleDNSItem.GlobalFakeIp != false)
|
||||
{
|
||||
fakeIPMatchDomain.UnionWith(directDomainList);
|
||||
fakeIPMatchDomain.UnionWith(directGeositeList);
|
||||
fakeIPMatchDomain.UnionWith(expectedDomainList);
|
||||
}
|
||||
if (fakeIPMatchDomain.Count > 0)
|
||||
{
|
||||
GenFakeDns();
|
||||
AddDnsServers(["fakedns"], fakeIPMatchDomain.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
AddDnsServers(remoteDNSAddress, proxyDomainList);
|
||||
AddDnsServers(directDNSAddress, directDomainList, true);
|
||||
AddDnsServers(remoteDNSAddress, proxyGeositeList);
|
||||
@@ -384,11 +440,9 @@ public partial class CoreConfigV2rayService
|
||||
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new()
|
||||
{
|
||||
domainStrategy = domainStrategy4Freedom,
|
||||
userLevel = 0,
|
||||
};
|
||||
outbound.streamSettings ??= new();
|
||||
outbound.streamSettings.sockopt ??= new();
|
||||
outbound.streamSettings.sockopt.domainStrategy = domainStrategy4Freedom;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,4 +525,28 @@ public partial class CoreConfigV2rayService
|
||||
};
|
||||
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
||||
}
|
||||
|
||||
private void FillSockoptDomainStrategy(Outbounds4Ray outbound, string? domainStrategy, bool skipHappyEyeballs = false)
|
||||
{
|
||||
if (domainStrategy.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
outbound.streamSettings ??= new();
|
||||
outbound.streamSettings.sockopt ??= new();
|
||||
var sockopt = outbound.streamSettings.sockopt;
|
||||
sockopt.domainStrategy = domainStrategy;
|
||||
|
||||
if (skipHappyEyeballs || _config.SimpleDNSItem.EnableHappyEyeballs != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var happyEyeballsItem = _config.HappyEyeballs4RayItem;
|
||||
sockopt.happyEyeballs ??= new();
|
||||
var happyEyeballs = sockopt.happyEyeballs;
|
||||
happyEyeballs.tryDelayMs = happyEyeballsItem.TryDelayMs;
|
||||
happyEyeballs.prioritizeIPv6 = happyEyeballsItem.PrioritizeIPv6;
|
||||
happyEyeballs.interleave = happyEyeballsItem.Interleave;
|
||||
happyEyeballs.maxConcurrentTry = happyEyeballsItem.MaxConcurrentTry;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +63,24 @@ public partial class CoreConfigV2rayService
|
||||
new Inbounds4Ray();
|
||||
tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun";
|
||||
tunInbound.settings.MTU = _config.TunModeItem.Mtu;
|
||||
if (!_config.TunModeItem.EnableIPv6Address)
|
||||
|
||||
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
|
||||
tunInbound.settings.gateway = [address];
|
||||
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
|
||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||
{
|
||||
tunInbound.settings.gateway = ["172.18.0.1/30"];
|
||||
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
|
||||
var address6 = _config.TunModeItem.IPv6Address.NullIfEmpty() ?? Global.TunIPv6Address.First();
|
||||
tunInbound.settings.gateway.Add(address6);
|
||||
tunInbound.settings.autoSystemRoutingTable.Add("::/0");
|
||||
}
|
||||
|
||||
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
|
||||
if (!bindInterface.IsNullOrEmpty())
|
||||
{
|
||||
tunInbound.settings.autoOutboundsInterface = bindInterface;
|
||||
}
|
||||
tunInbound.sniffing = inbound.sniffing;
|
||||
tunInbound.sniffing.routeOnly = true;
|
||||
|
||||
if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||
{
|
||||
@@ -151,6 +158,16 @@ public partial class CoreConfigV2rayService
|
||||
inbound.sniffing.destOverride = inItem.DestOverride;
|
||||
inbound.sniffing.routeOnly = inItem.RouteOnly;
|
||||
|
||||
if (_config.SimpleDNSItem.FakeIP == true)
|
||||
{
|
||||
// Ensure destOverride contains "fakedns" if FakeIP is enabled
|
||||
inbound.sniffing.destOverride ??= [];
|
||||
if (!inbound.sniffing.destOverride.Contains("fakedns"))
|
||||
{
|
||||
inbound.sniffing.destOverride.Add("fakedns");
|
||||
}
|
||||
}
|
||||
|
||||
return inbound;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
if (_node.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
outbound.tag = baseTagName;
|
||||
context.CustomOutboundMap[outbound] = _node.IndexId;
|
||||
return outbound;
|
||||
}
|
||||
FillOutbound(outbound);
|
||||
outbound.tag = baseTagName;
|
||||
return outbound;
|
||||
@@ -404,7 +410,6 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
TlsSettings4Ray tlsSettings = new()
|
||||
{
|
||||
allowInsecure = _node.GetAllowInsecure(),
|
||||
alpn = _node.GetAlpn(),
|
||||
fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint,
|
||||
echConfigList = _node.EchConfigList.NullIfEmpty(),
|
||||
@@ -438,12 +443,10 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
tlsSettings.certificates = certsettings;
|
||||
tlsSettings.disableSystemRoot = true;
|
||||
tlsSettings.allowInsecure = false;
|
||||
}
|
||||
else if (!_node.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
tlsSettings.pinnedPeerCertSha256 = _node.CertSha;
|
||||
tlsSettings.allowInsecure = false;
|
||||
}
|
||||
streamSettings.tlsSettings = tlsSettings;
|
||||
}
|
||||
@@ -788,12 +791,12 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
else if (chainStartNodes.Count > 1)
|
||||
{
|
||||
var existedChainNodes = JsonUtils.DeepCopy(resultOutbounds);
|
||||
var existedChainNodes = CloneOutbounds(resultOutbounds);
|
||||
resultOutbounds.Clear();
|
||||
var j = 0;
|
||||
foreach (var chainStartNode in chainStartNodes)
|
||||
{
|
||||
var existedChainNodesClone = JsonUtils.DeepCopy(existedChainNodes);
|
||||
var existedChainNodesClone = CloneOutbounds(existedChainNodes);
|
||||
foreach (var existedChainNode in existedChainNodesClone)
|
||||
{
|
||||
var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}";
|
||||
@@ -865,21 +868,10 @@ public partial class CoreConfigV2rayService
|
||||
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
|
||||
.ToList();
|
||||
|
||||
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
|
||||
var fragmentMask = BuildFragmentsMasks();
|
||||
|
||||
foreach (var outbound in actOutboundWithTlsList)
|
||||
{
|
||||
//var packets = configPackets;
|
||||
//if (outbound.streamSettings.security == Global.StreamSecurityReality
|
||||
// && packets == "tlshello")
|
||||
//{
|
||||
// packets = "1-3";
|
||||
//}
|
||||
//else if (outbound.streamSettings.security == Global.StreamSecurity
|
||||
// && packets != "tlshello")
|
||||
//{
|
||||
// packets = "tlshello";
|
||||
//}
|
||||
var finalMaskJsonObj = JsonUtils.ParseJson(JsonUtils.Serialize(outbound.streamSettings?.finalmask)) as JsonObject ?? new JsonObject();
|
||||
// tcp fragment
|
||||
var tcpFinalmaskList = finalMaskJsonObj["tcp"] as JsonArray ?? [];
|
||||
@@ -888,13 +880,6 @@ public partial class CoreConfigV2rayService
|
||||
tcpFinalmaskList.Add(JsonUtils.SerializeToNode(fragmentMask));
|
||||
finalMaskJsonObj["tcp"] = tcpFinalmaskList;
|
||||
}
|
||||
// udp noise
|
||||
var udpFinalmaskList = finalMaskJsonObj["udp"] as JsonArray ?? [];
|
||||
if (udpFinalmaskList.Count == 0)
|
||||
{
|
||||
udpFinalmaskList.Add(JsonUtils.SerializeToNode(noiseMask));
|
||||
finalMaskJsonObj["udp"] = udpFinalmaskList;
|
||||
}
|
||||
// write back
|
||||
outbound.streamSettings.finalmask = finalMaskJsonObj;
|
||||
}
|
||||
@@ -902,7 +887,7 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
private void ApplyFinalFragment()
|
||||
{
|
||||
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
|
||||
var fragmentMask = BuildFragmentsMasks();
|
||||
var actOutboundList = _coreConfig.outbounds.Where(n => n.tag.StartsWith(Global.ProxyTag)).ToList();
|
||||
|
||||
var fragmentFreedom = new Outbounds4Ray()
|
||||
@@ -914,9 +899,8 @@ public partial class CoreConfigV2rayService
|
||||
finalmask = new Finalmask4Ray
|
||||
{
|
||||
tcp = [fragmentMask],
|
||||
udp = [noiseMask],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var outbound in actOutboundList)
|
||||
@@ -934,13 +918,22 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
|
||||
private (Mask4Ray tcpFragment, Mask4Ray udpNoise) BuildFragmentsMasks()
|
||||
private Mask4Ray BuildFragmentsMasks()
|
||||
{
|
||||
var configPackets = _config.Fragment4RayItem?.Packets.NullIfEmpty() ?? "tlshello";
|
||||
var configLength = _config.Fragment4RayItem?.Length.NullIfEmpty() ?? "50-100";
|
||||
var configDelay = _config.Fragment4RayItem?.Interval.NullIfEmpty() ?? "10-20";
|
||||
var configLengths = _config.Fragment4RayItem?.Lengths ?? [];
|
||||
var configDelays = _config.Fragment4RayItem?.Delays ?? [];
|
||||
var configMaxSplit = _config.Fragment4RayItem?.MaxSplit.NullIfEmpty() ?? "0";
|
||||
|
||||
if (configLengths.Count == 0)
|
||||
{
|
||||
configLengths = ["50-100"];
|
||||
}
|
||||
if (configDelays.Count == 0)
|
||||
{
|
||||
configDelays = ["10-20"];
|
||||
}
|
||||
|
||||
var maxSplit = 0;
|
||||
var parts = configMaxSplit.Split('-');
|
||||
if (parts.Length > 0 && int.TryParse(parts[0], out var ms))
|
||||
@@ -954,21 +947,30 @@ public partial class CoreConfigV2rayService
|
||||
settings = new MaskSettings4Ray
|
||||
{
|
||||
packets = configPackets,
|
||||
length = configLength,
|
||||
delay = configDelay,
|
||||
lengths = configLengths,
|
||||
delays = configDelays,
|
||||
maxSplit = maxSplit,
|
||||
}
|
||||
};
|
||||
var noiseMask = new Mask4Ray
|
||||
{
|
||||
type = "noise",
|
||||
settings = new MaskSettings4Ray
|
||||
{
|
||||
length = "10-20",
|
||||
delay = "10-16",
|
||||
}
|
||||
// For legacy xray compatibility, remove this in the future
|
||||
length = configLengths.FirstOrDefault(),
|
||||
delay = configDelays.FirstOrDefault(),
|
||||
},
|
||||
};
|
||||
|
||||
return (fragmentMask, noiseMask);
|
||||
return fragmentMask;
|
||||
}
|
||||
|
||||
private List<Outbounds4Ray> CloneOutbounds(List<Outbounds4Ray> outbounds)
|
||||
{
|
||||
var clonedOutbounds = new List<Outbounds4Ray>();
|
||||
foreach (var outbound in outbounds)
|
||||
{
|
||||
var clonedOutbound = JsonUtils.DeepCopy(outbound);
|
||||
clonedOutbounds.Add(clonedOutbound);
|
||||
if (context.CustomOutboundMap.ContainsKey(outbound))
|
||||
{
|
||||
context.CustomOutboundMap[clonedOutbound] = context.CustomOutboundMap[outbound];
|
||||
}
|
||||
}
|
||||
return clonedOutbounds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,21 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
_coreConfig.routing.rules.AddRange(tunRules);
|
||||
}
|
||||
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
var lstDirectExe = BuildRoutingDirectExe();
|
||||
if (lstDirectExe.Count > 0)
|
||||
{
|
||||
port = "53",
|
||||
process = lstDnsExe,
|
||||
outboundTag = Global.DnsOutboundTag,
|
||||
});
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
process = lstDirectExe,
|
||||
outboundTag = Global.DirectTag,
|
||||
});
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
port = "53",
|
||||
process = lstDirectExe,
|
||||
outboundTag = Global.DnsOutboundTag,
|
||||
});
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
process = lstDirectExe,
|
||||
outboundTag = Global.DirectTag,
|
||||
});
|
||||
}
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
inboundTag = ["tun"],
|
||||
@@ -185,7 +188,8 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
if (node == null
|
||||
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
|
||||
&& !node.ConfigType.IsGroupType()))
|
||||
&& !node.ConfigType.IsGroupType()
|
||||
&& node.ConfigType is not EConfigType.Outbound))
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
@@ -232,36 +236,44 @@ public partial class CoreConfigV2rayService
|
||||
return finalRule;
|
||||
}
|
||||
|
||||
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
|
||||
private List<string> BuildRoutingDirectExe()
|
||||
{
|
||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
|
||||
var allCoreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||
|
||||
foreach (var coreConfig in coreInfoResult)
|
||||
foreach (var coreConfig in allCoreInfo)
|
||||
{
|
||||
if (!context.ProtectCoreTypeList.Contains(coreConfig.CoreType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (coreConfig.CoreExes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (coreConfig.CoreType == ECoreType.Xray)
|
||||
{
|
||||
directExeSet.Add("xray/");
|
||||
continue;
|
||||
}
|
||||
foreach (var baseExeName in coreConfig.CoreExes)
|
||||
{
|
||||
if (coreConfig.CoreType != ECoreType.Xray)
|
||||
//directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
var exePath = CoreInfoManager.Instance.GetCoreExecFile(coreConfig, out _);
|
||||
if (!exePath.IsNullOrEmpty())
|
||||
{
|
||||
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
directExeSet.Add(exePath);
|
||||
}
|
||||
directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
}
|
||||
|
||||
directExeSet.Add("xray/");
|
||||
//directExeSet.Add("xray/");
|
||||
directExeSet.Add("self/");
|
||||
|
||||
var lstDnsExe = new List<string>(dnsExeSet);
|
||||
var lstDirectExe = new List<string>(directExeSet);
|
||||
|
||||
return (lstDnsExe, lstDirectExe);
|
||||
return directExeSet.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,58 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddGroupServerViewModel : MyReactiveObject
|
||||
public partial class AddGroupServerViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedChild { get; set; }
|
||||
public partial ProfileItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public IList<ProfileItem> SelectedChildren { get; set; }
|
||||
public partial ProfileItem SelectedChild { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? CoreType { get; set; }
|
||||
public partial IList<ProfileItem> SelectedChildren { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? PolicyGroupType { get; set; }
|
||||
public partial string? CoreType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public SubItem? SelectedSubItem { get; set; }
|
||||
public partial string? PolicyGroupType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? Filter { get; set; }
|
||||
public partial SubItem? SelectedSubItem { get; set; }
|
||||
|
||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||
[Reactive]
|
||||
public partial string? Filter { get; set; }
|
||||
|
||||
public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
||||
public BulkObservableCollection<SubItem> SubItems { get; } = [];
|
||||
|
||||
public IObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
||||
public BulkObservableCollection<ProfileItem> ChildItemsObs { get; } = [];
|
||||
|
||||
//public ReactiveCommand<Unit, Unit> AddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoveCmd { get; }
|
||||
public BulkObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = [];
|
||||
|
||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RemoveCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveTopCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveUpCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveDownCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveBottomCmd { get; }
|
||||
|
||||
public AddGroupServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public AddGroupServerViewModel(ProfileItem profileItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
x => x.SelectedChild,
|
||||
SelectedChild => SelectedChild != null && !SelectedChild.Remarks.IsNullOrEmpty());
|
||||
selectedChild => selectedChild != null && !selectedChild.Remarks.IsNullOrEmpty());
|
||||
|
||||
AddCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddChildAsync();
|
||||
});
|
||||
RemoveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await ChildRemoveAsync();
|
||||
@@ -103,6 +108,20 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
ChildItemsObs.AddRange(childItemList);
|
||||
}
|
||||
|
||||
public async Task AddChildAsync()
|
||||
{
|
||||
var profileSelectViewModel = new ProfilesSelectViewModel();
|
||||
profileSelectViewModel.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
|
||||
profileSelectViewModel.MultiSelect = true;
|
||||
var result = await AppManager.Instance.WindowDialog.ShowDialogAsync(profileSelectViewModel);
|
||||
if (result != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var profiles = await profileSelectViewModel.GetProfileItems() ?? [];
|
||||
ChildItemsObs.AddRange(profiles);
|
||||
}
|
||||
|
||||
public async Task ChildRemoveAsync()
|
||||
{
|
||||
if (SelectedChild == null || SelectedChild.IndexId.IsNullOrEmpty())
|
||||
@@ -230,7 +249,7 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddServer2ViewModel : MyReactiveObject
|
||||
public partial class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public Interaction<RxVoid, string?> BrowseConfigFileInteraction { get; } = new();
|
||||
|
||||
[Reactive]
|
||||
public string? CoreType { get; set; }
|
||||
public partial ProfileItem SelectedSource { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> BrowseServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> EditServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveServerCmd { get; }
|
||||
[Reactive]
|
||||
public partial string? CoreType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public partial bool IsSingboxEndpoint { get; set; }
|
||||
|
||||
public ReactiveCommand<RxVoid, RxVoid> BrowseServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> EditServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveServerCmd { get; }
|
||||
public bool IsModified { get; set; }
|
||||
|
||||
public AddServer2ViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public AddServer2ViewModel(ProfileItem profileItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.BrowseServer, null);
|
||||
await Task.CompletedTask;
|
||||
var fileName = await BrowseConfigFileInteraction.Handle(RxVoid.Default);
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
await BrowseServer(fileName);
|
||||
});
|
||||
EditServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -33,7 +43,10 @@ public class AddServer2ViewModel : MyReactiveObject
|
||||
});
|
||||
|
||||
SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem);
|
||||
CoreType = SelectedSource?.CoreType?.ToString();
|
||||
var coreStr = SelectedSource?.CoreType?.ToString();
|
||||
coreStr = coreStr.IsNullOrEmpty() ? Global.CoreTypes.FirstOrDefault() : coreStr;
|
||||
CoreType = coreStr;
|
||||
IsSingboxEndpoint = SelectedSource?.GetProtocolExtra()?.IsSingboxEndpoint ?? false;
|
||||
}
|
||||
|
||||
private async Task SaveServerAsync()
|
||||
@@ -51,11 +64,15 @@ public class AddServer2ViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : Enum.Parse<ECoreType>(CoreType);
|
||||
SelectedSource.SetProtocolExtra(SelectedSource?.GetProtocolExtra() with
|
||||
{
|
||||
IsSingboxEndpoint = IsSingboxEndpoint ? true : null,
|
||||
});
|
||||
|
||||
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -73,7 +90,8 @@ public class AddServer2ViewModel : MyReactiveObject
|
||||
var item = await AppManager.Instance.GetProfileItem(SelectedSource.IndexId);
|
||||
item ??= SelectedSource;
|
||||
item.Address = fileName;
|
||||
if (await ConfigHandler.AddCustomServer(_config, item, false) == 0)
|
||||
var result = item.ConfigType == EConfigType.Outbound ? await ConfigHandler.AddCustomOutboundServer(_config, item, false) : await ConfigHandler.AddCustomServer(_config, item, false);
|
||||
if (result == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer);
|
||||
if (item.IndexId.IsNotEmpty())
|
||||
|
||||
@@ -1,129 +1,131 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddServerViewModel : MyReactiveObject
|
||||
public partial class AddServerViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public string? CoreType { get; set; }
|
||||
public partial ProfileItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AllowInsecure { get; set; }
|
||||
public partial string? CoreType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool MuxEnabled { get; set; }
|
||||
public partial bool AllowInsecure { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Cert { get; set; }
|
||||
public partial bool MuxEnabled { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CertTip { get; set; }
|
||||
public partial string Cert { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CertSha { get; set; }
|
||||
public partial string CertTip { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SalamanderPass { get; set; }
|
||||
public partial string CertSha { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int AlterId { get; set; }
|
||||
public partial string SalamanderPass { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Ports { get; set; }
|
||||
public partial int AlterId { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? UpMbps { get; set; }
|
||||
public partial string Ports { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? DownMbps { get; set; }
|
||||
public partial int? UpMbps { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string HopInterval { get; set; }
|
||||
public partial int? DownMbps { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Flow { get; set; }
|
||||
public partial string HopInterval { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string VmessSecurity { get; set; }
|
||||
public partial string Flow { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string VlessEncryption { get; set; }
|
||||
public partial string VmessSecurity { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SsMethod { get; set; }
|
||||
public partial string VlessEncryption { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgPublicKey { get; set; }
|
||||
public partial string SsMethod { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgPresharedKey { get; set; }
|
||||
public partial string WgPublicKey { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgInterfaceAddress { get; set; }
|
||||
public partial string WgPresharedKey { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgReserved { get; set; }
|
||||
public partial string WgInterfaceAddress { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int WgMtu { get; set; }
|
||||
public partial string WgReserved { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool Uot { get; set; }
|
||||
public partial int WgMtu { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CongestionControl { get; set; }
|
||||
public partial bool Uot { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? InsecureConcurrency { get; set; }
|
||||
public partial string CongestionControl { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool NaiveQuic { get; set; }
|
||||
public partial int? InsecureConcurrency { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string HttpHeadersJson { get; set; }
|
||||
public partial bool NaiveQuic { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Hy2RealmUrl { get; set; }
|
||||
public partial string HttpHeadersJson { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int GeckoMinPacketSize { get; set; }
|
||||
public partial string Hy2RealmUrl { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int GeckoMaxPacketSize { get; set; }
|
||||
public partial int GeckoMinPacketSize { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RawHeaderType { get; set; }
|
||||
public partial int GeckoMaxPacketSize { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Host { get; set; }
|
||||
public partial string RawHeaderType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Path { get; set; }
|
||||
public partial string Host { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string XhttpMode { get; set; }
|
||||
public partial string Path { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string XhttpExtra { get; set; }
|
||||
public partial string XhttpMode { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string GrpcAuthority { get; set; }
|
||||
public partial string XhttpExtra { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string GrpcServiceName { get; set; }
|
||||
public partial string GrpcAuthority { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string GrpcMode { get; set; }
|
||||
public partial string GrpcServiceName { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string KcpHeaderType { get; set; }
|
||||
public partial string GrpcMode { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string KcpSeed { get; set; }
|
||||
public partial string KcpHeaderType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? KcpMtu { get; set; }
|
||||
public partial string KcpSeed { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public partial int? KcpMtu { get; set; }
|
||||
|
||||
public string TransportHeaderType
|
||||
{
|
||||
@@ -237,14 +239,13 @@ public class AddServerViewModel : MyReactiveObject
|
||||
}
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> FetchCertCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> FetchCertChainCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public AddServerViewModel(ProfileItem profileItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
FetchCertCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -443,7 +444,7 @@ public class AddServerViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class BackupAndRestoreViewModel : MyReactiveObject
|
||||
public partial class BackupAndRestoreViewModel : MyReactiveObject
|
||||
{
|
||||
private readonly string _guiConfigs = "guiConfigs";
|
||||
private static string BackupFileName => $"backup_{DateTime.Now:yyyyMMddHHmmss}.zip";
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RemoteBackupCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoteRestoreCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> WebDavCheckCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RemoteBackupCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RemoteRestoreCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> WebDavCheckCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public WebDavItem SelectedSource { get; set; }
|
||||
public partial WebDavItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string OperationMsg { get; set; }
|
||||
public partial string OperationMsg { get; set; } = string.Empty;
|
||||
|
||||
public BackupAndRestoreViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public BackupAndRestoreViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class CheckUpdateViewModel : MyReactiveObject
|
||||
public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
{
|
||||
private const string _geo = "GeoFiles";
|
||||
private readonly ECoreType _v2rayN = ECoreType.v2rayN;
|
||||
private List<CheckUpdateModel> _lstUpdated = [];
|
||||
private static readonly string _tag = "CheckUpdateViewModel";
|
||||
|
||||
public IObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = new ObservableCollectionExtended<CheckUpdateModel>();
|
||||
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> CheckOnlyCmd { get; }
|
||||
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
|
||||
public EventChannel<RxVoid> ReloadRequested { get; } = new();
|
||||
|
||||
public CheckUpdateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public BulkObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = [];
|
||||
public ReactiveCommand<RxVoid, RxVoid> CheckUpdateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> CheckOnlyCmd { get; }
|
||||
[Reactive] public partial bool EnableCheckPreReleaseUpdate { get; set; }
|
||||
|
||||
public CheckUpdateViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
CheckUpdateCmd = ReactiveCommand.CreateFromTask(CheckUpdate);
|
||||
CheckUpdateCmd.ThrownExceptions.Subscribe(ex =>
|
||||
@@ -287,10 +288,9 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
|
||||
private async Task UpdateFinishedSub(bool blReload)
|
||||
{
|
||||
RxSchedulers.MainThreadScheduler.Schedule(blReload, (scheduler, blReload) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = UpdateFinishedResult(blReload);
|
||||
return Disposable.Empty;
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
@@ -299,7 +299,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
{
|
||||
if (blReload)
|
||||
{
|
||||
AppEvents.ReloadRequested.Publish();
|
||||
ReloadRequested.Publish();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -403,10 +403,9 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
Remarks = msg,
|
||||
};
|
||||
|
||||
RxSchedulers.MainThreadScheduler.Schedule(item, (scheduler, model) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = UpdateViewResult(model);
|
||||
return Disposable.Empty;
|
||||
_ = UpdateViewResult(item);
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class ClashConnectionsViewModel : MyReactiveObject
|
||||
public partial class ClashConnectionsViewModel : MyReactiveObject
|
||||
{
|
||||
public IObservableCollection<ClashConnectionModel> ConnectionItems { get; } = new ObservableCollectionExtended<ClashConnectionModel>();
|
||||
public BulkObservableCollection<ClashConnectionModel> ConnectionItems { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public ClashConnectionModel SelectedSource { get; set; }
|
||||
public partial ClashConnectionModel SelectedSource { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ConnectionCloseCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ConnectionCloseAllCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseAllCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public string HostFilter { get; set; }
|
||||
public partial string HostFilter { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
public partial bool AutoRefresh { get; set; }
|
||||
|
||||
public ClashConnectionsViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ClashConnectionsViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
@@ -56,10 +55,9 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
|
||||
RxSchedulers.MainThreadScheduler.Schedule(ret?.connections, (scheduler, model) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = RefreshConnections(model);
|
||||
return Disposable.Empty;
|
||||
_ = RefreshConnections(ret?.connections);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +1,40 @@
|
||||
using System.Reactive.Concurrency;
|
||||
using static ServiceLib.Models.Dto.ClashProviders;
|
||||
using static ServiceLib.Models.Dto.ClashProxies;
|
||||
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class ClashProxiesViewModel : MyReactiveObject
|
||||
public partial class ClashProxiesViewModel : MyReactiveObject
|
||||
{
|
||||
private Dictionary<string, ProxiesItem>? _proxies;
|
||||
private Dictionary<string, ProvidersItem>? _providers;
|
||||
private readonly int _delayTimeout = 99999999;
|
||||
|
||||
public IObservableCollection<ClashProxyModel> ProxyGroups { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
||||
public IObservableCollection<ClashProxyModel> ProxyDetails { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
||||
public BulkObservableCollection<ClashProxyModel> ProxyGroups { get; } = [];
|
||||
public BulkObservableCollection<ClashProxyModel> ProxyDetails { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public ClashProxyModel SelectedGroup { get; set; }
|
||||
public partial ClashProxyModel SelectedGroup { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public ClashProxyModel SelectedDetail { get; set; }
|
||||
public partial ClashProxyModel SelectedDetail { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ProxiesReloadCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ProxiesDelayTestCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ProxiesDelayTestPartCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ProxiesSelectActivityCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesReloadCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesDelayTestCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesDelayTestPartCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesSelectActivityCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public int RuleModeSelected { get; set; }
|
||||
public partial int RuleModeSelected { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int SortingSelected { get; set; }
|
||||
public partial int SortingSelected { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
public partial bool AutoRefresh { get; set; }
|
||||
|
||||
public ClashProxiesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ClashProxiesViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -86,15 +84,6 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
|
||||
#endregion WhenAnyValue && ReactiveCommand
|
||||
|
||||
#region AppEvents
|
||||
|
||||
AppEvents.ProxiesReloadRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await ProxiesReload());
|
||||
|
||||
#endregion AppEvents
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
@@ -389,10 +378,9 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
}
|
||||
|
||||
var model = new SpeedTestResult() { IndexId = item.Name, Delay = result };
|
||||
RxSchedulers.MainThreadScheduler.Schedule(model, (scheduler, model) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = ProxiesDelayTestResult(model);
|
||||
return Disposable.Empty;
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
|
||||
@@ -1,44 +1,48 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class DNSSettingViewModel : MyReactiveObject
|
||||
public partial class DNSSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
[Reactive] public bool? UseSystemHosts { get; set; }
|
||||
[Reactive] public bool? AddCommonHosts { get; set; }
|
||||
[Reactive] public bool? FakeIP { get; set; }
|
||||
[Reactive] public bool? BlockBindingQuery { get; set; }
|
||||
[Reactive] public string? DirectDNS { get; set; }
|
||||
[Reactive] public string? RemoteDNS { get; set; }
|
||||
[Reactive] public string? BootstrapDNS { get; set; }
|
||||
[Reactive] public string? Strategy4Freedom { get; set; }
|
||||
[Reactive] public string? Strategy4Proxy { get; set; }
|
||||
[Reactive] public string? Hosts { get; set; }
|
||||
[Reactive] public string? DirectExpectedIPs { get; set; }
|
||||
[Reactive] public bool? ParallelQuery { get; set; }
|
||||
[Reactive] public bool? ServeStale { get; set; }
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive] public bool UseSystemHostsCompatible { get; set; }
|
||||
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; }
|
||||
[Reactive] public string DomainDNSAddressCompatible { get; set; }
|
||||
[Reactive] public string NormalDNSCompatible { get; set; }
|
||||
[Reactive] public string TunDNSCompatible { get; set; }
|
||||
[Reactive] public partial bool UseSystemHosts { get; set; }
|
||||
[Reactive] public partial bool AddCommonHosts { get; set; }
|
||||
[Reactive] public partial bool FakeIP { get; set; }
|
||||
[Reactive] public partial string FakeIPRange { get; set; }
|
||||
[Reactive] public partial bool BlockBindingQuery { get; set; }
|
||||
[Reactive] public partial string DirectDNS { get; set; }
|
||||
[Reactive] public partial string RemoteDNS { get; set; }
|
||||
[Reactive] public partial string BootstrapDNS { get; set; }
|
||||
[Reactive] public partial string Strategy4Freedom { get; set; }
|
||||
[Reactive] public partial string Strategy4Proxy { get; set; }
|
||||
[Reactive] public partial string Strategy4ProxyDial { get; set; }
|
||||
[Reactive] public partial string Hosts { get; set; }
|
||||
[Reactive] public partial string DirectExpectedIPs { get; set; }
|
||||
[Reactive] public partial bool ParallelQuery { get; set; }
|
||||
[Reactive] public partial bool ServeStale { get; set; }
|
||||
[Reactive] public partial bool EnableHappyEyeballs { get; set; }
|
||||
|
||||
[Reactive] public string DomainStrategy4Freedom2Compatible { get; set; }
|
||||
[Reactive] public string DomainDNSAddress2Compatible { get; set; }
|
||||
[Reactive] public string NormalDNS2Compatible { get; set; }
|
||||
[Reactive] public string TunDNS2Compatible { get; set; }
|
||||
[Reactive] public bool RayCustomDNSEnableCompatible { get; set; }
|
||||
[Reactive] public bool SBCustomDNSEnableCompatible { get; set; }
|
||||
[Reactive] public partial bool UseSystemHostsCompatible { get; set; }
|
||||
[Reactive] public partial string DomainStrategy4FreedomCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string DomainDNSAddressCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string NormalDNSCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string TunDNSCompatible { get; set; } = string.Empty;
|
||||
|
||||
[ObservableAsProperty] public bool IsSimpleDNSEnabled { get; }
|
||||
[Reactive] public partial string DomainStrategy4Freedom2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string DomainDNSAddress2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string NormalDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string TunDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial bool RayCustomDNSEnableCompatible { get; set; }
|
||||
[Reactive] public partial bool SBCustomDNSEnableCompatible { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4V2rayCompatibleCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4SingboxCompatibleCmd { get; }
|
||||
public bool IsSimpleDNSEnabled => !(RayCustomDNSEnableCompatible && SBCustomDNSEnableCompatible);
|
||||
|
||||
public DNSSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ImportDefConfig4V2rayCompatibleCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ImportDefConfig4SingboxCompatibleCmd { get; }
|
||||
|
||||
public DNSSettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync);
|
||||
|
||||
ImportDefConfig4V2rayCompatibleCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
@@ -56,8 +60,7 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.RayCustomDNSEnableCompatible, x => x.SBCustomDNSEnableCompatible)
|
||||
.Select(x => x is not { Item1: true, Item2: true })
|
||||
.ToPropertyEx(this, x => x.IsSimpleDNSEnabled);
|
||||
.Subscribe(_ => this.RaisePropertyChanged(nameof(IsSimpleDNSEnabled)));
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
@@ -66,20 +69,22 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
var item = _config.SimpleDNSItem;
|
||||
UseSystemHosts = item.UseSystemHosts;
|
||||
AddCommonHosts = item.AddCommonHosts;
|
||||
FakeIP = item.FakeIP;
|
||||
BlockBindingQuery = item.BlockBindingQuery;
|
||||
DirectDNS = item.DirectDNS;
|
||||
RemoteDNS = item.RemoteDNS;
|
||||
BootstrapDNS = item.BootstrapDNS;
|
||||
Strategy4Freedom = item.Strategy4Freedom;
|
||||
Strategy4Proxy = item.Strategy4Proxy;
|
||||
Hosts = item.Hosts;
|
||||
DirectExpectedIPs = item.DirectExpectedIPs;
|
||||
ParallelQuery = item.ParallelQuery;
|
||||
ServeStale = item.ServeStale;
|
||||
|
||||
UseSystemHosts = item.UseSystemHosts ?? false;
|
||||
AddCommonHosts = item.AddCommonHosts ?? false;
|
||||
FakeIP = item.FakeIP ?? false;
|
||||
FakeIPRange = item.FakeIPRange ?? string.Empty;
|
||||
BlockBindingQuery = item.BlockBindingQuery ?? false;
|
||||
DirectDNS = item.DirectDNS ?? string.Empty;
|
||||
RemoteDNS = item.RemoteDNS ?? string.Empty;
|
||||
BootstrapDNS = item.BootstrapDNS ?? string.Empty;
|
||||
Strategy4Freedom = item.Strategy4Freedom ?? string.Empty;
|
||||
Strategy4Proxy = item.Strategy4Proxy ?? string.Empty;
|
||||
Strategy4ProxyDial = item.Strategy4ProxyDial ?? string.Empty;
|
||||
Hosts = item.Hosts ?? string.Empty;
|
||||
DirectExpectedIPs = item.DirectExpectedIPs ?? string.Empty;
|
||||
ParallelQuery = item.ParallelQuery ?? false;
|
||||
ServeStale = item.ServeStale ?? false;
|
||||
EnableHappyEyeballs = item.EnableHappyEyeballs ?? false;
|
||||
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
RayCustomDNSEnableCompatible = item1.Enabled;
|
||||
UseSystemHostsCompatible = item1.UseSystemHosts;
|
||||
@@ -101,17 +106,19 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
_config.SimpleDNSItem.UseSystemHosts = UseSystemHosts;
|
||||
_config.SimpleDNSItem.AddCommonHosts = AddCommonHosts;
|
||||
_config.SimpleDNSItem.FakeIP = FakeIP;
|
||||
_config.SimpleDNSItem.FakeIPRange = FakeIPRange;
|
||||
_config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery;
|
||||
_config.SimpleDNSItem.DirectDNS = DirectDNS;
|
||||
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
|
||||
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
|
||||
_config.SimpleDNSItem.Strategy4Freedom = Strategy4Freedom;
|
||||
_config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy;
|
||||
_config.SimpleDNSItem.Strategy4ProxyDial = Strategy4ProxyDial;
|
||||
_config.SimpleDNSItem.Hosts = Hosts;
|
||||
_config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs;
|
||||
_config.SimpleDNSItem.ParallelQuery = ParallelQuery;
|
||||
_config.SimpleDNSItem.ServeStale = ServeStale;
|
||||
|
||||
_config.SimpleDNSItem.EnableHappyEyeballs = EnableHappyEyeballs;
|
||||
if (NormalDNSCompatible.IsNotEmpty())
|
||||
{
|
||||
var obj = JsonUtils.ParseJson(NormalDNSCompatible);
|
||||
@@ -183,9 +190,6 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
await ConfigHandler.SaveDNSItems(_config, item2);
|
||||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
if (_updateView != null)
|
||||
{
|
||||
await _updateView(EViewAction.CloseWindow, null);
|
||||
}
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,48 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
public partial class FullConfigTemplateViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
#region Reactive
|
||||
|
||||
[Reactive]
|
||||
public bool EnableFullConfigTemplate4Ray { get; set; }
|
||||
public partial bool EnableFullConfigTemplate4Ray { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool EnableFullConfigTemplate4Singbox { get; set; }
|
||||
public partial bool EnableFullConfigTemplate4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string FullConfigTemplate4Ray { get; set; }
|
||||
public partial string FullConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullTunConfigTemplate4Ray { get; set; }
|
||||
public partial string FullTunConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullConfigTemplate4Singbox { get; set; }
|
||||
public partial string FullConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullTunConfigTemplate4Singbox { get; set; }
|
||||
public partial string FullTunConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public bool AddProxyOnly4Ray { get; set; }
|
||||
public partial bool AddProxyOnly4Ray { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AddProxyOnly4Singbox { get; set; }
|
||||
public partial bool AddProxyOnly4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string ProxyDetour4Ray { get; set; }
|
||||
public partial string ProxyDetour4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string ProxyDetour4Singbox { get; set; }
|
||||
public partial string ProxyDetour4Singbox { get; set; } = string.Empty;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
#endregion Reactive
|
||||
|
||||
public FullConfigTemplateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public FullConfigTemplateViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveSettingAsync();
|
||||
@@ -84,7 +85,7 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
}
|
||||
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_ = _updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task<bool> SaveXrayConfigAsync()
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class GlobalHotkeySettingViewModel : MyReactiveObject
|
||||
public class GlobalHotkeySettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
private readonly List<KeyEventItem> _globalHotkeys;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public GlobalHotkeySettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public GlobalHotkeySettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
_globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys);
|
||||
|
||||
@@ -51,7 +52,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject
|
||||
|
||||
if (await ConfigHandler.SaveConfig(_config) == 0)
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,81 +1,99 @@
|
||||
using System.Reactive.Concurrency;
|
||||
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class MainWindowViewModel : MyReactiveObject
|
||||
public partial class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<RxVoid, string?> ReadTextFromClipboardInteraction { get; } = new();
|
||||
public Interaction<RxVoid, byte[]?> ScanScreenInteraction { get; } = new();
|
||||
public Interaction<RxVoid, string?> BrowseImageFileInteraction { get; } = new();
|
||||
public Interaction<bool?, RxVoid> ShowHideWindowInteraction { get; } = new();
|
||||
|
||||
public bool DesignMode { get; set; }
|
||||
|
||||
public ProfilesViewModel ProfilesViewModel { get; } = new();
|
||||
public MsgViewModel MsgViewModel { get; } = new();
|
||||
public ClashProxiesViewModel ClashProxiesViewModel { get; } = new();
|
||||
public ClashConnectionsViewModel ClashConnectionsViewModel { get; } = new();
|
||||
public CheckUpdateViewModel CheckUpdateViewModel { get; } = new();
|
||||
public BackupAndRestoreViewModel BackupAndRestoreViewModel { get; } = new();
|
||||
public StatusBarViewModel StatusBarViewModel { get; } = StatusBarViewModel.Instance;
|
||||
|
||||
#region Menu
|
||||
|
||||
//servers
|
||||
public ReactiveCommand<Unit, Unit> AddVmessServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddVmessServerCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> AddVlessServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddShadowsocksServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddSocksServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddHttpServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddTrojanServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddAnytlsServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddNaiveServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddPolicyGroupServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddProxyChainServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaImageCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddVlessServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddShadowsocksServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddSocksServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddHttpServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddTrojanServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddHysteria2ServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddTuicServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddWireguardServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddAnytlsServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddNaiveServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddCustomServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddCustomOutboundServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddPolicyGroupServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddProxyChainServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddServerViaClipboardCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddServerViaScanCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddServerViaImageCmd { get; }
|
||||
|
||||
//Subscription
|
||||
public ReactiveCommand<Unit, Unit> SubSettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubSettingCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SubUpdateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubUpdateViaProxyCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubGroupUpdateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubGroupUpdateViaProxyCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubUpdateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubUpdateViaProxyCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubGroupUpdateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubGroupUpdateViaProxyCmd { get; }
|
||||
|
||||
//Setting
|
||||
public ReactiveCommand<Unit, Unit> OptionSettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> OptionSettingCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RoutingSettingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> DNSSettingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> FullConfigTemplateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GlobalHotkeySettingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RebootAsAdminCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> OpenTheFileLocationCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RoutingSettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> DNSSettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> FullConfigTemplateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> GlobalHotkeySettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RebootAsAdminCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ClearServerStatisticsCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> OpenTheFileLocationCmd { get; }
|
||||
|
||||
//Presets
|
||||
public ReactiveCommand<Unit, Unit> RegionalPresetDefaultCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RegionalPresetDefaultCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RegionalPresetRussiaCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RegionalPresetRussiaCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RegionalPresetIranCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RegionalPresetIranCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ReloadCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ReloadCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public bool BlReloadEnabled { get; set; }
|
||||
public partial bool BlReloadEnabled { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool ShowClashUI { get; set; }
|
||||
public partial bool ShowClashUI { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int TabMainSelectedIndex { get; set; }
|
||||
public partial int TabMainSelectedIndex { get; set; }
|
||||
|
||||
[Reactive] public bool BlIsWindows { get; set; }
|
||||
[Reactive] public partial bool BlIsWindows { get; set; }
|
||||
|
||||
[Reactive] public bool BlNewUpdate { get; set; }
|
||||
[Reactive] public partial bool BlNewUpdate { get; set; }
|
||||
|
||||
[Reactive] public partial EGirdOrientation MainGirdOrientation { get; set; }
|
||||
|
||||
#endregion Menu
|
||||
|
||||
private readonly SynchronizationContext _uiContext = SynchronizationContext.Current;
|
||||
|
||||
#region Init
|
||||
|
||||
public MainWindowViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
BlIsWindows = Utils.IsWindows();
|
||||
MainGirdOrientation = _config.UiItem.MainGirdOrientation;
|
||||
|
||||
#region WhenAnyValue && ReactiveCommand
|
||||
|
||||
@@ -128,6 +146,10 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
await AddServerAsync(EConfigType.Custom);
|
||||
});
|
||||
AddCustomOutboundServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddServerAsync(EConfigType.Outbound);
|
||||
});
|
||||
AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddServerAsync(EConfigType.PolicyGroup);
|
||||
@@ -191,7 +213,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
});
|
||||
GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
|
||||
var globalHotkeySettingViewModel = new GlobalHotkeySettingViewModel();
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(globalHotkeySettingViewModel) == true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
}
|
||||
@@ -233,26 +256,11 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
#region AppEvents
|
||||
|
||||
AppEvents.ReloadRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await Reload());
|
||||
|
||||
AppEvents.AddServerViaScanRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaScanAsync());
|
||||
|
||||
AppEvents.AddServerViaClipboardRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
|
||||
|
||||
AppEvents.SubscriptionsUpdateRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
|
||||
|
||||
AppEvents.HasUpdateNotified
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
@@ -260,6 +268,53 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
#endregion AppEvents
|
||||
|
||||
ProfilesViewModel.RefreshServersRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await RefreshServers());
|
||||
|
||||
var vmReloadRequestedList = new List<IObservable<RxVoid>>
|
||||
{
|
||||
ProfilesViewModel.ReloadRequested.AsObservable(),
|
||||
StatusBarViewModel.ReloadRequested.AsObservable(),
|
||||
CheckUpdateViewModel.ReloadRequested.AsObservable(),
|
||||
};
|
||||
|
||||
foreach (var reloadRequested in vmReloadRequestedList)
|
||||
{
|
||||
reloadRequested
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await Reload());
|
||||
}
|
||||
|
||||
StatusBarViewModel.AddServerViaScanRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaScanAsync());
|
||||
|
||||
StatusBarViewModel.AddServerViaClipboardRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
|
||||
|
||||
StatusBarViewModel.ShowHideWindowRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async blShow =>
|
||||
{
|
||||
await ShowHideWindowInteraction.Handle(blShow);
|
||||
});
|
||||
|
||||
StatusBarViewModel.SetDefaultServerRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async indexId => await ProfilesViewModel.SetDefaultServer(indexId));
|
||||
|
||||
StatusBarViewModel.SubscriptionsUpdateRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
@@ -267,6 +322,11 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
AppManager.Instance.ShowInTaskbar = true;
|
||||
|
||||
if (DesignMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//await ConfigHandler.InitBuiltinRouting(_config);
|
||||
await ConfigHandler.InitBuiltinDNS(_config);
|
||||
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
|
||||
@@ -279,7 +339,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
|
||||
}
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
|
||||
await Reload();
|
||||
}
|
||||
@@ -304,7 +364,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
if (success)
|
||||
{
|
||||
var indexIdOld = _config.IndexId;
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
|
||||
// If indexId changed or subIndexId is empty, directly reload.
|
||||
if (indexIdOld != _config.IndexId || _config.SubIndexId.IsNullOrEmpty())
|
||||
@@ -323,7 +383,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
|
||||
{
|
||||
AppEvents.AdjustMainLvColWidthRequested.Publish();
|
||||
await ProfilesViewModel.AdjustMainLvColWidth();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,14 +404,23 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task RefreshServers()
|
||||
{
|
||||
AppEvents.ProfilesRefreshRequested.Publish();
|
||||
await ProfilesViewModel.RefreshServersBiz();
|
||||
await StatusBarViewModel.RefreshServersBiz();
|
||||
|
||||
await Task.Delay(200);
|
||||
// await Task.Delay(200);
|
||||
}
|
||||
|
||||
private void RefreshSubscriptions()
|
||||
private async Task RefreshServersDispatcherAsync()
|
||||
{
|
||||
AppEvents.SubscriptionsRefreshRequested.Publish();
|
||||
//await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
||||
_uiContext?.Post(_ => _ = RefreshServers(), null);
|
||||
}
|
||||
|
||||
private async Task RefreshSubscriptions()
|
||||
{
|
||||
//await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
||||
|
||||
_uiContext?.Post(_ => _ = ProfilesViewModel.RefreshSubscriptions(), null);
|
||||
}
|
||||
|
||||
#endregion Servers && Groups
|
||||
@@ -368,21 +437,24 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
};
|
||||
|
||||
bool? ret = false;
|
||||
if (eConfigType == EConfigType.Custom)
|
||||
if (eConfigType is EConfigType.Custom or EConfigType.Outbound)
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item);
|
||||
var addServer2ViewModel = new AddServer2ViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
||||
}
|
||||
else if (eConfigType.IsGroupType())
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddGroupServerWindow, item);
|
||||
var addGroupServerViewModel = new AddGroupServerViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addGroupServerViewModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item);
|
||||
var addServerViewModel = new AddServerViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServerViewModel);
|
||||
}
|
||||
if (ret == true)
|
||||
{
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
if (item.IndexId == _config.IndexId)
|
||||
{
|
||||
await Reload();
|
||||
@@ -392,16 +464,22 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaClipboardAsync(string? clipboardData)
|
||||
{
|
||||
var stringData = clipboardData;
|
||||
if (clipboardData == null)
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null);
|
||||
return;
|
||||
var result = await ReadTextFromClipboardInteraction.Handle(RxVoid.Default);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
stringData = result;
|
||||
}
|
||||
var ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false);
|
||||
var ret = await ConfigHandler.AddBatchServers(_config, stringData, _config.SubIndexId, false);
|
||||
if (ret > 0)
|
||||
{
|
||||
RefreshSubscriptions();
|
||||
await RefreshServers();
|
||||
await RefreshSubscriptions();
|
||||
await RefreshServersDispatcherAsync();
|
||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
|
||||
}
|
||||
else
|
||||
@@ -412,8 +490,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaScanAsync()
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.ScanScreenTask, null);
|
||||
await Task.CompletedTask;
|
||||
var result = await ScanScreenInteraction.Handle(RxVoid.Default);
|
||||
await ScanScreenResult(result);
|
||||
}
|
||||
|
||||
public async Task ScanScreenResult(byte[]? bytes)
|
||||
@@ -424,8 +502,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaImageAsync()
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.ScanImageTask, null);
|
||||
await Task.CompletedTask;
|
||||
var imageFileName = await BrowseImageFileInteraction.Handle(RxVoid.Default);
|
||||
await AddScanResultAsync(imageFileName);
|
||||
}
|
||||
|
||||
public async Task ScanImageResult(string fileName)
|
||||
@@ -450,8 +528,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
var ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false);
|
||||
if (ret > 0)
|
||||
{
|
||||
RefreshSubscriptions();
|
||||
await RefreshServers();
|
||||
await RefreshSubscriptions();
|
||||
await RefreshServersDispatcherAsync();
|
||||
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
|
||||
}
|
||||
else
|
||||
@@ -467,9 +545,10 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task SubSettingAsync()
|
||||
{
|
||||
if (await _updateView?.Invoke(EViewAction.SubSettingWindow, null) == true)
|
||||
var subSettingViewModel = new SubSettingViewModel();
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(subSettingViewModel) == true)
|
||||
{
|
||||
RefreshSubscriptions();
|
||||
await RefreshSubscriptions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,28 +563,38 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task OptionSettingAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null);
|
||||
var settingViewModel = new OptionSettingViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(settingViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
AppEvents.InboundDisplayRequested.Publish();
|
||||
MainGirdOrientation = _config.UiItem.MainGirdOrientation;
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.InboundDisplayStatus();
|
||||
});
|
||||
await Reload();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RoutingSettingAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.RoutingSettingWindow, null);
|
||||
var routingSettingViewModel = new RoutingSettingViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(routingSettingViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
await ConfigHandler.InitBuiltinRouting(_config);
|
||||
AppEvents.RoutingsMenuRefreshRequested.Publish();
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.RefreshRoutingsMenu();
|
||||
});
|
||||
await Reload();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DNSSettingAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.DNSSettingWindow, null);
|
||||
var dnsSettingViewModel = new DNSSettingViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(dnsSettingViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
await Reload();
|
||||
@@ -514,7 +603,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task FullConfigTemplateAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.FullConfigTemplateWindow, null);
|
||||
var fullConfigTemplateViewModel = new FullConfigTemplateViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(fullConfigTemplateViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
await Reload();
|
||||
@@ -524,7 +614,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
private async Task ClearServerStatistics()
|
||||
{
|
||||
await StatisticsManager.Instance.ClearAllServerStatistics();
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
}
|
||||
|
||||
private async Task OpenTheFileLocation()
|
||||
@@ -561,6 +651,12 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
|
||||
if (DesignMode)
|
||||
{
|
||||
_reloadSemaphore.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SetReloadEnabled(false);
|
||||
@@ -583,12 +679,22 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
await SysProxyHandler.UpdateSysProxy(_config, false);
|
||||
await Task.Delay(1000);
|
||||
});
|
||||
AppEvents.TestServerRequested.Publish();
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.TestServerAvailability();
|
||||
});
|
||||
|
||||
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box);
|
||||
if (showClashUI)
|
||||
{
|
||||
AppEvents.ProxiesReloadRequested.Publish();
|
||||
//await Observable.Start(async () =>
|
||||
//{
|
||||
// await ClashProxiesViewModel.ProxiesReload();
|
||||
//}, RxSchedulers.MainThreadScheduler);
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await ClashProxiesViewModel.ProxiesReload();
|
||||
});
|
||||
}
|
||||
|
||||
ReloadResult(showClashUI);
|
||||
@@ -633,7 +739,10 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
await ConfigHandler.ApplyRegionalPreset(_config, type);
|
||||
await ConfigHandler.InitRouting(_config);
|
||||
AppEvents.RoutingsMenuRefreshRequested.Publish();
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.RefreshRoutingsMenu();
|
||||
});
|
||||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
await new UpdateService(_config, UpdateTaskHandler).UpdateGeoFileAll();
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class MsgViewModel : MyReactiveObject
|
||||
public partial class MsgViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, RxVoid> DispatcherShowMsgInteraction { get; } = new();
|
||||
|
||||
private readonly ConcurrentQueue<string> _queueMsg = new();
|
||||
private volatile bool _lastMsgFilterNotAvailable;
|
||||
private int _showLock = 0; // 0 = unlocked, 1 = locked
|
||||
public int NumMaxMsg { get; } = 500;
|
||||
|
||||
[Reactive]
|
||||
public string MsgFilter { get; set; }
|
||||
public partial string MsgFilter { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
public partial bool AutoRefresh { get; set; }
|
||||
|
||||
public MsgViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public MsgViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty;
|
||||
AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true;
|
||||
|
||||
@@ -35,6 +36,11 @@ public class MsgViewModel : MyReactiveObject
|
||||
.Subscribe(content => _ = AppendQueueMsg(content));
|
||||
}
|
||||
|
||||
public void FlushQueueMsg()
|
||||
{
|
||||
_ = AppendQueueMsg(string.Empty);
|
||||
}
|
||||
|
||||
private async Task AppendQueueMsg(string msg)
|
||||
{
|
||||
if (AutoRefresh == false)
|
||||
@@ -64,7 +70,17 @@ public class MsgViewModel : MyReactiveObject
|
||||
sb.Append(line);
|
||||
}
|
||||
|
||||
await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString());
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await DispatcherShowMsgInteraction.Handle(sb.ToString());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_queueMsg.Enqueue(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -74,6 +90,11 @@ public class MsgViewModel : MyReactiveObject
|
||||
|
||||
private void EnqueueQueueMsg(string msg)
|
||||
{
|
||||
if (string.IsNullOrEmpty(msg))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//filter msg
|
||||
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable)
|
||||
{
|
||||
|
||||
@@ -1,132 +1,123 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class OptionSettingViewModel : MyReactiveObject
|
||||
public partial class OptionSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
#region Core
|
||||
|
||||
[Reactive] public int LocalPort { get; set; }
|
||||
[Reactive] public bool SecondLocalPortEnabled { get; set; }
|
||||
[Reactive] public bool UdpEnabled { get; set; }
|
||||
[Reactive] public bool SniffingEnabled { get; set; }
|
||||
[Reactive] public partial int LocalPort { get; set; }
|
||||
[Reactive] public partial bool SecondLocalPortEnabled { get; set; }
|
||||
[Reactive] public partial bool UdpEnabled { get; set; }
|
||||
[Reactive] public partial bool SniffingEnabled { get; set; }
|
||||
public IList<string> DestOverride { get; set; }
|
||||
[Reactive] public bool RouteOnly { get; set; }
|
||||
[Reactive] public bool AllowLANConn { get; set; }
|
||||
[Reactive] public bool NewPort4LAN { get; set; }
|
||||
[Reactive] public string User { get; set; }
|
||||
[Reactive] public string Pass { get; set; }
|
||||
[Reactive] public bool LogEnabled { get; set; }
|
||||
[Reactive] public string Loglevel { get; set; }
|
||||
[Reactive] public string DefFingerprint { get; set; }
|
||||
[Reactive] public string DefUserAgent { get; set; }
|
||||
[Reactive] public string SendThrough { get; set; }
|
||||
[Reactive] public string BindInterface { get; set; }
|
||||
[Reactive] public string Mux4SboxProtocol { get; set; }
|
||||
[Reactive] public bool EnableCacheFile4Sbox { get; set; }
|
||||
[Reactive] public int? HyUpMbps { get; set; }
|
||||
[Reactive] public int? HyDownMbps { get; set; }
|
||||
[Reactive] public bool EnableFragment { get; set; }
|
||||
[Reactive] public bool EnableFinalFragment { get; set; }
|
||||
[Reactive] public string FragmentPackets { get; set; }
|
||||
[Reactive] public string FragmentLength { get; set; }
|
||||
[Reactive] public string FragmentInterval { get; set; }
|
||||
[Reactive] public string FragmentMaxSplit { get; set; }
|
||||
[Reactive] public partial bool RouteOnly { get; set; }
|
||||
[Reactive] public partial bool AllowLANConn { get; set; }
|
||||
[Reactive] public partial bool NewPort4LAN { get; set; }
|
||||
[Reactive] public partial string User { get; set; }
|
||||
[Reactive] public partial string Pass { get; set; }
|
||||
[Reactive] public partial bool LogEnabled { get; set; }
|
||||
[Reactive] public partial string Loglevel { get; set; }
|
||||
[Reactive] public partial string DefFingerprint { get; set; }
|
||||
[Reactive] public partial string DefUserAgent { get; set; }
|
||||
[Reactive] public partial string SendThrough { get; set; }
|
||||
[Reactive] public partial string BindInterface { get; set; }
|
||||
[Reactive] public partial string Mux4SboxProtocol { get; set; }
|
||||
[Reactive] public partial bool EnableCacheFile4Sbox { get; set; }
|
||||
[Reactive] public partial int? HyUpMbps { get; set; }
|
||||
[Reactive] public partial int? HyDownMbps { get; set; }
|
||||
[Reactive] public partial bool EnableFragment { get; set; }
|
||||
[Reactive] public partial bool EnableFinalFragment { get; set; }
|
||||
[Reactive] public partial string FragmentPackets { get; set; }
|
||||
[Reactive] public partial string FragmentLengths { get; set; }
|
||||
[Reactive] public partial string FragmentDelays { get; set; }
|
||||
[Reactive] public partial string FragmentMaxSplit { get; set; }
|
||||
|
||||
#endregion Core
|
||||
|
||||
#region Core KCP
|
||||
|
||||
//[Reactive] public int Kcpmtu { get; set; }
|
||||
//[Reactive] public int Kcptti { get; set; }
|
||||
//[Reactive] public int KcpuplinkCapacity { get; set; }
|
||||
//[Reactive] public int KcpdownlinkCapacity { get; set; }
|
||||
//[Reactive] public int KcpreadBufferSize { get; set; }
|
||||
//[Reactive] public int KcpwriteBufferSize { get; set; }
|
||||
//[Reactive] public bool Kcpcongestion { get; set; }
|
||||
|
||||
#endregion Core KCP
|
||||
|
||||
#region UI
|
||||
|
||||
[Reactive] public bool AutoRun { get; set; }
|
||||
[Reactive] public bool EnableStatistics { get; set; }
|
||||
[Reactive] public bool KeepOlderDedupl { get; set; }
|
||||
[Reactive] public bool DisplayRealTimeSpeed { get; set; }
|
||||
[Reactive] public bool EnableAutoAdjustMainLvColWidth { get; set; }
|
||||
[Reactive] public bool AutoHideStartup { get; set; }
|
||||
[Reactive] public bool Hide2TrayWhenClose { get; set; }
|
||||
[Reactive] public bool MacOSShowInDock { get; set; }
|
||||
[Reactive] public bool EnableDragDropSort { get; set; }
|
||||
[Reactive] public bool DoubleClick2Activate { get; set; }
|
||||
[Reactive] public int AutoUpdateInterval { get; set; }
|
||||
[Reactive] public int TrayMenuServersLimit { get; set; }
|
||||
[Reactive] public string CurrentFontFamily { get; set; }
|
||||
[Reactive] public int SpeedTestTimeout { get; set; }
|
||||
[Reactive] public string SpeedTestUrl { get; set; }
|
||||
[Reactive] public string SpeedPingTestUrl { get; set; }
|
||||
[Reactive] public string UdpTestTarget { get; set; }
|
||||
[Reactive] public int MixedConcurrencyCount { get; set; }
|
||||
[Reactive] public bool EnableHWA { get; set; }
|
||||
[Reactive] public string SubConvertUrl { get; set; }
|
||||
[Reactive] public int MainGirdOrientation { get; set; }
|
||||
[Reactive] public string GeoFileSourceUrl { get; set; }
|
||||
[Reactive] public string SrsFileSourceUrl { get; set; }
|
||||
[Reactive] public string RoutingRulesSourceUrl { get; set; }
|
||||
[Reactive] public string IPAPIUrl { get; set; }
|
||||
[Reactive] public string RootCertProvider { get; set; }
|
||||
[Reactive] public partial bool AutoRun { get; set; }
|
||||
[Reactive] public partial bool EnableStatistics { get; set; }
|
||||
[Reactive] public partial bool KeepOlderDedupl { get; set; }
|
||||
[Reactive] public partial bool DisplayRealTimeSpeed { get; set; }
|
||||
[Reactive] public partial bool EnableAutoAdjustMainLvColWidth { get; set; }
|
||||
[Reactive] public partial bool AutoHideStartup { get; set; }
|
||||
[Reactive] public partial bool Hide2TrayWhenClose { get; set; }
|
||||
[Reactive] public partial bool MacOSShowInDock { get; set; }
|
||||
[Reactive] public partial bool EnableDragDropSort { get; set; }
|
||||
[Reactive] public partial bool DoubleClick2Activate { get; set; }
|
||||
[Reactive] public partial int AutoUpdateInterval { get; set; }
|
||||
[Reactive] public partial int TrayMenuServersLimit { get; set; }
|
||||
[Reactive] public partial string CurrentFontFamily { get; set; }
|
||||
[Reactive] public partial int SpeedTestTimeout { get; set; }
|
||||
[Reactive] public partial string SpeedTestUrl { get; set; }
|
||||
[Reactive] public partial string SpeedPingTestUrl { get; set; }
|
||||
[Reactive] public partial string UdpTestTarget { get; set; }
|
||||
[Reactive] public partial int MixedConcurrencyCount { get; set; }
|
||||
[Reactive] public partial bool EnableHWA { get; set; }
|
||||
[Reactive] public partial string SubConvertUrl { get; set; }
|
||||
[Reactive] public partial int MainGirdOrientation { get; set; }
|
||||
[Reactive] public partial string GeoFileSourceUrl { get; set; }
|
||||
[Reactive] public partial string SrsFileSourceUrl { get; set; }
|
||||
[Reactive] public partial string RoutingRulesSourceUrl { get; set; }
|
||||
[Reactive] public partial string IPAPIUrl { get; set; }
|
||||
[Reactive] public partial string RootCertProvider { get; set; }
|
||||
|
||||
#endregion UI
|
||||
|
||||
#region UI visibility
|
||||
|
||||
[Reactive] public bool BlIsWindows { get; set; }
|
||||
[Reactive] public bool BlIsLinux { get; set; }
|
||||
[Reactive] public bool BlIsIsMacOS { get; set; }
|
||||
[Reactive] public bool BlIsNonWindows { get; set; }
|
||||
[Reactive] public partial bool BlIsWindows { get; set; }
|
||||
[Reactive] public partial bool BlIsLinux { get; set; }
|
||||
[Reactive] public partial bool BlIsIsMacOS { get; set; }
|
||||
[Reactive] public partial bool BlIsNonWindows { get; set; }
|
||||
|
||||
#endregion UI visibility
|
||||
|
||||
#region System proxy
|
||||
|
||||
[Reactive] public bool NotProxyLocalAddress { get; set; }
|
||||
[Reactive] public string SystemProxyAdvancedProtocol { get; set; }
|
||||
[Reactive] public string SystemProxyExceptions { get; set; }
|
||||
[Reactive] public string CustomSystemProxyPacPath { get; set; }
|
||||
[Reactive] public string CustomSystemProxyScriptPath { get; set; }
|
||||
[Reactive] public partial bool NotProxyLocalAddress { get; set; }
|
||||
[Reactive] public partial string SystemProxyAdvancedProtocol { get; set; }
|
||||
[Reactive] public partial string SystemProxyExceptions { get; set; }
|
||||
[Reactive] public partial string CustomSystemProxyPacPath { get; set; }
|
||||
[Reactive] public partial string CustomSystemProxyScriptPath { get; set; }
|
||||
|
||||
#endregion System proxy
|
||||
|
||||
#region Tun mode
|
||||
|
||||
[Reactive] public bool TunAutoRoute { get; set; }
|
||||
[Reactive] public bool TunStrictRoute { get; set; }
|
||||
[Reactive] public string TunStack { get; set; }
|
||||
[Reactive] public int TunMtu { get; set; }
|
||||
[Reactive] public bool TunEnableIPv6Address { get; set; }
|
||||
[Reactive] public string TunIcmpRouting { get; set; }
|
||||
[Reactive] public bool TunEnableLegacyProtect { get; set; }
|
||||
[Reactive] public string TunRouteExcludeAddress { get; set; }
|
||||
[Reactive] public partial bool TunAutoRoute { get; set; }
|
||||
[Reactive] public partial bool TunStrictRoute { get; set; }
|
||||
[Reactive] public partial string TunStack { get; set; }
|
||||
[Reactive] public partial int TunMtu { get; set; }
|
||||
[Reactive] public partial bool TunEnableIPv6Address { get; set; }
|
||||
[Reactive] public partial string TunIcmpRouting { get; set; }
|
||||
[Reactive] public partial bool TunEnableLegacyProtect { get; set; }
|
||||
[Reactive] public partial string TunRouteExcludeAddress { get; set; }
|
||||
[Reactive] public partial string TunIPv4Address { get; set; }
|
||||
[Reactive] public partial string TunIPv6Address { get; set; }
|
||||
|
||||
#endregion Tun mode
|
||||
|
||||
#region CoreType
|
||||
|
||||
[Reactive] public string CoreType1 { get; set; }
|
||||
[Reactive] public string CoreType2 { get; set; }
|
||||
[Reactive] public string CoreType3 { get; set; }
|
||||
[Reactive] public string CoreType4 { get; set; }
|
||||
[Reactive] public string CoreType5 { get; set; }
|
||||
[Reactive] public string CoreType6 { get; set; }
|
||||
[Reactive] public string CoreType7 { get; set; }
|
||||
[Reactive] public string CoreType9 { get; set; }
|
||||
[Reactive] public partial string CoreType1 { get; set; }
|
||||
[Reactive] public partial string CoreType2 { get; set; }
|
||||
[Reactive] public partial string CoreType3 { get; set; }
|
||||
[Reactive] public partial string CoreType4 { get; set; }
|
||||
[Reactive] public partial string CoreType5 { get; set; }
|
||||
[Reactive] public partial string CoreType6 { get; set; }
|
||||
[Reactive] public partial string CoreType7 { get; set; }
|
||||
[Reactive] public partial string CoreType9 { get; set; }
|
||||
|
||||
#endregion CoreType
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public OptionSettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
BlIsWindows = Utils.IsWindows();
|
||||
BlIsLinux = Utils.IsLinux();
|
||||
BlIsIsMacOS = Utils.IsMacOS();
|
||||
@@ -142,8 +133,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.InitSettingFont, null);
|
||||
|
||||
#region Core
|
||||
|
||||
var inbound = _config.Inbound.First();
|
||||
@@ -151,6 +140,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
SecondLocalPortEnabled = inbound.SecondLocalPortEnabled;
|
||||
UdpEnabled = inbound.UdpEnabled;
|
||||
SniffingEnabled = inbound.SniffingEnabled;
|
||||
DestOverride = inbound.DestOverride ?? [];
|
||||
RouteOnly = inbound.RouteOnly;
|
||||
AllowLANConn = inbound.AllowLANConn;
|
||||
NewPort4LAN = inbound.NewPort4LAN;
|
||||
@@ -169,24 +159,12 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
EnableFragment = _config.CoreBasicItem.EnableFragment;
|
||||
EnableFinalFragment = _config.CoreBasicItem.EnableFinalFragment;
|
||||
FragmentPackets = _config.Fragment4RayItem?.Packets;
|
||||
FragmentLength = _config.Fragment4RayItem?.Length;
|
||||
FragmentInterval = _config.Fragment4RayItem?.Interval;
|
||||
FragmentLengths = Utils.List2String(_config.Fragment4RayItem?.Lengths);
|
||||
FragmentDelays = Utils.List2String(_config.Fragment4RayItem?.Delays);
|
||||
FragmentMaxSplit = _config.Fragment4RayItem?.MaxSplit;
|
||||
|
||||
#endregion Core
|
||||
|
||||
#region Core KCP
|
||||
|
||||
//Kcpmtu = _config.kcpItem.mtu;
|
||||
//Kcptti = _config.kcpItem.tti;
|
||||
//KcpuplinkCapacity = _config.kcpItem.uplinkCapacity;
|
||||
//KcpdownlinkCapacity = _config.kcpItem.downlinkCapacity;
|
||||
//KcpreadBufferSize = _config.kcpItem.readBufferSize;
|
||||
//KcpwriteBufferSize = _config.kcpItem.writeBufferSize;
|
||||
//Kcpcongestion = _config.kcpItem.congestion;
|
||||
|
||||
#endregion Core KCP
|
||||
|
||||
#region UI
|
||||
|
||||
AutoRun = _config.GuiItem.AutoRun;
|
||||
@@ -238,6 +216,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
TunIcmpRouting = _config.TunModeItem.IcmpRouting;
|
||||
TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect;
|
||||
TunRouteExcludeAddress = Utils.List2String(_config.TunModeItem.RouteExcludeAddress, true);
|
||||
TunIPv4Address = _config.TunModeItem.IPv4Address;
|
||||
TunIPv6Address = _config.TunModeItem.IPv6Address;
|
||||
|
||||
#endregion Tun mode
|
||||
|
||||
@@ -310,35 +290,33 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
||||
return;
|
||||
}
|
||||
var fragmentLengths = Utils.String2List(FragmentLengths) ?? [];
|
||||
var fragmentDelays = Utils.String2List(FragmentDelays) ?? [];
|
||||
if (fragmentLengths.Any(item => !Utils.TryParseRange(item, 0, int.MaxValue, out _, out _))
|
||||
|| fragmentDelays.Any(item => !Utils.TryParseRange(item, 0, int.MaxValue, out _, out _))
|
||||
|| (FragmentMaxSplit.IsNotEmpty() && !Utils.TryParseMaxSplit(FragmentMaxSplit, 0, 10000, out _, out _)))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics
|
||||
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|
||||
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort
|
||||
|| EnableHWA != _config.GuiItem.EnableHWA
|
||||
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily
|
||||
|| MainGirdOrientation != (int)_config.UiItem.MainGirdOrientation;
|
||||
|
||||
//if (Utile.IsNullOrEmpty(Kcpmtu.ToString()) || !Utile.IsNumeric(Kcpmtu.ToString())
|
||||
// || Utile.IsNullOrEmpty(Kcptti.ToString()) || !Utile.IsNumeric(Kcptti.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpuplinkCapacity.ToString()) || !Utile.IsNumeric(KcpuplinkCapacity.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpdownlinkCapacity.ToString()) || !Utile.IsNumeric(KcpdownlinkCapacity.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpreadBufferSize.ToString()) || !Utile.IsNumeric(KcpreadBufferSize.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpwriteBufferSize.ToString()) || !Utile.IsNumeric(KcpwriteBufferSize.ToString()))
|
||||
//{
|
||||
// NoticeHandler.Instance.Enqueue(ResUI.FillKcpParameters);
|
||||
// return;
|
||||
//}
|
||||
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily;
|
||||
|
||||
//Core
|
||||
_config.Inbound.First().LocalPort = LocalPort;
|
||||
_config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled;
|
||||
_config.Inbound.First().UdpEnabled = UdpEnabled;
|
||||
_config.Inbound.First().SniffingEnabled = SniffingEnabled;
|
||||
_config.Inbound.First().DestOverride = DestOverride?.ToList();
|
||||
_config.Inbound.First().RouteOnly = RouteOnly;
|
||||
_config.Inbound.First().AllowLANConn = AllowLANConn;
|
||||
_config.Inbound.First().NewPort4LAN = NewPort4LAN;
|
||||
_config.Inbound.First().User = User;
|
||||
_config.Inbound.First().Pass = Pass;
|
||||
var inbound = _config.Inbound.First();
|
||||
inbound.LocalPort = LocalPort;
|
||||
inbound.SecondLocalPortEnabled = SecondLocalPortEnabled;
|
||||
inbound.UdpEnabled = UdpEnabled;
|
||||
inbound.SniffingEnabled = SniffingEnabled;
|
||||
inbound.DestOverride = DestOverride?.ToList();
|
||||
inbound.RouteOnly = RouteOnly;
|
||||
inbound.AllowLANConn = AllowLANConn;
|
||||
inbound.NewPort4LAN = NewPort4LAN;
|
||||
inbound.User = User;
|
||||
inbound.Pass = Pass;
|
||||
if (_config.Inbound.Count > 1)
|
||||
{
|
||||
_config.Inbound.RemoveAt(1);
|
||||
@@ -353,30 +331,12 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.CoreBasicItem.EnableCacheFile4Sbox = EnableCacheFile4Sbox;
|
||||
_config.HysteriaItem.UpMbps = HyUpMbps ?? 0;
|
||||
_config.HysteriaItem.DownMbps = HyDownMbps ?? 0;
|
||||
if (EnableFragment)
|
||||
{
|
||||
if (!Utils.TryParseRange(FragmentLength, 0, int.MaxValue, out _, out _))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
if (!Utils.TryParseRange(FragmentInterval, 1, 100, out _, out _))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
if (FragmentMaxSplit.IsNotEmpty()
|
||||
&& !Utils.TryParseMaxSplit(FragmentMaxSplit, 0, 10000, out _, out _))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_config.CoreBasicItem.EnableFragment = EnableFragment;
|
||||
_config.CoreBasicItem.EnableFinalFragment = EnableFinalFragment;
|
||||
_config.Fragment4RayItem ??= new();
|
||||
_config.Fragment4RayItem.Packets = FragmentPackets;
|
||||
_config.Fragment4RayItem.Length = FragmentLength;
|
||||
_config.Fragment4RayItem.Interval = FragmentInterval;
|
||||
_config.Fragment4RayItem.Lengths = fragmentLengths;
|
||||
_config.Fragment4RayItem.Delays = fragmentDelays;
|
||||
_config.Fragment4RayItem.MaxSplit = FragmentMaxSplit;
|
||||
|
||||
_config.GuiItem.AutoRun = AutoRun;
|
||||
@@ -422,6 +382,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.TunModeItem.IcmpRouting = TunIcmpRouting;
|
||||
_config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect;
|
||||
_config.TunModeItem.RouteExcludeAddress = Utils.String2List(TunRouteExcludeAddress);
|
||||
_config.TunModeItem.IPv4Address = TunIPv4Address;
|
||||
_config.TunModeItem.IPv6Address = TunIPv6Address;
|
||||
|
||||
//coreType
|
||||
await SaveCoreType();
|
||||
@@ -432,7 +394,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
AppManager.Instance.Reset();
|
||||
|
||||
NoticeManager.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user