mirror of
https://github.com/2dust/v2rayN.git
synced 2026-07-26 18:02:05 +03:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
7100b3f0a5 | ||
|
|
8d94997ba0 | ||
|
|
c97dc1d894 | ||
|
|
b198fd8be7 | ||
|
|
e0c1ff24be | ||
|
|
3cebf492d1 | ||
|
|
ef46f4e7e6 | ||
|
|
81c118c9b2 | ||
|
|
f1b88ccf2a | ||
|
|
ce380b861c |
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)");
|
||||
}
|
||||
9
.github/workflows/build-all.yml
vendored
9
.github/workflows/build-all.yml
vendored
@@ -9,8 +9,17 @@ on:
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
public-key:
|
||||
if: inputs.release_tag != ''
|
||||
uses: ./.github/workflows/pub-key.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
120
.github/workflows/build-linux.yml
vendored
120
.github/workflows/build-linux.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -26,6 +27,7 @@ jobs:
|
||||
with:
|
||||
target: linux
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets: inherit
|
||||
|
||||
deb:
|
||||
name: build and release deb x64 & arm64
|
||||
@@ -56,9 +58,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/deb" -name "v2rayn_*_arm64.deb" -exec mv {} "$GITHUB_WORKSPACE/dist/deb/v2rayN-linux-arm64.deb" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/deb" || true
|
||||
- name: Upload DEBs to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with: { file: "dist/deb/**/*.deb", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true }
|
||||
- name: Upload DEB artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-deb, path: "dist/deb/**/*.deb", if-no-files-found: error }
|
||||
|
||||
rpm:
|
||||
name: build and release rpm x64 & arm64
|
||||
@@ -115,9 +117,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/rpm" -name "v2rayN-*-1*.aarch64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
|
||||
- name: Upload RPMs to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with: { file: "dist/rpm/**/*.rpm", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true }
|
||||
- name: Upload RPM artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-rpm, path: "dist/rpm/**/*.rpm", if-no-files-found: error }
|
||||
|
||||
rpm-riscv64:
|
||||
name: build and release rpm riscv64
|
||||
@@ -155,25 +157,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/rpm-riscv64" -name "*.riscv64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm-riscv64/v2rayN-linux-rhel-riscv64.rpm" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/rpm-riscv64" || true
|
||||
- name: Upload RPMs to release
|
||||
shell: bash
|
||||
env: { GITHUB_TOKEN: "${{ github.token }}" }
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s globstar nullglob
|
||||
files=(dist/rpm-riscv64/**/*.rpm)
|
||||
(( ${#files[@]} )) || { echo "No RPMs found."; exit 1; }
|
||||
api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}"
|
||||
upload_url="$(curl -fsSL -H "Authorization: Bearer ${GITHUB_TOKEN}" "$api" | jq -r '.upload_url // empty' | sed 's/{?name,label}//')"
|
||||
[[ "$upload_url" ]] || { echo "Release upload URL not found: ${RELEASE_TAG}"; exit 1; }
|
||||
for f in "${files[@]}"; do
|
||||
echo "Uploading ${f##*/}"
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
-H "Content-Type: application/x-rpm" \
|
||||
--data-binary @"$f" \
|
||||
"${upload_url}?name=${f##*/}"
|
||||
done
|
||||
- name: Upload RPM artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-rpm-riscv64, path: "dist/rpm-riscv64/**/*.rpm", if-no-files-found: error }
|
||||
|
||||
deb-riscv64:
|
||||
name: build and release deb riscv64
|
||||
@@ -212,25 +198,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/deb-riscv64" -name "*.deb" -exec mv {} "$GITHUB_WORKSPACE/dist/deb-riscv64/v2rayN-linux-riscv64.deb" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/deb-riscv64" || true
|
||||
- name: Upload DEBs to release
|
||||
shell: bash
|
||||
env: { GITHUB_TOKEN: "${{ github.token }}" }
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s globstar nullglob
|
||||
files=(dist/deb-riscv64/**/*.deb)
|
||||
(( ${#files[@]} )) || { echo "No DEBs found."; exit 1; }
|
||||
api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}"
|
||||
upload_url="$(curl -fsSL -H "Authorization: Bearer ${GITHUB_TOKEN}" "$api" | jq -r '.upload_url // empty' | sed 's/{?name,label}//')"
|
||||
[[ "$upload_url" ]] || { echo "Release upload URL not found: ${RELEASE_TAG}"; exit 1; }
|
||||
for f in "${files[@]}"; do
|
||||
echo "Uploading ${f##*/}"
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
-H "Content-Type: application/vnd.debian.binary-package" \
|
||||
--data-binary @"$f" \
|
||||
"${upload_url}?name=${f##*/}"
|
||||
done
|
||||
- name: Upload DEB artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-deb-riscv64, path: "dist/deb-riscv64/**/*.deb", if-no-files-found: error }
|
||||
|
||||
deb-loong64:
|
||||
name: build and release deb loong64
|
||||
@@ -244,7 +214,7 @@ jobs:
|
||||
QCOW2_IMAGE: debian13-loong64.qcow2
|
||||
EFI_CODE: edk2-loongarch64-code.fd
|
||||
EFI_VARS: edk2-loongarch64-vars.fd
|
||||
QEMU_VERSION: 10.2.3
|
||||
QEMU_VERSION: 10.2.4
|
||||
steps:
|
||||
- name: Prepare host tools
|
||||
shell: bash
|
||||
@@ -288,7 +258,7 @@ jobs:
|
||||
export VIRTIOFSD_SOCKET="$RUNNER_TEMP/virtiofs-deb-loong64.sock"
|
||||
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=$!
|
||||
trap 'sudo kill "$VIRTIOFSD_PID" 2>/dev/null || true; rm -f "$VIRTIOFSD_SOCKET"' EXIT
|
||||
@@ -378,9 +348,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/deb-loong64" -name "*.deb" -exec mv {} "$GITHUB_WORKSPACE/dist/deb-loong64/v2rayN-linux-loong64.deb" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/deb-loong64"
|
||||
- name: Upload DEBs to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with: { file: "dist/deb-loong64/**/*.deb", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true }
|
||||
- name: Upload DEB artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-deb-loong64, path: "dist/deb-loong64/**/*.deb", if-no-files-found: error }
|
||||
|
||||
rpm-loong64:
|
||||
name: build and release rpm loong64
|
||||
@@ -394,7 +364,7 @@ jobs:
|
||||
QCOW2_IMAGE: fedora43-loong64.qcow2
|
||||
EFI_CODE: edk2-loongarch64-code.fd
|
||||
EFI_VARS: edk2-loongarch64-vars.fd
|
||||
QEMU_VERSION: 10.2.3
|
||||
QEMU_VERSION: 10.2.4
|
||||
steps:
|
||||
- name: Prepare host tools
|
||||
shell: bash
|
||||
@@ -438,7 +408,7 @@ jobs:
|
||||
export VIRTIOFSD_SOCKET="$RUNNER_TEMP/virtiofs-rpm-loong64.sock"
|
||||
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=$!
|
||||
trap 'sudo kill "$VIRTIOFSD_PID" 2>/dev/null || true; rm -f "$VIRTIOFSD_SOCKET"' EXIT
|
||||
@@ -528,6 +498,48 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/rpm-loong64" -name "*.loongarch64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm-loong64/v2rayN-linux-rhel-loong64.rpm" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/rpm-loong64"
|
||||
- name: Upload RPMs to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with: { file: "dist/rpm-loong64/**/*.rpm", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true }
|
||||
- name: Upload RPM artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-rpm-loong64, path: "dist/rpm-loong64/**/*.rpm", if-no-files-found: error }
|
||||
|
||||
release-deb:
|
||||
name: sign and upload deb x64 arm64
|
||||
needs: deb
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-deb, file_pattern: "*.deb" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-rpm:
|
||||
name: sign and upload rpm x64 arm64
|
||||
needs: rpm
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-rpm, file_pattern: "*.rpm" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-rpm-riscv64:
|
||||
name: sign and upload rpm riscv64
|
||||
needs: rpm-riscv64
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-rpm-riscv64, file_pattern: "*.rpm" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-deb-riscv64:
|
||||
name: sign and upload deb riscv64
|
||||
needs: deb-riscv64
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-deb-riscv64, file_pattern: "*.deb" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-deb-loong64:
|
||||
name: sign and upload deb loong64
|
||||
needs: deb-loong64
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-deb-loong64, file_pattern: "*.deb" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-rpm-loong64:
|
||||
name: sign and upload rpm loong64
|
||||
needs: rpm-loong64
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-rpm-loong64, file_pattern: "*.rpm" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
31
.github/workflows/build-osx.yml
vendored
31
.github/workflows/build-osx.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -26,6 +27,7 @@ jobs:
|
||||
with:
|
||||
target: macos
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets: inherit
|
||||
|
||||
dmg:
|
||||
name: package and release macOS dmg
|
||||
@@ -62,13 +64,24 @@ jobs:
|
||||
- name: Package dmg
|
||||
run: ./package-osx.sh macos-$Arch v2rayN-macos-$Arch ${{ inputs.release_tag }}
|
||||
|
||||
- name: Sleep for race condition between matrix jobs
|
||||
run: sleep "$(od -An -N2 -tu2 /dev/urandom | awk 'NR==1{printf "%.2f", $1/5461}')"
|
||||
|
||||
- name: Upload dmg to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
- name: Upload dmg artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
file: ${{ github.workspace }}/v2rayN*.dmg
|
||||
tag: ${{ inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
name: release-dmg-${{ matrix.arch }}
|
||||
path: v2rayN*.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
release-dmg:
|
||||
name: sign and upload macOS dmg
|
||||
if: inputs.release_tag != ''
|
||||
needs: dmg
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ x64, arm64 ]
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
artifact_name: release-dmg-${{ matrix.arch }}
|
||||
file_pattern: '*.dmg'
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
2
.github/workflows/build-windows-desktop.yml
vendored
2
.github/workflows/build-windows-desktop.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -26,3 +27,4 @@ jobs:
|
||||
with:
|
||||
target: windows-desktop
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets: inherit
|
||||
|
||||
30
.github/workflows/build-windows-x86.yml
vendored
30
.github/workflows/build-windows-x86.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
package-x86:
|
||||
@@ -51,7 +52,7 @@ jobs:
|
||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||
|
||||
- name: Setup .NET 10.0.1xx
|
||||
uses: actions/setup-dotnet@v5.3.0
|
||||
uses: actions/setup-dotnet@v6.0.0
|
||||
with:
|
||||
dotnet-version: 10.0.1xx
|
||||
|
||||
@@ -222,10 +223,25 @@ jobs:
|
||||
-DestinationPath "${{ matrix.zip_name }}" `
|
||||
-Force
|
||||
|
||||
- name: Upload zip archive to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
- name: Upload zip artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
file: ${{ matrix.zip_name }}
|
||||
tag: ${{ inputs.release_tag }}
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
name: release-windows-x86-${{ matrix.flavor }}
|
||||
path: ${{ matrix.zip_name }}
|
||||
if-no-files-found: error
|
||||
|
||||
release-x86:
|
||||
name: sign and upload windows x86
|
||||
if: inputs.release_tag != ''
|
||||
needs: package-x86
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [ wpf, desktop ]
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
artifact_name: release-windows-x86-${{ matrix.flavor }}
|
||||
file_pattern: '*.zip'
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
2
.github/workflows/build-windows.yml
vendored
2
.github/workflows/build-windows.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -27,3 +28,4 @@ jobs:
|
||||
with:
|
||||
target: windows
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets: inherit
|
||||
|
||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -22,6 +22,7 @@ jobs:
|
||||
case(
|
||||
inputs.target == 'macos', 'macos-latest',
|
||||
inputs.target == 'linux', 'ubuntu-24.04',
|
||||
inputs.target == 'windows', 'windows-latest',
|
||||
'ubuntu-latest'
|
||||
)
|
||||
}}
|
||||
@@ -68,18 +69,25 @@ jobs:
|
||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.3.0
|
||||
uses: actions/setup-dotnet@v6.0.0
|
||||
with:
|
||||
dotnet-version: '10.0.1xx'
|
||||
|
||||
- name: Build v2rayN
|
||||
shell: bash
|
||||
working-directory: ./v2rayN
|
||||
run: dotnet publish $Project -c Release -r $RID -p:SelfContained=true $ExtOpt -o $Output
|
||||
|
||||
- name: Build AmazTool
|
||||
shell: bash
|
||||
working-directory: ./v2rayN
|
||||
run: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r $RID -p:SelfContained=true -p:PublishTrimmed=true $ExtOpt -o $Output
|
||||
|
||||
- name: Remove Debug Symbols
|
||||
shell: bash
|
||||
run: |
|
||||
find ${{ matrix.arch }} -type f -name '*.pdb' -delete
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
|
||||
32
.github/workflows/package-zip.yml
vendored
32
.github/workflows/package-zip.yml
vendored
@@ -9,9 +9,13 @@ on:
|
||||
target: # windows linux macos windows-desktop
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY:
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
package:
|
||||
@@ -55,13 +59,23 @@ jobs:
|
||||
if: inputs.target == 'windows-desktop'
|
||||
run: mv "v2rayN-$Target-$Arch.zip" "v2rayN-$Target-$Arch-desktop.zip"
|
||||
|
||||
- name: Sleep for race condition between matrix jobs
|
||||
run: sleep "$(od -An -N2 -tu2 /dev/urandom | awk 'NR==1{printf "%.2f", $1/5461}')"
|
||||
|
||||
- name: Upload zip archive to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
- name: Upload zip artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
file: ${{ github.workspace }}/v2rayN*.zip
|
||||
tag: ${{ inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
name: release-${{ inputs.target }}-${{ matrix.arch }}
|
||||
path: v2rayN*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
name: sign and upload zip x64 arm64
|
||||
needs: package
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ x64, arm64 ]
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
artifact_name: release-${{ inputs.target }}-${{ matrix.arch }}
|
||||
file_pattern: '*.zip'
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
48
.github/workflows/pub-key.yml
vendored
Normal file
48
.github/workflows/pub-key.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: publish GPG public key
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY:
|
||||
required: true
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Import GPG private key
|
||||
shell: bash
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
install -m 700 -d ~/.gnupg
|
||||
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
|
||||
|
||||
- name: Export GPG public key
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
fingerprint="$(gpg --batch --list-secret-keys --with-colons | awk -F: '$1 == "fpr" { print $10; exit }')"
|
||||
[[ -n "$fingerprint" ]] || { echo "No GPG secret key found."; exit 1; }
|
||||
gpg --batch --armor --export "$fingerprint" > v2rayN-public-key.asc
|
||||
|
||||
- name: Upload GPG public key to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
file: v2rayN-public-key.asc
|
||||
tag: ${{ inputs.release_tag }}
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.3.0
|
||||
uses: actions/setup-dotnet@v6.0.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
|
||||
76
.github/workflows/upload-sign.yml
vendored
Normal file
76
.github/workflows/upload-sign.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: sign and upload release files
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
type: string
|
||||
artifact_name:
|
||||
required: true
|
||||
type: string
|
||||
file_pattern:
|
||||
required: false
|
||||
type: string
|
||||
default: '**/*'
|
||||
content_type:
|
||||
required: false
|
||||
type: string
|
||||
default: application/octet-stream
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY:
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Download release artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: dist
|
||||
|
||||
- name: Import GPG private key
|
||||
shell: bash
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
install -m 700 -d ~/.gnupg
|
||||
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
|
||||
|
||||
- name: Sign release files
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd dist
|
||||
shopt -s globstar nullglob
|
||||
files=( ${{ inputs.file_pattern }} )
|
||||
(( ${#files[@]} )) || { echo "No files matched: ${{ inputs.file_pattern }}"; exit 1; }
|
||||
for f in "${files[@]}"; do
|
||||
[[ -f "$f" ]] || continue
|
||||
gpg --batch --yes --armor --detach-sign --output "$f.sig" "$f"
|
||||
done
|
||||
|
||||
- name: Upload release files
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
file: dist/${{ inputs.file_pattern }}
|
||||
tag: ${{ inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
|
||||
- name: Upload release signatures
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
file: dist/${{ inputs.file_pattern }}.sig
|
||||
tag: ${{ inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
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
|
||||
|
||||
$x64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip' | Select -ExpandProperty browser_download_url
|
||||
$arm64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-arm64\.zip' | Select -ExpandProperty browser_download_url
|
||||
$assets = $targetRelease | Select -ExpandProperty assets -First 1
|
||||
$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
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -84,6 +84,9 @@ StyleCopReport.xml
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
bin/
|
||||
obj/
|
||||
build/
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "v2rayN/GlobalHotKeys"]
|
||||
path = v2rayN/GlobalHotKeys
|
||||
url = https://github.com/2dust/GlobalHotKeys
|
||||
[submodule "v2rayN/NetBridge"]
|
||||
path = v2rayN/NetBridge
|
||||
url = https://github.com/2dust/NetBridge
|
||||
|
||||
80
README.md
80
README.md
@@ -1,18 +1,78 @@
|
||||
# v2rayN
|
||||
|
||||
A GUI client for Windows, Linux and macOS, support [Xray](https://github.com/XTLS/Xray-core)
|
||||
and [sing-box](https://github.com/SagerNet/sing-box)
|
||||
and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
### A GUI client for Windows, Linux and macOS. Support [Xray](https://github.com/XTLS/Xray-core) and [sing-box](https://github.com/SagerNet/sing-box) and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
|
||||
[](https://github.com/2dust/v2rayN/commits/master)
|
||||
[](https://www.codefactor.io/repository/github/2dust/v2rayn)
|
||||
[](https://github.com/2dust/v2rayN/releases)
|
||||
[](https://t.me/v2rayn)
|
||||
[](https://github.com/2dust/v2rayN/releases)
|
||||
[](https://github.com/2dust/v2rayN/releases)
|
||||
[](https://t.me/v2rayn)
|
||||
|
||||
[](https://github.com/2dust/v2rayN)
|
||||
[](https://github.com/2dust/v2rayN)
|
||||
[](https://github.com/2dust/v2rayN)
|
||||
[](https://github.com/2dust/v2rayN)
|
||||
|
||||
## How to use
|
||||
|
||||
Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details.
|
||||
---
|
||||
|
||||
## Telegram Channel
|
||||
## Download / 下载
|
||||
|
||||
[github_2dust](https://t.me/github_2dust)
|
||||
Download the latest release here:
|
||||
|
||||
在这里下载最新版本:
|
||||
|
||||
[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 / 使用文档
|
||||
|
||||
Read the Wiki for usage guides and configuration details.
|
||||
|
||||
请阅读 Wiki 获取使用说明和配置教程。
|
||||
|
||||
[https://github.com/2dust/v2rayN/wiki](https://github.com/2dust/v2rayN/wiki)
|
||||
|
||||
---
|
||||
|
||||
## Supported Platforms / 支持平台
|
||||
|
||||
| Platform / 平台 | x64 | x86 | arm64 | riscv64 | loong64 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Windows | ✅ | ✅ | ✅ | - | - |
|
||||
| Linux | ✅ | - | ✅ | ✅ | ✅ |
|
||||
| macOS | ✅ | - | ✅ | - | - |
|
||||
|
||||
---
|
||||
|
||||
## GPG Verification / GPG 签名校验
|
||||
|
||||
Release files are signed with GPG to verify authenticity and integrity, helping prevent mirror, ISP, or CDN hijacking.
|
||||
|
||||
发布文件已使用 GPG 签名,可用于校验文件真实性与完整性,预防镜像站、运营商或 CDN 劫持。
|
||||
|
||||
### Fingerprint / 公钥指纹
|
||||
|
||||
```text
|
||||
7694 5E9F 3E9A 168F 8070 F195 805D 661C
|
||||
134D FAF6 8903 C199 463C 31E5 AE90 3AE0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Community / 社区
|
||||
|
||||
Telegram Group / Telegram 群组:
|
||||
|
||||
[https://t.me/v2rayN](https://t.me/v2rayN)
|
||||
|
||||
Telegram Channel / Telegram 频道:
|
||||
|
||||
[https://t.me/github_2dust](https://t.me/github_2dust)
|
||||
|
||||
@@ -8,13 +8,13 @@ BUILD_FROM=""
|
||||
XRAY_VER="${XRAY_VER:-}"
|
||||
SING_VER="${SING_VER:-}"
|
||||
|
||||
MIN_KERNEL="5.10"
|
||||
MIN_KERNEL="6.12"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
OUTPUT_DIR="${HOME}/debbuild"
|
||||
DOTNET_TFM="net10.0"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.109"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.110"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
||||
@@ -116,20 +116,7 @@ install_dependencies() {
|
||||
|
||||
export PATH="$HOME/.dotnet:$PATH"
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -610,8 +597,8 @@ package_binary() {
|
||||
write_desktop_file "$stage"
|
||||
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"
|
||||
cat > "$workdir/debian/control" <<EOF
|
||||
Source: v2rayn
|
||||
|
||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
OUTPUT_DIR="${HOME}/debbuild"
|
||||
DOTNET_RISCV_VERSION="10.0.109"
|
||||
DOTNET_RISCV_VERSION="10.0.110"
|
||||
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
||||
@@ -595,8 +595,8 @@ package_binary() {
|
||||
write_desktop_file "$stage"
|
||||
write_maintainer_scripts "$debian_dir"
|
||||
|
||||
extra_depends="libc6 (>= 2.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"
|
||||
cat > "$workdir/debian/control" <<EOF
|
||||
Source: v2rayn
|
||||
|
||||
@@ -609,7 +609,7 @@ package_binary() {
|
||||
write_desktop_file "$stage"
|
||||
write_maintainer_scripts "$debian_dir"
|
||||
|
||||
extra_depends="libc6 (>= 2.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"
|
||||
cat > "$workdir/debian/control" <<EOF
|
||||
@@ -753,4 +753,4 @@ main() {
|
||||
print_summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
main "$@"
|
||||
|
||||
@@ -54,7 +54,7 @@ cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.7</string>
|
||||
<string>13.7</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
@@ -8,12 +8,12 @@ BUILD_FROM=""
|
||||
XRAY_VER="${XRAY_VER:-}"
|
||||
SING_VER="${SING_VER:-}"
|
||||
|
||||
MIN_KERNEL="5.10"
|
||||
MIN_KERNEL="6.12"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.109"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.110"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
||||
@@ -112,20 +112,7 @@ install_dependencies() {
|
||||
|
||||
export PATH="$HOME/.dotnet:$PATH"
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -523,13 +510,13 @@ ExclusiveArch: loongarch64
|
||||
Source0: __PKGROOT__.tar.gz
|
||||
|
||||
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.34
|
||||
Requires: fontconfig >= 2.13.1
|
||||
Requires: glibc >= 2.39
|
||||
Requires: fontconfig >= 2.15.0
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 8.32
|
||||
Requires: bash >= 5.1
|
||||
Requires: freetype >= 2.10
|
||||
Requires: coreutils >= 9.4
|
||||
Requires: bash >= 5.2.21
|
||||
Requires: freetype >= 2.13
|
||||
|
||||
%description
|
||||
v2rayN Linux for Red Hat Enterprise Linux
|
||||
|
||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
||||
DOTNET_RISCV_VERSION="10.0.109"
|
||||
DOTNET_RISCV_VERSION="10.0.110"
|
||||
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
||||
@@ -509,13 +509,13 @@ ExclusiveArch: riscv64
|
||||
Source0: __PKGROOT__.tar.gz
|
||||
|
||||
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.34
|
||||
Requires: fontconfig >= 2.13.1
|
||||
Requires: glibc >= 2.39
|
||||
Requires: fontconfig >= 2.15.0
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 8.32
|
||||
Requires: bash >= 5.1
|
||||
Requires: freetype >= 2.10
|
||||
Requires: coreutils >= 9.4
|
||||
Requires: bash >= 5.2.21
|
||||
Requires: freetype >= 2.13
|
||||
|
||||
%description
|
||||
v2rayN Linux for Red Hat Enterprise Linux
|
||||
|
||||
@@ -498,13 +498,13 @@ ExclusiveArch: aarch64 x86_64
|
||||
Source0: __PKGROOT__.tar.gz
|
||||
|
||||
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.34
|
||||
Requires: fontconfig >= 2.13.1
|
||||
Requires: glibc >= 2.39
|
||||
Requires: fontconfig >= 2.15.0
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 8.32
|
||||
Requires: bash >= 5.1
|
||||
Requires: freetype >= 2.10
|
||||
Requires: coreutils >= 9.4
|
||||
Requires: bash >= 5.2.21
|
||||
Requires: freetype >= 2.13
|
||||
|
||||
%description
|
||||
v2rayN Linux for Red Hat Enterprise Linux
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.23.0</Version>
|
||||
<Version>7.24.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.17" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.17" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.13" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.1" />
|
||||
<PackageVersion Include="Downloader" Version="5.7.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.14" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||
<PackageVersion Include="NLog" Version="6.1.3" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.2" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||
<PackageVersion Include="YamlDotNet" Version="18.0.0" />
|
||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.0" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="12.1.0" />
|
||||
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.5.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.3" />
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.0.3" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.2" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.5" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="12.1.0" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0" />
|
||||
<PackageVersion Include="NLog" Version="6.1.4" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3.10" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
|
||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Submodule v2rayN/GlobalHotKeys updated: 569a95bb0f...162d401dfe
Submodule v2rayN/NetBridge deleted from e9b16cd187
@@ -59,7 +59,7 @@ internal static class CoreConfigTestFactory
|
||||
},
|
||||
WebDavItem = new WebDavItem(),
|
||||
CheckUpdateItem = new CheckUpdateItem(),
|
||||
Fragment4RayItem = new Fragment4RayItem { Packets = "tlshello", Length = "100-200", Interval = "10-20" },
|
||||
Fragment4RayItem = new Fragment4RayItem { Packets = "tlshello", Lengths = ["100-200"], Delays = ["10-20"] },
|
||||
Inbound =
|
||||
[
|
||||
new InItem
|
||||
@@ -84,6 +84,7 @@ internal static class CoreConfigTestFactory
|
||||
ParallelQuery = false,
|
||||
Strategy4Freedom = Global.AsIs,
|
||||
Strategy4Proxy = Global.AsIs,
|
||||
Strategy4ProxyDial = Global.AsIs,
|
||||
},
|
||||
IndexId = string.Empty,
|
||||
SubIndexId = string.Empty,
|
||||
@@ -130,6 +131,25 @@ 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 CreatePolicyGroupNode(ECoreType coreType, string indexId, string remarks,
|
||||
IEnumerable<string> childIndexIds)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using AwesomeAssertions;
|
||||
using ServiceLib.Common;
|
||||
using ServiceLib.Enums;
|
||||
using ServiceLib.Handler.Fmt;
|
||||
using ServiceLib.Manager;
|
||||
using ServiceLib.Models;
|
||||
using ServiceLib.Models.Dto;
|
||||
using ServiceLib.Services.CoreConfig;
|
||||
using Xunit;
|
||||
|
||||
@@ -557,4 +559,37 @@ 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.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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,39 @@ public class CoreConfigV2rayServiceTests
|
||||
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]
|
||||
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");
|
||||
directOutbound.Should().NotBeNull();
|
||||
directOutbound!.settings.domainStrategy.Should().Be("UseIPv4");
|
||||
directOutbound!.streamSettings.sockopt!.domainStrategy.Should().Be("UseIPv4");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
91
v2rayN/ServiceLib.Tests/Fmt/HyRealmTests.cs
Normal file
91
v2rayN/ServiceLib.Tests/Fmt/HyRealmTests.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using AwesomeAssertions;
|
||||
using ServiceLib.Handler.Fmt;
|
||||
using ServiceLib.Models.Dto;
|
||||
using Xunit;
|
||||
|
||||
namespace ServiceLib.Tests.Fmt;
|
||||
|
||||
public class HyRealmTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryParse_ShouldParseValidRealm()
|
||||
{
|
||||
var str = "realm://public@realm.hy2.io/57f9be7c-2810-4f5b-8cb9-260bc84d6c90?stun=example.stun:3478&stun=example2.stun:3478";
|
||||
var result = HyRealm.TryParse(str, out var realm);
|
||||
result.Should().BeTrue();
|
||||
realm.Should().NotBeNull();
|
||||
|
||||
realm.IsHttp.Should().BeFalse();
|
||||
realm.Token.Should().Be("public");
|
||||
realm.RendezvousHost.Should().Be("realm.hy2.io");
|
||||
realm.RendezvousPort.Should().Be(443);
|
||||
realm.RealmName.Should().Be("57f9be7c-2810-4f5b-8cb9-260bc84d6c90");
|
||||
realm.StunList.Should().HaveCount(2);
|
||||
realm.StunList.Should().Contain("example.stun:3478");
|
||||
realm.StunList.Should().Contain("example2.stun:3478");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToUri_ShouldGenerateValidUri()
|
||||
{
|
||||
var realm = new HyRealm(
|
||||
IsHttp: false,
|
||||
Token: "public",
|
||||
RendezvousHost: "realm.hy2.io",
|
||||
RendezvousPort: 443,
|
||||
RealmName: "57f9be7c-2810-4f5b-8cb9-260bc84d6c90",
|
||||
StunList: ["example.stun:3478", "example2.stun:3478"]
|
||||
);
|
||||
var uri = realm.ToUri();
|
||||
uri.Should().Contain("realm://public@realm.hy2.io");
|
||||
uri.Should().Contain("/57f9be7c-2810-4f5b-8cb9-260bc84d6c90");
|
||||
uri.Should().Contain("stun=example.stun:3478");
|
||||
uri.Should().Contain("stun=example2.stun:3478");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetShareUriAndResolveConfig_Hy2Realm_ShouldRoundTripBasicFields()
|
||||
{
|
||||
var str = "hysteria2+realm://mytoken@rendezvous.example.com/my-cabin-1f3a8c2e9b?auth=your_password&insecure=1&pinSHA256=deadbeef#remark";
|
||||
var resolved = Hysteria2Fmt.ResolveRealm(str, out var msg);
|
||||
resolved.Should().NotBeNull();
|
||||
resolved.Password.Should().Be("your_password");
|
||||
var result = HyRealm.TryParse(resolved.GetProtocolExtra().Hy2RealmUrl, out var realm);
|
||||
result.Should().BeTrue();
|
||||
realm.Should().NotBeNull();
|
||||
realm.Token.Should().Be("mytoken");
|
||||
|
||||
// To uri
|
||||
var uri = Hysteria2Fmt.ToUri(resolved);
|
||||
uri.Should().Contain("hysteria2+realm://mytoken@rendezvous.example.com");
|
||||
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");
|
||||
}
|
||||
}
|
||||
6
v2rayN/ServiceLib/Base/ICloseable.cs
Normal file
6
v2rayN/ServiceLib/Base/ICloseable.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace ServiceLib.Base;
|
||||
|
||||
public interface ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
}
|
||||
7
v2rayN/ServiceLib/Base/IWindowDialog.cs
Normal file
7
v2rayN/ServiceLib/Base/IWindowDialog.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ServiceLib.Base;
|
||||
|
||||
public interface IWindowDialog
|
||||
{
|
||||
public Task<bool> ShowDialogAsync<TViewModel>(TViewModel vm)
|
||||
where TViewModel : class;
|
||||
}
|
||||
@@ -3,5 +3,4 @@ namespace ServiceLib.Base;
|
||||
public class MyReactiveObject : ReactiveObject
|
||||
{
|
||||
protected static Config? _config;
|
||||
protected Func<EViewAction, object?, Task<bool>>? _updateView;
|
||||
}
|
||||
|
||||
@@ -324,6 +324,29 @@ public class Utils
|
||||
.ReplaceLineBreaks(",");
|
||||
}
|
||||
|
||||
public static string ParseProcess(string text)
|
||||
{
|
||||
if (text.IsNullOrEmpty())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (text.StartsWith('"'))
|
||||
{
|
||||
text = text[1..];
|
||||
}
|
||||
if (text.EndsWith('"'))
|
||||
{
|
||||
text = text[..^1];
|
||||
}
|
||||
return List2String(text.Replace(",", ",")
|
||||
.Replace("\\", "/")
|
||||
.ReplaceLineBreaks(",")
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.TrimEx())
|
||||
.Where(x => x.IsNotEmpty())
|
||||
.ToList());
|
||||
}
|
||||
|
||||
public static List<string> GetEnumNames<TEnum>() where TEnum : Enum
|
||||
{
|
||||
return Enum.GetValues(typeof(TEnum))
|
||||
@@ -462,12 +485,17 @@ public class Utils
|
||||
|
||||
public static string? DomainStrategy4Sbox(string? strategy)
|
||||
{
|
||||
if (strategy is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return strategy switch
|
||||
{
|
||||
not null when strategy.StartsWith("UseIPv4") => "prefer_ipv4",
|
||||
not null when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
|
||||
not null when strategy.StartsWith("ForceIPv4") => "ipv4_only",
|
||||
not null when strategy.StartsWith("ForceIPv6") => "ipv6_only",
|
||||
_ when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
|
||||
_ when strategy.StartsWith("UseIP") => "prefer_ipv4",
|
||||
_ when strategy.StartsWith("ForceIPv6") => "ipv6_only",
|
||||
_ when strategy.StartsWith("ForceIP") => "ipv4_only",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
@@ -576,6 +604,58 @@ public class Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryParseRange(string? input, int min, int max, out int from, out int to)
|
||||
{
|
||||
from = to = 0;
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var parts = input.Split('-');
|
||||
if (parts.Length == 1)
|
||||
{
|
||||
if (!int.TryParse(parts[0], out from))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
to = from;
|
||||
return from >= min && to <= max;
|
||||
}
|
||||
if (parts.Length != 2
|
||||
|| !int.TryParse(parts[0], out from)
|
||||
|| !int.TryParse(parts[1], out to))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return from >= min && to <= max && from <= to;
|
||||
}
|
||||
|
||||
public static bool TryParseMaxSplit(string? input, int min, int max, out int from, out int to)
|
||||
{
|
||||
from = to = 0;
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var parts = input.Split('-');
|
||||
if (parts.Length == 1)
|
||||
{
|
||||
if (!int.TryParse(parts[0], out from))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
to = from;
|
||||
return from >= min && to <= max;
|
||||
}
|
||||
if (parts.Length != 2
|
||||
|| !int.TryParse(parts[0], out from)
|
||||
|| !int.TryParse(parts[1], out to))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return from >= min && to <= max && from <= to;
|
||||
}
|
||||
|
||||
public static bool IsPrivateNetwork(string ip)
|
||||
{
|
||||
if (IPAddress.TryParse(ip, out var address))
|
||||
@@ -728,6 +808,12 @@ public class Utils
|
||||
.Any(ua => ua.Address.Equals(targetAddress));
|
||||
}
|
||||
|
||||
public static bool ContainsInterfaceName(string inInterfaceName)
|
||||
{
|
||||
return NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Any(ni => ni.Name.Equals(inInterfaceName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
#endregion Speed Test
|
||||
|
||||
#region Miscellaneous
|
||||
|
||||
@@ -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,16 +2,9 @@ namespace ServiceLib.Events;
|
||||
|
||||
public static class AppEvents
|
||||
{
|
||||
public static readonly EventChannel<Unit> ReloadRequested = new();
|
||||
public static readonly EventChannel<bool?> ShowHideWindowRequested = new();
|
||||
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
|
||||
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
|
||||
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
|
||||
public static readonly EventChannel<bool> HasUpdateNotified = new();
|
||||
|
||||
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
|
||||
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
|
||||
public static readonly EventChannel<Unit> ProxiesReloadRequested = new();
|
||||
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
||||
|
||||
public static readonly EventChannel<string> SendSnackMsgRequested = new();
|
||||
@@ -20,12 +13,5 @@ public static class AppEvents
|
||||
public static readonly EventChannel<Unit> AppExitRequested = new();
|
||||
public static readonly EventChannel<bool> ShutdownRequested = new();
|
||||
|
||||
public static readonly EventChannel<Unit> AdjustMainLvColWidthRequested = new();
|
||||
|
||||
public static readonly EventChannel<string> SetDefaultServerRequested = new();
|
||||
|
||||
public static readonly EventChannel<Unit> RoutingsMenuRefreshRequested = new();
|
||||
public static readonly EventChannel<Unit> TestServerRequested = new();
|
||||
public static readonly EventChannel<Unit> InboundDisplayRequested = new();
|
||||
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ namespace ServiceLib;
|
||||
|
||||
public class Global
|
||||
{
|
||||
#region const
|
||||
|
||||
public const string AppName = "v2rayN";
|
||||
public const string GithubUrl = "https://github.com";
|
||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||
@@ -42,6 +40,11 @@ public class Global
|
||||
public const string KillAsSudoOSXShellFileName = NamespaceSample + "kill_as_sudo_osx_sh";
|
||||
public const string KillAsSudoLinuxShellFileName = NamespaceSample + "kill_as_sudo_linux_sh";
|
||||
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 DefaultNetwork = "raw";
|
||||
@@ -92,6 +95,7 @@ public class Global
|
||||
public const string LinuxBash = "/bin/bash";
|
||||
public const string StringTrue = "true";
|
||||
public const string StringFalse = "false";
|
||||
public const int SqliteMaxBatchSize = 10000;
|
||||
|
||||
public const string SingboxDirectDNSTag = "direct_dns";
|
||||
public const string SingboxRemoteDNSTag = "remote_dns";
|
||||
@@ -100,9 +104,7 @@ public class Global
|
||||
public const string SingboxFakeDNSTag = "fake_dns";
|
||||
|
||||
public const int Hysteria2DefaultHopInt = 30;
|
||||
|
||||
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";
|
||||
|
||||
public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$";
|
||||
|
||||
public static readonly List<string> PolicyGroupDefaultFilterList =
|
||||
@@ -210,6 +212,10 @@ public class Global
|
||||
|
||||
public const string Hysteria2ProtocolShare = "hy2://";
|
||||
|
||||
public const string Hysteria2RealmProtocolShare = "hysteria2+realm://";
|
||||
|
||||
public const string Hysteria2HttpRealmProtocolShare = "hysteria2+realm+http://";
|
||||
|
||||
public const string NaiveHttpsProtocolShare = "naive+https://";
|
||||
|
||||
public const string NaiveQuicProtocolShare = "naive+quic://";
|
||||
@@ -406,6 +412,16 @@ public class Global
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> FragmentPacketsOptions =
|
||||
[
|
||||
"tlshello",
|
||||
"1-1",
|
||||
"1-2",
|
||||
"1-3",
|
||||
"1-4",
|
||||
"1-5"
|
||||
];
|
||||
|
||||
public static readonly List<string> UserAgent =
|
||||
[
|
||||
"chrome",
|
||||
@@ -537,7 +553,6 @@ public class Global
|
||||
"http",
|
||||
"tls",
|
||||
"quic",
|
||||
"fakedns",
|
||||
];
|
||||
|
||||
public static readonly List<int> TunMtus =
|
||||
@@ -667,6 +682,14 @@ public class Global
|
||||
"mcbe:bedrock.talonmc.net",
|
||||
];
|
||||
|
||||
public static readonly List<string> DefaultRealmStunList =
|
||||
[
|
||||
"turn.cloudflare.com:3478",
|
||||
"stun.nextcloud.com:3478",
|
||||
"stun.sip.us:3478",
|
||||
"global.stun.twilio.com:3478",
|
||||
];
|
||||
|
||||
public static readonly List<string> OutboundTags =
|
||||
[
|
||||
ProxyTag,
|
||||
@@ -709,5 +732,40 @@ public class Global
|
||||
"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",
|
||||
];
|
||||
|
||||
public static readonly IReadOnlyList<string> TunIpv6Address =
|
||||
[
|
||||
"fc00::172:18:0:1/128",
|
||||
"fc00::172:31:0:1/128",
|
||||
"fc00::172:20:0:1/128",
|
||||
"fc00::172:16:0:1/128",
|
||||
"fc00::192:168:100:1/128",
|
||||
"fc00::10:10:14:1/128",
|
||||
"fc00::10:1:0:1/128",
|
||||
"fc00::10:0:0:1/128",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ global using ServiceLib.Handler.Fmt;
|
||||
global using ServiceLib.Handler.SysProxy;
|
||||
global using ServiceLib.Helper;
|
||||
global using ServiceLib.Manager;
|
||||
global using ServiceLib.Models.CoreConfigs;
|
||||
global using ServiceLib.Models.Configs;
|
||||
global using ServiceLib.Models.CoreConfigs;
|
||||
global using ServiceLib.Models.Dto;
|
||||
global using ServiceLib.Models.Entities;
|
||||
global using ServiceLib.Resx;
|
||||
|
||||
@@ -96,17 +96,39 @@ public class CoreConfigContextBuilder
|
||||
}
|
||||
if (context.IsTunEnabled && context.AppConfig.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||
{
|
||||
var appConfig = JsonUtils.DeepCopy(config);
|
||||
var routeExcludeAddressList = new List<string>();
|
||||
foreach (var addr in context.AppConfig.TunModeItem.RouteExcludeAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPNetwork2.Parse(addr);
|
||||
routeExcludeAddressList.Add(addr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
validatorResult.Errors.Add(string.Format(ResUI.MsgTunRouteExcludeInvalidAddress, addr));
|
||||
validatorResult.Warnings.Add(string.Format(ResUI.MsgTunRouteExcludeInvalidAddress, addr));
|
||||
}
|
||||
}
|
||||
appConfig.TunModeItem.RouteExcludeAddress = routeExcludeAddressList;
|
||||
context = context with { AppConfig = appConfig };
|
||||
}
|
||||
if (!context.AppConfig.CoreBasicItem.SendThrough.IsNullOrEmpty()
|
||||
&& !Utils.IsLocalIP(context.AppConfig.CoreBasicItem.SendThrough))
|
||||
{
|
||||
validatorResult.Warnings.Add(string.Format(ResUI.MsgInvalidProperty, ResUI.TbSettingsSendThrough));
|
||||
var appConfig = JsonUtils.DeepCopy(config);
|
||||
appConfig.CoreBasicItem.SendThrough = string.Empty;
|
||||
context = context with { AppConfig = appConfig };
|
||||
}
|
||||
if (!context.AppConfig.CoreBasicItem.BindInterface.IsNullOrEmpty()
|
||||
&& (!Utils.ContainsInterfaceName(context.AppConfig.CoreBasicItem.BindInterface)
|
||||
|| !(context.IsTunEnabled || context.IsWindows)))
|
||||
{
|
||||
validatorResult.Warnings.Add(string.Format(ResUI.MsgInvalidProperty, ResUI.TbSettingsBindInterface));
|
||||
var appConfig = JsonUtils.DeepCopy(config);
|
||||
appConfig.CoreBasicItem.BindInterface = string.Empty;
|
||||
context = context with { AppConfig = appConfig };
|
||||
}
|
||||
|
||||
return new CoreConfigContextBuilderResult(context, validatorResult);
|
||||
@@ -171,12 +193,16 @@ public class CoreConfigContextBuilder
|
||||
if (preSocksItem != null)
|
||||
{
|
||||
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
|
||||
|
||||
var protectCoreTypeList = new HashSet<ECoreType>(nodeContext.ProtectCoreTypeList) { nodeContext.RunCoreType };
|
||||
|
||||
return preSocksResult with
|
||||
{
|
||||
Context = preSocksResult.Context with
|
||||
{
|
||||
ProtectDomainList =
|
||||
[.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
|
||||
ProtectCoreTypeList = protectCoreTypeList,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ public class NodeValidator
|
||||
}
|
||||
|
||||
// Basic Property Validation
|
||||
v.Assert(!item.Address.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Address"));
|
||||
v.Assert(item.Port is > 0 and <= 65535, string.Format(ResUI.MsgInvalidProperty, "Port"));
|
||||
v.Assert(!item.Address.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, ResUI.TbAddress));
|
||||
v.Assert(item.Port is > 0 and <= 65535, string.Format(ResUI.MsgInvalidProperty, ResUI.TbPort));
|
||||
|
||||
// Network & Core Logic
|
||||
var net = item.GetNetwork();
|
||||
@@ -75,25 +75,25 @@ public class NodeValidator
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
v.Assert(!item.Password.IsNullOrEmpty() && Utils.IsGuidByParse(item.Password),
|
||||
string.Format(ResUI.MsgInvalidProperty, "Password"));
|
||||
string.Format(ResUI.MsgInvalidProperty, ResUI.TbId));
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
v.Assert(
|
||||
!item.Password.IsNullOrEmpty()
|
||||
&& (Utils.IsGuidByParse(item.Password) || item.Password.Length <= 30),
|
||||
string.Format(ResUI.MsgInvalidProperty, "Password")
|
||||
string.Format(ResUI.MsgInvalidProperty, ResUI.TbId5)
|
||||
);
|
||||
v.Assert(Global.Flows.Contains(protocolExtra.Flow ?? string.Empty),
|
||||
string.Format(ResUI.MsgInvalidProperty, "Flow"));
|
||||
string.Format(ResUI.MsgInvalidProperty, ResUI.TbFlow5));
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
v.Assert(!item.Password.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Password"));
|
||||
v.Assert(!item.Password.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, ResUI.TbId3));
|
||||
v.Assert(
|
||||
!string.IsNullOrEmpty(protocolExtra.SsMethod) &&
|
||||
Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod),
|
||||
string.Format(ResUI.MsgInvalidProperty, "SsMethod"));
|
||||
string.Format(ResUI.MsgInvalidProperty, ResUI.TbSecurity3));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -115,10 +115,11 @@ public class NodeValidator
|
||||
// TLS & Security
|
||||
if (item.StreamSecurity == Global.StreamSecurity)
|
||||
{
|
||||
if (!item.Cert.IsNullOrEmpty() && CertPemManager.ParsePemChain(item.Cert).Count == 0 &&
|
||||
!item.CertSha.IsNullOrEmpty())
|
||||
var isCertProvided = !item.Cert.IsNullOrEmpty();
|
||||
if (!item.Cert.IsNullOrEmpty() && CertPemManager.ParsePemChain(item.Cert).Count == 0)
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, "TLS Certificate"));
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, ResUI.TbFullCertTips));
|
||||
isCertProvided = false;
|
||||
}
|
||||
|
||||
// Check for deprecated allowInsecure property when TLS is enabled
|
||||
@@ -131,19 +132,19 @@ public class NodeValidator
|
||||
|
||||
if ((coreType == ECoreType.Xray
|
||||
&& item.GetAllowInsecure()
|
||||
&& item.Cert.IsNullOrEmpty()
|
||||
&& !isCertProvided
|
||||
&& item.CertSha.IsNullOrEmpty())
|
||||
|| (coreType == ECoreType.sing_box
|
||||
&& item.GetAllowInsecure()
|
||||
&& item.Cert.IsNullOrEmpty()))
|
||||
&& !isCertProvided))
|
||||
{
|
||||
v.Warning("Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks.");
|
||||
v.Warning(ResUI.MsgInsecureConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.StreamSecurity == Global.StreamSecurityReality)
|
||||
{
|
||||
v.Assert(!item.PublicKey.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "PublicKey"));
|
||||
v.Assert(!item.PublicKey.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, ResUI.TbPublicKey));
|
||||
}
|
||||
|
||||
var transport = item.GetTransportExtra();
|
||||
@@ -151,7 +152,7 @@ public class NodeValidator
|
||||
{
|
||||
if (JsonUtils.ParseJson(transport.XhttpExtra) is not JsonObject)
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, "XHTTP Extra"));
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, ResUI.TransportExtra));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ public class NodeValidator
|
||||
{
|
||||
if (JsonUtils.ParseJson(item.Finalmask) is not JsonObject)
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, "Finalmask"));
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, ResUI.TbFinalmask));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,10 @@ public static class ConfigHandler
|
||||
EnableLegacyProtect = false,
|
||||
};
|
||||
config.GuiItem ??= new();
|
||||
if (!Global.RootCertProviders.Contains(config.GuiItem.RootCertProvider))
|
||||
{
|
||||
config.GuiItem.RootCertProvider = Global.RootCertProviders.First();
|
||||
}
|
||||
config.MsgUIItem ??= new();
|
||||
|
||||
config.UiItem ??= new();
|
||||
@@ -111,10 +115,12 @@ public static class ConfigHandler
|
||||
config.ConstItem ??= new ConstItem();
|
||||
|
||||
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
||||
config.SimpleDNSItem.FakeIPRange ??= Global.FakeIPRanges.FirstOrDefault();
|
||||
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
||||
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
||||
config.SimpleDNSItem.ServeStale ??= false;
|
||||
config.SimpleDNSItem.ParallelQuery ??= false;
|
||||
config.SimpleDNSItem.EnableHappyEyeballs ??= false;
|
||||
|
||||
config.SpeedTestItem ??= new();
|
||||
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
||||
@@ -161,16 +167,27 @@ public static class ConfigHandler
|
||||
config.SystemProxyItem ??= new();
|
||||
config.WebDavItem ??= new();
|
||||
config.CheckUpdateItem ??= new();
|
||||
config.NetBridgeItem ??= new()
|
||||
{
|
||||
RuleProcess = string.Empty
|
||||
};
|
||||
config.Fragment4RayItem ??= new()
|
||||
{
|
||||
Packets = "tlshello",
|
||||
Length = "50-100",
|
||||
Interval = "10-20"
|
||||
};
|
||||
config.Fragment4RayItem.MaxSplit ??= "0";
|
||||
|
||||
config.HappyEyeballs4RayItem ??= new()
|
||||
{
|
||||
TryDelayMs = 250,
|
||||
PrioritizeIPv6 = false,
|
||||
Interleave = 1,
|
||||
MaxConcurrentTry = 4,
|
||||
};
|
||||
if ((config.Fragment4RayItem.Lengths ?? []).Count == 0)
|
||||
{
|
||||
config.Fragment4RayItem.Lengths = [config.Fragment4RayItem.Length ?? "50-100"];
|
||||
}
|
||||
if ((config.Fragment4RayItem.Delays ?? []).Count == 0)
|
||||
{
|
||||
config.Fragment4RayItem.Delays = [config.Fragment4RayItem.Interval ?? "10-20"];
|
||||
}
|
||||
config.GlobalHotkeys ??= [];
|
||||
|
||||
if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty())
|
||||
@@ -724,12 +741,51 @@ public static class ConfigHandler
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||
var protocolExtra = profileItem.GetProtocolExtra();
|
||||
profileItem.SetProtocolExtra(protocolExtra with
|
||||
{
|
||||
SalamanderPass = profileItem.GetProtocolExtra().SalamanderPass?.TrimEx(),
|
||||
HopInterval = profileItem.GetProtocolExtra().HopInterval?.TrimEx(),
|
||||
SalamanderPass = protocolExtra.SalamanderPass?.TrimEx(),
|
||||
HopInterval = protocolExtra.HopInterval?.TrimEx(),
|
||||
});
|
||||
|
||||
if (!protocolExtra.Hy2RealmUrl.IsNullOrEmpty())
|
||||
{
|
||||
var realmResult = HyRealm.TryParse(protocolExtra.Hy2RealmUrl, out var realm);
|
||||
if (!realmResult || realm is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (realm.StunList.Count == 0)
|
||||
{
|
||||
realm = realm with
|
||||
{
|
||||
StunList = Global.DefaultRealmStunList,
|
||||
};
|
||||
}
|
||||
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||
{
|
||||
Hy2RealmUrl = realm.ToUri(),
|
||||
});
|
||||
}
|
||||
var isGecko = !protocolExtra.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtra.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||
if (isGecko)
|
||||
{
|
||||
var minPacketSize = protocolExtra.GeckoMinPacketSize.ToInt();
|
||||
var maxPacketSize = protocolExtra.GeckoMaxPacketSize.ToInt();
|
||||
if (minPacketSize <= 0
|
||||
|| minPacketSize > maxPacketSize
|
||||
|| maxPacketSize > 2048)
|
||||
{
|
||||
minPacketSize = 512;
|
||||
maxPacketSize = 1200;
|
||||
}
|
||||
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||
{
|
||||
GeckoMinPacketSize = minPacketSize.ToString(),
|
||||
GeckoMaxPacketSize = maxPacketSize.ToString(),
|
||||
});
|
||||
}
|
||||
|
||||
await AddServerCommon(config, profileItem, toFile);
|
||||
|
||||
return 0;
|
||||
@@ -1454,8 +1510,7 @@ public static class ConfigHandler
|
||||
public static ProfileItem? GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
|
||||
{
|
||||
ProfileItem? itemSocks = null;
|
||||
var enableLegacyProtect = config.TunModeItem.EnableLegacyProtect
|
||||
|| Utils.IsNonWindows();
|
||||
var enableLegacyProtect = config.TunModeItem.EnableLegacyProtect;
|
||||
if (node.ConfigType != EConfigType.Custom
|
||||
&& coreType != ECoreType.sing_box
|
||||
&& config.TunModeItem.EnableTun
|
||||
@@ -1472,7 +1527,8 @@ public static class ConfigHandler
|
||||
else if (node.ConfigType == EConfigType.Custom
|
||||
&& node.PreSocksPort is > 0 and <= 65535)
|
||||
{
|
||||
var preCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
||||
var customPreCoreType = AppManager.Instance.GetCoreType(null, EConfigType.Custom);
|
||||
var preCoreType = (enableLegacyProtect && config.TunModeItem.EnableTun) ? ECoreType.sing_box : customPreCoreType;
|
||||
itemSocks = new ProfileItem()
|
||||
{
|
||||
CoreType = preCoreType,
|
||||
|
||||
@@ -38,7 +38,7 @@ public static class ConnectionHandler
|
||||
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
responseTime = await GetRealPingTime(webProxy, 10);
|
||||
responseTime = await GetRealPingTime(webProxy);
|
||||
if (responseTime > 0)
|
||||
{
|
||||
break;
|
||||
@@ -66,7 +66,7 @@ public static class ConnectionHandler
|
||||
/// <summary>
|
||||
/// Measures response time by sending HTTP requests through proxy.
|
||||
/// </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 responseTime = -1;
|
||||
@@ -77,7 +77,8 @@ public static class ConnectionHandler
|
||||
using var client = new HttpClient(new SocketsHttpHandler()
|
||||
{
|
||||
Proxy = webProxy,
|
||||
UseProxy = webProxy != null
|
||||
UseProxy = webProxy != null,
|
||||
ConnectTimeout = TimeSpan.FromSeconds(3)
|
||||
});
|
||||
|
||||
List<int> oneTime = [];
|
||||
|
||||
@@ -72,6 +72,11 @@ public class FmtHandler
|
||||
{
|
||||
return Hysteria2Fmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.Hysteria2RealmProtocolShare)
|
||||
|| str.StartsWith(Global.Hysteria2HttpRealmProtocolShare))
|
||||
{
|
||||
return Hysteria2Fmt.ResolveRealm(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.TUIC]))
|
||||
{
|
||||
return TuicFmt.Resolve(str, out msg);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class Hysteria2Fmt : BaseFmt
|
||||
@@ -23,15 +25,7 @@ public class Hysteria2Fmt : BaseFmt
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveUriQuery(query, ref item);
|
||||
if (item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||
}
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
Ports = GetQueryDecoded(query, "mport"),
|
||||
SalamanderPass = GetQueryDecoded(query, "obfs-password"),
|
||||
});
|
||||
ResolveHy2UriQuery(query, ref item);
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -42,8 +36,11 @@ public class Hysteria2Fmt : BaseFmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var url = string.Empty;
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
if (!protocolExtraItem.Hy2RealmUrl.TrimEx().IsNullOrEmpty())
|
||||
{
|
||||
return ToUriForRealm(item);
|
||||
}
|
||||
|
||||
var remark = string.Empty;
|
||||
if (item.Remarks.IsNotEmpty())
|
||||
@@ -52,27 +49,7 @@ public class Hysteria2Fmt : BaseFmt
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
ToUriQueryLite(item, ref dicQuery);
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
|
||||
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("obfs", "salamander");
|
||||
dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass));
|
||||
}
|
||||
if (!protocolExtraItem.Ports.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("mport", Utils.UrlEncode(protocolExtraItem.Ports.Replace(':', '-')));
|
||||
}
|
||||
if (!item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
var sha = item.CertSha;
|
||||
var idx = sha.IndexOf(',');
|
||||
if (idx > 0)
|
||||
{
|
||||
sha = sha[..idx];
|
||||
}
|
||||
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
|
||||
}
|
||||
ToHy2UriQuery(item, ref dicQuery);
|
||||
|
||||
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Password, dicQuery, remark);
|
||||
}
|
||||
@@ -94,4 +71,155 @@ public class Hysteria2Fmt : BaseFmt
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveRealm(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem item = new()
|
||||
{
|
||||
ConfigType = EConfigType.Hysteria2
|
||||
};
|
||||
var realmStr = str["hysteria2+".Length..];
|
||||
var result = HyRealm.TryParse(realmStr, out var realm);
|
||||
if (!result || realm == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
if (url == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.Address = realm.RendezvousHost;
|
||||
item.Port = realm.RendezvousPort;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveUriQuery(query, ref item);
|
||||
var authPassword = GetQueryDecoded(query, "auth");
|
||||
if (authPassword.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.Password = authPassword;
|
||||
ResolveHy2UriQuery(query, ref item);
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
Hy2RealmUrl = realm.ToUri(),
|
||||
});
|
||||
|
||||
msg = string.Empty;
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUriForRealm(ProfileItem? item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
var result = HyRealm.TryParse(protocolExtraItem.Hy2RealmUrl, out var realm);
|
||||
if (!result || realm == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var remark = string.Empty;
|
||||
if (item.Remarks.IsNotEmpty())
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
ToUriQueryLite(item, ref dicQuery);
|
||||
ToHy2UriQuery(item, ref dicQuery);
|
||||
|
||||
dicQuery.Add("auth", Utils.UrlEncode(item.Password));
|
||||
|
||||
var queryBuilder = new StringBuilder();
|
||||
queryBuilder.Append('?');
|
||||
foreach (var kv in dicQuery)
|
||||
{
|
||||
queryBuilder.Append(kv.Key);
|
||||
queryBuilder.Append('=');
|
||||
queryBuilder.Append(kv.Value);
|
||||
queryBuilder.Append('&');
|
||||
}
|
||||
foreach (var stun in realm.StunList)
|
||||
{
|
||||
queryBuilder.Append("stun=");
|
||||
queryBuilder.Append(Utils.UrlEncode(stun));
|
||||
queryBuilder.Append('&');
|
||||
}
|
||||
var query = queryBuilder.ToString().TrimEnd('&');
|
||||
|
||||
var url = $"{Utils.UrlEncode(realm.Token)}@{GetIpv6(realm.RendezvousHost)}:{realm.RendezvousPort}";
|
||||
var scheme = realm.IsHttp ? Global.Hysteria2HttpRealmProtocolShare : Global.Hysteria2RealmProtocolShare;
|
||||
return $"{scheme}{url}/{realm.RealmName}{query}{remark}";
|
||||
}
|
||||
|
||||
private static void ResolveHy2UriQuery(NameValueCollection query, ref ProfileItem item)
|
||||
{
|
||||
if (item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||
}
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
Ports = GetQueryDecoded(query, "mport"),
|
||||
SalamanderPass = GetQueryDecoded(query, "obfs-password"),
|
||||
// NOTE: The "PacketSize" parameter is not defined by the official URI Scheme, may remove or rename it in the future.
|
||||
GeckoMinPacketSize = GetQueryDecoded(query, "minPacketSize"),
|
||||
GeckoMaxPacketSize = GetQueryDecoded(query, "maxPacketSize"),
|
||||
});
|
||||
if (GetQueryDecoded(query, "obfs") == "gecko")
|
||||
{
|
||||
// Ensure the "PacketSize" parameters are present for gecko obfs.
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
if (protocolExtraItem.GeckoMinPacketSize.IsNullOrEmpty())
|
||||
{
|
||||
protocolExtraItem = protocolExtraItem with
|
||||
{
|
||||
GeckoMinPacketSize = "512",
|
||||
};
|
||||
}
|
||||
if (protocolExtraItem.GeckoMaxPacketSize.IsNullOrEmpty())
|
||||
{
|
||||
protocolExtraItem = protocolExtraItem with
|
||||
{
|
||||
GeckoMaxPacketSize = "1200",
|
||||
};
|
||||
}
|
||||
item.SetProtocolExtra(protocolExtraItem);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToHy2UriQuery(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
||||
{
|
||||
if (!item.CertSha.IsNullOrEmpty()
|
||||
&& !item.CertSha.Contains(','))
|
||||
{
|
||||
var sha = item.CertSha;
|
||||
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
|
||||
}
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
var isGecko = !protocolExtraItem.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtraItem.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("obfs", isGecko ? "gecko" : "salamander");
|
||||
dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass));
|
||||
if (isGecko)
|
||||
{
|
||||
// NOTE: The "PacketSize" parameter is not defined by the official URI Scheme, may remove or rename it in the future.
|
||||
dicQuery.Add("minPacketSize", protocolExtraItem.GeckoMinPacketSize);
|
||||
dicQuery.Add("maxPacketSize", protocolExtraItem.GeckoMaxPacketSize);
|
||||
}
|
||||
}
|
||||
if (!protocolExtraItem.Ports.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("mport", Utils.UrlEncode(protocolExtraItem.Ports.Replace(':', '-')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ public static class SysProxyHandler
|
||||
try
|
||||
{
|
||||
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
if (port <= 0)
|
||||
{
|
||||
return false;
|
||||
@@ -24,17 +23,18 @@ public static class SysProxyHandler
|
||||
switch (type)
|
||||
{
|
||||
case ESysProxyType.ForcedChange when Utils.IsWindows():
|
||||
{
|
||||
GetWindowsProxyString(config, port, out var strProxy, out var strExceptions);
|
||||
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
|
||||
break;
|
||||
}
|
||||
var (strProxy, strExceptions) = GetWindowsProxyString(config, port);
|
||||
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedChange when Utils.IsLinux():
|
||||
var exceptions = SanitizeExceptions(config);
|
||||
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedChange when Utils.IsMacOS():
|
||||
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions);
|
||||
var exceptions2 = SanitizeExceptions(config);
|
||||
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions2);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedClear when Utils.IsWindows():
|
||||
@@ -66,15 +66,31 @@ public static class SysProxyHandler
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void GetWindowsProxyString(Config config, int port, out string strProxy, out string strExceptions)
|
||||
private static string SanitizeExceptions(Config config)
|
||||
{
|
||||
strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions;
|
||||
if (exceptions.IsNullOrEmpty())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var items = exceptions
|
||||
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(item => item.Replace(" ", string.Empty))
|
||||
.Where(item => item.Length > 0);
|
||||
|
||||
return string.Join(',', items);
|
||||
}
|
||||
|
||||
private static (string strProxy, string strExceptions) GetWindowsProxyString(Config config, int port)
|
||||
{
|
||||
var strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
if (config.SystemProxyItem.NotProxyLocalAddress)
|
||||
{
|
||||
strExceptions = $"<local>;{strExceptions}";
|
||||
}
|
||||
|
||||
strProxy = string.Empty;
|
||||
var strProxy = string.Empty;
|
||||
if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty())
|
||||
{
|
||||
strProxy = $"{Global.Loopback}:{port}";
|
||||
@@ -86,6 +102,8 @@ public static class SysProxyHandler
|
||||
.Replace("{http_port}", port.ToString())
|
||||
.Replace("{socks_port}", port.ToString());
|
||||
}
|
||||
|
||||
return (strProxy, strExceptions);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Security.Authentication;
|
||||
using Downloader;
|
||||
|
||||
namespace ServiceLib.Helper;
|
||||
@@ -14,6 +15,8 @@ public class DownloaderHelper
|
||||
return null;
|
||||
}
|
||||
|
||||
var connectTimeout = Math.Clamp(timeout / 5, 2, 5);
|
||||
|
||||
Uri uri = new(url);
|
||||
//Authorization Header
|
||||
var headers = new WebHeaderCollection();
|
||||
@@ -22,17 +25,19 @@ public class DownloaderHelper
|
||||
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()
|
||||
{
|
||||
BlockTimeout = timeout * 1000,
|
||||
MaxTryAgainOnFailure = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Headers = headers,
|
||||
UserAgent = userAgent,
|
||||
ConnectTimeout = timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
RequestConfiguration = requestConfiguration,
|
||||
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
|
||||
};
|
||||
|
||||
await using var downloader = new Downloader.DownloadService(downloadOpt);
|
||||
@@ -45,7 +50,9 @@ public class DownloaderHelper
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
downloadOpt = null;
|
||||
@@ -60,15 +67,18 @@ public class DownloaderHelper
|
||||
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()
|
||||
{
|
||||
BlockTimeout = timeout * 1000,
|
||||
MaxTryAgainOnFailure = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
ConnectTimeout= timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
RequestConfiguration = requestConfiguration,
|
||||
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
|
||||
};
|
||||
|
||||
var lastUpdateTime = DateTime.Now;
|
||||
@@ -116,7 +126,7 @@ public class DownloaderHelper
|
||||
};
|
||||
//progress.Report("......");
|
||||
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);
|
||||
|
||||
downloadOpt = null;
|
||||
@@ -137,15 +147,18 @@ public class DownloaderHelper
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
var connectTimeout = Math.Clamp(timeout / 5, 2, 5);
|
||||
var requestConfiguration = new RequestConfiguration()
|
||||
{
|
||||
ConnectTimeout = connectTimeout * 1000,
|
||||
Proxy = webProxy
|
||||
};
|
||||
var downloadOpt = new DownloadConfiguration()
|
||||
{
|
||||
BlockTimeout = timeout * 1000,
|
||||
MaxTryAgainOnFailure = 2,
|
||||
RequestConfiguration =
|
||||
{
|
||||
ConnectTimeout= timeout * 1000,
|
||||
Proxy = webProxy
|
||||
}
|
||||
RequestConfiguration = requestConfiguration,
|
||||
CustomHttpMessageHandlerFactory = () => GetSocketsHttpHandler(requestConfiguration),
|
||||
};
|
||||
|
||||
var progressPercentage = 0;
|
||||
@@ -182,4 +195,75 @@ public class DownloaderHelper
|
||||
|
||||
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;
|
||||
public static AppManager Instance => _instance.Value;
|
||||
public Config Config => _config;
|
||||
public IWindowDialog WindowDialog { get; set; } = null!;
|
||||
|
||||
public int StatePort
|
||||
{
|
||||
@@ -244,9 +245,26 @@ public sealed class AppManager
|
||||
{
|
||||
return [];
|
||||
}
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => ids.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
|
||||
if (ids.Count <= Global.SqliteMaxBatchSize)
|
||||
{
|
||||
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)
|
||||
@@ -257,18 +275,11 @@ public sealed class AppManager
|
||||
|
||||
public async Task<List<ProfileItem>> GetProfileItemsOrderedByIndexIds(IEnumerable<string> indexIds)
|
||||
{
|
||||
var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
||||
if (idList.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var items = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => idList.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
||||
var items = await GetProfileItemsByIndexIds(ids);
|
||||
var itemMap = items.ToDictionary(it => it.IndexId);
|
||||
|
||||
return idList.Select(itemMap.GetValueOrDefault)
|
||||
return ids.Select(itemMap.GetValueOrDefault)
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
@@ -652,7 +663,7 @@ public sealed class AppManager
|
||||
return Global.SsSecuritiesInSingbox;
|
||||
}
|
||||
|
||||
public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType)
|
||||
public ECoreType GetCoreType(ProfileItem? profileItem, EConfigType eConfigType)
|
||||
{
|
||||
if (profileItem?.CoreType != null)
|
||||
{
|
||||
|
||||
@@ -10,180 +10,17 @@ public class CertPemManager
|
||||
{
|
||||
private static readonly string _tag = "CertPemManager";
|
||||
private static readonly Lazy<CertPemManager> _instance = new(() => new());
|
||||
private Config _config;
|
||||
|
||||
/// <summary>
|
||||
/// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks
|
||||
/// </summary>
|
||||
private static readonly HashSet<string> TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase)
|
||||
public async Task Init(Config config)
|
||||
{
|
||||
"EBD41040E4BB3EC742C9E381D31EF2A41A48B6685C96E7CEF3C1DF6CD4331C99", // GlobalSign Root CA
|
||||
"6DC47172E01CBCB0BF62580D895FE2B8AC9AD4F873801E0C10B9C837D21EB177", // Entrust.net Premium 2048 Secure Server CA
|
||||
"73C176434F1BC6D5ADF45B0E76E727287C8DE57616C1E6E6141A2B2CBC7D8E4C", // Entrust Root Certification Authority
|
||||
"D8E0FEBC1DB2E38D00940F37D27D41344D993E734B99D5656D9778D4D8143624", // Certum Root CA
|
||||
"D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root
|
||||
"85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2
|
||||
"18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3
|
||||
"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
|
||||
};
|
||||
if (_config != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_config = config;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static CertPemManager Instance => _instance.Value;
|
||||
|
||||
@@ -292,7 +129,7 @@ public class CertPemManager
|
||||
/// <summary>
|
||||
/// Validate server certificate with CA pinning
|
||||
/// </summary>
|
||||
private static bool ValidateServerCertificate(
|
||||
private bool ValidateServerCertificate(
|
||||
object _,
|
||||
X509Certificate? certificate,
|
||||
X509Chain? chain,
|
||||
@@ -327,9 +164,10 @@ public class CertPemManager
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -442,4 +280,50 @@ public class CertPemManager
|
||||
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();
|
||||
sb.AppendLine("#!/bin/bash");
|
||||
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
|
||||
sb.AppendLine($"exec sudo -S -- {cmdLine}");
|
||||
|
||||
// Passing environment variables to the sudo command, here it only xray or sing-box.
|
||||
if (coreInfo.Environment.Count > 0)
|
||||
{
|
||||
var envArgs = string.Join(" ", coreInfo.Environment.Where(kv => kv.Value.IsNotEmpty()).Select(kv => $"{kv.Key}={kv.Value.AppendQuotes()}"));
|
||||
sb.AppendLine($"exec sudo -S -- env {envArgs} {cmdLine}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"exec sudo -S -- {cmdLine}");
|
||||
}
|
||||
|
||||
var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
|
||||
|
||||
var procService = new ProcessService(
|
||||
|
||||
@@ -8,8 +8,10 @@ public class CoreManager
|
||||
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
||||
public static CoreManager Instance => _instance.Value;
|
||||
private Config _config;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private WindowsJobService? _processJob;
|
||||
|
||||
private ProcessService? _processService;
|
||||
private ProcessService? _processPreService;
|
||||
private bool _linuxSudo = false;
|
||||
@@ -90,6 +92,7 @@ public class CoreManager
|
||||
}
|
||||
|
||||
await CoreStart(mainContext);
|
||||
await WaitForProxyPort(preContext);
|
||||
await CoreStartPreService(preContext);
|
||||
|
||||
AppManager.Instance.RunningCoreType = preContext?.RunCoreType ?? mainContext.RunCoreType;
|
||||
@@ -213,6 +216,75 @@ public class CoreManager
|
||||
await _updateFunc?.Invoke(notify, msg);
|
||||
}
|
||||
|
||||
private static async Task WaitForProxyPort(CoreConfigContext? preContext, int timeoutMs = 5000)
|
||||
{
|
||||
if (preContext is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!preContext.AppConfig.TunModeItem.EnableTun)
|
||||
{
|
||||
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
|
||||
|
||||
#region Process
|
||||
@@ -230,7 +302,7 @@ public class CoreManager
|
||||
{
|
||||
if (mayNeedSudo
|
||||
&& _config.TunModeItem.EnableTun
|
||||
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo)
|
||||
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo or ECoreType.Xray)
|
||||
&& Utils.IsNonWindows())
|
||||
{
|
||||
_linuxSudo = true;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -29,12 +29,12 @@ public class Config
|
||||
public SystemProxyItem SystemProxyItem { get; set; }
|
||||
public WebDavItem WebDavItem { get; set; }
|
||||
public CheckUpdateItem CheckUpdateItem { get; set; }
|
||||
public NetBridgeItem NetBridgeItem { get; set; }
|
||||
public Fragment4RayItem? Fragment4RayItem { get; set; }
|
||||
public List<InItem> Inbound { get; set; }
|
||||
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
||||
public List<CoreTypeItem> CoreTypeItem { get; set; }
|
||||
public SimpleDNSItem SimpleDNSItem { get; set; }
|
||||
public HappyEyeballs4RayItem HappyEyeballs4RayItem { get; set; }
|
||||
|
||||
#endregion other entities
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ public class GUIItem
|
||||
public int TrayMenuServersLimit { get; set; } = 20;
|
||||
public bool EnableHWA { get; set; } = false;
|
||||
public bool EnableLog { get; set; } = true;
|
||||
public string? RootCertProvider { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -102,6 +103,7 @@ public class UIItem
|
||||
public bool MacOSShowInDock { get; set; }
|
||||
public List<ColumnItem> MainColumnItem { get; set; }
|
||||
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
||||
public bool HideColumnIpInfo { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -147,6 +149,8 @@ public class TunModeItem
|
||||
public string IcmpRouting { get; set; }
|
||||
public bool EnableLegacyProtect { get; set; }
|
||||
public List<string>? RouteExcludeAddress { get; set; }
|
||||
public string Ipv4Address { get; set; }
|
||||
public string Ipv6Address { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -247,8 +251,15 @@ public class CheckUpdateItem
|
||||
public class Fragment4RayItem
|
||||
{
|
||||
public string? Packets { get; set; }
|
||||
public List<string>? Lengths { get; set; }
|
||||
public List<string>? Delays { get; set; }
|
||||
public string? MaxSplit { get; set; }
|
||||
|
||||
// For migration from old version, remove those properties in the future
|
||||
public string? Length { get; set; }
|
||||
|
||||
public string? Interval { get; set; }
|
||||
// migration end
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -266,21 +277,26 @@ public class SimpleDNSItem
|
||||
public bool? AddCommonHosts { get; set; }
|
||||
public bool? FakeIP { get; set; }
|
||||
public bool? GlobalFakeIp { get; set; }
|
||||
public string? FakeIPRange { get; set; }
|
||||
public bool? BlockBindingQuery { get; set; }
|
||||
public string? DirectDNS { get; set; }
|
||||
public string? RemoteDNS { get; set; }
|
||||
public string? BootstrapDNS { get; set; }
|
||||
public string? Strategy4Freedom { get; set; }
|
||||
public string? Strategy4Proxy { get; set; }
|
||||
public string? Strategy4ProxyDial { get; set; }
|
||||
public bool? ServeStale { get; set; }
|
||||
public bool? ParallelQuery { get; set; }
|
||||
public string? Hosts { get; set; }
|
||||
public string? DirectExpectedIPs { get; set; }
|
||||
public bool? EnableHappyEyeballs { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class NetBridgeItem
|
||||
public class HappyEyeballs4RayItem
|
||||
{
|
||||
public string? RuleProcess { get; set; }
|
||||
public bool EnableDnsViaProxy { get; set; }
|
||||
public int? TryDelayMs { get; set; }
|
||||
public bool? PrioritizeIPv6 { get; set; }
|
||||
public int? Interleave { get; set; }
|
||||
public int? MaxConcurrentTry { get; set; }
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ public record CoreConfigContext
|
||||
// TUN Compatibility
|
||||
public bool IsTunEnabled { get; init; } = false;
|
||||
public HashSet<string> ProtectDomainList { get; init; } = [];
|
||||
// Typically, it is the core of the outbound chain
|
||||
public HashSet<ECoreType> ProtectCoreTypeList { get; init; } = [];
|
||||
|
||||
public bool IsWindows { get; init; }
|
||||
public bool IsMacOS { get; init; }
|
||||
|
||||
@@ -248,6 +248,16 @@ public class HyObfs4Sbox
|
||||
{
|
||||
public string? type { get; set; }
|
||||
public string? password { get; set; }
|
||||
public int? min_packet_size { get; set; }
|
||||
public int? max_packet_size { get; set; }
|
||||
}
|
||||
|
||||
public class HyRealm4Sbox
|
||||
{
|
||||
public string? server_url { get; set; }
|
||||
public string? token { get; set; }
|
||||
public string? realm_id { get; set; }
|
||||
public List<string>? stun_servers { get; set; }
|
||||
}
|
||||
|
||||
public class Server4Sbox : BaseServer4Sbox
|
||||
@@ -334,6 +344,7 @@ public abstract class DialFields4Sbox
|
||||
public Multiplex4Sbox? multiplex { get; set; }
|
||||
public Transport4Sbox? transport { get; set; }
|
||||
public HyObfs4Sbox? obfs { get; set; }
|
||||
public HyRealm4Sbox? realm { get; set; }
|
||||
}
|
||||
|
||||
public abstract class BaseServer4Sbox : DialFields4Sbox
|
||||
|
||||
@@ -4,6 +4,7 @@ public class V2rayConfig
|
||||
{
|
||||
public Log4Ray log { get; set; }
|
||||
public object dns { get; set; }
|
||||
public FakeDns4Ray? fakedns { get; set; }
|
||||
public List<Inbounds4Ray> inbounds { get; set; }
|
||||
public List<Outbounds4Ray> outbounds { get; set; }
|
||||
public Routing4Ray routing { get; set; }
|
||||
@@ -43,6 +44,12 @@ public class Log4Ray
|
||||
public string? loglevel { get; set; }
|
||||
}
|
||||
|
||||
public class FakeDns4Ray
|
||||
{
|
||||
public string? ipPool { get; set; }
|
||||
public long? poolSize { get; set; }
|
||||
}
|
||||
|
||||
public class Inbounds4Ray
|
||||
{
|
||||
public string tag { get; set; }
|
||||
@@ -136,8 +143,6 @@ public class Outboundsettings4Ray
|
||||
|
||||
public Response4Ray? response { get; set; }
|
||||
|
||||
public string? domainStrategy { get; set; }
|
||||
|
||||
public int? userLevel { get; set; }
|
||||
|
||||
public string? secretKey { get; set; }
|
||||
@@ -146,6 +151,16 @@ public class Outboundsettings4Ray
|
||||
|
||||
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 bool? noKernelTun { get; set; }
|
||||
@@ -276,7 +291,6 @@ public class BalancersItem4Ray
|
||||
public List<string>? selector { get; set; }
|
||||
public BalancersStrategy4Ray? strategy { get; set; }
|
||||
public string? tag { get; set; }
|
||||
public string? fallbackTag { get; set; }
|
||||
}
|
||||
|
||||
public class BalancersStrategy4Ray
|
||||
@@ -289,16 +303,7 @@ public class BalancersStrategySettings4Ray
|
||||
{
|
||||
public int? expected { get; set; }
|
||||
public string? maxRTT { get; set; }
|
||||
public float? 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 double? tolerance { get; set; }
|
||||
}
|
||||
|
||||
public class Observatory4Ray
|
||||
@@ -507,11 +512,18 @@ public class MaskSettings4Ray
|
||||
|
||||
public string? password { get; set; }
|
||||
|
||||
public string? url { get; set; }
|
||||
public List<string>? stunServers { get; set; }
|
||||
public string? packetSize { get; set; }
|
||||
|
||||
// fragment
|
||||
public string? packets { get; set; }
|
||||
|
||||
public string? length { get; set; }
|
||||
public string? delay { get; set; }
|
||||
public List<string>? lengths { get; set; }
|
||||
public List<string>? delays { get; set; }
|
||||
public int? maxSplit { get; set; }
|
||||
|
||||
// noise
|
||||
public int? reset { get; set; }
|
||||
@@ -542,15 +554,20 @@ public class AccountsItem4Ray
|
||||
|
||||
public class Sockopt4Ray
|
||||
{
|
||||
public string? domainStrategy { get; set; }
|
||||
|
||||
public string? dialerProxy { get; set; }
|
||||
|
||||
[JsonPropertyName("interface")]
|
||||
public string? Interface { get; set; }
|
||||
|
||||
public HappyEyeballs4Ray? happyEyeballs { get; set; }
|
||||
}
|
||||
|
||||
public class FragmentItem4Ray
|
||||
public class HappyEyeballs4Ray
|
||||
{
|
||||
public string? packets { get; set; }
|
||||
public string? length { get; set; }
|
||||
public string? interval { get; set; }
|
||||
public int? tryDelayMs { get; set; }
|
||||
public bool? prioritizeIPv6 { get; set; }
|
||||
public int? interleave { get; set; }
|
||||
public int? maxConcurrentTry { get; set; }
|
||||
}
|
||||
|
||||
115
v2rayN/ServiceLib/Models/Dto/HyRealm.cs
Normal file
115
v2rayN/ServiceLib/Models/Dto/HyRealm.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
// realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]&stun=<stun-host>[:port]...
|
||||
// realm+http://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]&stun=<stun-host>[:port]...
|
||||
// example:
|
||||
// realm://public@realm.hy2.io/57f9be7c-2810-4f5b-8cb9-260bc84d6c90?stun=example.stun:3478&stun=example2.stun:3478
|
||||
public record HyRealm(
|
||||
bool IsHttp,
|
||||
string Token,
|
||||
string RendezvousHost,
|
||||
int RendezvousPort,
|
||||
string RealmName,
|
||||
List<string> StunList
|
||||
)
|
||||
{
|
||||
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)
|
||||
{
|
||||
realm = null;
|
||||
if (str == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var isHttp = str.StartsWith("realm+http://");
|
||||
var prefix = isHttp ? "realm+http://" : "realm://";
|
||||
if (!str.StartsWith(prefix))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var uri = new Uri(str);
|
||||
var token = Utils.UrlDecode(uri.UserInfo);
|
||||
var rendezvousHost = uri.Host;
|
||||
var rendezvousPort = uri.IsDefaultPort ? (isHttp ? 80 : 443) : uri.Port;
|
||||
var realmName = uri.AbsolutePath.TrimStart('/');
|
||||
var stunList = new List<string>();
|
||||
var query = uri.Query;
|
||||
if (!query.IsNullOrEmpty())
|
||||
{
|
||||
if (query.StartsWith('?'))
|
||||
{
|
||||
query = query.Substring(1);
|
||||
}
|
||||
var pairs = query.Split('&', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
var idx = pair.IndexOf('=');
|
||||
if (idx <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var key = pair.Substring(0, idx);
|
||||
if (!key.Equals("stun", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var val = pair.Substring(idx + 1);
|
||||
stunList.Add(Utils.UrlDecode(val));
|
||||
}
|
||||
}
|
||||
realm = new HyRealm(isHttp, token, rendezvousHost, rendezvousPort, realmName, stunList);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string ToUri()
|
||||
{
|
||||
var prefix = IsHttp ? "realm+http://" : "realm://";
|
||||
var uriBuilder = new UriBuilder
|
||||
{
|
||||
Scheme = IsHttp ? "http" : "realm",
|
||||
Host = RendezvousHost,
|
||||
Port = RendezvousPort,
|
||||
Path = "/" + RealmName,
|
||||
UserName = Utils.UrlEncode(Token),
|
||||
};
|
||||
if (StunList is { Count: > 0 })
|
||||
{
|
||||
// var query = string.Join('&', StunList.Select(s => "stun=" + Utils.UrlEncode(s)));
|
||||
// NOTE: maybe we don't need to encode the stun host:port, since it should be a valid URI component already, and encoding will make it unreadable
|
||||
var query = string.Join('&', StunList.Select(s => "stun=" + s));
|
||||
uriBuilder.Query = query;
|
||||
}
|
||||
return prefix + uriBuilder.Uri.GetComponents(
|
||||
UriComponents.UserInfo | UriComponents.HostAndPort | UriComponents.PathAndQuery, UriFormat.UriEscaped);
|
||||
}
|
||||
|
||||
public string ToUriForFinalmask()
|
||||
{
|
||||
var prefix = IsHttp ? "realm+http://" : "realm://";
|
||||
var uriBuilder = new StringBuilder();
|
||||
uriBuilder.Append(prefix);
|
||||
uriBuilder.Append(Utils.UrlEncode(Token));
|
||||
uriBuilder.Append('@');
|
||||
uriBuilder.Append(RendezvousHostPort);
|
||||
uriBuilder.Append('/');
|
||||
uriBuilder.Append(Utils.UrlEncode(RealmName));
|
||||
return uriBuilder.ToString();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -5,6 +5,9 @@ public record ProtocolExtraItem
|
||||
public bool? Uot { get; init; }
|
||||
public string? CongestionControl { get; init; }
|
||||
|
||||
// http outbound
|
||||
public string? HttpHeaders { get; init; }
|
||||
|
||||
// vmess
|
||||
public string? AlterId { get; init; }
|
||||
public string? VmessSecurity { get; init; }
|
||||
@@ -31,6 +34,12 @@ public record ProtocolExtraItem
|
||||
public int? DownMbps { get; init; }
|
||||
public string? Ports { get; init; }
|
||||
public string? HopInterval { get; init; }
|
||||
// realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]&stun=<stun-host>[:port]...
|
||||
// example:
|
||||
// realm://public@realm.hy2.io/57f9be7c-2810-4f5b-8cb9-260bc84d6c90?stun=example.stun:3478&stun=example2.stun:3478
|
||||
public string? Hy2RealmUrl { get; init; }
|
||||
public string? GeckoMinPacketSize { get; init; }
|
||||
public string? GeckoMaxPacketSize { get; init; }
|
||||
|
||||
// naiveproxy
|
||||
public int? InsecureConcurrency { get; init; }
|
||||
|
||||
289
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
289
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -222,15 +222,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please fill in the correct IPv4 address for SendThrough. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string FillCorrectSendThroughIPv4 {
|
||||
get {
|
||||
return ResourceManager.GetString("FillCorrectSendThroughIPv4", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please enter the correct port format. 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -240,6 +231,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Invalid range format. Use 'from-to' (e.g., 50-100). 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string FillFragmentParameterError {
|
||||
get {
|
||||
return ResourceManager.GetString("FillFragmentParameterError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please enter the local listening port. 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -321,6 +321,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please enter valid HTTP request headers JSON. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string InvalidHttpOutboundHeaders {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidHttpOutboundHeaders", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Invalid Realm URL. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string InvalidHy2RealmUrl {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidHy2RealmUrl", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Invalid address (URL) 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1320,15 +1338,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Process traffic hijacking (experimental) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuNetBridge {
|
||||
get {
|
||||
return ResourceManager.GetString("menuNetBridge", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 New Update 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2076,6 +2085,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgInsecureConfiguration {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgInsecureConfiguration", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 The {0} property is invalid, please check 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2310,15 +2328,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>
|
||||
/// 查找类似 Non-VMess or SS protocol 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3052,11 +3061,20 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static string TbEnableNetBridge {
|
||||
public static string TbEnableHappyEyeballs {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3088,7 +3106,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Applies globally by default, with built-in FakeIP filtering (sing-box only). 的本地化字符串。
|
||||
/// 查找类似 Applies globally by default, and built-in FakeIP filtering is only built into sing-box. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbFakeIPTips {
|
||||
get {
|
||||
@@ -3177,6 +3195,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Gecko Packet Size (min/max) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbGeckoPacketSize {
|
||||
get {
|
||||
return ResourceManager.GetString("TbGeckoPacketSize", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Global Hotkey Settings 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3240,6 +3267,33 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 HTTP headers 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbHttpOutboundHeaders {
|
||||
get {
|
||||
return ResourceManager.GetString("TbHttpOutboundHeaders", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Realm URL 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbHy2RealmUrl {
|
||||
get {
|
||||
return ResourceManager.GetString("TbHy2RealmUrl", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Format: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port] 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbHy2RealmUrlTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbHy2RealmUrlTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 ICMP routing policy 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3294,6 +3348,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Ipv4 Address 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbIpv4Address {
|
||||
get {
|
||||
return ResourceManager.GetString("TbIpv4Address", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Ipv6 Address 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbIpv6Address {
|
||||
get {
|
||||
return ResourceManager.GetString("TbIpv6Address", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Most Stable 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3321,6 +3393,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>
|
||||
/// 查找类似 Address (IPv4, IPv6) 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3483,6 +3564,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Proxy Dial Resolution Strategy 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbProxyDialResolveStrategy {
|
||||
get {
|
||||
return ResourceManager.GetString("TbProxyDialResolveStrategy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Not recommended; may cause routing loops. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbProxyDialResolveStrategyTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbProxyDialResolveStrategyTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Public Key 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3582,6 +3681,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>
|
||||
/// 查找类似 Round Robin 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3708,15 +3825,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Save rules 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSaveNetBridgeRule {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSaveNetBridgeRule", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 sing-box Full Config Template 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3853,7 +3961,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static string TbSettingsBindInterfaceTip {
|
||||
get {
|
||||
@@ -4140,6 +4248,96 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fallback Delay 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentFallbackDelay {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentFallbackDelay", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fallback delay when ACK detection unavailable (e.g., 500ms). sing-box only. Empty = default. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentFallbackDelayTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentFallbackDelayTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fragment Interval 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentInterval {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentInterval", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Delay between fragments in ms (e.g., 10-20). Range 1-100. First value must be <= second. Empty = default. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentIntervalTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentIntervalTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fragment Length 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentLength {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentLength", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fragment size range in bytes (e.g., 50-100). First value must be <= second. Empty = default. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentLengthTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentLengthTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Max Split 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentMaxSplit {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentMaxSplit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Maximum number of fragments per packet (0-10000, 0 = unlimited). Xray only. Empty = default. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentMaxSplitTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentMaxSplitTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fragment Packets 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentPackets {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentPackets", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Packets to fragment: tlshello (TLS ClientHello) or 1-1 to 1-5 (first N TCP packets) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentPacketsTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentPacketsTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Geo files source (optional) 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -4860,6 +5058,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>
|
||||
/// 查找类似 *Default value raw 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -1104,6 +1104,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>فعال کردن فرگمنت</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>پکتهای فرگمنت</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>پکتهای قابل فرگمنت: tlshello (TLS ClientHello) یا 1-1 تا 1-5 (N اول پکت TCP)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>طول فرگمنت</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>محدوده اندازه فرگمنت بایت (مثلا 50-100). اول ≤ دوم. خالی = پیشفرض.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>فاصله فرگمنت</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>تأخیر بین فرگمنتها میلیثانیه (مثلا 10-20). بازه 1-100. اول ≤ دوم. خالی = پیشفرض.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>حداکثر تقسیم</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>مکسیمم تعداد تقسیم (0 = نامحدود، 0-10000). فقط Xray-core. خالی = پیشفرض.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>تأخیر فولبک</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>تأخیر فولبک فرگمنت TLS. فقط sing-box. خالی = پیشفرض.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>فرمت محدوده نامعتبر. از 'from-to' استفاده کنید (مثلا 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>فعال کردن کش فایل مجموعه قوانین برای sing-box</value>
|
||||
</data>
|
||||
@@ -1462,7 +1495,7 @@
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
@@ -1710,9 +1743,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Please fill in the correct IPv4 address for SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Bind Interface</value>
|
||||
</data>
|
||||
@@ -1740,4 +1770,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="menuNewUpdate" xml:space="preserve">
|
||||
<value>New Update</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
@@ -1101,6 +1101,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Activer le fragmentation (Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Paquets fragmentés</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Paquets à fragmenter : tlshello (TLS ClientHello) ou 1-1 à 1-5 (premiers N paquets TCP)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Longueur fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Plage de taille fragment (ex: 50-100). Première valeur ≤ seconde. Vide = défaut.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Intervalle fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Délai entre fragments en ms (ex: 10-20). Plage 1-100. Première valeur ≤ seconde. Vide = défaut.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Max Split</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Nb max de splits (0 = illimité, 0-10000). Xray-core seulement. Vide = défaut.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Délai fallback</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>Délai fallback pour fragment TLS. sing-box seulement. Vide = défaut.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Format de plage invalide. Utilisez 'from-to' (ex: 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Activer le fichier de cache de sing-box (fichiers règles)</value>
|
||||
</data>
|
||||
@@ -1459,7 +1492,7 @@
|
||||
<value>Choisir une config.</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Actif globalement par défaut, avec filtre FakeIP intégré ; ne fonctionne que dans sing-box</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Veuillez ajouter au moins une configuration</value>
|
||||
@@ -1701,9 +1734,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>Pour environnements multi-interfaces, entrez l'adresse IPv4 de la machine locale</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Veuillez saisir l’adresse IPv4 correcte de SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Lier l'interface</value>
|
||||
</data>
|
||||
|
||||
@@ -1104,6 +1104,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Fragment engedélyezése</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragmentált csomagok</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Fragmentálandó csomagok: tlshello (TLS ClientHello) vagy 1-1 – 1-5 (az első N TCP csomag)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment hossz</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Fragment méret tartománya bájtokban (pl. 50-100). Az első érték ≤ második. Üres = alapértelmezett.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment intervallum</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Késleltetés a fragmented csomagok között ms-ben (pl. 10-20). Tartomány 1-100. Az első érték ≤ második. Üres = alapértelmezett.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Max darabolás</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Maximális darabolások száma (0 = korlátlan, 0-10000). Csak Xray-core. Üres = alapértelmezett.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Fallback késleltetés</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>TLS fragment fallback késleltetés. Csak sing-box. Üres = alapértelmezett.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Érvénytelen tartomány formátum. Használja 'from-to' (pl. 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Gyorsítótár fájl engedélyezése sing-boxhoz (szabálykészlet fájlok)</value>
|
||||
</data>
|
||||
@@ -1462,7 +1495,7 @@
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
@@ -1710,9 +1743,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Please fill in the correct IPv4 address for SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Bind Interface</value>
|
||||
</data>
|
||||
|
||||
@@ -1104,6 +1104,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Aktifkan fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Paket Fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Paket untuk difragmentasikan: tlshello (TLS ClientHello) atau 1-1 s.d. 1-5 (N paket TCP pertama)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Panjang Fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Rentang ukuran fragment (byte), cth: 50-100. Nilai pertama harus ≤ kedua. Kosong = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Interval Fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Jeda antar fragment (ms), cth: 10-20. Rentang 1-100. Nilai pertama harus ≤ kedua. Kosong = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Maks Split</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Maks jumlah split (0 = unlimited, 0-10000). Hanya Xray-core. Kosong = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Fallback Delay</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>Delay fallback untuk TLS fragment. Hanya sing-box. Kosong = default.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Format range tidak valid. Gunakan 'from-to' (cth: 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Aktifkan file cache untuk sing-box (file ruleset)</value>
|
||||
</data>
|
||||
@@ -1462,7 +1495,7 @@
|
||||
<value>Pilih profil</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Berlaku secara global secara default, dengan filter FakeIP bawaan (hanya sing-box).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Silakan tambahkan setidaknya satu konfigurasi.</value>
|
||||
@@ -1701,12 +1734,6 @@ Tindakan "Ambil sertifikat" mungkin gagal jika sertifikat self-signed digunakan
|
||||
<data name="TransportExtra" xml:space="preserve">
|
||||
<value>XHTTP Extra</value>
|
||||
</data>
|
||||
<data name="TbAllowInsecureCertFetch" xml:space="preserve">
|
||||
<value>Izinkan pengambilan sertifikat tidak aman (self-signed)</value>
|
||||
</data>
|
||||
<data name="TbAllowInsecureCertFetchTips" xml:space="preserve">
|
||||
<value>Hanya untuk mengambil sertifikat self-signed. Fitur ini dapat mengekspos Anda pada risiko MITM.</value>
|
||||
</data>
|
||||
<data name="menuUdpTestServer" xml:space="preserve">
|
||||
<value>Uji latensi UDP konfigurasi</value>
|
||||
</data>
|
||||
@@ -1719,9 +1746,6 @@ Tindakan "Ambil sertifikat" mungkin gagal jika sertifikat self-signed digunakan
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>Untuk lingkungan multi-antarmuka, masukkan alamat IPv4 mesin lokal.</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Isi alamat IPv4 yang benar untuk SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Bind Interface</value>
|
||||
</data>
|
||||
|
||||
@@ -1044,6 +1044,12 @@
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>Congestion control</value>
|
||||
</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">
|
||||
<value>Previous proxy remarks</value>
|
||||
</data>
|
||||
@@ -1104,6 +1110,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Enable fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragment Packets</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Packets to fragment: tlshello (TLS ClientHello) or 1-1 to 1-5 (first N TCP packets)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment Length</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Fragment size range in bytes (e.g., 50-100). First value must be <= second. Empty = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment Interval</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Delay between fragments in ms (e.g., 10-20). Range 1-100. First value must be <= second. Empty = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Max Split</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Maximum number of fragments per packet (0-10000, 0 = unlimited). Xray only. Empty = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Fallback Delay</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>Fallback delay when ACK detection unavailable (e.g., 500ms). sing-box only. Empty = default.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Invalid range format. Use 'from-to' (e.g., 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Enable cache file for sing-box (ruleset files)</value>
|
||||
</data>
|
||||
@@ -1462,7 +1501,7 @@
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
@@ -1713,14 +1752,11 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Please fill in the correct IPv4 address for SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Bind Interface</value>
|
||||
</data>
|
||||
<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 name="TbPreSharedKey" xml:space="preserve">
|
||||
<value>PreSharedKey</value>
|
||||
@@ -1764,19 +1800,52 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>Split the tail of packets into smaller fragments. This may affect throughput and latency.</value>
|
||||
</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">
|
||||
<value>DNS via Bridge</value>
|
||||
</data>
|
||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
||||
<value>Save rules</value>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL</value>
|
||||
</data>
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>Invalid Realm URL.</value>
|
||||
</data>
|
||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>Please enter valid HTTP request headers JSON.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>Format: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||
<value>Gecko Packet Size (min/max)</value>
|
||||
</data>
|
||||
<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>
|
||||
</root>
|
||||
@@ -1044,6 +1044,12 @@
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>Управление перегрузками</value>
|
||||
</data>
|
||||
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>HTTP-заголовки</value>
|
||||
</data>
|
||||
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>Пользовательские заголовки исходящего HTTP-запроса. Введите JSON-объект, значения которого — строка или массив строк.</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>Псевдоним предыдущего прокси</value>
|
||||
</data>
|
||||
@@ -1104,6 +1110,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Включить фрагментацию (Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Пакеты для фрагментации</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Пакеты для фрагментации: tlshello (TLS ClientHello) или 1-1 — 1-5 (первые N TCP-пакетов)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Длина фрагмента</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Диапазон размера фрагмента в байтах (например, 50-100). Первое значение не больше второго. Пусто = по умолчанию.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Интервал фрагментации</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Задержка между фрагментами в мс (например, 10-20). Диапазон 1-100. Первое значение не больше второго. Пусто = по умолчанию.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Макс. разделение</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Максимальное число разделений (0 = без лимита, 0-10000). Только для Xray-core. Пусто = по умолчанию.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Резервная задержка</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>Резервная задержка для TLS-фрагмента. Только для sing-box. Пусто = по умолчанию.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Неверный формат диапазона. Используйте 'from-to' (например, 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Включить файл кэша для sing-box (файлы наборов правил)</value>
|
||||
</data>
|
||||
@@ -1462,7 +1501,7 @@
|
||||
<value>Выбрать профиль</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>По умолчанию применяется глобально, со встроенной фильтрацией FakeIP (только sing-box).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Добавьте хотя бы одну конфигурацию</value>
|
||||
@@ -1581,6 +1620,9 @@
|
||||
<data name="TbEchConfigList" xml:space="preserve">
|
||||
<value>EchConfigList</value>
|
||||
</data>
|
||||
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
||||
<value>Проверять сертификат узла по имени</value>
|
||||
</data>
|
||||
<data name="TbFullCertTips" xml:space="preserve">
|
||||
<value>Полный сертификат (цепочка) в формате PEM</value>
|
||||
</data>
|
||||
@@ -1710,14 +1752,11 @@
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>Для среды с несколькими сетевыми интерфейсами укажите IPv4-адрес локального компьютера</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Укажите корректный IPv4-адрес для SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Привязать интерфейс</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
||||
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для привязки. Работает только в Windows и режиме TUN</value>
|
||||
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для исходящих подключений. На Linux/macOS работает только при включённом TUN.</value>
|
||||
</data>
|
||||
<data name="TbPreSharedKey" xml:space="preserve">
|
||||
<value>Общий ключ (PSK)</value>
|
||||
@@ -1740,4 +1779,55 @@
|
||||
<data name="menuNewUpdate" xml:space="preserve">
|
||||
<value>Доступно обновление</value>
|
||||
</data>
|
||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
||||
<value>Предупреждение: 1 августа 2026 г. Xray отключит пропуск проверки сертификата (allowInsecure). Как можно скорее перейдите на привязанный отпечаток сертификата (pinnedPeerCertSha256). После этой даты allowInsecure использовать будет нельзя.</value>
|
||||
</data>
|
||||
<data name="TbRouteExcludeAddress" xml:space="preserve">
|
||||
<value>Адреса, исключаемые из маршрутизации</value>
|
||||
</data>
|
||||
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
|
||||
<value>Разделяйте запятыми (,).</value>
|
||||
</data>
|
||||
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
|
||||
<value>Недопустимый адрес в списке исключений маршрутизации TUN: {0}</value>
|
||||
</data>
|
||||
<data name="MsgOptionsConflict" xml:space="preserve">
|
||||
<value>Конфликт между {0} и {1}</value>
|
||||
</data>
|
||||
<data name="TbEnableFinalFragment" xml:space="preserve">
|
||||
<value>Включить финальную фрагментацию (Final Fragment)</value>
|
||||
</data>
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>Разбивать конец пакетов на более мелкие фрагменты при отправке. Это может влиять на пропускную способность и задержку.</value>
|
||||
</data>
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS через Bridge</value>
|
||||
</data>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>Обнаружена небезопасная конфигурация: AllowInsecure включён, но сертификат не предоставлен. Это может привести к атаке «человек посередине» (MITM).</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL</value>
|
||||
</data>
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>Некорректный Realm URL.</value>
|
||||
</data>
|
||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>Введите корректный JSON заголовков HTTP-запроса.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>Формат: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||
<value>Размер пакета Gecko (мин/макс)</value>
|
||||
</data>
|
||||
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||
<value>Если включено, используется sing-box TUN; иначе — xray TUN.</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||
<value>Поставщик корневых сертификатов</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>Применяется только к загрузкам и сетевым запросам графического интерфейса v2rayN. Не влияет на проверку сертификатов ядром.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1030,7 +1030,7 @@
|
||||
<value>添加 [Hysteria2]</value>
|
||||
</data>
|
||||
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
|
||||
<value>Hysteria 最大带宽 (Up/Dw)</value>
|
||||
<value>Hysteria 最大带宽 (上传/下载)</value>
|
||||
</data>
|
||||
<data name="TbSettingsUseSystemHosts" xml:space="preserve">
|
||||
<value>使用系统 hosts</value>
|
||||
@@ -1041,6 +1041,12 @@
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>拥塞控制算法</value>
|
||||
</data>
|
||||
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>HTTP 请求头</value>
|
||||
</data>
|
||||
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>自定义 HTTP 出站请求头,请输入值为字符串或字符串数组的 JSON 对象。</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>前置代理配置别名</value>
|
||||
</data>
|
||||
@@ -1101,6 +1107,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>启用分片 (Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>分片包类型</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>要分片的包类型: tlshello (TLS ClientHello) 或 1-1 到 1-5 (前 N 个 TCP 包)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>分片长度</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>分片大小范围(字节), 如 50-100. 第一个值必须 <= 第二个值. 留空使用默认值.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>分片间隔</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>分片间延迟(毫秒), 如 10-20. 范围 1-100. 第一个值必须 <= 第二个值. 留空使用默认值.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>最大分片数</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>每个包最大分片数 (0-10000, 0=无限制). 仅 Xray. 留空使用默认值.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>回退延迟</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>ACK 检测不可用时的回退延迟 (如 500ms). 仅 sing-box. 留空使用默认值.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>无效的范围格式。请使用 'from-to' (如 50-100)。</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>启用 sing-box (规则集文件) 的缓存文件</value>
|
||||
</data>
|
||||
@@ -1459,7 +1498,7 @@
|
||||
<value>选择配置</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
|
||||
<value>默认全局生效,仅在 sing-box 中内置 FakeIP 过滤。</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>请至少添加一个配置</value>
|
||||
@@ -1707,9 +1746,6 @@
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>用于多网口环境,请填写本机 IPv4 地址</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>请填写正确的 SendThrough IPv4 地址。</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>绑定网口</value>
|
||||
</data>
|
||||
@@ -1761,19 +1797,52 @@
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>将数据包末端拆分为更小片段发送,可能影响吞吐与延迟。</value>
|
||||
</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">
|
||||
<value>DNS 通过 Bridge</value>
|
||||
</data>
|
||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
||||
<value>保存规则</value>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>检测到不安全配置:AllowInsecure 已启用,但未提供证书。这可能会导致中间人攻击。</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>请填写合法的HTTP请求头JSON。</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>连接代理解析策略</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
|
||||
<value>不建议开启,特殊情况可能回环。</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballs" xml:space="preserve">
|
||||
<value>启用 Happy Eyeballs</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
|
||||
<value>需配合 UseIP 策略使用。启用后将同时尝试 IPv4 和 IPv6 连接,并自动选择更快可用的路径。</value>
|
||||
</data>
|
||||
<data name="TbIpv6Address" xml:space="preserve">
|
||||
<value>Ipv6 地址</value>
|
||||
</data>
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 地址</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1041,6 +1041,12 @@
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>擁塞控制算法</value>
|
||||
</data>
|
||||
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>HTTP 請求頭</value>
|
||||
</data>
|
||||
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>自訂 HTTP 出站請求頭,請輸入值為字串或字串陣列的 JSON 物件。</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>前置代理節點別名</value>
|
||||
</data>
|
||||
@@ -1101,6 +1107,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>啟用分片(Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>分片封包類型</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>要分片的封包類型:tlshello (TLS ClientHello) 或 1-1 到 1-5 (前 N 個 TCP 封包)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>分片長度</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>分片大小範圍(位元組),如 50-100。第一個值必須 <= 第二個值。留空使用預設值。</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>分片間隔</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>分片間延遲(毫秒),如 10-20。範圍 1-100。第一個值必須 <= 第二個值。留空使用預設值。</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>最大分片數</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>每個封包最大分片數 (0-10000, 0=無限制)。僅 Xray。留空使用預設值。</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>回退延遲</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>ACK 偵測不可用時的回退延遲 (如 500ms)。僅 sing-box。留空使用預設值。</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>無效的範圍格式。請使用 'from-to' (如 50-100)。</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>啟用 sing-box(規則集檔案)的快取檔案</value>
|
||||
</data>
|
||||
@@ -1459,7 +1498,7 @@
|
||||
<value>選擇節點</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>默認全域生效,內置 FakeIP 過濾,僅在 sing-box 中生效</value>
|
||||
<value>默認全局生效,僅在 sing-box 中內置 FakeIP 過濾。</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>請至少添加一個節點</value>
|
||||
@@ -1707,9 +1746,6 @@
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>適用於多網路介面環境,請填寫本機 IPv4 位址</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>請填寫正確的 SendThrough IPv4 位址。</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>綁定網路介面</value>
|
||||
</data>
|
||||
@@ -1761,19 +1797,52 @@
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>將封包末端拆分為更小的片段進行傳送,可能會影響吞吐量與延遲</value>
|
||||
</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">
|
||||
<value>DNS 透過 Bridge</value>
|
||||
</data>
|
||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
||||
<value>保存規則</value>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>偵測到不安全的設定:已啟用 AllowInsecure,但未提供憑證。這可能導致中間人攻擊(MITM)</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL</value>
|
||||
</data>
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>無效的 Realm URL.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>格式: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||
<value>Gecko 封包大小 (min/max)</value>
|
||||
</data>
|
||||
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||
<value>啟用則使用 sing-box TUN ,否則使用 xray TUN</value>
|
||||
</data>
|
||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>請填寫合法的HTTP請求頭JSON。</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||
<value>根憑證提供者</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>僅適用於 v2rayN GUI 的下載與網路請求,不影響核心的憑證驗證</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
|
||||
<value>連線代理解析策略</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
|
||||
<value>不建議啟用,特殊情況可能造成連線循環</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballs" xml:space="preserve">
|
||||
<value>啟用 Happy Eyeballs</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
|
||||
<value>需搭配 UseIP 策略,啟用後會同時嘗試 IPv4 與 IPv6 連線,並自動選擇速度較快的路徑</value>
|
||||
</data>
|
||||
<data name="TbIpv6Address" xml:space="preserve">
|
||||
<value>Ipv6 位址</value>
|
||||
</data>
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 位址</value>
|
||||
</data>
|
||||
</root>
|
||||
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
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
@@ -23,6 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Sample\chrome_roots_pem" />
|
||||
<EmbeddedResource Include="Sample\clash_mixin_yaml" />
|
||||
<EmbeddedResource Include="Sample\clash_tun_yaml" />
|
||||
<EmbeddedResource Include="Sample\custom_routing_black" />
|
||||
@@ -32,6 +33,7 @@
|
||||
<EmbeddedResource Include="Sample\dns_v2ray_normal" />
|
||||
<EmbeddedResource Include="Sample\kill_as_sudo_linux_sh" />
|
||||
<EmbeddedResource Include="Sample\kill_as_sudo_osx_sh" />
|
||||
<EmbeddedResource Include="Sample\mozilla_roots_pem" />
|
||||
<EmbeddedResource Include="Sample\pac" />
|
||||
<EmbeddedResource Include="Sample\proxy_set_linux_sh" />
|
||||
<EmbeddedResource Include="Sample\proxy_set_osx_sh" />
|
||||
@@ -90,7 +92,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NetBridge\src\NetBridgeLib\NetBridgeLib.csproj" />
|
||||
<ProjectReference Include="..\ServiceLib.UdpTest\ServiceLib.UdpTest.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -66,10 +66,6 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!(context.IsTunEnabled || context.IsWindows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
outbound.bind_interface = ShouldBindNet(outbound) ? bindInterface : null;
|
||||
@@ -83,10 +79,6 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Utils.IsLocalIP(sendThrough))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
|
||||
@@ -137,12 +137,12 @@ public partial class CoreConfigSingboxService
|
||||
// fake ip
|
||||
if (simpleDnsItem.FakeIP == true)
|
||||
{
|
||||
var fakeipRange = simpleDnsItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : simpleDnsItem.FakeIPRange;
|
||||
var fakeip = new Server4Sbox
|
||||
{
|
||||
tag = Global.SingboxFakeDNSTag,
|
||||
type = "fakeip",
|
||||
inet4_range = "198.18.0.0/15",
|
||||
inet6_range = "fc00::/18",
|
||||
inet4_range = fakeipRange,
|
||||
};
|
||||
_coreConfig.dns.servers.Add(fakeip);
|
||||
}
|
||||
@@ -171,7 +171,7 @@ public partial class CoreConfigSingboxService
|
||||
_coreConfig.dns.rules.Add(new()
|
||||
{
|
||||
server = Global.SingboxDirectDNSTag,
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial),
|
||||
domain = context.ProtectDomainList.ToList(),
|
||||
});
|
||||
}
|
||||
@@ -266,7 +266,7 @@ public partial class CoreConfigSingboxService
|
||||
});
|
||||
}
|
||||
|
||||
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true)
|
||||
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp != false)
|
||||
{
|
||||
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
|
||||
fakeipFilterRule.invert = true;
|
||||
|
||||
@@ -67,9 +67,13 @@ public partial class CoreConfigSingboxService
|
||||
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
||||
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
||||
tunInbound.stack = _config.TunModeItem.Stack;
|
||||
if (_config.TunModeItem.EnableIPv6Address == false)
|
||||
|
||||
var address = _config.TunModeItem.Ipv4Address.NullIfEmpty() ?? Global.TunIpv4Address.First();
|
||||
tunInbound.address = [address];
|
||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||
{
|
||||
tunInbound.address = ["172.18.0.1/30"];
|
||||
var address6 = _config.TunModeItem.Ipv6Address.NullIfEmpty() ?? Global.TunIpv6Address.First();
|
||||
tunInbound.address.Add(address6);
|
||||
}
|
||||
tunInbound.route_exclude_address = _config.TunModeItem.RouteExcludeAddress;
|
||||
|
||||
|
||||
@@ -217,11 +217,17 @@ public partial class CoreConfigSingboxService
|
||||
|
||||
if (!protocolExtra.SalamanderPass.IsNullOrEmpty())
|
||||
{
|
||||
var isGecko = !protocolExtra.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtra.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||
outbound.obfs = new()
|
||||
{
|
||||
type = "salamander",
|
||||
type = isGecko ? "gecko" : "salamander",
|
||||
password = protocolExtra.SalamanderPass.TrimEx(),
|
||||
};
|
||||
if (isGecko)
|
||||
{
|
||||
outbound.obfs.min_packet_size = protocolExtra.GeckoMinPacketSize.ToInt();
|
||||
outbound.obfs.max_packet_size = protocolExtra.GeckoMaxPacketSize.ToInt();
|
||||
}
|
||||
}
|
||||
int? upMbps = protocolExtra?.UpMbps is { } su and >= 0
|
||||
? su
|
||||
@@ -264,6 +270,22 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
if (HyRealm.TryParse(protocolExtra.Hy2RealmUrl, out var realm)
|
||||
&& realm is not null)
|
||||
{
|
||||
var realm4Sbox = new HyRealm4Sbox()
|
||||
{
|
||||
server_url = realm.ToServerUrl(),
|
||||
token = realm.Token,
|
||||
realm_id = realm.RealmName,
|
||||
stun_servers = realm.StunList?.Count > 0 ? realm.StunList : null,
|
||||
};
|
||||
outbound.realm = realm4Sbox;
|
||||
outbound.server = null;
|
||||
outbound.server_port = null;
|
||||
outbound.server_ports = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EConfigType.TUIC:
|
||||
@@ -395,11 +417,15 @@ public partial class CoreConfigSingboxService
|
||||
var tls = new Tls4Sbox()
|
||||
{
|
||||
enabled = true,
|
||||
record_fragment = _config.CoreBasicItem.EnableFragment ? true : null,
|
||||
server_name = serverName,
|
||||
insecure = _node.GetAllowInsecure(),
|
||||
alpn = _node.GetAlpn(),
|
||||
};
|
||||
if (_config.CoreBasicItem.EnableFragment == true)
|
||||
{
|
||||
tls.fragment = true;
|
||||
tls.record_fragment = true;
|
||||
}
|
||||
if (_node.Fingerprint.IsNotEmpty())
|
||||
{
|
||||
tls.utls = new Utls4Sbox()
|
||||
|
||||
@@ -10,18 +10,27 @@ public partial class CoreConfigSingboxService
|
||||
var simpleDnsItem = context.SimpleDnsItem;
|
||||
|
||||
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
|
||||
var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
|
||||
var dialDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial);
|
||||
|
||||
var rawDNSItem = context.RawDnsItem;
|
||||
if (rawDNSItem is { Enabled: true })
|
||||
{
|
||||
defaultDomainResolverTag = Global.SingboxLocalDNSTag;
|
||||
directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
|
||||
dialDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
|
||||
}
|
||||
else if (!simpleDnsItem.Strategy4Freedom.IsNullOrEmpty())
|
||||
{
|
||||
var directOutbound = _coreConfig.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag);
|
||||
directOutbound?.domain_resolver = new()
|
||||
{
|
||||
server = defaultDomainResolverTag,
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||
};
|
||||
}
|
||||
_coreConfig.route.default_domain_resolver = new()
|
||||
{
|
||||
server = defaultDomainResolverTag,
|
||||
strategy = directDnsStrategy
|
||||
strategy = dialDnsStrategy
|
||||
};
|
||||
|
||||
if (context.IsTunEnabled)
|
||||
@@ -34,19 +43,22 @@ public partial class CoreConfigSingboxService
|
||||
_coreConfig.route.rules.AddRange(tunRules);
|
||||
}
|
||||
|
||||
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
|
||||
_coreConfig.route.rules.Add(new()
|
||||
var lstDirectExe = BuildRoutingDirectExe();
|
||||
if (lstDirectExe.Count > 0)
|
||||
{
|
||||
port = [53],
|
||||
action = "hijack-dns",
|
||||
process_name = lstDnsExe
|
||||
});
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
port = [53],
|
||||
action = "hijack-dns",
|
||||
process_path = lstDirectExe,
|
||||
});
|
||||
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
process_name = lstDirectExe
|
||||
});
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
process_path = lstDirectExe,
|
||||
});
|
||||
}
|
||||
|
||||
// ICMP Routing
|
||||
var icmpRouting = _config.TunModeItem.IcmpRouting ?? "";
|
||||
@@ -256,34 +268,38 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
|
||||
private List<string> BuildRoutingDirectExe()
|
||||
{
|
||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
|
||||
var allCoreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||
|
||||
foreach (var coreConfig in coreInfoResult)
|
||||
foreach (var coreConfig in allCoreInfo)
|
||||
{
|
||||
if (!context.ProtectCoreTypeList.Contains(coreConfig.CoreType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (coreConfig.CoreExes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var baseExeName in coreConfig.CoreExes)
|
||||
{
|
||||
if (coreConfig.CoreType != ECoreType.sing_box)
|
||||
//directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
var exePath = CoreInfoManager.Instance.GetCoreExecFile(coreConfig, out _);
|
||||
if (!exePath.IsNullOrEmpty())
|
||||
{
|
||||
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
directExeSet.Add(exePath);
|
||||
}
|
||||
directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
}
|
||||
|
||||
var lstDnsExe = new List<string>(dnsExeSet);
|
||||
var lstDirectExe = new List<string>(directExeSet);
|
||||
|
||||
return (lstDnsExe, lstDirectExe);
|
||||
return directExeSet.ToList();
|
||||
}
|
||||
|
||||
private void GenRoutingUserRule(RulesItem? item)
|
||||
|
||||
@@ -89,6 +89,7 @@ public partial class CoreConfigV2rayService
|
||||
EMultipleLoad.RoundRobin => "roundRobin",
|
||||
EMultipleLoad.LeastPing => "leastPing",
|
||||
EMultipleLoad.LeastLoad => "leastLoad",
|
||||
EMultipleLoad.Fallback => "leastLoad",
|
||||
_ => "roundRobin",
|
||||
};
|
||||
var balancerTag = $"{selector}{Global.BalancerTagSuffix}";
|
||||
@@ -98,13 +99,16 @@ public partial class CoreConfigV2rayService
|
||||
strategy = new()
|
||||
{
|
||||
type = strategyType,
|
||||
settings = new()
|
||||
{
|
||||
expected = 1,
|
||||
},
|
||||
settings = strategyType == "leastLoad"
|
||||
? new()
|
||||
{
|
||||
expected = 1,
|
||||
tolerance = multipleLoad == EMultipleLoad.Fallback ? 0.2 : null,
|
||||
maxRTT = multipleLoad == EMultipleLoad.Fallback ? "5000ms" : null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
tag = balancerTag,
|
||||
fallbackTag = multipleLoad == EMultipleLoad.Fallback ? Global.DirectTag : null,
|
||||
};
|
||||
_coreConfig.routing.balancers ??= [];
|
||||
_coreConfig.routing.balancers.Add(balancer);
|
||||
|
||||
@@ -138,10 +138,6 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!(context.IsTunEnabled || context.IsWindows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
if (!ShouldBindNet(outbound))
|
||||
@@ -176,10 +172,6 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Utils.IsLocalIP(sendThrough))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ public partial class CoreConfigV2rayService
|
||||
_coreConfig.routing.rules.Add(new RulesItem4Ray
|
||||
{
|
||||
type = "field",
|
||||
inboundTag = new List<string> { Global.DnsTag },
|
||||
inboundTag = [Global.DnsTag],
|
||||
outboundTag = Global.ProxyTag,
|
||||
});
|
||||
return;
|
||||
@@ -43,11 +43,7 @@ public partial class CoreConfigV2rayService
|
||||
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new()
|
||||
{
|
||||
domainStrategy = strategy4Freedom,
|
||||
userLevel = 0
|
||||
};
|
||||
FillSockoptDomainStrategy(outbound, strategy4Freedom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +60,22 @@ public partial class CoreConfigV2rayService
|
||||
.ForEach(outbound => outbound.targetStrategy = strategy4Proxy);
|
||||
}
|
||||
|
||||
var strategy4DialProxy = simpleDnsItem?.Strategy4ProxyDial ?? Global.AsIs;
|
||||
//Outbound DialProxy domainStrategy
|
||||
if (strategy4DialProxy.IsNotEmpty() && strategy4DialProxy != Global.AsIs)
|
||||
{
|
||||
var xraySupportConfigTypeNames = Global.XraySupportConfigType
|
||||
.Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x])
|
||||
.ToHashSet();
|
||||
_coreConfig.outbounds
|
||||
.Where(t => xraySupportConfigTypeNames.Contains(t.protocol))
|
||||
.ToList()
|
||||
.ForEach(outbound =>
|
||||
{
|
||||
FillSockoptDomainStrategy(outbound, strategy4DialProxy);
|
||||
});
|
||||
}
|
||||
|
||||
FillDnsServers(dnsItem);
|
||||
FillDnsHosts(dnsItem);
|
||||
|
||||
@@ -108,6 +120,33 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
|
||||
private void GenFakeDns()
|
||||
{
|
||||
var fakeipRange = _config.SimpleDNSItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : _config.SimpleDNSItem.FakeIPRange;
|
||||
var poolSize = 65535L;
|
||||
try
|
||||
{
|
||||
var fakeipNetwork = IPNetwork2.Parse(fakeipRange);
|
||||
var totalIPs = fakeipNetwork.Total;
|
||||
// see https://github.com/XTLS/Xray-core/blob/6e3322d219140a025285ded1114fe17a5edb74d8/app/dns/fakedns/fake.go#L88
|
||||
// if math.Log2(float64(lruSize)) >= float64(rooms) { return errors.New("LRU size is bigger than subnet size").AtError() }
|
||||
totalIPs -= 1;
|
||||
if (totalIPs > 0)
|
||||
{
|
||||
poolSize = (totalIPs >= long.MaxValue) ? long.MaxValue : (long)totalIPs;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
_coreConfig.fakedns = new()
|
||||
{
|
||||
ipPool = fakeipRange,
|
||||
poolSize = poolSize,
|
||||
};
|
||||
}
|
||||
|
||||
private void FillDnsServers(Dns4Ray dnsItem)
|
||||
{
|
||||
var simpleDNSItem = context.SimpleDnsItem;
|
||||
@@ -234,6 +273,23 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
var directDnsTagIndex = 1;
|
||||
|
||||
if (simpleDNSItem.FakeIP == true)
|
||||
{
|
||||
var fakeIPMatchDomain = new HashSet<string>(proxyDomainList);
|
||||
fakeIPMatchDomain.UnionWith(proxyGeositeList);
|
||||
if (simpleDNSItem.GlobalFakeIp != false)
|
||||
{
|
||||
fakeIPMatchDomain.UnionWith(directDomainList);
|
||||
fakeIPMatchDomain.UnionWith(directGeositeList);
|
||||
fakeIPMatchDomain.UnionWith(expectedDomainList);
|
||||
}
|
||||
if (fakeIPMatchDomain.Count > 0)
|
||||
{
|
||||
GenFakeDns();
|
||||
AddDnsServers(["fakedns"], fakeIPMatchDomain.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
AddDnsServers(remoteDNSAddress, proxyDomainList);
|
||||
AddDnsServers(directDNSAddress, directDomainList, true);
|
||||
AddDnsServers(remoteDNSAddress, proxyGeositeList);
|
||||
@@ -384,11 +440,9 @@ public partial class CoreConfigV2rayService
|
||||
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new()
|
||||
{
|
||||
domainStrategy = domainStrategy4Freedom,
|
||||
userLevel = 0,
|
||||
};
|
||||
outbound.streamSettings ??= new();
|
||||
outbound.streamSettings.sockopt ??= new();
|
||||
outbound.streamSettings.sockopt.domainStrategy = domainStrategy4Freedom;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,4 +525,28 @@ public partial class CoreConfigV2rayService
|
||||
};
|
||||
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
||||
}
|
||||
|
||||
private void FillSockoptDomainStrategy(Outbounds4Ray outbound, string? domainStrategy, bool skipHappyEyeballs = false)
|
||||
{
|
||||
if (domainStrategy.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
outbound.streamSettings ??= new();
|
||||
outbound.streamSettings.sockopt ??= new();
|
||||
var sockopt = outbound.streamSettings.sockopt;
|
||||
sockopt.domainStrategy = domainStrategy;
|
||||
|
||||
if (skipHappyEyeballs || _config.SimpleDNSItem.EnableHappyEyeballs != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var happyEyeballsItem = _config.HappyEyeballs4RayItem;
|
||||
sockopt.happyEyeballs ??= new();
|
||||
var happyEyeballs = sockopt.happyEyeballs;
|
||||
happyEyeballs.tryDelayMs = happyEyeballsItem.TryDelayMs;
|
||||
happyEyeballs.prioritizeIPv6 = happyEyeballsItem.PrioritizeIPv6;
|
||||
happyEyeballs.interleave = happyEyeballsItem.Interleave;
|
||||
happyEyeballs.maxConcurrentTry = happyEyeballsItem.MaxConcurrentTry;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +63,23 @@ public partial class CoreConfigV2rayService
|
||||
new Inbounds4Ray();
|
||||
tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun";
|
||||
tunInbound.settings.MTU = _config.TunModeItem.Mtu;
|
||||
if (!_config.TunModeItem.EnableIPv6Address)
|
||||
|
||||
var address = _config.TunModeItem.Ipv4Address.NullIfEmpty() ?? Global.TunIpv4Address.First();
|
||||
tunInbound.settings.gateway = [address];
|
||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||
{
|
||||
tunInbound.settings.gateway = ["172.18.0.1/30"];
|
||||
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
|
||||
var address6 = _config.TunModeItem.Ipv6Address.NullIfEmpty() ?? Global.TunIpv6Address.First();
|
||||
tunInbound.settings.gateway.Add(address6);
|
||||
}
|
||||
tunInbound.settings.dns = [address.Split('/').First()];
|
||||
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
|
||||
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
|
||||
if (!bindInterface.IsNullOrEmpty())
|
||||
{
|
||||
tunInbound.settings.autoOutboundsInterface = bindInterface;
|
||||
}
|
||||
tunInbound.sniffing = inbound.sniffing;
|
||||
tunInbound.sniffing.routeOnly = true;
|
||||
|
||||
if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||
{
|
||||
@@ -151,6 +157,16 @@ public partial class CoreConfigV2rayService
|
||||
inbound.sniffing.destOverride = inItem.DestOverride;
|
||||
inbound.sniffing.routeOnly = inItem.RouteOnly;
|
||||
|
||||
if (_config.SimpleDNSItem.FakeIP == true)
|
||||
{
|
||||
// Ensure destOverride contains "fakedns" if FakeIP is enabled
|
||||
inbound.sniffing.destOverride ??= [];
|
||||
if (!inbound.sniffing.destOverride.Contains("fakedns"))
|
||||
{
|
||||
inbound.sniffing.destOverride.Add("fakedns");
|
||||
}
|
||||
}
|
||||
|
||||
return inbound;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ public partial class CoreConfigV2rayService
|
||||
break;
|
||||
}
|
||||
case EConfigType.SOCKS:
|
||||
case EConfigType.HTTP:
|
||||
{
|
||||
ServersItem4Ray serversItem;
|
||||
if (outbound.settings.servers.Count <= 0)
|
||||
@@ -171,6 +170,31 @@ public partial class CoreConfigV2rayService
|
||||
outbound.settings.vnext = null;
|
||||
break;
|
||||
}
|
||||
case EConfigType.HTTP:
|
||||
{
|
||||
outbound.settings.address = _node.Address;
|
||||
outbound.settings.port = _node.Port;
|
||||
|
||||
if (protocolExtra.HttpHeaders.IsNotEmpty())
|
||||
{
|
||||
outbound.settings.headers = JsonUtils.ParseJson(protocolExtra.HttpHeaders);
|
||||
}
|
||||
|
||||
if (_node.Username.IsNotEmpty()
|
||||
&& _node.Password.IsNotEmpty())
|
||||
{
|
||||
outbound.settings.user = _node.Username;
|
||||
outbound.settings.pass = _node.Password;
|
||||
outbound.settings.level = 1;
|
||||
outbound.settings.email = Global.UserEMail;
|
||||
}
|
||||
|
||||
FillOutboundMux(outbound);
|
||||
|
||||
outbound.settings.vnext = null;
|
||||
outbound.settings.servers = null;
|
||||
break;
|
||||
}
|
||||
case EConfigType.VLESS:
|
||||
{
|
||||
VnextItem4Ray vnextItem;
|
||||
@@ -607,16 +631,32 @@ public partial class CoreConfigV2rayService
|
||||
quicParams.congestion = "bbr";
|
||||
}
|
||||
hy2Finalmask.quicParams = quicParams;
|
||||
hy2Finalmask.udp ??= [];
|
||||
if (HyRealm.TryParse(protocolExtra.Hy2RealmUrl, out var realm)
|
||||
&& realm is not null)
|
||||
{
|
||||
hy2Finalmask.udp.Add(new Mask4Ray
|
||||
{
|
||||
type = "realm",
|
||||
settings = new MaskSettings4Ray { url = realm.ToUriForFinalmask(), stunServers = realm.StunList },
|
||||
});
|
||||
}
|
||||
if (!protocolExtra.SalamanderPass.IsNullOrEmpty())
|
||||
{
|
||||
hy2Finalmask.udp =
|
||||
[
|
||||
new Mask4Ray
|
||||
{
|
||||
type = "salamander",
|
||||
settings = new MaskSettings4Ray { password = protocolExtra.SalamanderPass.TrimEx(), }
|
||||
}
|
||||
];
|
||||
var isGecko = !protocolExtra.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtra.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||
var salamanderSettings = new MaskSettings4Ray
|
||||
{
|
||||
password = protocolExtra.SalamanderPass.TrimEx(),
|
||||
};
|
||||
if (isGecko)
|
||||
{
|
||||
salamanderSettings.packetSize = $"{protocolExtra.GeckoMinPacketSize}-{protocolExtra.GeckoMaxPacketSize}";
|
||||
}
|
||||
hy2Finalmask.udp.Add(new Mask4Ray
|
||||
{
|
||||
type = "salamander",
|
||||
settings = salamanderSettings,
|
||||
});
|
||||
}
|
||||
streamSettings.hysteriaSettings = new()
|
||||
{
|
||||
@@ -825,21 +865,10 @@ public partial class CoreConfigV2rayService
|
||||
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
|
||||
.ToList();
|
||||
|
||||
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
|
||||
var fragmentMask = BuildFragmentsMasks();
|
||||
|
||||
foreach (var outbound in actOutboundWithTlsList)
|
||||
{
|
||||
//var packets = configPackets;
|
||||
//if (outbound.streamSettings.security == Global.StreamSecurityReality
|
||||
// && packets == "tlshello")
|
||||
//{
|
||||
// packets = "1-3";
|
||||
//}
|
||||
//else if (outbound.streamSettings.security == Global.StreamSecurity
|
||||
// && packets != "tlshello")
|
||||
//{
|
||||
// packets = "tlshello";
|
||||
//}
|
||||
var finalMaskJsonObj = JsonUtils.ParseJson(JsonUtils.Serialize(outbound.streamSettings?.finalmask)) as JsonObject ?? new JsonObject();
|
||||
// tcp fragment
|
||||
var tcpFinalmaskList = finalMaskJsonObj["tcp"] as JsonArray ?? [];
|
||||
@@ -848,13 +877,6 @@ public partial class CoreConfigV2rayService
|
||||
tcpFinalmaskList.Add(JsonUtils.SerializeToNode(fragmentMask));
|
||||
finalMaskJsonObj["tcp"] = tcpFinalmaskList;
|
||||
}
|
||||
// udp noise
|
||||
var udpFinalmaskList = finalMaskJsonObj["udp"] as JsonArray ?? [];
|
||||
if (udpFinalmaskList.Count == 0)
|
||||
{
|
||||
udpFinalmaskList.Add(JsonUtils.SerializeToNode(noiseMask));
|
||||
finalMaskJsonObj["udp"] = udpFinalmaskList;
|
||||
}
|
||||
// write back
|
||||
outbound.streamSettings.finalmask = finalMaskJsonObj;
|
||||
}
|
||||
@@ -862,7 +884,7 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
private void ApplyFinalFragment()
|
||||
{
|
||||
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
|
||||
var fragmentMask = BuildFragmentsMasks();
|
||||
var actOutboundList = _coreConfig.outbounds.Where(n => n.tag.StartsWith(Global.ProxyTag)).ToList();
|
||||
|
||||
var fragmentFreedom = new Outbounds4Ray()
|
||||
@@ -874,9 +896,8 @@ public partial class CoreConfigV2rayService
|
||||
finalmask = new Finalmask4Ray
|
||||
{
|
||||
tcp = [fragmentMask],
|
||||
udp = [noiseMask],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var outbound in actOutboundList)
|
||||
@@ -894,11 +915,28 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
|
||||
private (Mask4Ray tcpFragment, Mask4Ray udpNoise) BuildFragmentsMasks()
|
||||
private Mask4Ray BuildFragmentsMasks()
|
||||
{
|
||||
var configPackets = _config.Fragment4RayItem?.Packets ?? "tlshello";
|
||||
var configLength = _config.Fragment4RayItem?.Length ?? "50-100";
|
||||
var configDelay = _config.Fragment4RayItem?.Interval ?? "10-20";
|
||||
var configPackets = _config.Fragment4RayItem?.Packets.NullIfEmpty() ?? "tlshello";
|
||||
var configLengths = _config.Fragment4RayItem?.Lengths ?? [];
|
||||
var configDelays = _config.Fragment4RayItem?.Delays ?? [];
|
||||
var configMaxSplit = _config.Fragment4RayItem?.MaxSplit.NullIfEmpty() ?? "0";
|
||||
|
||||
if (configLengths.Count == 0)
|
||||
{
|
||||
configLengths = ["50-100"];
|
||||
}
|
||||
if (configDelays.Count == 0)
|
||||
{
|
||||
configDelays = ["10-20"];
|
||||
}
|
||||
|
||||
var maxSplit = 0;
|
||||
var parts = configMaxSplit.Split('-');
|
||||
if (parts.Length > 0 && int.TryParse(parts[0], out var ms))
|
||||
{
|
||||
maxSplit = ms;
|
||||
}
|
||||
|
||||
var fragmentMask = new Mask4Ray
|
||||
{
|
||||
@@ -906,20 +944,15 @@ public partial class CoreConfigV2rayService
|
||||
settings = new MaskSettings4Ray
|
||||
{
|
||||
packets = configPackets,
|
||||
length = configLength,
|
||||
delay = configDelay,
|
||||
}
|
||||
};
|
||||
var noiseMask = new Mask4Ray
|
||||
{
|
||||
type = "noise",
|
||||
settings = new MaskSettings4Ray
|
||||
{
|
||||
length = "10-20",
|
||||
delay = "10-16",
|
||||
}
|
||||
lengths = configLengths,
|
||||
delays = configDelays,
|
||||
maxSplit = maxSplit,
|
||||
// For legacy xray compatibility, remove this in the future
|
||||
length = configLengths.FirstOrDefault(),
|
||||
delay = configDelays.FirstOrDefault(),
|
||||
},
|
||||
};
|
||||
|
||||
return (fragmentMask, noiseMask);
|
||||
return fragmentMask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,21 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
_coreConfig.routing.rules.AddRange(tunRules);
|
||||
}
|
||||
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
var lstDirectExe = BuildRoutingDirectExe();
|
||||
if (lstDirectExe.Count > 0)
|
||||
{
|
||||
port = "53",
|
||||
process = lstDnsExe,
|
||||
outboundTag = Global.DnsOutboundTag,
|
||||
});
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
process = lstDirectExe,
|
||||
outboundTag = Global.DirectTag,
|
||||
});
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
port = "53",
|
||||
process = lstDirectExe,
|
||||
outboundTag = Global.DnsOutboundTag,
|
||||
});
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
process = lstDirectExe,
|
||||
outboundTag = Global.DirectTag,
|
||||
});
|
||||
}
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
inboundTag = ["tun"],
|
||||
@@ -232,36 +235,44 @@ public partial class CoreConfigV2rayService
|
||||
return finalRule;
|
||||
}
|
||||
|
||||
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
|
||||
private List<string> BuildRoutingDirectExe()
|
||||
{
|
||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
|
||||
var allCoreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||
|
||||
foreach (var coreConfig in coreInfoResult)
|
||||
foreach (var coreConfig in allCoreInfo)
|
||||
{
|
||||
if (!context.ProtectCoreTypeList.Contains(coreConfig.CoreType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (coreConfig.CoreExes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (coreConfig.CoreType == ECoreType.Xray)
|
||||
{
|
||||
directExeSet.Add("xray/");
|
||||
continue;
|
||||
}
|
||||
foreach (var baseExeName in coreConfig.CoreExes)
|
||||
{
|
||||
if (coreConfig.CoreType != ECoreType.Xray)
|
||||
//directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
var exePath = CoreInfoManager.Instance.GetCoreExecFile(coreConfig, out _);
|
||||
if (!exePath.IsNullOrEmpty())
|
||||
{
|
||||
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
directExeSet.Add(exePath);
|
||||
}
|
||||
directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
}
|
||||
|
||||
directExeSet.Add("xray/");
|
||||
//directExeSet.Add("xray/");
|
||||
directExeSet.Add("self/");
|
||||
|
||||
var lstDnsExe = new List<string>(dnsExeSet);
|
||||
var lstDirectExe = new List<string>(directExeSet);
|
||||
|
||||
return (lstDnsExe, lstDirectExe);
|
||||
return directExeSet.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,13 @@ public class DownloadService
|
||||
AllowAutoRedirect = false,
|
||||
Proxy = await GetWebProxy(blProxy)
|
||||
};
|
||||
var client = new HttpClient(webRequestHandler);
|
||||
var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy();
|
||||
if (certificateChainPolicy != null)
|
||||
{
|
||||
webRequestHandler.SslOptions.CertificateChainPolicy = certificateChainPolicy;
|
||||
webRequestHandler.SslOptions.RemoteCertificateValidationCallback = null;
|
||||
}
|
||||
using var client = new HttpClient(webRequestHandler);
|
||||
|
||||
var response = await client.GetAsync(url);
|
||||
if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null)
|
||||
@@ -109,9 +115,10 @@ public class DownloadService
|
||||
/// </summary>
|
||||
public async Task<string?> TryDownloadString(string url, IWebProxy? webProxy, string userAgent)
|
||||
{
|
||||
var timeout = 15;
|
||||
try
|
||||
{
|
||||
var result1 = await DownloadStringAsync(url, webProxy, userAgent, 15);
|
||||
var result1 = await DownloadStringAsync(url, webProxy, userAgent, timeout);
|
||||
if (result1.IsNotEmpty())
|
||||
{
|
||||
return result1;
|
||||
@@ -129,7 +136,7 @@ public class DownloadService
|
||||
|
||||
try
|
||||
{
|
||||
var result2 = await DownloadStringViaDownloader(url, webProxy, userAgent, 15);
|
||||
var result2 = await DownloadStringViaDownloader(url, webProxy, userAgent, timeout);
|
||||
if (result2.IsNotEmpty())
|
||||
{
|
||||
return result2;
|
||||
@@ -155,11 +162,24 @@ public class DownloadService
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = new HttpClient(new SocketsHttpHandler()
|
||||
var connectTimeout = Math.Clamp(timeout / 5, 2, 5);
|
||||
var handler = new SocketsHttpHandler
|
||||
{
|
||||
Proxy = webProxy,
|
||||
UseProxy = webProxy != null
|
||||
});
|
||||
UseProxy = webProxy != null,
|
||||
ConnectTimeout = TimeSpan.FromSeconds(connectTimeout)
|
||||
};
|
||||
var certificateChainPolicy = CertPemManager.Instance.BuildCertificateChainPolicy();
|
||||
if (certificateChainPolicy != null)
|
||||
{
|
||||
handler.SslOptions.CertificateChainPolicy = certificateChainPolicy;
|
||||
handler.SslOptions.RemoteCertificateValidationCallback = null;
|
||||
}
|
||||
|
||||
using var client = new HttpClient(handler)
|
||||
{
|
||||
Timeout = Timeout.InfiniteTimeSpan
|
||||
};
|
||||
|
||||
if (userAgent.IsNullOrEmpty())
|
||||
{
|
||||
@@ -175,8 +195,9 @@ public class DownloadService
|
||||
}
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
var result = await client.GetStringAsync(url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
|
||||
return result;
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
|
||||
|
||||
return await client.GetStringAsync(url, cts.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -187,6 +208,7 @@ public class DownloadService
|
||||
Error?.Invoke(this, new ErrorEventArgs(ex.InnerException));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
switch (actionType)
|
||||
{
|
||||
case ESpeedActionType.Tcping:
|
||||
await RunTcpingAsync(lstSelected);
|
||||
await RunTcpingAsync(lstSelected, exitLoopKey);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Realping:
|
||||
@@ -135,16 +135,28 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
return lstSelected;
|
||||
}
|
||||
|
||||
private async Task RunTcpingAsync(List<ServerTestItem> selecteds)
|
||||
private async Task RunTcpingAsync(List<ServerTestItem> selecteds, string exitLoopKey)
|
||||
{
|
||||
var pageSize = Math.Min(selecteds.Count, _speedTestPageSize);
|
||||
var lstBatch = GetTestBatchItem(selecteds, pageSize);
|
||||
|
||||
foreach (var lst in lstBatch)
|
||||
{
|
||||
if (ShouldStopTest(exitLoopKey))
|
||||
{
|
||||
await UpdateFunc("", ResUI.SpeedtestingSkip);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Task> tasks = [];
|
||||
|
||||
foreach (var it in lst)
|
||||
{
|
||||
if (ShouldStopTest(exitLoopKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
@@ -160,7 +172,14 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
if (ShouldStopTest(exitLoopKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(_delayInterval);
|
||||
}
|
||||
}
|
||||
@@ -401,12 +420,12 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
private async Task<int> DoRealPing(ServerTestItem it)
|
||||
{
|
||||
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
|
||||
var responseTime = await ConnectionHandler.GetRealPingTime(webProxy, 10);
|
||||
var responseTime = await ConnectionHandler.GetRealPingTime(webProxy);
|
||||
|
||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||
await UpdateFunc(it.IndexId, responseTime.ToString());
|
||||
|
||||
if (responseTime > 0)
|
||||
if (!_config.UiItem.HideColumnIpInfo && responseTime > 0)
|
||||
{
|
||||
var ipInfo = await ConnectionHandler.GetIPInfo(webProxy);
|
||||
var ipStr = ipInfo?.ToString() ?? Global.None;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddGroupServerViewModel : MyReactiveObject
|
||||
public class AddGroupServerViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
|
||||
@@ -29,7 +31,7 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
|
||||
public IObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
||||
|
||||
//public ReactiveCommand<Unit, Unit> AddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoveCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
||||
@@ -39,15 +41,18 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public AddGroupServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public AddGroupServerViewModel(ProfileItem profileItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
x => x.SelectedChild,
|
||||
SelectedChild => SelectedChild != null && !SelectedChild.Remarks.IsNullOrEmpty());
|
||||
selectedChild => selectedChild != null && !selectedChild.Remarks.IsNullOrEmpty());
|
||||
|
||||
AddCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddChildAsync();
|
||||
});
|
||||
RemoveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await ChildRemoveAsync();
|
||||
@@ -103,6 +108,20 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
ChildItemsObs.AddRange(childItemList);
|
||||
}
|
||||
|
||||
public async Task AddChildAsync()
|
||||
{
|
||||
var profileSelectViewModel = new ProfilesSelectViewModel();
|
||||
profileSelectViewModel.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
|
||||
profileSelectViewModel.MultiSelect = true;
|
||||
var result = await AppManager.Instance.WindowDialog.ShowDialogAsync(profileSelectViewModel);
|
||||
if (result != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var profiles = await profileSelectViewModel.GetProfileItems() ?? [];
|
||||
ChildItemsObs.AddRange(profiles);
|
||||
}
|
||||
|
||||
public async Task ChildRemoveAsync()
|
||||
{
|
||||
if (SelectedChild == null || SelectedChild.IndexId.IsNullOrEmpty())
|
||||
@@ -230,7 +249,7 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddServer2ViewModel : MyReactiveObject
|
||||
public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public Interaction<Unit, string?> BrowseConfigFileInteraction { get; } = new();
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
|
||||
@@ -13,15 +17,18 @@ public class AddServer2ViewModel : MyReactiveObject
|
||||
public ReactiveCommand<Unit, Unit> SaveServerCmd { get; }
|
||||
public bool IsModified { get; set; }
|
||||
|
||||
public AddServer2ViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public AddServer2ViewModel(ProfileItem profileItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.BrowseServer, null);
|
||||
await Task.CompletedTask;
|
||||
var fileName = await BrowseConfigFileInteraction.Handle(Unit.Default);
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
await BrowseServer(fileName);
|
||||
});
|
||||
EditServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -55,7 +62,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddServerViewModel : MyReactiveObject
|
||||
public class AddServerViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
|
||||
@@ -80,6 +82,18 @@ public class AddServerViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool NaiveQuic { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string HttpHeadersJson { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Hy2RealmUrl { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int GeckoMinPacketSize { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int GeckoMaxPacketSize { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RawHeaderType { get; set; }
|
||||
|
||||
@@ -229,10 +243,9 @@ public class AddServerViewModel : MyReactiveObject
|
||||
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public AddServerViewModel(ProfileItem profileItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
FetchCertCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -246,7 +259,6 @@ public class AddServerViewModel : MyReactiveObject
|
||||
{
|
||||
await SaveServerAsync();
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.Cert)
|
||||
.Subscribe(_ => UpdateCertTip());
|
||||
|
||||
@@ -302,6 +314,10 @@ public class AddServerViewModel : MyReactiveObject
|
||||
CongestionControl = protocolExtra.CongestionControl ?? string.Empty;
|
||||
InsecureConcurrency = protocolExtra.InsecureConcurrency > 0 ? protocolExtra.InsecureConcurrency : null;
|
||||
NaiveQuic = protocolExtra.NaiveQuic ?? false;
|
||||
HttpHeadersJson = protocolExtra.HttpHeaders ?? string.Empty;
|
||||
Hy2RealmUrl = protocolExtra.Hy2RealmUrl ?? string.Empty;
|
||||
GeckoMinPacketSize = protocolExtra.GeckoMinPacketSize.ToInt();
|
||||
GeckoMaxPacketSize = protocolExtra.GeckoMaxPacketSize.ToInt();
|
||||
|
||||
RawHeaderType = transport.RawHeaderType ?? Global.None;
|
||||
Host = transport.Host ?? string.Empty;
|
||||
@@ -357,6 +373,21 @@ public class AddServerViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
}
|
||||
HyRealm? realm = null;
|
||||
if (!Hy2RealmUrl.IsNullOrEmpty())
|
||||
{
|
||||
var realmResult = HyRealm.TryParse(Hy2RealmUrl, out realm);
|
||||
if (!realmResult)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.InvalidHy2RealmUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (HttpHeadersJson.IsNotEmpty() && JsonUtils.ParseJson(HttpHeadersJson) == null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.InvalidHttpOutboundHeaders);
|
||||
return;
|
||||
}
|
||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : Enum.Parse<ECoreType>(CoreType);
|
||||
SelectedSource.AllowInsecure = AllowInsecure ? Global.StringTrue : Global.StringFalse;
|
||||
SelectedSource.MuxEnabled = MuxEnabled;
|
||||
@@ -394,6 +425,7 @@ public class AddServerViewModel : MyReactiveObject
|
||||
VmessSecurity = VmessSecurity.NullIfEmpty(),
|
||||
VlessEncryption = VlessEncryption.NullIfEmpty(),
|
||||
SsMethod = SsMethod.NullIfEmpty(),
|
||||
HttpHeaders = SelectedSource.ConfigType == EConfigType.HTTP ? HttpHeadersJson.NullIfEmpty() : null,
|
||||
WgPublicKey = WgPublicKey.NullIfEmpty(),
|
||||
WgPresharedKey = WgPresharedKey.NullIfEmpty(),
|
||||
WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(),
|
||||
@@ -403,13 +435,16 @@ public class AddServerViewModel : MyReactiveObject
|
||||
CongestionControl = CongestionControl.NullIfEmpty(),
|
||||
InsecureConcurrency = InsecureConcurrency > 0 ? InsecureConcurrency : null,
|
||||
NaiveQuic = NaiveQuic ? true : null,
|
||||
Hy2RealmUrl = realm?.ToUri().NullIfEmpty(),
|
||||
GeckoMinPacketSize = GeckoMinPacketSize > 0 ? GeckoMinPacketSize.ToString() : null,
|
||||
GeckoMaxPacketSize = GeckoMaxPacketSize > 0 ? GeckoMaxPacketSize.ToString() : null,
|
||||
});
|
||||
SelectedSource.SetTransportExtra(transport);
|
||||
|
||||
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -13,12 +13,11 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
||||
public WebDavItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string OperationMsg { get; set; }
|
||||
public string OperationMsg { get; set; } = string.Empty;
|
||||
|
||||
public BackupAndRestoreViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public BackupAndRestoreViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
|
||||
@@ -7,15 +7,16 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
private List<CheckUpdateModel> _lstUpdated = [];
|
||||
private static readonly string _tag = "CheckUpdateViewModel";
|
||||
|
||||
public EventChannel<Unit> ReloadRequested { get; } = new();
|
||||
|
||||
public IObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = new ObservableCollectionExtended<CheckUpdateModel>();
|
||||
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> CheckOnlyCmd { get; }
|
||||
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
|
||||
|
||||
public CheckUpdateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public CheckUpdateViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
CheckUpdateCmd = ReactiveCommand.CreateFromTask(CheckUpdate);
|
||||
CheckUpdateCmd.ThrownExceptions.Subscribe(ex =>
|
||||
@@ -43,14 +44,14 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
|
||||
private void RefreshCheckUpdateItems()
|
||||
{
|
||||
var models = CoreInfoManager.Instance.GetCheckUpdateCoreTypes()
|
||||
.Select(t => GetCheckUpdateModel(t))
|
||||
.ToList();
|
||||
|
||||
models.Add(GetGeoFileCheckUpdateModel());
|
||||
|
||||
CheckUpdateModels.Clear();
|
||||
|
||||
foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes())
|
||||
{
|
||||
CheckUpdateModels.Add(GetCheckUpdateModel(type));
|
||||
}
|
||||
|
||||
CheckUpdateModels.Add(GetGeoFileCheckUpdateModel());
|
||||
CheckUpdateModels.AddRange(models);
|
||||
}
|
||||
|
||||
private CheckUpdateModel GetCheckUpdateModel(ECoreType coreType)
|
||||
@@ -299,7 +300,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
{
|
||||
if (blReload)
|
||||
{
|
||||
AppEvents.ReloadRequested.Publish();
|
||||
ReloadRequested.Publish();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -16,10 +16,9 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
|
||||
public ClashConnectionsViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ClashConnectionsViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
|
||||
@@ -33,10 +33,9 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
|
||||
public ClashProxiesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ClashProxiesViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -86,15 +85,6 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
|
||||
#endregion WhenAnyValue && ReactiveCommand
|
||||
|
||||
#region AppEvents
|
||||
|
||||
AppEvents.ProxiesReloadRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await ProxiesReload());
|
||||
|
||||
#endregion AppEvents
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
@@ -187,6 +177,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
var selectedName = SelectedGroup?.Name;
|
||||
ProxyGroups.Clear();
|
||||
|
||||
var lstProxyGroups = new List<ClashProxyModel>();
|
||||
var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
|
||||
if (proxyGroups is { Count: > 0 })
|
||||
{
|
||||
@@ -200,7 +191,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ProxyGroups.Add(new ClashProxyModel()
|
||||
lstProxyGroups.Add(new ClashProxyModel()
|
||||
{
|
||||
Now = item.now,
|
||||
Name = item.name,
|
||||
@@ -216,12 +207,12 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = ProxyGroups.FirstOrDefault(t => t.Name == kv.Key);
|
||||
var item = lstProxyGroups.FirstOrDefault(t => t.Name == kv.Key);
|
||||
if (item != null && item.Name.IsNotEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ProxyGroups.Add(new ClashProxyModel()
|
||||
lstProxyGroups.Add(new ClashProxyModel()
|
||||
{
|
||||
Now = kv.Value.now,
|
||||
Name = kv.Key,
|
||||
@@ -229,6 +220,8 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
});
|
||||
}
|
||||
|
||||
ProxyGroups.AddRange(lstProxyGroups);
|
||||
|
||||
if (ProxyGroups is { Count: > 0 })
|
||||
{
|
||||
if (selectedName != null && ProxyGroups.Any(t => t.Name == selectedName))
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class DNSSettingViewModel : MyReactiveObject
|
||||
public class DNSSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
[Reactive] public bool? UseSystemHosts { get; set; }
|
||||
[Reactive] public bool? AddCommonHosts { get; set; }
|
||||
[Reactive] public bool? FakeIP { get; set; }
|
||||
[Reactive] public bool? BlockBindingQuery { get; set; }
|
||||
[Reactive] public string? DirectDNS { get; set; }
|
||||
[Reactive] public string? RemoteDNS { get; set; }
|
||||
[Reactive] public string? BootstrapDNS { get; set; }
|
||||
[Reactive] public string? Strategy4Freedom { get; set; }
|
||||
[Reactive] public string? Strategy4Proxy { get; set; }
|
||||
[Reactive] public string? Hosts { get; set; }
|
||||
[Reactive] public string? DirectExpectedIPs { get; set; }
|
||||
[Reactive] public bool? ParallelQuery { get; set; }
|
||||
[Reactive] public bool? ServeStale { get; set; }
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive] public bool UseSystemHosts { get; set; }
|
||||
[Reactive] public bool AddCommonHosts { get; set; }
|
||||
[Reactive] public bool FakeIP { get; set; }
|
||||
[Reactive] public string FakeIPRange { get; set; }
|
||||
[Reactive] public bool BlockBindingQuery { get; set; }
|
||||
[Reactive] public string DirectDNS { get; set; }
|
||||
[Reactive] public string RemoteDNS { get; set; }
|
||||
[Reactive] public string BootstrapDNS { get; set; }
|
||||
[Reactive] public string Strategy4Freedom { get; set; }
|
||||
[Reactive] public string Strategy4Proxy { get; set; }
|
||||
[Reactive] public string Strategy4ProxyDial { get; set; }
|
||||
[Reactive] public string Hosts { get; set; }
|
||||
[Reactive] public string DirectExpectedIPs { get; set; }
|
||||
[Reactive] public bool ParallelQuery { get; set; }
|
||||
[Reactive] public bool ServeStale { get; set; }
|
||||
[Reactive] public bool EnableHappyEyeballs { get; set; }
|
||||
|
||||
[Reactive] public bool UseSystemHostsCompatible { get; set; }
|
||||
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; }
|
||||
[Reactive] public string DomainDNSAddressCompatible { get; set; }
|
||||
[Reactive] public string NormalDNSCompatible { get; set; }
|
||||
[Reactive] public string TunDNSCompatible { get; set; }
|
||||
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public string DomainDNSAddressCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public string NormalDNSCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public string TunDNSCompatible { get; set; } = string.Empty;
|
||||
|
||||
[Reactive] public string DomainStrategy4Freedom2Compatible { get; set; }
|
||||
[Reactive] public string DomainDNSAddress2Compatible { get; set; }
|
||||
[Reactive] public string NormalDNS2Compatible { get; set; }
|
||||
[Reactive] public string TunDNS2Compatible { get; set; }
|
||||
[Reactive] public string DomainStrategy4Freedom2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public string DomainDNSAddress2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public string NormalDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public string TunDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public bool RayCustomDNSEnableCompatible { get; set; }
|
||||
[Reactive] public bool SBCustomDNSEnableCompatible { get; set; }
|
||||
|
||||
@@ -35,10 +40,9 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4V2rayCompatibleCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4SingboxCompatibleCmd { get; }
|
||||
|
||||
public DNSSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public DNSSettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync);
|
||||
|
||||
ImportDefConfig4V2rayCompatibleCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
@@ -66,20 +70,22 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
var item = _config.SimpleDNSItem;
|
||||
UseSystemHosts = item.UseSystemHosts;
|
||||
AddCommonHosts = item.AddCommonHosts;
|
||||
FakeIP = item.FakeIP;
|
||||
BlockBindingQuery = item.BlockBindingQuery;
|
||||
DirectDNS = item.DirectDNS;
|
||||
RemoteDNS = item.RemoteDNS;
|
||||
BootstrapDNS = item.BootstrapDNS;
|
||||
Strategy4Freedom = item.Strategy4Freedom;
|
||||
Strategy4Proxy = item.Strategy4Proxy;
|
||||
Hosts = item.Hosts;
|
||||
DirectExpectedIPs = item.DirectExpectedIPs;
|
||||
ParallelQuery = item.ParallelQuery;
|
||||
ServeStale = item.ServeStale;
|
||||
|
||||
UseSystemHosts = item.UseSystemHosts ?? false;
|
||||
AddCommonHosts = item.AddCommonHosts ?? false;
|
||||
FakeIP = item.FakeIP ?? false;
|
||||
FakeIPRange = item.FakeIPRange ?? string.Empty;
|
||||
BlockBindingQuery = item.BlockBindingQuery ?? false;
|
||||
DirectDNS = item.DirectDNS ?? string.Empty;
|
||||
RemoteDNS = item.RemoteDNS ?? string.Empty;
|
||||
BootstrapDNS = item.BootstrapDNS ?? string.Empty;
|
||||
Strategy4Freedom = item.Strategy4Freedom ?? string.Empty;
|
||||
Strategy4Proxy = item.Strategy4Proxy ?? string.Empty;
|
||||
Strategy4ProxyDial = item.Strategy4ProxyDial ?? string.Empty;
|
||||
Hosts = item.Hosts ?? string.Empty;
|
||||
DirectExpectedIPs = item.DirectExpectedIPs ?? string.Empty;
|
||||
ParallelQuery = item.ParallelQuery ?? false;
|
||||
ServeStale = item.ServeStale ?? false;
|
||||
EnableHappyEyeballs = item.EnableHappyEyeballs ?? false;
|
||||
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
RayCustomDNSEnableCompatible = item1.Enabled;
|
||||
UseSystemHostsCompatible = item1.UseSystemHosts;
|
||||
@@ -101,17 +107,19 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
_config.SimpleDNSItem.UseSystemHosts = UseSystemHosts;
|
||||
_config.SimpleDNSItem.AddCommonHosts = AddCommonHosts;
|
||||
_config.SimpleDNSItem.FakeIP = FakeIP;
|
||||
_config.SimpleDNSItem.FakeIPRange = FakeIPRange;
|
||||
_config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery;
|
||||
_config.SimpleDNSItem.DirectDNS = DirectDNS;
|
||||
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
|
||||
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
|
||||
_config.SimpleDNSItem.Strategy4Freedom = Strategy4Freedom;
|
||||
_config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy;
|
||||
_config.SimpleDNSItem.Strategy4ProxyDial = Strategy4ProxyDial;
|
||||
_config.SimpleDNSItem.Hosts = Hosts;
|
||||
_config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs;
|
||||
_config.SimpleDNSItem.ParallelQuery = ParallelQuery;
|
||||
_config.SimpleDNSItem.ServeStale = ServeStale;
|
||||
|
||||
_config.SimpleDNSItem.EnableHappyEyeballs = EnableHappyEyeballs;
|
||||
if (NormalDNSCompatible.IsNotEmpty())
|
||||
{
|
||||
var obj = JsonUtils.ParseJson(NormalDNSCompatible);
|
||||
@@ -183,9 +191,6 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
await ConfigHandler.SaveDNSItems(_config, item2);
|
||||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
if (_updateView != null)
|
||||
{
|
||||
await _updateView(EViewAction.CloseWindow, null);
|
||||
}
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
public class FullConfigTemplateViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
#region Reactive
|
||||
|
||||
[Reactive]
|
||||
@@ -11,16 +13,16 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
public bool EnableFullConfigTemplate4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string FullConfigTemplate4Ray { get; set; }
|
||||
public string FullConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullTunConfigTemplate4Ray { get; set; }
|
||||
public string FullTunConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullConfigTemplate4Singbox { get; set; }
|
||||
public string FullConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullTunConfigTemplate4Singbox { get; set; }
|
||||
public string FullTunConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public bool AddProxyOnly4Ray { get; set; }
|
||||
@@ -29,19 +31,18 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
public bool AddProxyOnly4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string ProxyDetour4Ray { get; set; }
|
||||
public string ProxyDetour4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string ProxyDetour4Singbox { get; set; }
|
||||
public string ProxyDetour4Singbox { get; set; } = string.Empty;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
#endregion Reactive
|
||||
|
||||
public FullConfigTemplateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public FullConfigTemplateViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveSettingAsync();
|
||||
@@ -84,7 +85,7 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
}
|
||||
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_ = _updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task<bool> SaveXrayConfigAsync()
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class GlobalHotkeySettingViewModel : MyReactiveObject
|
||||
public class GlobalHotkeySettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
private readonly List<KeyEventItem> _globalHotkeys;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public GlobalHotkeySettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public GlobalHotkeySettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
_globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys);
|
||||
|
||||
@@ -51,7 +52,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject
|
||||
|
||||
if (await ConfigHandler.SaveConfig(_config) == 0)
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,6 +4,21 @@ namespace ServiceLib.ViewModels;
|
||||
|
||||
public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<Unit, string?> ReadTextFromClipboardInteraction { get; } = new();
|
||||
public Interaction<Unit, byte[]?> ScanScreenInteraction { get; } = new();
|
||||
public Interaction<Unit, string?> BrowseImageFileInteraction { get; } = new();
|
||||
public Interaction<bool?, Unit> ShowHideWindowInteraction { get; } = new();
|
||||
|
||||
public bool DesignMode { get; set; }
|
||||
|
||||
public ProfilesViewModel ProfilesViewModel { get; } = new();
|
||||
public MsgViewModel MsgViewModel { get; } = new();
|
||||
public ClashProxiesViewModel ClashProxiesViewModel { get; } = new();
|
||||
public ClashConnectionsViewModel ClashConnectionsViewModel { get; } = new();
|
||||
public CheckUpdateViewModel CheckUpdateViewModel { get; } = new();
|
||||
public BackupAndRestoreViewModel BackupAndRestoreViewModel { get; } = new();
|
||||
public StatusBarViewModel StatusBarViewModel { get; } = StatusBarViewModel.Instance;
|
||||
|
||||
#region Menu
|
||||
|
||||
//servers
|
||||
@@ -67,15 +82,17 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
[Reactive] public bool BlNewUpdate { get; set; }
|
||||
|
||||
[Reactive] public EGirdOrientation MainGirdOrientation { get; set; }
|
||||
|
||||
#endregion Menu
|
||||
|
||||
#region Init
|
||||
|
||||
public MainWindowViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
BlIsWindows = Utils.IsWindows();
|
||||
MainGirdOrientation = _config.UiItem.MainGirdOrientation;
|
||||
|
||||
#region WhenAnyValue && ReactiveCommand
|
||||
|
||||
@@ -191,7 +208,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
});
|
||||
GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
|
||||
var globalHotkeySettingViewModel = new GlobalHotkeySettingViewModel();
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(globalHotkeySettingViewModel) == true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
}
|
||||
@@ -233,26 +251,11 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
#region AppEvents
|
||||
|
||||
AppEvents.ReloadRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await Reload());
|
||||
|
||||
AppEvents.AddServerViaScanRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaScanAsync());
|
||||
|
||||
AppEvents.AddServerViaClipboardRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
|
||||
|
||||
AppEvents.SubscriptionsUpdateRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
|
||||
|
||||
AppEvents.HasUpdateNotified
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
@@ -260,6 +263,53 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
#endregion AppEvents
|
||||
|
||||
ProfilesViewModel.RefreshServersRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await RefreshServers());
|
||||
|
||||
var vmReloadRequestedList = new List<IObservable<Unit>>
|
||||
{
|
||||
ProfilesViewModel.ReloadRequested.AsObservable(),
|
||||
StatusBarViewModel.ReloadRequested.AsObservable(),
|
||||
CheckUpdateViewModel.ReloadRequested.AsObservable(),
|
||||
};
|
||||
|
||||
foreach (var reloadRequested in vmReloadRequestedList)
|
||||
{
|
||||
reloadRequested
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await Reload());
|
||||
}
|
||||
|
||||
StatusBarViewModel.AddServerViaScanRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaScanAsync());
|
||||
|
||||
StatusBarViewModel.AddServerViaClipboardRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
|
||||
|
||||
StatusBarViewModel.ShowHideWindowRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async blShow =>
|
||||
{
|
||||
await ShowHideWindowInteraction.Handle(blShow);
|
||||
});
|
||||
|
||||
StatusBarViewModel.SetDefaultServerRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async indexId => await ProfilesViewModel.SetDefaultServer(indexId));
|
||||
|
||||
StatusBarViewModel.SubscriptionsUpdateRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
@@ -267,18 +317,24 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
AppManager.Instance.ShowInTaskbar = true;
|
||||
|
||||
if (DesignMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//await ConfigHandler.InitBuiltinRouting(_config);
|
||||
await ConfigHandler.InitBuiltinDNS(_config);
|
||||
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
|
||||
await ProfileExManager.Instance.Init();
|
||||
await CoreManager.Instance.Init(_config, UpdateHandler);
|
||||
await CertPemManager.Instance.Init(_config);
|
||||
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);
|
||||
|
||||
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
||||
{
|
||||
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
|
||||
}
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
|
||||
await Reload();
|
||||
}
|
||||
@@ -303,7 +359,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
if (success)
|
||||
{
|
||||
var indexIdOld = _config.IndexId;
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
|
||||
// If indexId changed or subIndexId is empty, directly reload.
|
||||
if (indexIdOld != _config.IndexId || _config.SubIndexId.IsNullOrEmpty())
|
||||
@@ -322,7 +378,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
|
||||
{
|
||||
AppEvents.AdjustMainLvColWidthRequested.Publish();
|
||||
await ProfilesViewModel.AdjustMainLvColWidth();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,14 +399,20 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task RefreshServers()
|
||||
{
|
||||
AppEvents.ProfilesRefreshRequested.Publish();
|
||||
await ProfilesViewModel.RefreshServersBiz();
|
||||
await StatusBarViewModel.RefreshServersBiz();
|
||||
|
||||
await Task.Delay(200);
|
||||
// await Task.Delay(200);
|
||||
}
|
||||
|
||||
private void RefreshSubscriptions()
|
||||
private async Task RefreshServersDispatcherAsync()
|
||||
{
|
||||
AppEvents.SubscriptionsRefreshRequested.Publish();
|
||||
await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
||||
}
|
||||
|
||||
private async Task RefreshSubscriptions()
|
||||
{
|
||||
await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
||||
}
|
||||
|
||||
#endregion Servers && Groups
|
||||
@@ -369,19 +431,22 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
bool? ret = false;
|
||||
if (eConfigType == EConfigType.Custom)
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item);
|
||||
var addServer2ViewModel = new AddServer2ViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
||||
}
|
||||
else if (eConfigType.IsGroupType())
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddGroupServerWindow, item);
|
||||
var addGroupServerViewModel = new AddGroupServerViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addGroupServerViewModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item);
|
||||
var addServerViewModel = new AddServerViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServerViewModel);
|
||||
}
|
||||
if (ret == true)
|
||||
{
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
if (item.IndexId == _config.IndexId)
|
||||
{
|
||||
await Reload();
|
||||
@@ -391,16 +456,22 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaClipboardAsync(string? clipboardData)
|
||||
{
|
||||
var stringData = clipboardData;
|
||||
if (clipboardData == null)
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null);
|
||||
return;
|
||||
var result = await ReadTextFromClipboardInteraction.Handle(Unit.Default);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
stringData = result;
|
||||
}
|
||||
var ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false);
|
||||
var ret = await ConfigHandler.AddBatchServers(_config, stringData, _config.SubIndexId, false);
|
||||
if (ret > 0)
|
||||
{
|
||||
RefreshSubscriptions();
|
||||
await RefreshServers();
|
||||
await RefreshSubscriptions();
|
||||
await RefreshServersDispatcherAsync();
|
||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
|
||||
}
|
||||
else
|
||||
@@ -411,8 +482,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaScanAsync()
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.ScanScreenTask, null);
|
||||
await Task.CompletedTask;
|
||||
var result = await ScanScreenInteraction.Handle(Unit.Default);
|
||||
await ScanScreenResult(result);
|
||||
}
|
||||
|
||||
public async Task ScanScreenResult(byte[]? bytes)
|
||||
@@ -423,8 +494,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaImageAsync()
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.ScanImageTask, null);
|
||||
await Task.CompletedTask;
|
||||
var imageFileName = await BrowseImageFileInteraction.Handle(Unit.Default);
|
||||
await AddScanResultAsync(imageFileName);
|
||||
}
|
||||
|
||||
public async Task ScanImageResult(string fileName)
|
||||
@@ -449,8 +520,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
var ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false);
|
||||
if (ret > 0)
|
||||
{
|
||||
RefreshSubscriptions();
|
||||
await RefreshServers();
|
||||
await RefreshSubscriptions();
|
||||
await RefreshServersDispatcherAsync();
|
||||
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
|
||||
}
|
||||
else
|
||||
@@ -466,9 +537,10 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task SubSettingAsync()
|
||||
{
|
||||
if (await _updateView?.Invoke(EViewAction.SubSettingWindow, null) == true)
|
||||
var subSettingViewModel = new SubSettingViewModel();
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(subSettingViewModel) == true)
|
||||
{
|
||||
RefreshSubscriptions();
|
||||
await RefreshSubscriptions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,28 +555,38 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task OptionSettingAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null);
|
||||
var settingViewModel = new OptionSettingViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(settingViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
AppEvents.InboundDisplayRequested.Publish();
|
||||
MainGirdOrientation = _config.UiItem.MainGirdOrientation;
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.InboundDisplayStatus();
|
||||
});
|
||||
await Reload();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RoutingSettingAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.RoutingSettingWindow, null);
|
||||
var routingSettingViewModel = new RoutingSettingViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(routingSettingViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
await ConfigHandler.InitBuiltinRouting(_config);
|
||||
AppEvents.RoutingsMenuRefreshRequested.Publish();
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.RefreshRoutingsMenu();
|
||||
});
|
||||
await Reload();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DNSSettingAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.DNSSettingWindow, null);
|
||||
var dnsSettingViewModel = new DNSSettingViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(dnsSettingViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
await Reload();
|
||||
@@ -513,7 +595,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task FullConfigTemplateAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.FullConfigTemplateWindow, null);
|
||||
var fullConfigTemplateViewModel = new FullConfigTemplateViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(fullConfigTemplateViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
await Reload();
|
||||
@@ -523,7 +606,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
private async Task ClearServerStatistics()
|
||||
{
|
||||
await StatisticsManager.Instance.ClearAllServerStatistics();
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
}
|
||||
|
||||
private async Task OpenTheFileLocation()
|
||||
@@ -560,6 +643,12 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
|
||||
if (DesignMode)
|
||||
{
|
||||
_reloadSemaphore.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SetReloadEnabled(false);
|
||||
@@ -582,12 +671,22 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
await SysProxyHandler.UpdateSysProxy(_config, false);
|
||||
await Task.Delay(1000);
|
||||
});
|
||||
AppEvents.TestServerRequested.Publish();
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.TestServerAvailability();
|
||||
});
|
||||
|
||||
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box);
|
||||
if (showClashUI)
|
||||
{
|
||||
AppEvents.ProxiesReloadRequested.Publish();
|
||||
//await Observable.Start(async () =>
|
||||
//{
|
||||
// await ClashProxiesViewModel.ProxiesReload();
|
||||
//}, RxSchedulers.MainThreadScheduler);
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await ClashProxiesViewModel.ProxiesReload();
|
||||
});
|
||||
}
|
||||
|
||||
ReloadResult(showClashUI);
|
||||
@@ -632,7 +731,10 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
await ConfigHandler.ApplyRegionalPreset(_config, type);
|
||||
await ConfigHandler.InitRouting(_config);
|
||||
AppEvents.RoutingsMenuRefreshRequested.Publish();
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.RefreshRoutingsMenu();
|
||||
});
|
||||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
await new UpdateService(_config, UpdateTaskHandler).UpdateGeoFileAll();
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace ServiceLib.ViewModels;
|
||||
|
||||
public class MsgViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, Unit> DispatcherShowMsgInteraction { get; } = new();
|
||||
|
||||
private readonly ConcurrentQueue<string> _queueMsg = new();
|
||||
private volatile bool _lastMsgFilterNotAvailable;
|
||||
private int _showLock = 0; // 0 = unlocked, 1 = locked
|
||||
@@ -13,10 +15,9 @@ public class MsgViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
|
||||
public MsgViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public MsgViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty;
|
||||
AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true;
|
||||
|
||||
@@ -64,7 +65,7 @@ public class MsgViewModel : MyReactiveObject
|
||||
sb.Append(line);
|
||||
}
|
||||
|
||||
await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString());
|
||||
await DispatcherShowMsgInteraction.Handle(sb.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class NetBridgeViewModel : MyReactiveObject
|
||||
{
|
||||
[Reactive]
|
||||
public bool EnableNetBridge { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool EnabletDnsViaProxy { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RuleProcess { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveRulesCmd { get; }
|
||||
|
||||
public NetBridgeViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
SaveRulesCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveRulesAsync();
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.EnableNetBridge)
|
||||
.Skip(1)
|
||||
.Subscribe(async enabled =>
|
||||
{
|
||||
await ToggleNetBridgeAsync(enabled);
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.EnabletDnsViaProxy)
|
||||
.Skip(1)
|
||||
.Subscribe(async enabled =>
|
||||
{
|
||||
await ToggleDnsViaProxyAsync(enabled);
|
||||
});
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
EnabletDnsViaProxy = _config.NetBridgeItem.EnableDnsViaProxy;
|
||||
|
||||
_config.NetBridgeItem ??= new()
|
||||
{
|
||||
RuleProcess = string.Empty
|
||||
};
|
||||
|
||||
EnableNetBridge = false;
|
||||
if (_config.NetBridgeItem.RuleProcess.IsNullOrEmpty())
|
||||
{
|
||||
_config.NetBridgeItem.RuleProcess = "Chrome.exe";
|
||||
}
|
||||
RuleProcess = _config.NetBridgeItem.RuleProcess;
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ToggleNetBridgeAsync(bool enabled)
|
||||
{
|
||||
await NetBridgeManager.Instance.Init(UpdateViewHandler);
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
var succeed = await NetBridgeManager.Instance.Start();
|
||||
if (succeed)
|
||||
{
|
||||
await NetBridgeManager.Instance.UpdateProxyConfig(Global.Loopback, AppManager.Instance.GetLocalPort(EInboundProtocol.socks));
|
||||
await NetBridgeManager.Instance.UpdateRoutes(RuleProcess);
|
||||
await NetBridgeManager.Instance.SetDnsViaProxy(EnabletDnsViaProxy);
|
||||
}
|
||||
NoticeManager.Instance.Enqueue(succeed ? ResUI.OperationSuccess : ResUI.OperationFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
var succeed = await NetBridgeManager.Instance.Stop();
|
||||
NoticeManager.Instance.Enqueue(succeed ? ResUI.OperationSuccess : ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ToggleDnsViaProxyAsync(bool enabled)
|
||||
{
|
||||
_config.NetBridgeItem.EnableDnsViaProxy = enabled;
|
||||
|
||||
await NetBridgeManager.Instance.SetDnsViaProxy(enabled);
|
||||
}
|
||||
|
||||
/// </summary>
|
||||
private async Task SaveRulesAsync()
|
||||
{
|
||||
_config.NetBridgeItem ??= new();
|
||||
|
||||
var normalizedRuleProcess = RuleProcess;
|
||||
_config.NetBridgeItem.RuleProcess = normalizedRuleProcess;
|
||||
RuleProcess = normalizedRuleProcess;
|
||||
|
||||
if (await ConfigHandler.SaveConfig(_config) != 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
await NetBridgeManager.Instance.Init(UpdateViewHandler);
|
||||
if (EnableNetBridge)
|
||||
{
|
||||
var routesUpdated = await NetBridgeManager.Instance.UpdateRoutes(normalizedRuleProcess);
|
||||
NoticeManager.Instance.Enqueue(routesUpdated ? ResUI.OperationSuccess : ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(bool isError, string msg)
|
||||
{
|
||||
NoticeManager.Instance.SendMessageEx(msg);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class OptionSettingViewModel : MyReactiveObject
|
||||
public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
#region Core
|
||||
|
||||
[Reactive] public int LocalPort { get; set; }
|
||||
@@ -26,21 +28,13 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
[Reactive] public int? HyDownMbps { get; set; }
|
||||
[Reactive] public bool EnableFragment { get; set; }
|
||||
[Reactive] public bool EnableFinalFragment { get; set; }
|
||||
[Reactive] public string FragmentPackets { get; set; }
|
||||
[Reactive] public string FragmentLengths { get; set; }
|
||||
[Reactive] public string FragmentDelays { get; set; }
|
||||
[Reactive] public string FragmentMaxSplit { get; set; }
|
||||
|
||||
#endregion Core
|
||||
|
||||
#region Core KCP
|
||||
|
||||
//[Reactive] public int Kcpmtu { get; set; }
|
||||
//[Reactive] public int Kcptti { get; set; }
|
||||
//[Reactive] public int KcpuplinkCapacity { get; set; }
|
||||
//[Reactive] public int KcpdownlinkCapacity { get; set; }
|
||||
//[Reactive] public int KcpreadBufferSize { get; set; }
|
||||
//[Reactive] public int KcpwriteBufferSize { get; set; }
|
||||
//[Reactive] public bool Kcpcongestion { get; set; }
|
||||
|
||||
#endregion Core KCP
|
||||
|
||||
#region UI
|
||||
|
||||
[Reactive] public bool AutoRun { get; set; }
|
||||
@@ -68,6 +62,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
[Reactive] public string SrsFileSourceUrl { get; set; }
|
||||
[Reactive] public string RoutingRulesSourceUrl { get; set; }
|
||||
[Reactive] public string IPAPIUrl { get; set; }
|
||||
[Reactive] public string RootCertProvider { get; set; }
|
||||
|
||||
#endregion UI
|
||||
|
||||
@@ -100,6 +95,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
[Reactive] public string TunIcmpRouting { get; set; }
|
||||
[Reactive] public bool TunEnableLegacyProtect { get; set; }
|
||||
[Reactive] public string TunRouteExcludeAddress { get; set; }
|
||||
[Reactive] public string TunIpv4Address { get; set; }
|
||||
[Reactive] public string TunIpv6Address { get; set; }
|
||||
|
||||
#endregion Tun mode
|
||||
|
||||
@@ -118,10 +115,9 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public OptionSettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
BlIsWindows = Utils.IsWindows();
|
||||
BlIsLinux = Utils.IsLinux();
|
||||
BlIsIsMacOS = Utils.IsMacOS();
|
||||
@@ -137,8 +133,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.InitSettingFont, null);
|
||||
|
||||
#region Core
|
||||
|
||||
var inbound = _config.Inbound.First();
|
||||
@@ -146,6 +140,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
SecondLocalPortEnabled = inbound.SecondLocalPortEnabled;
|
||||
UdpEnabled = inbound.UdpEnabled;
|
||||
SniffingEnabled = inbound.SniffingEnabled;
|
||||
DestOverride = inbound.DestOverride ?? [];
|
||||
RouteOnly = inbound.RouteOnly;
|
||||
AllowLANConn = inbound.AllowLANConn;
|
||||
NewPort4LAN = inbound.NewPort4LAN;
|
||||
@@ -163,21 +158,13 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
HyDownMbps = _config.HysteriaItem.DownMbps;
|
||||
EnableFragment = _config.CoreBasicItem.EnableFragment;
|
||||
EnableFinalFragment = _config.CoreBasicItem.EnableFinalFragment;
|
||||
FragmentPackets = _config.Fragment4RayItem?.Packets;
|
||||
FragmentLengths = Utils.List2String(_config.Fragment4RayItem?.Lengths);
|
||||
FragmentDelays = Utils.List2String(_config.Fragment4RayItem?.Delays);
|
||||
FragmentMaxSplit = _config.Fragment4RayItem?.MaxSplit;
|
||||
|
||||
#endregion Core
|
||||
|
||||
#region Core KCP
|
||||
|
||||
//Kcpmtu = _config.kcpItem.mtu;
|
||||
//Kcptti = _config.kcpItem.tti;
|
||||
//KcpuplinkCapacity = _config.kcpItem.uplinkCapacity;
|
||||
//KcpdownlinkCapacity = _config.kcpItem.downlinkCapacity;
|
||||
//KcpreadBufferSize = _config.kcpItem.readBufferSize;
|
||||
//KcpwriteBufferSize = _config.kcpItem.writeBufferSize;
|
||||
//Kcpcongestion = _config.kcpItem.congestion;
|
||||
|
||||
#endregion Core KCP
|
||||
|
||||
#region UI
|
||||
|
||||
AutoRun = _config.GuiItem.AutoRun;
|
||||
@@ -205,6 +192,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
SrsFileSourceUrl = _config.ConstItem.SrsSourceUrl;
|
||||
RoutingRulesSourceUrl = _config.ConstItem.RouteRulesTemplateSourceUrl;
|
||||
IPAPIUrl = _config.SpeedTestItem.IPAPIUrl;
|
||||
RootCertProvider = _config.GuiItem.RootCertProvider;
|
||||
|
||||
#endregion UI
|
||||
|
||||
@@ -228,6 +216,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
TunIcmpRouting = _config.TunModeItem.IcmpRouting;
|
||||
TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect;
|
||||
TunRouteExcludeAddress = Utils.List2String(_config.TunModeItem.RouteExcludeAddress, true);
|
||||
TunIpv4Address = _config.TunModeItem.Ipv4Address;
|
||||
TunIpv6Address = _config.TunModeItem.Ipv6Address;
|
||||
|
||||
#endregion Tun mode
|
||||
|
||||
@@ -300,41 +290,33 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
||||
return;
|
||||
}
|
||||
var sendThroughValue = SendThrough.TrimEx();
|
||||
if (sendThroughValue.IsNotEmpty() && !Utils.IsIpv4(sendThroughValue))
|
||||
var fragmentLengths = Utils.String2List(FragmentLengths) ?? [];
|
||||
var fragmentDelays = Utils.String2List(FragmentDelays) ?? [];
|
||||
if (fragmentLengths.Any(item => !Utils.TryParseRange(item, 0, int.MaxValue, out _, out _))
|
||||
|| fragmentDelays.Any(item => !Utils.TryParseRange(item, 0, int.MaxValue, out _, out _))
|
||||
|| (FragmentMaxSplit.IsNotEmpty() && !Utils.TryParseMaxSplit(FragmentMaxSplit, 0, 10000, out _, out _)))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillCorrectSendThroughIPv4);
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics
|
||||
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|
||||
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort
|
||||
|| EnableHWA != _config.GuiItem.EnableHWA
|
||||
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily
|
||||
|| MainGirdOrientation != (int)_config.UiItem.MainGirdOrientation;
|
||||
|
||||
//if (Utile.IsNullOrEmpty(Kcpmtu.ToString()) || !Utile.IsNumeric(Kcpmtu.ToString())
|
||||
// || Utile.IsNullOrEmpty(Kcptti.ToString()) || !Utile.IsNumeric(Kcptti.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpuplinkCapacity.ToString()) || !Utile.IsNumeric(KcpuplinkCapacity.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpdownlinkCapacity.ToString()) || !Utile.IsNumeric(KcpdownlinkCapacity.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpreadBufferSize.ToString()) || !Utile.IsNumeric(KcpreadBufferSize.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpwriteBufferSize.ToString()) || !Utile.IsNumeric(KcpwriteBufferSize.ToString()))
|
||||
//{
|
||||
// NoticeHandler.Instance.Enqueue(ResUI.FillKcpParameters);
|
||||
// return;
|
||||
//}
|
||||
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily;
|
||||
|
||||
//Core
|
||||
_config.Inbound.First().LocalPort = LocalPort;
|
||||
_config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled;
|
||||
_config.Inbound.First().UdpEnabled = UdpEnabled;
|
||||
_config.Inbound.First().SniffingEnabled = SniffingEnabled;
|
||||
_config.Inbound.First().DestOverride = DestOverride?.ToList();
|
||||
_config.Inbound.First().RouteOnly = RouteOnly;
|
||||
_config.Inbound.First().AllowLANConn = AllowLANConn;
|
||||
_config.Inbound.First().NewPort4LAN = NewPort4LAN;
|
||||
_config.Inbound.First().User = User;
|
||||
_config.Inbound.First().Pass = Pass;
|
||||
var inbound = _config.Inbound.First();
|
||||
inbound.LocalPort = LocalPort;
|
||||
inbound.SecondLocalPortEnabled = SecondLocalPortEnabled;
|
||||
inbound.UdpEnabled = UdpEnabled;
|
||||
inbound.SniffingEnabled = SniffingEnabled;
|
||||
inbound.DestOverride = DestOverride?.ToList();
|
||||
inbound.RouteOnly = RouteOnly;
|
||||
inbound.AllowLANConn = AllowLANConn;
|
||||
inbound.NewPort4LAN = NewPort4LAN;
|
||||
inbound.User = User;
|
||||
inbound.Pass = Pass;
|
||||
if (_config.Inbound.Count > 1)
|
||||
{
|
||||
_config.Inbound.RemoveAt(1);
|
||||
@@ -351,6 +333,11 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.HysteriaItem.DownMbps = HyDownMbps ?? 0;
|
||||
_config.CoreBasicItem.EnableFragment = EnableFragment;
|
||||
_config.CoreBasicItem.EnableFinalFragment = EnableFinalFragment;
|
||||
_config.Fragment4RayItem ??= new();
|
||||
_config.Fragment4RayItem.Packets = FragmentPackets;
|
||||
_config.Fragment4RayItem.Lengths = fragmentLengths;
|
||||
_config.Fragment4RayItem.Delays = fragmentDelays;
|
||||
_config.Fragment4RayItem.MaxSplit = FragmentMaxSplit;
|
||||
|
||||
_config.GuiItem.AutoRun = AutoRun;
|
||||
_config.GuiItem.EnableStatistics = EnableStatistics;
|
||||
@@ -377,6 +364,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.ConstItem.SrsSourceUrl = SrsFileSourceUrl;
|
||||
_config.ConstItem.RouteRulesTemplateSourceUrl = RoutingRulesSourceUrl;
|
||||
_config.SpeedTestItem.IPAPIUrl = IPAPIUrl;
|
||||
_config.GuiItem.RootCertProvider = RootCertProvider;
|
||||
|
||||
//systemProxy
|
||||
_config.SystemProxyItem.SystemProxyExceptions = SystemProxyExceptions;
|
||||
@@ -394,6 +382,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.TunModeItem.IcmpRouting = TunIcmpRouting;
|
||||
_config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect;
|
||||
_config.TunModeItem.RouteExcludeAddress = Utils.String2List(TunRouteExcludeAddress);
|
||||
_config.TunModeItem.Ipv4Address = TunIpv4Address;
|
||||
_config.TunModeItem.Ipv6Address = TunIpv6Address;
|
||||
|
||||
//coreType
|
||||
await SaveCoreType();
|
||||
@@ -404,7 +394,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
AppManager.Instance.Reset();
|
||||
|
||||
NoticeManager.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class ProfilesSelectViewModel : MyReactiveObject
|
||||
public class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public Interaction<Unit, Unit> ProfilesFocusInteraction { get; } = new();
|
||||
|
||||
#region private prop
|
||||
|
||||
private string _serverFilter = string.Empty;
|
||||
@@ -12,6 +16,8 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
|
||||
#endregion private prop
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
#region ObservableCollection
|
||||
|
||||
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
||||
@@ -36,18 +42,25 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool FilterExclude { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool MultiSelect { get; set; }
|
||||
|
||||
#endregion ObservableCollection
|
||||
|
||||
#region Init
|
||||
|
||||
public ProfilesSelectViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ProfilesSelectViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
_subIndexId = _config.SubIndexId ?? string.Empty;
|
||||
|
||||
#region WhenAnyValue && ReactiveCommand
|
||||
|
||||
SaveCmd = ReactiveCommand.Create(() =>
|
||||
{
|
||||
SelectFinish();
|
||||
});
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.SelectedSub,
|
||||
y => y != null && !y.Remarks.IsNullOrEmpty() && _subIndexId != y.Id)
|
||||
@@ -107,7 +120,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -125,7 +138,13 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
|
||||
await RefreshServers();
|
||||
|
||||
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
||||
try
|
||||
{
|
||||
await ProfilesFocusInteraction.Handle(Unit.Default);
|
||||
}
|
||||
catch (UnhandledInteractionException<Unit, Unit>)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ServerFilterChanged(bool c)
|
||||
@@ -157,23 +176,19 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
||||
SelectedProfile = selected ?? lstModel.First();
|
||||
}
|
||||
|
||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||
}
|
||||
|
||||
public async Task RefreshSubscriptions()
|
||||
private async Task RefreshSubscriptions()
|
||||
{
|
||||
var subItems = await AppManager.Instance.SubItems();
|
||||
subItems.Insert(0, new SubItem { Remarks = ResUI.AllGroupServers });
|
||||
|
||||
SubItems.Clear();
|
||||
SubItems.AddRange(subItems);
|
||||
|
||||
SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
||||
|
||||
foreach (var item in await AppManager.Instance.SubItems())
|
||||
{
|
||||
SubItems.Add(item);
|
||||
}
|
||||
SelectedSub = (_config.SubIndexId.IsNotEmpty()
|
||||
? SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId)
|
||||
: null) ?? SubItems.FirstOrDefault();
|
||||
? subItems.FirstOrDefault(t => t.Id == _config.SubIndexId)
|
||||
: null) ?? subItems.FirstOrDefault();
|
||||
}
|
||||
|
||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user