mirror of
https://github.com/2dust/v2rayN.git
synced 2026-08-12 18:22:04 +03:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
5dd5b25869 | ||
|
|
fafdb4a0b6 | ||
|
|
223642dd67 | ||
|
|
165e0bf9e9 | ||
|
|
e615314582 | ||
|
|
ca9978b625 | ||
|
|
74ab7ad097 | ||
|
|
8f5cbad988 | ||
|
|
1ab9757498 | ||
|
|
50ab02e8a9 | ||
|
|
b9a1c129c4 | ||
|
|
996eb51f9b | ||
|
|
b9a58e7137 | ||
|
|
9664ee380e | ||
|
|
6f50206606 | ||
|
|
1de83f96ed | ||
|
|
994bc1ca6a | ||
|
|
3ca49dd9db | ||
|
|
a0bd8f9934 | ||
|
|
88d59488fd | ||
|
|
2c70b018ce | ||
|
|
b6ab428dfc | ||
|
|
45ab7503e3 | ||
|
|
1768b4a7ee | ||
|
|
3e9f78f831 | ||
|
|
8c4bd52389 | ||
|
|
fa61fedf11 | ||
|
|
ad12a6c456 | ||
|
|
66b48c231f | ||
|
|
52766d66bf | ||
|
|
e1a5c360ac | ||
|
|
0c570fcb38 | ||
|
|
a236fab27c | ||
|
|
f8e1186095 | ||
|
|
ca186e2636 | ||
|
|
1e8ba9ad9b | ||
|
|
31e32f174e | ||
|
|
bf376a8fac | ||
|
|
5a9a9b7ab1 | ||
|
|
e7a9aa66dc | ||
|
|
709b9a0958 | ||
|
|
191fe48257 | ||
|
|
48d58d482a |
113
.github/scripts/UpdateCert.cs
vendored
Normal file
113
.github/scripts/UpdateCert.cs
vendored
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
THIRD-PARTY CA CERTIFICATE DATA ATTRIBUTION
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
This component includes CA certificate data obtained from third-party sources.
|
||||||
|
|
||||||
|
1. Common CA Database (CCADB)
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Website:
|
||||||
|
https://www.ccadb.org/
|
||||||
|
|
||||||
|
Data source:
|
||||||
|
https://ccadb.my.salesforce-sites.com/mozilla/IncludedRootsPEMTxt?TrustBitsInclude=Websites
|
||||||
|
|
||||||
|
License:
|
||||||
|
Community Data License Agreement – Permissive, Version 2.0 (CDLA-2.0 Permissive)
|
||||||
|
|
||||||
|
The names and trademarks of CCADB contributors may not be used to endorse or
|
||||||
|
promote products derived from this data without prior written permission.
|
||||||
|
|
||||||
|
License text:
|
||||||
|
https://cdla.dev/permissive-2-0/
|
||||||
|
|
||||||
|
|
||||||
|
2. Chromium Root Store
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Source:
|
||||||
|
Chromium Project
|
||||||
|
|
||||||
|
Data source:
|
||||||
|
https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/root_store.certs?format=TEXT
|
||||||
|
|
||||||
|
License:
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright:
|
||||||
|
Copyright (c) The Chromium Authors
|
||||||
|
|
||||||
|
The Chromium Root Store data is distributed under the BSD 3-Clause License.
|
||||||
|
|
||||||
|
License text:
|
||||||
|
https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
var client = new HttpClient { Timeout = TimeSpan.FromMinutes(2) };
|
||||||
|
|
||||||
|
var outputDir = args.Length > 0
|
||||||
|
? args[0]
|
||||||
|
: Path.Combine(Environment.CurrentDirectory, "v2rayN", "ServiceLib", "Sample");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(outputDir);
|
||||||
|
|
||||||
|
Console.WriteLine("=== Mozilla + Chrome Root Store PEM Converter ===\n");
|
||||||
|
Console.WriteLine($"Output directory: {outputDir}\n");
|
||||||
|
|
||||||
|
// Process Mozilla
|
||||||
|
await ProcessMozillaAsync(outputDir);
|
||||||
|
|
||||||
|
// Process Chrome
|
||||||
|
await ProcessChromeAsync(outputDir);
|
||||||
|
|
||||||
|
Console.WriteLine("\nAll done!");
|
||||||
|
|
||||||
|
async Task ProcessMozillaAsync(string outputDir)
|
||||||
|
{
|
||||||
|
const string certdataUrl = "https://ccadb.my.salesforce-sites.com/mozilla/IncludedRootsPEMTxt?TrustBitsInclude=Websites";
|
||||||
|
var outputFile = Path.Combine(outputDir, "mozilla_roots_pem");
|
||||||
|
|
||||||
|
Console.WriteLine("Downloading Mozilla certdata.txt...");
|
||||||
|
var content = await client.GetStringAsync(certdataUrl);
|
||||||
|
|
||||||
|
var pemMatches = Regex.Matches(content, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
|
||||||
|
var pems = new List<string>();
|
||||||
|
foreach (Match m in pemMatches)
|
||||||
|
{
|
||||||
|
pems.Add(m.Groups[1].Value.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(outputFile, string.Join("\n\n", pems));
|
||||||
|
Console.WriteLine($"Mozilla roots saved to: {outputFile} ({pems.Count} certificates)");
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
var chromeOutput = Path.Combine(outputDir, "chrome_roots_pem");
|
||||||
|
|
||||||
|
Console.WriteLine("Downloading Chrome root_store.certs...");
|
||||||
|
var base64Content = await client.GetStringAsync(chromeCertsUrl);
|
||||||
|
var decoded = Convert.FromBase64String(base64Content.Replace("\n", ""));
|
||||||
|
|
||||||
|
var text = Encoding.UTF8.GetString(decoded);
|
||||||
|
|
||||||
|
var pemMatches = Regex.Matches(text, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
|
||||||
|
var pems = new List<string>();
|
||||||
|
foreach (Match m in pemMatches)
|
||||||
|
{
|
||||||
|
pems.Add(m.Groups[1].Value.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(chromeOutput, string.Join("\n\n", pems));
|
||||||
|
Console.WriteLine($"Chrome roots saved to: {chromeOutput} ({pems.Count} certificates)");
|
||||||
|
}
|
||||||
24
.github/workflows/build-linux.yml
vendored
24
.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
|
||||||
@@ -214,7 +214,7 @@ jobs:
|
|||||||
QCOW2_IMAGE: debian13-loong64.qcow2
|
QCOW2_IMAGE: debian13-loong64.qcow2
|
||||||
EFI_CODE: edk2-loongarch64-code.fd
|
EFI_CODE: edk2-loongarch64-code.fd
|
||||||
EFI_VARS: edk2-loongarch64-vars.fd
|
EFI_VARS: edk2-loongarch64-vars.fd
|
||||||
QEMU_VERSION: 10.2.3
|
QEMU_VERSION: 10.2.4
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare host tools
|
- name: Prepare host tools
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -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
|
||||||
@@ -258,7 +258,7 @@ jobs:
|
|||||||
export VIRTIOFSD_SOCKET="$RUNNER_TEMP/virtiofs-deb-loong64.sock"
|
export VIRTIOFSD_SOCKET="$RUNNER_TEMP/virtiofs-deb-loong64.sock"
|
||||||
rm -f "$VIRTIOFSD_SOCKET"
|
rm -f "$VIRTIOFSD_SOCKET"
|
||||||
|
|
||||||
sudo virtiofsd --socket-path="$VIRTIOFSD_SOCKET" -o source="$GITHUB_WORKSPACE" -o cache=auto &
|
sudo virtiofsd --socket-path="$VIRTIOFSD_SOCKET" --shared-dir="$GITHUB_WORKSPACE" --cache=auto &
|
||||||
|
|
||||||
VIRTIOFSD_PID=$!
|
VIRTIOFSD_PID=$!
|
||||||
trap 'sudo kill "$VIRTIOFSD_PID" 2>/dev/null || true; rm -f "$VIRTIOFSD_SOCKET"' EXIT
|
trap 'sudo kill "$VIRTIOFSD_PID" 2>/dev/null || true; rm -f "$VIRTIOFSD_SOCKET"' EXIT
|
||||||
@@ -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
|
||||||
@@ -364,7 +364,7 @@ jobs:
|
|||||||
QCOW2_IMAGE: fedora43-loong64.qcow2
|
QCOW2_IMAGE: fedora43-loong64.qcow2
|
||||||
EFI_CODE: edk2-loongarch64-code.fd
|
EFI_CODE: edk2-loongarch64-code.fd
|
||||||
EFI_VARS: edk2-loongarch64-vars.fd
|
EFI_VARS: edk2-loongarch64-vars.fd
|
||||||
QEMU_VERSION: 10.2.3
|
QEMU_VERSION: 10.2.4
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare host tools
|
- name: Prepare host tools
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -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
|
||||||
@@ -408,7 +408,7 @@ jobs:
|
|||||||
export VIRTIOFSD_SOCKET="$RUNNER_TEMP/virtiofs-rpm-loong64.sock"
|
export VIRTIOFSD_SOCKET="$RUNNER_TEMP/virtiofs-rpm-loong64.sock"
|
||||||
rm -f "$VIRTIOFSD_SOCKET"
|
rm -f "$VIRTIOFSD_SOCKET"
|
||||||
|
|
||||||
sudo virtiofsd --socket-path="$VIRTIOFSD_SOCKET" -o source="$GITHUB_WORKSPACE" -o cache=auto &
|
sudo virtiofsd --socket-path="$VIRTIOFSD_SOCKET" --shared-dir="$GITHUB_WORKSPACE" --cache=auto &
|
||||||
|
|
||||||
VIRTIOFSD_PID=$!
|
VIRTIOFSD_PID=$!
|
||||||
trap 'sudo kill "$VIRTIOFSD_PID" 2>/dev/null || true; rm -f "$VIRTIOFSD_SOCKET"' EXIT
|
trap 'sudo kill "$VIRTIOFSD_PID" 2>/dev/null || true; rm -f "$VIRTIOFSD_SOCKET"' EXIT
|
||||||
|
|||||||
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.3.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"
|
||||||
|
|||||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -22,6 +22,7 @@ jobs:
|
|||||||
case(
|
case(
|
||||||
inputs.target == 'macos', 'macos-latest',
|
inputs.target == 'macos', 'macos-latest',
|
||||||
inputs.target == 'linux', 'ubuntu-24.04',
|
inputs.target == 'linux', 'ubuntu-24.04',
|
||||||
|
inputs.target == 'windows', 'windows-latest',
|
||||||
'ubuntu-latest'
|
'ubuntu-latest'
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -68,18 +69,25 @@ 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.3.0
|
uses: actions/setup-dotnet@v6.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.1xx'
|
dotnet-version: '10.0.1xx'
|
||||||
|
|
||||||
- name: Build v2rayN
|
- name: Build v2rayN
|
||||||
|
shell: bash
|
||||||
working-directory: ./v2rayN
|
working-directory: ./v2rayN
|
||||||
run: dotnet publish $Project -c Release -r $RID -p:SelfContained=true $ExtOpt -o $Output
|
run: dotnet publish $Project -c Release -r $RID -p:SelfContained=true $ExtOpt -o $Output
|
||||||
|
|
||||||
- name: Build AmazTool
|
- name: Build AmazTool
|
||||||
|
shell: bash
|
||||||
working-directory: ./v2rayN
|
working-directory: ./v2rayN
|
||||||
run: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r $RID -p:SelfContained=true -p:PublishTrimmed=true $ExtOpt -o $Output
|
run: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r $RID -p:SelfContained=true -p:PublishTrimmed=true $ExtOpt -o $Output
|
||||||
|
|
||||||
|
- name: Remove Debug Symbols
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
find ${{ matrix.arch }} -type f -name '*.pdb' -delete
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v7.0.1
|
uses: actions/upload-artifact@v7.0.1
|
||||||
with:
|
with:
|
||||||
|
|||||||
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.3.0
|
uses: actions/setup-dotnet@v6.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
|||||||
9
.github/workflows/winget-publish.yml
vendored
9
.github/workflows/winget-publish.yml
vendored
@@ -23,8 +23,13 @@ jobs:
|
|||||||
|
|
||||||
$targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1
|
$targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1
|
||||||
|
|
||||||
$x64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip' | Select -ExpandProperty browser_download_url
|
$assets = $targetRelease | Select -ExpandProperty assets -First 1
|
||||||
$arm64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-arm64\.zip' | Select -ExpandProperty browser_download_url
|
$x64InstallerUrl = $assets | Where-Object { $_.name -eq 'v2rayN-windows-64.zip' } | Select -ExpandProperty browser_download_url
|
||||||
|
$arm64InstallerUrl = $assets | Where-Object { $_.name -eq 'v2rayN-windows-arm64.zip' } | Select -ExpandProperty browser_download_url
|
||||||
|
|
||||||
|
if (-not $x64InstallerUrl -or -not $arm64InstallerUrl) {
|
||||||
|
throw "Could not find required installers in release assets."
|
||||||
|
}
|
||||||
|
|
||||||
$ver = $targetRelease.tag_name
|
$ver = $targetRelease.tag_name
|
||||||
|
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
|||||||
[submodule "v2rayN/GlobalHotKeys"]
|
[submodule "v2rayN/GlobalHotKeys"]
|
||||||
path = v2rayN/GlobalHotKeys
|
path = v2rayN/GlobalHotKeys
|
||||||
url = https://github.com/2dust/GlobalHotKeys
|
url = https://github.com/2dust/GlobalHotKeys
|
||||||
[submodule "v2rayN/NetBridge"]
|
|
||||||
path = v2rayN/NetBridge
|
|
||||||
url = https://github.com/2dust/NetBridge
|
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ Download the latest release here:
|
|||||||
|
|
||||||
[https://github.com/2dust/v2rayN/releases](https://github.com/2dust/v2rayN/releases)
|
[https://github.com/2dust/v2rayN/releases](https://github.com/2dust/v2rayN/releases)
|
||||||
|
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> v2rayN is the desktop version. For the mobile version, please visit the v2rayNG \
|
||||||
|
> v2rayN 是电脑版,手机版请访问 v2rayNG
|
||||||
|
>
|
||||||
|
> https://github.com/2dust/v2rayNG
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Documentation / 使用文档
|
## Documentation / 使用文档
|
||||||
@@ -43,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 签名校验
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ BUILD_FROM=""
|
|||||||
XRAY_VER="${XRAY_VER:-}"
|
XRAY_VER="${XRAY_VER:-}"
|
||||||
SING_VER="${SING_VER:-}"
|
SING_VER="${SING_VER:-}"
|
||||||
|
|
||||||
MIN_KERNEL="5.10"
|
MIN_KERNEL="6.12"
|
||||||
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_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}"
|
||||||
@@ -116,20 +116,7 @@ install_dependencies() {
|
|||||||
|
|
||||||
export PATH="$HOME/.dotnet:$PATH"
|
export PATH="$HOME/.dotnet:$PATH"
|
||||||
export DOTNET_ROOT="$HOME/.dotnet"
|
export DOTNET_ROOT="$HOME/.dotnet"
|
||||||
|
|
||||||
mkdir -p "$HOME/.nuget/NuGet"
|
|
||||||
|
|
||||||
cat > "$HOME/.nuget/NuGet/NuGet.Config" <<EOF
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<packageSources>
|
|
||||||
<clear />
|
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
|
||||||
<add key="loongnix" value="https://lnuget.loongnix.cn/v3/index.json" allowInsecureConnections="true" />
|
|
||||||
</packageSources>
|
|
||||||
</configuration>
|
|
||||||
EOF
|
|
||||||
|
|
||||||
dotnet --info >/dev/null 2>&1 && install_ok=1
|
dotnet --info >/dev/null 2>&1 && install_ok=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -610,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.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1), libfreetype6 (>= 2.11)"
|
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.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1), libfreetype6 (>= 2.11)"
|
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.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1), libfreetype6 (>= 2.11)"
|
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
|
||||||
@@ -753,4 +753,4 @@ main() {
|
|||||||
print_summary
|
print_summary
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -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>12.7</string>
|
<string>13.6</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ BUILD_FROM=""
|
|||||||
XRAY_VER="${XRAY_VER:-}"
|
XRAY_VER="${XRAY_VER:-}"
|
||||||
SING_VER="${SING_VER:-}"
|
SING_VER="${SING_VER:-}"
|
||||||
|
|
||||||
MIN_KERNEL="5.10"
|
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}"
|
||||||
@@ -112,20 +112,7 @@ install_dependencies() {
|
|||||||
|
|
||||||
export PATH="$HOME/.dotnet:$PATH"
|
export PATH="$HOME/.dotnet:$PATH"
|
||||||
export DOTNET_ROOT="$HOME/.dotnet"
|
export DOTNET_ROOT="$HOME/.dotnet"
|
||||||
|
|
||||||
mkdir -p "$HOME/.nuget/NuGet"
|
|
||||||
|
|
||||||
cat > "$HOME/.nuget/NuGet/NuGet.Config" <<EOF
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<packageSources>
|
|
||||||
<clear />
|
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
|
||||||
<add key="loongnix" value="https://lnuget.loongnix.cn/v3/index.json" allowInsecureConnections="true" />
|
|
||||||
</packageSources>
|
|
||||||
</configuration>
|
|
||||||
EOF
|
|
||||||
|
|
||||||
dotnet --info >/dev/null 2>&1 || install_ok=0
|
dotnet --info >/dev/null 2>&1 || install_ok=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -523,13 +510,13 @@ ExclusiveArch: loongarch64
|
|||||||
Source0: __PKGROOT__.tar.gz
|
Source0: __PKGROOT__.tar.gz
|
||||||
|
|
||||||
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||||
Requires: glibc >= 2.34
|
Requires: glibc >= 2.39
|
||||||
Requires: fontconfig >= 2.13.1
|
Requires: fontconfig >= 2.15.0
|
||||||
Requires: desktop-file-utils >= 0.26
|
Requires: desktop-file-utils >= 0.26
|
||||||
Requires: xdg-utils >= 1.1.3
|
Requires: xdg-utils >= 1.1.3
|
||||||
Requires: coreutils >= 8.32
|
Requires: coreutils >= 9.4
|
||||||
Requires: bash >= 5.1
|
Requires: bash >= 5.2.21
|
||||||
Requires: freetype >= 2.10
|
Requires: freetype >= 2.13
|
||||||
|
|
||||||
%description
|
%description
|
||||||
v2rayN Linux for Red Hat Enterprise Linux
|
v2rayN Linux for Red Hat Enterprise Linux
|
||||||
|
|||||||
@@ -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}"
|
||||||
@@ -509,13 +509,13 @@ ExclusiveArch: riscv64
|
|||||||
Source0: __PKGROOT__.tar.gz
|
Source0: __PKGROOT__.tar.gz
|
||||||
|
|
||||||
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||||
Requires: glibc >= 2.34
|
Requires: glibc >= 2.39
|
||||||
Requires: fontconfig >= 2.13.1
|
Requires: fontconfig >= 2.15.0
|
||||||
Requires: desktop-file-utils >= 0.26
|
Requires: desktop-file-utils >= 0.26
|
||||||
Requires: xdg-utils >= 1.1.3
|
Requires: xdg-utils >= 1.1.3
|
||||||
Requires: coreutils >= 8.32
|
Requires: coreutils >= 9.4
|
||||||
Requires: bash >= 5.1
|
Requires: bash >= 5.2.21
|
||||||
Requires: freetype >= 2.10
|
Requires: freetype >= 2.13
|
||||||
|
|
||||||
%description
|
%description
|
||||||
v2rayN Linux for Red Hat Enterprise Linux
|
v2rayN Linux for Red Hat Enterprise Linux
|
||||||
|
|||||||
@@ -498,13 +498,13 @@ ExclusiveArch: aarch64 x86_64
|
|||||||
Source0: __PKGROOT__.tar.gz
|
Source0: __PKGROOT__.tar.gz
|
||||||
|
|
||||||
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||||
Requires: glibc >= 2.34
|
Requires: glibc >= 2.39
|
||||||
Requires: fontconfig >= 2.13.1
|
Requires: fontconfig >= 2.15.0
|
||||||
Requires: desktop-file-utils >= 0.26
|
Requires: desktop-file-utils >= 0.26
|
||||||
Requires: xdg-utils >= 1.1.3
|
Requires: xdg-utils >= 1.1.3
|
||||||
Requires: coreutils >= 8.32
|
Requires: coreutils >= 9.4
|
||||||
Requires: bash >= 5.1
|
Requires: bash >= 5.2.21
|
||||||
Requires: freetype >= 2.10
|
Requires: freetype >= 2.13
|
||||||
|
|
||||||
%description
|
%description
|
||||||
v2rayN Linux for Red Hat Enterprise Linux
|
v2rayN Linux for Red Hat Enterprise Linux
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.23.0</Version>
|
<Version>7.24.6</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.2" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.17" />
|
<PackageVersion Include="Avalonia.Desktop" Version="12.1.1" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.17" />
|
<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.11.0" />
|
<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="11.4.13" />
|
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.1" />
|
||||||
<PackageVersion Include="CliWrap" Version="3.10.1" />
|
<PackageVersion Include="CliWrap" Version="3.10.4" />
|
||||||
<PackageVersion Include="Downloader" Version="5.7.0" />
|
<PackageVersion Include="Downloader" Version="5.9.5" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.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="11.3.14" />
|
<PackageVersion Include="Semi.Avalonia" Version="12.1.0.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" />
|
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0.1" />
|
||||||
<PackageVersion Include="NLog" Version="6.1.3" />
|
<PackageVersion Include="NLog" Version="6.1.4" />
|
||||||
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
|
||||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.2" />
|
<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" />
|
||||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||||
<PackageVersion Include="YamlDotNet" Version="18.0.0" />
|
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
|
||||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Submodule v2rayN/GlobalHotKeys updated: 569a95bb0f...162d401dfe
Submodule v2rayN/NetBridge deleted from e9b16cd187
@@ -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,
|
||||||
@@ -130,6 +131,42 @@ internal static class CoreConfigTestFactory
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ProfileItem CreateHttpNode(ECoreType coreType, string indexId = "node-http-1",
|
||||||
|
string remarks = "demo-http")
|
||||||
|
{
|
||||||
|
return new ProfileItem
|
||||||
|
{
|
||||||
|
IndexId = indexId,
|
||||||
|
ConfigType = EConfigType.HTTP,
|
||||||
|
CoreType = coreType,
|
||||||
|
Remarks = remarks,
|
||||||
|
Address = "proxy.example.com",
|
||||||
|
Port = 8080,
|
||||||
|
Password = "pass",
|
||||||
|
Username = "user",
|
||||||
|
Network = nameof(ETransport.raw),
|
||||||
|
StreamSecurity = string.Empty,
|
||||||
|
Subid = string.Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using AwesomeAssertions;
|
using AwesomeAssertions;
|
||||||
using ServiceLib.Common;
|
using ServiceLib.Common;
|
||||||
using ServiceLib.Enums;
|
using ServiceLib.Enums;
|
||||||
|
using ServiceLib.Handler.Fmt;
|
||||||
using ServiceLib.Manager;
|
using ServiceLib.Manager;
|
||||||
using ServiceLib.Models;
|
using ServiceLib.Models;
|
||||||
|
using ServiceLib.Models.Dto;
|
||||||
using ServiceLib.Services.CoreConfig;
|
using ServiceLib.Services.CoreConfig;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -557,4 +559,107 @@ public class CoreConfigSingboxServiceTests
|
|||||||
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Global));
|
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Global));
|
||||||
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Direct));
|
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Direct));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_Hysteria2Realm_ShouldEmitHttpsServerUrl()
|
||||||
|
{
|
||||||
|
var shareLink =
|
||||||
|
"hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test";
|
||||||
|
var node = Hysteria2Fmt.ResolveRealm(shareLink, out _);
|
||||||
|
node.Should().NotBeNull();
|
||||||
|
node!.CoreType = ECoreType.sing_box;
|
||||||
|
|
||||||
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
|
config.CoreTypeItem =
|
||||||
|
[
|
||||||
|
new CoreTypeItem { ConfigType = EConfigType.Hysteria2, CoreType = ECoreType.sing_box }
|
||||||
|
];
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box);
|
||||||
|
|
||||||
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||||
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
var proxy = cfg.outbounds.First(o => o.tag == Global.ProxyTag);
|
||||||
|
|
||||||
|
proxy.type.Should().Be("hysteria2");
|
||||||
|
proxy.realm.Should().NotBeNull();
|
||||||
|
proxy.realm!.server_url.Should().StartWith("https://");
|
||||||
|
proxy.realm.server_url.Should().Contain("realm.hy2.io");
|
||||||
|
proxy.realm.token.Should().Be("public");
|
||||||
|
proxy.realm.realm_id.Should().Be("my-realm-id");
|
||||||
|
proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478");
|
||||||
|
proxy.server.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_TunSystemStackWithIpv6_ShouldUsePrefixWithPeerAddress()
|
||||||
|
{
|
||||||
|
// Regression test for #9820: sing-box fails with "need one more IPv6 address in
|
||||||
|
// first prefix for system stack" when the TUN inbound uses a /128 IPv6 prefix.
|
||||||
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
|
config.TunModeItem.EnableTun = true;
|
||||||
|
config.TunModeItem.Stack = "system";
|
||||||
|
config.TunModeItem.EnableIPv6Address = true;
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
|
||||||
|
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.sing_box);
|
||||||
|
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with
|
||||||
|
{
|
||||||
|
IsTunEnabled = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||||
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||||
|
var tun = cfg.inbounds.First(i => i.type == "tun");
|
||||||
|
|
||||||
|
tun.address.Should().NotBeNullOrEmpty();
|
||||||
|
foreach (var address in tun.address!)
|
||||||
|
{
|
||||||
|
var prefixLength = int.Parse(address[(address.LastIndexOf('/') + 1)..]);
|
||||||
|
var isIpv6 = address.Contains(':');
|
||||||
|
prefixLength.Should().BeLessThanOrEqualTo(isIpv6 ? 126 : 30,
|
||||||
|
$"'{address}' must leave room for the peer address the system stack derives");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
||||||
|
{
|
||||||
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
|
||||||
|
var customNode = CoreConfigTestFactory.CreateCustomOutboundNode(ECoreType.sing_box, "n-custom", "custom-singbox");
|
||||||
|
var customJsonContent = """
|
||||||
|
{
|
||||||
|
"type": "shadowsocks",
|
||||||
|
"server": "1.2.3.4",
|
||||||
|
"server_port": 8388,
|
||||||
|
"method": "aes-128-gcm",
|
||||||
|
"password": "custom_password"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
var context = CoreConfigTestFactory.CreateContext(config, customNode, ECoreType.sing_box);
|
||||||
|
context.CustomOutboundContent[customNode.IndexId] = customJsonContent;
|
||||||
|
|
||||||
|
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||||
|
result.Data.Should().NotBeNull();
|
||||||
|
|
||||||
|
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString());
|
||||||
|
cfg.Should().NotBeNull();
|
||||||
|
var proxyOutbound = cfg!.outbounds.FirstOrDefault(o => o.tag == Global.ProxyTag);
|
||||||
|
proxyOutbound.Should().NotBeNull();
|
||||||
|
proxyOutbound!.type.Should().Be("shadowsocks");
|
||||||
|
proxyOutbound.server.Should().Be("1.2.3.4");
|
||||||
|
proxyOutbound.server_port.Should().Be(8388);
|
||||||
|
proxyOutbound.method.Should().Be("aes-128-gcm");
|
||||||
|
proxyOutbound.password.Should().Be("custom_password");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,39 @@ public class CoreConfigV2rayServiceTests
|
|||||||
v2rayConfig.inbounds.Should().Contain(i => i.protocol == nameof(EInboundProtocol.mixed));
|
v2rayConfig.inbounds.Should().Contain(i => i.protocol == nameof(EInboundProtocol.mixed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateClientConfigContent_HttpOutbound_ShouldEmitHeadersInSettings()
|
||||||
|
{
|
||||||
|
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||||
|
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||||
|
var node = CoreConfigTestFactory.CreateHttpNode(ECoreType.Xray);
|
||||||
|
node.SetProtocolExtra(node.GetProtocolExtra() with
|
||||||
|
{
|
||||||
|
HttpHeaders = "{\"User-Agent\":\"v2rayN\",\"Set-Cookie\":[\"a=1\",\"b=2\"]}",
|
||||||
|
});
|
||||||
|
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 outbound = cfg.outbounds.First(o => o.tag == Global.ProxyTag && o.protocol == "http");
|
||||||
|
|
||||||
|
outbound.settings.address?.ToString().Should().Be("proxy.example.com");
|
||||||
|
outbound.settings.port.Should().Be(8080);
|
||||||
|
outbound.settings.user.Should().Be("user");
|
||||||
|
outbound.settings.pass.Should().Be("pass");
|
||||||
|
outbound.settings.level.Should().Be(1);
|
||||||
|
outbound.settings.headers.Should().NotBeNull();
|
||||||
|
var headers = JsonUtils.ParseJson(outbound.settings.headers.ToString());
|
||||||
|
headers["User-Agent"]!.GetValue<string>().Should().Be("v2rayN");
|
||||||
|
headers["Set-Cookie"]!.AsArray()
|
||||||
|
.Select(item => item!.GetValue<string>())
|
||||||
|
.Should().Equal("a=1", "b=2");
|
||||||
|
outbound.settings.servers.Should().BeNull();
|
||||||
|
outbound.settings.vnext.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GenerateClientConfigContent_PolicyGroup_ShouldExpandChildrenAndBuildBalancer()
|
public void GenerateClientConfigContent_PolicyGroup_ShouldExpandChildrenAndBuildBalancer()
|
||||||
{
|
{
|
||||||
@@ -534,7 +567,7 @@ 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");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -559,4 +592,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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using AwesomeAssertions;
|
using AwesomeAssertions;
|
||||||
|
using ServiceLib.Handler.Fmt;
|
||||||
|
using ServiceLib.Models.Dto;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace ServiceLib.Tests.Fmt;
|
namespace ServiceLib.Tests.Fmt;
|
||||||
@@ -58,4 +60,32 @@ public class HyRealmTests
|
|||||||
uri.Should().Contain("hysteria2+realm://mytoken@rendezvous.example.com");
|
uri.Should().Contain("hysteria2+realm://mytoken@rendezvous.example.com");
|
||||||
uri.Should().EndWith("#remark");
|
uri.Should().EndWith("#remark");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToServerUrl_ShouldIncludeSchemeForSingbox()
|
||||||
|
{
|
||||||
|
var realm = new HyRealm(
|
||||||
|
IsHttp: false,
|
||||||
|
Token: "public",
|
||||||
|
RendezvousHost: "realm.hy2.io",
|
||||||
|
RendezvousPort: 443,
|
||||||
|
RealmName: "my-realm-id",
|
||||||
|
StunList: ["turn.cloudflare.com:3478"]
|
||||||
|
);
|
||||||
|
|
||||||
|
realm.ToServerUrl().Should().Be("https://realm.hy2.io:443");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveRealm_Issue9635_ShouldProduceHttpsServerUrl()
|
||||||
|
{
|
||||||
|
var str = "hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test";
|
||||||
|
var resolved = Hysteria2Fmt.ResolveRealm(str, out _);
|
||||||
|
resolved.Should().NotBeNull();
|
||||||
|
|
||||||
|
HyRealm.TryParse(resolved!.GetProtocolExtra().Hy2RealmUrl, out var realm).Should().BeTrue();
|
||||||
|
realm!.ToServerUrl().Should().StartWith("https://");
|
||||||
|
realm.ToServerUrl().Should().Contain("realm.hy2.io");
|
||||||
|
realm.StunList.Should().Contain("turn.cloudflare.com:3478");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
v2rayN/ServiceLib/Base/ICloseable.cs
Normal file
6
v2rayN/ServiceLib/Base/ICloseable.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ServiceLib.Base;
|
||||||
|
|
||||||
|
public interface ICloseable
|
||||||
|
{
|
||||||
|
public event EventHandler? RequestClose;
|
||||||
|
}
|
||||||
7
v2rayN/ServiceLib/Base/IWindowDialog.cs
Normal file
7
v2rayN/ServiceLib/Base/IWindowDialog.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace ServiceLib.Base;
|
||||||
|
|
||||||
|
public interface IWindowDialog
|
||||||
|
{
|
||||||
|
public Task<bool> ShowDialogAsync<TViewModel>(TViewModel vm)
|
||||||
|
where TViewModel : class;
|
||||||
|
}
|
||||||
@@ -3,5 +3,4 @@ namespace ServiceLib.Base;
|
|||||||
public class MyReactiveObject : ReactiveObject
|
public class MyReactiveObject : ReactiveObject
|
||||||
{
|
{
|
||||||
protected static Config? _config;
|
protected static Config? _config;
|
||||||
protected Func<EViewAction, object?, Task<bool>>? _updateView;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public static class Extension
|
|||||||
|
|
||||||
public static bool IsComplexType(this EConfigType configType)
|
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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ internal static class WindowsUtils
|
|||||||
|
|
||||||
public static async Task RemoveTunDevice()
|
public static async Task RemoveTunDevice()
|
||||||
{
|
{
|
||||||
var tunNameList = new List<string> { "wintunsingbox_tun", "xray_tun" };
|
var tunNameList = new List<string> { "singbox_tun", "xray_tun" };
|
||||||
foreach (var tunName in tunNameList)
|
foreach (var tunName in tunNameList)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sum = MD5.HashData(Encoding.UTF8.GetBytes(tunName));
|
var sum = MD5.HashData(Encoding.UTF8.GetBytes($"wintun{tunName}"));
|
||||||
var guid = new Guid(sum);
|
var guid = new Guid(sum);
|
||||||
var pnpUtilPath = @"C:\Windows\System32\pnputil.exe";
|
var pnpUtilPath = @"C:\Windows\System32\pnputil.exe";
|
||||||
var arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """;
|
var arg = $$""" /remove-device "SWD\Wintun\{{{guid}}}" """;
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
namespace ServiceLib.Enums;
|
|
||||||
|
|
||||||
public enum EViewAction
|
|
||||||
{
|
|
||||||
CloseWindow,
|
|
||||||
ShowYesNo,
|
|
||||||
SaveFileDialog,
|
|
||||||
AddBatchRoutingRulesYesNo,
|
|
||||||
SetClipboardData,
|
|
||||||
AddServerViaClipboard,
|
|
||||||
ImportRulesFromClipboard,
|
|
||||||
ProfilesFocus,
|
|
||||||
ShareSub,
|
|
||||||
ShareServer,
|
|
||||||
ScanScreenTask,
|
|
||||||
ScanImageTask,
|
|
||||||
BrowseServer,
|
|
||||||
ImportRulesFromFile,
|
|
||||||
InitSettingFont,
|
|
||||||
PasswordInput,
|
|
||||||
SubEditWindow,
|
|
||||||
RoutingRuleSettingWindow,
|
|
||||||
RoutingRuleDetailsWindow,
|
|
||||||
AddServerWindow,
|
|
||||||
AddServer2Window,
|
|
||||||
AddGroupServerWindow,
|
|
||||||
DNSSettingWindow,
|
|
||||||
RoutingSettingWindow,
|
|
||||||
OptionSettingWindow,
|
|
||||||
FullConfigTemplateWindow,
|
|
||||||
GlobalHotkeySettingWindow,
|
|
||||||
SubSettingWindow,
|
|
||||||
DispatcherRefreshServersBiz,
|
|
||||||
DispatcherRefreshIcon,
|
|
||||||
DispatcherShowMsg,
|
|
||||||
}
|
|
||||||
@@ -2,30 +2,16 @@ namespace ServiceLib.Events;
|
|||||||
|
|
||||||
public static class AppEvents
|
public static class AppEvents
|
||||||
{
|
{
|
||||||
public static readonly EventChannel<Unit> ReloadRequested = new();
|
public static readonly EventChannel<RxVoid> AddServerViaClipboardRequested = new();
|
||||||
public static readonly EventChannel<bool?> ShowHideWindowRequested = new();
|
|
||||||
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
|
|
||||||
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
|
|
||||||
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
|
|
||||||
public static readonly EventChannel<bool> HasUpdateNotified = new();
|
public static readonly EventChannel<bool> HasUpdateNotified = new();
|
||||||
|
|
||||||
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
|
|
||||||
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
|
|
||||||
public static readonly EventChannel<Unit> ProxiesReloadRequested = new();
|
|
||||||
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
||||||
|
|
||||||
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<Unit> AdjustMainLvColWidthRequested = new();
|
|
||||||
|
|
||||||
public static readonly EventChannel<string> SetDefaultServerRequested = new();
|
|
||||||
|
|
||||||
public static readonly EventChannel<Unit> RoutingsMenuRefreshRequested = new();
|
|
||||||
public static readonly EventChannel<Unit> TestServerRequested = new();
|
|
||||||
public static readonly EventChannel<Unit> InboundDisplayRequested = new();
|
|
||||||
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
|
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";
|
||||||
@@ -42,6 +40,11 @@ public class Global
|
|||||||
public const string KillAsSudoOSXShellFileName = NamespaceSample + "kill_as_sudo_osx_sh";
|
public const string KillAsSudoOSXShellFileName = NamespaceSample + "kill_as_sudo_osx_sh";
|
||||||
public const string KillAsSudoLinuxShellFileName = NamespaceSample + "kill_as_sudo_linux_sh";
|
public const string KillAsSudoLinuxShellFileName = NamespaceSample + "kill_as_sudo_linux_sh";
|
||||||
public const string SingboxFakeIPFilterFileName = NamespaceSample + "singbox_fakeip_filter";
|
public const string SingboxFakeIPFilterFileName = NamespaceSample + "singbox_fakeip_filter";
|
||||||
|
public const string ChromeRootCertFileName = NamespaceSample + "chrome_roots_pem";
|
||||||
|
public const string MozillaRootCertFileName = NamespaceSample + "mozilla_roots_pem";
|
||||||
|
|
||||||
|
public const string ChromeRootProvider = "chrome";
|
||||||
|
public const string MozillaRootProvider = "mozilla";
|
||||||
|
|
||||||
public const string DefaultSecurity = "auto";
|
public const string DefaultSecurity = "auto";
|
||||||
public const string DefaultNetwork = "raw";
|
public const string DefaultNetwork = "raw";
|
||||||
@@ -92,6 +95,7 @@ public class Global
|
|||||||
public const string LinuxBash = "/bin/bash";
|
public const string LinuxBash = "/bin/bash";
|
||||||
public const string StringTrue = "true";
|
public const string StringTrue = "true";
|
||||||
public const string StringFalse = "false";
|
public const string StringFalse = "false";
|
||||||
|
public const int SqliteMaxBatchSize = 10000;
|
||||||
|
|
||||||
public const string SingboxDirectDNSTag = "direct_dns";
|
public const string SingboxDirectDNSTag = "direct_dns";
|
||||||
public const string SingboxRemoteDNSTag = "remote_dns";
|
public const string SingboxRemoteDNSTag = "remote_dns";
|
||||||
@@ -100,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 =
|
||||||
@@ -551,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 =
|
||||||
@@ -731,5 +732,42 @@ public class Global
|
|||||||
"reply",
|
"reply",
|
||||||
];
|
];
|
||||||
|
|
||||||
#endregion const
|
public static readonly List<string> FakeIPRanges =
|
||||||
|
[
|
||||||
|
"198.18.0.0/15",
|
||||||
|
"11.0.0.0/8",
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> RootCertProviders =
|
||||||
|
[
|
||||||
|
"system",
|
||||||
|
ChromeRootProvider,
|
||||||
|
MozillaRootProvider,
|
||||||
|
];
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -193,12 +197,16 @@ public class CoreConfigContextBuilder
|
|||||||
if (preSocksItem != null)
|
if (preSocksItem != null)
|
||||||
{
|
{
|
||||||
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
|
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
|
||||||
|
|
||||||
|
var protectCoreTypeList = new HashSet<ECoreType>(nodeContext.ProtectCoreTypeList) { nodeContext.RunCoreType };
|
||||||
|
|
||||||
return preSocksResult with
|
return preSocksResult with
|
||||||
{
|
{
|
||||||
Context = preSocksResult.Context with
|
Context = preSocksResult.Context with
|
||||||
{
|
{
|
||||||
ProtectDomainList =
|
ProtectDomainList =
|
||||||
[.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
|
[.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
|
||||||
|
ProtectCoreTypeList = protectCoreTypeList,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -317,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())
|
||||||
{
|
{
|
||||||
@@ -332,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)
|
||||||
{
|
{
|
||||||
@@ -433,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,9 +92,13 @@ 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))
|
||||||
|
{
|
||||||
|
config.GuiItem.RootCertProvider = Global.RootCertProviders.First();
|
||||||
|
}
|
||||||
config.MsgUIItem ??= new();
|
config.MsgUIItem ??= new();
|
||||||
|
|
||||||
config.UiItem ??= new();
|
config.UiItem ??= new();
|
||||||
@@ -111,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)
|
||||||
@@ -161,17 +167,27 @@ public static class ConfigHandler
|
|||||||
config.SystemProxyItem ??= new();
|
config.SystemProxyItem ??= new();
|
||||||
config.WebDavItem ??= new();
|
config.WebDavItem ??= new();
|
||||||
config.CheckUpdateItem ??= new();
|
config.CheckUpdateItem ??= new();
|
||||||
config.NetBridgeItem ??= new()
|
|
||||||
{
|
|
||||||
RuleProcess = string.Empty
|
|
||||||
};
|
|
||||||
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() ?? [];
|
||||||
@@ -1511,7 +1573,8 @@ public static class ConfigHandler
|
|||||||
else if (node.ConfigType == EConfigType.Custom
|
else if (node.ConfigType == EConfigType.Custom
|
||||||
&& node.PreSocksPort is > 0 and <= 65535)
|
&& node.PreSocksPort is > 0 and <= 65535)
|
||||||
{
|
{
|
||||||
var preCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
var customPreCoreType = AppManager.Instance.GetCoreType(null, EConfigType.Custom);
|
||||||
|
var preCoreType = (enableLegacyProtect && config.TunModeItem.EnableTun) ? ECoreType.sing_box : customPreCoreType;
|
||||||
itemSocks = new ProfileItem()
|
itemSocks = new ProfileItem()
|
||||||
{
|
{
|
||||||
CoreType = preCoreType,
|
CoreType = preCoreType,
|
||||||
@@ -1658,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1820,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)
|
||||||
@@ -2021,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())
|
||||||
@@ -2061,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}'");
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public static class ConnectionHandler
|
|||||||
|
|
||||||
for (var i = 0; i < 2; i++)
|
for (var i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
responseTime = await GetRealPingTime(webProxy, 10);
|
responseTime = await GetRealPingTime(webProxy);
|
||||||
if (responseTime > 0)
|
if (responseTime > 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -66,7 +66,7 @@ public static class ConnectionHandler
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Measures response time by sending HTTP requests through proxy.
|
/// Measures response time by sending HTTP requests through proxy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static async Task<int> GetRealPingTime(IWebProxy? webProxy, int downloadTimeout)
|
public static async Task<int> GetRealPingTime(IWebProxy? webProxy, int downloadTimeout = 9)
|
||||||
{
|
{
|
||||||
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
||||||
var responseTime = -1;
|
var responseTime = -1;
|
||||||
@@ -77,7 +77,8 @@ public static class ConnectionHandler
|
|||||||
using var client = new HttpClient(new SocketsHttpHandler()
|
using var client = new HttpClient(new SocketsHttpHandler()
|
||||||
{
|
{
|
||||||
Proxy = webProxy,
|
Proxy = webProxy,
|
||||||
UseProxy = webProxy != null
|
UseProxy = webProxy != null,
|
||||||
|
ConnectTimeout = TimeSpan.FromSeconds(3)
|
||||||
});
|
});
|
||||||
|
|
||||||
List<int> oneTime = [];
|
List<int> oneTime = [];
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ public class Hysteria2Fmt : BaseFmt
|
|||||||
Hy2RealmUrl = realm.ToUri(),
|
Hy2RealmUrl = realm.ToUri(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
msg = string.Empty;
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +162,10 @@ 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");
|
||||||
@@ -197,6 +202,10 @@ 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(','))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Security.Authentication;
|
||||||
using Downloader;
|
using Downloader;
|
||||||
|
|
||||||
namespace ServiceLib.Helper;
|
namespace ServiceLib.Helper;
|
||||||
@@ -14,6 +15,8 @@ public class DownloaderHelper
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var connectTimeout = Math.Clamp(timeout / 5, 2, 5);
|
||||||
|
|
||||||
Uri uri = new(url);
|
Uri uri = new(url);
|
||||||
//Authorization Header
|
//Authorization Header
|
||||||
var headers = new WebHeaderCollection();
|
var headers = new WebHeaderCollection();
|
||||||
@@ -22,17 +25,19 @@ public class DownloaderHelper
|
|||||||
headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo));
|
headers.Add(HttpRequestHeader.Authorization, "Basic " + Utils.Base64Encode(uri.UserInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var requestConfiguration = new RequestConfiguration()
|
||||||
|
{
|
||||||
|
Headers = headers,
|
||||||
|
UserAgent = userAgent,
|
||||||
|
ConnectTimeout = connectTimeout * 1000,
|
||||||
|
Proxy = webProxy
|
||||||
|
};
|
||||||
var downloadOpt = new DownloadConfiguration()
|
var downloadOpt = new DownloadConfiguration()
|
||||||
{
|
{
|
||||||
BlockTimeout = timeout * 1000,
|
BlockTimeout = timeout * 1000,
|
||||||
MaxTryAgainOnFailure = 2,
|
MaxTryAgainOnFailure = 2,
|
||||||
RequestConfiguration =
|
RequestConfiguration = requestConfiguration,
|
||||||
{
|
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
|
||||||
Headers = headers,
|
|
||||||
UserAgent = userAgent,
|
|
||||||
ConnectTimeout = timeout * 1000,
|
|
||||||
Proxy = webProxy
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||||
@@ -45,7 +50,9 @@ public class DownloaderHelper
|
|||||||
};
|
};
|
||||||
|
|
||||||
using var cts = new CancellationTokenSource();
|
using var cts = new CancellationTokenSource();
|
||||||
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
|
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
|
||||||
|
|
||||||
|
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
||||||
using StreamReader reader = new(stream);
|
using StreamReader reader = new(stream);
|
||||||
|
|
||||||
downloadOpt = null;
|
downloadOpt = null;
|
||||||
@@ -60,15 +67,18 @@ public class DownloaderHelper
|
|||||||
throw new ArgumentNullException(nameof(url));
|
throw new ArgumentNullException(nameof(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var connectTimeout = Math.Clamp(timeout / 5, 2, 5);
|
||||||
|
var requestConfiguration = new RequestConfiguration()
|
||||||
|
{
|
||||||
|
ConnectTimeout = connectTimeout * 1000,
|
||||||
|
Proxy = webProxy
|
||||||
|
};
|
||||||
var downloadOpt = new DownloadConfiguration()
|
var downloadOpt = new DownloadConfiguration()
|
||||||
{
|
{
|
||||||
BlockTimeout = timeout * 1000,
|
BlockTimeout = timeout * 1000,
|
||||||
MaxTryAgainOnFailure = 2,
|
MaxTryAgainOnFailure = 2,
|
||||||
RequestConfiguration =
|
RequestConfiguration = requestConfiguration,
|
||||||
{
|
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
|
||||||
ConnectTimeout= timeout * 1000,
|
|
||||||
Proxy = webProxy
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var lastUpdateTime = DateTime.Now;
|
var lastUpdateTime = DateTime.Now;
|
||||||
@@ -116,7 +126,7 @@ public class DownloaderHelper
|
|||||||
};
|
};
|
||||||
//progress.Report("......");
|
//progress.Report("......");
|
||||||
using var cts = new CancellationTokenSource();
|
using var cts = new CancellationTokenSource();
|
||||||
cts.CancelAfter(timeout * 1000);
|
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
|
||||||
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
await using var stream = await downloader.DownloadFileTaskAsync(address: url, cts.Token);
|
||||||
|
|
||||||
downloadOpt = null;
|
downloadOpt = null;
|
||||||
@@ -137,15 +147,18 @@ public class DownloaderHelper
|
|||||||
File.Delete(fileName);
|
File.Delete(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var connectTimeout = Math.Clamp(timeout / 5, 2, 5);
|
||||||
|
var requestConfiguration = new RequestConfiguration()
|
||||||
|
{
|
||||||
|
ConnectTimeout = connectTimeout * 1000,
|
||||||
|
Proxy = webProxy
|
||||||
|
};
|
||||||
var downloadOpt = new DownloadConfiguration()
|
var downloadOpt = new DownloadConfiguration()
|
||||||
{
|
{
|
||||||
BlockTimeout = timeout * 1000,
|
BlockTimeout = timeout * 1000,
|
||||||
MaxTryAgainOnFailure = 2,
|
MaxTryAgainOnFailure = 2,
|
||||||
RequestConfiguration =
|
RequestConfiguration = requestConfiguration,
|
||||||
{
|
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
|
||||||
ConnectTimeout= timeout * 1000,
|
|
||||||
Proxy = webProxy
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var progressPercentage = 0;
|
var progressPercentage = 0;
|
||||||
@@ -182,4 +195,75 @@ public class DownloaderHelper
|
|||||||
|
|
||||||
downloadOpt = null;
|
downloadOpt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/bezzad/Downloader/blob/a75a6e431acd6cbba6293f7afdcf676544a09174/src/Downloader/SocketClient.cs#L45
|
||||||
|
// There is a risk of MITM attacks
|
||||||
|
// https://github.com/bezzad/Downloader/blob/a75a6e431acd6cbba6293f7afdcf676544a09174/src/Downloader/Extensions/ExceptionHelper.cs#L111
|
||||||
|
private static SocketsHttpHandler GetSocketsHttpHandler(RequestConfiguration config)
|
||||||
|
{
|
||||||
|
SocketsHttpHandler handler = new()
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = config.AllowAutoRedirect,
|
||||||
|
MaxAutomaticRedirections = config.MaximumAutomaticRedirections,
|
||||||
|
AutomaticDecompression = config.AutomaticDecompression,
|
||||||
|
PreAuthenticate = config.PreAuthenticate,
|
||||||
|
UseCookies = config.CookieContainer != null,
|
||||||
|
UseProxy = config.Proxy != null,
|
||||||
|
MaxConnectionsPerServer = 1000,
|
||||||
|
PooledConnectionIdleTimeout = config.KeepAliveTimeout,
|
||||||
|
PooledConnectionLifetime = Timeout.InfiniteTimeSpan,
|
||||||
|
EnableMultipleHttp2Connections = true,
|
||||||
|
ConnectTimeout = TimeSpan.FromMilliseconds(config.ConnectTimeout)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up the SslClientAuthenticationOptions for custom certificate validation
|
||||||
|
if (config.ClientCertificates?.Count > 0)
|
||||||
|
{
|
||||||
|
handler.SslOptions.ClientCertificates = config.ClientCertificates;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.SslOptions.EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12;
|
||||||
|
//handler.SslOptions.RemoteCertificateValidationCallback = ExceptionHelper.CertificateValidationCallBack;
|
||||||
|
|
||||||
|
var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy();
|
||||||
|
if (certificateChainPolicy != null)
|
||||||
|
{
|
||||||
|
handler.SslOptions.CertificateChainPolicy = certificateChainPolicy;
|
||||||
|
handler.SslOptions.RemoteCertificateValidationCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure keep-alive
|
||||||
|
if (config.KeepAlive)
|
||||||
|
{
|
||||||
|
handler.KeepAlivePingTimeout = config.KeepAliveTimeout;
|
||||||
|
handler.KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure credentials
|
||||||
|
if (config.Credentials != null)
|
||||||
|
{
|
||||||
|
handler.Credentials = config.Credentials;
|
||||||
|
handler.PreAuthenticate = config.PreAuthenticate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure cookies
|
||||||
|
if (handler.UseCookies && config.CookieContainer != null)
|
||||||
|
{
|
||||||
|
handler.CookieContainer = config.CookieContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure proxy
|
||||||
|
if (handler.UseProxy && config.Proxy != null)
|
||||||
|
{
|
||||||
|
handler.Proxy = config.Proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add expect header
|
||||||
|
if (!string.IsNullOrWhiteSpace(config.Expect))
|
||||||
|
{
|
||||||
|
handler.Expect100ContinueTimeout = TimeSpan.FromSeconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public sealed class AppManager
|
|||||||
private int? _statePort2;
|
private int? _statePort2;
|
||||||
public static AppManager Instance => _instance.Value;
|
public static AppManager Instance => _instance.Value;
|
||||||
public Config Config => _config;
|
public Config Config => _config;
|
||||||
|
public IWindowDialog WindowDialog { get; set; } = null!;
|
||||||
|
|
||||||
public int StatePort
|
public int StatePort
|
||||||
{
|
{
|
||||||
@@ -244,9 +245,26 @@ public sealed class AppManager
|
|||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
|
||||||
.Where(it => ids.Contains(it.IndexId))
|
if (ids.Count <= Global.SqliteMaxBatchSize)
|
||||||
.ToListAsync();
|
{
|
||||||
|
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||||
|
.Where(it => ids.Contains(it.IndexId))
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = new List<ProfileItem>();
|
||||||
|
for (var size = 0; size < ids.Count; size += Global.SqliteMaxBatchSize)
|
||||||
|
{
|
||||||
|
var chunk = ids.Skip(size).Take(Global.SqliteMaxBatchSize).ToList();
|
||||||
|
var chunkItems = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||||
|
.Where(it => chunk.Contains(it.IndexId))
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
items.AddRange(chunkItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Dictionary<string, ProfileItem>> GetProfileItemsByIndexIdsAsMap(IEnumerable<string> indexIds)
|
public async Task<Dictionary<string, ProfileItem>> GetProfileItemsByIndexIdsAsMap(IEnumerable<string> indexIds)
|
||||||
@@ -257,18 +275,11 @@ public sealed class AppManager
|
|||||||
|
|
||||||
public async Task<List<ProfileItem>> GetProfileItemsOrderedByIndexIds(IEnumerable<string> indexIds)
|
public async Task<List<ProfileItem>> GetProfileItemsOrderedByIndexIds(IEnumerable<string> indexIds)
|
||||||
{
|
{
|
||||||
var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
||||||
if (idList.Count == 0)
|
var items = await GetProfileItemsByIndexIds(ids);
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var items = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
|
||||||
.Where(it => idList.Contains(it.IndexId))
|
|
||||||
.ToListAsync();
|
|
||||||
var itemMap = items.ToDictionary(it => it.IndexId);
|
var itemMap = items.ToDictionary(it => it.IndexId);
|
||||||
|
|
||||||
return idList.Select(itemMap.GetValueOrDefault)
|
return ids.Select(itemMap.GetValueOrDefault)
|
||||||
.Where(item => item != null)
|
.Where(item => item != null)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
@@ -652,7 +663,7 @@ public sealed class AppManager
|
|||||||
return Global.SsSecuritiesInSingbox;
|
return Global.SsSecuritiesInSingbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType)
|
public ECoreType GetCoreType(ProfileItem? profileItem, EConfigType eConfigType)
|
||||||
{
|
{
|
||||||
if (profileItem?.CoreType != null)
|
if (profileItem?.CoreType != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,180 +10,17 @@ public class CertPemManager
|
|||||||
{
|
{
|
||||||
private static readonly string _tag = "CertPemManager";
|
private static readonly string _tag = "CertPemManager";
|
||||||
private static readonly Lazy<CertPemManager> _instance = new(() => new());
|
private static readonly Lazy<CertPemManager> _instance = new(() => new());
|
||||||
|
private Config _config;
|
||||||
|
|
||||||
/// <summary>
|
public async Task Init(Config config)
|
||||||
/// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks
|
|
||||||
/// </summary>
|
|
||||||
private static readonly HashSet<string> TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase)
|
|
||||||
{
|
{
|
||||||
"EBD41040E4BB3EC742C9E381D31EF2A41A48B6685C96E7CEF3C1DF6CD4331C99", // GlobalSign Root CA
|
if (_config != null)
|
||||||
"6DC47172E01CBCB0BF62580D895FE2B8AC9AD4F873801E0C10B9C837D21EB177", // Entrust.net Premium 2048 Secure Server CA
|
{
|
||||||
"73C176434F1BC6D5ADF45B0E76E727287C8DE57616C1E6E6141A2B2CBC7D8E4C", // Entrust Root Certification Authority
|
return;
|
||||||
"D8E0FEBC1DB2E38D00940F37D27D41344D993E734B99D5656D9778D4D8143624", // Certum Root CA
|
}
|
||||||
"D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root
|
_config = config;
|
||||||
"85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2
|
await Task.CompletedTask;
|
||||||
"18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3
|
}
|
||||||
"C3846BF24B9E93CA64274C0EC67C1ECC5E024FFCACD2D74019350E81FE546AE4", // Go Daddy Class 2 CA
|
|
||||||
"1465FA205397B876FAA6F0A9958E5590E40FCC7FAA4FB7C2C8677521FB5FB658", // Starfield Class 2 CA
|
|
||||||
"3E9099B5015E8F486C00BCEA9D111EE721FABA355A89BCF1DF69561E3DC6325C", // DigiCert Assured ID Root CA
|
|
||||||
"4348A0E9444C78CB265E058D5E8944B4D84F9662BD26DB257F8934A443C70161", // DigiCert Global Root CA
|
|
||||||
"7431E5F4C3C1CE4690774F0B61E05440883BA9A01ED00BA6ABD7806ED3B118CF", // DigiCert High Assurance EV Root CA
|
|
||||||
"62DD0BE9B9F50A163EA0F8E75C053B1ECA57EA55C8688F647C6881F2C8357B95", // SwissSign Gold CA - G2
|
|
||||||
"0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66", // COMODO Certification Authority
|
|
||||||
"1793927A0614549789ADCE2F8F34F7F0B66D0F3AE3A3B84D21EC15DBBA4FADC7", // COMODO ECC Certification Authority
|
|
||||||
"41C923866AB4CAD6B7AD578081582E020797A6CBDF4FFF78CE8396B38937D7F5", // OISTE WISeKey Global Root GA CA
|
|
||||||
"E3B6A2DB2ED7CE48842F7AC53241C7B71D54144BFB40C11F3F1D0B42F5EEA12D", // Certigna
|
|
||||||
"C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5", // ePKI Root Certification Authority
|
|
||||||
"6C61DAC3A2DEF031506BE036D2A6FE401994FBD13DF9C8D466599274C446EC98", // NetLock Arany (Class Gold) Főtanúsítvány
|
|
||||||
"3C5F81FEA5FAB82C64BFA2EAECAFCDE8E077FC8620A7CAE537163DF36EDBF378", // Microsec e-Szigno Root CA 2009
|
|
||||||
"CBB522D7B7F127AD6A0113865BDF1CD4102E7D0759AF635A7CF4720DC963C53B", // GlobalSign Root CA - R3
|
|
||||||
"2530CC8E98321502BAD96F9B1FBA1B099E2D299E0F4548BB914F363BC0D4531F", // Izenpe.com
|
|
||||||
"45140B3247EB9CC8C5B4F0D7B53091F73292089E6E5A63E2749DD3ACA9198EDA", // Go Daddy Root Certificate Authority - G2
|
|
||||||
"2CE1CB0BF9D2F9E102993FBE215152C3B2DD0CABDE1C68E5319B839154DBB7F5", // Starfield Root Certificate Authority - G2
|
|
||||||
"568D6905A2C88708A4B3025190EDCFEDB1974A606A13C6E5290FCB2AE63EDAB5", // Starfield Services Root Certificate Authority - G2
|
|
||||||
"5C58468D55F58E497E743982D2B50010B6D165374ACF83A7D4A32DB768C4408E", // Certum Trusted Network CA
|
|
||||||
"BFD88FE1101C41AE3E801BF8BE56350EE9BAD1A6B9BD515EDC5C6D5B8711AC44", // TWCA Root Certification Authority
|
|
||||||
"513B2CECB810D4CDE5DD85391ADFC6C2DD60D87BB736D2B521484AA47A0EBEF6", // Security Communication RootCA2
|
|
||||||
"55926084EC963A64B96E2ABE01CE0BA86A64FBFEBCC7AAB5AFC155B37FD76066", // Actalis Authentication Root CA
|
|
||||||
"9A114025197C5BB95D94E63D55CD43790847B646B23CDF11ADA4A00EFF15FB48", // Buypass Class 2 Root CA
|
|
||||||
"EDF7EBBCA27A2A384D387B7D4010C666E2EDB4843E4C29B4AE1D5B9332E6B24D", // Buypass Class 3 Root CA
|
|
||||||
"FD73DAD31C644FF1B43BEF0CCDDA96710B9CD9875ECA7E31707AF3E96D522BBD", // T-TeleSec GlobalRoot Class 3
|
|
||||||
"49E7A442ACF0EA6287050054B52564B650E4F49E42E348D6AA38E039E957B1C1", // D-TRUST Root Class 3 CA 2 2009
|
|
||||||
"EEC5496B988CE98625B934092EEC2908BED0B0F316C2D4730C84EAF1F3D34881", // D-TRUST Root Class 3 CA 2 EV 2009
|
|
||||||
"E23D4A036D7B70E9F595B1422079D2B91EDFBB1FB651A0633EAA8A9DC5F80703", // CA Disig Root R2
|
|
||||||
"9A6EC012E1A7DA9DBE34194D478AD7C0DB1822FB071DF12981496ED104384113", // ACCVRAIZ1
|
|
||||||
"59769007F7685D0FCD50872F9F95D5755A5B2B457D81F3692B610A98672F0E1B", // TWCA Global Root CA
|
|
||||||
"91E2F5788D5810EBA7BA58737DE1548A8ECACD014598BC0B143E041B17052552", // T-TeleSec GlobalRoot Class 2
|
|
||||||
"F356BEA244B7A91EB35D53CA9AD7864ACE018E2D35D5F8F96DDF68A6F41AA474", // Atos TrustedRoot 2011
|
|
||||||
"8A866FD1B276B57E578E921C65828A2BED58E9F2F288054134B7F1F4BFC9CC74", // QuoVadis Root CA 1 G3
|
|
||||||
"8FE4FB0AF93A4D0D67DB0BEBB23E37C71BF325DCBCDD240EA04DAF58B47E1840", // QuoVadis Root CA 2 G3
|
|
||||||
"88EF81DE202EB018452E43F864725CEA5FBD1FC2D9D205730709C5D8B8690F46", // QuoVadis Root CA 3 G3
|
|
||||||
"7D05EBB682339F8C9451EE094EEBFEFA7953A114EDB2F44949452FAB7D2FC185", // DigiCert Assured ID Root G2
|
|
||||||
"7E37CB8B4C47090CAB36551BA6F45DB840680FBA166A952DB100717F43053FC2", // DigiCert Assured ID Root G3
|
|
||||||
"CB3CCBB76031E5E0138F8DD39A23F9DE47FFC35E43C1144CEA27D46A5AB1CB5F", // DigiCert Global Root G2
|
|
||||||
"31AD6648F8104138C738F39EA4320133393E3A18CC02296EF97C2AC9EF6731D0", // DigiCert Global Root G3
|
|
||||||
"552F7BDCF1A7AF9E6CE672017F4F12ABF77240C78E761AC203D1D9D20AC89988", // DigiCert Trusted Root G4
|
|
||||||
"52F0E1C4E58EC629291B60317F074671B85D7EA80D5B07273463534B32B40234", // COMODO RSA Certification Authority
|
|
||||||
"E793C9B02FD8AA13E21C31228ACCB08119643B749C898964B1746D46C3D4CBD2", // USERTrust RSA Certification Authority
|
|
||||||
"4FF460D54B9C86DABFBCFC5712E0400D2BED3FBC4D4FBDAA86E06ADCD2A9AD7A", // USERTrust ECC Certification Authority
|
|
||||||
"179FBC148A3DD00FD24EA13458CC43BFA7F59C8182D783A513F6EBEC100C8924", // GlobalSign ECC Root CA - R5
|
|
||||||
"3C4FB0B95AB8B30032F432B86F535FE172C185D0FD39865837CF36187FA6F428", // Staat der Nederlanden Root CA - G3
|
|
||||||
"5D56499BE4D2E08BCFCAD08A3E38723D50503BDE706948E42F55603019E528AE", // IdenTrust Commercial Root CA 1
|
|
||||||
"30D0895A9A448A262091635522D1F52010B5867ACAE12C78EF958FD4F4389F2F", // IdenTrust Public Sector Root CA 1
|
|
||||||
"43DF5774B03E7FEF5FE40D931A7BEDF1BB2E6B42738C4E6D3841103D3AA7F339", // Entrust Root Certification Authority - G2
|
|
||||||
"02ED0EB28C14DA45165C566791700D6451D7FB56F0B2AB1D3B8EB070E56EDFF5", // Entrust Root Certification Authority - EC1
|
|
||||||
"5CC3D78E4E1D5E45547A04E6873E64F90CF9536D1CCC2EF800F355C4C5FD70FD", // CFCA EV ROOT
|
|
||||||
"6B9C08E86EB0F767CFAD65CD98B62149E5494A67F5845E7BD1ED019F27B86BD6", // OISTE WISeKey Global Root GB CA
|
|
||||||
"A1339D33281A0B56E557D3D32B1CE7F9367EB094BD5FA72A7E5004C8DED7CAFE", // SZAFIR ROOT CA2
|
|
||||||
"B676F2EDDAE8775CD36CB0F63CD1D4603961F49E6265BA013A2F0307B6D0B804", // Certum Trusted Network CA 2
|
|
||||||
"A040929A02CE53B4ACF4F2FFC6981CE4496F755E6D45FE0B2A692BCD52523F36", // Hellenic Academic and Research Institutions RootCA 2015
|
|
||||||
"44B545AA8A25E65A73CA15DC27FC36D24C1CB9953A066539B11582DC487B4833", // Hellenic Academic and Research Institutions ECC RootCA 2015
|
|
||||||
"96BCEC06264976F37460779ACF28C5A7CFE8A3C0AAE11A8FFCEE05C0BDDF08C6", // ISRG Root X1
|
|
||||||
"EBC5570C29018C4D67B1AA127BAF12F703B4611EBC17B7DAB5573894179B93FA", // AC RAIZ FNMT-RCM
|
|
||||||
"8ECDE6884F3D87B1125BA31AC3FCB13D7016DE7F57CC904FE1CB97C6AE98196E", // Amazon Root CA 1
|
|
||||||
"1BA5B2AA8C65401A82960118F80BEC4F62304D83CEC4713A19C39C011EA46DB4", // Amazon Root CA 2
|
|
||||||
"18CE6CFE7BF14E60B2E347B8DFE868CB31D02EBB3ADA271569F50343B46DB3A4", // Amazon Root CA 3
|
|
||||||
"E35D28419ED02025CFA69038CD623962458DA5C695FBDEA3C22B0BFB25897092", // Amazon Root CA 4
|
|
||||||
"A1A86D04121EB87F027C66F53303C28E5739F943FC84B38AD6AF009035DD9457", // D-TRUST Root CA 3 2013
|
|
||||||
"46EDC3689046D53A453FB3104AB80DCAEC658B2660EA1629DD7E867990648716", // TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1
|
|
||||||
"BFFF8FD04433487D6A8AA60C1A29767A9FC2BBB05E420F713A13B992891D3893", // GDCA TrustAUTH R5 ROOT
|
|
||||||
"85666A562EE0BE5CE925C1D8890A6F76A87EC16D4D7D5F29EA7419CF20123B69", // SSL.com Root Certification Authority RSA
|
|
||||||
"3417BB06CC6007DA1B961C920B8AB4CE3FAD820E4AA30B9ACBC4A74EBDCEBC65", // SSL.com Root Certification Authority ECC
|
|
||||||
"2E7BF16CC22485A7BBE2AA8696750761B0AE39BE3B2FE9D0CC6D4EF73491425C", // SSL.com EV Root Certification Authority RSA R2
|
|
||||||
"22A2C1F7BDED704CC1E701B5F408C310880FE956B5DE2A4A44F99C873A25A7C8", // SSL.com EV Root Certification Authority ECC
|
|
||||||
"2CABEAFE37D06CA22ABA7391C0033D25982952C453647349763A3AB5AD6CCF69", // GlobalSign Root CA - R6
|
|
||||||
"8560F91C3624DABA9570B5FEA0DBE36FF11A8323BE9486854FB3F34A5571198D", // OISTE WISeKey Global Root GC CA
|
|
||||||
"9BEA11C976FE014764C1BE56A6F914B5A560317ABD9988393382E5161AA0493C", // UCA Global G2 Root
|
|
||||||
"D43AF9B35473755C9684FC06D7D8CB70EE5C28E773FB294EB41EE71722924D24", // UCA Extended Validation Root
|
|
||||||
"D48D3D23EEDB50A459E55197601C27774B9D7B18C94D5A059511A10250B93168", // Certigna Root CA
|
|
||||||
"40F6AF0346A99AA1CD1D555A4E9CCE62C7F9634603EE406615833DC8C8D00367", // emSign Root CA - G1
|
|
||||||
"86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B", // emSign ECC Root CA - G3
|
|
||||||
"125609AA301DA0A249B97A8239CB6A34216F44DCAC9F3954B14292F2E8C8608F", // emSign Root CA - C1
|
|
||||||
"BC4D809B15189D78DB3E1D8CF4F9726A795DA1643CA5F1358E1DDB0EDC0D7EB3", // emSign ECC Root CA - C3
|
|
||||||
"5A2FC03F0C83B090BBFA40604B0988446C7636183DF9846E17101A447FB8EFD6", // Hongkong Post Root CA 3
|
|
||||||
"DB3517D1F6732A2D5AB97C533EC70779EE3270A62FB4AC4238372460E6F01E88", // Entrust Root Certification Authority - G4
|
|
||||||
"358DF39D764AF9E1B766E9C972DF352EE15CFAC227AF6AD1D70E8E4A6EDCBA02", // Microsoft ECC Root Certificate Authority 2017
|
|
||||||
"C741F70F4B2A8D88BF2E71C14122EF53EF10EBA0CFA5E64CFA20F418853073E0", // Microsoft RSA Root Certificate Authority 2017
|
|
||||||
"BEB00B30839B9BC32C32E4447905950641F26421B15ED089198B518AE2EA1B99", // e-Szigno Root CA 2017
|
|
||||||
"657CFE2FA73FAA38462571F332A2363A46FCE7020951710702CDFBB6EEDA3305", // certSIGN Root CA G2
|
|
||||||
"88F438DCF8FFD1FA8F429115FFE5F82AE1E06E0C70C375FAAD717B34A49E7265", // NAVER Global Root Certification Authority
|
|
||||||
"554153B13D2CF9DDB753BFBE1A4E0AE08D0AA4187058FE60A2B862B2E4B87BCB", // AC RAIZ FNMT-RCM SERVIDORES SEGUROS
|
|
||||||
"319AF0A7729E6F89269C131EA6A3A16FCD86389FDCAB3C47A4A675C161A3F974", // GlobalSign Secure Mail Root R45
|
|
||||||
"5CBF6FB81FD417EA4128CD6F8172A3C9402094F74AB2ED3A06B4405D04F30B19", // GlobalSign Secure Mail Root E45
|
|
||||||
"4FA3126D8D3A11D1C4855A4F807CBAD6CF919D3A5A88B03BEA2C6372D93C40C9", // GlobalSign Root R46
|
|
||||||
"CBB9C44D84B8043E1050EA31A69F514955D7BFD2E2C6B49301019AD61D9F5058", // GlobalSign Root E46
|
|
||||||
"FB8FEC759169B9106B1E511644C618C51304373F6C0643088D8BEFFD1B997599", // ANF Secure Server Root CA
|
|
||||||
"6B328085625318AA50D173C98D8BDA09D57E27413D114CF787A0F5D06C030CF6", // Certum EC-384 CA
|
|
||||||
"FE7696573855773E37A95E7AD4D9CC96C30157C15D31765BA9B15704E1AE78FD", // Certum Trusted Root CA
|
|
||||||
"2E44102AB58CB85419451C8E19D9ACF3662CAFBC614B6A53960A30F7D0E2EB41", // TunTrust Root CA
|
|
||||||
"D95D0E8EDA79525BF9BEB11B14D2100D3294985F0C62D9FABD9CD999ECCB7B1D", // HARICA TLS RSA Root CA 2021
|
|
||||||
"3F99CC474ACFCE4DFED58794665E478D1547739F2E780F1BB4CA9B133097D401", // HARICA TLS ECC Root CA 2021
|
|
||||||
"1BE7ABE30686B16348AFD1C61B6866A0EA7F4821E67D5E8AF937CF8011BC750D", // HARICA Client RSA Root CA 2021
|
|
||||||
"8DD4B5373CB0DE36769C12339280D82746B3AA6CD426E797A31BABE4279CF00B", // HARICA Client ECC Root CA 2021
|
|
||||||
"57DE0583EFD2B26E0361DA99DA9DF4648DEF7EE8441C3B728AFA9BCDE0F9B26A", // Autoridad de Certificacion Firmaprofesional CIF A62634068
|
|
||||||
"30FBBA2C32238E2A98547AF97931E550428B9B3F1C8EEB6633DCFA86C5B27DD3", // vTrus ECC Root CA
|
|
||||||
"8A71DE6559336F426C26E53880D00D88A18DA4C6A91F0DCB6194E206C5C96387", // vTrus Root CA
|
|
||||||
"69729B8E15A86EFC177A57AFB7171DFC64ADD28C2FCA8CF1507E34453CCB1470", // ISRG Root X2
|
|
||||||
"F015CE3CC239BFEF064BE9F1D2C417E1A0264A0A94BE1F0C8D121864EB6949CC", // HiPKI Root CA - G1
|
|
||||||
"B085D70B964F191A73E4AF0D54AE7A0E07AAFDAF9B71DD0862138AB7325A24A2", // GlobalSign ECC Root CA - R4
|
|
||||||
"D947432ABDE7B7FA90FC2E6B59101B1280E0E1C7E4E40FA3C6887FFF57A7F4CF", // GTS Root R1
|
|
||||||
"8D25CD97229DBF70356BDA4EB3CC734031E24CF00FAFCFD32DC76EB5841C7EA8", // GTS Root R2
|
|
||||||
"34D8A73EE208D9BCDB0D956520934B4E40E69482596E8B6F73C8426B010A6F48", // GTS Root R3
|
|
||||||
"349DFA4058C5E263123B398AE795573C4E1313C83FE68F93556CD5E8031B3C7D", // GTS Root R4
|
|
||||||
"242B69742FCB1E5B2ABF98898B94572187544E5B4D9911786573621F6A74B82C", // Telia Root CA v2
|
|
||||||
"E59AAA816009C22BFF5B25BAD37DF306F049797C1F81D85AB089E657BD8F0044", // D-TRUST BR Root CA 1 2020
|
|
||||||
"08170D1AA36453901A2F959245E347DB0C8D37ABAABC56B81AA100DC958970DB", // D-TRUST EV Root CA 1 2020
|
|
||||||
"018E13F0772532CF809BD1B17281867283FC48C6E13BE9C69812854A490C1B05", // DigiCert TLS ECC P384 Root G5
|
|
||||||
"371A00DC0533B3721A7EEB40E8419E70799D2B0A0F2C1D80693165F7CEC4AD75", // DigiCert TLS RSA4096 Root G5
|
|
||||||
"E8E8176536A60CC2C4E10187C3BEFCA20EF263497018F566D5BEA0F94D0C111B", // DigiCert SMIME ECC P384 Root G5
|
|
||||||
"90370D3EFA88BF58C30105BA25104A358460A7FA52DFC2011DF233A0F417912A", // DigiCert SMIME RSA4096 Root G5
|
|
||||||
"77B82CD8644C4305F7ACC5CB156B45675004033D51C60C6202A8E0C33467D3A0", // Certainly Root R1
|
|
||||||
"B4585F22E4AC756A4E8612A1361C5D9D031A93FD84FEBB778FA3068B0FC42DC2", // Certainly Root E1
|
|
||||||
"82BD5D851ACF7F6E1BA7BFCBC53030D0E7BC3C21DF772D858CAB41D199BDF595", // DIGITALSIGN GLOBAL ROOT RSA CA
|
|
||||||
"261D7114AE5F8FF2D8C7209A9DE4289E6AFC9D717023D85450909199F1857CFE", // DIGITALSIGN GLOBAL ROOT ECDSA CA
|
|
||||||
"E74FBDA55BD564C473A36B441AA799C8A68E077440E8288B9FA1E50E4BBACA11", // Security Communication ECC RootCA1
|
|
||||||
"F3896F88FE7C0A882766A7FA6AD2749FB57A7F3E98FB769C1FA7B09C2C44D5AE", // BJCA Global Root CA1
|
|
||||||
"574DF6931E278039667B720AFDC1600FC27EB66DD3092979FB73856487212882", // BJCA Global Root CA2
|
|
||||||
"48E1CF9E43B688A51044160F46D773B8277FE45BEAAD0E4DF90D1974382FEA99", // LAWtrust Root CA2 (4096)
|
|
||||||
"22D9599234D60F1D4BC7C7E96F43FA555B07301FD475175089DAFB8C25E477B3", // Sectigo Public Email Protection Root E46
|
|
||||||
"D5917A7791EB7CF20A2E57EB98284A67B28A57E89182DA53D546678C9FDE2B4F", // Sectigo Public Email Protection Root R46
|
|
||||||
"C90F26F0FB1B4018B22227519B5CA2B53E2CA5B3BE5CF18EFE1BEF47380C5383", // Sectigo Public Server Authentication Root E46
|
|
||||||
"7BB647A62AEEAC88BF257AA522D01FFEA395E0AB45C73F93F65654EC38F25A06", // Sectigo Public Server Authentication Root R46
|
|
||||||
"8FAF7D2E2CB4709BB8E0B33666BF75A5DD45B5DE480F8EA8D4BFE6BEBC17F2ED", // SSL.com TLS RSA Root CA 2022
|
|
||||||
"C32FFD9F46F936D16C3673990959434B9AD60AAFBB9E7CF33654F144CC1BA143", // SSL.com TLS ECC Root CA 2022
|
|
||||||
"AD7DD58D03AEDB22A30B5084394920CE12230C2D8017AD9B81AB04079BDD026B", // SSL.com Client ECC Root CA 2022
|
|
||||||
"1D4CA4A2AB21D0093659804FC0EB2175A617279B56A2475245C9517AFEB59153", // SSL.com Client RSA Root CA 2022
|
|
||||||
"E38655F4B0190C84D3B3893D840A687E190A256D98052F159E6D4A39F589A6EB", // Atos TrustedRoot Root CA ECC G2 2020
|
|
||||||
"78833A783BB2986C254B9370D3C20E5EBA8FA7840CBF63FE17297A0B0119685E", // Atos TrustedRoot Root CA RSA G2 2020
|
|
||||||
"B2FAE53E14CCD7AB9212064701AE279C1D8988FACB775FA8A008914E663988A8", // Atos TrustedRoot Root CA ECC TLS 2021
|
|
||||||
"81A9088EA59FB364C548A6F85559099B6F0405EFBF18E5324EC9F457BA00112F", // Atos TrustedRoot Root CA RSA TLS 2021
|
|
||||||
"E0D3226AEB1163C2E48FF9BE3B50B4C6431BE7BB1EACC5C36B5D5EC509039A08", // TrustAsia Global Root CA G3
|
|
||||||
"BE4B56CB5056C0136A526DF444508DAA36A0B54F42E4AC38F72AF470E479654C", // TrustAsia Global Root CA G4
|
|
||||||
"D92C171F5CF890BA428019292927FE22F3207FD2B54449CB6F675AF4922146E2", // D-Trust SBR Root CA 1 2022
|
|
||||||
"DBA84DD7EF622D485463A90137EA4D574DF8550928F6AFA03B4D8B1141E636CC", // D-Trust SBR Root CA 2 2022
|
|
||||||
"3AE6DF7E0D637A65A8C81612EC6F9A142F85A16834C10280D88E707028518755", // Telekom Security SMIME ECC Root 2021
|
|
||||||
"578AF4DED0853F4E5998DB4AEAF9CBEA8D945F60B620A38D1A3C13B2BC7BA8E1", // Telekom Security TLS ECC Root 2020
|
|
||||||
"78A656344F947E9CC0F734D9053D32F6742086B6B9CD2CAE4FAE1A2E4EFDE048", // Telekom Security SMIME RSA Root 2023
|
|
||||||
"EFC65CADBB59ADB6EFE84DA22311B35624B71B3B1EA0DA8B6655174EC8978646", // Telekom Security TLS RSA Root 2023
|
|
||||||
"3F63BB2814BE174EC8B6439CF08D6D56F0B7C405883A5648A334424D6B3EC558", // TWCA CYBER Root CA
|
|
||||||
"3A0072D49FFC04E996C59AEB75991D3C340F3615D6FD4DCE90AC0B3D88EAD4F4", // TWCA Global Root CA G2
|
|
||||||
"3F034BB5704D44B2D08545A02057DE93EBF3905FCE721ACBC730C06DDAEE904E", // SecureSign Root CA12
|
|
||||||
"4B009C1034494F9AB56BBA3BA1D62731FC4D20D8955ADCEC10A925607261E338", // SecureSign Root CA14
|
|
||||||
"E778F0F095FE843729CD1A0082179E5314A9C291442805E1FB1D8FB6B8886C3A", // SecureSign Root CA15
|
|
||||||
"0552E6F83FDF65E8FA9670E666DF28A4E21340B510CBE52566F97C4FB94B2BD1", // D-TRUST BR Root CA 2 2023
|
|
||||||
"436472C1009A325C54F1A5BBB5468A7BAEECCBE05DE5F099CB70D3FE41E13C16", // TrustAsia SMIME ECC Root CA
|
|
||||||
"C7796BEB62C101BB143D262A7C96A0C6168183223EF50D699632D86E03B8CC9B", // TrustAsia SMIME RSA Root CA
|
|
||||||
"C0076B9EF0531FB1A656D67C4EBE97CD5DBAA41EF44598ACC2489878C92D8711", // TrustAsia TLS ECC Root CA
|
|
||||||
"06C08D7DAFD876971EB1124FE67F847EC0C7A158D3EA53CBE940E2EA9791F4C3", // TrustAsia TLS RSA Root CA
|
|
||||||
"8E8221B2E7D4007836A1672F0DCC299C33BC07D316F132FA1A206D587150F1CE", // D-TRUST EV Root CA 2 2023
|
|
||||||
"9A12C392BFE57891A0C545309D4D9FD567E480CB613D6342278B195C79A7931F", // SwissSign RSA SMIME Root CA 2022 - 1
|
|
||||||
"193144F431E0FDDB740717D4DE926A571133884B4360D30E272913CBE660CE41", // SwissSign RSA TLS Root CA 2022 - 1
|
|
||||||
"D9A32485A8CCA85539CEF12FFFFF711378A17851D73DA2732AB4302D763BD62B", // OISTE Client Root ECC G1
|
|
||||||
"D02A0F994A868C66395F2E7A880DF509BD0C29C96DE16015A0FD501EDA4F96A9", // OISTE Client Root RSA G1
|
|
||||||
"EEC997C0C30F216F7E3B8B307D2BAE42412D753FC8219DAFD1520B2572850F49", // OISTE Server Root ECC G1
|
|
||||||
"9AE36232A5189FFDDB353DFD26520C015395D22777DAC59DB57B98C089A651E6", // OISTE Server Root RSA G1
|
|
||||||
"B49141502D00663D740F2E7EC340C52800962666121A36D09CF7DD2B90384FB4", // e-Szigno TLS Root CA 2023
|
|
||||||
};
|
|
||||||
|
|
||||||
public static CertPemManager Instance => _instance.Value;
|
public static CertPemManager Instance => _instance.Value;
|
||||||
|
|
||||||
@@ -292,7 +129,7 @@ public class CertPemManager
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validate server certificate with CA pinning
|
/// Validate server certificate with CA pinning
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static bool ValidateServerCertificate(
|
private bool ValidateServerCertificate(
|
||||||
object _,
|
object _,
|
||||||
X509Certificate? certificate,
|
X509Certificate? certificate,
|
||||||
X509Chain? chain,
|
X509Chain? chain,
|
||||||
@@ -327,9 +164,10 @@ public class CertPemManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rootCert = certChain.ChainElements[^1].Certificate;
|
var rootCert = certChain.ChainElements[^1].Certificate;
|
||||||
var rootThumbprint = rootCert.GetCertHashString(HashAlgorithmName.SHA256);
|
|
||||||
|
|
||||||
if (!TrustedCaThumbprints.Contains(rootThumbprint))
|
var trustedCerts = BuildTrustedCertificateCollection();
|
||||||
|
|
||||||
|
if (!trustedCerts.Contains(rootCert))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -442,4 +280,50 @@ public class CertPemManager
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly Lazy<X509Certificate2Collection> _chromeRootCerts = new(() =>
|
||||||
|
{
|
||||||
|
var pemText = EmbedUtils.GetEmbedText(Global.ChromeRootCertFileName);
|
||||||
|
var collection = new X509Certificate2Collection();
|
||||||
|
collection.ImportFromPem(pemText);
|
||||||
|
return collection;
|
||||||
|
});
|
||||||
|
|
||||||
|
private static readonly Lazy<X509Certificate2Collection> _mozillaRootCerts = new(() =>
|
||||||
|
{
|
||||||
|
var pemText = EmbedUtils.GetEmbedText(Global.MozillaRootCertFileName);
|
||||||
|
var collection = new X509Certificate2Collection();
|
||||||
|
collection.ImportFromPem(pemText);
|
||||||
|
return collection;
|
||||||
|
});
|
||||||
|
|
||||||
|
private X509Certificate2Collection BuildTrustedCertificateCollection()
|
||||||
|
{
|
||||||
|
if (_config.GuiItem.RootCertProvider == Global.ChromeRootProvider)
|
||||||
|
{
|
||||||
|
return _chromeRootCerts.Value;
|
||||||
|
}
|
||||||
|
return _mozillaRootCerts.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSystemRootCertProvider()
|
||||||
|
{
|
||||||
|
return _config.GuiItem.RootCertProvider != Global.ChromeRootProvider && _config.GuiItem.RootCertProvider != Global.MozillaRootProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509ChainPolicy? BuildCertificateChainPolicy()
|
||||||
|
{
|
||||||
|
if (IsSystemRootCertProvider())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var trustedCerts = BuildTrustedCertificateCollection();
|
||||||
|
var chainPolicy = new X509ChainPolicy
|
||||||
|
{
|
||||||
|
TrustMode = X509ChainTrustMode.CustomRootTrust,
|
||||||
|
RevocationMode = X509RevocationMode.NoCheck,
|
||||||
|
};
|
||||||
|
chainPolicy.CustomTrustStore.AddRange(trustedCerts);
|
||||||
|
return chainPolicy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,18 @@ public class CoreAdminManager
|
|||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
sb.AppendLine("#!/bin/bash");
|
sb.AppendLine("#!/bin/bash");
|
||||||
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
|
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
|
||||||
sb.AppendLine($"exec sudo -S -- {cmdLine}");
|
|
||||||
|
// Passing environment variables to the sudo command, here it only xray or sing-box.
|
||||||
|
if (coreInfo.Environment.Count > 0)
|
||||||
|
{
|
||||||
|
var envArgs = string.Join(" ", coreInfo.Environment.Where(kv => kv.Value.IsNotEmpty()).Select(kv => $"{kv.Key}={kv.Value.AppendQuotes()}"));
|
||||||
|
sb.AppendLine($"exec sudo -S -- env {envArgs} {cmdLine}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.AppendLine($"exec sudo -S -- {cmdLine}");
|
||||||
|
}
|
||||||
|
|
||||||
var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
|
var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
|
||||||
|
|
||||||
var procService = new ProcessService(
|
var procService = new ProcessService(
|
||||||
@@ -80,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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ public class CoreManager
|
|||||||
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
||||||
public static CoreManager Instance => _instance.Value;
|
public static CoreManager Instance => _instance.Value;
|
||||||
private Config _config;
|
private Config _config;
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
private WindowsJobService? _processJob;
|
private WindowsJobService? _processJob;
|
||||||
|
|
||||||
private ProcessService? _processService;
|
private ProcessService? _processService;
|
||||||
private ProcessService? _processPreService;
|
private ProcessService? _processPreService;
|
||||||
private bool _linuxSudo = false;
|
private bool _linuxSudo = false;
|
||||||
@@ -83,13 +85,14 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreStart(mainContext);
|
await CoreStart(mainContext);
|
||||||
|
await WaitForProxyPort(preContext);
|
||||||
await CoreStartPreService(preContext);
|
await CoreStartPreService(preContext);
|
||||||
|
|
||||||
AppManager.Instance.RunningCoreType = preContext?.RunCoreType ?? mainContext.RunCoreType;
|
AppManager.Instance.RunningCoreType = preContext?.RunCoreType ?? mainContext.RunCoreType;
|
||||||
@@ -180,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;
|
||||||
@@ -198,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;
|
||||||
@@ -213,11 +216,93 @@ public class CoreManager
|
|||||||
await _updateFunc?.Invoke(notify, msg);
|
await _updateFunc?.Invoke(notify, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task WaitForProxyPort(CoreConfigContext? preContext, int timeoutMs = 5000)
|
||||||
|
{
|
||||||
|
if (preContext is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!preContext.IsTunEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var rootCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeoutMs));
|
||||||
|
var rootToken = rootCts.Token;
|
||||||
|
|
||||||
|
var port = preContext.Node.Port;
|
||||||
|
// SOCKS5 client greeting: VER=5, NMETHODS=1, METHOD=0x00 (no auth)
|
||||||
|
ReadOnlyMemory<byte> greeting = new byte[] { 0x05, 0x01, 0x00 };
|
||||||
|
var buf = new byte[2];
|
||||||
|
|
||||||
|
while (!rootToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
using var tcp = new TcpClient();
|
||||||
|
using var attemptCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
|
||||||
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(rootToken, attemptCts.Token);
|
||||||
|
var linkedToken = linkedCts.Token;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await tcp.ConnectAsync(Global.Loopback, port, linkedToken);
|
||||||
|
var stream = tcp.GetStream();
|
||||||
|
|
||||||
|
await stream.WriteAsync(greeting, linkedToken);
|
||||||
|
|
||||||
|
var read = await stream.ReadAsync(buf.AsMemory(0, 2), linkedToken);
|
||||||
|
|
||||||
|
// Server selection: VER=5, METHOD=0x00 — proxy is fully ready
|
||||||
|
if (read == 2 && buf[0] == 0x05)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
if (!rootToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Logging.SaveLog($"WaitForProxyPort Timeout waiting for proxy port {port} to be ready.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionRefused)
|
||||||
|
{
|
||||||
|
// Connection refused, proxy not ready yet, wait 50ms before retrying
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(50, rootToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Logging.SaveLog($"WaitForProxyPort Timeout waiting for proxy port {port} to be ready.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore other exceptions and continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion Private
|
#endregion Private
|
||||||
|
|
||||||
#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())
|
||||||
@@ -229,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,239 +0,0 @@
|
|||||||
using NetBridgeLib.Services;
|
|
||||||
|
|
||||||
namespace ServiceLib.Manager;
|
|
||||||
|
|
||||||
public sealed class NetBridgeManager
|
|
||||||
{
|
|
||||||
private static readonly Lazy<NetBridgeManager> _instance = new(() => new());
|
|
||||||
public static NetBridgeManager Instance => _instance.Value;
|
|
||||||
private readonly Config _config = AppManager.Instance.Config;
|
|
||||||
private NetBridgeService? _netBridgeService;
|
|
||||||
private bool _isProxyRunning;
|
|
||||||
private bool _isInitialized;
|
|
||||||
private List<NetBridgeRuleConfig> _ruleConfigs = [];
|
|
||||||
private Func<bool, string, Task>? _updateFunc;
|
|
||||||
private uint _proxyConfigId;
|
|
||||||
|
|
||||||
public async Task Init(Func<bool, string, Task>? updateFunc = null)
|
|
||||||
{
|
|
||||||
if (_isInitialized)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateFunc = updateFunc;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_netBridgeService = new NetBridgeService();
|
|
||||||
_netBridgeService.LogReceived += msg =>
|
|
||||||
{
|
|
||||||
var message = $"NetBridge Log: {msg}";
|
|
||||||
_ = _updateFunc?.Invoke(false, message);
|
|
||||||
};
|
|
||||||
|
|
||||||
_netBridgeService.ConnectionReceived += (processName, pid, destIp, destPort, proxyInfo) =>
|
|
||||||
{
|
|
||||||
var message = $"NetBridge Connection: {processName} (PID: {pid}) -> {destIp}:{destPort} -> {proxyInfo}";
|
|
||||||
_ = _updateFunc?.Invoke(false, message);
|
|
||||||
};
|
|
||||||
|
|
||||||
_ruleConfigs = BuildRuleConfigs(_config.NetBridgeItem?.RuleProcess);
|
|
||||||
_isInitialized = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var error = $"Failed to initialize NetBridgeService: {ex.Message}";
|
|
||||||
await _updateFunc?.Invoke(true, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> Start()
|
|
||||||
{
|
|
||||||
if (_isProxyRunning)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_netBridgeService == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var started = _netBridgeService.Start();
|
|
||||||
if (!started)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isProxyRunning = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var error = $"Failed to start NetBridgeService: {ex.Message}";
|
|
||||||
await _updateFunc?.Invoke(true, error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> Stop()
|
|
||||||
{
|
|
||||||
if (!_isProxyRunning)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_netBridgeService == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stopped = _netBridgeService.Stop();
|
|
||||||
if (!stopped)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isProxyRunning = false;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var error = $"Failed to stop NetBridgeService: {ex.Message}";
|
|
||||||
await _updateFunc?.Invoke(true, error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> UpdateRoutes(string? ruleProcess)
|
|
||||||
{
|
|
||||||
var newRuleConfigs = BuildRuleConfigs(ruleProcess);
|
|
||||||
|
|
||||||
_ruleConfigs = newRuleConfigs;
|
|
||||||
|
|
||||||
if (!_isProxyRunning)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await ApplyRoutesInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> UpdateProxyConfig(string proxyHost, int proxyPort)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_netBridgeService == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var proxyType = "SOCKS5";
|
|
||||||
var username = "";
|
|
||||||
var password = "";
|
|
||||||
|
|
||||||
if (_proxyConfigId > 0)
|
|
||||||
{
|
|
||||||
var edited = _netBridgeService.EditProxyConfig(_proxyConfigId, proxyType, proxyHost, (ushort)proxyPort, username, password);
|
|
||||||
if (!edited)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_proxyConfigId = _netBridgeService.AddProxyConfig(proxyType, proxyHost, (ushort)proxyPort, username, password);
|
|
||||||
if (_proxyConfigId == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var error = $"Failed to update proxy config: {ex.Message}";
|
|
||||||
await _updateFunc?.Invoke(true, error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> SetDnsViaProxy(bool enable)
|
|
||||||
{
|
|
||||||
if (_netBridgeService == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_netBridgeService.SetDnsViaProxy(enable);
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> ApplyRoutesInternal()
|
|
||||||
{
|
|
||||||
if (_netBridgeService == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<NetBridgeRuleConfig> rules;
|
|
||||||
|
|
||||||
rules = _ruleConfigs.Select(JsonUtils.DeepCopy).ToList();
|
|
||||||
|
|
||||||
foreach (var rule in rules.Where(x => x.RuleId > 0))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ = _netBridgeService.DeleteRule(rule.RuleId);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < rules.Count; i++)
|
|
||||||
{
|
|
||||||
var rule = rules[i];
|
|
||||||
var newRuleId = _netBridgeService.AddRule(rule.ProcessName, rule.TargetHosts, rule.TargetPorts, rule.Protocol, rule.Action, rule.ProxyConfigId);
|
|
||||||
if (newRuleId == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
rules[i].RuleId = newRuleId;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ruleConfigs = rules;
|
|
||||||
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<NetBridgeRuleConfig> BuildRuleConfigs(string? ruleProcess)
|
|
||||||
{
|
|
||||||
if (ruleProcess.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return new();
|
|
||||||
}
|
|
||||||
|
|
||||||
var processNames = Utils.String2List(Utils.Convert2Comma(ruleProcess));
|
|
||||||
return processNames.Select(processName => new NetBridgeRuleConfig
|
|
||||||
{
|
|
||||||
ProcessName = processName,
|
|
||||||
TargetHosts = "*",
|
|
||||||
TargetPorts = "*",
|
|
||||||
Protocol = "BOTH",
|
|
||||||
Action = "PROXY",
|
|
||||||
ProxyConfigId = 0
|
|
||||||
}).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
//using System.Reactive.Linq;
|
|
||||||
|
|
||||||
namespace ServiceLib.Manager;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public class ProfileExManager
|
public class ProfileExManager
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ public class Config
|
|||||||
public SystemProxyItem SystemProxyItem { get; set; }
|
public SystemProxyItem SystemProxyItem { get; set; }
|
||||||
public WebDavItem WebDavItem { get; set; }
|
public WebDavItem WebDavItem { get; set; }
|
||||||
public CheckUpdateItem CheckUpdateItem { get; set; }
|
public CheckUpdateItem CheckUpdateItem { get; set; }
|
||||||
public NetBridgeItem NetBridgeItem { get; set; }
|
|
||||||
public Fragment4RayItem? Fragment4RayItem { get; set; }
|
public Fragment4RayItem? Fragment4RayItem { get; set; }
|
||||||
public List<InItem> Inbound { get; set; }
|
public List<InItem> Inbound { get; set; }
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ public class GUIItem
|
|||||||
public int TrayMenuServersLimit { get; set; } = 20;
|
public int TrayMenuServersLimit { get; set; } = 20;
|
||||||
public bool EnableHWA { get; set; } = false;
|
public bool EnableHWA { get; set; } = false;
|
||||||
public bool EnableLog { get; set; } = true;
|
public bool EnableLog { get; set; } = true;
|
||||||
|
public string? RootCertProvider { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
@@ -102,6 +103,7 @@ public class UIItem
|
|||||||
public bool MacOSShowInDock { get; set; }
|
public bool MacOSShowInDock { get; set; }
|
||||||
public List<ColumnItem> MainColumnItem { get; set; }
|
public List<ColumnItem> MainColumnItem { get; set; }
|
||||||
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
||||||
|
public bool HideColumnIpInfo { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
@@ -145,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]
|
||||||
@@ -247,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]
|
||||||
@@ -267,21 +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]
|
[Serializable]
|
||||||
public class NetBridgeItem
|
public class HappyEyeballs4RayItem
|
||||||
{
|
{
|
||||||
public string? RuleProcess { get; set; }
|
public int? TryDelayMs { get; set; }
|
||||||
public bool EnableDnsViaProxy { get; set; }
|
public bool? PrioritizeIPv6 { get; set; }
|
||||||
|
public int? Interleave { get; set; }
|
||||||
|
public int? MaxConcurrentTry { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,20 @@ public record CoreConfigContext
|
|||||||
public Config AppConfig { get; init; } = new();
|
public 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();
|
||||||
|
|
||||||
// TUN Compatibility
|
// TUN Compatibility
|
||||||
public bool IsTunEnabled { get; init; } = false;
|
public bool IsTunEnabled { get; init; } = false;
|
||||||
public HashSet<string> ProtectDomainList { get; init; } = [];
|
public HashSet<string> ProtectDomainList { get; init; } = [];
|
||||||
|
// Typically, it is the core of the outbound chain
|
||||||
|
public HashSet<ECoreType> ProtectCoreTypeList { get; init; } = [];
|
||||||
|
|
||||||
public bool IsWindows { get; init; }
|
public bool IsWindows { get; init; }
|
||||||
public bool IsMacOS { get; init; }
|
public bool IsMacOS { get; init; }
|
||||||
|
|
||||||
|
// Generation Context
|
||||||
|
public Dictionary<object, string> CustomOutboundMap { get; init; } = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ public class HyRealm4Sbox
|
|||||||
public string? server_url { get; set; }
|
public string? server_url { get; set; }
|
||||||
public string? token { get; set; }
|
public string? token { get; set; }
|
||||||
public string? realm_id { get; set; }
|
public string? realm_id { get; set; }
|
||||||
|
public List<string>? stun_servers { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Server4Sbox : BaseServer4Sbox
|
public class Server4Sbox : BaseServer4Sbox
|
||||||
|
|||||||
@@ -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; }
|
||||||
@@ -146,6 +151,16 @@ public class Outboundsettings4Ray
|
|||||||
|
|
||||||
public int? port { get; set; }
|
public int? port { get; set; }
|
||||||
|
|
||||||
|
public string? user { get; set; }
|
||||||
|
|
||||||
|
public string? pass { get; set; }
|
||||||
|
|
||||||
|
public int? level { get; set; }
|
||||||
|
|
||||||
|
public string? email { get; set; }
|
||||||
|
|
||||||
|
public object? headers { get; set; }
|
||||||
|
|
||||||
public List<WireguardPeer4Ray>? peers { get; set; }
|
public List<WireguardPeer4Ray>? peers { get; set; }
|
||||||
|
|
||||||
public bool? noKernelTun { get; set; }
|
public bool? noKernelTun { get; set; }
|
||||||
@@ -276,7 +291,6 @@ public class BalancersItem4Ray
|
|||||||
public List<string>? selector { get; set; }
|
public List<string>? selector { get; set; }
|
||||||
public BalancersStrategy4Ray? strategy { get; set; }
|
public BalancersStrategy4Ray? strategy { get; set; }
|
||||||
public string? tag { get; set; }
|
public string? tag { get; set; }
|
||||||
public string? fallbackTag { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BalancersStrategy4Ray
|
public class BalancersStrategy4Ray
|
||||||
@@ -289,16 +303,7 @@ public class BalancersStrategySettings4Ray
|
|||||||
{
|
{
|
||||||
public int? expected { get; set; }
|
public int? expected { get; set; }
|
||||||
public string? maxRTT { get; set; }
|
public string? maxRTT { get; set; }
|
||||||
public float? tolerance { get; set; }
|
public double? tolerance { get; set; }
|
||||||
public List<string>? baselines { get; set; }
|
|
||||||
public List<BalancersStrategySettingsCosts4Ray>? costs { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BalancersStrategySettingsCosts4Ray
|
|
||||||
{
|
|
||||||
public bool? regexp { get; set; }
|
|
||||||
public string? match { get; set; }
|
|
||||||
public float? value { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Observatory4Ray
|
public class Observatory4Ray
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ public record HyRealm(
|
|||||||
{
|
{
|
||||||
public string RendezvousHostPort => RendezvousPort > 0 ? $"{RendezvousHost}:{RendezvousPort}" : RendezvousHost;
|
public string RendezvousHostPort => RendezvousPort > 0 ? $"{RendezvousHost}:{RendezvousPort}" : RendezvousHost;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// sing-box realm.server_url requires a scheme (https:// or http://).
|
||||||
|
/// </summary>
|
||||||
|
public string ToServerUrl()
|
||||||
|
{
|
||||||
|
var scheme = IsHttp ? "http" : "https";
|
||||||
|
return $"{scheme}://{RendezvousHostPort}";
|
||||||
|
}
|
||||||
|
|
||||||
public static bool TryParse(string? str, out HyRealm? realm)
|
public static bool TryParse(string? str, out HyRealm? realm)
|
||||||
{
|
{
|
||||||
realm = null;
|
realm = null;
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace ServiceLib.Models.Dto;
|
|
||||||
|
|
||||||
public sealed class NetBridgeRuleConfig
|
|
||||||
{
|
|
||||||
public uint RuleId { get; set; }
|
|
||||||
public string ProcessName { get; set; }
|
|
||||||
public string TargetHosts { get; set; }
|
|
||||||
public string TargetPorts { get; set; }
|
|
||||||
public string Protocol { get; set; }
|
|
||||||
public string Action { get; set; }
|
|
||||||
public uint ProxyConfigId { 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ public record ProtocolExtraItem
|
|||||||
public bool? Uot { get; init; }
|
public bool? Uot { get; init; }
|
||||||
public string? CongestionControl { get; init; }
|
public string? CongestionControl { get; init; }
|
||||||
|
|
||||||
|
// http outbound
|
||||||
|
public string? HttpHeaders { get; init; }
|
||||||
|
|
||||||
// vmess
|
// vmess
|
||||||
public string? AlterId { get; init; }
|
public string? AlterId { get; init; }
|
||||||
public string? VmessSecurity { get; init; }
|
public string? VmessSecurity { get; init; }
|
||||||
@@ -48,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; }
|
||||||
}
|
}
|
||||||
|
|||||||
180
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
180
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 {
|
||||||
@@ -321,6 +321,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Please enter valid HTTP request headers JSON. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string InvalidHttpOutboundHeaders {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InvalidHttpOutboundHeaders", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Invalid Realm URL. 的本地化字符串。
|
/// 查找类似 Invalid Realm URL. 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -429,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>
|
||||||
@@ -726,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>
|
||||||
@@ -1329,15 +1356,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Process traffic hijacking (experimental) 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string menuNetBridge {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("menuNetBridge", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 New Update 的本地化字符串。
|
/// 查找类似 New Update 的本地化字符串。
|
||||||
/// </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>
|
||||||
@@ -2328,15 +2355,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 The process names that need to be proxied; separate multiple processes with commas. Processes not in the list will also be hijacked, but will connect directly. 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string NetBridgeRuleTips {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("NetBridgeRuleTips", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Non-VMess or SS protocol 的本地化字符串。
|
/// 查找类似 Non-VMess or SS protocol 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2635,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 {
|
||||||
@@ -2934,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>
|
||||||
@@ -3070,11 +3097,20 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Enables process traffic hijacking (experimental), similar to Proxifer; can coexist with system proxy; conflicts with TUN mode, please do not enable them simultaneously;please allow v2rayN to use a private network in your firewall. 的本地化字符串。
|
/// 查找类似 Enable Happy Eyeballs 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string TbEnableNetBridge {
|
public static string TbEnableHappyEyeballs {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("TbEnableNetBridge", resourceCulture);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3106,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 {
|
||||||
@@ -3267,6 +3303,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 HTTP headers 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbHttpOutboundHeaders {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbHttpOutboundHeaders", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Realm URL 的本地化字符串。
|
/// 查找类似 Realm URL 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3339,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>
|
||||||
@@ -3366,6 +3429,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 If enabled, use sing-box TUN; otherwise, use xray TUN. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbLegacyProtectTip {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbLegacyProtectTip", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Address (IPv4, IPv6) 的本地化字符串。
|
/// 查找类似 Address (IPv4, IPv6) 的本地化字符串。
|
||||||
/// </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>
|
||||||
@@ -3627,6 +3717,24 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Root Certificate Provider 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbRootCertificateProvider {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbRootCertificateProvider", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Only applies to the v2rayN GUI's downloads and network requests. Does not affect the core's certificate validation. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbRootCertificateProviderTip {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbRootCertificateProviderTip", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Round Robin 的本地化字符串。
|
/// 查找类似 Round Robin 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3753,15 +3861,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Save rules 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbSaveNetBridgeRule {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbSaveNetBridgeRule", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 sing-box Full Config Template 的本地化字符串。
|
/// 查找类似 sing-box Full Config Template 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3898,7 +3997,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode 的本地化字符串。
|
/// 查找类似 For multi-interface environments, enter the interface name for outbound connections. On Linux/macOS, it only works when TUN mode is enabled. 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string TbSettingsBindInterfaceTip {
|
public static string TbSettingsBindInterfaceTip {
|
||||||
get {
|
get {
|
||||||
@@ -4995,6 +5094,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Custom HTTP outbound request headers as a JSON object with string or string array values. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TipHttpOutboundHeaders {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TipHttpOutboundHeaders", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 *Default value raw 的本地化字符串。
|
/// 查找类似 *Default value raw 的本地化字符串。
|
||||||
/// </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>
|
||||||
@@ -1044,6 +1044,12 @@
|
|||||||
<data name="TbHeaderType8" xml:space="preserve">
|
<data name="TbHeaderType8" xml:space="preserve">
|
||||||
<value>Congestion control</value>
|
<value>Congestion control</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>HTTP headers</value>
|
||||||
|
</data>
|
||||||
|
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>Custom HTTP outbound request headers as a JSON object with string or string array values.</value>
|
||||||
|
</data>
|
||||||
<data name="LvPrevProfile" xml:space="preserve">
|
<data name="LvPrevProfile" xml:space="preserve">
|
||||||
<value>Previous proxy remarks</value>
|
<value>Previous proxy remarks</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1495,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>
|
||||||
@@ -1750,7 +1756,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<value>Bind Interface</value>
|
<value>Bind Interface</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
||||||
<value>For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode</value>
|
<value>For multi-interface environments, enter the interface name for outbound connections. On Linux/macOS, it only works when TUN mode is enabled.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbPreSharedKey" xml:space="preserve">
|
<data name="TbPreSharedKey" xml:space="preserve">
|
||||||
<value>PreSharedKey</value>
|
<value>PreSharedKey</value>
|
||||||
@@ -1774,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>
|
||||||
@@ -1794,21 +1800,9 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||||
<value>Split the tail of packets into smaller fragments. This may affect throughput and latency.</value>
|
<value>Split the tail of packets into smaller fragments. This may affect throughput and latency.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbEnableNetBridge" xml:space="preserve">
|
|
||||||
<value>Enables process traffic hijacking (experimental), similar to Proxifer; can coexist with system proxy; conflicts with TUN mode, please do not enable them simultaneously;please allow v2rayN to use a private network in your firewall.</value>
|
|
||||||
</data>
|
|
||||||
<data name="NetBridgeRuleTips" xml:space="preserve">
|
|
||||||
<value>The process names that need to be proxied; separate multiple processes with commas. Processes not in the list will also be hijacked, but will connect directly.</value>
|
|
||||||
</data>
|
|
||||||
<data name="menuNetBridge" xml:space="preserve">
|
|
||||||
<value>Process traffic hijacking (experimental)</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||||
<value>DNS via Bridge</value>
|
<value>DNS via Bridge</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
|
||||||
<value>Save rules</value>
|
|
||||||
</data>
|
|
||||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||||
<value>Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks.</value>
|
<value>Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks.</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1818,10 +1812,52 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||||
<value>Invalid Realm URL.</value>
|
<value>Invalid Realm URL.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>Please enter valid HTTP request headers JSON.</value>
|
||||||
|
</data>
|
||||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||||
<value>Format: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
<value>Format: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||||
<value>Gecko Packet Size (min/max)</value>
|
<value>Gecko Packet Size (min/max)</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||||
|
<value>If enabled, use sing-box TUN; otherwise, use xray TUN.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||||
|
<value>Root Certificate Provider</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||||
|
<value>Only applies to the v2rayN GUI's downloads and network requests. Does not affect the core's certificate validation.</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -1044,6 +1044,12 @@
|
|||||||
<data name="TbHeaderType8" xml:space="preserve">
|
<data name="TbHeaderType8" xml:space="preserve">
|
||||||
<value>Управление перегрузками</value>
|
<value>Управление перегрузками</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>HTTP-заголовки</value>
|
||||||
|
</data>
|
||||||
|
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>Пользовательские заголовки исходящего HTTP-запроса. Введите JSON-объект, значения которого — строка или массив строк</value>
|
||||||
|
</data>
|
||||||
<data name="LvPrevProfile" xml:space="preserve">
|
<data name="LvPrevProfile" xml:space="preserve">
|
||||||
<value>Псевдоним предыдущего прокси</value>
|
<value>Псевдоним предыдущего прокси</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1114,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>
|
||||||
@@ -1345,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>
|
||||||
@@ -1405,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>
|
||||||
@@ -1486,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>
|
||||||
@@ -1495,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>
|
||||||
@@ -1582,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>
|
||||||
@@ -1614,6 +1620,9 @@
|
|||||||
<data name="TbEchConfigList" xml:space="preserve">
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
<value>EchConfigList</value>
|
<value>EchConfigList</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
||||||
|
<value>Проверять сертификат узла по имени</value>
|
||||||
|
</data>
|
||||||
<data name="TbFullCertTips" xml:space="preserve">
|
<data name="TbFullCertTips" xml:space="preserve">
|
||||||
<value>Полный сертификат (цепочка) в формате PEM</value>
|
<value>Полный сертификат (цепочка) в формате PEM</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1633,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>
|
||||||
@@ -1651,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>
|
||||||
@@ -1747,7 +1756,7 @@
|
|||||||
<value>Привязать интерфейс</value>
|
<value>Привязать интерфейс</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
||||||
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для привязки. Работает только в Windows и режиме 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>
|
||||||
@@ -1770,4 +1779,85 @@
|
|||||||
<data name="menuNewUpdate" xml:space="preserve">
|
<data name="menuNewUpdate" xml:space="preserve">
|
||||||
<value>Доступно обновление</value>
|
<value>Доступно обновление</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
||||||
|
<value>Предупреждение: 1 августа 2026 г. Xray отключит пропуск проверки сертификата (allowInsecure). Как можно скорее перейдите на привязанный отпечаток сертификата (pinnedPeerCertSha256). После этой даты allowInsecure использовать будет нельзя</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRouteExcludeAddress" xml:space="preserve">
|
||||||
|
<value>Адреса, исключаемые из маршрутизации</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
|
||||||
|
<value>Разделяйте запятыми (,)</value>
|
||||||
|
</data>
|
||||||
|
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
|
||||||
|
<value>Недопустимый адрес в списке исключений маршрутизации TUN: {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="MsgOptionsConflict" xml:space="preserve">
|
||||||
|
<value>Конфликт между {0} и {1}</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEnableFinalFragment" xml:space="preserve">
|
||||||
|
<value>Включить финальную фрагментацию (Final Fragment)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||||
|
<value>Разбивать конец пакетов на более мелкие фрагменты при отправке. Это может влиять на пропускную способность и задержку</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||||
|
<value>DNS через Bridge</value>
|
||||||
|
</data>
|
||||||
|
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||||
|
<value>Обнаружена небезопасная конфигурация: AllowInsecure включён, но сертификат не предоставлен. Это может привести к атаке «человек посередине» (MITM)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||||
|
<value>Realm URL</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||||
|
<value>Некорректный Realm URL</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>Введите корректный JSON заголовков HTTP-запроса</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||||
|
<value>Формат: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||||
|
<value>Размер пакета Gecko (мин/макс)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||||
|
<value>Если включено, используется sing-box TUN; иначе — xray TUN</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||||
|
<value>Поставщик корневых сертификатов</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||||
|
<value>Применяется только к загрузкам и сетевым запросам графического интерфейса v2rayN. Не влияет на проверку сертификатов ядром</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
||||||
</root>
|
</root>
|
||||||
@@ -1041,6 +1041,12 @@
|
|||||||
<data name="TbHeaderType8" xml:space="preserve">
|
<data name="TbHeaderType8" xml:space="preserve">
|
||||||
<value>拥塞控制算法</value>
|
<value>拥塞控制算法</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>HTTP 请求头</value>
|
||||||
|
</data>
|
||||||
|
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>自定义 HTTP 出站请求头,请输入值为字符串或字符串数组的 JSON 对象。</value>
|
||||||
|
</data>
|
||||||
<data name="LvPrevProfile" xml:space="preserve">
|
<data name="LvPrevProfile" xml:space="preserve">
|
||||||
<value>前置代理配置别名</value>
|
<value>前置代理配置别名</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1492,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>
|
||||||
@@ -1768,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>
|
||||||
@@ -1791,21 +1801,9 @@
|
|||||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||||
<value>将数据包末端拆分为更小片段发送,可能影响吞吐与延迟。</value>
|
<value>将数据包末端拆分为更小片段发送,可能影响吞吐与延迟。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbEnableNetBridge" xml:space="preserve">
|
|
||||||
<value>启用进程流量劫持(实验性),类似 Proxifer 功能;可以和系统代理并存;和 TUN 模式冲突,请不要同时开启;请在防火墙中允许 v2rayN 使用专用网络</value>
|
|
||||||
</data>
|
|
||||||
<data name="NetBridgeRuleTips" xml:space="preserve">
|
|
||||||
<value>需要代理的进程名,多个进程请用逗号分隔;不在列表中的进程也会被劫持,但是会直连</value>
|
|
||||||
</data>
|
|
||||||
<data name="menuNetBridge" xml:space="preserve">
|
|
||||||
<value>进程流量劫持(实验性)</value>
|
|
||||||
</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="TbSaveNetBridgeRule" xml:space="preserve">
|
|
||||||
<value>保存规则</value>
|
|
||||||
</data>
|
|
||||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||||
<value>检测到不安全配置:AllowInsecure 已启用,但未提供证书。这可能会导致中间人攻击。</value>
|
<value>检测到不安全配置:AllowInsecure 已启用,但未提供证书。这可能会导致中间人攻击。</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1815,10 +1813,52 @@
|
|||||||
<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">
|
||||||
|
<value>请填写合法的HTTP请求头JSON。</value>
|
||||||
|
</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>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||||
<value>Gecko 包大小 (最小/最大)</value>
|
<value>Gecko 包大小 (最小/最大)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||||
|
<value>启用则使用 sing-box TUN ,否则使用 xray TUN</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||||
|
<value>根证书提供者</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||||
|
<value>仅用于 v2rayN 界面程序的下载及网络请求,不影响核心的证书验证。</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
</root>
|
||||||
@@ -1041,6 +1041,12 @@
|
|||||||
<data name="TbHeaderType8" xml:space="preserve">
|
<data name="TbHeaderType8" xml:space="preserve">
|
||||||
<value>擁塞控制算法</value>
|
<value>擁塞控制算法</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>HTTP 請求頭</value>
|
||||||
|
</data>
|
||||||
|
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>自訂 HTTP 出站請求頭,請輸入值為字串或字串陣列的 JSON 物件。</value>
|
||||||
|
</data>
|
||||||
<data name="LvPrevProfile" xml:space="preserve">
|
<data name="LvPrevProfile" xml:space="preserve">
|
||||||
<value>前置代理節點別名</value>
|
<value>前置代理節點別名</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1492,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>
|
||||||
@@ -1768,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>
|
||||||
@@ -1791,19 +1801,64 @@
|
|||||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||||
<value>將封包末端拆分為更小的片段進行傳送,可能會影響吞吐量與延遲</value>
|
<value>將封包末端拆分為更小的片段進行傳送,可能會影響吞吐量與延遲</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbEnableNetBridge" xml:space="preserve">
|
|
||||||
<value>啟用進程流量劫持(實驗性),類似 Proxifer 功能;可以和系統代理並存;和 TUN 模式衝突,請不要同時開啟;請在防火牆中允許 v2rayN 使用專用網絡</value>
|
|
||||||
</data>
|
|
||||||
<data name="NetBridgeRuleTips" xml:space="preserve">
|
|
||||||
<value>需要代理程式的進程名,多個進程請用逗號分隔;不在清單中的進程也會被劫持,但是會直連</value>
|
|
||||||
</data>
|
|
||||||
<data name="menuNetBridge" xml:space="preserve">
|
|
||||||
<value>進程流量劫持(實驗性)</value>
|
|
||||||
</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="TbSaveNetBridgeRule" xml:space="preserve">
|
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||||
<value>保存規則</value>
|
<value>偵測到不安全的設定:已啟用 AllowInsecure,但未提供憑證。這可能導致中間人攻擊(MITM)</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||||
|
<value>Realm URL</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||||
|
<value>無效的 Realm URL.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||||
|
<value>格式: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||||
|
<value>Gecko 封包大小 (min/max)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||||
|
<value>啟用則使用 sing-box TUN ,否則使用 xray TUN</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||||
|
<value>請填寫合法的HTTP請求頭JSON。</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||||
|
<value>根憑證提供者</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||||
|
<value>僅適用於 v2rayN GUI 的下載與網路請求,不影響核心的憑證驗證</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
|
||||||
|
<value>連線代理解析策略</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
|
||||||
|
<value>不建議啟用,特殊情況可能造成連線循環</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEnableHappyEyeballs" xml:space="preserve">
|
||||||
|
<value>啟用 Happy Eyeballs</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
|
||||||
|
<value>需搭配 UseIP 策略,啟用後會同時嘗試 IPv4 與 IPv6 連線,並自動選擇速度較快的路徑</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbIpv6Address" xml:space="preserve">
|
||||||
|
<value>Ipv6 位址</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbIpv4Address" xml:space="preserve">
|
||||||
|
<value>Ipv4 位址</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
2697
v2rayN/ServiceLib/Sample/chrome_roots_pem
Normal file
2697
v2rayN/ServiceLib/Sample/chrome_roots_pem
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 "============================================"
|
||||||
|
|||||||
3111
v2rayN/ServiceLib/Sample/mozilla_roots_pem
Normal file
3111
v2rayN/ServiceLib/Sample/mozilla_roots_pem
Normal file
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" />
|
||||||
@@ -23,6 +26,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Sample\chrome_roots_pem" />
|
||||||
<EmbeddedResource Include="Sample\clash_mixin_yaml" />
|
<EmbeddedResource Include="Sample\clash_mixin_yaml" />
|
||||||
<EmbeddedResource Include="Sample\clash_tun_yaml" />
|
<EmbeddedResource Include="Sample\clash_tun_yaml" />
|
||||||
<EmbeddedResource Include="Sample\custom_routing_black" />
|
<EmbeddedResource Include="Sample\custom_routing_black" />
|
||||||
@@ -32,6 +36,7 @@
|
|||||||
<EmbeddedResource Include="Sample\dns_v2ray_normal" />
|
<EmbeddedResource Include="Sample\dns_v2ray_normal" />
|
||||||
<EmbeddedResource Include="Sample\kill_as_sudo_linux_sh" />
|
<EmbeddedResource Include="Sample\kill_as_sudo_linux_sh" />
|
||||||
<EmbeddedResource Include="Sample\kill_as_sudo_osx_sh" />
|
<EmbeddedResource Include="Sample\kill_as_sudo_osx_sh" />
|
||||||
|
<EmbeddedResource Include="Sample\mozilla_roots_pem" />
|
||||||
<EmbeddedResource Include="Sample\pac" />
|
<EmbeddedResource Include="Sample\pac" />
|
||||||
<EmbeddedResource Include="Sample\proxy_set_linux_sh" />
|
<EmbeddedResource Include="Sample\proxy_set_linux_sh" />
|
||||||
<EmbeddedResource Include="Sample\proxy_set_osx_sh" />
|
<EmbeddedResource Include="Sample\proxy_set_osx_sh" />
|
||||||
@@ -90,7 +95,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\NetBridge\src\NetBridgeLib\NetBridgeLib.csproj" />
|
|
||||||
<ProjectReference Include="..\ServiceLib.UdpTest\ServiceLib.UdpTest.csproj" />
|
<ProjectReference Include="..\ServiceLib.UdpTest\ServiceLib.UdpTest.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -275,9 +293,10 @@ public partial class CoreConfigSingboxService
|
|||||||
{
|
{
|
||||||
var realm4Sbox = new HyRealm4Sbox()
|
var realm4Sbox = new HyRealm4Sbox()
|
||||||
{
|
{
|
||||||
server_url = realm.RendezvousHostPort,
|
server_url = realm.ToServerUrl(),
|
||||||
token = realm.Token,
|
token = realm.Token,
|
||||||
realm_id = realm.RealmName,
|
realm_id = realm.RealmName,
|
||||||
|
stun_servers = realm.StunList?.Count > 0 ? realm.StunList : null,
|
||||||
};
|
};
|
||||||
outbound.realm = realm4Sbox;
|
outbound.realm = realm4Sbox;
|
||||||
outbound.server = null;
|
outbound.server = null;
|
||||||
@@ -416,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()
|
||||||
@@ -588,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);
|
||||||
@@ -716,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 [];
|
||||||
}
|
}
|
||||||
@@ -735,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,38 @@ public partial class CoreConfigSingboxService
|
|||||||
_coreConfig.route.rules.AddRange(tunRules);
|
_coreConfig.route.rules.AddRange(tunRules);
|
||||||
}
|
}
|
||||||
|
|
||||||
var (lstDnsExe, 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.
|
||||||
|
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_name = lstDnsExe
|
ip_cidr = [.. tunAddresses],
|
||||||
});
|
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_name = 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,34 +284,38 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
|
private List<string> BuildRoutingDirectExe()
|
||||||
{
|
{
|
||||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
|
var allCoreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||||
|
|
||||||
foreach (var coreConfig in coreInfoResult)
|
foreach (var coreConfig in allCoreInfo)
|
||||||
{
|
{
|
||||||
|
if (!context.ProtectCoreTypeList.Contains(coreConfig.CoreType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (coreConfig.CoreType == ECoreType.v2rayN)
|
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (coreConfig.CoreExes == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
foreach (var baseExeName in coreConfig.CoreExes)
|
foreach (var baseExeName in coreConfig.CoreExes)
|
||||||
{
|
{
|
||||||
if (coreConfig.CoreType != ECoreType.sing_box)
|
//directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||||
|
var exePath = CoreInfoManager.Instance.GetCoreExecFile(coreConfig, out _);
|
||||||
|
if (!exePath.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
directExeSet.Add(exePath);
|
||||||
}
|
}
|
||||||
directExeSet.Add(Utils.GetExeName(baseExeName));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var lstDnsExe = new List<string>(dnsExeSet);
|
return directExeSet.ToList();
|
||||||
var lstDirectExe = new List<string>(directExeSet);
|
|
||||||
|
|
||||||
return (lstDnsExe, lstDirectExe);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenRoutingUserRule(RulesItem? item)
|
private void GenRoutingUserRule(RulesItem? item)
|
||||||
@@ -542,7 +574,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)
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public partial class CoreConfigV2rayService
|
|||||||
EMultipleLoad.RoundRobin => "roundRobin",
|
EMultipleLoad.RoundRobin => "roundRobin",
|
||||||
EMultipleLoad.LeastPing => "leastPing",
|
EMultipleLoad.LeastPing => "leastPing",
|
||||||
EMultipleLoad.LeastLoad => "leastLoad",
|
EMultipleLoad.LeastLoad => "leastLoad",
|
||||||
|
EMultipleLoad.Fallback => "leastLoad",
|
||||||
_ => "roundRobin",
|
_ => "roundRobin",
|
||||||
};
|
};
|
||||||
var balancerTag = $"{selector}{Global.BalancerTagSuffix}";
|
var balancerTag = $"{selector}{Global.BalancerTagSuffix}";
|
||||||
@@ -98,13 +99,16 @@ public partial class CoreConfigV2rayService
|
|||||||
strategy = new()
|
strategy = new()
|
||||||
{
|
{
|
||||||
type = strategyType,
|
type = strategyType,
|
||||||
settings = new()
|
settings = strategyType == "leastLoad"
|
||||||
{
|
? new()
|
||||||
expected = 1,
|
{
|
||||||
},
|
expected = 1,
|
||||||
|
tolerance = multipleLoad == EMultipleLoad.Fallback ? 0.2 : null,
|
||||||
|
maxRTT = multipleLoad == EMultipleLoad.Fallback ? "5000ms" : null,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
},
|
},
|
||||||
tag = balancerTag,
|
tag = balancerTag,
|
||||||
fallbackTag = multipleLoad == EMultipleLoad.Fallback ? Global.DirectTag : null,
|
|
||||||
};
|
};
|
||||||
_coreConfig.routing.balancers ??= [];
|
_coreConfig.routing.balancers ??= [];
|
||||||
_coreConfig.routing.balancers.Add(balancer);
|
_coreConfig.routing.balancers.Add(balancer);
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user