mirror of
https://github.com/2dust/v2rayN.git
synced 2026-08-15 19:52:06 +03:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0ad2c46e6 | ||
|
|
230a2f6773 | ||
|
|
e7cc4c832e | ||
|
|
fb7033aab5 | ||
|
|
b6cc3913ac | ||
|
|
bc06a5dc21 | ||
|
|
9c1951178c | ||
|
|
b3df42b9ba | ||
|
|
e01717d832 | ||
|
|
8b4063b44b | ||
|
|
3136f573dd | ||
|
|
e101b1d7b0 | ||
|
|
31044f449d | ||
|
|
eacf8b0784 | ||
|
|
f347fe1c03 | ||
|
|
71208eb711 | ||
|
|
339eaa6bfd | ||
|
|
717ef7f0a7 | ||
|
|
4a9d83f9dc | ||
|
|
1ac14d0903 | ||
|
|
08d423e1b4 | ||
|
|
9638b4c368 | ||
|
|
1034ce6e02 | ||
|
|
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 |
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;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -27,68 +73,23 @@ Console.WriteLine("\nAll done!");
|
|||||||
|
|
||||||
async Task ProcessMozillaAsync(string outputDir)
|
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");
|
var outputFile = Path.Combine(outputDir, "mozilla_roots_pem");
|
||||||
|
|
||||||
Console.WriteLine("Downloading Mozilla certdata.txt...");
|
Console.WriteLine("Downloading Mozilla certdata.txt...");
|
||||||
var content = await client.GetStringAsync(certdataUrl);
|
var content = await client.GetStringAsync(certdataUrl);
|
||||||
|
|
||||||
Console.WriteLine("Parsing MULTILINE_OCTAL to PEM...");
|
var pemMatches = Regex.Matches(content, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
|
||||||
var pems = ParseMozillaCertData(content);
|
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));
|
await File.WriteAllTextAsync(outputFile, string.Join("\n\n", pems));
|
||||||
Console.WriteLine($"Mozilla roots saved to: {outputFile} ({pems.Count} certificates)");
|
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)
|
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";
|
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 decoded = Convert.FromBase64String(base64Content.Replace("\n", ""));
|
||||||
|
|
||||||
var text = Encoding.UTF8.GetString(decoded);
|
var text = Encoding.UTF8.GetString(decoded);
|
||||||
|
|
||||||
var pemMatches = Regex.Matches(text, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
|
var pemMatches = Regex.Matches(text, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
|
||||||
var pems = new List<string>();
|
var pems = new List<string>();
|
||||||
foreach (Match m in pemMatches)
|
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:
|
deb-loong64:
|
||||||
name: build and release 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/'))
|
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:
|
env:
|
||||||
RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}
|
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
|
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
|
- name: Download QEMU prebuild
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
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"
|
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-x64-v3.tar.gz
|
tar -xzf qemu-linux-arm64-v8.2.tar.gz
|
||||||
mkdir -p "$HOME/qemu-install"
|
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
|
"$HOME/qemu-install/bin/qemu-system-loongarch64" --version
|
||||||
- name: Download loong64 qcow2 and EFI firmware
|
- name: Download loong64 qcow2 and EFI firmware
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -355,7 +355,7 @@ jobs:
|
|||||||
rpm-loong64:
|
rpm-loong64:
|
||||||
name: build and release 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/'))
|
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:
|
env:
|
||||||
RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}
|
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
|
QCOW2_URL: https://github.com/xujiegb/fedora-loong64-qcow2/releases/download/43/fedora43-loong64.qcow2
|
||||||
@@ -384,10 +384,10 @@ jobs:
|
|||||||
- name: Download QEMU prebuild
|
- name: Download QEMU prebuild
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
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"
|
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-x64-v3.tar.gz
|
tar -xzf qemu-linux-arm64-v8.2.tar.gz
|
||||||
mkdir -p "$HOME/qemu-install"
|
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
|
"$HOME/qemu-install/bin/qemu-system-loongarch64" --version
|
||||||
- name: Download loong64 qcow2 and EFI firmware
|
- name: Download loong64 qcow2 and EFI firmware
|
||||||
shell: bash
|
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
|
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||||
|
|
||||||
- name: Setup .NET 10.0.1xx
|
- name: Setup .NET 10.0.1xx
|
||||||
uses: actions/setup-dotnet@v5.4.0
|
uses: actions/setup-dotnet@v6.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 10.0.1xx
|
dotnet-version: 10.0.1xx
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
$xrayTag = "v$xrayVer"
|
$xrayTag = "v$xrayVer"
|
||||||
$singTag = "v$singVer"
|
$singTag = "v$singVer"
|
||||||
|
|
||||||
$xrayUrl = "https://github.com/autorepobot/Xray-core/releases/download/$xrayTag/Xray-windows-32.zip"
|
$xrayUrl = "https://github.com/autorepobot/Xray/releases/download/$xrayTag/Xray-windows-32.zip"
|
||||||
$singUrl = "https://github.com/SagerNet/sing-box/releases/download/$singTag/sing-box-$singVer-windows-386.zip"
|
$singUrl = "https://github.com/SagerNet/sing-box/releases/download/$singTag/sing-box-$singVer-windows-386.zip"
|
||||||
|
|
||||||
Write-Host "Bundled Xray version: $xrayVer"
|
Write-Host "Bundled Xray version: $xrayVer"
|
||||||
|
|||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
|||||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v5.4.0
|
uses: actions/setup-dotnet@v6.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.1xx'
|
dotnet-version: '10.0.1xx'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v5.4.0
|
uses: actions/setup-dotnet@v6.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ Read the Wiki for usage guides and configuration details.
|
|||||||
| Linux | ✅ | - | ✅ | ✅ | ✅ |
|
| Linux | ✅ | - | ✅ | ✅ | ✅ |
|
||||||
| macOS | ✅ | - | ✅ | - | - |
|
| macOS | ✅ | - | ✅ | - | - |
|
||||||
|
|
||||||
|
Minimum OS requirements: [Release files introduction](https://github.com/2dust/v2rayN/wiki/Release-files-introduction) / 最低系统要求:[发布文件介绍](https://github.com/2dust/v2rayN/wiki/Release-files-introduction)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## GPG Verification / GPG 签名校验
|
## GPG Verification / GPG 签名校验
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ PKGROOT="v2rayN-publish"
|
|||||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
OUTPUT_DIR="${HOME}/debbuild"
|
OUTPUT_DIR="${HOME}/debbuild"
|
||||||
DOTNET_TFM="net10.0"
|
DOTNET_TFM="net10.0"
|
||||||
DOTNET_LOONGARCH_VERSION="10.0.109"
|
DOTNET_LOONGARCH_VERSION="10.0.110"
|
||||||
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
|
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
||||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
||||||
@@ -597,8 +597,8 @@ package_binary() {
|
|||||||
write_desktop_file "$stage"
|
write_desktop_file "$stage"
|
||||||
write_maintainer_scripts "$debian_dir"
|
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"
|
mkdir -p "$workdir/debian"
|
||||||
cat > "$workdir/debian/control" <<EOF
|
cat > "$workdir/debian/control" <<EOF
|
||||||
Source: v2rayn
|
Source: v2rayn
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
|||||||
PKGROOT="v2rayN-publish"
|
PKGROOT="v2rayN-publish"
|
||||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
OUTPUT_DIR="${HOME}/debbuild"
|
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_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
||||||
@@ -595,8 +595,8 @@ package_binary() {
|
|||||||
write_desktop_file "$stage"
|
write_desktop_file "$stage"
|
||||||
write_maintainer_scripts "$debian_dir"
|
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"
|
mkdir -p "$workdir/debian"
|
||||||
cat > "$workdir/debian/control" <<EOF
|
cat > "$workdir/debian/control" <<EOF
|
||||||
Source: v2rayn
|
Source: v2rayn
|
||||||
|
|||||||
@@ -609,7 +609,7 @@ package_binary() {
|
|||||||
write_desktop_file "$stage"
|
write_desktop_file "$stage"
|
||||||
write_maintainer_scripts "$debian_dir"
|
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"
|
mkdir -p "$workdir/debian"
|
||||||
cat > "$workdir/debian/control" <<EOF
|
cat > "$workdir/debian/control" <<EOF
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
|
|||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>13.7</string>
|
<string>13.6</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ MIN_KERNEL="6.12"
|
|||||||
PKGROOT="v2rayN-publish"
|
PKGROOT="v2rayN-publish"
|
||||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
RPM_TOPDIR="${HOME}/rpmbuild"
|
||||||
DOTNET_LOONGARCH_VERSION="10.0.109"
|
DOTNET_LOONGARCH_VERSION="10.0.110"
|
||||||
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
|
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
||||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
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: glibc >= 2.39
|
||||||
Requires: fontconfig >= 2.15.0
|
Requires: fontconfig >= 2.15.0
|
||||||
Requires: desktop-file-utils >= 0.26
|
Requires: desktop-file-utils >= 0.26
|
||||||
Requires: xdg-utils >= 1.2.0
|
Requires: xdg-utils >= 1.1.3
|
||||||
Requires: coreutils >= 9.5
|
Requires: coreutils >= 9.4
|
||||||
Requires: bash >= 5.2.26
|
Requires: bash >= 5.2.21
|
||||||
Requires: freetype >= 2.13
|
Requires: freetype >= 2.13
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
|||||||
PKGROOT="v2rayN-publish"
|
PKGROOT="v2rayN-publish"
|
||||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
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_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
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: glibc >= 2.39
|
||||||
Requires: fontconfig >= 2.15.0
|
Requires: fontconfig >= 2.15.0
|
||||||
Requires: desktop-file-utils >= 0.26
|
Requires: desktop-file-utils >= 0.26
|
||||||
Requires: xdg-utils >= 1.2.0
|
Requires: xdg-utils >= 1.1.3
|
||||||
Requires: coreutils >= 9.5
|
Requires: coreutils >= 9.4
|
||||||
Requires: bash >= 5.2.26
|
Requires: bash >= 5.2.21
|
||||||
Requires: freetype >= 2.13
|
Requires: freetype >= 2.13
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
@@ -501,9 +501,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
|||||||
Requires: glibc >= 2.39
|
Requires: glibc >= 2.39
|
||||||
Requires: fontconfig >= 2.15.0
|
Requires: fontconfig >= 2.15.0
|
||||||
Requires: desktop-file-utils >= 0.26
|
Requires: desktop-file-utils >= 0.26
|
||||||
Requires: xdg-utils >= 1.2.0
|
Requires: xdg-utils >= 1.1.3
|
||||||
Requires: coreutils >= 9.5
|
Requires: coreutils >= 9.4
|
||||||
Requires: bash >= 5.2.26
|
Requires: bash >= 5.2.21
|
||||||
Requires: freetype >= 2.13
|
Requires: freetype >= 2.13
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.24.0</Version>
|
<Version>7.24.7</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -6,28 +6,28 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.0" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.2" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="12.1.0" />
|
<PackageVersion Include="Avalonia.Desktop" Version="12.1.1" />
|
||||||
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
||||||
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
<PackageVersion Include="AwesomeAssertions" Version="9.5.0" />
|
||||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.2" />
|
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.3" />
|
||||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.0.3" />
|
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.1" />
|
||||||
<PackageVersion Include="CliWrap" Version="3.10.2" />
|
<PackageVersion Include="CliWrap" Version="3.10.4" />
|
||||||
<PackageVersion Include="Downloader" Version="5.9.4" />
|
<PackageVersion Include="Downloader" Version="5.9.5" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
|
||||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
<PackageVersion Include="ReactiveUI" Version="24.1.0" />
|
||||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="3.2.0" />
|
||||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
<PackageVersion Include="ReactiveUI.WPF" Version="24.1.0" />
|
||||||
<PackageVersion Include="Semi.Avalonia" Version="12.1.0" />
|
<PackageVersion Include="Semi.Avalonia" Version="12.1.0.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="12.0.0" />
|
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0" />
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0.1" />
|
||||||
<PackageVersion Include="NLog" Version="6.1.4" />
|
<PackageVersion Include="NLog" Version="6.1.4" />
|
||||||
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
|
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
|
||||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3" />
|
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.4.1" />
|
||||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ internal static class CoreConfigTestFactory
|
|||||||
},
|
},
|
||||||
WebDavItem = new WebDavItem(),
|
WebDavItem = new WebDavItem(),
|
||||||
CheckUpdateItem = new CheckUpdateItem(),
|
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 =
|
Inbound =
|
||||||
[
|
[
|
||||||
new InItem
|
new InItem
|
||||||
@@ -84,6 +84,7 @@ internal static class CoreConfigTestFactory
|
|||||||
ParallelQuery = false,
|
ParallelQuery = false,
|
||||||
Strategy4Freedom = Global.AsIs,
|
Strategy4Freedom = Global.AsIs,
|
||||||
Strategy4Proxy = Global.AsIs,
|
Strategy4Proxy = Global.AsIs,
|
||||||
|
Strategy4ProxyDial = Global.AsIs,
|
||||||
},
|
},
|
||||||
IndexId = string.Empty,
|
IndexId = string.Empty,
|
||||||
SubIndexId = 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,
|
public static ProfileItem CreatePolicyGroupNode(ECoreType coreType, string indexId, string remarks,
|
||||||
IEnumerable<string> childIndexIds)
|
IEnumerable<string> childIndexIds)
|
||||||
{
|
{
|
||||||
@@ -233,4 +251,12 @@ internal static class CoreConfigTestFactory
|
|||||||
config.TunModeItem.RouteExcludeAddress = ["10.0.0.1/32", "192.168.1.0/24", "fc00::/7"];
|
config.TunModeItem.RouteExcludeAddress = ["10.0.0.1/32", "192.168.1.0/24", "fc00::/7"];
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Config CreateConfigWithTun(ECoreType coreType, bool enableIPv6Address)
|
||||||
|
{
|
||||||
|
var config = CreateConfig(coreType);
|
||||||
|
config.TunModeItem.EnableTun = true;
|
||||||
|
config.TunModeItem.EnableIPv6Address = enableIPv6Address;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,85 @@ public class CoreConfigSingboxServiceTests
|
|||||||
cfg.inbounds.Should().Contain(i => i.type == "tun");
|
cfg.inbounds.Should().Contain(i => i.type == "tun");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_TunEnabled_ShouldKeepEmbeddedTunRules()
|
||||||
|
{
|
||||||
|
// The embedded tun rules reject local-network noise (NetBIOS/mDNS, multicast).
|
||||||
|
// They are deserialized into List<Rule4Sbox>, so a schema mismatch in the
|
||||||
|
// embedded template makes JsonUtils.Deserialize return null and silently
|
||||||
|
// drops every one of them.
|
||||||
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
|
config.TunModeItem.EnableTun = 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())!;
|
||||||
|
|
||||||
|
cfg.route.rules.Should().Contain(
|
||||||
|
r => r.action == "reject"
|
||||||
|
&& r.network != null && r.network.Contains("udp")
|
||||||
|
&& r.port != null && r.port.Contains(5353),
|
||||||
|
"the embedded tun rules must reject mDNS/NetBIOS noise");
|
||||||
|
cfg.route.rules.Should().Contain(
|
||||||
|
r => r.action == "reject"
|
||||||
|
&& r.ip_cidr != null && r.ip_cidr.Contains("224.0.0.0/3"),
|
||||||
|
"the embedded tun rules must reject multicast traffic");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_TunEnabled_ShouldRejectTrafficToTunOwnAddresses()
|
||||||
|
{
|
||||||
|
// Regression test: 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 routes it to the
|
||||||
|
// outbound again - an infinite loop that pins a CPU core. Observed in the wild
|
||||||
|
// with WebRTC ICE connectivity checks against the TUN's own fc00::/7 ULA
|
||||||
|
// address, sustaining ~8k packets/s out of the TUN interface.
|
||||||
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
|
config.TunModeItem.EnableTun = true;
|
||||||
|
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 self = IPAddress.Parse(address.Split('/').First());
|
||||||
|
var hostBits = self.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32;
|
||||||
|
var expected = $"{self}/{hostBits}";
|
||||||
|
cfg.route.rules.Should().Contain(
|
||||||
|
r => r.action == "reject" && r.ip_cidr != null && r.ip_cidr.Contains(expected),
|
||||||
|
$"traffic to the TUN's own address '{address}' must be rejected, not routed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The match has to stay on the addresses themselves. sing-tun derives the TUN's DNS
|
||||||
|
// entry from the address right after the interface's own, and every prefix offered
|
||||||
|
// here leaves room for it, so a prefix match would drop system name lookups too.
|
||||||
|
var dropRule = cfg.route.rules.First(r =>
|
||||||
|
r.action == "reject" && r.method == "drop" && r.ip_cidr?.Count > 0);
|
||||||
|
dropRule.ip_cidr!.Should().OnlyContain(c =>
|
||||||
|
c.EndsWith("/32", StringComparison.Ordinal) || c.EndsWith("/128", StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GenerateClientConfigContent_BindInterface_ShouldUseDialBindInterface()
|
public void GenerateClientConfigContent_BindInterface_ShouldUseDialBindInterface()
|
||||||
{
|
{
|
||||||
@@ -592,4 +671,74 @@ public class CoreConfigSingboxServiceTests
|
|||||||
proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478");
|
proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478");
|
||||||
proxy.server.Should().BeNull();
|
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,52 @@ public class CoreConfigV2rayServiceTests
|
|||||||
|
|
||||||
var directOutbound = cfg.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag && o.protocol == "freedom");
|
var directOutbound = cfg.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag && o.protocol == "freedom");
|
||||||
directOutbound.Should().NotBeNull();
|
directOutbound.Should().NotBeNull();
|
||||||
directOutbound!.settings.domainStrategy.Should().Be("UseIPv4");
|
directOutbound!.streamSettings.sockopt!.domainStrategy.Should().Be("UseIPv4");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void GenerateClientConfigContent_Tun_ShouldRouteIPv6IntoTunnel(bool enableIPv6Address)
|
||||||
|
{
|
||||||
|
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address);
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
|
||||||
|
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
||||||
|
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray);
|
||||||
|
|
||||||
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue();
|
||||||
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
|
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
||||||
|
|
||||||
|
tunInbound.Should().NotBeNull();
|
||||||
|
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("0.0.0.0/0");
|
||||||
|
tunInbound.settings.autoSystemRoutingTable.Should().Contain("::/0");
|
||||||
|
|
||||||
|
// EnableIPv6Address governs the interface address only, never the routing table.
|
||||||
|
tunInbound.settings.gateway.Should().HaveCount(enableIPv6Address ? 2 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_TunRouteExcludeAddress_ShouldIncludeIPv6Ranges()
|
||||||
|
{
|
||||||
|
var config = CoreConfigTestFactory.CreateConfigWithTunRouteExcludeAddress(ECoreType.Xray);
|
||||||
|
config.TunModeItem.EnableIPv6Address = false;
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
|
||||||
|
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
||||||
|
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray);
|
||||||
|
|
||||||
|
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue();
|
||||||
|
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||||
|
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
||||||
|
|
||||||
|
tunInbound.Should().NotBeNull();
|
||||||
|
tunInbound!.settings.autoSystemRoutingTable.Should().Contain(x => x.Contains(':'));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -592,4 +637,43 @@ public class CoreConfigV2rayServiceTests
|
|||||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.0/32");
|
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.0/32");
|
||||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.2/31");
|
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;
|
||||||
global using System.Net.NetworkInformation;
|
global using System.Net.NetworkInformation;
|
||||||
global using System.Net.Sockets;
|
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.Reflection;
|
||||||
global using System.Runtime.InteropServices;
|
global using System.Runtime.InteropServices;
|
||||||
global using System.Security.Cryptography;
|
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.Nodes;
|
||||||
global using System.Text.Json.Serialization;
|
global using System.Text.Json.Serialization;
|
||||||
global using System.Text.RegularExpressions;
|
global using System.Text.RegularExpressions;
|
||||||
global using DynamicData;
|
|
||||||
global using DynamicData.Binding;
|
|
||||||
global using ReactiveUI;
|
global using ReactiveUI;
|
||||||
global using ReactiveUI.Fody.Helpers;
|
|
||||||
global using ServiceLib.Base;
|
global using ServiceLib.Base;
|
||||||
global using ServiceLib.Common;
|
global using ServiceLib.Common;
|
||||||
global using ServiceLib.Enums;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -92,7 +92,7 @@ public static class Extension
|
|||||||
|
|
||||||
public static bool IsComplexType(this EConfigType configType)
|
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>
|
/// <summary>
|
||||||
@@ -134,4 +134,25 @@ public static class Extension
|
|||||||
.Replace("\r", replacement)
|
.Replace("\r", replacement)
|
||||||
.Replace("\n", replacement);
|
.Replace("\n", replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<TOutput> HandleSafe<TInput, TOutput>(
|
||||||
|
this Interaction<TInput, TOutput> interaction,
|
||||||
|
TInput input,
|
||||||
|
TOutput defaultValue = default!)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await interaction.Handle(input);
|
||||||
|
}
|
||||||
|
catch (UnhandledInteractionException<TInput, TOutput> ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"Unhandled interaction exception for input: {input}", ex);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"Exception occurred while handling interaction for input: {input}", ex);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -485,12 +485,17 @@ public class Utils
|
|||||||
|
|
||||||
public static string? DomainStrategy4Sbox(string? strategy)
|
public static string? DomainStrategy4Sbox(string? strategy)
|
||||||
{
|
{
|
||||||
|
if (strategy is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return strategy switch
|
return strategy switch
|
||||||
{
|
{
|
||||||
not null when strategy.StartsWith("UseIPv4") => "prefer_ipv4",
|
_ when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
|
||||||
not null when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
|
_ when strategy.StartsWith("UseIP") => "prefer_ipv4",
|
||||||
not null when strategy.StartsWith("ForceIPv4") => "ipv4_only",
|
_ when strategy.StartsWith("ForceIPv6") => "ipv6_only",
|
||||||
not null when strategy.StartsWith("ForceIPv6") => "ipv6_only",
|
_ when strategy.StartsWith("ForceIP") => "ipv4_only",
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -748,11 +753,11 @@ public class Utils
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetFreePort(int defaultPort = 0)
|
public static int GetFreePort(int defaultPort)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!(defaultPort == 0 || Utils.PortInUse(defaultPort)))
|
if (!PortInUse(defaultPort))
|
||||||
{
|
{
|
||||||
return defaultPort;
|
return defaultPort;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public enum EConfigType
|
|||||||
HTTP = 10,
|
HTTP = 10,
|
||||||
Anytls = 11,
|
Anytls = 11,
|
||||||
Naive = 12,
|
Naive = 12,
|
||||||
|
Outbound = 13,
|
||||||
PolicyGroup = 101,
|
PolicyGroup = 101,
|
||||||
ProxyChain = 102,
|
ProxyChain = 102,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ namespace ServiceLib.Events;
|
|||||||
|
|
||||||
public static class AppEvents
|
public static class AppEvents
|
||||||
{
|
{
|
||||||
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
|
public static readonly EventChannel<RxVoid> AddServerViaClipboardRequested = new();
|
||||||
public static readonly EventChannel<bool> HasUpdateNotified = new();
|
public static readonly EventChannel<bool> HasUpdateNotified = new();
|
||||||
|
|
||||||
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
||||||
@@ -10,7 +10,7 @@ public static class AppEvents
|
|||||||
public static readonly EventChannel<string> SendSnackMsgRequested = new();
|
public static readonly EventChannel<string> SendSnackMsgRequested = new();
|
||||||
public static readonly EventChannel<string> SendMsgViewRequested = 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<bool> ShutdownRequested = new();
|
||||||
|
|
||||||
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
|
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
|
||||||
|
|||||||
@@ -1,27 +1,37 @@
|
|||||||
using System.Reactive.Subjects;
|
|
||||||
|
|
||||||
namespace ServiceLib.Events;
|
namespace ServiceLib.Events;
|
||||||
|
|
||||||
public sealed class EventChannel<T>
|
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()
|
public IObservable<T> AsObservable()
|
||||||
{
|
{
|
||||||
return _subject.AsObservable();
|
return _observable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Publish(T value)
|
public void Publish(T value)
|
||||||
{
|
{
|
||||||
_subject.OnNext(value);
|
lock (_gate)
|
||||||
|
{
|
||||||
|
_signal.OnNext(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Publish()
|
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
|
public class Global
|
||||||
{
|
{
|
||||||
#region const
|
|
||||||
|
|
||||||
public const string AppName = "v2rayN";
|
public const string AppName = "v2rayN";
|
||||||
public const string GithubUrl = "https://github.com";
|
public const string GithubUrl = "https://github.com";
|
||||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||||
@@ -106,9 +104,7 @@ public class Global
|
|||||||
public const string SingboxFakeDNSTag = "fake_dns";
|
public const string SingboxFakeDNSTag = "fake_dns";
|
||||||
|
|
||||||
public const int Hysteria2DefaultHopInt = 30;
|
public const int Hysteria2DefaultHopInt = 30;
|
||||||
|
|
||||||
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";
|
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";
|
||||||
|
|
||||||
public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$";
|
public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$";
|
||||||
|
|
||||||
public static readonly List<string> PolicyGroupDefaultFilterList =
|
public static readonly List<string> PolicyGroupDefaultFilterList =
|
||||||
@@ -557,7 +553,6 @@ public class Global
|
|||||||
"http",
|
"http",
|
||||||
"tls",
|
"tls",
|
||||||
"quic",
|
"quic",
|
||||||
"fakedns",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<int> TunMtus =
|
public static readonly List<int> TunMtus =
|
||||||
@@ -737,6 +732,12 @@ public class Global
|
|||||||
"reply",
|
"reply",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> FakeIPRanges =
|
||||||
|
[
|
||||||
|
"198.18.0.0/15",
|
||||||
|
"11.0.0.0/8",
|
||||||
|
];
|
||||||
|
|
||||||
public static readonly List<string> RootCertProviders =
|
public static readonly List<string> RootCertProviders =
|
||||||
[
|
[
|
||||||
"system",
|
"system",
|
||||||
@@ -744,5 +745,29 @@ public class Global
|
|||||||
MozillaRootProvider,
|
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.Concurrent;
|
||||||
|
global using System.Collections.ObjectModel;
|
||||||
|
global using System.Collections.Specialized;
|
||||||
|
global using System.ComponentModel;
|
||||||
global using System.Diagnostics;
|
global using System.Diagnostics;
|
||||||
global using System.Net;
|
global using System.Net;
|
||||||
global using System.Net.NetworkInformation;
|
global using System.Net.NetworkInformation;
|
||||||
global using System.Net.Sockets;
|
global using System.Net.Sockets;
|
||||||
global using System.Reactive;
|
global using ReactiveUI.Primitives;
|
||||||
global using System.Reactive.Disposables;
|
global using ReactiveUI.Primitives.Concurrency;
|
||||||
global using System.Reactive.Linq;
|
global using ReactiveUI.Primitives.Disposables;
|
||||||
|
global using ReactiveUI.Primitives.Signals;
|
||||||
global using System.Reflection;
|
global using System.Reflection;
|
||||||
global using System.Runtime.InteropServices;
|
global using System.Runtime.InteropServices;
|
||||||
global using System.Runtime.Versioning;
|
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.Nodes;
|
||||||
global using System.Text.Json.Serialization;
|
global using System.Text.Json.Serialization;
|
||||||
global using System.Text.RegularExpressions;
|
global using System.Text.RegularExpressions;
|
||||||
global using DynamicData;
|
|
||||||
global using DynamicData.Binding;
|
|
||||||
global using ReactiveUI;
|
global using ReactiveUI;
|
||||||
global using ReactiveUI.Fody.Helpers;
|
global using ReactiveUI.SourceGenerators;
|
||||||
global using ServiceLib.Base;
|
global using ServiceLib.Base;
|
||||||
global using ServiceLib.Common;
|
global using ServiceLib.Common;
|
||||||
global using ServiceLib.Enums;
|
global using ServiceLib.Enums;
|
||||||
@@ -30,8 +32,8 @@ global using ServiceLib.Handler.Fmt;
|
|||||||
global using ServiceLib.Handler.SysProxy;
|
global using ServiceLib.Handler.SysProxy;
|
||||||
global using ServiceLib.Helper;
|
global using ServiceLib.Helper;
|
||||||
global using ServiceLib.Manager;
|
global using ServiceLib.Manager;
|
||||||
global using ServiceLib.Models.CoreConfigs;
|
|
||||||
global using ServiceLib.Models.Configs;
|
global using ServiceLib.Models.Configs;
|
||||||
|
global using ServiceLib.Models.CoreConfigs;
|
||||||
global using ServiceLib.Models.Dto;
|
global using ServiceLib.Models.Dto;
|
||||||
global using ServiceLib.Models.Entities;
|
global using ServiceLib.Models.Entities;
|
||||||
global using ServiceLib.Resx;
|
global using ServiceLib.Resx;
|
||||||
@@ -39,3 +41,5 @@ global using ServiceLib.Services;
|
|||||||
global using ServiceLib.Services.CoreConfig;
|
global using ServiceLib.Services.CoreConfig;
|
||||||
global using ServiceLib.Services.Statistics;
|
global using ServiceLib.Services.Statistics;
|
||||||
global using SQLite;
|
global using SQLite;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace ServiceLib.Handler.Builder;
|
namespace ServiceLib.Handler.Builder;
|
||||||
|
|
||||||
public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult)
|
public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult)
|
||||||
@@ -49,6 +51,7 @@ public class CoreConfigContextBuilder
|
|||||||
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
|
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
|
||||||
IsWindows = Utils.IsWindows(),
|
IsWindows = Utils.IsWindows(),
|
||||||
IsMacOS = Utils.IsMacOS(),
|
IsMacOS = Utils.IsMacOS(),
|
||||||
|
ProtectCoreTypeList = config.TunModeItem.EnableTun ? [ECoreType.Xray, ECoreType.sing_box] : []
|
||||||
};
|
};
|
||||||
var validatorResult = NodeValidatorResult.Empty();
|
var validatorResult = NodeValidatorResult.Empty();
|
||||||
var (actNode, nodeValidatorResult) = await ResolveNodeAsync(context, node);
|
var (actNode, nodeValidatorResult) = await ResolveNodeAsync(context, node);
|
||||||
@@ -94,6 +97,7 @@ public class CoreConfigContextBuilder
|
|||||||
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode;
|
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.IsTunEnabled && context.AppConfig.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
if (context.IsTunEnabled && context.AppConfig.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||||
{
|
{
|
||||||
var appConfig = JsonUtils.DeepCopy(config);
|
var appConfig = JsonUtils.DeepCopy(config);
|
||||||
@@ -321,14 +325,14 @@ public class CoreConfigContextBuilder
|
|||||||
{
|
{
|
||||||
return await RegisterGroupNodeAsync(context, node);
|
return await RegisterGroupNodeAsync(context, node);
|
||||||
}
|
}
|
||||||
return RegisterSingleNodeAsync(context, node);
|
return await RegisterSingleNodeAsync(context, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates a single (non-group) node and, on success, adds it to the proxy map
|
/// 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.
|
/// and records any domain addresses that should bypass the proxy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static NodeValidatorResult RegisterSingleNodeAsync(CoreConfigContext context, ProfileItem node)
|
private static async Task<NodeValidatorResult> RegisterSingleNodeAsync(CoreConfigContext context, ProfileItem node)
|
||||||
{
|
{
|
||||||
if (node.ConfigType.IsGroupType())
|
if (node.ConfigType.IsGroupType())
|
||||||
{
|
{
|
||||||
@@ -336,6 +340,29 @@ public class CoreConfigContextBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
var nodeValidatorResult = NodeValidator.Validate(node, context.RunCoreType);
|
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]);
|
var msgs = new List<string>([.. nodeValidatorResult.Errors, .. nodeValidatorResult.Warnings]);
|
||||||
if (msgs.Count > 0)
|
if (msgs.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -437,7 +464,7 @@ public class CoreConfigContextBuilder
|
|||||||
|
|
||||||
if (!childNode.ConfigType.IsGroupType())
|
if (!childNode.ConfigType.IsGroupType())
|
||||||
{
|
{
|
||||||
var childNodeResult = RegisterSingleNodeAsync(context, childNode);
|
var childNodeResult = await RegisterSingleNodeAsync(context, childNode);
|
||||||
childNodeValidatorResult.Warnings.AddRange(childNodeResult.Warnings.Select(w =>
|
childNodeValidatorResult.Warnings.AddRange(childNodeResult.Warnings.Select(w =>
|
||||||
string.Format(ResUI.MsgGroupChildNodeWarning, node.Remarks, childNode.Remarks, w)));
|
string.Format(ResUI.MsgGroupChildNodeWarning, node.Remarks, childNode.Remarks, w)));
|
||||||
childNodeValidatorResult.Errors.AddRange(childNodeResult.Errors.Select(e =>
|
childNodeValidatorResult.Errors.AddRange(childNodeResult.Errors.Select(e =>
|
||||||
|
|||||||
@@ -36,6 +36,15 @@ public class NodeValidator
|
|||||||
return;
|
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())
|
if (item.ConfigType.IsGroupType())
|
||||||
{
|
{
|
||||||
// Group logic is handled in ValidateGroupNode
|
// Group logic is handled in ValidateGroupNode
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public static class ConfigHandler
|
|||||||
EnableTun = false,
|
EnableTun = false,
|
||||||
Mtu = 9000,
|
Mtu = 9000,
|
||||||
IcmpRouting = Global.TunIcmpRoutingPolicies.First(),
|
IcmpRouting = Global.TunIcmpRoutingPolicies.First(),
|
||||||
EnableLegacyProtect = false,
|
EnableLegacyProtect = true,
|
||||||
};
|
};
|
||||||
config.GuiItem ??= new();
|
config.GuiItem ??= new();
|
||||||
if (!Global.RootCertProviders.Contains(config.GuiItem.RootCertProvider))
|
if (!Global.RootCertProviders.Contains(config.GuiItem.RootCertProvider))
|
||||||
@@ -115,10 +115,12 @@ public static class ConfigHandler
|
|||||||
config.ConstItem ??= new ConstItem();
|
config.ConstItem ??= new ConstItem();
|
||||||
|
|
||||||
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
||||||
|
config.SimpleDNSItem.FakeIPRange ??= Global.FakeIPRanges.FirstOrDefault();
|
||||||
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
||||||
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
||||||
config.SimpleDNSItem.ServeStale ??= false;
|
config.SimpleDNSItem.ServeStale ??= false;
|
||||||
config.SimpleDNSItem.ParallelQuery ??= false;
|
config.SimpleDNSItem.ParallelQuery ??= false;
|
||||||
|
config.SimpleDNSItem.EnableHappyEyeballs ??= false;
|
||||||
|
|
||||||
config.SpeedTestItem ??= new();
|
config.SpeedTestItem ??= new();
|
||||||
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
||||||
@@ -168,10 +170,24 @@ public static class ConfigHandler
|
|||||||
config.Fragment4RayItem ??= new()
|
config.Fragment4RayItem ??= new()
|
||||||
{
|
{
|
||||||
Packets = "tlshello",
|
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 ??= [];
|
config.GlobalHotkeys ??= [];
|
||||||
|
|
||||||
if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty())
|
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
|
else
|
||||||
{
|
{
|
||||||
await AddServerCommon(config, profileItem, true);
|
await AddServerCommon(config, profileItem, true);
|
||||||
@@ -563,6 +586,43 @@ public static class ConfigHandler
|
|||||||
return 0;
|
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>
|
/// <summary>
|
||||||
/// Edit an existing custom server configuration
|
/// Edit an existing custom server configuration
|
||||||
/// Updates the server's properties without changing the file
|
/// Updates the server's properties without changing the file
|
||||||
@@ -584,6 +644,8 @@ public static class ConfigHandler
|
|||||||
item.CoreType = profileItem.CoreType;
|
item.CoreType = profileItem.CoreType;
|
||||||
item.DisplayLog = profileItem.DisplayLog;
|
item.DisplayLog = profileItem.DisplayLog;
|
||||||
item.PreSocksPort = profileItem.PreSocksPort;
|
item.PreSocksPort = profileItem.PreSocksPort;
|
||||||
|
|
||||||
|
item.ProtoExtra = profileItem.ProtoExtra;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await SQLiteHelper.Instance.UpdateAsync(item) > 0)
|
if (await SQLiteHelper.Instance.UpdateAsync(item) > 0)
|
||||||
@@ -1463,7 +1525,7 @@ public static class ConfigHandler
|
|||||||
var matchedChildProfiles = childProfiles?.Where(p =>
|
var matchedChildProfiles = childProfiles?.Where(p =>
|
||||||
p != null &&
|
p != null &&
|
||||||
p.IsValid() &&
|
p.IsValid() &&
|
||||||
!p.ConfigType.IsComplexType() &&
|
(!p.ConfigType.IsComplexType() || p.ConfigType == EConfigType.Outbound) &&
|
||||||
(extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter))
|
(extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter))
|
||||||
)
|
)
|
||||||
.ToList() ?? [];
|
.ToList() ?? [];
|
||||||
@@ -1659,68 +1721,198 @@ public static class ConfigHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
var subItem = await AppManager.Instance.GetSubItem(subid);
|
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 subRemarks = subItem?.Remarks;
|
||||||
var preSocksPort = subItem?.PreSocksPort;
|
// Prioritize using complete custom parsing, followed by custom outbound parsing.
|
||||||
|
var lstProfiles = V2rayFmt.ResolveToCustom(strData, subRemarks);
|
||||||
List<ProfileItem>? lstProfiles = null;
|
if (lstProfiles.Count == 0)
|
||||||
//Is sing-box array configuration
|
|
||||||
if (lstProfiles is null || lstProfiles.Count <= 0)
|
|
||||||
{
|
{
|
||||||
lstProfiles = SingboxFmt.ResolveFullArray(strData, subRemarks);
|
lstProfiles = SingboxFmt.ResolveToCustom(strData, subRemarks);
|
||||||
}
|
}
|
||||||
//Is v2ray array configuration
|
if (lstProfiles.Count == 0)
|
||||||
if (lstProfiles is null || lstProfiles.Count <= 0)
|
|
||||||
{
|
{
|
||||||
lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks);
|
lstProfiles = V2rayFmt.ResolveToCustomOutbound(strData, subRemarks);
|
||||||
}
|
}
|
||||||
if (lstProfiles is { Count: > 0 })
|
if (lstProfiles.Count == 0)
|
||||||
{
|
{
|
||||||
var count = 0;
|
lstProfiles = SingboxFmt.ResolveToCustomOutbound(strData, subRemarks);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (lstProfiles.Count == 0)
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
//Is Clash configuration
|
|
||||||
profileItem ??= ClashFmt.ResolveFull(strData, subRemarks);
|
var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub);
|
||||||
//Is hysteria configuration
|
if (count > 0)
|
||||||
profileItem ??= Hysteria2Fmt.ResolveFull2(strData, subRemarks);
|
{
|
||||||
if (profileItem is null || profileItem.Address.IsNullOrEmpty())
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HtmlPageFmt.IsHtmlPage(strData))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var profileItem = ClashFmt.ResolveFull(strData, subRemarks)
|
||||||
|
?? Hysteria2Fmt.ResolveFull2(strData, subRemarks);
|
||||||
|
|
||||||
|
if (profileItem == null)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
profileItem.Subid = subid;
|
profileItem.Subid = subid;
|
||||||
profileItem.IsSub = isSub;
|
profileItem.IsSub = isSub;
|
||||||
profileItem.PreSocksPort = preSocksPort;
|
profileItem.PreSocksPort = subItem?.PreSocksPort;
|
||||||
if (await AddCustomServer(config, profileItem, true) == 0)
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
var 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 AddBatchCustomServers(config, lstProfiles, subid, isSub);
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
return await SaveCustomRawFileServer(config, strData, subid, isSub, subItem, customCoreType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<int> AddBatchCustomServers(
|
||||||
|
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 (it.ConfigType == EConfigType.Custom)
|
||||||
|
{
|
||||||
|
if (await AddCustomServer(config, it, true) == 0)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1821,6 +2013,7 @@ public static class ConfigHandler
|
|||||||
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
|
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
|
||||||
EConfigType.Naive => await AddNaiveServer(config, profileItem, false),
|
EConfigType.Naive => await AddNaiveServer(config, profileItem, false),
|
||||||
EConfigType.PolicyGroup or EConfigType.ProxyChain => await AddServerCommon(config, profileItem, false),
|
EConfigType.PolicyGroup or EConfigType.ProxyChain => await AddServerCommon(config, profileItem, false),
|
||||||
|
EConfigType.Outbound => await AddCustomOutboundServer(config, profileItem, true, false),
|
||||||
_ => -1,
|
_ => -1,
|
||||||
};
|
};
|
||||||
if (addStatus == 0)
|
if (addStatus == 0)
|
||||||
@@ -2022,6 +2215,7 @@ public static class ConfigHandler
|
|||||||
item.NextProfile = subItem.NextProfile;
|
item.NextProfile = subItem.NextProfile;
|
||||||
item.PreSocksPort = subItem.PreSocksPort;
|
item.PreSocksPort = subItem.PreSocksPort;
|
||||||
item.Memo = subItem.Memo;
|
item.Memo = subItem.Memo;
|
||||||
|
item.CustomCoreType = subItem.CustomCoreType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Id.IsNullOrEmpty())
|
if (item.Id.IsNullOrEmpty())
|
||||||
@@ -2062,7 +2256,7 @@ public static class ConfigHandler
|
|||||||
{
|
{
|
||||||
return -1;
|
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)
|
if (isSub)
|
||||||
{
|
{
|
||||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where isSub = 1 and subid = '{subid}'");
|
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
|
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)
|
_ => await GenerateClientCustomConfig(node, fileName)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ public class AnytlsFmt : BaseFmt
|
|||||||
var query = Utils.ParseQueryString(parsedUrl.Query);
|
var query = Utils.ParseQueryString(parsedUrl.Query);
|
||||||
ResolveUriQuery(query, ref item);
|
ResolveUriQuery(query, ref item);
|
||||||
|
|
||||||
|
if (GetQueryValue(query, "insecure") == "1")
|
||||||
|
{
|
||||||
|
item.AllowInsecure = Global.StringTrue;
|
||||||
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +46,10 @@ public class AnytlsFmt : BaseFmt
|
|||||||
}
|
}
|
||||||
var pw = item.Password;
|
var pw = item.Password;
|
||||||
var dicQuery = new Dictionary<string, string>();
|
var dicQuery = new Dictionary<string, string>();
|
||||||
|
if (item.GetAllowInsecure())
|
||||||
|
{
|
||||||
|
dicQuery.Add("insecure", "1");
|
||||||
|
}
|
||||||
ToUriQuery(item, Global.None, ref dicQuery);
|
ToUriQuery(item, Global.None, ref dicQuery);
|
||||||
|
|
||||||
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
|
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ namespace ServiceLib.Handler.Fmt;
|
|||||||
|
|
||||||
public class BaseFmt
|
public class BaseFmt
|
||||||
{
|
{
|
||||||
private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure" };
|
|
||||||
|
|
||||||
private static string UrlEncodeSafe(string? value) => Utils.UrlEncode(value ?? string.Empty);
|
private static string UrlEncodeSafe(string? value) => Utils.UrlEncode(value ?? string.Empty);
|
||||||
|
|
||||||
protected static string GetIpv6(string address)
|
protected static string GetIpv6(string address)
|
||||||
@@ -67,7 +65,6 @@ public class BaseFmt
|
|||||||
{
|
{
|
||||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||||
}
|
}
|
||||||
ToUriQueryAllowInsecure(item, ref dicQuery);
|
|
||||||
}
|
}
|
||||||
if (item.EchConfigList.IsNotEmpty())
|
if (item.EchConfigList.IsNotEmpty())
|
||||||
{
|
{
|
||||||
@@ -196,25 +193,6 @@ public class BaseFmt
|
|||||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,19 +230,6 @@ public class BaseFmt
|
|||||||
item.Finalmask = string.Empty;
|
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));
|
var net = GetQueryValue(query, "type", nameof(ETransport.raw));
|
||||||
if (net == Global.RawNetworkAlias)
|
if (net == Global.RawNetworkAlias)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Collections.Specialized;
|
|
||||||
|
|
||||||
namespace ServiceLib.Handler.Fmt;
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
public class Hysteria2Fmt : BaseFmt
|
public class Hysteria2Fmt : BaseFmt
|
||||||
@@ -162,10 +160,23 @@ public class Hysteria2Fmt : BaseFmt
|
|||||||
|
|
||||||
private static void ResolveHy2UriQuery(NameValueCollection query, ref ProfileItem item)
|
private static void ResolveHy2UriQuery(NameValueCollection query, ref ProfileItem item)
|
||||||
{
|
{
|
||||||
|
if (GetQueryValue(query, "insecure") == "1")
|
||||||
|
{
|
||||||
|
item.AllowInsecure = Global.StringTrue;
|
||||||
|
}
|
||||||
if (item.CertSha.IsNullOrEmpty())
|
if (item.CertSha.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||||
|
// NOTE:
|
||||||
|
// To accommodate Xray changes,
|
||||||
|
// some providers issue self-signed cert links with `insecure = false` and a certificate fingerprint,
|
||||||
|
// breaking interoperability between Xray, official Hysteria 2 client, and sing-box.
|
||||||
|
// Since this won't compromise the overall security model,
|
||||||
|
// `insecure = true` is automatically set when a fingerprint is detected,
|
||||||
|
// and the value is restored when generating configurations.
|
||||||
|
item.AllowInsecure = Global.StringTrue;
|
||||||
}
|
}
|
||||||
|
item.EchConfigList = GetQueryDecoded(query, "ech");
|
||||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||||
{
|
{
|
||||||
Ports = GetQueryDecoded(query, "mport"),
|
Ports = GetQueryDecoded(query, "mport"),
|
||||||
@@ -198,12 +209,20 @@ public class Hysteria2Fmt : BaseFmt
|
|||||||
|
|
||||||
private static void ToHy2UriQuery(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
private static void ToHy2UriQuery(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
||||||
{
|
{
|
||||||
|
if (item.GetAllowInsecure())
|
||||||
|
{
|
||||||
|
dicQuery.Add("insecure", "1");
|
||||||
|
}
|
||||||
if (!item.CertSha.IsNullOrEmpty()
|
if (!item.CertSha.IsNullOrEmpty()
|
||||||
&& !item.CertSha.Contains(','))
|
&& !item.CertSha.Contains(','))
|
||||||
{
|
{
|
||||||
var sha = item.CertSha;
|
var sha = item.CertSha;
|
||||||
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
|
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
|
||||||
}
|
}
|
||||||
|
if (!item.EchConfigList.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
|
||||||
|
}
|
||||||
var protocolExtraItem = item.GetProtocolExtra();
|
var protocolExtraItem = item.GetProtocolExtra();
|
||||||
var isGecko = !protocolExtraItem.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtraItem.GeckoMaxPacketSize.IsNullOrEmpty();
|
var isGecko = !protocolExtraItem.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtraItem.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||||
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace ServiceLib.Handler.Fmt;
|
namespace ServiceLib.Handler.Fmt;
|
||||||
|
|
||||||
public class InnerFmt
|
public class InnerFmt : BaseFmt
|
||||||
{
|
{
|
||||||
private static readonly Lazy<string> SessionSalt = new(() => Utils.GetGuid(false));
|
private static readonly Lazy<string> SessionSalt = new(() => Utils.GetGuid(false));
|
||||||
|
|
||||||
@@ -50,19 +50,19 @@ public class InnerFmt
|
|||||||
var protocolExtra = item.GetProtocolExtra();
|
var protocolExtra = item.GetProtocolExtra();
|
||||||
// Only allow "self" as a special value for SubChildItems to avoid possible sources of attacks,
|
// 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
|
// 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")
|
if (protocolExtra.SubChildItems == "self")
|
||||||
{
|
{
|
||||||
protocolExtra = protocolExtra with
|
protocolExtra = protocolExtra with
|
||||||
{
|
{
|
||||||
SubChildItems = subid
|
SubChildItems = subid,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
protocolExtra = protocolExtra with
|
protocolExtra = protocolExtra with
|
||||||
{
|
{
|
||||||
SubChildItems = null
|
SubChildItems = null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
|
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
|
||||||
@@ -73,14 +73,14 @@ public class InnerFmt
|
|||||||
.ToList();
|
.ToList();
|
||||||
protocolExtra = protocolExtra with
|
protocolExtra = protocolExtra with
|
||||||
{
|
{
|
||||||
ChildItems = Utils.List2String(newChildIndexIds)
|
ChildItems = Utils.List2String(newChildIndexIds),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
protocolExtra = protocolExtra with
|
protocolExtra = protocolExtra with
|
||||||
{
|
{
|
||||||
ChildItems = null
|
ChildItems = null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
item.SetProtocolExtra(protocolExtra);
|
item.SetProtocolExtra(protocolExtra);
|
||||||
@@ -120,7 +120,7 @@ public class InnerFmt
|
|||||||
{
|
{
|
||||||
protocolExtra = protocolExtra with
|
protocolExtra = protocolExtra with
|
||||||
{
|
{
|
||||||
SubChildItems = "self"
|
SubChildItems = "self",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
|
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
|
||||||
@@ -131,7 +131,7 @@ public class InnerFmt
|
|||||||
.ToList();
|
.ToList();
|
||||||
protocolExtra = protocolExtra with
|
protocolExtra = protocolExtra with
|
||||||
{
|
{
|
||||||
ChildItems = Utils.List2String(newChildIndexIds)
|
ChildItems = Utils.List2String(newChildIndexIds),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
itemClone.SetProtocolExtra(protocolExtra);
|
itemClone.SetProtocolExtra(protocolExtra);
|
||||||
@@ -175,6 +175,19 @@ public class InnerFmt
|
|||||||
jsonObj["TransportExtra"] = JsonUtils.Serialize(transportExtraObj, false);
|
jsonObj["TransportExtra"] = JsonUtils.Serialize(transportExtraObj, false);
|
||||||
jsonObj.Remove("TransportExtraObj");
|
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));
|
var profileItem = JsonUtils.Deserialize<ProfileItem>(JsonUtils.Serialize(jsonObj, false));
|
||||||
if (profileItem is null)
|
if (profileItem is null)
|
||||||
{
|
{
|
||||||
@@ -193,6 +206,14 @@ public class InnerFmt
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (profileItem.ConfigType is EConfigType.Outbound)
|
||||||
|
{
|
||||||
|
if (customOutboundFilePath.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
profileItem.Address = customOutboundFilePath;
|
||||||
|
}
|
||||||
var protocolExtra = profileItem.GetProtocolExtra();
|
var protocolExtra = profileItem.GetProtocolExtra();
|
||||||
var multipleLoad = protocolExtra.MultipleLoad;
|
var multipleLoad = protocolExtra.MultipleLoad;
|
||||||
if (multipleLoad is not null && !Enum.IsDefined(typeof(EMultipleLoad), multipleLoad))
|
if (multipleLoad is not null && !Enum.IsDefined(typeof(EMultipleLoad), multipleLoad))
|
||||||
@@ -209,6 +230,26 @@ public class InnerFmt
|
|||||||
{
|
{
|
||||||
return null;
|
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
|
// unflatten
|
||||||
// move jsonObj.ProtoExtra (string) to jsonObj.ProtoExtraObj
|
// move jsonObj.ProtoExtra (string) to jsonObj.ProtoExtraObj
|
||||||
// move jsonObj.TransportExtra (string) to jsonObj.TransportExtraObj
|
// 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),
|
JsonValue value when value.TryGetValue<string>(out var str) => string.IsNullOrEmpty(str),
|
||||||
JsonObject obj => obj.Count == 0,
|
JsonObject obj => obj.Count == 0,
|
||||||
JsonArray arr => arr.Count == 0,
|
JsonArray arr => arr.Count == 0,
|
||||||
_ => false
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,103 @@ namespace ServiceLib.Handler.Fmt;
|
|||||||
|
|
||||||
public class SingboxFmt : BaseFmt
|
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);
|
var jsonNode = JsonUtils.ParseJson(strData);
|
||||||
if (configObjects is not { Length: > 0 })
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ProfileItem> lstResult = [];
|
if (jsonObject["outbounds"] is JsonArray outboundsArray)
|
||||||
foreach (var configObject in configObjects)
|
|
||||||
{
|
{
|
||||||
var objectString = JsonUtils.Serialize(configObject);
|
if (!outboundsArray.Any(IsValidSingboxOutbound))
|
||||||
var profileIt = ResolveFull(objectString, subRemarks);
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||||
|
var profileItem = new ProfileItem
|
||||||
|
{
|
||||||
|
ConfigType = EConfigType.Custom,
|
||||||
|
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)
|
if (profileIt != null)
|
||||||
{
|
{
|
||||||
lstResult.Add(profileIt);
|
lstResult.Add(profileIt);
|
||||||
@@ -23,25 +107,58 @@ public class SingboxFmt : BaseFmt
|
|||||||
return lstResult;
|
return lstResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
private static ProfileItem? ResolveOutbound(JsonObject jsonObject, string? subRemarks)
|
||||||
{
|
{
|
||||||
var config = JsonUtils.ParseJson(strData);
|
if (!IsValidSingboxOutbound(jsonObject))
|
||||||
if (config?["inbounds"] == null
|
|
||||||
|| config["outbounds"] == null
|
|
||||||
|| config["route"] == null
|
|
||||||
|| config["dns"] == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
var type = jsonObject["type"]?.ToString();
|
||||||
var fileName = WriteAllText(strData);
|
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
|
var profileItem = new ProfileItem
|
||||||
{
|
{
|
||||||
|
ConfigType = EConfigType.Outbound,
|
||||||
CoreType = ECoreType.sing_box,
|
CoreType = ECoreType.sing_box,
|
||||||
Address = fileName,
|
Address = fileName,
|
||||||
Remarks = subRemarks ?? "singbox_custom"
|
Remarks = remarks,
|
||||||
};
|
};
|
||||||
|
|
||||||
return profileItem;
|
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
|
public class TrojanFmt : BaseFmt
|
||||||
{
|
{
|
||||||
|
private static readonly List<string> _insecureQueryKeys = new() { "allowInsecure", "insecure" };
|
||||||
|
|
||||||
public static ProfileItem? Resolve(string str, out string msg)
|
public static ProfileItem? Resolve(string str, out string msg)
|
||||||
{
|
{
|
||||||
msg = ResUI.ConfigurationFormatIncorrect;
|
msg = ResUI.ConfigurationFormatIncorrect;
|
||||||
@@ -23,6 +25,10 @@ public class TrojanFmt : BaseFmt
|
|||||||
item.Password = Utils.UrlDecode(url.UserInfo);
|
item.Password = Utils.UrlDecode(url.UserInfo);
|
||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
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") });
|
item.SetProtocolExtra(item.GetProtocolExtra() with { Flow = GetQueryValue(query, "flow") });
|
||||||
ResolveUriQuery(query, ref item);
|
ResolveUriQuery(query, ref item);
|
||||||
|
|
||||||
@@ -41,6 +47,10 @@ public class TrojanFmt : BaseFmt
|
|||||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
}
|
}
|
||||||
var dicQuery = new Dictionary<string, string>();
|
var dicQuery = new Dictionary<string, string>();
|
||||||
|
if (item.GetAllowInsecure())
|
||||||
|
{
|
||||||
|
_insecureQueryKeys.ForEach(q => dicQuery.Add(q, "1"));
|
||||||
|
}
|
||||||
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
|
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
|
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ public class TuicFmt : BaseFmt
|
|||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
ResolveUriQuery(query, ref item);
|
ResolveUriQuery(query, ref item);
|
||||||
|
if (GetQueryValue(query, "allow_insecure") == "1")
|
||||||
|
{
|
||||||
|
item.AllowInsecure = Global.StringTrue;
|
||||||
|
}
|
||||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||||
{
|
{
|
||||||
CongestionControl = GetQueryValue(query, "congestion_control")
|
CongestionControl = GetQueryValue(query, "congestion_control")
|
||||||
@@ -53,7 +57,10 @@ public class TuicFmt : BaseFmt
|
|||||||
|
|
||||||
var dicQuery = new Dictionary<string, string>();
|
var dicQuery = new Dictionary<string, string>();
|
||||||
ToUriQueryLite(item, ref dicQuery);
|
ToUriQueryLite(item, ref dicQuery);
|
||||||
|
if (item.GetAllowInsecure())
|
||||||
|
{
|
||||||
|
dicQuery.Add("allow_insecure", "1");
|
||||||
|
}
|
||||||
if (!item.GetProtocolExtra().CongestionControl.IsNullOrEmpty())
|
if (!item.GetProtocolExtra().CongestionControl.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
dicQuery.Add("congestion_control", item.GetProtocolExtra().CongestionControl);
|
dicQuery.Add("congestion_control", item.GetProtocolExtra().CongestionControl);
|
||||||
|
|||||||
@@ -2,47 +2,166 @@ namespace ServiceLib.Handler.Fmt;
|
|||||||
|
|
||||||
public class V2rayFmt : BaseFmt
|
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);
|
var jsonNode = JsonUtils.ParseJson(strData);
|
||||||
if (configObjects is not { Length: > 0 })
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ProfileItem> lstResult = [];
|
if (jsonObject["outbounds"] is JsonArray outboundsArray)
|
||||||
foreach (var configObject in configObjects)
|
|
||||||
{
|
{
|
||||||
var objectString = JsonUtils.Serialize(configObject);
|
if (!outboundsArray.Any(IsValidV2rayOutbound))
|
||||||
var profileIt = ResolveFull(objectString, subRemarks);
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||||
|
|
||||||
|
var profileItem = new ProfileItem
|
||||||
|
{
|
||||||
|
ConfigType = EConfigType.Custom,
|
||||||
|
CoreType = ECoreType.Xray,
|
||||||
|
Address = fileName,
|
||||||
|
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)
|
if (profileIt != null)
|
||||||
{
|
{
|
||||||
lstResult.Add(profileIt);
|
lstResult.Add(profileIt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lstResult;
|
return lstResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
public static ProfileItem? ResolveOutbound(JsonObject jsonObject, string? subRemarks)
|
||||||
{
|
{
|
||||||
var config = JsonUtils.ParseJson(strData);
|
if (!IsValidV2rayOutbound(jsonObject))
|
||||||
if (config?["inbounds"] == null
|
|
||||||
|| config["outbounds"] == null
|
|
||||||
|| config["routing"] == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
var protocol = jsonObject["protocol"]?.ToString();
|
||||||
var fileName = WriteAllText(strData);
|
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
|
var profileItem = new ProfileItem
|
||||||
{
|
{
|
||||||
|
ConfigType = EConfigType.Outbound,
|
||||||
CoreType = ECoreType.Xray,
|
CoreType = ECoreType.Xray,
|
||||||
Address = fileName,
|
Address = fileName,
|
||||||
Remarks = config?["remarks"]?.ToString() ?? subRemarks ?? "v2ray_custom"
|
Remarks = remarks,
|
||||||
};
|
};
|
||||||
|
|
||||||
return profileItem;
|
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
|
try
|
||||||
{
|
{
|
||||||
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
|
||||||
if (port <= 0)
|
if (port <= 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -24,17 +23,18 @@ public static class SysProxyHandler
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case ESysProxyType.ForcedChange when Utils.IsWindows():
|
case ESysProxyType.ForcedChange when Utils.IsWindows():
|
||||||
{
|
var (strProxy, strExceptions) = GetWindowsProxyString(config, port);
|
||||||
GetWindowsProxyString(config, port, out var strProxy, out var strExceptions);
|
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
|
||||||
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ESysProxyType.ForcedChange when Utils.IsLinux():
|
case ESysProxyType.ForcedChange when Utils.IsLinux():
|
||||||
|
var exceptions = SanitizeExceptions(config);
|
||||||
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
|
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ESysProxyType.ForcedChange when Utils.IsMacOS():
|
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;
|
break;
|
||||||
|
|
||||||
case ESysProxyType.ForcedClear when Utils.IsWindows():
|
case ESysProxyType.ForcedClear when Utils.IsWindows():
|
||||||
@@ -66,15 +66,31 @@ public static class SysProxyHandler
|
|||||||
return true;
|
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)
|
if (config.SystemProxyItem.NotProxyLocalAddress)
|
||||||
{
|
{
|
||||||
strExceptions = $"<local>;{strExceptions}";
|
strExceptions = $"<local>;{strExceptions}";
|
||||||
}
|
}
|
||||||
|
|
||||||
strProxy = string.Empty;
|
var strProxy = string.Empty;
|
||||||
if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty())
|
if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
strProxy = $"{Global.Loopback}:{port}";
|
strProxy = $"{Global.Loopback}:{port}";
|
||||||
@@ -86,6 +102,8 @@ public static class SysProxyHandler
|
|||||||
.Replace("{http_port}", port.ToString())
|
.Replace("{http_port}", port.ToString())
|
||||||
.Replace("{socks_port}", port.ToString());
|
.Replace("{socks_port}", port.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (strProxy, strExceptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ public class CoreAdminManager
|
|||||||
.ExecuteBufferedAsync();
|
.ExecuteBufferedAsync();
|
||||||
|
|
||||||
await UpdateFunc(false, result.StandardOutput.ToString());
|
await UpdateFunc(false, result.StandardOutput.ToString());
|
||||||
|
|
||||||
|
await Task.Delay(1000); // Wait for a second to ensure the process is killed
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public class CoreManager
|
|||||||
await CoreStop();
|
await CoreStop();
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
|
|
||||||
if (Utils.IsWindows() && _config.TunModeItem.EnableTun)
|
if (Utils.IsWindows() && (mainContext?.IsTunEnabled == true || preContext?.IsTunEnabled == true))
|
||||||
{
|
{
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
await WindowsUtils.RemoveTunDevice();
|
await WindowsUtils.RemoveTunDevice();
|
||||||
@@ -183,7 +183,7 @@ public class CoreManager
|
|||||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||||
|
|
||||||
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
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)
|
if (proc is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -201,7 +201,7 @@ public class CoreManager
|
|||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
|
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)
|
if (proc is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -222,7 +222,7 @@ public class CoreManager
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!preContext.AppConfig.TunModeItem.EnableTun)
|
if (!preContext.IsTunEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,20 @@ public class CoreManager
|
|||||||
|
|
||||||
#region Process
|
#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);
|
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
|
||||||
if (fileName.IsNullOrEmpty())
|
if (fileName.IsNullOrEmpty())
|
||||||
@@ -301,9 +314,7 @@ public class CoreManager
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (mayNeedSudo
|
if (mayNeedSudo
|
||||||
&& _config.TunModeItem.EnableTun
|
&& ShouldRunAsSudo(isTunLaunch, coreInfo.CoreType, Utils.IsNonWindows()))
|
||||||
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo or ECoreType.Xray)
|
|
||||||
&& Utils.IsNonWindows())
|
|
||||||
{
|
{
|
||||||
_linuxSudo = true;
|
_linuxSudo = true;
|
||||||
await CoreAdminManager.Instance.Init(_config, _updateFunc);
|
await CoreAdminManager.Instance.Init(_config, _updateFunc);
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class GroupProfileManager
|
|||||||
return childProfiles?.Where(p =>
|
return childProfiles?.Where(p =>
|
||||||
p != null &&
|
p != null &&
|
||||||
p.IsValid() &&
|
p.IsValid() &&
|
||||||
!p.ConfigType.IsComplexType() &&
|
(!p.ConfigType.IsComplexType() || p.ConfigType == EConfigType.Outbound) &&
|
||||||
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
|
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
|
||||||
)
|
)
|
||||||
.ToList() ?? [];
|
.ToList() ?? [];
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//using System.Reactive.Linq;
|
|
||||||
|
|
||||||
namespace ServiceLib.Manager;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public class ProfileExManager
|
public class ProfileExManager
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public class Config
|
|||||||
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
||||||
public List<CoreTypeItem> CoreTypeItem { get; set; }
|
public List<CoreTypeItem> CoreTypeItem { get; set; }
|
||||||
public SimpleDNSItem SimpleDNSItem { get; set; }
|
public SimpleDNSItem SimpleDNSItem { get; set; }
|
||||||
|
public HappyEyeballs4RayItem HappyEyeballs4RayItem { get; set; }
|
||||||
|
|
||||||
#endregion other entities
|
#endregion other entities
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,8 +147,10 @@ public class TunModeItem
|
|||||||
public int Mtu { get; set; }
|
public int Mtu { get; set; }
|
||||||
public bool EnableIPv6Address { get; set; }
|
public bool EnableIPv6Address { get; set; }
|
||||||
public string IcmpRouting { 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 List<string>? RouteExcludeAddress { get; set; }
|
||||||
|
public string IPv4Address { get; set; }
|
||||||
|
public string IPv6Address { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
@@ -249,9 +251,15 @@ public class CheckUpdateItem
|
|||||||
public class Fragment4RayItem
|
public class Fragment4RayItem
|
||||||
{
|
{
|
||||||
public string? Packets { get; set; }
|
public string? Packets { get; set; }
|
||||||
public string? Length { get; set; }
|
public List<string>? Lengths { get; set; }
|
||||||
public string? Interval { get; set; }
|
public List<string>? Delays { get; set; }
|
||||||
public string? MaxSplit { 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]
|
[Serializable]
|
||||||
@@ -269,14 +277,26 @@ public class SimpleDNSItem
|
|||||||
public bool? AddCommonHosts { get; set; }
|
public bool? AddCommonHosts { get; set; }
|
||||||
public bool? FakeIP { get; set; }
|
public bool? FakeIP { get; set; }
|
||||||
public bool? GlobalFakeIp { get; set; }
|
public bool? GlobalFakeIp { get; set; }
|
||||||
|
public string? FakeIPRange { get; set; }
|
||||||
public bool? BlockBindingQuery { get; set; }
|
public bool? BlockBindingQuery { get; set; }
|
||||||
public string? DirectDNS { get; set; }
|
public string? DirectDNS { get; set; }
|
||||||
public string? RemoteDNS { get; set; }
|
public string? RemoteDNS { get; set; }
|
||||||
public string? BootstrapDNS { get; set; }
|
public string? BootstrapDNS { get; set; }
|
||||||
public string? Strategy4Freedom { get; set; }
|
public string? Strategy4Freedom { get; set; }
|
||||||
public string? Strategy4Proxy { get; set; }
|
public string? Strategy4Proxy { get; set; }
|
||||||
|
public string? Strategy4ProxyDial { get; set; }
|
||||||
public bool? ServeStale { get; set; }
|
public bool? ServeStale { get; set; }
|
||||||
public bool? ParallelQuery { get; set; }
|
public bool? ParallelQuery { get; set; }
|
||||||
public string? Hosts { get; set; }
|
public string? Hosts { get; set; }
|
||||||
public string? DirectExpectedIPs { 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,6 +11,8 @@ public record CoreConfigContext
|
|||||||
public Config AppConfig { get; init; } = new();
|
public Config AppConfig { get; init; } = new();
|
||||||
public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new();
|
public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new();
|
||||||
|
|
||||||
|
public Dictionary<string, string> CustomOutboundContent { get; init; } = new();
|
||||||
|
|
||||||
// Test ServerTestItem Map
|
// Test ServerTestItem Map
|
||||||
public Dictionary<string, string> ServerTestItemMap { get; init; } = new();
|
public Dictionary<string, string> ServerTestItemMap { get; init; } = new();
|
||||||
|
|
||||||
@@ -22,4 +24,7 @@ public record CoreConfigContext
|
|||||||
|
|
||||||
public bool IsWindows { get; init; }
|
public bool IsWindows { get; init; }
|
||||||
public bool IsMacOS { 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 Log4Ray log { get; set; }
|
||||||
public object dns { get; set; }
|
public object dns { get; set; }
|
||||||
|
public FakeDns4Ray? fakedns { get; set; }
|
||||||
public List<Inbounds4Ray> inbounds { get; set; }
|
public List<Inbounds4Ray> inbounds { get; set; }
|
||||||
public List<Outbounds4Ray> outbounds { get; set; }
|
public List<Outbounds4Ray> outbounds { get; set; }
|
||||||
public Routing4Ray routing { get; set; }
|
public Routing4Ray routing { get; set; }
|
||||||
@@ -43,6 +44,12 @@ public class Log4Ray
|
|||||||
public string? loglevel { get; set; }
|
public string? loglevel { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class FakeDns4Ray
|
||||||
|
{
|
||||||
|
public string? ipPool { get; set; }
|
||||||
|
public long? poolSize { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Inbounds4Ray
|
public class Inbounds4Ray
|
||||||
{
|
{
|
||||||
public string tag { get; set; }
|
public string tag { get; set; }
|
||||||
@@ -136,8 +143,6 @@ public class Outboundsettings4Ray
|
|||||||
|
|
||||||
public Response4Ray? response { get; set; }
|
public Response4Ray? response { get; set; }
|
||||||
|
|
||||||
public string? domainStrategy { get; set; }
|
|
||||||
|
|
||||||
public int? userLevel { get; set; }
|
public int? userLevel { get; set; }
|
||||||
|
|
||||||
public string? secretKey { get; set; }
|
public string? secretKey { get; set; }
|
||||||
@@ -359,8 +364,6 @@ public class StreamSettings4Ray
|
|||||||
|
|
||||||
public class TlsSettings4Ray
|
public class TlsSettings4Ray
|
||||||
{
|
{
|
||||||
public bool? allowInsecure { get; set; }
|
|
||||||
|
|
||||||
public string? serverName { get; set; }
|
public string? serverName { get; set; }
|
||||||
|
|
||||||
public List<string>? alpn { get; set; }
|
public List<string>? alpn { get; set; }
|
||||||
@@ -516,6 +519,8 @@ public class MaskSettings4Ray
|
|||||||
|
|
||||||
public string? length { get; set; }
|
public string? length { get; set; }
|
||||||
public string? delay { get; set; }
|
public string? delay { get; set; }
|
||||||
|
public List<string>? lengths { get; set; }
|
||||||
|
public List<string>? delays { get; set; }
|
||||||
public int? maxSplit { get; set; }
|
public int? maxSplit { get; set; }
|
||||||
|
|
||||||
// noise
|
// noise
|
||||||
@@ -547,15 +552,20 @@ public class AccountsItem4Ray
|
|||||||
|
|
||||||
public class Sockopt4Ray
|
public class Sockopt4Ray
|
||||||
{
|
{
|
||||||
|
public string? domainStrategy { get; set; }
|
||||||
|
|
||||||
public string? dialerProxy { get; set; }
|
public string? dialerProxy { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("interface")]
|
[JsonPropertyName("interface")]
|
||||||
public string? Interface { get; set; }
|
public string? Interface { get; set; }
|
||||||
|
|
||||||
|
public HappyEyeballs4Ray? happyEyeballs { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FragmentItem4Ray
|
public class HappyEyeballs4Ray
|
||||||
{
|
{
|
||||||
public string? packets { get; set; }
|
public int? tryDelayMs { get; set; }
|
||||||
public string? length { get; set; }
|
public bool? prioritizeIPv6 { get; set; }
|
||||||
public string? interval { get; set; }
|
public int? interleave { get; set; }
|
||||||
|
public int? maxConcurrentTry { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
namespace ServiceLib.Models.Dto;
|
namespace ServiceLib.Models.Dto;
|
||||||
|
|
||||||
public class CheckUpdateModel : ReactiveObject
|
public partial class CheckUpdateModel : ReactiveObject
|
||||||
{
|
{
|
||||||
public bool? IsSelected { get; set; }
|
public bool? IsSelected { get; set; }
|
||||||
public ECoreType? CoreType { 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 string? FileName { get; set; }
|
||||||
public bool? IsFinished { get; set; }
|
public bool? IsFinished { get; set; }
|
||||||
public bool IsGeoFile { get; set; }
|
public bool IsGeoFile { get; set; }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace ServiceLib.Models.Dto;
|
namespace ServiceLib.Models.Dto;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ClashProxyModel : ReactiveObject
|
public partial class ClashProxyModel : ReactiveObject
|
||||||
{
|
{
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
@@ -9,9 +9,9 @@ public class ClashProxyModel : ReactiveObject
|
|||||||
|
|
||||||
public string? Now { get; set; }
|
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; }
|
public bool IsActive { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace ServiceLib.Models.Dto;
|
namespace ServiceLib.Models.Dto;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ProfileItemModel : ReactiveObject
|
public partial class ProfileItemModel : ReactiveObject
|
||||||
{
|
{
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
public string IndexId { get; set; }
|
public string IndexId { get; set; }
|
||||||
@@ -16,30 +16,30 @@ public class ProfileItemModel : ReactiveObject
|
|||||||
public int Sort { get; set; }
|
public int Sort { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int Delay { get; set; }
|
public partial int Delay { get; set; }
|
||||||
|
|
||||||
public decimal Speed { get; set; }
|
public decimal Speed { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string DelayVal { get; set; }
|
public partial string DelayVal { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string SpeedVal { get; set; }
|
public partial string SpeedVal { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string IpInfo { get; set; }
|
public partial string IpInfo { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string TodayUp { get; set; }
|
public partial string TodayUp { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string TodayDown { get; set; }
|
public partial string TodayDown { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string TotalUp { get; set; }
|
public partial string TotalUp { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string TotalDown { get; set; }
|
public partial string TotalDown { get; set; }
|
||||||
|
|
||||||
public string GetSummary()
|
public string GetSummary()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class ProfileItem
|
|||||||
|
|
||||||
public bool IsValid()
|
public bool IsValid()
|
||||||
{
|
{
|
||||||
if (IsComplex())
|
if (IsComplex() || ConfigType == EConfigType.Outbound)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,4 +51,7 @@ public record ProtocolExtraItem
|
|||||||
public string? SubChildItems { get; init; }
|
public string? SubChildItems { get; init; }
|
||||||
public string? Filter { get; init; }
|
public string? Filter { get; init; }
|
||||||
public EMultipleLoad? MultipleLoad { 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 int? PreSocksPort { get; set; }
|
||||||
|
|
||||||
public string? Memo { get; set; }
|
public string? Memo { get; set; }
|
||||||
|
|
||||||
|
public ECoreType? CustomCoreType { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
100
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
100
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -178,7 +178,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Failed to import custom configuration Configuration 的本地化字符串。
|
/// 查找类似 Failed to import configuration 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string FailedImportedCustomServer {
|
public static string FailedImportedCustomServer {
|
||||||
get {
|
get {
|
||||||
@@ -268,7 +268,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Please browse to import Configuration configuration 的本地化字符串。
|
/// 查找类似 Please browse to import configuration 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string FillServerAddressCustom {
|
public static string FillServerAddressCustom {
|
||||||
get {
|
get {
|
||||||
@@ -438,6 +438,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Custom config core 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string LvCustomCoreType {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LvCustomCoreType", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Custom icon 的本地化字符串。
|
/// 查找类似 Custom icon 的本地化字符串。
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// 查找类似 Add a custom configuration 的本地化字符串。
|
/// 查找类似 Add a custom configuration 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1933,7 +1951,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public static string MsgAllowInsecureDeprecated {
|
public static string MsgAllowInsecureDeprecated {
|
||||||
get {
|
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>
|
/// <summary>
|
||||||
/// 查找类似 Downloaded GeoFile: {0} successfully 的本地化字符串。
|
/// 查找类似 Downloaded GeoFile: {0} successfully 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2626,7 +2653,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Custom configuration Configuration imported successfully 的本地化字符串。
|
/// 查找类似 Custom configuration imported successfully 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string SuccessfullyImportedCustomServer {
|
public static string SuccessfullyImportedCustomServer {
|
||||||
get {
|
get {
|
||||||
@@ -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>
|
/// <summary>
|
||||||
/// 查找类似 Direct Target Resolution Strategy 的本地化字符串。
|
/// 查找类似 Direct Target Resolution Strategy 的本地化字符串。
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// 查找类似 DNS via Bridge 的本地化字符串。
|
/// 查找类似 DNS via Bridge 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3088,7 +3142,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public static string TbFakeIPTips {
|
public static string TbFakeIPTips {
|
||||||
get {
|
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>
|
/// <summary>
|
||||||
/// 查找类似 Most Stable 的本地化字符串。
|
/// 查找类似 Most Stable 的本地化字符串。
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// 查找类似 Public Key 的本地化字符串。
|
/// 查找类似 Public Key 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1495,7 +1495,7 @@
|
|||||||
<value>Select Profile</value>
|
<value>Select Profile</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<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>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>Please Add At Least One Configuration</value>
|
<value>Please Add At Least One Configuration</value>
|
||||||
|
|||||||
@@ -1492,7 +1492,7 @@
|
|||||||
<value>Choisir une config.</value>
|
<value>Choisir une config.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<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>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>Veuillez ajouter au moins une configuration</value>
|
<value>Veuillez ajouter au moins une configuration</value>
|
||||||
|
|||||||
@@ -1495,7 +1495,7 @@
|
|||||||
<value>Select Profile</value>
|
<value>Select Profile</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<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>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>Please Add At Least One Configuration</value>
|
<value>Please Add At Least One Configuration</value>
|
||||||
|
|||||||
@@ -1495,7 +1495,7 @@
|
|||||||
<value>Pilih profil</value>
|
<value>Pilih profil</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<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>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>Silakan tambahkan setidaknya satu konfigurasi.</value>
|
<value>Silakan tambahkan setidaknya satu konfigurasi.</value>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
<value>Failed to get the default configuration</value>
|
<value>Failed to get the default configuration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FailedImportedCustomServer" xml:space="preserve">
|
<data name="FailedImportedCustomServer" xml:space="preserve">
|
||||||
<value>Failed to import custom configuration Configuration</value>
|
<value>Failed to import configuration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FailedReadConfiguration" xml:space="preserve">
|
<data name="FailedReadConfiguration" xml:space="preserve">
|
||||||
<value>Failed to read configuration file</value>
|
<value>Failed to read configuration file</value>
|
||||||
@@ -283,7 +283,7 @@
|
|||||||
<value>Configuration successful. {0}</value>
|
<value>Configuration successful. {0}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
|
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
|
||||||
<value>Custom configuration Configuration imported successfully</value>
|
<value>Custom configuration imported successfully</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
|
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
|
||||||
<value>{0} Configurations have been imported from clipboard</value>
|
<value>{0} Configurations have been imported from clipboard</value>
|
||||||
@@ -385,7 +385,7 @@
|
|||||||
<value>All</value>
|
<value>All</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillServerAddressCustom" xml:space="preserve">
|
<data name="FillServerAddressCustom" xml:space="preserve">
|
||||||
<value>Please browse to import Configuration configuration</value>
|
<value>Please browse to import configuration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Speedtesting" xml:space="preserve">
|
<data name="Speedtesting" xml:space="preserve">
|
||||||
<value>Testing...</value>
|
<value>Testing...</value>
|
||||||
@@ -1501,7 +1501,7 @@
|
|||||||
<value>Select Profile</value>
|
<value>Select Profile</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<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>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>Please Add At Least One Configuration</value>
|
<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>
|
<value>New Update</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
<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>
|
||||||
<data name="TbRouteExcludeAddress" xml:space="preserve">
|
<data name="TbRouteExcludeAddress" xml:space="preserve">
|
||||||
<value>Route Exclude Address</value>
|
<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">
|
<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>
|
<value>Only applies to the v2rayN GUI's downloads and network requests. Does not affect the core's certificate validation.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<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>
|
</root>
|
||||||
|
|||||||
@@ -268,7 +268,7 @@
|
|||||||
<value>Сначала выберите сервер</value>
|
<value>Сначала выберите сервер</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoveDuplicateServerResult" xml:space="preserve">
|
<data name="RemoveDuplicateServerResult" xml:space="preserve">
|
||||||
<value>Дедупликация конфигураций завершена. Было: {0}, Стало: {1}.</value>
|
<value>Дедупликация конфигураций завершена. Было: {0}, Стало: {1}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoveServer" xml:space="preserve">
|
<data name="RemoveServer" xml:space="preserve">
|
||||||
<value>Вы уверены, что хотите удалить сервер?</value>
|
<value>Вы уверены, что хотите удалить сервер?</value>
|
||||||
@@ -1048,7 +1048,7 @@
|
|||||||
<value>HTTP-заголовки</value>
|
<value>HTTP-заголовки</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||||
<value>Пользовательские заголовки исходящего HTTP-запроса. Введите JSON-объект, значения которого — строка или массив строк.</value>
|
<value>Пользовательские заголовки исходящего HTTP-запроса. Введите JSON-объект, значения которого — строка или массив строк</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LvPrevProfile" xml:space="preserve">
|
<data name="LvPrevProfile" xml:space="preserve">
|
||||||
<value>Псевдоним предыдущего прокси</value>
|
<value>Псевдоним предыдущего прокси</value>
|
||||||
@@ -1120,28 +1120,28 @@
|
|||||||
<value>Длина фрагмента</value>
|
<value>Длина фрагмента</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||||
<value>Диапазон размера фрагмента в байтах (например, 50-100). Первое значение не больше второго. Пусто = по умолчанию.</value>
|
<value>Диапазон размера фрагмента в байтах (например, 50-100). Первое значение не больше второго. Пусто = по умолчанию</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||||
<value>Интервал фрагментации</value>
|
<value>Интервал фрагментации</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||||
<value>Задержка между фрагментами в мс (например, 10-20). Диапазон 1-100. Первое значение не больше второго. Пусто = по умолчанию.</value>
|
<value>Задержка между фрагментами в мс (например, 10-20). Диапазон 1-100. Первое значение не больше второго. Пусто = по умолчанию</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||||
<value>Макс. разделение</value>
|
<value>Макс. разделение</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||||
<value>Максимальное число разделений (0 = без лимита, 0-10000). Только для Xray-core. Пусто = по умолчанию.</value>
|
<value>Максимальное число разделений (0 = без лимита, 0-10000). Только для Xray-core. Пусто = по умолчанию</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||||
<value>Резервная задержка</value>
|
<value>Резервная задержка</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||||
<value>Резервная задержка для TLS-фрагмента. Только для sing-box. Пусто = по умолчанию.</value>
|
<value>Резервная задержка для TLS-фрагмента. Только для sing-box. Пусто = по умолчанию</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||||
<value>Неверный формат диапазона. Используйте 'from-to' (например, 50-100).</value>
|
<value>Неверный формат диапазона. Используйте 'from-to' (например, 50-100)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||||
<value>Включить файл кэша для sing-box (файлы наборов правил)</value>
|
<value>Включить файл кэша для sing-box (файлы наборов правил)</value>
|
||||||
@@ -1351,7 +1351,7 @@
|
|||||||
<value>Пароль sudo системы</value>
|
<value>Пароль sudo системы</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
|
||||||
<value>Пароль sudo будет проверен в терминале. Если из-за ошибки проверки приложение начнёт работать некорректно, перезапустите его. Пароль не сохраняется — его нужно вводить после каждого перезапуска.</value>
|
<value>Пароль sudo будет проверен в терминале. Если из-за ошибки проверки приложение начнёт работать некорректно, перезапустите его. Пароль не сохраняется — его нужно вводить после каждого перезапуска</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TransportHeaderType5" xml:space="preserve">
|
<data name="TransportHeaderType5" xml:space="preserve">
|
||||||
<value>XHTTP-режим</value>
|
<value>XHTTP-режим</value>
|
||||||
@@ -1411,7 +1411,7 @@
|
|||||||
<value>Можно указать псевдоним из конфигурации, убедитесь, что он существует и уникален</value>
|
<value>Можно указать псевдоним из конфигурации, убедитесь, что он существует и уникален</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
|
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
|
||||||
<value>Неверный пароль, попробуйте ещё раз.</value>
|
<value>Неверный пароль, попробуйте ещё раз</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbMldsa65Verify" xml:space="preserve">
|
<data name="TbMldsa65Verify" xml:space="preserve">
|
||||||
<value>Mldsa65Verify</value>
|
<value>Mldsa65Verify</value>
|
||||||
@@ -1492,7 +1492,7 @@
|
|||||||
<value>Добавляет только конфигурацию Outbound и Endpoint. Нажмите, чтобы открыть документ</value>
|
<value>Добавляет только конфигурацию Outbound и Endpoint. Нажмите, чтобы открыть документ</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную.</value>
|
<value>Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>Начинается разбор и обработка содержимого подписки</value>
|
<value>Начинается разбор и обработка содержимого подписки</value>
|
||||||
@@ -1501,7 +1501,7 @@
|
|||||||
<value>Выбрать профиль</value>
|
<value>Выбрать профиль</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
<value>По умолчанию применяется глобально, со встроенной фильтрацией FakeIP (только sing-box).</value>
|
<value>Применяется глобально по умолчанию; встроенная фильтрация FakeIP есть только в sing-box</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>Добавьте хотя бы одну конфигурацию</value>
|
<value>Добавьте хотя бы одну конфигурацию</value>
|
||||||
@@ -1588,7 +1588,7 @@
|
|||||||
<value>Привязанный сертификат (заполните любое из полей)
|
<value>Привязанный сертификат (заполните любое из полей)
|
||||||
При указании сертификат будет привязан, а «Пропустить проверку сертификата» отключится.
|
При указании сертификат будет привязан, а «Пропустить проверку сертификата» отключится.
|
||||||
|
|
||||||
Получение сертификата может завершиться неудачей при использовании самоподписанного сертификата или при наличии ненадёжного / вредоносного ЦС в системе.</value>
|
Получение сертификата может завершиться неудачей при использовании самоподписанного сертификата или при наличии ненадёжного / вредоносного ЦС в системе</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFetchCert" xml:space="preserve">
|
<data name="TbFetchCert" xml:space="preserve">
|
||||||
<value>Получить сертификат</value>
|
<value>Получить сертификат</value>
|
||||||
@@ -1642,10 +1642,10 @@
|
|||||||
<value>По умолчанию используется только при разрешении имён в процессе маршрутизации; убедитесь, что удалённый сервер может достичь этого DNS</value>
|
<value>По умолчанию используется только при разрешении имён в процессе маршрутизации; убедитесь, что удалённый сервер может достичь этого DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
|
||||||
<value>Если не задано или «AsIs», используется системный DNS; иначе — встроенный DNS-модуль.</value>
|
<value>Если не задано или «AsIs», используется системный DNS; иначе — встроенный DNS-модуль</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
|
||||||
<value>Если не задано или «AsIs», разрешение DNS выполняется DNS удалённого сервера; иначе — встроенный DNS-модуль.</value>
|
<value>Если не задано или «AsIs», разрешение DNS выполняется DNS удалённого сервера; иначе — встроенный DNS-модуль</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbHopInt7" xml:space="preserve">
|
<data name="TbHopInt7" xml:space="preserve">
|
||||||
<value>Интервал смены портов (Port Hopping)</value>
|
<value>Интервал смены портов (Port Hopping)</value>
|
||||||
@@ -1660,37 +1660,37 @@
|
|||||||
<value>Правило маршрутизации {0}, исходящий узел {1}, предупреждение: {2}</value>
|
<value>Правило маршрутизации {0}, исходящий узел {1}, предупреждение: {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
|
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
|
||||||
<value>Правило маршрутизации {0}, исходящий узел {1}, ошибка: {2}. Используется только прокси-узел.</value>
|
<value>Правило маршрутизации {0}, исходящий узел {1}, ошибка: {2}. Используется только прокси-узел</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgGroupCycleDependency" xml:space="preserve">
|
<data name="MsgGroupCycleDependency" xml:space="preserve">
|
||||||
<value>Группа {0} имеет циклическую зависимость на дочерний узел {1}. Узел пропущен.</value>
|
<value>Группа {0} имеет циклическую зависимость на дочерний узел {1}. Узел пропущен</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
|
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
|
||||||
<value>Группа {0}: предупреждение дочернего узла {1}: {2}</value>
|
<value>Группа {0}: предупреждение дочернего узла {1}: {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgGroupChildNodeError" xml:space="preserve">
|
<data name="MsgGroupChildNodeError" xml:space="preserve">
|
||||||
<value>Группа {0}: ошибка дочернего узла {1}: {2}. Узел пропущен.</value>
|
<value>Группа {0}: ошибка дочернего узла {1}: {2}. Узел пропущен</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
|
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
|
||||||
<value>Группа {0}: предупреждение дочернего узла группы {1}: {2}</value>
|
<value>Группа {0}: предупреждение дочернего узла группы {1}: {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
|
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
|
||||||
<value>Группа {0}: ошибка дочернего узла группы {1}: {2}. Узел пропущен.</value>
|
<value>Группа {0}: ошибка дочернего узла группы {1}: {2}. Узел пропущен</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
|
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
|
||||||
<value>У группы {0} нет допустимых дочерних узлов.</value>
|
<value>У группы {0} нет допустимых дочерних узлов</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
|
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
|
||||||
<value>У правила маршрутизации {0} пустой исходящий тег. Используется только прокси-узел.</value>
|
<value>У правила маршрутизации {0} пустой исходящий тег. Используется только прокси-узел</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
|
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
|
||||||
<value>Правило маршрутизации {0}, исходящий узел {1} не найден. Используется только прокси-узел.</value>
|
<value>Правило маршрутизации {0}, исходящий узел {1} не найден. Используется только прокси-узел</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
|
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
|
||||||
<value>Предыдущий прокси подписки {0} не найден. Пропущено.</value>
|
<value>Предыдущий прокси подписки {0} не найден. Пропущено</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
|
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
|
||||||
<value>Следующий прокси подписки {0} не найден. Пропущено.</value>
|
<value>Следующий прокси подписки {0} не найден. Пропущено</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuGenGroupServer" xml:space="preserve">
|
<data name="menuGenGroupServer" xml:space="preserve">
|
||||||
<value>Сгенерировать группу политик</value>
|
<value>Сгенерировать группу политик</value>
|
||||||
@@ -1756,7 +1756,7 @@
|
|||||||
<value>Привязать интерфейс</value>
|
<value>Привязать интерфейс</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
||||||
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для исходящих подключений. На Linux/macOS работает только при включённом TUN.</value>
|
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для исходящих подключений. На Linux/macOS работает только при включённом TUN</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPreSharedKey" xml:space="preserve">
|
<data name="TbPreSharedKey" xml:space="preserve">
|
||||||
<value>Общий ключ (PSK)</value>
|
<value>Общий ключ (PSK)</value>
|
||||||
@@ -1780,13 +1780,13 @@
|
|||||||
<value>Доступно обновление</value>
|
<value>Доступно обновление</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
||||||
<value>Предупреждение: 1 августа 2026 г. Xray отключит пропуск проверки сертификата (allowInsecure). Как можно скорее перейдите на привязанный отпечаток сертификата (pinnedPeerCertSha256). После этой даты allowInsecure использовать будет нельзя.</value>
|
<value>Предупреждение: 1 августа 2026 г. Xray отключит пропуск проверки сертификата (allowInsecure). Как можно скорее перейдите на привязанный отпечаток сертификата (pinnedPeerCertSha256). После этой даты allowInsecure использовать будет нельзя</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRouteExcludeAddress" xml:space="preserve">
|
<data name="TbRouteExcludeAddress" xml:space="preserve">
|
||||||
<value>Адреса, исключаемые из маршрутизации</value>
|
<value>Адреса, исключаемые из маршрутизации</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
|
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
|
||||||
<value>Разделяйте запятыми (,).</value>
|
<value>Разделяйте запятыми (,)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
|
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
|
||||||
<value>Недопустимый адрес в списке исключений маршрутизации TUN: {0}</value>
|
<value>Недопустимый адрес в списке исключений маршрутизации TUN: {0}</value>
|
||||||
@@ -1798,22 +1798,22 @@
|
|||||||
<value>Включить финальную фрагментацию (Final Fragment)</value>
|
<value>Включить финальную фрагментацию (Final Fragment)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||||
<value>Разбивать конец пакетов на более мелкие фрагменты при отправке. Это может влиять на пропускную способность и задержку.</value>
|
<value>Разбивать конец пакетов на более мелкие фрагменты при отправке. Это может влиять на пропускную способность и задержку</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||||
<value>DNS через Bridge</value>
|
<value>DNS через Bridge</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||||
<value>Обнаружена небезопасная конфигурация: AllowInsecure включён, но сертификат не предоставлен. Это может привести к атаке «человек посередине» (MITM).</value>
|
<value>Обнаружена небезопасная конфигурация: AllowInsecure включён, но сертификат не предоставлен. Это может привести к атаке «человек посередине» (MITM)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||||
<value>Realm URL</value>
|
<value>Realm URL</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||||
<value>Некорректный Realm URL.</value>
|
<value>Некорректный Realm URL</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||||
<value>Введите корректный JSON заголовков HTTP-запроса.</value>
|
<value>Введите корректный JSON заголовков HTTP-запроса</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||||
<value>Формат: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
<value>Формат: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||||
@@ -1822,12 +1822,42 @@
|
|||||||
<value>Размер пакета Gecko (мин/макс)</value>
|
<value>Размер пакета Gecko (мин/макс)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbLegacyProtectTip" xml:space="preserve">
|
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||||
<value>Если включено, используется sing-box TUN; иначе — xray TUN.</value>
|
<value>Если включено, используется sing-box TUN; иначе — xray TUN</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRootCertificateProvider" xml:space="preserve">
|
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||||
<value>Поставщик корневых сертификатов</value>
|
<value>Поставщик корневых сертификатов</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||||
<value>Применяется только к загрузкам и сетевым запросам графического интерфейса v2rayN. Не влияет на проверку сертификатов ядром.</value>
|
<value>Применяется только к загрузкам и сетевым запросам графического интерфейса v2rayN. Не влияет на проверку сертификатов ядром</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
|
||||||
|
<value>Стратегия разрешения DNS при подключении к прокси</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>Добавить пользовательский outbound</value>
|
||||||
|
</data>
|
||||||
|
<data name="MsgCustomOutboundFileNotFound" xml:space="preserve">
|
||||||
|
<value>Файл пользовательского outbound {0} не найден: {1}</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbCustomOutboundTip" xml:space="preserve">
|
||||||
|
<value>Для xray/sing-box поддерживается только один outbound/endpoint</value>
|
||||||
|
</data>
|
||||||
|
<data name="LvCustomCoreType" xml:space="preserve">
|
||||||
|
<value>Ядро пользовательской конфигурации</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1498,7 +1498,7 @@
|
|||||||
<value>选择配置</value>
|
<value>选择配置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
|
<value>默认全局生效,仅在 sing-box 中内置 FakeIP 过滤。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>请至少添加一个配置</value>
|
<value>请至少添加一个配置</value>
|
||||||
@@ -1774,7 +1774,11 @@
|
|||||||
<value>有更新</value>
|
<value>有更新</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
<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>
|
||||||
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
||||||
<value>Verify Peer Cert By Name</value>
|
<value>Verify Peer Cert By Name</value>
|
||||||
@@ -1827,4 +1831,34 @@
|
|||||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||||
<value>仅用于 v2rayN 界面程序的下载及网络请求,不影响核心的证书验证。</value>
|
<value>仅用于 v2rayN 界面程序的下载及网络请求,不影响核心的证书验证。</value>
|
||||||
</data>
|
</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>
|
||||||
@@ -1417,7 +1417,7 @@
|
|||||||
<value>新增 [Anytls] 節點</value>
|
<value>新增 [Anytls] 節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRemoteDNS" xml:space="preserve">
|
<data name="TbRemoteDNS" xml:space="preserve">
|
||||||
<value>遠程 DNS</value>
|
<value>遠端 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDomesticDNS" xml:space="preserve">
|
<data name="TbDomesticDNS" xml:space="preserve">
|
||||||
<value>直連 DNS</value>
|
<value>直連 DNS</value>
|
||||||
@@ -1429,16 +1429,16 @@
|
|||||||
<value>代理目標解析策略</value>
|
<value>代理目標解析策略</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>新增常用 DNS Hosts</value>
|
<value>新增常用 DNS 主機對應</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
|
||||||
<value>阻止 SVCB 和 HTTPS 查詢</value>
|
<value>封鎖 SVCB 與 HTTPS 查詢</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts:(“網域名稱1 ip1 ip2” 一行一個)</value>
|
<value>DNS 主機對應:(每行一組「網域名稱1 ip1 ip2」)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>DNS 基礎設定</value>
|
<value>DNS 基礎設定</value>
|
||||||
@@ -1447,49 +1447,49 @@
|
|||||||
<value>DNS 進階設定</value>
|
<value>DNS 進階設定</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
|
||||||
<value>校驗相應地區域名 IP</value>
|
<value>驗證區域網域名稱的 IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
|
||||||
<value>配置後,會對相應地區域名(如 geosite:cn - geoip:cn)的返回 IP 進行校驗,僅返回期望 IP</value>
|
<value>設定後,系統會驗證區域網域名稱(例如 geosite:cn - geoip:cn)所傳回的 IP,並只傳回預期的 IP。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnable" xml:space="preserve">
|
<data name="TbCustomDNSEnable" xml:space="preserve">
|
||||||
<value>啟用自訂 DNS</value>
|
<value>啟用自訂 DNS</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
|
||||||
<value>自訂 DNS 已啟用,此頁面配置將無效</value>
|
<value>已啟用自訂 DNS,此頁面的設定將不會生效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
|
||||||
<value>開啟後將阻止 ECH 和 HTTP/3 可用性查詢</value>
|
<value>啟用後將封鎖 ECH 與 HTTP/3 可用性查詢</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
|
||||||
<value>請填寫正確的配置範本</value>
|
<value>請填寫正確的設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuFullConfigTemplate" xml:space="preserve">
|
<data name="menuFullConfigTemplate" xml:space="preserve">
|
||||||
<value>完整配置範本設定</value>
|
<value>完整設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
|
||||||
<value>啟用完整配置範本</value>
|
<value>啟用完整設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
<data name="TbRayFullConfigTemplate" xml:space="preserve">
|
||||||
<value>v2ray 完整配置範本</value>
|
<value>v2ray 完整設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>僅添加出站配置,routing.balancers 和 routing.rules.outboundTag,點擊查看文檔</value>
|
<value>僅新增出站設定、routing.balancers 與 routing.rules.outboundTag。點選以查看說明文件</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
|
||||||
<value>不添加非代理協定出站</value>
|
<value>不新增非代理協定出站</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
|
||||||
<value>設定上游代理 tag</value>
|
<value>設定上游代理標籤</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
<data name="TbSBFullConfigTemplate" xml:space="preserve">
|
||||||
<value>sing-box 完整配置範本</value>
|
<value>sing-box 完整設定範本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>僅添加出站和端點配置,點擊查看文檔</value>
|
<value>僅新增出站與端點設定。點選以查看說明文件</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
|
||||||
<value>此功能供高級用戶和有特殊需求的用戶使用。 啟用此功能後,將忽略 Core 的基礎設定,DNS 設定 ,路由設定。你需要保證系統代理的埠和流量統計等功能的配置正確,一切都由你來設定。</value>
|
<value>此功能適合進階使用者與有特殊需求的使用者。啟用後,Core 基礎設定、DNS 設定與路由設定將被忽略。請確認系統代理連接埠、流量統計及其他相關設定皆正確;所有項目都必須由您自行設定。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>開始解析和處理訂閱內容</value>
|
<value>開始解析和處理訂閱內容</value>
|
||||||
@@ -1498,10 +1498,10 @@
|
|||||||
<value>選擇節點</value>
|
<value>選擇節點</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbFakeIPTips" xml:space="preserve">
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
<value>默認全域生效,內置 FakeIP 過濾,僅在 sing-box 中生效</value>
|
<value>預設會套用至全域;內建 FakeIP 過濾僅支援 sing-box。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||||
<value>請至少添加一個節點</value>
|
<value>請至少新增一個設定檔</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
||||||
<value>策略組</value>
|
<value>策略組</value>
|
||||||
@@ -1627,7 +1627,7 @@
|
|||||||
<value>提供過期快取(Serve Stale)</value>
|
<value>提供過期快取(Serve Stale)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbParallelQuery" xml:space="preserve">
|
<data name="TbParallelQuery" xml:space="preserve">
|
||||||
<value>并行查詢</value>
|
<value>並行查詢</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbDomesticDNSTips" xml:space="preserve">
|
<data name="TbDomesticDNSTips" xml:space="preserve">
|
||||||
<value>預設僅在路由期間進行解析時調用</value>
|
<value>預設僅在路由期間進行解析時調用</value>
|
||||||
@@ -1774,7 +1774,11 @@
|
|||||||
<value>有更新</value>
|
<value>有更新</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
<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>
|
||||||
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
||||||
<value>Verify Peer Cert By Name</value>
|
<value>Verify Peer Cert By Name</value>
|
||||||
@@ -1827,4 +1831,34 @@
|
|||||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||||
<value>僅適用於 v2rayN GUI 的下載與網路請求,不影響核心的憑證驗證</value>
|
<value>僅適用於 v2rayN GUI 的下載與網路請求,不影響核心的憑證驗證</value>
|
||||||
</data>
|
</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>
|
||||||
|
<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/endpoint</value>
|
||||||
|
</data>
|
||||||
|
<data name="LvCustomCoreType" xml:space="preserve">
|
||||||
|
<value>自訂設定核心</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
{
|
{
|
||||||
"tag": "tun",
|
"tag": "tun",
|
||||||
"protocol": "tun",
|
"protocol": "tun",
|
||||||
"settings": {
|
"settings": {
|
||||||
"name": "xray_tun",
|
"name": "xray_tun",
|
||||||
"MTU": 9000,
|
"MTU": 9000,
|
||||||
"gateway": [
|
"gateway": [
|
||||||
"172.18.0.1/30",
|
"172.18.0.1/30",
|
||||||
"fdfe:dcba:9876::1/126"
|
"fdfe:dcba:9876::1/126"
|
||||||
],
|
],
|
||||||
"dns": [
|
"dns": [
|
||||||
"172.18.0.1"
|
"1.1.1.1",
|
||||||
],
|
"8.8.8.8"
|
||||||
"autoSystemRoutingTable": [
|
],
|
||||||
"0.0.0.0/0",
|
"autoSystemRoutingTable": [
|
||||||
"::/0"
|
"0.0.0.0/0",
|
||||||
],
|
"::/0"
|
||||||
"autoOutboundsInterface": "auto"
|
],
|
||||||
},
|
"autoOutboundsInterface": "auto"
|
||||||
"sniffing": {
|
},
|
||||||
"enabled": true,
|
"sniffing": {
|
||||||
"destOverride": [
|
"enabled": true,
|
||||||
"http",
|
"destOverride": [
|
||||||
"tls"
|
"http",
|
||||||
]
|
"tls"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ kill_children "$PID"
|
|||||||
echo "Terminating main process: $PID"
|
echo "Terminating main process: $PID"
|
||||||
kill -9 "$PID" 2>/dev/null || true
|
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 "============================================"
|
||||||
echo "Process $PID and all its children have been terminated"
|
echo "Process $PID and all its children have been terminated"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ kill_descendants "$PID"
|
|||||||
echo "Terminating main process: $PID"
|
echo "Terminating main process: $PID"
|
||||||
kill -9 "$PID" 2>/dev/null || true
|
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 "============================================"
|
||||||
echo "Process $PID and all its descendants have been terminated"
|
echo "Process $PID and all its descendants have been terminated"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"network": "udp",
|
"network": [
|
||||||
|
"udp"
|
||||||
|
],
|
||||||
"port": [
|
"port": [
|
||||||
135,
|
135,
|
||||||
137,
|
137,
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
<PackageReference Include="ReactiveUI">
|
<PackageReference Include="ReactiveUI">
|
||||||
<TreatAsUsed>true</TreatAsUsed>
|
<TreatAsUsed>true</TreatAsUsed>
|
||||||
</PackageReference>
|
</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="sqlite-net-e" />
|
||||||
<PackageReference Include="Repobot.SQLite.Unofficial" />
|
<PackageReference Include="Repobot.SQLite.Unofficial" />
|
||||||
<PackageReference Include="NLog" />
|
<PackageReference Include="NLog" />
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
namespace ServiceLib.Services.CoreConfig;
|
namespace ServiceLib.Services.CoreConfig;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class CoreConfigClashService(Config config)
|
public class CoreConfigClashService(Config config, bool isTunEnabled)
|
||||||
{
|
{
|
||||||
private static readonly string _tag = "CoreConfigClashService";
|
private static readonly string _tag = "CoreConfigClashService";
|
||||||
|
|
||||||
@@ -102,7 +104,7 @@ public class CoreConfigClashService(Config config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
//enable tun mode
|
//enable tun mode
|
||||||
if (config.TunModeItem.EnableTun)
|
if (isTunEnabled)
|
||||||
{
|
{
|
||||||
var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml);
|
var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml);
|
||||||
if (tun.IsNotEmpty())
|
if (tun.IsNotEmpty())
|
||||||
@@ -171,7 +173,7 @@ public class CoreConfigClashService(Config config)
|
|||||||
}
|
}
|
||||||
foreach (var item in mixinContent)
|
foreach (var item in mixinContent)
|
||||||
{
|
{
|
||||||
if (!config.TunModeItem.EnableTun && item.Key == "tun")
|
if (!isTunEnabled && item.Key == "tun")
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,13 +57,10 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
|||||||
|
|
||||||
ConvertGeo2Ruleset();
|
ConvertGeo2Ruleset();
|
||||||
|
|
||||||
ApplyOutboundBindInterface();
|
|
||||||
ApplyOutboundSendThrough();
|
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
|
|
||||||
ret.Data = ApplyFullConfigTemplate();
|
ret.Data = ApplyFinalConfigModifiers();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -107,7 +104,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
|||||||
|
|
||||||
foreach (var it in selecteds)
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -174,7 +171,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
|||||||
ApplyOutboundBindInterface();
|
ApplyOutboundBindInterface();
|
||||||
ApplyOutboundSendThrough();
|
ApplyOutboundSendThrough();
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
ret.Data = ApplyCustomOutboundReplace();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -236,7 +233,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
|||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
ret.Data = ApplyCustomOutboundReplace();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -2,54 +2,137 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigSingboxService
|
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;
|
var fullConfigTemplate = context.FullConfigTemplate;
|
||||||
if (fullConfigTemplate is not { Enabled: true })
|
if (fullConfigTemplate is not { Enabled: true })
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(_coreConfig);
|
return coreConfigContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
||||||
if (fullConfigTemplateItem.IsNullOrEmpty())
|
if (fullConfigTemplateItem.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(_coreConfig);
|
return coreConfigContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||||
if (fullConfigTemplateNode == null)
|
if (fullConfigTemplateNode == null)
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(_coreConfig);
|
return coreConfigContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process outbounds
|
// Process outbounds
|
||||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] as JsonArray ?? [];
|
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)
|
if (fullConfigTemplate.AddProxyOnly == true)
|
||||||
{
|
{
|
||||||
continue;
|
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));
|
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||||
}
|
}
|
||||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||||
|
|
||||||
// Process endpoints
|
// Process endpoints
|
||||||
if (_coreConfig.endpoints is { Count: > 0 })
|
if (fullConfigTemplateNode["endpoints"] is JsonArray { Count: > 0 } coreConfigEndpointsNode)
|
||||||
{
|
{
|
||||||
var customEndpointsNode = fullConfigTemplateNode["endpoints"] as JsonArray ?? [];
|
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));
|
customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,12 +137,12 @@ public partial class CoreConfigSingboxService
|
|||||||
// fake ip
|
// fake ip
|
||||||
if (simpleDnsItem.FakeIP == true)
|
if (simpleDnsItem.FakeIP == true)
|
||||||
{
|
{
|
||||||
|
var fakeipRange = simpleDnsItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : simpleDnsItem.FakeIPRange;
|
||||||
var fakeip = new Server4Sbox
|
var fakeip = new Server4Sbox
|
||||||
{
|
{
|
||||||
tag = Global.SingboxFakeDNSTag,
|
tag = Global.SingboxFakeDNSTag,
|
||||||
type = "fakeip",
|
type = "fakeip",
|
||||||
inet4_range = "198.18.0.0/15",
|
inet4_range = fakeipRange,
|
||||||
inet6_range = "fc00::/18",
|
|
||||||
};
|
};
|
||||||
_coreConfig.dns.servers.Add(fakeip);
|
_coreConfig.dns.servers.Add(fakeip);
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ public partial class CoreConfigSingboxService
|
|||||||
_coreConfig.dns.rules.Add(new()
|
_coreConfig.dns.rules.Add(new()
|
||||||
{
|
{
|
||||||
server = Global.SingboxDirectDNSTag,
|
server = Global.SingboxDirectDNSTag,
|
||||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial),
|
||||||
domain = context.ProtectDomainList.ToList(),
|
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));
|
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
|
||||||
fakeipFilterRule.invert = true;
|
fakeipFilterRule.invert = true;
|
||||||
|
|||||||
@@ -21,26 +21,27 @@ public partial class CoreConfigSingboxService
|
|||||||
};
|
};
|
||||||
_coreConfig.inbounds.Add(inbound);
|
_coreConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
|
var inboundConf = _config.Inbound.First();
|
||||||
inbound.listen_port = listenPort;
|
inbound.listen_port = listenPort;
|
||||||
|
|
||||||
if (_config.Inbound.First().SecondLocalPortEnabled)
|
if (inboundConf.SecondLocalPortEnabled)
|
||||||
{
|
{
|
||||||
var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true);
|
var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true);
|
||||||
_coreConfig.inbounds.Add(inbound2);
|
_coreConfig.inbounds.Add(inbound2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_config.Inbound.First().AllowLANConn)
|
if (inboundConf.AllowLANConn)
|
||||||
{
|
{
|
||||||
if (_config.Inbound.First().NewPort4LAN)
|
if (inboundConf.NewPort4LAN)
|
||||||
{
|
{
|
||||||
var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true);
|
var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true);
|
||||||
inbound3.listen = listen;
|
inbound3.listen = listen;
|
||||||
_coreConfig.inbounds.Add(inbound3);
|
_coreConfig.inbounds.Add(inbound3);
|
||||||
|
|
||||||
//auth
|
//auth
|
||||||
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
if (inboundConf.User.IsNotEmpty() && inboundConf.Pass.IsNotEmpty())
|
||||||
{
|
{
|
||||||
inbound3.users = new() { new() { username = _config.Inbound.First().User, password = _config.Inbound.First().Pass } };
|
inbound3.users = new() { new() { username = inboundConf.User, password = inboundConf.Pass } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -67,9 +68,13 @@ public partial class CoreConfigSingboxService
|
|||||||
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
||||||
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
||||||
tunInbound.stack = _config.TunModeItem.Stack;
|
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;
|
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)
|
private List<BaseServer4Sbox> BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag, bool withSelector = true)
|
||||||
{
|
{
|
||||||
var proxyOutboundList = new List<BaseServer4Sbox>();
|
var proxyOutboundList = new List<BaseServer4Sbox>();
|
||||||
if (!_node.ConfigType.IsComplexType())
|
if (!_node.ConfigType.IsGroupType())
|
||||||
{
|
{
|
||||||
var outbound = BuildProxyOutbound(baseTagName);
|
var outbound = BuildProxyOutbound(baseTagName);
|
||||||
proxyOutboundList.Add(outbound);
|
proxyOutboundList.Add(outbound);
|
||||||
@@ -35,6 +35,10 @@ public partial class CoreConfigSingboxService
|
|||||||
{
|
{
|
||||||
var outbound = BuildProxyServer();
|
var outbound = BuildProxyServer();
|
||||||
outbound.tag = baseTagName;
|
outbound.tag = baseTagName;
|
||||||
|
if (_node.ConfigType == EConfigType.Outbound)
|
||||||
|
{
|
||||||
|
context.CustomOutboundMap[outbound] = _node.IndexId;
|
||||||
|
}
|
||||||
return outbound;
|
return outbound;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +63,20 @@ public partial class CoreConfigSingboxService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
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)
|
if (_node.ConfigType == EConfigType.WireGuard)
|
||||||
{
|
{
|
||||||
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
|
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
|
||||||
@@ -417,11 +435,15 @@ public partial class CoreConfigSingboxService
|
|||||||
var tls = new Tls4Sbox()
|
var tls = new Tls4Sbox()
|
||||||
{
|
{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
record_fragment = _config.CoreBasicItem.EnableFragment ? true : null,
|
|
||||||
server_name = serverName,
|
server_name = serverName,
|
||||||
insecure = _node.GetAllowInsecure(),
|
insecure = _node.GetAllowInsecure(),
|
||||||
alpn = _node.GetAlpn(),
|
alpn = _node.GetAlpn(),
|
||||||
};
|
};
|
||||||
|
if (_config.CoreBasicItem.EnableFragment == true)
|
||||||
|
{
|
||||||
|
tls.fragment = true;
|
||||||
|
tls.record_fragment = true;
|
||||||
|
}
|
||||||
if (_node.Fingerprint.IsNotEmpty())
|
if (_node.Fingerprint.IsNotEmpty())
|
||||||
{
|
{
|
||||||
tls.utls = new Utls4Sbox()
|
tls.utls = new Utls4Sbox()
|
||||||
@@ -589,7 +611,7 @@ public partial class CoreConfigSingboxService
|
|||||||
{
|
{
|
||||||
type = "selector",
|
type = "selector",
|
||||||
tag = baseTagName,
|
tag = baseTagName,
|
||||||
outbounds = JsonUtils.DeepCopy(proxyTags),
|
outbounds = [.. proxyTags],
|
||||||
interrupt_exist_connections = false,
|
interrupt_exist_connections = false,
|
||||||
};
|
};
|
||||||
outSelector.outbounds.Insert(0, outUrltest.tag);
|
outSelector.outbounds.Insert(0, outUrltest.tag);
|
||||||
@@ -717,9 +739,9 @@ public partial class CoreConfigSingboxService
|
|||||||
return resultOutbounds;
|
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 [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -736,9 +758,14 @@ public partial class CoreConfigSingboxService
|
|||||||
{
|
{
|
||||||
clone = JsonUtils.DeepCopy(endpoint);
|
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;
|
return result;
|
||||||
|
|||||||
@@ -10,18 +10,27 @@ public partial class CoreConfigSingboxService
|
|||||||
var simpleDnsItem = context.SimpleDnsItem;
|
var simpleDnsItem = context.SimpleDnsItem;
|
||||||
|
|
||||||
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
|
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
|
||||||
var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
|
var dialDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial);
|
||||||
|
|
||||||
var rawDNSItem = context.RawDnsItem;
|
var rawDNSItem = context.RawDnsItem;
|
||||||
if (rawDNSItem is { Enabled: true })
|
if (rawDNSItem is { Enabled: true })
|
||||||
{
|
{
|
||||||
defaultDomainResolverTag = Global.SingboxLocalDNSTag;
|
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()
|
_coreConfig.route.default_domain_resolver = new()
|
||||||
{
|
{
|
||||||
server = defaultDomainResolverTag,
|
server = defaultDomainResolverTag,
|
||||||
strategy = directDnsStrategy
|
strategy = dialDnsStrategy
|
||||||
};
|
};
|
||||||
|
|
||||||
if (context.IsTunEnabled)
|
if (context.IsTunEnabled)
|
||||||
@@ -34,19 +43,44 @@ public partial class CoreConfigSingboxService
|
|||||||
_coreConfig.route.rules.AddRange(tunRules);
|
_coreConfig.route.rules.AddRange(tunRules);
|
||||||
}
|
}
|
||||||
|
|
||||||
var lstDirectExe = BuildRoutingDirectExe();
|
// Traffic addressed to the TUN interface's own addresses must never reach an
|
||||||
_coreConfig.route.rules.Add(new()
|
// 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.
|
||||||
|
//
|
||||||
|
// Match each address on its own, not the prefix it carries. On Linux sing-tun
|
||||||
|
// registers Inet4Address[0].Addr().Next() with systemd-resolved as a "~." DNS
|
||||||
|
// upstream, and every prefix offered here is a /30 or /126, so carrying the
|
||||||
|
// prefix through would cover that resolver address too and drop every system
|
||||||
|
// name lookup along with the loop.
|
||||||
|
var tunAddresses = _coreConfig.inbounds.FirstOrDefault(i => i.type == "tun")?.address;
|
||||||
|
if (tunAddresses?.Count > 0)
|
||||||
{
|
{
|
||||||
port = [53],
|
_coreConfig.route.rules.Add(new()
|
||||||
action = "hijack-dns",
|
{
|
||||||
process_path = lstDirectExe
|
ip_cidr = [.. tunAddresses.Select(ToSingleAddressPrefix)],
|
||||||
});
|
action = "reject",
|
||||||
|
method = "drop",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_coreConfig.route.rules.Add(new()
|
var lstDirectExe = BuildRoutingDirectExe();
|
||||||
|
if (lstDirectExe.Count > 0)
|
||||||
{
|
{
|
||||||
outbound = Global.DirectTag,
|
_coreConfig.route.rules.Add(new()
|
||||||
process_path = lstDirectExe
|
{
|
||||||
});
|
port = [53],
|
||||||
|
action = "hijack-dns",
|
||||||
|
process_path = lstDirectExe,
|
||||||
|
});
|
||||||
|
|
||||||
|
_coreConfig.route.rules.Add(new()
|
||||||
|
{
|
||||||
|
outbound = Global.DirectTag,
|
||||||
|
process_path = lstDirectExe,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ICMP Routing
|
// ICMP Routing
|
||||||
var icmpRouting = _config.TunModeItem.IcmpRouting ?? "";
|
var icmpRouting = _config.TunModeItem.IcmpRouting ?? "";
|
||||||
@@ -256,6 +290,14 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ToSingleAddressPrefix(string address)
|
||||||
|
{
|
||||||
|
var addr = address.Split('/').First();
|
||||||
|
return IPAddress.TryParse(addr, out var ip)
|
||||||
|
? $"{addr}/{(ip.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32)}"
|
||||||
|
: address;
|
||||||
|
}
|
||||||
|
|
||||||
private List<string> BuildRoutingDirectExe()
|
private List<string> BuildRoutingDirectExe()
|
||||||
{
|
{
|
||||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -546,7 +588,8 @@ public partial class CoreConfigSingboxService
|
|||||||
|
|
||||||
if (node == null
|
if (node == null
|
||||||
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
|
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
|
||||||
&& !node.ConfigType.IsGroupType()))
|
&& !node.ConfigType.IsGroupType()
|
||||||
|
&& node.ConfigType is not EConfigType.Outbound))
|
||||||
{
|
{
|
||||||
return Global.ProxyTag;
|
return Global.ProxyTag;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
|||||||
{
|
{
|
||||||
ApplyFinalFragment();
|
ApplyFinalFragment();
|
||||||
}
|
}
|
||||||
ApplyOutboundBindInterface();
|
|
||||||
ApplyOutboundSendThrough();
|
|
||||||
|
|
||||||
var finalRule = BuildFinalRule();
|
var finalRule = BuildFinalRule();
|
||||||
if (!string.IsNullOrEmpty(finalRule?.balancerTag))
|
if (!string.IsNullOrEmpty(finalRule?.balancerTag))
|
||||||
@@ -75,7 +73,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
|||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = ApplyFullConfigTemplate();
|
ret.Data = ApplyFinalConfigModifiers();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -119,7 +117,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
|||||||
|
|
||||||
foreach (var it in selecteds)
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -216,7 +214,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
|||||||
ApplyOutboundSendThrough();
|
ApplyOutboundSendThrough();
|
||||||
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
ret.Data = ApplyCustomOutboundReplace();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -293,7 +291,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
|||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
ret.Data = ApplyCustomOutboundReplace();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -2,24 +2,105 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigV2rayService
|
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;
|
var fullConfigTemplate = context.FullConfigTemplate;
|
||||||
if (fullConfigTemplate is not { Enabled: true })
|
if (fullConfigTemplate is not { Enabled: true })
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(_coreConfig);
|
return coreConfigContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
||||||
if (fullConfigTemplateItem.IsNullOrEmpty())
|
if (fullConfigTemplateItem.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(_coreConfig);
|
return coreConfigContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||||
if (fullConfigTemplateNode == null)
|
if (fullConfigTemplateNode == null)
|
||||||
{
|
{
|
||||||
return JsonUtils.Serialize(_coreConfig);
|
return coreConfigContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle balancer and rules modifications (for multiple load scenarios)
|
// Handle balancer and rules modifications (for multiple load scenarios)
|
||||||
@@ -74,8 +155,8 @@ public partial class CoreConfigV2rayService
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var subjectSelector = _coreConfig.observatory.subjectSelector;
|
var subjectSelector = _coreConfig.observatory.subjectSelector;
|
||||||
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
subjectSelector?.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||||
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
fullConfigTemplateNode["observatory"]?["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector?.Distinct().ToList()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,16 +169,18 @@ public partial class CoreConfigV2rayService
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var subjectSelector = _coreConfig.burstObservatory.subjectSelector;
|
var subjectSelector = _coreConfig.burstObservatory.subjectSelector;
|
||||||
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
subjectSelector?.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||||
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
fullConfigTemplateNode["burstObservatory"]?["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector?.Distinct().ToList()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var customOutboundsNode = new JsonArray();
|
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)
|
if (fullConfigTemplate.AddProxyOnly == true)
|
||||||
{
|
{
|
||||||
@@ -105,14 +188,22 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()
|
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
|
var outboundAddress = outbound["settings"]?["servers"]?.AsArray()?.FirstOrDefault()?["address"]?.ToString()
|
||||||
?? outbound.settings?.vnext?.FirstOrDefault()?.address
|
?? outbound["settings"]?["vnext"]?.AsArray()?.FirstOrDefault()?["address"]?.ToString()
|
||||||
?? string.Empty;
|
?? string.Empty;
|
||||||
if (!Utils.IsPrivateNetwork(outboundAddress))
|
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));
|
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public partial class CoreConfigV2rayService
|
|||||||
_coreConfig.routing.rules.Add(new RulesItem4Ray
|
_coreConfig.routing.rules.Add(new RulesItem4Ray
|
||||||
{
|
{
|
||||||
type = "field",
|
type = "field",
|
||||||
inboundTag = new List<string> { Global.DnsTag },
|
inboundTag = [Global.DnsTag],
|
||||||
outboundTag = Global.ProxyTag,
|
outboundTag = Global.ProxyTag,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -43,11 +43,7 @@ public partial class CoreConfigV2rayService
|
|||||||
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||||
if (outbound != null)
|
if (outbound != null)
|
||||||
{
|
{
|
||||||
outbound.settings = new()
|
FillSockoptDomainStrategy(outbound, strategy4Freedom);
|
||||||
{
|
|
||||||
domainStrategy = strategy4Freedom,
|
|
||||||
userLevel = 0
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +60,22 @@ public partial class CoreConfigV2rayService
|
|||||||
.ForEach(outbound => outbound.targetStrategy = strategy4Proxy);
|
.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);
|
FillDnsServers(dnsItem);
|
||||||
FillDnsHosts(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)
|
private void FillDnsServers(Dns4Ray dnsItem)
|
||||||
{
|
{
|
||||||
var simpleDNSItem = context.SimpleDnsItem;
|
var simpleDNSItem = context.SimpleDnsItem;
|
||||||
@@ -234,6 +273,23 @@ public partial class CoreConfigV2rayService
|
|||||||
|
|
||||||
var directDnsTagIndex = 1;
|
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(remoteDNSAddress, proxyDomainList);
|
||||||
AddDnsServers(directDNSAddress, directDomainList, true);
|
AddDnsServers(directDNSAddress, directDomainList, true);
|
||||||
AddDnsServers(remoteDNSAddress, proxyGeositeList);
|
AddDnsServers(remoteDNSAddress, proxyGeositeList);
|
||||||
@@ -384,11 +440,9 @@ public partial class CoreConfigV2rayService
|
|||||||
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||||
if (outbound != null)
|
if (outbound != null)
|
||||||
{
|
{
|
||||||
outbound.settings = new()
|
outbound.streamSettings ??= new();
|
||||||
{
|
outbound.streamSettings.sockopt ??= new();
|
||||||
domainStrategy = domainStrategy4Freedom,
|
outbound.streamSettings.sockopt.domainStrategy = domainStrategy4Freedom;
|
||||||
userLevel = 0,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,4 +525,28 @@ public partial class CoreConfigV2rayService
|
|||||||
};
|
};
|
||||||
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,37 +9,38 @@ public partial class CoreConfigV2rayService
|
|||||||
var listen = "0.0.0.0";
|
var listen = "0.0.0.0";
|
||||||
var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||||
_coreConfig.inbounds = [];
|
_coreConfig.inbounds = [];
|
||||||
var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
|
var inboundConf = _config.Inbound.First();
|
||||||
|
var inbound = BuildInbound(inboundConf, EInboundProtocol.socks, true);
|
||||||
var isUsingLocalMixedPort = _node.Address == Global.Loopback && _node.Port == listenPort;
|
var isUsingLocalMixedPort = _node.Address == Global.Loopback && _node.Port == listenPort;
|
||||||
|
|
||||||
if (!context.IsTunEnabled || !isUsingLocalMixedPort)
|
if (!context.IsTunEnabled || !isUsingLocalMixedPort)
|
||||||
{
|
{
|
||||||
_coreConfig.inbounds.Add(inbound);
|
_coreConfig.inbounds.Add(inbound);
|
||||||
|
|
||||||
if (_config.Inbound.First().SecondLocalPortEnabled)
|
if (inboundConf.SecondLocalPortEnabled)
|
||||||
{
|
{
|
||||||
var inbound2 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
|
var inbound2 = BuildInbound(inboundConf, EInboundProtocol.socks2, true);
|
||||||
_coreConfig.inbounds.Add(inbound2);
|
_coreConfig.inbounds.Add(inbound2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_config.Inbound.First().AllowLANConn)
|
if (inboundConf.AllowLANConn)
|
||||||
{
|
{
|
||||||
if (_config.Inbound.First().NewPort4LAN)
|
if (inboundConf.NewPort4LAN)
|
||||||
{
|
{
|
||||||
var inbound3 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
|
var inbound3 = BuildInbound(inboundConf, EInboundProtocol.socks3, true);
|
||||||
inbound3.listen = listen;
|
inbound3.listen = listen;
|
||||||
_coreConfig.inbounds.Add(inbound3);
|
_coreConfig.inbounds.Add(inbound3);
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
if (inboundConf.User.IsNotEmpty() && inboundConf.Pass.IsNotEmpty())
|
||||||
{
|
{
|
||||||
inbound3.settings.auth = "password";
|
inbound3.settings.auth = "password";
|
||||||
inbound3.settings.accounts =
|
inbound3.settings.accounts =
|
||||||
[
|
[
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
user = _config.Inbound.First().User,
|
user = inboundConf.User,
|
||||||
pass = _config.Inbound.First().Pass,
|
pass = inboundConf.Pass,
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
@@ -63,17 +64,26 @@ public partial class CoreConfigV2rayService
|
|||||||
new Inbounds4Ray();
|
new Inbounds4Ray();
|
||||||
tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun";
|
tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun";
|
||||||
tunInbound.settings.MTU = _config.TunModeItem.Mtu;
|
tunInbound.settings.MTU = _config.TunModeItem.Mtu;
|
||||||
if (!_config.TunModeItem.EnableIPv6Address)
|
|
||||||
|
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
|
||||||
|
tunInbound.settings.gateway = [address];
|
||||||
|
// Route both families into the tunnel regardless of EnableIPv6Address. That option only
|
||||||
|
// controls whether the interface gets an IPv6 address; leaving ::/0 out of the routing
|
||||||
|
// table makes IPv6 follow the system default route and bypass the tunnel entirely.
|
||||||
|
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0", "::/0"];
|
||||||
|
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||||
{
|
{
|
||||||
tunInbound.settings.gateway = ["172.18.0.1/30"];
|
var address6 = _config.TunModeItem.IPv6Address.NullIfEmpty() ?? Global.TunIPv6Address.First();
|
||||||
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
|
tunInbound.settings.gateway.Add(address6);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
|
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
|
||||||
if (!bindInterface.IsNullOrEmpty())
|
if (!bindInterface.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
tunInbound.settings.autoOutboundsInterface = bindInterface;
|
tunInbound.settings.autoOutboundsInterface = bindInterface;
|
||||||
}
|
}
|
||||||
tunInbound.sniffing = inbound.sniffing;
|
tunInbound.sniffing = inbound.sniffing;
|
||||||
|
// tunInbound.sniffing.routeOnly = inbound.sniffing.routeOnly;
|
||||||
tunInbound.sniffing.routeOnly = true;
|
tunInbound.sniffing.routeOnly = true;
|
||||||
|
|
||||||
if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||||
@@ -111,15 +121,8 @@ public partial class CoreConfigV2rayService
|
|||||||
includeList = IPNetwork2.Supernet(includeList.ToArray()).ToList();
|
includeList = IPNetwork2.Supernet(includeList.ToArray()).ToList();
|
||||||
includeListV6 = IPNetwork2.Supernet(includeListV6.ToArray()).ToList();
|
includeListV6 = IPNetwork2.Supernet(includeListV6.ToArray()).ToList();
|
||||||
|
|
||||||
if (_config.TunModeItem.EnableIPv6Address)
|
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString())
|
||||||
{
|
.Concat(includeListV6.Select(x => x.ToString())).ToList();
|
||||||
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString())
|
|
||||||
.Concat(includeListV6.Select(x => x.ToString())).ToList();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString()).ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_coreConfig.inbounds.Add(tunInbound);
|
_coreConfig.inbounds.Add(tunInbound);
|
||||||
@@ -152,6 +155,16 @@ public partial class CoreConfigV2rayService
|
|||||||
inbound.sniffing.destOverride = inItem.DestOverride;
|
inbound.sniffing.destOverride = inItem.DestOverride;
|
||||||
inbound.sniffing.routeOnly = inItem.RouteOnly;
|
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;
|
return inbound;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ public partial class CoreConfigV2rayService
|
|||||||
{
|
{
|
||||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
if (_node.ConfigType == EConfigType.Outbound)
|
||||||
|
{
|
||||||
|
outbound.tag = baseTagName;
|
||||||
|
context.CustomOutboundMap[outbound] = _node.IndexId;
|
||||||
|
return outbound;
|
||||||
|
}
|
||||||
FillOutbound(outbound);
|
FillOutbound(outbound);
|
||||||
outbound.tag = baseTagName;
|
outbound.tag = baseTagName;
|
||||||
return outbound;
|
return outbound;
|
||||||
@@ -404,7 +410,6 @@ public partial class CoreConfigV2rayService
|
|||||||
|
|
||||||
TlsSettings4Ray tlsSettings = new()
|
TlsSettings4Ray tlsSettings = new()
|
||||||
{
|
{
|
||||||
allowInsecure = _node.GetAllowInsecure(),
|
|
||||||
alpn = _node.GetAlpn(),
|
alpn = _node.GetAlpn(),
|
||||||
fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint,
|
fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint,
|
||||||
echConfigList = _node.EchConfigList.NullIfEmpty(),
|
echConfigList = _node.EchConfigList.NullIfEmpty(),
|
||||||
@@ -438,12 +443,10 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
tlsSettings.certificates = certsettings;
|
tlsSettings.certificates = certsettings;
|
||||||
tlsSettings.disableSystemRoot = true;
|
tlsSettings.disableSystemRoot = true;
|
||||||
tlsSettings.allowInsecure = false;
|
|
||||||
}
|
}
|
||||||
else if (!_node.CertSha.IsNullOrEmpty())
|
else if (!_node.CertSha.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
tlsSettings.pinnedPeerCertSha256 = _node.CertSha;
|
tlsSettings.pinnedPeerCertSha256 = _node.CertSha;
|
||||||
tlsSettings.allowInsecure = false;
|
|
||||||
}
|
}
|
||||||
streamSettings.tlsSettings = tlsSettings;
|
streamSettings.tlsSettings = tlsSettings;
|
||||||
}
|
}
|
||||||
@@ -509,6 +512,7 @@ public partial class CoreConfigV2rayService
|
|||||||
settings = new MaskSettings4Ray { value = kcpSeed },
|
settings = new MaskSettings4Ray { value = kcpSeed },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
kcpFinalmask.udp?.Reverse();
|
||||||
streamSettings.kcpSettings = kcpSettings;
|
streamSettings.kcpSettings = kcpSettings;
|
||||||
streamSettings.finalmask = kcpFinalmask;
|
streamSettings.finalmask = kcpFinalmask;
|
||||||
break;
|
break;
|
||||||
@@ -663,6 +667,7 @@ public partial class CoreConfigV2rayService
|
|||||||
version = 2,
|
version = 2,
|
||||||
auth = _node.Password,
|
auth = _node.Password,
|
||||||
};
|
};
|
||||||
|
hy2Finalmask.udp?.Reverse();
|
||||||
streamSettings.finalmask = hy2Finalmask;
|
streamSettings.finalmask = hy2Finalmask;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -788,12 +793,12 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
else if (chainStartNodes.Count > 1)
|
else if (chainStartNodes.Count > 1)
|
||||||
{
|
{
|
||||||
var existedChainNodes = JsonUtils.DeepCopy(resultOutbounds);
|
var existedChainNodes = CloneOutbounds(resultOutbounds);
|
||||||
resultOutbounds.Clear();
|
resultOutbounds.Clear();
|
||||||
var j = 0;
|
var j = 0;
|
||||||
foreach (var chainStartNode in chainStartNodes)
|
foreach (var chainStartNode in chainStartNodes)
|
||||||
{
|
{
|
||||||
var existedChainNodesClone = JsonUtils.DeepCopy(existedChainNodes);
|
var existedChainNodesClone = CloneOutbounds(existedChainNodes);
|
||||||
foreach (var existedChainNode in existedChainNodesClone)
|
foreach (var existedChainNode in existedChainNodesClone)
|
||||||
{
|
{
|
||||||
var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}";
|
var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}";
|
||||||
@@ -865,21 +870,10 @@ public partial class CoreConfigV2rayService
|
|||||||
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
|
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
|
var fragmentMask = BuildFragmentsMasks();
|
||||||
|
|
||||||
foreach (var outbound in actOutboundWithTlsList)
|
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();
|
var finalMaskJsonObj = JsonUtils.ParseJson(JsonUtils.Serialize(outbound.streamSettings?.finalmask)) as JsonObject ?? new JsonObject();
|
||||||
// tcp fragment
|
// tcp fragment
|
||||||
var tcpFinalmaskList = finalMaskJsonObj["tcp"] as JsonArray ?? [];
|
var tcpFinalmaskList = finalMaskJsonObj["tcp"] as JsonArray ?? [];
|
||||||
@@ -888,13 +882,6 @@ public partial class CoreConfigV2rayService
|
|||||||
tcpFinalmaskList.Add(JsonUtils.SerializeToNode(fragmentMask));
|
tcpFinalmaskList.Add(JsonUtils.SerializeToNode(fragmentMask));
|
||||||
finalMaskJsonObj["tcp"] = tcpFinalmaskList;
|
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
|
// write back
|
||||||
outbound.streamSettings.finalmask = finalMaskJsonObj;
|
outbound.streamSettings.finalmask = finalMaskJsonObj;
|
||||||
}
|
}
|
||||||
@@ -902,7 +889,7 @@ public partial class CoreConfigV2rayService
|
|||||||
|
|
||||||
private void ApplyFinalFragment()
|
private void ApplyFinalFragment()
|
||||||
{
|
{
|
||||||
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
|
var fragmentMask = BuildFragmentsMasks();
|
||||||
var actOutboundList = _coreConfig.outbounds.Where(n => n.tag.StartsWith(Global.ProxyTag)).ToList();
|
var actOutboundList = _coreConfig.outbounds.Where(n => n.tag.StartsWith(Global.ProxyTag)).ToList();
|
||||||
|
|
||||||
var fragmentFreedom = new Outbounds4Ray()
|
var fragmentFreedom = new Outbounds4Ray()
|
||||||
@@ -914,9 +901,8 @@ public partial class CoreConfigV2rayService
|
|||||||
finalmask = new Finalmask4Ray
|
finalmask = new Finalmask4Ray
|
||||||
{
|
{
|
||||||
tcp = [fragmentMask],
|
tcp = [fragmentMask],
|
||||||
udp = [noiseMask],
|
},
|
||||||
}
|
},
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var outbound in actOutboundList)
|
foreach (var outbound in actOutboundList)
|
||||||
@@ -934,13 +920,22 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private (Mask4Ray tcpFragment, Mask4Ray udpNoise) BuildFragmentsMasks()
|
private Mask4Ray BuildFragmentsMasks()
|
||||||
{
|
{
|
||||||
var configPackets = _config.Fragment4RayItem?.Packets.NullIfEmpty() ?? "tlshello";
|
var configPackets = _config.Fragment4RayItem?.Packets.NullIfEmpty() ?? "tlshello";
|
||||||
var configLength = _config.Fragment4RayItem?.Length.NullIfEmpty() ?? "50-100";
|
var configLengths = _config.Fragment4RayItem?.Lengths ?? [];
|
||||||
var configDelay = _config.Fragment4RayItem?.Interval.NullIfEmpty() ?? "10-20";
|
var configDelays = _config.Fragment4RayItem?.Delays ?? [];
|
||||||
var configMaxSplit = _config.Fragment4RayItem?.MaxSplit.NullIfEmpty() ?? "0";
|
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 maxSplit = 0;
|
||||||
var parts = configMaxSplit.Split('-');
|
var parts = configMaxSplit.Split('-');
|
||||||
if (parts.Length > 0 && int.TryParse(parts[0], out var ms))
|
if (parts.Length > 0 && int.TryParse(parts[0], out var ms))
|
||||||
@@ -954,21 +949,30 @@ public partial class CoreConfigV2rayService
|
|||||||
settings = new MaskSettings4Ray
|
settings = new MaskSettings4Ray
|
||||||
{
|
{
|
||||||
packets = configPackets,
|
packets = configPackets,
|
||||||
length = configLength,
|
lengths = configLengths,
|
||||||
delay = configDelay,
|
delays = configDelays,
|
||||||
maxSplit = maxSplit,
|
maxSplit = maxSplit,
|
||||||
}
|
// For legacy xray compatibility, remove this in the future
|
||||||
};
|
length = configLengths.FirstOrDefault(),
|
||||||
var noiseMask = new Mask4Ray
|
delay = configDelays.FirstOrDefault(),
|
||||||
{
|
},
|
||||||
type = "noise",
|
|
||||||
settings = new MaskSettings4Ray
|
|
||||||
{
|
|
||||||
length = "10-20",
|
|
||||||
delay = "10-16",
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,20 @@ public partial class CoreConfigV2rayService
|
|||||||
_coreConfig.routing.rules.AddRange(tunRules);
|
_coreConfig.routing.rules.AddRange(tunRules);
|
||||||
}
|
}
|
||||||
var lstDirectExe = BuildRoutingDirectExe();
|
var lstDirectExe = BuildRoutingDirectExe();
|
||||||
_coreConfig.routing.rules.Add(new()
|
if (lstDirectExe.Count > 0)
|
||||||
{
|
{
|
||||||
port = "53",
|
_coreConfig.routing.rules.Add(new()
|
||||||
process = lstDirectExe,
|
{
|
||||||
outboundTag = Global.DnsOutboundTag,
|
port = "53",
|
||||||
});
|
process = lstDirectExe,
|
||||||
_coreConfig.routing.rules.Add(new()
|
outboundTag = Global.DnsOutboundTag,
|
||||||
{
|
});
|
||||||
process = lstDirectExe,
|
_coreConfig.routing.rules.Add(new()
|
||||||
outboundTag = Global.DirectTag,
|
{
|
||||||
});
|
process = lstDirectExe,
|
||||||
|
outboundTag = Global.DirectTag,
|
||||||
|
});
|
||||||
|
}
|
||||||
_coreConfig.routing.rules.Add(new()
|
_coreConfig.routing.rules.Add(new()
|
||||||
{
|
{
|
||||||
inboundTag = ["tun"],
|
inboundTag = ["tun"],
|
||||||
@@ -185,7 +188,8 @@ public partial class CoreConfigV2rayService
|
|||||||
|
|
||||||
if (node == null
|
if (node == null
|
||||||
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
|
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
|
||||||
&& !node.ConfigType.IsGroupType()))
|
&& !node.ConfigType.IsGroupType()
|
||||||
|
&& node.ConfigType is not EConfigType.Outbound))
|
||||||
{
|
{
|
||||||
return Global.ProxyTag;
|
return Global.ProxyTag;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,45 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class AddGroupServerViewModel : MyReactiveObject, ICloseable
|
public partial class AddGroupServerViewModel : MyReactiveObject, ICloseable
|
||||||
{
|
{
|
||||||
public event EventHandler? RequestClose;
|
public event EventHandler? RequestClose;
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ProfileItem SelectedSource { get; set; }
|
public partial ProfileItem SelectedSource { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ProfileItem SelectedChild { get; set; }
|
public partial ProfileItem SelectedChild { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public IList<ProfileItem> SelectedChildren { get; set; }
|
public partial IList<ProfileItem> SelectedChildren { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string? CoreType { get; set; }
|
public partial string? CoreType { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string? PolicyGroupType { get; set; }
|
public partial string? PolicyGroupType { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public SubItem? SelectedSubItem { get; set; }
|
public partial SubItem? SelectedSubItem { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string? Filter { get; set; }
|
public partial string? Filter { get; set; }
|
||||||
|
|
||||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
public BulkObservableCollection<SubItem> SubItems { get; } = [];
|
||||||
|
|
||||||
public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
public BulkObservableCollection<ProfileItem> ChildItemsObs { get; } = [];
|
||||||
|
|
||||||
public IObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
public BulkObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = [];
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> AddCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> RemoveCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> RemoveCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> MoveTopCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> MoveUpCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> MoveDownCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> MoveBottomCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||||
|
|
||||||
public AddGroupServerViewModel(ProfileItem profileItem)
|
public AddGroupServerViewModel(ProfileItem profileItem)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
public partial class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||||
{
|
{
|
||||||
public event EventHandler? RequestClose;
|
public event EventHandler? RequestClose;
|
||||||
public Interaction<Unit, string?> BrowseConfigFileInteraction { get; } = new();
|
|
||||||
|
public Interaction<RxVoid, string?> BrowseConfigFileInteraction { get; } = new();
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ProfileItem SelectedSource { get; set; }
|
public partial ProfileItem SelectedSource { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string? CoreType { get; set; }
|
public partial string? CoreType { get; set; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> BrowseServerCmd { get; }
|
[Reactive]
|
||||||
public ReactiveCommand<Unit, Unit> EditServerCmd { get; }
|
public partial bool IsSingboxEndpoint { get; set; }
|
||||||
public ReactiveCommand<Unit, Unit> SaveServerCmd { get; }
|
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> BrowseServerCmd { get; }
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> EditServerCmd { get; }
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> SaveServerCmd { get; }
|
||||||
public bool IsModified { get; set; }
|
public bool IsModified { get; set; }
|
||||||
|
|
||||||
public AddServer2ViewModel(ProfileItem profileItem)
|
public AddServer2ViewModel(ProfileItem profileItem)
|
||||||
@@ -22,7 +26,7 @@ public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
var fileName = await BrowseConfigFileInteraction.Handle(Unit.Default);
|
var fileName = await BrowseConfigFileInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (fileName.IsNullOrEmpty())
|
if (fileName.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -39,7 +43,10 @@ public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
|||||||
});
|
});
|
||||||
|
|
||||||
SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem);
|
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()
|
private async Task SaveServerAsync()
|
||||||
@@ -57,6 +64,10 @@ public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : Enum.Parse<ECoreType>(CoreType);
|
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)
|
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
|
||||||
{
|
{
|
||||||
@@ -79,7 +90,8 @@ public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
|||||||
var item = await AppManager.Instance.GetProfileItem(SelectedSource.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(SelectedSource.IndexId);
|
||||||
item ??= SelectedSource;
|
item ??= SelectedSource;
|
||||||
item.Address = fileName;
|
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);
|
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer);
|
||||||
if (item.IndexId.IsNotEmpty())
|
if (item.IndexId.IsNotEmpty())
|
||||||
|
|||||||
@@ -1,131 +1,131 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class AddServerViewModel : MyReactiveObject, ICloseable
|
public partial class AddServerViewModel : MyReactiveObject, ICloseable
|
||||||
{
|
{
|
||||||
public event EventHandler? RequestClose;
|
public event EventHandler? RequestClose;
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ProfileItem SelectedSource { get; set; }
|
public partial ProfileItem SelectedSource { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string? CoreType { get; set; }
|
public partial string? CoreType { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool AllowInsecure { get; set; }
|
public partial bool AllowInsecure { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool MuxEnabled { get; set; }
|
public partial bool MuxEnabled { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string Cert { get; set; }
|
public partial string Cert { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string CertTip { get; set; }
|
public partial string CertTip { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string CertSha { get; set; }
|
public partial string CertSha { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string SalamanderPass { get; set; }
|
public partial string SalamanderPass { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int AlterId { get; set; }
|
public partial int AlterId { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string Ports { get; set; }
|
public partial string Ports { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int? UpMbps { get; set; }
|
public partial int? UpMbps { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int? DownMbps { get; set; }
|
public partial int? DownMbps { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string HopInterval { get; set; }
|
public partial string HopInterval { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string Flow { get; set; }
|
public partial string Flow { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string VmessSecurity { get; set; }
|
public partial string VmessSecurity { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string VlessEncryption { get; set; }
|
public partial string VlessEncryption { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string SsMethod { get; set; }
|
public partial string SsMethod { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string WgPublicKey { get; set; }
|
public partial string WgPublicKey { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string WgPresharedKey { get; set; }
|
public partial string WgPresharedKey { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string WgInterfaceAddress { get; set; }
|
public partial string WgInterfaceAddress { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string WgReserved { get; set; }
|
public partial string WgReserved { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int WgMtu { get; set; }
|
public partial int WgMtu { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool Uot { get; set; }
|
public partial bool Uot { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string CongestionControl { get; set; }
|
public partial string CongestionControl { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int? InsecureConcurrency { get; set; }
|
public partial int? InsecureConcurrency { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool NaiveQuic { get; set; }
|
public partial bool NaiveQuic { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string HttpHeadersJson { get; set; }
|
public partial string HttpHeadersJson { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string Hy2RealmUrl { get; set; }
|
public partial string Hy2RealmUrl { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int GeckoMinPacketSize { get; set; }
|
public partial int GeckoMinPacketSize { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int GeckoMaxPacketSize { get; set; }
|
public partial int GeckoMaxPacketSize { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string RawHeaderType { get; set; }
|
public partial string RawHeaderType { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string Host { get; set; }
|
public partial string Host { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string Path { get; set; }
|
public partial string Path { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string XhttpMode { get; set; }
|
public partial string XhttpMode { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string XhttpExtra { get; set; }
|
public partial string XhttpExtra { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string GrpcAuthority { get; set; }
|
public partial string GrpcAuthority { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string GrpcServiceName { get; set; }
|
public partial string GrpcServiceName { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string GrpcMode { get; set; }
|
public partial string GrpcMode { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string KcpHeaderType { get; set; }
|
public partial string KcpHeaderType { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string KcpSeed { get; set; }
|
public partial string KcpSeed { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int? KcpMtu { get; set; }
|
public partial int? KcpMtu { get; set; }
|
||||||
|
|
||||||
public string TransportHeaderType
|
public string TransportHeaderType
|
||||||
{
|
{
|
||||||
@@ -239,9 +239,9 @@ public class AddServerViewModel : MyReactiveObject, ICloseable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> FetchCertCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> FetchCertChainCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||||
|
|
||||||
public AddServerViewModel(ProfileItem profileItem)
|
public AddServerViewModel(ProfileItem profileItem)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class BackupAndRestoreViewModel : MyReactiveObject
|
public partial class BackupAndRestoreViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private readonly string _guiConfigs = "guiConfigs";
|
private readonly string _guiConfigs = "guiConfigs";
|
||||||
private static string BackupFileName => $"backup_{DateTime.Now:yyyyMMddHHmmss}.zip";
|
private static string BackupFileName => $"backup_{DateTime.Now:yyyyMMddHHmmss}.zip";
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> RemoteBackupCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> RemoteBackupCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> RemoteRestoreCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> RemoteRestoreCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> WebDavCheckCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> WebDavCheckCmd { get; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public WebDavItem SelectedSource { get; set; }
|
public partial WebDavItem SelectedSource { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string OperationMsg { get; set; } = string.Empty;
|
public partial string OperationMsg { get; set; } = string.Empty;
|
||||||
|
|
||||||
public BackupAndRestoreViewModel()
|
public BackupAndRestoreViewModel()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class CheckUpdateViewModel : MyReactiveObject
|
public partial class CheckUpdateViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private const string _geo = "GeoFiles";
|
private const string _geo = "GeoFiles";
|
||||||
private readonly ECoreType _v2rayN = ECoreType.v2rayN;
|
private readonly ECoreType _v2rayN = ECoreType.v2rayN;
|
||||||
private List<CheckUpdateModel> _lstUpdated = [];
|
private List<CheckUpdateModel> _lstUpdated = [];
|
||||||
private static readonly string _tag = "CheckUpdateViewModel";
|
private static readonly string _tag = "CheckUpdateViewModel";
|
||||||
|
|
||||||
public EventChannel<Unit> ReloadRequested { get; } = new();
|
public EventChannel<RxVoid> ReloadRequested { get; } = new();
|
||||||
|
|
||||||
public IObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = new ObservableCollectionExtended<CheckUpdateModel>();
|
public BulkObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = [];
|
||||||
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> CheckUpdateCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> CheckOnlyCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> CheckOnlyCmd { get; }
|
||||||
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
|
[Reactive] public partial bool EnableCheckPreReleaseUpdate { get; set; }
|
||||||
|
|
||||||
public CheckUpdateViewModel()
|
public CheckUpdateViewModel()
|
||||||
{
|
{
|
||||||
@@ -288,10 +288,9 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task UpdateFinishedSub(bool blReload)
|
private async Task UpdateFinishedSub(bool blReload)
|
||||||
{
|
{
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(blReload, (scheduler, blReload) =>
|
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||||
{
|
{
|
||||||
_ = UpdateFinishedResult(blReload);
|
_ = UpdateFinishedResult(blReload);
|
||||||
return Disposable.Empty;
|
|
||||||
});
|
});
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -404,10 +403,9 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
Remarks = msg,
|
Remarks = msg,
|
||||||
};
|
};
|
||||||
|
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(item, (scheduler, model) =>
|
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||||
{
|
{
|
||||||
_ = UpdateViewResult(model);
|
_ = UpdateViewResult(item);
|
||||||
return Disposable.Empty;
|
|
||||||
});
|
});
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
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]
|
[Reactive]
|
||||||
public ClashConnectionModel SelectedSource { get; set; }
|
public partial ClashConnectionModel SelectedSource { get; set; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> ConnectionCloseCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ConnectionCloseAllCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseAllCmd { get; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string HostFilter { get; set; }
|
public partial string HostFilter { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool AutoRefresh { get; set; }
|
public partial bool AutoRefresh { get; set; }
|
||||||
|
|
||||||
public ClashConnectionsViewModel()
|
public ClashConnectionsViewModel()
|
||||||
{
|
{
|
||||||
@@ -55,10 +55,9 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(ret?.connections, (scheduler, model) =>
|
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||||
{
|
{
|
||||||
_ = RefreshConnections(model);
|
_ = RefreshConnections(ret?.connections);
|
||||||
return Disposable.Empty;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,36 @@
|
|||||||
using System.Reactive.Concurrency;
|
|
||||||
using static ServiceLib.Models.Dto.ClashProviders;
|
using static ServiceLib.Models.Dto.ClashProviders;
|
||||||
using static ServiceLib.Models.Dto.ClashProxies;
|
using static ServiceLib.Models.Dto.ClashProxies;
|
||||||
|
|
||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class ClashProxiesViewModel : MyReactiveObject
|
public partial class ClashProxiesViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private Dictionary<string, ProxiesItem>? _proxies;
|
private Dictionary<string, ProxiesItem>? _proxies;
|
||||||
private Dictionary<string, ProvidersItem>? _providers;
|
private Dictionary<string, ProvidersItem>? _providers;
|
||||||
private readonly int _delayTimeout = 99999999;
|
private readonly int _delayTimeout = 99999999;
|
||||||
|
|
||||||
public IObservableCollection<ClashProxyModel> ProxyGroups { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
public BulkObservableCollection<ClashProxyModel> ProxyGroups { get; } = [];
|
||||||
public IObservableCollection<ClashProxyModel> ProxyDetails { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
public BulkObservableCollection<ClashProxyModel> ProxyDetails { get; } = [];
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ClashProxyModel SelectedGroup { get; set; }
|
public partial ClashProxyModel SelectedGroup { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ClashProxyModel SelectedDetail { get; set; }
|
public partial ClashProxyModel SelectedDetail { get; set; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> ProxiesReloadCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ProxiesReloadCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ProxiesDelayTestCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ProxiesDelayTestCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ProxiesDelayTestPartCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ProxiesDelayTestPartCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ProxiesSelectActivityCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ProxiesSelectActivityCmd { get; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int RuleModeSelected { get; set; }
|
public partial int RuleModeSelected { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public int SortingSelected { get; set; }
|
public partial int SortingSelected { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool AutoRefresh { get; set; }
|
public partial bool AutoRefresh { get; set; }
|
||||||
|
|
||||||
public ClashProxiesViewModel()
|
public ClashProxiesViewModel()
|
||||||
{
|
{
|
||||||
@@ -379,10 +378,9 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
var model = new SpeedTestResult() { IndexId = item.Name, Delay = result };
|
var model = new SpeedTestResult() { IndexId = item.Name, Delay = result };
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(model, (scheduler, model) =>
|
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||||
{
|
{
|
||||||
_ = ProxiesDelayTestResult(model);
|
_ = ProxiesDelayTestResult(model);
|
||||||
return Disposable.Empty;
|
|
||||||
});
|
});
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,41 +1,44 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class DNSSettingViewModel : MyReactiveObject, ICloseable
|
public partial class DNSSettingViewModel : MyReactiveObject, ICloseable
|
||||||
{
|
{
|
||||||
public event EventHandler? RequestClose;
|
public event EventHandler? RequestClose;
|
||||||
|
|
||||||
[Reactive] public bool? UseSystemHosts { get; set; }
|
[Reactive] public partial bool UseSystemHosts { get; set; }
|
||||||
[Reactive] public bool? AddCommonHosts { get; set; }
|
[Reactive] public partial bool AddCommonHosts { get; set; }
|
||||||
[Reactive] public bool? FakeIP { get; set; }
|
[Reactive] public partial bool FakeIP { get; set; }
|
||||||
[Reactive] public bool? BlockBindingQuery { get; set; }
|
[Reactive] public partial string FakeIPRange { get; set; }
|
||||||
[Reactive] public string? DirectDNS { get; set; }
|
[Reactive] public partial bool BlockBindingQuery { get; set; }
|
||||||
[Reactive] public string? RemoteDNS { get; set; }
|
[Reactive] public partial string DirectDNS { get; set; }
|
||||||
[Reactive] public string? BootstrapDNS { get; set; }
|
[Reactive] public partial string RemoteDNS { get; set; }
|
||||||
[Reactive] public string? Strategy4Freedom { get; set; }
|
[Reactive] public partial string BootstrapDNS { get; set; }
|
||||||
[Reactive] public string? Strategy4Proxy { get; set; }
|
[Reactive] public partial string Strategy4Freedom { get; set; }
|
||||||
[Reactive] public string? Hosts { get; set; }
|
[Reactive] public partial string Strategy4Proxy { get; set; }
|
||||||
[Reactive] public string? DirectExpectedIPs { get; set; }
|
[Reactive] public partial string Strategy4ProxyDial { get; set; }
|
||||||
[Reactive] public bool? ParallelQuery { get; set; }
|
[Reactive] public partial string Hosts { get; set; }
|
||||||
[Reactive] public bool? ServeStale { 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 bool UseSystemHostsCompatible { get; set; }
|
[Reactive] public partial bool UseSystemHostsCompatible { get; set; }
|
||||||
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; } = string.Empty;
|
[Reactive] public partial string DomainStrategy4FreedomCompatible { get; set; } = string.Empty;
|
||||||
[Reactive] public string DomainDNSAddressCompatible { get; set; } = string.Empty;
|
[Reactive] public partial string DomainDNSAddressCompatible { get; set; } = string.Empty;
|
||||||
[Reactive] public string NormalDNSCompatible { get; set; } = string.Empty;
|
[Reactive] public partial string NormalDNSCompatible { get; set; } = string.Empty;
|
||||||
[Reactive] public string TunDNSCompatible { get; set; } = string.Empty;
|
[Reactive] public partial string TunDNSCompatible { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Reactive] public string DomainStrategy4Freedom2Compatible { get; set; } = string.Empty;
|
[Reactive] public partial string DomainStrategy4Freedom2Compatible { get; set; } = string.Empty;
|
||||||
[Reactive] public string DomainDNSAddress2Compatible { get; set; } = string.Empty;
|
[Reactive] public partial string DomainDNSAddress2Compatible { get; set; } = string.Empty;
|
||||||
[Reactive] public string NormalDNS2Compatible { get; set; } = string.Empty;
|
[Reactive] public partial string NormalDNS2Compatible { get; set; } = string.Empty;
|
||||||
[Reactive] public string TunDNS2Compatible { get; set; } = string.Empty;
|
[Reactive] public partial string TunDNS2Compatible { get; set; } = string.Empty;
|
||||||
[Reactive] public bool RayCustomDNSEnableCompatible { get; set; }
|
[Reactive] public partial bool RayCustomDNSEnableCompatible { get; set; }
|
||||||
[Reactive] public bool SBCustomDNSEnableCompatible { get; set; }
|
[Reactive] public partial bool SBCustomDNSEnableCompatible { get; set; }
|
||||||
|
|
||||||
[ObservableAsProperty] public bool IsSimpleDNSEnabled { get; }
|
public bool IsSimpleDNSEnabled => !(RayCustomDNSEnableCompatible && SBCustomDNSEnableCompatible);
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4V2rayCompatibleCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ImportDefConfig4V2rayCompatibleCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4SingboxCompatibleCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ImportDefConfig4SingboxCompatibleCmd { get; }
|
||||||
|
|
||||||
public DNSSettingViewModel()
|
public DNSSettingViewModel()
|
||||||
{
|
{
|
||||||
@@ -57,8 +60,7 @@ public class DNSSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.WhenAnyValue(x => x.RayCustomDNSEnableCompatible, x => x.SBCustomDNSEnableCompatible)
|
this.WhenAnyValue(x => x.RayCustomDNSEnableCompatible, x => x.SBCustomDNSEnableCompatible)
|
||||||
.Select(x => x is not { Item1: true, Item2: true })
|
.Subscribe(_ => this.RaisePropertyChanged(nameof(IsSimpleDNSEnabled)));
|
||||||
.ToPropertyEx(this, x => x.IsSimpleDNSEnabled);
|
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
}
|
}
|
||||||
@@ -67,20 +69,22 @@ public class DNSSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
{
|
{
|
||||||
_config = AppManager.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
var item = _config.SimpleDNSItem;
|
var item = _config.SimpleDNSItem;
|
||||||
UseSystemHosts = item.UseSystemHosts;
|
UseSystemHosts = item.UseSystemHosts ?? false;
|
||||||
AddCommonHosts = item.AddCommonHosts;
|
AddCommonHosts = item.AddCommonHosts ?? false;
|
||||||
FakeIP = item.FakeIP;
|
FakeIP = item.FakeIP ?? false;
|
||||||
BlockBindingQuery = item.BlockBindingQuery;
|
FakeIPRange = item.FakeIPRange ?? string.Empty;
|
||||||
DirectDNS = item.DirectDNS;
|
BlockBindingQuery = item.BlockBindingQuery ?? false;
|
||||||
RemoteDNS = item.RemoteDNS;
|
DirectDNS = item.DirectDNS ?? string.Empty;
|
||||||
BootstrapDNS = item.BootstrapDNS;
|
RemoteDNS = item.RemoteDNS ?? string.Empty;
|
||||||
Strategy4Freedom = item.Strategy4Freedom;
|
BootstrapDNS = item.BootstrapDNS ?? string.Empty;
|
||||||
Strategy4Proxy = item.Strategy4Proxy;
|
Strategy4Freedom = item.Strategy4Freedom ?? string.Empty;
|
||||||
Hosts = item.Hosts;
|
Strategy4Proxy = item.Strategy4Proxy ?? string.Empty;
|
||||||
DirectExpectedIPs = item.DirectExpectedIPs;
|
Strategy4ProxyDial = item.Strategy4ProxyDial ?? string.Empty;
|
||||||
ParallelQuery = item.ParallelQuery;
|
Hosts = item.Hosts ?? string.Empty;
|
||||||
ServeStale = item.ServeStale;
|
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);
|
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||||
RayCustomDNSEnableCompatible = item1.Enabled;
|
RayCustomDNSEnableCompatible = item1.Enabled;
|
||||||
UseSystemHostsCompatible = item1.UseSystemHosts;
|
UseSystemHostsCompatible = item1.UseSystemHosts;
|
||||||
@@ -102,17 +106,19 @@ public class DNSSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
_config.SimpleDNSItem.UseSystemHosts = UseSystemHosts;
|
_config.SimpleDNSItem.UseSystemHosts = UseSystemHosts;
|
||||||
_config.SimpleDNSItem.AddCommonHosts = AddCommonHosts;
|
_config.SimpleDNSItem.AddCommonHosts = AddCommonHosts;
|
||||||
_config.SimpleDNSItem.FakeIP = FakeIP;
|
_config.SimpleDNSItem.FakeIP = FakeIP;
|
||||||
|
_config.SimpleDNSItem.FakeIPRange = FakeIPRange;
|
||||||
_config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery;
|
_config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery;
|
||||||
_config.SimpleDNSItem.DirectDNS = DirectDNS;
|
_config.SimpleDNSItem.DirectDNS = DirectDNS;
|
||||||
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
|
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
|
||||||
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
|
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
|
||||||
_config.SimpleDNSItem.Strategy4Freedom = Strategy4Freedom;
|
_config.SimpleDNSItem.Strategy4Freedom = Strategy4Freedom;
|
||||||
_config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy;
|
_config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy;
|
||||||
|
_config.SimpleDNSItem.Strategy4ProxyDial = Strategy4ProxyDial;
|
||||||
_config.SimpleDNSItem.Hosts = Hosts;
|
_config.SimpleDNSItem.Hosts = Hosts;
|
||||||
_config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs;
|
_config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs;
|
||||||
_config.SimpleDNSItem.ParallelQuery = ParallelQuery;
|
_config.SimpleDNSItem.ParallelQuery = ParallelQuery;
|
||||||
_config.SimpleDNSItem.ServeStale = ServeStale;
|
_config.SimpleDNSItem.ServeStale = ServeStale;
|
||||||
|
_config.SimpleDNSItem.EnableHappyEyeballs = EnableHappyEyeballs;
|
||||||
if (NormalDNSCompatible.IsNotEmpty())
|
if (NormalDNSCompatible.IsNotEmpty())
|
||||||
{
|
{
|
||||||
var obj = JsonUtils.ParseJson(NormalDNSCompatible);
|
var obj = JsonUtils.ParseJson(NormalDNSCompatible);
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class FullConfigTemplateViewModel : MyReactiveObject, ICloseable
|
public partial class FullConfigTemplateViewModel : MyReactiveObject, ICloseable
|
||||||
{
|
{
|
||||||
public event EventHandler? RequestClose;
|
public event EventHandler? RequestClose;
|
||||||
|
|
||||||
#region Reactive
|
#region Reactive
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool EnableFullConfigTemplate4Ray { get; set; }
|
public partial bool EnableFullConfigTemplate4Ray { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool EnableFullConfigTemplate4Singbox { get; set; }
|
public partial bool EnableFullConfigTemplate4Singbox { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string FullConfigTemplate4Ray { get; set; } = string.Empty;
|
public partial string FullConfigTemplate4Ray { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string FullTunConfigTemplate4Ray { get; set; } = string.Empty;
|
public partial string FullTunConfigTemplate4Ray { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string FullConfigTemplate4Singbox { get; set; } = string.Empty;
|
public partial string FullConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string FullTunConfigTemplate4Singbox { get; set; } = string.Empty;
|
public partial string FullTunConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool AddProxyOnly4Ray { get; set; }
|
public partial bool AddProxyOnly4Ray { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool AddProxyOnly4Singbox { get; set; }
|
public partial bool AddProxyOnly4Singbox { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string ProxyDetour4Ray { get; set; } = string.Empty;
|
public partial string ProxyDetour4Ray { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string ProxyDetour4Singbox { get; set; } = string.Empty;
|
public partial string ProxyDetour4Singbox { get; set; } = string.Empty;
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||||
|
|
||||||
#endregion Reactive
|
#endregion Reactive
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
private readonly List<KeyEventItem> _globalHotkeys;
|
private readonly List<KeyEventItem> _globalHotkeys;
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||||
|
|
||||||
public GlobalHotkeySettingViewModel()
|
public GlobalHotkeySettingViewModel()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
using System.Reactive.Concurrency;
|
|
||||||
|
|
||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class MainWindowViewModel : MyReactiveObject
|
public partial class MainWindowViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
public Interaction<Unit, string?> ReadTextFromClipboardInteraction { get; } = new();
|
public Interaction<RxVoid, string?> ReadTextFromClipboardInteraction { get; } = new();
|
||||||
public Interaction<Unit, byte[]?> ScanScreenInteraction { get; } = new();
|
public Interaction<RxVoid, byte[]?> ScanScreenInteraction { get; } = new();
|
||||||
public Interaction<Unit, string?> BrowseImageFileInteraction { get; } = new();
|
public Interaction<RxVoid, string?> BrowseImageFileInteraction { get; } = new();
|
||||||
public Interaction<bool?, Unit> ShowHideWindowInteraction { get; } = new();
|
public Interaction<bool?, RxVoid> ShowHideWindowInteraction { get; } = new();
|
||||||
|
|
||||||
public bool DesignMode { get; set; }
|
public bool DesignMode { get; set; }
|
||||||
|
|
||||||
@@ -22,67 +20,68 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
#region Menu
|
#region Menu
|
||||||
|
|
||||||
//servers
|
//servers
|
||||||
public ReactiveCommand<Unit, Unit> AddVmessServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddVmessServerCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> AddVlessServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddVlessServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddShadowsocksServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddShadowsocksServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddSocksServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddSocksServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddHttpServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddHttpServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddTrojanServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddTrojanServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddHysteria2ServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddTuicServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddWireguardServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddAnytlsServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddAnytlsServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddNaiveServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddNaiveServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddCustomServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddPolicyGroupServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddCustomOutboundServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddProxyChainServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddPolicyGroupServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddProxyChainServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddServerViaClipboardCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> AddServerViaImageCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddServerViaScanCmd { get; }
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> AddServerViaImageCmd { get; }
|
||||||
|
|
||||||
//Subscription
|
//Subscription
|
||||||
public ReactiveCommand<Unit, Unit> SubSettingCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SubSettingCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SubUpdateCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SubUpdateCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> SubUpdateViaProxyCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SubUpdateViaProxyCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> SubGroupUpdateCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SubGroupUpdateCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> SubGroupUpdateViaProxyCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SubGroupUpdateViaProxyCmd { get; }
|
||||||
|
|
||||||
//Setting
|
//Setting
|
||||||
public ReactiveCommand<Unit, Unit> OptionSettingCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> OptionSettingCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> RoutingSettingCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> RoutingSettingCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> DNSSettingCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> DNSSettingCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> FullConfigTemplateCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> FullConfigTemplateCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> GlobalHotkeySettingCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> GlobalHotkeySettingCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> RebootAsAdminCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> RebootAsAdminCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ClearServerStatisticsCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> OpenTheFileLocationCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> OpenTheFileLocationCmd { get; }
|
||||||
|
|
||||||
//Presets
|
//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]
|
[Reactive]
|
||||||
public bool BlReloadEnabled { get; set; }
|
public partial bool BlReloadEnabled { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool ShowClashUI { get; set; }
|
public partial bool ShowClashUI { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[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 EGirdOrientation MainGirdOrientation { get; set; }
|
[Reactive] public partial EGirdOrientation MainGirdOrientation { get; set; }
|
||||||
|
|
||||||
#endregion Menu
|
#endregion Menu
|
||||||
|
|
||||||
@@ -145,6 +144,10 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
await AddServerAsync(EConfigType.Custom);
|
await AddServerAsync(EConfigType.Custom);
|
||||||
});
|
});
|
||||||
|
AddCustomOutboundServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
|
{
|
||||||
|
await AddServerAsync(EConfigType.Outbound);
|
||||||
|
});
|
||||||
AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await AddServerAsync(EConfigType.PolicyGroup);
|
await AddServerAsync(EConfigType.PolicyGroup);
|
||||||
@@ -263,7 +266,12 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#endregion AppEvents
|
#endregion AppEvents
|
||||||
|
|
||||||
var vmReloadRequestedList = new List<IObservable<Unit>>
|
ProfilesViewModel.RefreshServersRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await RefreshServers());
|
||||||
|
|
||||||
|
var vmReloadRequestedList = new List<IObservable<RxVoid>>
|
||||||
{
|
{
|
||||||
ProfilesViewModel.ReloadRequested.AsObservable(),
|
ProfilesViewModel.ReloadRequested.AsObservable(),
|
||||||
StatusBarViewModel.ReloadRequested.AsObservable(),
|
StatusBarViewModel.ReloadRequested.AsObservable(),
|
||||||
@@ -292,7 +300,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||||
.Subscribe(async blShow =>
|
.Subscribe(async blShow =>
|
||||||
{
|
{
|
||||||
await ShowHideWindowInteraction.Handle(blShow);
|
await ShowHideWindowInteraction.HandleSafe(blShow);
|
||||||
});
|
});
|
||||||
|
|
||||||
StatusBarViewModel.SetDefaultServerRequested
|
StatusBarViewModel.SetDefaultServerRequested
|
||||||
@@ -394,20 +402,34 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task RefreshServers()
|
private async Task RefreshServers()
|
||||||
{
|
{
|
||||||
await ProfilesViewModel.RefreshServers();
|
await ProfilesViewModel.RefreshServersBiz();
|
||||||
await StatusBarViewModel.RefreshServers();
|
await StatusBarViewModel.RefreshServersBiz();
|
||||||
|
|
||||||
// await Task.Delay(200);
|
// await Task.Delay(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshServersDispatcherAsync()
|
private async Task RefreshServersDispatcherAsync()
|
||||||
{
|
{
|
||||||
await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
//await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
||||||
|
await Signal.FromAsync(async () =>
|
||||||
|
{
|
||||||
|
await RefreshServers();
|
||||||
|
return RxVoid.Default;
|
||||||
|
})
|
||||||
|
.SubscribeOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshSubscriptions()
|
private async Task RefreshSubscriptions()
|
||||||
{
|
{
|
||||||
await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
//await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
||||||
|
await Signal.FromAsync(async () =>
|
||||||
|
{
|
||||||
|
await ProfilesViewModel.RefreshSubscriptions();
|
||||||
|
return RxVoid.Default;
|
||||||
|
})
|
||||||
|
.SubscribeOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Servers && Groups
|
#endregion Servers && Groups
|
||||||
@@ -424,7 +446,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool? ret = false;
|
bool? ret = false;
|
||||||
if (eConfigType == EConfigType.Custom)
|
if (eConfigType is EConfigType.Custom or EConfigType.Outbound)
|
||||||
{
|
{
|
||||||
var addServer2ViewModel = new AddServer2ViewModel(item);
|
var addServer2ViewModel = new AddServer2ViewModel(item);
|
||||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
||||||
@@ -454,7 +476,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
var stringData = clipboardData;
|
var stringData = clipboardData;
|
||||||
if (clipboardData == null)
|
if (clipboardData == null)
|
||||||
{
|
{
|
||||||
var result = await ReadTextFromClipboardInteraction.Handle(Unit.Default);
|
var result = await ReadTextFromClipboardInteraction.HandleSafe(RxVoid.Default);
|
||||||
if (result.IsNullOrEmpty())
|
if (result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
@@ -477,7 +499,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task AddServerViaScanAsync()
|
public async Task AddServerViaScanAsync()
|
||||||
{
|
{
|
||||||
var result = await ScanScreenInteraction.Handle(Unit.Default);
|
var result = await ScanScreenInteraction.HandleSafe(RxVoid.Default);
|
||||||
await ScanScreenResult(result);
|
await ScanScreenResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,7 +511,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task AddServerViaImageAsync()
|
public async Task AddServerViaImageAsync()
|
||||||
{
|
{
|
||||||
var imageFileName = await BrowseImageFileInteraction.Handle(Unit.Default);
|
var imageFileName = await BrowseImageFileInteraction.HandleSafe(RxVoid.Default);
|
||||||
await AddScanResultAsync(imageFileName);
|
await AddScanResultAsync(imageFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,10 +700,12 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
//{
|
//{
|
||||||
// await ClashProxiesViewModel.ProxiesReload();
|
// await ClashProxiesViewModel.ProxiesReload();
|
||||||
//}, RxSchedulers.MainThreadScheduler);
|
//}, RxSchedulers.MainThreadScheduler);
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
await Signal.FromAsync(async () =>
|
||||||
{
|
{
|
||||||
await ClashProxiesViewModel.ProxiesReload();
|
await ClashProxiesViewModel.ProxiesReload();
|
||||||
});
|
return RxVoid.Default;
|
||||||
|
}).SubscribeOn(RxSchedulers.MainThreadScheduler)
|
||||||
|
.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
ReloadResult(showClashUI);
|
ReloadResult(showClashUI);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class MsgViewModel : MyReactiveObject
|
public partial class MsgViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
public Interaction<string, Unit> DispatcherShowMsgInteraction { get; } = new();
|
public Interaction<string, RxVoid> DispatcherShowMsgInteraction { get; } = new();
|
||||||
|
|
||||||
private readonly ConcurrentQueue<string> _queueMsg = new();
|
private readonly ConcurrentQueue<string> _queueMsg = new();
|
||||||
private volatile bool _lastMsgFilterNotAvailable;
|
private volatile bool _lastMsgFilterNotAvailable;
|
||||||
@@ -10,10 +10,10 @@ public class MsgViewModel : MyReactiveObject
|
|||||||
public int NumMaxMsg { get; } = 500;
|
public int NumMaxMsg { get; } = 500;
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string MsgFilter { get; set; }
|
public partial string MsgFilter { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool AutoRefresh { get; set; }
|
public partial bool AutoRefresh { get; set; }
|
||||||
|
|
||||||
public MsgViewModel()
|
public MsgViewModel()
|
||||||
{
|
{
|
||||||
@@ -36,6 +36,11 @@ public class MsgViewModel : MyReactiveObject
|
|||||||
.Subscribe(content => _ = AppendQueueMsg(content));
|
.Subscribe(content => _ = AppendQueueMsg(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void FlushQueueMsg()
|
||||||
|
{
|
||||||
|
_ = AppendQueueMsg(string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task AppendQueueMsg(string msg)
|
private async Task AppendQueueMsg(string msg)
|
||||||
{
|
{
|
||||||
if (AutoRefresh == false)
|
if (AutoRefresh == false)
|
||||||
@@ -65,7 +70,17 @@ public class MsgViewModel : MyReactiveObject
|
|||||||
sb.Append(line);
|
sb.Append(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
await DispatcherShowMsgInteraction.Handle(sb.ToString());
|
if (sb.Length > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await DispatcherShowMsgInteraction.HandleSafe(sb.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
_queueMsg.Enqueue(sb.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -75,6 +90,11 @@ public class MsgViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private void EnqueueQueueMsg(string msg)
|
private void EnqueueQueueMsg(string msg)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(msg))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//filter msg
|
//filter msg
|
||||||
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable)
|
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,129 +1,119 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
public partial class OptionSettingViewModel : MyReactiveObject, ICloseable
|
||||||
{
|
{
|
||||||
public event EventHandler? RequestClose;
|
public event EventHandler? RequestClose;
|
||||||
|
|
||||||
#region Core
|
#region Core
|
||||||
|
|
||||||
[Reactive] public int LocalPort { get; set; }
|
[Reactive] public partial int LocalPort { get; set; }
|
||||||
[Reactive] public bool SecondLocalPortEnabled { get; set; }
|
[Reactive] public partial bool SecondLocalPortEnabled { get; set; }
|
||||||
[Reactive] public bool UdpEnabled { get; set; }
|
[Reactive] public partial bool UdpEnabled { get; set; }
|
||||||
[Reactive] public bool SniffingEnabled { get; set; }
|
[Reactive] public partial bool SniffingEnabled { get; set; }
|
||||||
public IList<string> DestOverride { get; set; }
|
public IList<string> DestOverride { get; set; }
|
||||||
[Reactive] public bool RouteOnly { get; set; }
|
[Reactive] public partial bool RouteOnly { get; set; }
|
||||||
[Reactive] public bool AllowLANConn { get; set; }
|
[Reactive] public partial bool AllowLANConn { get; set; }
|
||||||
[Reactive] public bool NewPort4LAN { get; set; }
|
[Reactive] public partial bool NewPort4LAN { get; set; }
|
||||||
[Reactive] public string User { get; set; }
|
[Reactive] public partial string User { get; set; }
|
||||||
[Reactive] public string Pass { get; set; }
|
[Reactive] public partial string Pass { get; set; }
|
||||||
[Reactive] public bool LogEnabled { get; set; }
|
[Reactive] public partial bool LogEnabled { get; set; }
|
||||||
[Reactive] public string Loglevel { get; set; }
|
[Reactive] public partial string Loglevel { get; set; }
|
||||||
[Reactive] public string DefFingerprint { get; set; }
|
[Reactive] public partial string DefFingerprint { get; set; }
|
||||||
[Reactive] public string DefUserAgent { get; set; }
|
[Reactive] public partial string DefUserAgent { get; set; }
|
||||||
[Reactive] public string SendThrough { get; set; }
|
[Reactive] public partial string SendThrough { get; set; }
|
||||||
[Reactive] public string BindInterface { get; set; }
|
[Reactive] public partial string BindInterface { get; set; }
|
||||||
[Reactive] public string Mux4SboxProtocol { get; set; }
|
[Reactive] public partial string Mux4SboxProtocol { get; set; }
|
||||||
[Reactive] public bool EnableCacheFile4Sbox { get; set; }
|
[Reactive] public partial bool EnableCacheFile4Sbox { get; set; }
|
||||||
[Reactive] public int? HyUpMbps { get; set; }
|
[Reactive] public partial int? HyUpMbps { get; set; }
|
||||||
[Reactive] public int? HyDownMbps { get; set; }
|
[Reactive] public partial int? HyDownMbps { get; set; }
|
||||||
[Reactive] public bool EnableFragment { get; set; }
|
[Reactive] public partial bool EnableFragment { get; set; }
|
||||||
[Reactive] public bool EnableFinalFragment { get; set; }
|
[Reactive] public partial bool EnableFinalFragment { get; set; }
|
||||||
[Reactive] public string FragmentPackets { get; set; }
|
[Reactive] public partial string FragmentPackets { get; set; }
|
||||||
[Reactive] public string FragmentLength { get; set; }
|
[Reactive] public partial string FragmentLengths { get; set; }
|
||||||
[Reactive] public string FragmentInterval { get; set; }
|
[Reactive] public partial string FragmentDelays { get; set; }
|
||||||
[Reactive] public string FragmentMaxSplit { get; set; }
|
[Reactive] public partial string FragmentMaxSplit { get; set; }
|
||||||
|
|
||||||
#endregion Core
|
#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
|
#region UI
|
||||||
|
|
||||||
[Reactive] public bool AutoRun { get; set; }
|
[Reactive] public partial bool AutoRun { get; set; }
|
||||||
[Reactive] public bool EnableStatistics { get; set; }
|
[Reactive] public partial bool EnableStatistics { get; set; }
|
||||||
[Reactive] public bool KeepOlderDedupl { get; set; }
|
[Reactive] public partial bool KeepOlderDedupl { get; set; }
|
||||||
[Reactive] public bool DisplayRealTimeSpeed { get; set; }
|
[Reactive] public partial bool DisplayRealTimeSpeed { get; set; }
|
||||||
[Reactive] public bool EnableAutoAdjustMainLvColWidth { get; set; }
|
[Reactive] public partial bool EnableAutoAdjustMainLvColWidth { get; set; }
|
||||||
[Reactive] public bool AutoHideStartup { get; set; }
|
[Reactive] public partial bool AutoHideStartup { get; set; }
|
||||||
[Reactive] public bool Hide2TrayWhenClose { get; set; }
|
[Reactive] public partial bool Hide2TrayWhenClose { get; set; }
|
||||||
[Reactive] public bool MacOSShowInDock { get; set; }
|
[Reactive] public partial bool MacOSShowInDock { get; set; }
|
||||||
[Reactive] public bool EnableDragDropSort { get; set; }
|
[Reactive] public partial bool EnableDragDropSort { get; set; }
|
||||||
[Reactive] public bool DoubleClick2Activate { get; set; }
|
[Reactive] public partial bool DoubleClick2Activate { get; set; }
|
||||||
[Reactive] public int AutoUpdateInterval { get; set; }
|
[Reactive] public partial int AutoUpdateInterval { get; set; }
|
||||||
[Reactive] public int TrayMenuServersLimit { get; set; }
|
[Reactive] public partial int TrayMenuServersLimit { get; set; }
|
||||||
[Reactive] public string CurrentFontFamily { get; set; }
|
[Reactive] public partial string CurrentFontFamily { get; set; }
|
||||||
[Reactive] public int SpeedTestTimeout { get; set; }
|
[Reactive] public partial int SpeedTestTimeout { get; set; }
|
||||||
[Reactive] public string SpeedTestUrl { get; set; }
|
[Reactive] public partial string SpeedTestUrl { get; set; }
|
||||||
[Reactive] public string SpeedPingTestUrl { get; set; }
|
[Reactive] public partial string SpeedPingTestUrl { get; set; }
|
||||||
[Reactive] public string UdpTestTarget { get; set; }
|
[Reactive] public partial string UdpTestTarget { get; set; }
|
||||||
[Reactive] public int MixedConcurrencyCount { get; set; }
|
[Reactive] public partial int MixedConcurrencyCount { get; set; }
|
||||||
[Reactive] public bool EnableHWA { get; set; }
|
[Reactive] public partial bool EnableHWA { get; set; }
|
||||||
[Reactive] public string SubConvertUrl { get; set; }
|
[Reactive] public partial string SubConvertUrl { get; set; }
|
||||||
[Reactive] public int MainGirdOrientation { get; set; }
|
[Reactive] public partial int MainGirdOrientation { get; set; }
|
||||||
[Reactive] public string GeoFileSourceUrl { get; set; }
|
[Reactive] public partial string GeoFileSourceUrl { get; set; }
|
||||||
[Reactive] public string SrsFileSourceUrl { get; set; }
|
[Reactive] public partial string SrsFileSourceUrl { get; set; }
|
||||||
[Reactive] public string RoutingRulesSourceUrl { get; set; }
|
[Reactive] public partial string RoutingRulesSourceUrl { get; set; }
|
||||||
[Reactive] public string IPAPIUrl { get; set; }
|
[Reactive] public partial string IPAPIUrl { get; set; }
|
||||||
[Reactive] public string RootCertProvider { get; set; }
|
[Reactive] public partial string RootCertProvider { get; set; }
|
||||||
|
|
||||||
#endregion UI
|
#endregion UI
|
||||||
|
|
||||||
#region UI visibility
|
#region UI visibility
|
||||||
|
|
||||||
[Reactive] public bool BlIsWindows { get; set; }
|
[Reactive] public partial bool BlIsWindows { get; set; }
|
||||||
[Reactive] public bool BlIsLinux { get; set; }
|
[Reactive] public partial bool BlIsLinux { get; set; }
|
||||||
[Reactive] public bool BlIsIsMacOS { get; set; }
|
[Reactive] public partial bool BlIsIsMacOS { get; set; }
|
||||||
[Reactive] public bool BlIsNonWindows { get; set; }
|
[Reactive] public partial bool BlIsNonWindows { get; set; }
|
||||||
|
|
||||||
#endregion UI visibility
|
#endregion UI visibility
|
||||||
|
|
||||||
#region System proxy
|
#region System proxy
|
||||||
|
|
||||||
[Reactive] public bool NotProxyLocalAddress { get; set; }
|
[Reactive] public partial bool NotProxyLocalAddress { get; set; }
|
||||||
[Reactive] public string SystemProxyAdvancedProtocol { get; set; }
|
[Reactive] public partial string SystemProxyAdvancedProtocol { get; set; }
|
||||||
[Reactive] public string SystemProxyExceptions { get; set; }
|
[Reactive] public partial string SystemProxyExceptions { get; set; }
|
||||||
[Reactive] public string CustomSystemProxyPacPath { get; set; }
|
[Reactive] public partial string CustomSystemProxyPacPath { get; set; }
|
||||||
[Reactive] public string CustomSystemProxyScriptPath { get; set; }
|
[Reactive] public partial string CustomSystemProxyScriptPath { get; set; }
|
||||||
|
|
||||||
#endregion System proxy
|
#endregion System proxy
|
||||||
|
|
||||||
#region Tun mode
|
#region Tun mode
|
||||||
|
|
||||||
[Reactive] public bool TunAutoRoute { get; set; }
|
[Reactive] public partial bool TunAutoRoute { get; set; }
|
||||||
[Reactive] public bool TunStrictRoute { get; set; }
|
[Reactive] public partial bool TunStrictRoute { get; set; }
|
||||||
[Reactive] public string TunStack { get; set; }
|
[Reactive] public partial string TunStack { get; set; }
|
||||||
[Reactive] public int TunMtu { get; set; }
|
[Reactive] public partial int TunMtu { get; set; }
|
||||||
[Reactive] public bool TunEnableIPv6Address { get; set; }
|
[Reactive] public partial bool TunEnableIPv6Address { get; set; }
|
||||||
[Reactive] public string TunIcmpRouting { get; set; }
|
[Reactive] public partial string TunIcmpRouting { get; set; }
|
||||||
[Reactive] public bool TunEnableLegacyProtect { get; set; }
|
[Reactive] public partial bool TunEnableLegacyProtect { get; set; }
|
||||||
[Reactive] public string TunRouteExcludeAddress { 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
|
#endregion Tun mode
|
||||||
|
|
||||||
#region CoreType
|
#region CoreType
|
||||||
|
|
||||||
[Reactive] public string CoreType1 { get; set; }
|
[Reactive] public partial string CoreType1 { get; set; }
|
||||||
[Reactive] public string CoreType2 { get; set; }
|
[Reactive] public partial string CoreType2 { get; set; }
|
||||||
[Reactive] public string CoreType3 { get; set; }
|
[Reactive] public partial string CoreType3 { get; set; }
|
||||||
[Reactive] public string CoreType4 { get; set; }
|
[Reactive] public partial string CoreType4 { get; set; }
|
||||||
[Reactive] public string CoreType5 { get; set; }
|
[Reactive] public partial string CoreType5 { get; set; }
|
||||||
[Reactive] public string CoreType6 { get; set; }
|
[Reactive] public partial string CoreType6 { get; set; }
|
||||||
[Reactive] public string CoreType7 { get; set; }
|
[Reactive] public partial string CoreType7 { get; set; }
|
||||||
[Reactive] public string CoreType9 { get; set; }
|
[Reactive] public partial string CoreType9 { get; set; }
|
||||||
|
|
||||||
#endregion CoreType
|
#endregion CoreType
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||||
|
|
||||||
public OptionSettingViewModel()
|
public OptionSettingViewModel()
|
||||||
{
|
{
|
||||||
@@ -150,6 +140,7 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
SecondLocalPortEnabled = inbound.SecondLocalPortEnabled;
|
SecondLocalPortEnabled = inbound.SecondLocalPortEnabled;
|
||||||
UdpEnabled = inbound.UdpEnabled;
|
UdpEnabled = inbound.UdpEnabled;
|
||||||
SniffingEnabled = inbound.SniffingEnabled;
|
SniffingEnabled = inbound.SniffingEnabled;
|
||||||
|
DestOverride = inbound.DestOverride ?? [];
|
||||||
RouteOnly = inbound.RouteOnly;
|
RouteOnly = inbound.RouteOnly;
|
||||||
AllowLANConn = inbound.AllowLANConn;
|
AllowLANConn = inbound.AllowLANConn;
|
||||||
NewPort4LAN = inbound.NewPort4LAN;
|
NewPort4LAN = inbound.NewPort4LAN;
|
||||||
@@ -168,24 +159,12 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
EnableFragment = _config.CoreBasicItem.EnableFragment;
|
EnableFragment = _config.CoreBasicItem.EnableFragment;
|
||||||
EnableFinalFragment = _config.CoreBasicItem.EnableFinalFragment;
|
EnableFinalFragment = _config.CoreBasicItem.EnableFinalFragment;
|
||||||
FragmentPackets = _config.Fragment4RayItem?.Packets;
|
FragmentPackets = _config.Fragment4RayItem?.Packets;
|
||||||
FragmentLength = _config.Fragment4RayItem?.Length;
|
FragmentLengths = Utils.List2String(_config.Fragment4RayItem?.Lengths);
|
||||||
FragmentInterval = _config.Fragment4RayItem?.Interval;
|
FragmentDelays = Utils.List2String(_config.Fragment4RayItem?.Delays);
|
||||||
FragmentMaxSplit = _config.Fragment4RayItem?.MaxSplit;
|
FragmentMaxSplit = _config.Fragment4RayItem?.MaxSplit;
|
||||||
|
|
||||||
#endregion Core
|
#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
|
#region UI
|
||||||
|
|
||||||
AutoRun = _config.GuiItem.AutoRun;
|
AutoRun = _config.GuiItem.AutoRun;
|
||||||
@@ -237,6 +216,8 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
TunIcmpRouting = _config.TunModeItem.IcmpRouting;
|
TunIcmpRouting = _config.TunModeItem.IcmpRouting;
|
||||||
TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect;
|
TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect;
|
||||||
TunRouteExcludeAddress = Utils.List2String(_config.TunModeItem.RouteExcludeAddress, true);
|
TunRouteExcludeAddress = Utils.List2String(_config.TunModeItem.RouteExcludeAddress, true);
|
||||||
|
TunIPv4Address = _config.TunModeItem.IPv4Address;
|
||||||
|
TunIPv6Address = _config.TunModeItem.IPv6Address;
|
||||||
|
|
||||||
#endregion Tun mode
|
#endregion Tun mode
|
||||||
|
|
||||||
@@ -309,34 +290,33 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
||||||
return;
|
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
|
var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics
|
||||||
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|
||||||
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort
|
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort
|
||||||
|| EnableHWA != _config.GuiItem.EnableHWA
|
|| EnableHWA != _config.GuiItem.EnableHWA
|
||||||
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily;
|
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily;
|
||||||
|
|
||||||
//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;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//Core
|
//Core
|
||||||
_config.Inbound.First().LocalPort = LocalPort;
|
var inbound = _config.Inbound.First();
|
||||||
_config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled;
|
inbound.LocalPort = LocalPort;
|
||||||
_config.Inbound.First().UdpEnabled = UdpEnabled;
|
inbound.SecondLocalPortEnabled = SecondLocalPortEnabled;
|
||||||
_config.Inbound.First().SniffingEnabled = SniffingEnabled;
|
inbound.UdpEnabled = UdpEnabled;
|
||||||
_config.Inbound.First().DestOverride = DestOverride?.ToList();
|
inbound.SniffingEnabled = SniffingEnabled;
|
||||||
_config.Inbound.First().RouteOnly = RouteOnly;
|
inbound.DestOverride = DestOverride?.ToList();
|
||||||
_config.Inbound.First().AllowLANConn = AllowLANConn;
|
inbound.RouteOnly = RouteOnly;
|
||||||
_config.Inbound.First().NewPort4LAN = NewPort4LAN;
|
inbound.AllowLANConn = AllowLANConn;
|
||||||
_config.Inbound.First().User = User;
|
inbound.NewPort4LAN = NewPort4LAN;
|
||||||
_config.Inbound.First().Pass = Pass;
|
inbound.User = User;
|
||||||
|
inbound.Pass = Pass;
|
||||||
if (_config.Inbound.Count > 1)
|
if (_config.Inbound.Count > 1)
|
||||||
{
|
{
|
||||||
_config.Inbound.RemoveAt(1);
|
_config.Inbound.RemoveAt(1);
|
||||||
@@ -351,30 +331,12 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
_config.CoreBasicItem.EnableCacheFile4Sbox = EnableCacheFile4Sbox;
|
_config.CoreBasicItem.EnableCacheFile4Sbox = EnableCacheFile4Sbox;
|
||||||
_config.HysteriaItem.UpMbps = HyUpMbps ?? 0;
|
_config.HysteriaItem.UpMbps = HyUpMbps ?? 0;
|
||||||
_config.HysteriaItem.DownMbps = HyDownMbps ?? 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.EnableFragment = EnableFragment;
|
||||||
_config.CoreBasicItem.EnableFinalFragment = EnableFinalFragment;
|
_config.CoreBasicItem.EnableFinalFragment = EnableFinalFragment;
|
||||||
|
_config.Fragment4RayItem ??= new();
|
||||||
_config.Fragment4RayItem.Packets = FragmentPackets;
|
_config.Fragment4RayItem.Packets = FragmentPackets;
|
||||||
_config.Fragment4RayItem.Length = FragmentLength;
|
_config.Fragment4RayItem.Lengths = fragmentLengths;
|
||||||
_config.Fragment4RayItem.Interval = FragmentInterval;
|
_config.Fragment4RayItem.Delays = fragmentDelays;
|
||||||
_config.Fragment4RayItem.MaxSplit = FragmentMaxSplit;
|
_config.Fragment4RayItem.MaxSplit = FragmentMaxSplit;
|
||||||
|
|
||||||
_config.GuiItem.AutoRun = AutoRun;
|
_config.GuiItem.AutoRun = AutoRun;
|
||||||
@@ -420,6 +382,8 @@ public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
|||||||
_config.TunModeItem.IcmpRouting = TunIcmpRouting;
|
_config.TunModeItem.IcmpRouting = TunIcmpRouting;
|
||||||
_config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect;
|
_config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect;
|
||||||
_config.TunModeItem.RouteExcludeAddress = Utils.String2List(TunRouteExcludeAddress);
|
_config.TunModeItem.RouteExcludeAddress = Utils.String2List(TunRouteExcludeAddress);
|
||||||
|
_config.TunModeItem.IPv4Address = TunIPv4Address;
|
||||||
|
_config.TunModeItem.IPv6Address = TunIPv6Address;
|
||||||
|
|
||||||
//coreType
|
//coreType
|
||||||
await SaveCoreType();
|
await SaveCoreType();
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
||||||
{
|
{
|
||||||
public event EventHandler? RequestClose;
|
public event EventHandler? RequestClose;
|
||||||
public Interaction<Unit, Unit> ProfilesFocusInteraction { get; } = new();
|
|
||||||
|
public Interaction<RxVoid, RxVoid> ProfilesFocusInteraction { get; } = new();
|
||||||
|
|
||||||
#region private prop
|
#region private prop
|
||||||
|
|
||||||
@@ -15,34 +16,34 @@ public class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
#endregion private prop
|
#endregion private prop
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||||
|
|
||||||
#region ObservableCollection
|
#region ObservableCollection
|
||||||
|
|
||||||
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
public BulkObservableCollection<ProfileItemModel> ProfileItems { get; } = [];
|
||||||
|
|
||||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
public BulkObservableCollection<SubItem> SubItems { get; } = [];
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ProfileItemModel SelectedProfile { get; set; }
|
public partial ProfileItemModel SelectedProfile { get; set; }
|
||||||
|
|
||||||
public IList<ProfileItemModel> SelectedProfiles { get; set; }
|
public IList<ProfileItemModel> SelectedProfiles { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public SubItem SelectedSub { get; set; }
|
public partial SubItem SelectedSub { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string ServerFilter { get; set; }
|
public partial string ServerFilter { get; set; }
|
||||||
|
|
||||||
// Include/Exclude filter for ConfigType
|
// Include/Exclude filter for ConfigType
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public List<EConfigType> FilterConfigTypes { get; set; }
|
public partial List<EConfigType> FilterConfigTypes { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool FilterExclude { get; set; }
|
public partial bool FilterExclude { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool MultiSelect { get; set; }
|
public partial bool MultiSelect { get; set; }
|
||||||
|
|
||||||
#endregion ObservableCollection
|
#endregion ObservableCollection
|
||||||
|
|
||||||
@@ -137,13 +138,7 @@ public class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
|||||||
|
|
||||||
await RefreshServers();
|
await RefreshServers();
|
||||||
|
|
||||||
try
|
await ProfilesFocusInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await ProfilesFocusInteraction.Handle(Unit.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<Unit, Unit>)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ServerFilterChanged(bool c)
|
private async Task ServerFilterChanged(bool c)
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class ProfilesViewModel : MyReactiveObject
|
public partial class ProfilesViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
||||||
public Interaction<ProfileItem, bool> SaveFileDialogInteraction { get; } = new();
|
public Interaction<ProfileItem, bool> SaveFileDialogInteraction { get; } = new();
|
||||||
public Interaction<string, Unit> SetClipboardDataInteraction { get; } = new();
|
public Interaction<string, RxVoid> SetClipboardDataInteraction { get; } = new();
|
||||||
public Interaction<Unit, Unit> ProfilesFocusInteraction { get; } = new();
|
public Interaction<RxVoid, RxVoid> ProfilesFocusInteraction { get; } = new();
|
||||||
public Interaction<string, Unit> ShareServerInteraction { get; } = new();
|
public Interaction<string, RxVoid> ShareServerInteraction { get; } = new();
|
||||||
public Interaction<Unit, Unit> DispatcherRefreshServersBizInteraction { get; } = new();
|
public Interaction<RxVoid, RxVoid> DispatcherRefreshServersBizInteraction { get; } = new();
|
||||||
public Interaction<Unit, Unit> AdjustMainLvColWidthInteraction { get; } = new();
|
public Interaction<RxVoid, RxVoid> AdjustMainLvColWidthInteraction { get; } = new();
|
||||||
|
|
||||||
public EventChannel<Unit> ReloadRequested { get; } = new();
|
public EventChannel<RxVoid> ReloadRequested { get; } = new();
|
||||||
|
public EventChannel<RxVoid> RefreshServersRequested { get; } = new();
|
||||||
|
|
||||||
#region private prop
|
#region private prop
|
||||||
|
|
||||||
@@ -24,69 +25,69 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#region ObservableCollection
|
#region ObservableCollection
|
||||||
|
|
||||||
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
public BulkObservableCollection<ProfileItemModel> ProfileItems { get; } = [];
|
||||||
|
|
||||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
public BulkObservableCollection<SubItem> SubItems { get; } = [];
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public ProfileItemModel SelectedProfile { get; set; }
|
public partial ProfileItemModel SelectedProfile { get; set; }
|
||||||
|
|
||||||
public IList<ProfileItemModel> SelectedProfiles { get; set; }
|
public IList<ProfileItemModel> SelectedProfiles { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public SubItem SelectedSub { get; set; }
|
public partial SubItem SelectedSub { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public SubItem SelectedMoveToGroup { get; set; }
|
public partial SubItem SelectedMoveToGroup { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string ServerFilter { get; set; }
|
public partial string ServerFilter { get; set; }
|
||||||
|
|
||||||
#endregion ObservableCollection
|
#endregion ObservableCollection
|
||||||
|
|
||||||
#region Menu
|
#region Menu
|
||||||
|
|
||||||
//servers delete
|
//servers delete
|
||||||
public ReactiveCommand<Unit, Unit> EditServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> EditServerCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> RemoveServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> RemoveServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> RemoveDuplicateServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> RemoveDuplicateServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> CopyServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> CopyServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SetDefaultServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ShareServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> ShareServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> GenGroupAllServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> GenGroupAllServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> GenGroupRegionServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> GenGroupRegionServerCmd { get; }
|
||||||
|
|
||||||
//servers move
|
//servers move
|
||||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> MoveTopCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> MoveUpCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> MoveDownCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> MoveBottomCmd { get; }
|
||||||
public ReactiveCommand<SubItem, Unit> MoveToGroupCmd { get; }
|
public ReactiveCommand<SubItem, RxVoid> MoveToGroupCmd { get; }
|
||||||
|
|
||||||
//servers ping
|
//servers ping
|
||||||
public ReactiveCommand<Unit, Unit> MixedTestServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> MixedTestServerCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> TcpingServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> TcpingServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> RealPingServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> RealPingServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> UdpTestServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> UdpTestServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> SpeedServerCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SpeedServerCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> SortServerResultCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> SortServerResultCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> RemoveInvalidServerResultCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> RemoveInvalidServerResultCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> FastRealPingCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> FastRealPingCmd { get; }
|
||||||
|
|
||||||
//servers export
|
//servers export
|
||||||
public ReactiveCommand<Unit, Unit> Export2ClientConfigCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> Export2ClientConfigCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> Export2ClientConfigClipboardCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> Export2ClientConfigClipboardCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> Export2ShareUrlCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> Export2ShareUrlCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> Export2ShareUrlBase64Cmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> Export2ShareUrlBase64Cmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> Export2InnerUriCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> Export2InnerUriCmd { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> AddSubCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> AddSubCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> EditSubCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> EditSubCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> DeleteSubCmd { get; }
|
public ReactiveCommand<RxVoid, RxVoid> DeleteSubCmd { get; }
|
||||||
|
|
||||||
#endregion Menu
|
#endregion Menu
|
||||||
|
|
||||||
@@ -344,13 +345,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
await RefreshServers();
|
await RefreshServers();
|
||||||
|
|
||||||
try
|
await ProfilesFocusInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await ProfilesFocusInteraction.Handle(Unit.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<Unit, Unit>)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ServerFilterChanged(bool c)
|
private async Task ServerFilterChanged(bool c)
|
||||||
@@ -368,12 +363,14 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task RefreshServers()
|
public async Task RefreshServers()
|
||||||
{
|
{
|
||||||
await RefreshServersBiz();
|
RefreshServersRequested.Publish();
|
||||||
|
|
||||||
// await Task.Delay(200);
|
// await Task.Delay(200);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshServersBiz()
|
public async Task RefreshServersBiz()
|
||||||
{
|
{
|
||||||
var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter);
|
var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter);
|
||||||
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
|
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
|
||||||
@@ -392,13 +389,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
SelectedProfile = selected ?? lstModel.First();
|
SelectedProfile = selected ?? lstModel.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await DispatcherRefreshServersBizInteraction.HandleSafe(RxVoid.Default);
|
||||||
{
|
|
||||||
await DispatcherRefreshServersBizInteraction.Handle(Unit.Default);
|
|
||||||
}
|
|
||||||
catch (UnhandledInteractionException<Unit, Unit>)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshSubscriptions()
|
public async Task RefreshSubscriptions()
|
||||||
@@ -416,7 +407,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task AdjustMainLvColWidth()
|
public async Task AdjustMainLvColWidth()
|
||||||
{
|
{
|
||||||
await AdjustMainLvColWidthInteraction.Handle(Unit.Default);
|
await AdjustMainLvColWidthInteraction.HandleSafe(RxVoid.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||||
@@ -500,7 +491,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
var eConfigType = item.ConfigType;
|
var eConfigType = item.ConfigType;
|
||||||
|
|
||||||
bool? ret = false;
|
bool? ret = false;
|
||||||
if (eConfigType == EConfigType.Custom)
|
if (eConfigType is EConfigType.Custom or EConfigType.Outbound)
|
||||||
{
|
{
|
||||||
var addServer2ViewModel = new AddServer2ViewModel(item);
|
var addServer2ViewModel = new AddServer2ViewModel(item);
|
||||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
||||||
@@ -532,7 +523,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -553,7 +544,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task RemoveDuplicateServer()
|
private async Task RemoveDuplicateServer()
|
||||||
{
|
{
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -628,7 +619,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ShareServerInteraction.Handle(url);
|
await ShareServerInteraction.HandleSafe(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GenGroupAllServer()
|
private async Task GenGroupAllServer()
|
||||||
@@ -758,10 +749,9 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
_speedtestService ??= new SpeedtestService(_config, async (SpeedTestResult result) =>
|
_speedtestService ??= new SpeedtestService(_config, async (SpeedTestResult result) =>
|
||||||
{
|
{
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(result, (scheduler, result) =>
|
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||||
{
|
{
|
||||||
_ = SetSpeedTestResult(result);
|
_ = SetSpeedTestResult(result);
|
||||||
return Disposable.Empty;
|
|
||||||
});
|
});
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
});
|
});
|
||||||
@@ -797,13 +787,13 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle((string)result.Data);
|
await SetClipboardDataInteraction.HandleSafe((string)result.Data);
|
||||||
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
|
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SaveFileDialogInteraction.Handle(item);
|
await SaveFileDialogInteraction.HandleSafe(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -852,11 +842,11 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (blEncode)
|
if (blEncode)
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle(Utils.Base64Encode(sb.ToString()));
|
await SetClipboardDataInteraction.HandleSafe(Utils.Base64Encode(sb.ToString()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle(sb.ToString());
|
await SetClipboardDataInteraction.HandleSafe(sb.ToString());
|
||||||
}
|
}
|
||||||
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||||
}
|
}
|
||||||
@@ -879,7 +869,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (!result.IsNullOrEmpty())
|
if (!result.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
await SetClipboardDataInteraction.Handle(result);
|
await SetClipboardDataInteraction.HandleSafe(result);
|
||||||
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -923,7 +913,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
if (await ShowYesNoInteraction.HandleSafe(ResUI.RemoveServer) == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
||||||
{
|
{
|
||||||
public event EventHandler? RequestClose;
|
public event EventHandler? RequestClose;
|
||||||
|
|
||||||
@@ -8,25 +8,40 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
|||||||
public IList<string> InboundTagItems { get; set; }
|
public IList<string> InboundTagItems { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public RulesItem SelectedSource { get; set; }
|
public partial RulesItem SelectedSource { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string Domain { get; set; }
|
public partial string Domain { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string IP { get; set; }
|
public partial string IP { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string Process { get; set; }
|
public partial string Process { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string? RuleType { get; set; }
|
public partial string? RuleType { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool AutoSort { get; set; }
|
public partial bool AutoSort { get; set; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SelectProfileCmd { get; }
|
[Reactive]
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public partial string OutboundTag { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial string Remarks { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial string Port { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial string Network { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public partial bool Enabled { get; set; }
|
||||||
|
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> SelectProfileCmd { get; }
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||||
|
|
||||||
public RoutingRuleDetailsViewModel(RulesItem rulesItem)
|
public RoutingRuleDetailsViewModel(RulesItem rulesItem)
|
||||||
{
|
{
|
||||||
@@ -57,6 +72,11 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
|||||||
IP = Utils.List2String(SelectedSource.Ip, true);
|
IP = Utils.List2String(SelectedSource.Ip, true);
|
||||||
Process = Utils.List2String(SelectedSource.Process, true);
|
Process = Utils.List2String(SelectedSource.Process, true);
|
||||||
RuleType = SelectedSource.RuleType?.ToString();
|
RuleType = SelectedSource.RuleType?.ToString();
|
||||||
|
OutboundTag = SelectedSource.OutboundTag;
|
||||||
|
Remarks = SelectedSource.Remarks;
|
||||||
|
Port = SelectedSource.Port;
|
||||||
|
Network = SelectedSource.Network;
|
||||||
|
Enabled = SelectedSource.Enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveRulesAsync()
|
private async Task SaveRulesAsync()
|
||||||
@@ -80,6 +100,11 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
|||||||
SelectedSource.Protocol = ProtocolItems?.ToList();
|
SelectedSource.Protocol = ProtocolItems?.ToList();
|
||||||
SelectedSource.InboundTag = InboundTagItems?.ToList();
|
SelectedSource.InboundTag = InboundTagItems?.ToList();
|
||||||
SelectedSource.RuleType = RuleType.IsNullOrEmpty() ? null : Enum.Parse<ERuleType>(RuleType);
|
SelectedSource.RuleType = RuleType.IsNullOrEmpty() ? null : Enum.Parse<ERuleType>(RuleType);
|
||||||
|
SelectedSource.OutboundTag = OutboundTag;
|
||||||
|
SelectedSource.Remarks = Remarks;
|
||||||
|
SelectedSource.Port = Port;
|
||||||
|
SelectedSource.Network = Network;
|
||||||
|
SelectedSource.Enabled = Enabled;
|
||||||
|
|
||||||
var hasRule = SelectedSource.Domain?.Count > 0
|
var hasRule = SelectedSource.Domain?.Count > 0
|
||||||
|| SelectedSource.Ip?.Count > 0
|
|| SelectedSource.Ip?.Count > 0
|
||||||
@@ -110,8 +135,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
|||||||
var profileItem = await profileSelectViewModel.GetProfileItem();
|
var profileItem = await profileSelectViewModel.GetProfileItem();
|
||||||
if (profileItem != null)
|
if (profileItem != null)
|
||||||
{
|
{
|
||||||
SelectedSource.OutboundTag = profileItem.Remarks;
|
OutboundTag = profileItem.Remarks;
|
||||||
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user