Compare commits

...

115 Commits

Author SHA1 Message Date
2dust
f98f517368 up 7.19.0 2026-02-26 19:58:34 +08:00
2dust
7931058342 Centralize active network info retrieval 2026-02-26 17:34:57 +08:00
2dust
b53507f486 Update Directory.Packages.props 2026-02-26 15:40:03 +08:00
DHR60
68ea10158a Fix udphop (#8843) 2026-02-26 15:08:50 +08:00
DHR60
2f35e7a99c Fix fragment (#8840) 2026-02-26 15:07:24 +08:00
DHR60
3c1ecf085b Tun protect (#8779)
* Tun protect

* Remove EnableExInbound

* Fix

* Fix balancer tag

* Fix

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-02-26 15:06:17 +08:00
DHR60
3a5293bf87 Fix cert separator (#8837) 2026-02-25 19:30:29 +08:00
DHR60
ac43bb051d Node test with sub chain (#8778) 2026-02-25 19:28:20 +08:00
DHR60
7b31bcdd9f Add Finalmask support (#8820)
* Add Finalmask support

* UI
2026-02-24 21:09:17 +08:00
DHR60
aea7078e95 Add IP cert support (#8833) 2026-02-24 19:58:23 +08:00
DHR60
9c82df5b49 Refactor core config gen (#8768)
* Refactor core config gen

* Update tag naming

* Support sing-box 1.11 DNS

* Fix

* Optimize ProfileItem acquisition speed

* Fix

* Fix
2026-02-24 19:48:59 +08:00
JieXu
b5800f7dfc Update SingboxDnsService.cs (#8775)
* Update Utils.cs

* Update SingboxDnsService.cs

* Update package-rhel.sh

* Update package-rhel.sh

* Withdraw
2026-02-07 19:31:35 +08:00
DHR60
0f3a3eac02 Group preview (#8760)
* Group Preview

* Fix
2026-02-06 14:33:58 +08:00
2dust
54608ab2b9 Adjust GroupProfileManager 2026-02-06 14:15:21 +08:00
2dust
6167624443 Rename ProfileItems to ProfileModels and refactor 2026-02-06 13:50:47 +08:00
2dust
7a58e78381 Refactor profile migration and add group handling 2026-02-06 11:45:26 +08:00
DHR60
677e81f9a7 Refactor profile item config (#8659)
* Refactor

* Add hysteria2 bandwidth and hop interval support

* Upgrade config version and rename

* Refactor id and security

* Refactor flow

* Fix hy2 bbr

* Fix warning CS0618

* Remove unused code

* Fix hy2 migrate

* Fix

* Refactor

* Refactor ProfileItem protocol extra handling

* Refactor, use record instead of class

* Hy2 SalamanderPass

* Fix Tuic

* Fix group

* Fix Tuic

* Fix hy2 Brutal Bandwidth

* Clean Code

* Fix

* Support interval range

* Add Username

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-02-05 19:48:33 +08:00
DHR60
d9843dc775 Add DNS Hosts features (#8756) 2026-02-05 17:05:27 +08:00
2dust
bceebc1661 Add process to routing domains
https://github.com/2dust/v2rayN/issues/8757
2026-02-05 16:52:48 +08:00
2dust
fdb733fa72 up 7.18.0 2026-02-04 15:19:54 +08:00
2dust
8d01d8fda5 Remove windows-64-SelfContained build/artifact steps 2026-02-04 15:13:10 +08:00
DHR60
018d541910 Update CA List (#8755) 2026-02-04 14:35:40 +08:00
DHR60
7e2e66bb0e Add DNS features (#8729)
* Simplify DNS Settings

* Add ParallelQuery and ServeStale features

* Fix

* Add Tips

* Simplify Predefined Hosts
2026-02-04 14:35:26 +08:00
2dust
3cb640c16b Add IP validation and improve hosts parsing
https://github.com/2dust/v2rayN/issues/8752
2026-02-04 14:33:34 +08:00
DHR60
fdde837698 Add xray v26.1.31 finalmask support (#8732)
* Add xray v26.1.31 hysteria2 support

* Add xray v26.1.31 mkcp support
2026-02-04 10:34:07 +08:00
2dust
c7afef3d70 Update Directory.Packages.props 2026-02-04 10:15:40 +08:00
2dust
19d4f1fa83 Enable sudo for mihomo core in TUN mode
https://github.com/2dust/v2rayN/issues/8673
2026-02-04 10:15:36 +08:00
2dust
7678ad9095 up 7.17.3 2026-02-01 15:49:00 +08:00
dependabot[bot]
585c24526f Bump actions/checkout from 6.0.1 to 6.0.2 (#8694)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.1...v6.0.2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-01 15:35:19 +08:00
DHR60
eb0f5bafde Disable insecure when cert pinned (#8733) 2026-02-01 15:34:04 +08:00
JieXu
d589713fd5 Update build-linux.yml (#8724) 2026-01-31 11:40:21 +08:00
DHR60
4550ddb14e Bug fix (#8728) 2026-01-31 10:34:25 +08:00
雨落
ffe401a26d Accept hosts.ics as a host file on windows. (#8714)
Signed-off-by: 秋雨落 <i@rain.cx>
2026-01-30 15:32:06 +08:00
2dust
8774e302b2 up 7.17.2 2026-01-30 10:43:01 +08:00
2dust
df016dd55c Bug fix
https://github.com/2dust/v2rayN/issues/8720
2026-01-30 10:35:00 +08:00
2dust
9ea80671d3 up 7.17.1 2026-01-18 19:32:44 +08:00
2dust
449849d8e8 Update Directory.Packages.props 2026-01-18 19:25:13 +08:00
DHR60
03b62b3d78 Fix (#8658) 2026-01-17 19:35:56 +08:00
DHR60
9f9b90cb97 Add hysteria2 uri cert sha pinning support (#8657) 2026-01-17 16:22:26 +08:00
DHR60
c42dcd2876 Add process matching rules support (#8643)
* Add process matching rules support

* Fix
2026-01-17 16:08:36 +08:00
2dust
2fefafdd37 Add support for CoreType7 (Hysteria2) in option settings 2026-01-17 16:06:29 +08:00
DHR60
2c9a90c878 Add xray hysteria2 outbound support (#8630) 2026-01-17 15:49:44 +08:00
DHR60
4e5f1838a2 Add Cert SHA-256 pinning support (#8613) 2026-01-17 15:42:40 +08:00
2dust
a45a1dc982 Ensure WebDAV base URL ends with trailing slash 2026-01-17 15:08:08 +08:00
2dust
fe183798b6 Refactor child item aggregation in managers 2026-01-13 20:24:52 +08:00
2dust
947c84cf10 Refactor 'Move to Group' menu in ProfilesView 2026-01-10 15:14:58 +08:00
2dust
9c74b51d74 up 7.17.0 2026-01-09 18:44:28 +08:00
2dust
abd962ab31 Update Global.cs 2026-01-08 17:15:59 +08:00
DHR60
f3b894015e Add sing-box ech support (#8603)
* Add sing-box ech support

* Support group config type

* Simplified code
2026-01-08 13:56:45 +08:00
2dust
4562d4cf00 Add ECH config support to profile and UI
Introduces EchConfigList and EchForceQuery fields to ProfileItem and V2rayConfig models, updates related handlers and services to process these fields, and extends the AddServerWindow UI to allow user input for ECH configuration. Also adds localization entries for the new fields and updates extension methods for string handling.
2026-01-07 11:34:13 +08:00
JieXu
bc36cf8a47 Code Clean (#8586) 2026-01-05 09:56:43 +08:00
Kazuto Iris
cbdfe2e15a fix: Fix failure to follow system theme changes (#8584)
Fix the issue where the application failed to sync with system dark/light mode changes in specific scenarios such as triggering system theme switching via scheduled tasks while waking from hibernation, caused by the unreliable HWND hook implementation that missed critical events.
2026-01-05 09:56:33 +08:00
2dust
68583e20bc Update package versions in Directory.Packages.props 2026-01-03 19:06:17 +08:00
DHR60
6d6459b009 Fix edge cases (#8564) 2026-01-03 10:20:27 +08:00
2dust
807562b69e Set all .NET publish tasks to self-contained 2025-12-28 14:10:00 +08:00
2dust
654d7d83d0 up 7.16.9 2025-12-25 18:34:10 +08:00
2dust
027252e687 Move ShowInTaskbar and RunningCoreType to AppManager 2025-12-24 16:01:28 +08:00
2dust
5478c90180 Bug fix
https://github.com/2dust/v2rayN/issues/8515
2025-12-24 14:19:36 +08:00
DHR60
28f30d7e97 Revert "Add TLS ALPN check for WS (#8469)" (#8517)
This reverts commit 6e27dca6cd.
2025-12-24 13:38:08 +08:00
2dust
ae7d54c2e5 up 7.16.8 2025-12-22 19:04:36 +08:00
2dust
56d0d65b06 Reduce minimum width of MainWindow 2025-12-22 19:03:47 +08:00
2dust
5e8e189c27 Increase MenuItemHeight to 32 in App.xaml 2025-12-21 18:53:09 +08:00
2dust
3fee86d44a Add context menu to subscription DataGrid 2025-12-21 18:53:00 +08:00
2dust
dd77eb79c6 Remove .NET self-contained zip check in UpdateService 2025-12-20 14:47:40 +08:00
2dust
d26a2559a6 up 7.16.7 2025-12-20 14:12:41 +08:00
2dust
e5ba1759aa Update Directory.Packages.props 2025-12-20 14:12:16 +08:00
dependabot[bot]
bfdee37cc1 Bump actions/upload-artifact from 5.0.0 to 6.0.0 (#8493)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 09:19:09 +08:00
dependabot[bot]
cf89cfcd95 Bump actions/download-artifact from 6 to 7 (#8492)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 09:19:00 +08:00
2dust
39a988c704 Update Directory.Packages.props 2025-12-13 15:18:52 +08:00
JieXu
2b28254fbc Update ResUI.fr.resx (#8472) 2025-12-10 17:56:19 +08:00
DHR60
6e27dca6cd Add TLS ALPN check for WS (#8469) 2025-12-09 20:22:13 +08:00
DHR60
7cee98887b Refactor Node Precheck (#8464) 2025-12-09 20:03:07 +08:00
DHR60
3885ff8b31 Fix Shadowsocks Fmt (#8462) 2025-12-08 19:55:27 +08:00
2dust
12abf383e9 up 7.16.6 2025-12-07 15:32:45 +08:00
2dust
5bef02bd6d Code clean 2025-12-07 15:32:03 +08:00
2dust
592f1260b5 Remove Cloudflare IP API URL from IPAPIUrls
https://github.com/2dust/v2rayN/issues/8441
2025-12-07 15:24:54 +08:00
2dust
18303688d7 Refactor AddGroupServerWindow tab controls layout 2025-12-07 15:22:40 +08:00
2dust
5c4b7f6636 Update Directory.Packages.props 2025-12-07 15:22:19 +08:00
tt2563
37cce2fa35 「desktop版本-啟用連線資訊測試位址自訂輸入」 (#8456) 2025-12-07 15:21:11 +08:00
dependabot[bot]
6f8b65c75b Bump actions/checkout from 6.0.0 to 6.0.1 (#8437)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.0...v6.0.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-04 18:24:14 +08:00
2dust
83c63b914a up 7.16.5 2025-11-29 19:59:48 +08:00
DHR60
1ca2485d2a Fix (#8407) 2025-11-29 19:58:51 +08:00
2dust
cc4154bb0d Increase UI grid column widths and font size options 2025-11-28 20:31:40 +08:00
2dust
d7f77f220c Improve group text description 2025-11-27 19:55:33 +08:00
JieXu
86f45d103d Update build-linux.yml to use Red Hat UBI image (#8392) 2025-11-27 15:01:44 +08:00
2dust
0077751f75 Add subscription delete functionality to ProfilesView 2025-11-26 20:11:14 +08:00
dependabot[bot]
fa2b4b3dc9 Bump actions/setup-dotnet from 5.0.0 to 5.0.1 (#8387)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-26 14:31:34 +08:00
DHR60
23cacb8339 Format imported xhttp extra (#8390) 2025-11-26 14:31:14 +08:00
DHR60
9ffa6a4eb6 Remove formatted spaces from extra JSON before URL encoding (#8385) 2025-11-25 17:40:41 +08:00
2dust
386209b835 Fix 2025-11-24 19:12:49 +08:00
jiuqianyuan
830dc89c32 Fix: tcping high latency and speedtest displayed 0 (#8374)
* Fix: High latency in tcping test due to thread blocking

* Fix: download to fast, speed displayed as 0.

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2025-11-24 19:01:04 +08:00
2dust
693afe3560 up 7.16.4 2025-11-23 14:33:01 +08:00
2dust
95361e8b65 Simplify configuration-related resource strings 2025-11-23 14:31:29 +08:00
2dust
3ff7299aca Refactor update result handling and model 2025-11-23 14:06:34 +08:00
DHR60
34fc4de0c2 Avoid xray warning (#8369) 2025-11-22 19:17:18 +08:00
DHR60
91536d3923 Fix sing-box ws (#8367)
* Fix sing-box ws

* Parse eh
2025-11-22 16:40:07 +08:00
2dust
6b87c09a96 Add confirmation before removing duplicate servers
https://github.com/2dust/v2rayN/issues/8365
2025-11-22 10:20:16 +08:00
2dust
ecaac2ac61 Fix
https://github.com/2dust/v2rayN/discussions/8366
2025-11-22 10:16:53 +08:00
2dust
ad74b1584d Refactor dialog layouts 2025-11-21 20:30:41 +08:00
dependabot[bot]
514d76953a Bump actions/checkout from 5.0.1 to 6.0.0 (#8359)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6.0.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-21 15:57:37 +08:00
DHR60
5b82f17995 Fix (#8363)
* Fix

* AI-optimized code
2025-11-21 15:56:42 +08:00
2dust
20ce35bc30 Update dotnet publish syntax in build workflows 2025-11-20 20:04:14 +08:00
2dust
c0fca0dddd Update build script and remove global.json 2025-11-20 19:56:14 +08:00
2dust
2ba896e17e Update Directory.Packages.props 2025-11-20 19:39:29 +08:00
DHR60
f61e6d8c63 perf: Shadowsocks (#8352)
* perf: Shadowsocks

* stricter plugin name fix for SIP002 URI

* Fix
2025-11-20 19:35:57 +08:00
2dust
d3e2e55ecf Add global.json for SDK configuration
Introduces a global.json file specifying the .NET SDK version (8.0.416) and disables rollForward to ensure consistent SDK usage across environments.
2025-11-20 10:12:44 +08:00
2dust
30e663cd4f up 7.16.3 2025-11-19 17:15:35 +08:00
JieXu
054efeb32c [PR]改进Emoji字体兼容性 (#8346)
* Update AppBuilderExtension.cs

* Update package-rhel.sh

* Update package-debian.sh

* Update AppBuilderExtension.cs

* Update AppBuilderExtension.cs

* Update AppBuilderExtension.cs

* Withdraw

* Update AppBuilderExtension.cs
2025-11-19 16:47:00 +08:00
2dust
2ebd2b28a8 Support Backspace for remove actions in UI lists
Changed key handling and menu input gestures to allow Backspace (in addition to Delete) for removing items in server, profile, and routing rule lists. This improves usability and consistency across both Avalonia and WPF views.
2025-11-18 16:54:42 +08:00
dependabot[bot]
84f812c8ee Bump actions/checkout from 5.0.0 to 5.0.1 (#8341)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 16:15:59 +08:00
2dust
b6ee40ab8d Update Directory.Packages.props 2025-11-18 16:15:19 +08:00
2dust
7f24f4a15f Remove shortcut hints from menu translations
Shortcut key hints (e.g., '(Ctrl+C)', '(Delete)') were removed from various menu item translations in resource files for all supported languages. This improves consistency and clarity in UI text across the application.
2025-11-18 16:00:02 +08:00
2dust
0d307671d1 Bug fix
https://github.com/2dust/v2rayN/issues/8267
2025-11-17 17:44:39 +08:00
Harry Huang
8ea5a57988 Optimize speedtest (#8325)
* Optimize stop-speedtest tip display

* Fix speedtest termination latency
2025-11-16 14:58:55 +08:00
Harry Huang
4fb41aeca1 Remove redundant string operation (#8324) 2025-11-16 14:21:34 +08:00
135 changed files with 6080 additions and 4637 deletions

View File

@@ -31,26 +31,26 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5.0.0
uses: actions/checkout@v6.0.2
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup .NET
uses: actions/setup-dotnet@v5.0.0
uses: actions/setup-dotnet@v5.0.1
with:
dotnet-version: '8.0.x'
- name: Build
run: |
cd v2rayN
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64 --self-contained=true -o "$OutputPath64"
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 --self-contained=true -o "$OutputPathArm64"
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-x64 --self-contained=true -p:PublishTrimmed=true -o "$OutputPath64"
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 --self-contained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-x64 -p:SelfContained=true -o "$OutputPath64"
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r linux-arm64 -p:SelfContained=true -o "$OutputPathArm64"
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-x64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPath64"
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-linux
path: |
@@ -97,26 +97,79 @@ jobs:
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
runs-on: ubuntu-24.04
container:
image: quay.io/almalinuxorg/10-base:latest
options: --platform=linux/amd64/v2
image: registry.access.redhat.com/ubi10/ubi
env:
RELEASE_TAG: ${{ github.event.inputs.release_tag != '' && github.event.inputs.release_tag || github.ref_name }}
steps:
- name: Prepare tools (Red Hat)
shell: bash
run: |
dnf -y makecache
dnf -y install epel-release
dnf -y install sudo git rpm-build rpmdevtools dnf-plugins-core rsync findutils tar gzip unzip which
set -euo pipefail
. /etc/os-release
EL_MAJOR="${VERSION_ID%%.*}"
echo "EL_MAJOR=${EL_MAJOR}"
dnf -y makecache || true
command -v curl >/dev/null || dnf -y install curl ca-certificates
ARCH="$(uname -m)"
case "$ARCH" in x86_64|aarch64) ;; *) echo "Unsupported arch: $ARCH"; exit 1 ;; esac
install_epel_from_dir() {
local base="$1" rpm
echo "Try: $base"
rpm="$(
{
curl -fsSL "$base/Packages/" 2>/dev/null
curl -fsSL "$base/Packages/e/" 2>/dev/null | sed 's|href="|href="e/|'
} |
sed -n 's/.*href="\([^"]*epel-release-[^"]*\.noarch\.rpm\)".*/\1/p' |
sort -V | tail -n1
)" || true
if [[ -n "$rpm" ]]; then
dnf -y install "$base/Packages/$rpm"
return 0
fi
return 1
}
FEDORA="https://dl.fedoraproject.org/pub/epel/epel-release-latest-${EL_MAJOR}.noarch.rpm"
echo "Try Fedora: $FEDORA"
if curl -fsSLI "$FEDORA" >/dev/null; then
dnf -y install "$FEDORA"
else
ROCKY="https://dl.rockylinux.org/pub/rocky/${EL_MAJOR}/extras/${ARCH}/os"
if install_epel_from_dir "$ROCKY"; then
:
else
ALMA="https://repo.almalinux.org/almalinux/${EL_MAJOR}/extras/${ARCH}/os"
if install_epel_from_dir "$ALMA"; then
:
else
echo "EPEL bootstrap failed (Fedora/Rocky/Alma)"
exit 1
fi
fi
fi
dnf -y install sudo git rpm-build rpmdevtools dnf-plugins-core \
rsync findutils tar gzip unzip which
dnf repolist | grep -i epel || true
- name: Checkout repo (for scripts)
uses: actions/checkout@v5.0.0
uses: actions/checkout@v6.0.2
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Restore build artifacts
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: v2rayN-linux
path: ${{ github.workspace }}/v2rayN/Release
@@ -137,7 +190,7 @@ jobs:
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
- name: Upload RPM artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-rpm
path: dist/rpm/**/*.rpm

View File

@@ -26,26 +26,26 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5.0.0
uses: actions/checkout@v6.0.2
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v5.0.0
uses: actions/setup-dotnet@v5.0.1
with:
dotnet-version: '8.0.x'
- name: Build
run: |
cd v2rayN
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64 --self-contained=true -o $OutputPath64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 --self-contained=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-x64 --self-contained=true -p:PublishTrimmed=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 --self-contained=true -p:PublishTrimmed=true -o $OutputPathArm64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-x64 -p:SelfContained=true -o $OutputPath64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r osx-arm64 -p:SelfContained=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-x64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-macos
path: |

View File

@@ -26,26 +26,26 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5.0.0
uses: actions/checkout@v6.0.2
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v5.0.0
uses: actions/setup-dotnet@v5.0.1
with:
dotnet-version: '8.0.x'
- name: Build
run: |
cd v2rayN
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./v2rayN.Desktop/v2rayN.Desktop.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-windows-desktop
path: |

View File

@@ -15,7 +15,6 @@ env:
OutputArchArm: "windows-arm64"
OutputPath64: "${{ github.workspace }}/v2rayN/Release/windows-64"
OutputPathArm64: "${{ github.workspace }}/v2rayN/Release/windows-arm64"
OutputPath64Sc: "${{ github.workspace }}/v2rayN/Release/windows-64-SelfContained"
jobs:
build:
@@ -27,26 +26,23 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5.0.0
uses: actions/checkout@v6.0.2
- name: Setup
uses: actions/setup-dotnet@v5.0.0
uses: actions/setup-dotnet@v5.0.1
with:
dotnet-version: '8.0.x'
- name: Build
run: |
cd v2rayN
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 --self-contained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 --self-contained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
- name: Upload build artifacts
uses: actions/upload-artifact@v5.0.0
uses: actions/upload-artifact@v6.0.0
with:
name: v2rayN-windows
path: |
@@ -59,7 +55,6 @@ jobs:
chmod 755 package-release-zip.sh
./package-release-zip.sh $OutputArch $OutputPath64
./package-release-zip.sh $OutputArchArm $OutputPathArm64
./package-release-zip.sh "windows-64-SelfContained" $OutputPath64Sc
- name: Upload zip archive to release
uses: svenstaro/upload-release-action@v2
@@ -68,4 +63,4 @@ jobs:
file: ${{ github.workspace }}/v2rayN*.zip
tag: ${{ github.event.inputs.release_tag }}
file_glob: true
prerelease: true
prerelease: true

View File

@@ -28,7 +28,7 @@ Package: v2rayN
Version: $Version
Architecture: $Arch2
Maintainer: https://github.com/2dust/v2rayN
Depends: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1)
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)
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
EOF

View File

@@ -1,16 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
# ====== Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS ======
if [[ -r /etc/os-release ]]; then
. /etc/os-release
case "$ID" in
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
rhel|rocky|almalinux|fedora|centos)
echo "[OK] Detected supported system: $NAME $VERSION_ID"
;;
*)
echo "[ERROR] Unsupported system: $NAME ($ID)."
echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS or Ubuntu/Debian."
echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS."
exit 1
;;
esac
@@ -30,7 +30,7 @@ echo "[INFO] Detected kernel version: $KERNEL_FULL"
if (( KERNEL_MAJOR < MIN_KERNEL_MAJOR )) || { (( KERNEL_MAJOR == MIN_KERNEL_MAJOR )) && (( KERNEL_MINOR < MIN_KERNEL_MINOR )); }; then
echo "[ERROR] Kernel $KERNEL_FULL is too old. Requires Linux >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}."
echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+, Debian 13+)."
echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+)."
exit 1
fi
@@ -80,7 +80,6 @@ host_arch="$(uname -m)"
install_ok=0
case "$ID" in
# ------------------------------ RHEL family (UNCHANGED) ------------------------------
rhel|rocky|almalinux|centos)
if command -v dnf >/dev/null 2>&1; then
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
@@ -92,58 +91,7 @@ case "$ID" in
install_ok=1
fi
;;
# ------------------------------ Ubuntu ----------------------------------------------
ubuntu)
sudo apt-get update
# Ensure 'universe' (Ubuntu) to get 'rpm'
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
sudo apt-get -y install software-properties-common || true
sudo add-apt-repository -y universe || true
sudo apt-get update
fi
# Base tools + rpm (provides rpmbuild)
sudo apt-get -y install curl unzip tar rsync rpm || true
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
# rpmbuild presence check
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
exit 1
fi
# .NET SDK 8 (best effort via apt)
if ! command -v dotnet >/dev/null 2>&1; then
sudo apt-get -y install dotnet-sdk-8.0 || true
sudo apt-get -y install dotnet-sdk-8 || true
sudo apt-get -y install dotnet-sdk || true
fi
install_ok=1
;;
# ------------------------------ Debian (KEEP, with local dotnet install) ------------
debian)
sudo apt-get update
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip
sudo apt-get -y install curl unzip tar rsync rpm binutils || true
# rpmbuild presence check
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
echo " Please ensure 'rpm' is available from Debian repos."
exit 1
fi
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet
if ! command -v dotnet >/dev/null 2>&1; then
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..."
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh"
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet"
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
export DOTNET_ROOT="$HOME/.dotnet"
if ! command -v dotnet >/dev/null 2>&1; then
echo "[ERROR] dotnet installation failed."
exit 1
fi
fi
install_ok=1
*)
;;
esac
@@ -154,7 +102,7 @@ fi
command -v curl >/dev/null
# Root directory = the script's location
# Root directory
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
@@ -164,14 +112,14 @@ if [[ -f .gitmodules ]]; then
git submodule update --init --recursive || true
fi
# ===== Locate project ================================================================
# Locate project
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
if [[ ! -f "$PROJECT" ]]; then
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
fi
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
# ===== Resolve GUI version & auto checkout ============================================
# Resolve GUI version & auto checkout
VERSION=""
choose_channel() {
@@ -391,22 +339,6 @@ download_singbox() {
install -Dm755 "$bin" "$outdir/sing-box"
}
# ---- NEW: download_mihomo (REQUIRED in --netcore mode) ----
download_mihomo() {
# Download mihomo into outroot/bin/mihomo/mihomo
local outroot="$1"
local url=""
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64/bin/mihomo/mihomo"
else
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo"
fi
echo "[+] Download mihomo: $url"
mkdir -p "$outroot/bin/mihomo"
curl -fL "$url" -o "$outroot/bin/mihomo/mihomo"
chmod +x "$outroot/bin/mihomo/mihomo" || true
}
# Move geo files to a unified path: outroot/bin
unify_geo_layout() {
local outroot="$1"
@@ -491,8 +423,7 @@ download_v2rayn_bundle() {
fi
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
# keep mihomo
# find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
local nested_dir
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
@@ -552,20 +483,11 @@ build_for_arch() {
trap '[[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"' RETURN
# rpmbuild topdir selection
local TOPDIR SPECDIR SOURCEDIR USE_TOPDIR_DEFINE
if [[ "$ID" =~ ^(rhel|rocky|almalinux|centos)$ ]]; then
rpmdev-setuptree
TOPDIR="${HOME}/rpmbuild"
SPECDIR="${TOPDIR}/SPECS"
SOURCEDIR="${TOPDIR}/SOURCES"
USE_TOPDIR_DEFINE=0
else
TOPDIR="${WORKDIR}/rpmbuild"
SPECDIR="${TOPDIR}/SPECS}"
SOURCEDIR="${TOPDIR}/SOURCES"
mkdir -p "${SPECDIR}" "${SOURCEDIR}" "${TOPDIR}/BUILD" "${TOPDIR}/RPMS" "${TOPDIR}/SRPMS"
USE_TOPDIR_DEFINE=1
fi
local TOPDIR SPECDIR SOURCEDIR
rpmdev-setuptree
TOPDIR="${HOME}/rpmbuild"
SPECDIR="${TOPDIR}/SPECS"
SOURCEDIR="${TOPDIR}/SOURCES"
# Stage publish content
mkdir -p "$WORKDIR/$PKGROOT"
@@ -602,8 +524,6 @@ build_for_arch() {
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
fi
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
fi
# Tarball
@@ -631,13 +551,14 @@ ExclusiveArch: aarch64 x86_64
Source0: __PKGROOT__.tar.gz
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
Requires: freetype, cairo, pango, openssl, mesa-libEGL, mesa-libGL
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
Requires: glibc >= 2.34
Requires: fontconfig >= 2.13.1
Requires: desktop-file-utils >= 0.26
Requires: xdg-utils >= 1.1.3
Requires: coreutils >= 8.32
Requires: bash >= 5.1
Requires: freetype >= 2.10
%description
v2rayN Linux for Red Hat Enterprise Linux
@@ -656,6 +577,12 @@ https://github.com/2dust/v2rayN
install -dm0755 %{buildroot}/opt/v2rayN
cp -a * %{buildroot}/opt/v2rayN/
install -dm0755 %{buildroot}%{_sysconfdir}/sudoers.d
cat > %{buildroot}%{_sysconfdir}/sudoers.d/v2rayn-mihomo-deny << 'EOF'
ALL ALL=(ALL) !/home/*/.local/share/v2rayN/bin/mihomo/mihomo
EOF
chmod 0440 %{buildroot}%{_sysconfdir}/sudoers.d/v2rayn-mihomo-deny
# Launcher (prefer native ELF first, then DLL fallback)
install -dm0755 %{buildroot}%{_bindir}
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
@@ -709,6 +636,7 @@ fi
/opt/v2rayN
%{_datadir}/applications/v2rayn.desktop
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
%config(noreplace) /etc/sudoers.d/v2rayn-mihomo-deny
SPEC
# Autostart injection (inside %install) and %files entry
@@ -748,35 +676,8 @@ SPEC
sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE"
sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE"
# ----- Select proper 'strip' per target arch on Ubuntu only (cross-binutils) -----
# NOTE: We define only __strip to point to the target-arch strip.
# DO NOT override __brp_strip (it must stay the brp script path).
local STRIP_ARGS=()
if [[ "$ID" == "ubuntu" ]]; then
local STRIP_BIN=""
if [[ "$short" == "x64" ]]; then
STRIP_BIN="/usr/bin/x86_64-linux-gnu-strip"
else
STRIP_BIN="/usr/bin/aarch64-linux-gnu-strip"
fi
if [[ -x "$STRIP_BIN" ]]; then
STRIP_ARGS=( --define "__strip $STRIP_BIN" )
fi
fi
# Build RPM for this arch (force rpm --target to match compile arch)
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
rpmbuild -ba "$SPECFILE" --define "_topdir $TOPDIR" --target "$rpm_target" "${STRIP_ARGS[@]}"
else
rpmbuild -ba "$SPECFILE" --target "$rpm_target" "${STRIP_ARGS[@]}"
fi
# Copy temporary rpmbuild to ~/rpmbuild on Debian/Ubuntu path
if [[ "$USE_TOPDIR_DEFINE" -eq 1 ]]; then
mkdir -p "$HOME/rpmbuild"
rsync -a "$TOPDIR"/ "$HOME/rpmbuild"/
TOPDIR="$HOME/rpmbuild"
fi
rpmbuild -ba "$SPECFILE" --target "$rpm_target"
echo "Build done for $short. RPM at:"
local f

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.16.2</Version>
<Version>7.19.0</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -5,28 +5,28 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.8" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.8" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.8" />
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" />
<PackageVersion Include="CliWrap" Version="3.9.0" />
<PackageVersion Include="CliWrap" Version="3.10.0" />
<PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.2" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
<PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="ReactiveUI" Version="22.2.1" />
<PackageVersion Include="ReactiveUI" Version="22.3.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="22.2.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.7" />
<PackageVersion Include="ReactiveUI.WPF" Version="22.3.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.3" />
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7" />
<PackageVersion Include="NLog" Version="6.0.6" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
<PackageVersion Include="NLog" Version="6.1.0" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
</ItemGroup>
</Project>
</Project>

View File

@@ -6,17 +6,17 @@ public static class Extension
{
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
{
return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value);
}
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
{
return string.IsNullOrWhiteSpace(value);
return string.IsNullOrWhiteSpace(value) || string.IsNullOrEmpty(value);
}
public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
{
return !string.IsNullOrEmpty(value);
return !string.IsNullOrWhiteSpace(value);
}
public static string? NullIfEmpty(this string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value;
}
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
@@ -94,4 +94,28 @@ public static class Extension
{
return configType is EConfigType.Custom or EConfigType.PolicyGroup or EConfigType.ProxyChain;
}
/// <summary>
/// Safely adds elements from a collection to the list. Does nothing if the source is null.
/// </summary>
public static void AddRangeSafe<T>(this ICollection<T> destination, IEnumerable<T>? source)
{
ArgumentNullException.ThrowIfNull(destination);
if (source is null)
{
return;
}
if (destination is List<T> list)
{
list.AddRange(source);
return;
}
foreach (var item in source)
{
destination.Add(item);
}
}
}

View File

@@ -332,19 +332,17 @@ public class Utils
.ToList();
}
public static Dictionary<string, List<string>> ParseHostsToDictionary(string hostsContent)
public static Dictionary<string, List<string>> ParseHostsToDictionary(string? hostsContent)
{
if (hostsContent.IsNullOrEmpty())
{
return new();
}
var userHostsMap = hostsContent
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim())
// skip full-line comments
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
// strip inline comments (truncate at '#')
.Select(line =>
{
var index = line.IndexOf('#');
return index >= 0 ? line.Substring(0, index).Trim() : line;
})
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#'))
// ensure line still contains valid parts
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
@@ -425,7 +423,7 @@ public class Utils
var domain = authority;
// Handle IPv6 addresses, e.g., "[2001:db8::1]:443"
if (authority.StartsWith("[") && authority.Contains("]"))
if (authority.StartsWith('[') && authority.Contains(']'))
{
var closingBracketIndex = authority.LastIndexOf(']');
if (closingBracketIndex < authority.Length - 1 && authority[closingBracketIndex + 1] == ':')
@@ -462,6 +460,18 @@ public class Utils
return (domain, port);
}
public static string? DomainStrategy4Sbox(string? strategy)
{
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",
_ => null
};
}
#endregion Conversion Functions
#region Data Checks
@@ -505,6 +515,31 @@ public class Utils
return false;
}
public static bool IsIpAddress(string? ip)
{
if (ip.IsNullOrEmpty())
{
return false;
}
ip = ip.Trim();
// First, validate using built-in parser
if (!IPAddress.TryParse(ip, out var address))
{
return false;
}
// For IPv4: ensure it has exactly 3 dots (meaning 4 parts)
if (address.AddressFamily == AddressFamily.InterNetwork)
{
return ip.Count(c => c == '.') == 3;
}
// For IPv6: TryParse is already strict enough
return address.AddressFamily == AddressFamily.InterNetworkV6;
}
public static Uri? TryUri(string url)
{
try
@@ -594,12 +629,7 @@ public class Utils
{
try
{
List<IPEndPoint> lstIpEndPoints = new();
List<TcpConnectionInformation> lstTcpConns = new();
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
var (lstIpEndPoints, lstTcpConns) = GetActiveNetworkInfo();
if (lstIpEndPoints?.FindIndex(it => it.Port == port) >= 0)
{
@@ -641,6 +671,27 @@ public class Utils
return 59090;
}
public static (List<IPEndPoint> endpoints, List<TcpConnectionInformation> connections) GetActiveNetworkInfo()
{
var endpoints = new List<IPEndPoint>();
var connections = new List<TcpConnectionInformation>();
try
{
var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
endpoints.AddRange(ipGlobalProperties.GetActiveTcpListeners());
endpoints.AddRange(ipGlobalProperties.GetActiveUdpListeners());
connections.AddRange(ipGlobalProperties.GetActiveTcpConnections());
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return (endpoints, connections);
}
#endregion Speed Test
#region Miscellaneous
@@ -719,33 +770,65 @@ public class Utils
return Guid.TryParse(strSrc, out _);
}
public static Dictionary<string, string> GetSystemHosts()
private static Dictionary<string, string> GetSystemHosts(string hostFile)
{
var systemHosts = new Dictionary<string, string>();
var hostFile = @"C:\Windows\System32\drivers\etc\hosts";
try
{
if (File.Exists(hostFile))
if (!File.Exists(hostFile))
{
var hosts = File.ReadAllText(hostFile).Replace("\r", "");
var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var host in hostsList)
{
if (host.StartsWith("#"))
{
continue;
}
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (hostItem.Length < 2)
{
continue;
}
systemHosts.Add(hostItem[1], hostItem[0]);
}
return systemHosts;
}
var hosts = File.ReadAllText(hostFile).Replace("\r", "");
var hostsList = hosts.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var host in hostsList)
{
// Trim whitespace
var line = host.Trim();
// Skip comments and empty lines
if (line.IsNullOrEmpty() || line.StartsWith("#"))
{
continue;
}
// Strip inline comments
var commentIndex = line.IndexOf('#');
if (commentIndex >= 0)
{
line = line.Substring(0, commentIndex).Trim();
}
if (line.IsNullOrEmpty())
{
continue;
}
var hostItem = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (hostItem.Length < 2)
{
continue;
}
var ipAddress = hostItem[0];
var domain = hostItem[1];
// Validate IP address
if (!IsIpAddress(ipAddress))
{
continue;
}
// Validate domain name
if (domain.IsNullOrEmpty() || domain.Length > 255)
{
continue;
}
systemHosts[domain] = ipAddress;
}
return systemHosts;
}
catch (Exception ex)
{
@@ -755,6 +838,19 @@ public class Utils
return systemHosts;
}
public static Dictionary<string, string> GetSystemHosts()
{
var hosts = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts");
var hostsIcs = GetSystemHosts(@"C:\Windows\System32\drivers\etc\hosts.ics");
foreach (var (key, value) in hostsIcs)
{
hosts[key] = value;
}
return hosts;
}
public static async Task<string?> GetCliWrapOutput(string filePath, string? arg)
{
return await GetCliWrapOutput(filePath, arg != null ? new List<string>() { arg } : null);
@@ -1004,7 +1100,19 @@ public class Utils
public static string GetExeName(string name)
{
return IsWindows() ? $"{name}.exe" : name;
if (name.IsNullOrEmpty() || IsNonWindows())
{
return name;
}
if (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
{
return name;
}
else
{
return $"{name}.exe";
}
}
public static bool IsAdministrator()

View File

@@ -15,7 +15,6 @@ public class Global
public const string CoreConfigFileName = "config.json";
public const string CorePreConfigFileName = "configPre.json";
public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
public const string ClashMixinConfigFileName = "Mixin.yaml";
public const string NamespaceSample = "ServiceLib.Sample.";
@@ -73,6 +72,7 @@ public class Global
public const string GrpcMultiMode = "multi";
public const int MaxPort = 65536;
public const int MinFontSize = 8;
public const int MinFontSizeCount = 13;
public const string RebootAs = "rebootas";
public const string AvaAssets = "avares://v2rayN/Assets/";
public const string LocalAppData = "V2RAYN_LOCAL_APPLICATION_DATA_V2";
@@ -88,231 +88,246 @@ public class Global
public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns";
public const int Hysteria2DefaultHopInt = 10;
public static readonly List<string> IEProxyProtocols =
[
"{ip}:{http_port}",
"socks={ip}:{socks_port}",
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
""
"socks={ip}:{socks_port}",
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}",
""
];
public static readonly List<string> SubConvertUrls =
[
@"https://sub.xeton.dev/sub?url={0}",
@"https://api.dler.io/sub?url={0}",
@"http://127.0.0.1:25500/sub?url={0}",
""
@"https://api.dler.io/sub?url={0}",
@"http://127.0.0.1:25500/sub?url={0}",
""
];
public static readonly List<string> SubConvertConfig =
[@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"];
[
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
];
public static readonly List<string> SubConvertTargets =
[
"",
"mixed",
"v2ray",
"clash",
"ss"
"mixed",
"v2ray",
"clash",
"ss"
];
public static readonly List<string> SpeedTestUrls =
[
@"https://cachefly.cachefly.net/50mb.test",
@"https://speed.cloudflare.com/__down?bytes=10000000",
@"https://speed.cloudflare.com/__down?bytes=50000000",
@"https://speed.cloudflare.com/__down?bytes=100000000",
];
@"https://speed.cloudflare.com/__down?bytes=10000000",
@"https://speed.cloudflare.com/__down?bytes=50000000",
@"https://speed.cloudflare.com/__down?bytes=100000000",
];
public static readonly List<string> SpeedPingTestUrls =
[
@"https://www.google.com/generate_204",
@"https://www.gstatic.com/generate_204",
@"https://www.apple.com/library/test/success.html",
@"http://www.msftconnecttest.com/connecttest.txt"
@"https://www.gstatic.com/generate_204",
@"https://www.apple.com/library/test/success.html",
@"http://www.msftconnecttest.com/connecttest.txt"
];
public static readonly List<string> GeoFilesSources =
[
"",
@"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat",
@"https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/{0}.dat"
@"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat",
@"https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/{0}.dat"
];
public static readonly List<string> SingboxRulesetSources =
[
"",
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
];
[
"",
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
];
public static readonly List<string> RoutingRulesSources =
[
"",
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/template.json",
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/template.json"
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/template.json",
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/template.json"
];
public static readonly List<string> DNSTemplateSources =
[
"",
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/",
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/"
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/",
@"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/"
];
public static readonly Dictionary<string, string> UserAgentTexts = new()
{
{"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" },
{"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" },
{"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" },
{"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" },
{"none",""}
};
{
{"chrome","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36" },
{"firefox","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" },
{"safari","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15" },
{"edge","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70" },
{"none",""}
};
public const string Hysteria2ProtocolShare = "hy2://";
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
{
{ EConfigType.VMess, "vmess://" },
{ EConfigType.Shadowsocks, "ss://" },
{ EConfigType.SOCKS, "socks://" },
{ EConfigType.VLESS, "vless://" },
{ EConfigType.Trojan, "trojan://" },
{ EConfigType.Hysteria2, "hysteria2://" },
{ EConfigType.TUIC, "tuic://" },
{ EConfigType.WireGuard, "wireguard://" },
{ EConfigType.Anytls, "anytls://" }
};
{
{ EConfigType.VMess, "vmess://" },
{ EConfigType.Shadowsocks, "ss://" },
{ EConfigType.SOCKS, "socks://" },
{ EConfigType.VLESS, "vless://" },
{ EConfigType.Trojan, "trojan://" },
{ EConfigType.Hysteria2, "hysteria2://" },
{ EConfigType.TUIC, "tuic://" },
{ EConfigType.WireGuard, "wireguard://" },
{ EConfigType.Anytls, "anytls://" }
};
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
{
{ EConfigType.VMess, "vmess" },
{ EConfigType.Shadowsocks, "shadowsocks" },
{ EConfigType.SOCKS, "socks" },
{ EConfigType.HTTP, "http" },
{ EConfigType.VLESS, "vless" },
{ EConfigType.Trojan, "trojan" },
{ EConfigType.Hysteria2, "hysteria2" },
{ EConfigType.TUIC, "tuic" },
{ EConfigType.WireGuard, "wireguard" },
{ EConfigType.Anytls, "anytls" }
};
{
{ EConfigType.VMess, "vmess" },
{ EConfigType.Shadowsocks, "shadowsocks" },
{ EConfigType.SOCKS, "socks" },
{ EConfigType.HTTP, "http" },
{ EConfigType.VLESS, "vless" },
{ EConfigType.Trojan, "trojan" },
{ EConfigType.Hysteria2, "hysteria2" },
{ EConfigType.TUIC, "tuic" },
{ EConfigType.WireGuard, "wireguard" },
{ EConfigType.Anytls, "anytls" }
};
public static readonly List<string> VmessSecurities =
[
"aes-128-gcm",
"chacha20-poly1305",
"auto",
"none",
"zero"
"chacha20-poly1305",
"auto",
"none",
"zero"
];
public static readonly List<string> SsSecurities =
[
"aes-256-gcm",
"aes-128-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"none",
"plain"
"aes-128-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"none",
"plain"
];
public static readonly List<string> SsSecuritiesInXray =
[
"aes-256-gcm",
"aes-128-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"xchacha20-poly1305",
"xchacha20-ietf-poly1305",
"none",
"plain",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305"
"aes-128-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"xchacha20-poly1305",
"xchacha20-ietf-poly1305",
"none",
"plain",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305"
];
public static readonly List<string> SsSecuritiesInSingbox =
[
"aes-256-gcm",
"aes-192-gcm",
"aes-128-gcm",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305",
"none",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305",
"aes-128-ctr",
"aes-192-ctr",
"aes-256-ctr",
"aes-128-cfb",
"aes-192-cfb",
"aes-256-cfb",
"rc4-md5",
"chacha20-ietf",
"xchacha20"
"aes-192-gcm",
"aes-128-gcm",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305",
"none",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305",
"aes-128-ctr",
"aes-192-ctr",
"aes-256-ctr",
"aes-128-cfb",
"aes-192-cfb",
"aes-256-cfb",
"rc4-md5",
"chacha20-ietf",
"xchacha20"
];
public static readonly List<string> Flows =
[
"",
"xtls-rprx-vision",
"xtls-rprx-vision-udp443"
"xtls-rprx-vision",
"xtls-rprx-vision-udp443"
];
public static readonly List<string> Networks =
[
"tcp",
"kcp",
"ws",
"httpupgrade",
"xhttp",
"h2",
"quic",
"grpc"
"kcp",
"ws",
"httpupgrade",
"xhttp",
"h2",
"quic",
"grpc"
];
public static readonly List<string> KcpHeaderTypes =
[
"srtp",
"utp",
"wechat-video",
"dtls",
"wireguard",
"dns"
"utp",
"wechat-video",
"dtls",
"wireguard",
"dns"
];
public static readonly Dictionary<string, string> KcpHeaderMaskMap = new()
{
{ "srtp", "header-srtp" },
{ "utp", "header-utp" },
{ "wechat-video", "header-wechat" },
{ "dtls", "header-dtls" },
{ "wireguard", "header-wireguard" },
{ "dns", "header-dns" }
};
public static readonly List<string> CoreTypes =
[
"Xray",
"sing_box"
"sing_box"
];
public static readonly HashSet<EConfigType> XraySupportConfigType =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.Hysteria2,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
];
public static readonly HashSet<EConfigType> SingboxSupportConfigType =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.Hysteria2,
EConfigType.TUIC,
EConfigType.Anytls,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.Hysteria2,
EConfigType.TUIC,
EConfigType.Anytls,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
];
public static readonly HashSet<EConfigType> SingboxOnlyConfigType = SingboxSupportConfigType.Except(XraySupportConfigType).ToHashSet();
@@ -324,132 +339,127 @@ public class Global
IPOnDemand
];
public static readonly List<string> DomainStrategies4Singbox =
public static readonly List<string> DomainStrategies4Sbox =
[
"",
"prefer_ipv4",
"prefer_ipv6",
"ipv4_only",
"ipv6_only",
"prefer_ipv4",
"prefer_ipv6",
""
"ipv6_only"
];
public static readonly List<string> Fingerprints =
[
"chrome",
"firefox",
"safari",
"ios",
"android",
"edge",
"360",
"qq",
"random",
"randomized",
""
"firefox",
"safari",
"ios",
"android",
"edge",
"360",
"qq",
"random",
"randomized",
""
];
public static readonly List<string> UserAgent =
[
"chrome",
"firefox",
"safari",
"edge",
"none"
"firefox",
"safari",
"edge",
"none"
];
public static readonly List<string> XhttpMode =
[
"auto",
"packet-up",
"stream-up",
"stream-one"
"packet-up",
"stream-up",
"stream-one"
];
public static readonly List<string> AllowInsecure =
[
"true",
"false",
""
"false",
""
];
public static readonly List<string> DomainStrategy4Freedoms =
public static readonly List<string> DomainStrategy =
[
"AsIs",
"UseIP",
"UseIPv4",
"UseIPv6",
""
];
public static readonly List<string> SingboxDomainStrategy4Out =
[
"",
"ipv4_only",
"prefer_ipv4",
"prefer_ipv6",
"ipv6_only"
"UseIP",
"UseIPv4v6",
"UseIPv6v4",
"UseIPv4",
"UseIPv6",
""
];
public static readonly List<string> DomainDirectDNSAddress =
[
"https://dns.alidns.com/dns-query",
"https://doh.pub/dns-query",
"223.5.5.5",
"119.29.29.29",
"localhost"
"https://doh.pub/dns-query",
"https://dns.alidns.com/dns-query,https://doh.pub/dns-query",
"223.5.5.5",
"119.29.29.29",
"localhost"
];
public static readonly List<string> DomainRemoteDNSAddress =
[
"https://cloudflare-dns.com/dns-query",
"https://dns.cloudflare.com/dns-query",
"https://dns.google/dns-query",
"https://doh.dns.sb/dns-query",
"https://doh.opendns.com/dns-query",
"https://common.dot.dns.yandex.net",
"8.8.8.8",
"1.1.1.1",
"185.222.222.222",
"208.67.222.222",
"77.88.8.8"
"https://dns.google/dns-query",
"https://cloudflare-dns.com/dns-query,https://dns.google/dns-query,8.8.8.8",
"https://dns.cloudflare.com/dns-query",
"https://doh.dns.sb/dns-query",
"https://doh.opendns.com/dns-query",
"https://common.dot.dns.yandex.net",
"8.8.8.8",
"1.1.1.1",
"185.222.222.222",
"208.67.222.222",
"77.88.8.8"
];
public static readonly List<string> DomainPureIPDNSAddress =
[
"223.5.5.5",
"119.29.29.29",
"localhost"
"119.29.29.29",
"localhost"
];
public static readonly List<string> Languages =
[
"zh-Hans",
"zh-Hant",
"en",
"fa-Ir",
"fr",
"ru",
"hu"
"zh-Hant",
"en",
"fa-Ir",
"fr",
"ru",
"hu"
];
public static readonly List<string> Alpns =
[
"h3",
"h2",
"http/1.1",
"h3,h2",
"h2,http/1.1",
"h3,h2,http/1.1",
""
"h2",
"http/1.1",
"h3,h2",
"h2,http/1.1",
"h3,h2,http/1.1",
""
];
public static readonly List<string> LogLevels =
[
"debug",
"info",
"warning",
"error",
"none"
"info",
"warning",
"error",
"none"
];
public static readonly Dictionary<string, string> LogLevelColors = new()
@@ -463,32 +473,32 @@ public class Global
public static readonly List<string> InboundTags =
[
"socks",
"socks2",
"socks3"
"socks2",
"socks3"
];
public static readonly List<string> RuleProtocols =
[
"http",
"tls",
"bittorrent"
"tls",
"bittorrent"
];
public static readonly List<string> RuleNetworks =
[
"",
"tcp",
"udp",
"tcp,udp"
"tcp",
"udp",
"tcp,udp"
];
public static readonly List<string> destOverrideProtocols =
[
"http",
"tls",
"quic",
"fakedns",
"fakedns+others"
"tls",
"quic",
"fakedns",
"fakedns+others"
];
public static readonly List<int> TunMtus =
@@ -504,88 +514,87 @@ public class Global
public static readonly List<string> TunStacks =
[
"gvisor",
"system",
"mixed"
"system",
"mixed"
];
public static readonly List<string> PresetMsgFilters =
[
"proxy",
"direct",
"block",
""
"direct",
"block",
""
];
public static readonly List<string> SingboxMuxs =
[
"h2mux",
"smux",
"yamux",
""
"smux",
"yamux",
""
];
public static readonly List<string> TuicCongestionControls =
[
"cubic",
"new_reno",
"bbr"
"new_reno",
"bbr"
];
public static readonly List<string> allowSelectType =
[
"selector",
"urltest",
"loadbalance",
"fallback"
"urltest",
"loadbalance",
"fallback"
];
public static readonly List<string> notAllowTestType =
[
"selector",
"urltest",
"direct",
"reject",
"compatible",
"pass",
"loadbalance",
"fallback"
"urltest",
"direct",
"reject",
"compatible",
"pass",
"loadbalance",
"fallback"
];
public static readonly List<string> proxyVehicleType =
[
"file",
"http"
"http"
];
public static readonly Dictionary<ECoreType, string> CoreUrls = new()
{
{ ECoreType.v2fly, "v2fly/v2ray-core" },
{ ECoreType.v2fly_v5, "v2fly/v2ray-core" },
{ ECoreType.Xray, "XTLS/Xray-core" },
{ ECoreType.sing_box, "SagerNet/sing-box" },
{ ECoreType.mihomo, "MetaCubeX/mihomo" },
{ ECoreType.hysteria, "apernet/hysteria" },
{ ECoreType.hysteria2, "apernet/hysteria" },
{ ECoreType.naiveproxy, "klzgrad/naiveproxy" },
{ ECoreType.tuic, "EAimTY/tuic" },
{ ECoreType.juicity, "juicity/juicity" },
{ ECoreType.brook, "txthinking/brook" },
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
{ ECoreType.mieru, "enfein/mieru" },
{ ECoreType.v2rayN, "2dust/v2rayN" },
};
{
{ ECoreType.v2fly, "v2fly/v2ray-core" },
{ ECoreType.v2fly_v5, "v2fly/v2ray-core" },
{ ECoreType.Xray, "XTLS/Xray-core" },
{ ECoreType.sing_box, "SagerNet/sing-box" },
{ ECoreType.mihomo, "MetaCubeX/mihomo" },
{ ECoreType.hysteria, "apernet/hysteria" },
{ ECoreType.hysteria2, "apernet/hysteria" },
{ ECoreType.naiveproxy, "klzgrad/naiveproxy" },
{ ECoreType.tuic, "EAimTY/tuic" },
{ ECoreType.juicity, "juicity/juicity" },
{ ECoreType.brook, "txthinking/brook" },
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
{ ECoreType.mieru, "enfein/mieru" },
{ ECoreType.v2rayN, "2dust/v2rayN" },
};
public static readonly List<string> OtherGeoUrls =
[
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat",
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
@"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"
@"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
@"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"
];
public static readonly List<string> IPAPIUrls =
[
@"https://speed.cloudflare.com/meta",
@"https://api.ip.sb/geoip",
@"https://api-ipv4.ip.sb/geoip",
@"https://api-ipv6.ip.sb/geoip",
@@ -601,29 +610,37 @@ public class Global
];
public static readonly Dictionary<string, List<string>> PredefinedHosts = new()
{
{ "dns.google", new List<string> { "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844" } },
{ "dns.alidns.com", new List<string> { "223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1" } },
{ "one.one.one.one", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
{ "1dot1dot1dot1.cloudflare-dns.com", new List<string> { "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001" } },
{ "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } },
{ "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } },
{ "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
{ "doh.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
{ "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } },
{ "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } },
{ "dns.sb", new List<string> { "185.222.222.222", "2a09::" } },
{ "dns.umbrella.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
{ "dns.sse.cisco.com", new List<string> { "208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53" } },
{ "engage.cloudflareclient.com", new List<string> { "162.159.192.1", "2606:4700:d0::a29f:c001" } }
};
{
{ "dns.google", ["8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844"] },
{ "dns.alidns.com", ["223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1"] },
{ "one.one.one.one", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] },
{ "1dot1dot1dot1.cloudflare-dns.com", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] },
{ "cloudflare-dns.com", ["104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9"] },
{ "dns.cloudflare.com", ["104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5"] },
{ "dot.pub", ["1.12.12.12", "120.53.53.53"] },
{ "doh.pub", ["1.12.12.12", "120.53.53.53"] },
{ "dns.quad9.net", ["9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9"] },
{ "dns.yandex.net", ["77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff"] },
{ "dns.sb", ["185.222.222.222", "2a09::"] },
{ "dns.umbrella.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] },
{ "dns.sse.cisco.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] },
{ "engage.cloudflareclient.com", ["162.159.192.1"] }
};
public static readonly List<string> ExpectedIPs =
[
"geoip:cn",
"geoip:ir",
"geoip:ru",
""
"geoip:ir",
"geoip:ru",
""
];
public static readonly List<string> EchForceQuerys =
[
"none",
"half",
"full",
""
];
#endregion const

View File

@@ -114,6 +114,8 @@ public static class ConfigHandler
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
config.SimpleDNSItem.GlobalFakeIp ??= true;
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
config.SimpleDNSItem.ServeStale ??= false;
config.SimpleDNSItem.ParallelQuery ??= false;
config.SpeedTestItem ??= new();
if (config.SpeedTestItem.SpeedTestTimeout < 10)
@@ -228,12 +230,8 @@ public static class ConfigHandler
item.Remarks = profileItem.Remarks;
item.Address = profileItem.Address;
item.Port = profileItem.Port;
item.Ports = profileItem.Ports;
item.Id = profileItem.Id;
item.AlterId = profileItem.AlterId;
item.Security = profileItem.Security;
item.Flow = profileItem.Flow;
item.Password = profileItem.Password;
item.Network = profileItem.Network;
item.HeaderType = profileItem.HeaderType;
@@ -253,6 +251,11 @@ public static class ConfigHandler
item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled;
item.Cert = profileItem.Cert;
item.CertSha = profileItem.CertSha;
item.EchConfigList = profileItem.EchConfigList;
item.EchForceQuery = profileItem.EchForceQuery;
item.Finalmask = profileItem.Finalmask;
item.ProtoExtra = profileItem.ProtoExtra;
}
var ret = item.ConfigType switch
@@ -285,19 +288,22 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.VMess;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{
VmessSecurity = profileItem.GetProtocolExtra().VmessSecurity?.TrimEx()
});
profileItem.Network = profileItem.Network.TrimEx();
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
profileItem.Path = profileItem.Path.TrimEx();
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
if (!Global.VmessSecurities.Contains(profileItem.Security))
if (!Global.VmessSecurities.Contains(profileItem.GetProtocolExtra().VmessSecurity))
{
return -1;
}
if (profileItem.Id.IsNullOrEmpty())
if (profileItem.Password.IsNullOrEmpty())
{
return -1;
}
@@ -355,11 +361,6 @@ public static class ConfigHandler
{
}
}
else if (profileItem.ConfigType.IsGroupType())
{
var profileGroupItem = await AppManager.Instance.GetProfileGroupItem(it.IndexId);
await AddGroupServerCommon(config, profileItem, profileGroupItem, true);
}
else
{
await AddServerCommon(config, profileItem, true);
@@ -605,14 +606,17 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.Shadowsocks;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{
SsMethod = profileItem.GetProtocolExtra().SsMethod?.TrimEx()
});
if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.GetProtocolExtra().SsMethod))
{
return -1;
}
if (profileItem.Id.IsNullOrEmpty())
if (profileItem.Password.IsNullOrEmpty())
{
return -1;
}
@@ -673,12 +677,12 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.Trojan;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
if (profileItem.StreamSecurity.IsNullOrEmpty())
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Id.IsNullOrEmpty())
if (profileItem.Password.IsNullOrEmpty())
{
return -1;
}
@@ -700,21 +704,27 @@ public static class ConfigHandler
public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Hysteria2;
profileItem.CoreType = ECoreType.sing_box;
//profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Path = profileItem.Path.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
profileItem.Network = string.Empty;
if (profileItem.StreamSecurity.IsNullOrEmpty())
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Id.IsNullOrEmpty())
if (profileItem.Password.IsNullOrEmpty())
{
return -1;
}
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{
SalamanderPass = profileItem.GetProtocolExtra().SalamanderPass?.TrimEx(),
UpMbps = profileItem.GetProtocolExtra().UpMbps is null or < 0 ? config.HysteriaItem.UpMbps : profileItem.GetProtocolExtra().UpMbps,
DownMbps = profileItem.GetProtocolExtra().DownMbps is null or < 0 ? config.HysteriaItem.DownMbps : profileItem.GetProtocolExtra().DownMbps,
HopInterval = profileItem.GetProtocolExtra().HopInterval?.TrimEx(),
});
await AddServerCommon(config, profileItem, toFile);
@@ -736,8 +746,8 @@ public static class ConfigHandler
profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Username = profileItem.Username.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
profileItem.Network = string.Empty;
if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType))
@@ -753,7 +763,7 @@ public static class ConfigHandler
{
profileItem.Alpn = "h3";
}
if (profileItem.Id.IsNullOrEmpty())
if (profileItem.Password.IsNullOrEmpty())
{
return -1;
}
@@ -776,17 +786,17 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.WireGuard;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.PublicKey = profileItem.PublicKey.TrimEx();
profileItem.Path = profileItem.Path.TrimEx();
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
profileItem.Network = string.Empty;
if (profileItem.ShortId.IsNullOrEmpty())
profileItem.Password = profileItem.Password.TrimEx();
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{
profileItem.ShortId = Global.TunMtus.First().ToString();
}
WgPublicKey = profileItem.GetProtocolExtra().WgPublicKey?.TrimEx(),
WgPresharedKey = profileItem.GetProtocolExtra().WgPresharedKey?.TrimEx(),
WgInterfaceAddress = profileItem.GetProtocolExtra().WgInterfaceAddress?.TrimEx(),
WgReserved = profileItem.GetProtocolExtra().WgReserved?.TrimEx(),
WgMtu = profileItem.GetProtocolExtra().WgMtu is null or <= 0 ? Global.TunMtus.First() : profileItem.GetProtocolExtra().WgMtu,
});
if (profileItem.Id.IsNullOrEmpty())
if (profileItem.Password.IsNullOrEmpty())
{
return -1;
}
@@ -810,14 +820,13 @@ public static class ConfigHandler
profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
profileItem.Network = string.Empty;
if (profileItem.StreamSecurity.IsNullOrEmpty())
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Id.IsNullOrEmpty())
if (profileItem.Password.IsNullOrEmpty())
{
return -1;
}
@@ -836,7 +845,7 @@ public static class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> SortServers(Config config, string subId, string colName, bool asc)
{
var lstModel = await AppManager.Instance.ProfileItems(subId, "");
var lstModel = await AppManager.Instance.ProfileModels(subId, "");
if (lstModel.Count <= 0)
{
return -1;
@@ -855,7 +864,7 @@ public static class ConfigHandler
Remarks = t.Remarks,
Address = t.Address,
Port = t.Port,
Security = t.Security,
//Security = t.Security,
Network = t.Network,
StreamSecurity = t.StreamSecurity,
Delay = t33?.Delay ?? 0,
@@ -954,26 +963,25 @@ public static class ConfigHandler
profileItem.ConfigType = EConfigType.VLESS;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Password = profileItem.Password.TrimEx();
profileItem.Network = profileItem.Network.TrimEx();
profileItem.HeaderType = profileItem.HeaderType.TrimEx();
profileItem.RequestHost = profileItem.RequestHost.TrimEx();
profileItem.Path = profileItem.Path.TrimEx();
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
if (!Global.Flows.Contains(profileItem.Flow))
var vlessEncryption = profileItem.GetProtocolExtra().VlessEncryption?.TrimEx();
var flow = profileItem.GetProtocolExtra().Flow?.TrimEx() ?? string.Empty;
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
{
profileItem.Flow = Global.Flows.First();
}
if (profileItem.Id.IsNullOrEmpty())
VlessEncryption = vlessEncryption.IsNullOrEmpty() ? Global.None : vlessEncryption,
Flow = Global.Flows.Contains(flow) ? flow : Global.Flows.First(),
});
if (profileItem.Password.IsNullOrEmpty())
{
return -1;
}
if (profileItem.Security.IsNullOrEmpty())
{
profileItem.Security = Global.None;
}
await AddServerCommon(config, profileItem, toFile);
@@ -1028,7 +1036,7 @@ public static class ConfigHandler
/// <returns>0 if successful</returns>
public static async Task<int> AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigVersion = 2;
profileItem.ConfigVersion = 3;
if (profileItem.StreamSecurity.IsNotEmpty())
{
@@ -1072,42 +1080,12 @@ public static class ConfigHandler
if (toFile)
{
profileItem.SetProtocolExtra();
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
}
return 0;
}
public static async Task<int> AddGroupServerCommon(Config config, ProfileItem profileItem, ProfileGroupItem profileGroupItem, bool toFile = true)
{
var maxSort = -1;
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExManager.Instance.GetMaxSort();
}
var groupType = profileItem.ConfigType == EConfigType.ProxyChain ? EConfigType.ProxyChain.ToString() : profileGroupItem.MultipleLoad.ToString();
profileItem.Address = $"{profileItem.CoreType}-{groupType}";
if (maxSort > 0)
{
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
}
if (toFile)
{
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
if (profileGroupItem != null)
{
profileGroupItem.IndexId = profileItem.IndexId;
await ProfileGroupItemManager.Instance.SaveItemAsync(profileGroupItem);
}
else
{
ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(profileItem.IndexId);
await ProfileGroupItemManager.Instance.SaveTo();
}
}
return 0;
}
/// <summary>
/// Compare two profile items to determine if they represent the same server
/// Used for deduplication and server matching
@@ -1123,22 +1101,29 @@ public static class ConfigHandler
return false;
}
var oProtocolExtra = o.GetProtocolExtra();
var nProtocolExtra = n.GetProtocolExtra();
return o.ConfigType == n.ConfigType
&& AreEqual(o.Address, n.Address)
&& o.Port == n.Port
&& AreEqual(o.Id, n.Id)
&& AreEqual(o.Security, n.Security)
&& AreEqual(o.Password, n.Password)
&& AreEqual(oProtocolExtra.VlessEncryption, nProtocolExtra.VlessEncryption)
&& AreEqual(oProtocolExtra.SsMethod, nProtocolExtra.SsMethod)
&& AreEqual(oProtocolExtra.VmessSecurity, nProtocolExtra.VmessSecurity)
&& AreEqual(o.Network, n.Network)
&& AreEqual(o.HeaderType, n.HeaderType)
&& AreEqual(o.RequestHost, n.RequestHost)
&& AreEqual(o.Path, n.Path)
&& (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity)
&& AreEqual(o.Flow, n.Flow)
&& AreEqual(oProtocolExtra.Flow, nProtocolExtra.Flow)
&& AreEqual(oProtocolExtra.SalamanderPass, nProtocolExtra.SalamanderPass)
&& AreEqual(o.Sni, n.Sni)
&& AreEqual(o.Alpn, n.Alpn)
&& AreEqual(o.Fingerprint, n.Fingerprint)
&& AreEqual(o.PublicKey, n.PublicKey)
&& AreEqual(o.ShortId, n.ShortId)
&& AreEqual(o.Finalmask, n.Finalmask)
&& (!remarks || o.Remarks == n.Remarks);
static bool AreEqual(string? a, string? b)
@@ -1194,7 +1179,7 @@ public static class ConfigHandler
var indexId = Utils.GetGuid(false);
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId)).Remarks} ";
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId))?.Remarks} ";
if (coreType == ECoreType.Xray)
{
remark += multipleLoad switch
@@ -1228,13 +1213,13 @@ public static class ConfigHandler
{
profile.Subid = subId;
}
var profileGroup = new ProfileGroupItem
var extraItem = new ProtocolExtraItem
{
ChildItems = childProfileIndexId,
MultipleLoad = multipleLoad,
IndexId = indexId,
};
var ret = await AddGroupServerCommon(config, profile, profileGroup, true);
profile.SetProtocolExtra(extraItem);
var ret = await AddServerCommon(config, profile, true);
result.Success = ret == 0;
result.Data = indexId;
return result;
@@ -1248,44 +1233,65 @@ public static class ConfigHandler
/// <param name="node">Server node that might need pre-SOCKS</param>
/// <param name="coreType">Core type being used</param>
/// <returns>A SOCKS profile item or null if not needed</returns>
public static async Task<ProfileItem?> GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
public static ProfileItem? GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
{
if (node.ConfigType != EConfigType.Custom || !(node.PreSocksPort > 0))
{
return null;
}
ProfileItem? itemSocks = null;
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
var preCoreType = AppManager.Instance.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem()
{
var tun2SocksAddress = node.Address;
if (node.ConfigType.IsGroupType())
{
var lstAddresses = (await ProfileGroupItemManager.GetAllChildDomainAddresses(node.IndexId)).ToList();
if (lstAddresses.Count > 0)
{
tun2SocksAddress = Utils.List2String(lstAddresses);
}
}
itemSocks = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.SOCKS,
Address = Global.Loopback,
SpiderX = tun2SocksAddress, // Tun2SocksAddress
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
};
}
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)
{
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem()
{
CoreType = preCoreType,
ConfigType = EConfigType.SOCKS,
Address = Global.Loopback,
Port = node.PreSocksPort.Value,
};
}
await Task.CompletedTask;
CoreType = preCoreType,
ConfigType = EConfigType.SOCKS,
Address = Global.Loopback,
Port = node.PreSocksPort.Value,
};
return itemSocks;
}
public static CoreConfigContext? GetPreSocksCoreConfigContext(CoreConfigContext nodeContext)
{
var config = nodeContext.AppConfig;
var node = nodeContext.Node;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var preSocksItem = GetPreSocksItem(config, node, coreType);
if (preSocksItem != null)
{
return nodeContext with { Node = preSocksItem, };
}
if ((!nodeContext.IsTunEnabled)
|| coreType != ECoreType.Xray
|| node.ConfigType == EConfigType.Custom)
{
return null;
}
var tunProtectSsPort = Utils.GetFreePort();
var proxyRelaySsPort = Utils.GetFreePort();
var preItem = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = proxyRelaySsPort,
Password = Global.None,
};
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
{
SsMethod = Global.None,
});
var preContext = nodeContext with
{
Node = preItem,
TunProtectSsPort = tunProtectSsPort,
ProxyRelaySsPort = proxyRelaySsPort,
};
return preContext;
}
/// <summary>
/// Remove servers with invalid test results (timeout)
/// Useful for cleaning up subscription lists
@@ -1295,7 +1301,7 @@ public static class ConfigHandler
/// <returns>Number of removed servers or -1 if failed</returns>
public static async Task<int> RemoveInvalidServerResult(Config config, string subid)
{
var lstModel = await AppManager.Instance.ProfileItems(subid, "");
var lstModel = await AppManager.Instance.ProfileModels(subid, "");
if (lstModel is { Count: <= 0 })
{
return -1;

View File

@@ -7,27 +7,27 @@ public static class CoreConfigHandler
{
private static readonly string _tag = "CoreConfigHandler";
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
public static async Task<RetResult> GenerateClientConfig(CoreConfigContext context, string? fileName)
{
var config = AppManager.Instance.Config;
var result = new RetResult();
var node = context.Node;
if (node.ConfigType == EConfigType.Custom)
{
result = node.CoreType switch
{
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName),
_ => await GenerateClientCustomConfig(node, fileName)
};
}
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
}
else
{
result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node);
result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
}
if (result.Success != true)
{
@@ -93,13 +93,26 @@ public static class CoreConfigHandler
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType)
{
var result = new RetResult();
var context = await BuildCoreConfigContext(config, new());
var ids = selecteds.Where(serverTestItem => !serverTestItem.IndexId.IsNullOrEmpty())
.Select(serverTestItem => serverTestItem.IndexId);
var nodes = await AppManager.Instance.GetProfileItemsByIndexIds(ids);
foreach (var node in nodes)
{
var actNode = await FillNodeContext(context, node, true);
if (node.IndexId == actNode.IndexId)
{
continue;
}
context.ServerTestItemMap[node.IndexId] = actNode.IndexId;
}
if (coreType == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds);
result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(selecteds);
}
else if (coreType == ECoreType.Xray)
{
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds);
result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(selecteds);
}
if (result.Success != true)
{
@@ -109,20 +122,21 @@ public static class CoreConfigHandler
return result;
}
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, CoreConfigContext context, ServerTestItem testItem, string fileName)
{
var result = new RetResult();
var node = context.Node;
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
testItem.Port = port;
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port);
}
else
{
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port);
result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(port);
}
if (result.Success != true)
{
@@ -132,4 +146,128 @@ public static class CoreConfigHandler
await File.WriteAllTextAsync(fileName, result.Data.ToString());
return result;
}
public static async Task<CoreConfigContext> BuildCoreConfigContext(Config config, ProfileItem node)
{
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray;
var context = new CoreConfigContext()
{
Node = node,
AllProxiesMap = [],
AppConfig = config,
FullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(coreType),
IsTunEnabled = config.TunModeItem.EnableTun,
SimpleDnsItem = config.SimpleDNSItem,
ProtectDomainList = [],
TunProtectSsPort = 0,
ProxyRelaySsPort = 0,
RawDnsItem = await AppManager.Instance.GetDNSItem(coreType),
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
};
context = context with
{
Node = await FillNodeContext(context, node)
};
if (!(context.RoutingItem?.RuleSet.IsNullOrEmpty() ?? true))
{
var rules = JsonUtils.Deserialize<List<RulesItem>>(context.RoutingItem?.RuleSet);
foreach (var ruleItem in rules.Where(ruleItem => !Global.OutboundTags.Contains(ruleItem.OutboundTag)))
{
var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag);
if (ruleOutboundNode != null)
{
var ruleOutboundNodeAct = await FillNodeContext(context, ruleOutboundNode, false);
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = ruleOutboundNodeAct;
}
}
}
return context;
}
private static async Task<ProfileItem> FillNodeContext(CoreConfigContext context, ProfileItem node, bool includeSubChain = true)
{
if (node.IndexId.IsNullOrEmpty())
{
return node;
}
var newItems = new List<ProfileItem> { node };
if (node.ConfigType.IsGroupType())
{
var (groupChildList, _) = await GroupProfileManager.GetChildProfileItems(node);
foreach (var childItem in groupChildList.Where(childItem => !context.AllProxiesMap.ContainsKey(childItem.IndexId)))
{
await FillNodeContext(context, childItem, false);
}
node.SetProtocolExtra(node.GetProtocolExtra() with
{
ChildItems = Utils.List2String(groupChildList.Select(n => n.IndexId).ToList()),
});
newItems.AddRange(groupChildList);
}
context.AllProxiesMap[node.IndexId] = node;
foreach (var item in newItems)
{
var address = item.Address;
if (Utils.IsDomain(address))
{
context.ProtectDomainList.Add(address);
}
if (item.EchConfigList.IsNullOrEmpty())
{
continue;
}
var echQuerySni = item.Sni;
if (item.StreamSecurity == Global.StreamSecurity
&& item.EchConfigList?.Contains("://") == true)
{
var idx = item.EchConfigList.IndexOf('+');
echQuerySni = idx > 0 ? item.EchConfigList[..idx] : item.Sni;
}
if (!Utils.IsDomain(echQuerySni))
{
continue;
}
context.ProtectDomainList.Add(echQuerySni);
}
if (!includeSubChain || node.Subid.IsNullOrEmpty())
{
return node;
}
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem == null)
{
return node;
}
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (prevNode is null && nextNode is null)
{
return node;
}
var prevNodeAct = prevNode is null ? null : await FillNodeContext(context, prevNode, false);
var nextNodeAct = nextNode is null ? null : await FillNodeContext(context, nextNode, false);
// Build new proxy chain node
var chainNode = new ProfileItem()
{
IndexId = $"inner-{Utils.GetGuid(false)}",
ConfigType = EConfigType.ProxyChain,
CoreType = node.CoreType ?? ECoreType.Xray,
};
List<string?> childItems = [prevNodeAct?.IndexId, node.IndexId, nextNodeAct?.IndexId];
var chainExtraItem = chainNode.GetProtocolExtra() with
{
GroupType = chainNode.ConfigType.ToString(),
ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())),
};
chainNode.SetProtocolExtra(chainExtraItem);
context.AllProxiesMap[chainNode.IndexId] = chainNode;
return chainNode;
}
}

View File

@@ -20,7 +20,7 @@ public class AnytlsFmt : BaseFmt
Port = parsedUrl.Port,
};
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
item.Id = rawUserInfo;
item.Password = rawUserInfo;
var query = Utils.ParseQueryString(parsedUrl.Query);
ResolveUriQuery(query, ref item);
@@ -39,7 +39,7 @@ public class AnytlsFmt : BaseFmt
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var pw = item.Id;
var pw = item.Password;
var dicQuery = new Dictionary<string, string>();
ToUriQuery(item, Global.None, ref dicQuery);

View File

@@ -4,7 +4,7 @@ namespace ServiceLib.Handler.Fmt;
public class BaseFmt
{
private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure", "verify" };
private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure" };
protected static string GetIpv6(string address)
{
@@ -21,11 +21,6 @@ public class BaseFmt
protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
{
if (item.Flow.IsNotEmpty())
{
dicQuery.Add("flow", item.Flow);
}
if (item.StreamSecurity.IsNotEmpty())
{
dicQuery.Add("security", item.StreamSecurity);
@@ -70,6 +65,27 @@ public class BaseFmt
}
ToUriQueryAllowInsecure(item, ref dicQuery);
}
if (item.EchConfigList.IsNotEmpty())
{
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
}
if (item.CertSha.IsNotEmpty())
{
dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha));
}
if (item.Finalmask.IsNotEmpty())
{
var node = JsonUtils.ParseJson(item.Finalmask);
var finalmask = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
: item.Finalmask;
dicQuery.Add("fm", Utils.UrlEncode(finalmask));
}
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
@@ -118,7 +134,16 @@ public class BaseFmt
}
if (item.Extra.IsNotEmpty())
{
dicQuery.Add("extra", Utils.UrlEncode(item.Extra));
var node = JsonUtils.ParseJson(item.Extra);
var extra = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
: item.Extra;
dicQuery.Add("extra", Utils.UrlEncode(extra));
}
break;
@@ -191,7 +216,6 @@ public class BaseFmt
protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item)
{
item.Flow = GetQueryValue(query, "flow");
item.StreamSecurity = GetQueryValue(query, "security");
item.Sni = GetQueryValue(query, "sni");
item.Alpn = GetQueryDecoded(query, "alpn");
@@ -200,6 +224,26 @@ public class BaseFmt
item.ShortId = GetQueryDecoded(query, "sid");
item.SpiderX = GetQueryDecoded(query, "spx");
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
item.EchConfigList = GetQueryDecoded(query, "ech");
item.CertSha = GetQueryDecoded(query, "pcs");
var finalmaskDecoded = GetQueryDecoded(query, "fm");
if (finalmaskDecoded.IsNotEmpty())
{
var node = JsonUtils.ParseJson(finalmaskDecoded);
item.Finalmask = node != null
? JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
: finalmaskDecoded;
}
else
{
item.Finalmask = string.Empty;
}
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
{
@@ -237,7 +281,21 @@ public class BaseFmt
item.RequestHost = GetQueryDecoded(query, "host");
item.Path = GetQueryDecoded(query, "path", "/");
item.HeaderType = GetQueryDecoded(query, "mode");
item.Extra = GetQueryDecoded(query, "extra");
var extraDecoded = GetQueryDecoded(query, "extra");
if (extraDecoded.IsNotEmpty())
{
var node = JsonUtils.ParseJson(extraDecoded);
if (node != null)
{
extraDecoded = JsonUtils.Serialize(node, new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
}
}
item.Extra = extraDecoded;
break;
case nameof(ETransport.http):

View File

@@ -19,12 +19,19 @@ public class Hysteria2Fmt : BaseFmt
item.Address = url.IdnHost;
item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo);
item.Password = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item);
item.Path = GetQueryDecoded(query, "obfs-password");
item.Ports = GetQueryDecoded(query, "mport");
if (item.CertSha.IsNullOrEmpty())
{
item.CertSha = GetQueryDecoded(query, "pinSHA256");
}
item.SetProtocolExtra(item.GetProtocolExtra() with
{
Ports = GetQueryDecoded(query, "mport"),
SalamanderPass = GetQueryDecoded(query, "obfs-password"),
});
return item;
}
@@ -45,18 +52,29 @@ public class Hysteria2Fmt : BaseFmt
}
var dicQuery = new Dictionary<string, string>();
ToUriQueryLite(item, ref dicQuery);
var protocolExtraItem = item.GetProtocolExtra();
if (item.Path.IsNotEmpty())
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
{
dicQuery.Add("obfs", "salamander");
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path));
dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass));
}
if (item.Ports.IsNotEmpty())
if (!protocolExtraItem.Ports.IsNullOrEmpty())
{
dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-')));
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));
}
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark);
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Password, dicQuery, remark);
}
public static ProfileItem? ResolveFull2(string strData, string? subRemarks)

View File

@@ -12,7 +12,8 @@ public class ShadowsocksFmt : BaseFmt
{
return null;
}
if (item.Address.Length == 0 || item.Port == 0 || item.Security.Length == 0 || item.Id.Length == 0)
if (item.Address.Length == 0 || item.Port == 0 || item.GetProtocolExtra().SsMethod.IsNullOrEmpty() || item.Password.Length == 0)
{
return null;
}
@@ -40,8 +41,69 @@ public class ShadowsocksFmt : BaseFmt
// item.port);
//url = Utile.Base64Encode(url);
//new Sip002
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark);
var pw = Utils.Base64Encode($"{item.GetProtocolExtra().SsMethod}:{item.Password}", true);
// plugin
var plugin = string.Empty;
var pluginArgs = string.Empty;
if (item.Network == nameof(ETransport.tcp) && item.HeaderType == Global.TcpHeaderHttp)
{
plugin = "obfs-local";
pluginArgs = $"obfs=http;obfs-host={item.RequestHost};";
}
else
{
if (item.Network == nameof(ETransport.ws))
{
pluginArgs += "mode=websocket;";
pluginArgs += $"host={item.RequestHost};";
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
// Equal signs and commas [and backslashes] must be escaped with a backslash.
var path = item.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
pluginArgs += $"path={path};";
}
else if (item.Network == nameof(ETransport.quic))
{
pluginArgs += "mode=quic;";
}
if (item.StreamSecurity == Global.StreamSecurity)
{
pluginArgs += "tls;";
var certs = CertPemManager.ParsePemChain(item.Cert);
if (certs.Count > 0)
{
var cert = certs.First();
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
const string endMarker = "\n-----END CERTIFICATE-----";
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
base64Content = base64Content.Replace("=", "\\=");
pluginArgs += $"certRaw={base64Content};";
}
}
if (pluginArgs.Length > 0)
{
plugin = "v2ray-plugin";
pluginArgs += "mux=0;";
}
}
var dicQuery = new Dictionary<string, string>();
if (plugin.IsNotEmpty())
{
var pluginStr = plugin + ";" + pluginArgs;
// pluginStr remove last ';' and url encode
if (pluginStr.EndsWith(';'))
{
pluginStr = pluginStr[..^1];
}
dicQuery["plugin"] = Utils.UrlEncode(pluginStr);
}
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, dicQuery, remark);
}
private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
@@ -75,8 +137,8 @@ public class ShadowsocksFmt : BaseFmt
{
return null;
}
item.Security = details.Groups["method"].Value;
item.Id = details.Groups["password"].Value;
item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = details.Groups["method"].Value });
item.Password = details.Groups["password"].Value;
item.Address = details.Groups["hostname"].Value;
item.Port = details.Groups["port"].Value.ToInt();
return item;
@@ -105,8 +167,8 @@ public class ShadowsocksFmt : BaseFmt
{
return null;
}
item.Security = userInfoParts.First();
item.Id = Utils.UrlDecode(userInfoParts.Last());
item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
item.Password = Utils.UrlDecode(userInfoParts.Last());
}
else
{
@@ -117,28 +179,103 @@ public class ShadowsocksFmt : BaseFmt
{
return null;
}
item.Security = userInfoParts.First();
item.Id = userInfoParts.Last();
item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
item.Password = userInfoParts.Last();
}
var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
if (queryParameters["plugin"] != null)
{
//obfs-host exists
var obfsHost = queryParameters["plugin"]?.Split(';').FirstOrDefault(t => t.Contains("obfs-host"));
if (queryParameters["plugin"].Contains("obfs=http") && obfsHost.IsNotEmpty())
{
obfsHost = obfsHost?.Replace("obfs-host=", "");
item.Network = Global.DefaultNetwork;
item.HeaderType = Global.TcpHeaderHttp;
item.RequestHost = obfsHost ?? "";
}
else
var pluginStr = queryParameters["plugin"];
var pluginParts = pluginStr.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
if (pluginParts.Length == 0)
{
return null;
}
}
var pluginName = pluginParts[0];
// A typo in https://github.com/shadowsocks/shadowsocks-org/blob/6b1c064db4129de99c516294960e731934841c94/docs/doc/sip002.md?plain=1#L15
// "simple-obfs" should be "obfs-local"
if (pluginName == "simple-obfs")
{
pluginName = "obfs-local";
}
// Parse obfs-local plugin
if (pluginName == "obfs-local")
{
var obfsMode = pluginParts.FirstOrDefault(t => t.StartsWith("obfs="));
var obfsHost = pluginParts.FirstOrDefault(t => t.StartsWith("obfs-host="));
if ((!obfsMode.IsNullOrEmpty()) && obfsMode.Contains("obfs=http") && obfsHost.IsNotEmpty())
{
obfsHost = obfsHost.Replace("obfs-host=", "");
item.Network = Global.DefaultNetwork;
item.HeaderType = Global.TcpHeaderHttp;
item.RequestHost = obfsHost;
}
}
// Parse v2ray-plugin
else if (pluginName == "v2ray-plugin")
{
var mode = pluginParts.FirstOrDefault(t => t.StartsWith("mode="), "websocket");
var host = pluginParts.FirstOrDefault(t => t.StartsWith("host="));
var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
var hasTls = pluginParts.Any(t => t == "tls");
var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
var mux = pluginParts.FirstOrDefault(t => t.StartsWith("mux="));
var modeValue = mode.Replace("mode=", "");
if (modeValue == "websocket")
{
item.Network = nameof(ETransport.ws);
if (!host.IsNullOrEmpty())
{
item.RequestHost = host.Replace("host=", "");
item.Sni = item.RequestHost;
}
if (!path.IsNullOrEmpty())
{
var pathValue = path.Replace("path=", "");
pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
item.Path = pathValue;
}
}
else if (modeValue == "quic")
{
item.Network = nameof(ETransport.quic);
}
if (hasTls)
{
item.StreamSecurity = Global.StreamSecurity;
if (!certRaw.IsNullOrEmpty())
{
var certBase64 = certRaw.Replace("certRaw=", "");
certBase64 = certBase64.Replace("\\=", "=");
const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
const string endMarker = "\n-----END CERTIFICATE-----";
var certPem = beginMarker + certBase64 + endMarker;
item.Cert = certPem;
}
}
if (!mux.IsNullOrEmpty())
{
var muxValue = mux.Replace("mux=", "");
var muxCount = muxValue.ToInt();
if (muxCount > 0)
{
return null;
}
}
}
}
return item;
}
@@ -163,11 +300,11 @@ public class ShadowsocksFmt : BaseFmt
var ssItem = new ProfileItem()
{
Remarks = it.remarks,
Security = it.method,
Id = it.password,
Password = it.password,
Address = it.server,
Port = it.server_port.ToInt()
};
ssItem.SetProtocolExtra(new ProtocolExtraItem() { SsMethod = it.method });
lst.Add(ssItem);
}
return lst;

View File

@@ -33,7 +33,7 @@ public class SocksFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks);
}
//new
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
var pw = Utils.Base64Encode($"{item.Username}:{item.Password}", true);
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
}
@@ -45,18 +45,18 @@ public class SocksFmt : BaseFmt
};
result = result[Global.ProtocolShares[EConfigType.SOCKS].Length..];
//remark
var indexRemark = result.IndexOf("#");
var indexRemark = result.IndexOf('#');
if (indexRemark > 0)
{
try
{
item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1, result.Length - indexRemark - 1));
item.Remarks = Utils.UrlDecode(result.Substring(indexRemark + 1));
}
catch { }
result = result[..indexRemark];
}
//part decode
var indexS = result.IndexOf("@");
var indexS = result.IndexOf('@');
if (indexS > 0)
{
}
@@ -78,9 +78,8 @@ public class SocksFmt : BaseFmt
}
item.Address = arr1[1][..indexPort];
item.Port = arr1[1][(indexPort + 1)..].ToInt();
item.Security = arr21.First();
item.Id = arr21[1];
item.Username = arr21.First();
item.Password = arr21[1];
return item;
}
@@ -98,15 +97,14 @@ public class SocksFmt : BaseFmt
Address = parsedUrl.IdnHost,
Port = parsedUrl.Port,
};
// parse base64 UserInfo
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
var userInfo = Utils.Base64Decode(rawUserInfo);
var userInfoParts = userInfo.Split([':'], 2);
if (userInfoParts.Length == 2)
{
item.Security = userInfoParts.First();
item.Id = userInfoParts[1];
item.Username = userInfoParts.First();
item.Password = userInfoParts[1];
}
return item;

View File

@@ -20,9 +20,10 @@ public class TrojanFmt : BaseFmt
item.Address = url.IdnHost;
item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo);
item.Password = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query);
item.SetProtocolExtra(item.GetProtocolExtra() with { Flow = GetQueryValue(query, "flow") });
ResolveUriQuery(query, ref item);
return item;
@@ -40,8 +41,12 @@ public class TrojanFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
{
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
}
ToUriQuery(item, null, ref dicQuery);
return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark);
return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Password, dicQuery, remark);
}
}

View File

@@ -24,8 +24,8 @@ public class TuicFmt : BaseFmt
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length == 2)
{
item.Id = userInfoParts.First();
item.Security = userInfoParts.Last();
item.Username = userInfoParts.First();
item.Password = userInfoParts.Last();
}
var query = Utils.ParseQueryString(url.Query);
@@ -53,6 +53,6 @@ public class TuicFmt : BaseFmt
dicQuery.Add("congestion_control", item.HeaderType);
return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark);
return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Username ?? ""}:{item.Password}", dicQuery, remark);
}
}

View File

@@ -9,7 +9,6 @@ public class VLESSFmt : BaseFmt
ProfileItem item = new()
{
ConfigType = EConfigType.VLESS,
Security = Global.None
};
var url = Utils.TryUri(str);
@@ -21,10 +20,14 @@ public class VLESSFmt : BaseFmt
item.Address = url.IdnHost;
item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo);
item.Password = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query);
item.Security = GetQueryValue(query, "encryption", Global.None);
item.SetProtocolExtra(item.GetProtocolExtra() with
{
VlessEncryption = GetQueryValue(query, "encryption", Global.None),
Flow = GetQueryValue(query, "flow")
});
item.StreamSecurity = GetQueryValue(query, "security");
ResolveUriQuery(query, ref item);
@@ -44,16 +47,14 @@ public class VLESSFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var dicQuery = new Dictionary<string, string>();
if (item.Security.IsNotEmpty())
dicQuery.Add("encryption",
!item.GetProtocolExtra().VlessEncryption.IsNullOrEmpty() ? item.GetProtocolExtra().VlessEncryption : Global.None);
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
{
dicQuery.Add("encryption", item.Security);
}
else
{
dicQuery.Add("encryption", Global.None);
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
}
ToUriQuery(item, Global.None, ref dicQuery);
return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark);
return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Password, dicQuery, remark);
}
}

View File

@@ -23,15 +23,16 @@ public class VmessFmt : BaseFmt
{
return null;
}
var vmessQRCode = new VmessQRCode
{
v = item.ConfigVersion,
v = 2,
ps = item.Remarks.TrimEx(),
add = item.Address,
port = item.Port,
id = item.Id,
aid = item.AlterId,
scy = item.Security,
id = item.Password,
aid = int.TryParse(item.GetProtocolExtra()?.AlterId, out var result) ? result : 0,
scy = item.GetProtocolExtra().VmessSecurity ?? "",
net = item.Network,
type = item.HeaderType,
host = item.RequestHost,
@@ -71,15 +72,16 @@ public class VmessFmt : BaseFmt
item.Network = Global.DefaultNetwork;
item.HeaderType = Global.None;
item.ConfigVersion = vmessQRCode.v;
//item.ConfigVersion = vmessQRCode.v;
item.Remarks = Utils.ToString(vmessQRCode.ps);
item.Address = Utils.ToString(vmessQRCode.add);
item.Port = vmessQRCode.port;
item.Id = Utils.ToString(vmessQRCode.id);
item.AlterId = vmessQRCode.aid;
item.Security = Utils.ToString(vmessQRCode.scy);
item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity;
item.Password = Utils.ToString(vmessQRCode.id);
item.SetProtocolExtra(new ProtocolExtraItem
{
AlterId = vmessQRCode.aid.ToString(),
VmessSecurity = vmessQRCode.scy.IsNullOrEmpty() ? Global.DefaultSecurity : vmessQRCode.scy,
});
if (vmessQRCode.net.IsNotEmpty())
{
item.Network = vmessQRCode.net;
@@ -105,7 +107,6 @@ public class VmessFmt : BaseFmt
var item = new ProfileItem
{
ConfigType = EConfigType.VMess,
Security = "auto"
};
var url = Utils.TryUri(str);
@@ -117,7 +118,12 @@ public class VmessFmt : BaseFmt
item.Address = url.IdnHost;
item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo);
item.Password = Utils.UrlDecode(url.UserInfo);
item.SetProtocolExtra(new ProtocolExtraItem
{
VmessSecurity = "auto",
});
var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item);

View File

@@ -20,14 +20,17 @@ public class WireguardFmt : BaseFmt
item.Address = url.IdnHost;
item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
item.Id = Utils.UrlDecode(url.UserInfo);
item.Password = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query);
item.PublicKey = GetQueryDecoded(query, "publickey");
item.Path = GetQueryDecoded(query, "reserved");
item.RequestHost = GetQueryDecoded(query, "address");
item.ShortId = GetQueryDecoded(query, "mtu");
item.SetProtocolExtra(item.GetProtocolExtra() with
{
WgPublicKey = GetQueryDecoded(query, "publickey"),
WgReserved = GetQueryDecoded(query, "reserved"),
WgInterfaceAddress = GetQueryDecoded(query, "address"),
WgMtu = int.TryParse(GetQueryDecoded(query, "mtu"), out var mtuVal) ? mtuVal : 1280,
});
return item;
}
@@ -46,22 +49,19 @@ public class WireguardFmt : BaseFmt
}
var dicQuery = new Dictionary<string, string>();
if (item.PublicKey.IsNotEmpty())
if (!item.GetProtocolExtra().WgPublicKey.IsNullOrEmpty())
{
dicQuery.Add("publickey", Utils.UrlEncode(item.PublicKey));
dicQuery.Add("publickey", Utils.UrlEncode(item.GetProtocolExtra().WgPublicKey));
}
if (item.Path.IsNotEmpty())
if (!item.GetProtocolExtra().WgReserved.IsNullOrEmpty())
{
dicQuery.Add("reserved", Utils.UrlEncode(item.Path));
dicQuery.Add("reserved", Utils.UrlEncode(item.GetProtocolExtra().WgReserved));
}
if (item.RequestHost.IsNotEmpty())
if (!item.GetProtocolExtra().WgInterfaceAddress.IsNullOrEmpty())
{
dicQuery.Add("address", Utils.UrlEncode(item.RequestHost));
dicQuery.Add("address", Utils.UrlEncode(item.GetProtocolExtra().WgInterfaceAddress));
}
if (item.ShortId.IsNotEmpty())
{
dicQuery.Add("mtu", Utils.UrlEncode(item.ShortId));
}
return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark);
dicQuery.Add("mtu", Utils.UrlEncode(item.GetProtocolExtra().WgMtu > 0 ? item.GetProtocolExtra().WgMtu.ToString() : "1280"));
return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Password, dicQuery, remark);
}
}

View File

@@ -71,28 +71,25 @@ public class DownloaderHelper
}
};
var totalDatetime = DateTime.Now;
var totalSecond = 0;
var lastUpdateTime = DateTime.Now;
var hasValue = false;
double maxSpeed = 0;
await using var downloader = new Downloader.DownloadService(downloadOpt);
//downloader.DownloadStarted += (sender, value) =>
//{
// if (progress != null)
// {
// progress.Report("Start download data...");
// }
//};
downloader.DownloadProgressChanged += (sender, value) =>
{
var ts = DateTime.Now - totalDatetime;
if (progress != null && ts.Seconds > totalSecond)
if (progress != null && value.BytesPerSecondSpeed > 0)
{
hasValue = true;
totalSecond = ts.Seconds;
if (value.BytesPerSecondSpeed > maxSpeed)
{
maxSpeed = value.BytesPerSecondSpeed;
}
var ts = DateTime.Now - lastUpdateTime;
if (ts.TotalMilliseconds >= 1000)
{
lastUpdateTime = DateTime.Now;
var speed = (maxSpeed / 1000 / 1000).ToString("#0.0");
progress.Report(speed);
}
@@ -102,10 +99,19 @@ public class DownloaderHelper
{
if (progress != null)
{
if (!hasValue && value.Error != null)
if (hasValue && maxSpeed > 0)
{
var finalSpeed = (maxSpeed / 1000 / 1000).ToString("#0.0");
progress.Report(finalSpeed);
}
else if (value.Error != null)
{
progress.Report(value.Error?.Message);
}
else
{
progress.Report("0");
}
}
};
//progress.Report("......");

View File

@@ -3,12 +3,19 @@ namespace ServiceLib.Manager;
/// <summary>
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
/// </summary>
public class ActionPrecheckManager(Config config)
public class ActionPrecheckManager
{
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
private static readonly Lazy<ActionPrecheckManager> _instance = new();
public static ActionPrecheckManager Instance => _instance.Value;
private readonly Config _config = config;
// sing-box supported transports for different protocol types
private static readonly HashSet<string> SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)];
private static readonly HashSet<EConfigType> SingboxTransportSupportedProtocols =
[EConfigType.VMess, EConfigType.VLESS, EConfigType.Trojan, EConfigType.Shadowsocks];
private static readonly HashSet<string> SingboxShadowsocksAllowedTransports =
[nameof(ETransport.tcp), nameof(ETransport.ws), nameof(ETransport.quic)];
public async Task<List<string>> Check(string? indexId)
{
@@ -47,6 +54,7 @@ public class ActionPrecheckManager(Config config)
{
return [];
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
return await ValidateNodeAndCoreSupport(item, coreType);
}
@@ -62,153 +70,196 @@ public class ActionPrecheckManager(Config config)
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
return errors;
}
if (!item.IsComplex())
else if (item.ConfigType.IsGroupType())
{
if (item.Address.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
return errors;
}
if (item.Port is <= 0 or >= 65536)
{
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
return errors;
}
switch (item.ConfigType)
{
case EConfigType.VMess:
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
break;
case EConfigType.VLESS:
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (!Global.Flows.Contains(item.Flow))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
break;
case EConfigType.Shadowsocks:
if (item.Id.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
}
break;
}
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
&& item.StreamSecurity == Global.StreamSecurityReality
&& item.PublicKey.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
}
if (errors.Count > 0)
{
return errors;
}
var groupErrors = await ValidateGroupNode(item, coreType);
errors.AddRange(groupErrors);
return errors;
}
if (item.ConfigType.IsGroupType())
else if (!item.IsComplex())
{
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
}
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
if (hasCycle)
{
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
return errors;
}
var childIds = Utils.String2List(group.ChildItems) ?? [];
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRange(subItems.Select(p => p.IndexId));
foreach (var child in childIds)
{
var childErrors = new List<string>();
if (child.IsNullOrEmpty())
{
continue;
}
var childItem = await AppManager.Instance.GetProfileItem(child);
if (childItem is null)
{
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
continue;
}
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
{
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
continue;
}
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
errors.AddRange(childErrors);
}
var normalErrors = await ValidateNormalNode(item, coreType);
errors.AddRange(normalErrors);
return errors;
}
var net = item.GetNetwork() ?? item.Network;
return errors;
}
private async Task<List<string>> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
if (item.Address.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
return errors;
}
if (item.Port is <= 0 or > 65535)
{
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
return errors;
}
var net = item.GetNetwork();
if (coreType == ECoreType.sing_box)
{
// sing-box does not support xhttp / kcp
// sing-box does not support transports like ws/http/httpupgrade/etc. when the node is not vmess/trojan/vless
if (net is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
var transportError = ValidateSingboxTransport(item.ConfigType, net);
if (transportError != null)
{
errors.Add(string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net));
return errors;
errors.Add(transportError);
}
if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan))
if (!Global.SingboxSupportConfigType.Contains(item.ConfigType))
{
if (net is nameof(ETransport.ws) or nameof(ETransport.http) or nameof(ETransport.h2) or nameof(ETransport.quic) or nameof(ETransport.httpupgrade))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), item.ConfigType.ToString(), net));
return errors;
}
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
nameof(ECoreType.sing_box), item.ConfigType.ToString()));
}
}
else if (coreType is ECoreType.Xray)
{
// Xray core does not support these protocols
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
&& !item.IsComplex())
if (!Global.XraySupportConfigType.Contains(item.ConfigType))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
return errors;
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
nameof(ECoreType.Xray), item.ConfigType.ToString()));
}
}
var protocolExtra = item.GetProtocolExtra();
switch (item.ConfigType)
{
case EConfigType.VMess:
if (item.Password.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Password))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Password"));
}
break;
case EConfigType.VLESS:
if (item.Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Password) && item.Password.Length > 30))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Password"));
}
if (!Global.Flows.Contains(protocolExtra.Flow ?? string.Empty))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
break;
case EConfigType.Shadowsocks:
if (item.Password.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Password"));
}
if (string.IsNullOrEmpty(protocolExtra.SsMethod) || !Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod))
{
errors.Add(string.Format(ResUI.InvalidProperty, "SsMethod"));
}
break;
}
if (item.StreamSecurity == Global.StreamSecurity)
{
// check certificate validity
if (!item.Cert.IsNullOrEmpty()
&& (CertPemManager.ParsePemChain(item.Cert).Count == 0)
&& !item.CertSha.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate"));
}
}
if (item.StreamSecurity == Global.StreamSecurityReality)
{
if (item.PublicKey.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
}
}
if (item.Network == nameof(ETransport.xhttp)
&& !item.Extra.IsNullOrEmpty())
{
// check xhttp extra json validity
var xhttpExtra = JsonUtils.ParseJson(item.Extra);
if (xhttpExtra is null)
{
errors.Add(string.Format(ResUI.InvalidProperty, "XHTTP Extra"));
}
}
return errors;
}
private async Task<List<string>> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
var hasCycle = await GroupProfileManager.HasCycle(item.IndexId, item.GetProtocolExtra());
if (hasCycle)
{
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
return errors;
}
var (childItems, _) = await GroupProfileManager.GetChildProfileItems(item);
foreach (var childItem in childItems)
{
var childErrors = new List<string>();
if (childItem is null)
{
childErrors.Add(string.Format(ResUI.NodeTagNotExist, ""));
continue;
}
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
{
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
continue;
}
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: ")));
}
return errors;
}
private static string? ValidateSingboxTransport(EConfigType configType, string net)
{
// sing-box does not support xhttp / kcp transports
if (SingboxUnsupportedTransports.Contains(net))
{
return string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net);
}
// sing-box does not support non-tcp transports for protocols other than vmess/trojan/vless/shadowsocks
if (!SingboxTransportSupportedProtocols.Contains(configType) && net != nameof(ETransport.tcp))
{
return string.Format(ResUI.CoreNotSupportProtocolTransport,
nameof(ECoreType.sing_box), configType.ToString(), net);
}
// sing-box shadowsocks only supports tcp/ws/quic transports
if (configType == EConfigType.Shadowsocks && !SingboxShadowsocksAllowedTransports.Contains(net))
{
return string.Format(ResUI.CoreNotSupportProtocolTransport,
nameof(ECoreType.sing_box), configType.ToString(), net);
}
return null;
}
private async Task<List<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
@@ -247,7 +298,7 @@ public class ActionPrecheckManager(Config config)
if (node is not null)
{
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + $"{node.Remarks}: " + s));
}
else if (tag.IsNotEmpty())
{
@@ -265,7 +316,7 @@ public class ActionPrecheckManager(Config config)
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = await ConfigHandler.GetDefaultRouting(AppManager.Instance.Config);
if (routing == null)
{
return errors;
@@ -293,7 +344,7 @@ public class ActionPrecheckManager(Config config)
}
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + $"{tagItem.Remarks}: " + s));
}
return errors;

View File

@@ -31,6 +31,23 @@ public sealed class AppManager
public string LinuxSudoPwd { get; set; }
public bool ShowInTaskbar { get; set; }
public ECoreType RunningCoreType { get; set; }
public bool IsRunningCore(ECoreType type)
{
switch (type)
{
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
return true;
default:
return false;
}
}
#endregion Property
#region App
@@ -64,7 +81,9 @@ public sealed class AppManager
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
SQLiteHelper.Instance.CreateTable<DNSItem>();
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
#pragma warning disable CS0618
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
#pragma warning restore CS0618
return true;
}
@@ -77,6 +96,11 @@ public sealed class AppManager
_ = StatePort;
_ = StatePort2;
Task.Run(async () =>
{
await MigrateProfileExtra();
}).Wait();
return true;
}
@@ -167,10 +191,17 @@ public sealed class AppManager
return (await ProfileItems(subid))?.Select(t => t.IndexId)?.ToList();
}
public async Task<List<ProfileItemModel>?> ProfileItems(string subid, string filter)
public async Task<List<ProfileItemModel>?> ProfileModels(string subid, string filter)
{
var sql = @$"select a.*
,b.remarks subRemarks
var sql = @$"select a.IndexId
,a.ConfigType
,a.Remarks
,a.Address
,a.Port
,a.Network
,a.StreamSecurity
,a.Subid
,b.remarks as subRemarks
from ProfileItem a
left join SubItem b on a.subid = b.id
where 1=1 ";
@@ -199,6 +230,18 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
}
public async Task<List<ProfileItem>> GetProfileItemsByIndexIds(IEnumerable<string> indexIds)
{
var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
if (ids.Count == 0)
{
return [];
}
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
.Where(it => ids.Contains(it.IndexId))
.ToListAsync();
}
public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
{
if (remarks.IsNullOrEmpty())
@@ -208,15 +251,6 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks);
}
public async Task<ProfileGroupItem?> GetProfileGroupItem(string indexId)
{
if (indexId.IsNullOrEmpty())
{
return null;
}
return await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
}
public async Task<List<RoutingItem>?> RoutingItems()
{
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();
@@ -247,6 +281,205 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
}
public async Task MigrateProfileExtra()
{
await MigrateProfileExtraGroup();
#pragma warning disable CS0618
const int pageSize = 100;
var offset = 0;
while (true)
{
var sql = $"SELECT * FROM ProfileItem " +
$"WHERE ConfigVersion < 3 " +
$"AND ConfigType NOT IN ({(int)EConfigType.PolicyGroup}, {(int)EConfigType.ProxyChain}) " +
$"LIMIT {pageSize} OFFSET {offset}";
var batch = await SQLiteHelper.Instance.QueryAsync<ProfileItem>(sql);
if (batch is null || batch.Count == 0)
{
break;
}
var batchSuccessCount = await MigrateProfileExtraSub(batch);
// Only increment offset by the number of failed items that remain in the result set
// Successfully updated items are automatically excluded from future queries due to ConfigVersion = 3
offset += batch.Count - batchSuccessCount;
}
//await ProfileGroupItemManager.Instance.ClearAll();
#pragma warning restore CS0618
}
private async Task<int> MigrateProfileExtraSub(List<ProfileItem> batch)
{
var updateProfileItems = new List<ProfileItem>();
foreach (var item in batch)
{
try
{
var extra = item.GetProtocolExtra();
switch (item.ConfigType)
{
case EConfigType.Shadowsocks:
extra = extra with { SsMethod = item.Security.NullIfEmpty() };
break;
case EConfigType.VMess:
extra = extra with
{
AlterId = item.AlterId.ToString(),
VmessSecurity = item.Security.NullIfEmpty(),
};
break;
case EConfigType.VLESS:
extra = extra with
{
Flow = item.Flow.NullIfEmpty(),
VlessEncryption = item.Security,
};
break;
case EConfigType.Hysteria2:
extra = extra with
{
SalamanderPass = item.Path.NullIfEmpty(),
Ports = item.Ports.NullIfEmpty(),
UpMbps = _config.HysteriaItem.UpMbps,
DownMbps = _config.HysteriaItem.DownMbps,
HopInterval = _config.HysteriaItem.HopInterval.ToString(),
};
break;
case EConfigType.TUIC:
item.Username = item.Id;
item.Id = item.Security;
item.Password = item.Security;
break;
case EConfigType.HTTP:
case EConfigType.SOCKS:
item.Username = item.Security;
break;
case EConfigType.WireGuard:
extra = extra with
{
WgPublicKey = item.PublicKey.NullIfEmpty(),
WgInterfaceAddress = item.RequestHost.NullIfEmpty(),
WgReserved = item.Path.NullIfEmpty(),
WgMtu = int.TryParse(item.ShortId, out var mtu) ? mtu : 1280
};
break;
}
item.SetProtocolExtra(extra);
item.Password = item.Id;
item.ConfigVersion = 3;
updateProfileItems.Add(item);
}
catch (Exception ex)
{
Logging.SaveLog($"MigrateProfileExtra Error: {ex}");
}
}
if (updateProfileItems.Count > 0)
{
try
{
var count = await SQLiteHelper.Instance.UpdateAllAsync(updateProfileItems);
return count;
}
catch (Exception ex)
{
Logging.SaveLog($"MigrateProfileExtraGroup update error: {ex}");
return 0;
}
}
else
{
return 0;
}
}
private async Task<bool> MigrateProfileExtraGroup()
{
#pragma warning disable CS0618
var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
var groupItems = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!));
var sql = $"SELECT * FROM ProfileItem WHERE ConfigVersion < 3 AND ConfigType IN ({(int)EConfigType.PolicyGroup}, {(int)EConfigType.ProxyChain})";
var items = await SQLiteHelper.Instance.QueryAsync<ProfileItem>(sql);
if (items is null || items.Count == 0)
{
Logging.SaveLog("MigrateProfileExtraGroup: No items to migrate.");
return true;
}
Logging.SaveLog($"MigrateProfileExtraGroup: Found {items.Count} group items to migrate.");
var updateProfileItems = new List<ProfileItem>();
foreach (var item in items)
{
try
{
var extra = item.GetProtocolExtra();
extra = extra with { GroupType = nameof(item.ConfigType) };
groupItems.TryGetValue(item.IndexId, out var groupItem);
if (groupItem != null && !groupItem.NotHasChild())
{
extra = extra with
{
ChildItems = groupItem.ChildItems,
SubChildItems = groupItem.SubChildItems,
Filter = groupItem.Filter,
MultipleLoad = groupItem.MultipleLoad,
};
}
item.SetProtocolExtra(extra);
item.ConfigVersion = 3;
updateProfileItems.Add(item);
}
catch (Exception ex)
{
Logging.SaveLog($"MigrateProfileExtraGroup item error [{item.IndexId}]: {ex}");
}
}
if (updateProfileItems.Count > 0)
{
try
{
var count = await SQLiteHelper.Instance.UpdateAllAsync(updateProfileItems);
Logging.SaveLog($"MigrateProfileExtraGroup: Successfully updated {updateProfileItems.Count} items.");
return updateProfileItems.Count == count;
}
catch (Exception ex)
{
Logging.SaveLog($"MigrateProfileExtraGroup update error: {ex}");
return false;
}
}
return true;
//await ProfileGroupItemManager.Instance.ClearAll();
#pragma warning restore CS0618
}
#endregion SqliteHelper
#region Core Type

View File

@@ -197,6 +197,7 @@ public class CertPemManager
"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
};
/// <summary>
@@ -214,7 +215,7 @@ public class CertPemManager
using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
var sslOptions = new SslClientAuthenticationOptions
{
@@ -261,7 +262,7 @@ public class CertPemManager
using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
var sslOptions = new SslClientAuthenticationOptions
{
@@ -279,11 +280,7 @@ public class CertPemManager
var chain = new X509Chain();
chain.Build(certChain);
foreach (var element in chain.ChainElements)
{
var pem = ExportCertToPem(element.Certificate);
pemList.Add(pem);
}
pemList.AddRange(chain.ChainElements.Select(element => ExportCertToPem(element.Certificate)));
return (pemList, null);
}
@@ -416,4 +413,22 @@ public class CertPemManager
return string.Concat(pemList);
}
public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = false)
{
try
{
var cert = X509Certificate2.CreateFromPem(pemCert);
var thumbprint = cert.GetCertHashString(HashAlgorithmName.SHA256);
if (includeColon)
{
return string.Join(":", thumbprint.Chunk(2).Select(c => new string(c)));
}
return thumbprint;
}
catch
{
return string.Empty;
}
}
}

View File

@@ -125,7 +125,7 @@ public sealed class CoreInfoManager
new CoreInfo
{
CoreType = ECoreType.mihomo,
CoreExes = ["mihomo-windows-amd64-v1", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
CoreExes = GetMihomoCoreExes(),
Arguments = "-f {0}" + PortableMode(),
Url = GetCoreUrl(ECoreType.mihomo),
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
@@ -248,4 +248,34 @@ public sealed class CoreInfoManager
{
return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases";
}
private static List<string>? GetMihomoCoreExes()
{
var names = new List<string>();
if (Utils.IsWindows())
{
names.Add("mihomo-windows-amd64-v1");
names.Add("mihomo-windows-amd64-compatible");
names.Add("mihomo-windows-amd64");
names.Add("mihomo-windows-arm64");
}
else if (Utils.IsLinux())
{
names.Add("mihomo-linux-amd64-v1");
names.Add("mihomo-linux-amd64");
names.Add("mihomo-linux-arm64");
}
else if (Utils.IsMacOS())
{
names.Add("mihomo-darwin-amd64-v1");
names.Add("mihomo-darwin-amd64");
names.Add("mihomo-darwin-arm64");
}
names.Add("clash");
names.Add("mihomo");
return names;
}
}

View File

@@ -66,7 +66,17 @@ public class CoreManager
}
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node);
var preContext = ConfigHandler.GetPreSocksCoreConfigContext(context);
if (preContext is not null)
{
context = context with
{
TunProtectSsPort = preContext.TunProtectSsPort,
ProxyRelaySsPort = preContext.ProxyRelaySsPort,
};
}
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true)
{
await UpdateFunc(true, result.Msg);
@@ -85,8 +95,8 @@ public class CoreManager
await WindowsUtils.RemoveTunDevice();
}
await CoreStart(node);
await CoreStartPreService(node);
await CoreStart(context);
await CoreStartPreService(preContext);
if (_processService != null)
{
await UpdateFunc(true, $"{node.GetSummary()}");
@@ -122,7 +132,8 @@ public class CoreManager
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath);
if (result.Success != true)
{
return null;
@@ -165,9 +176,10 @@ public class CoreManager
#region Private
private async Task CoreStart(ProfileItem node)
private async Task CoreStart(CoreConfigContext context)
{
var coreType = _config.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var node = context.Node;
var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
@@ -179,27 +191,22 @@ public class CoreManager
_processService = proc;
}
private async Task CoreStartPreService(ProfileItem node)
private async Task CoreStartPreService(CoreConfigContext? preContext)
{
if (_processService != null && !_processService.HasExited)
if (_processService is { HasExited: false } && preContext != null)
{
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
if (itemSocks != null)
var preCoreType = preContext?.Node?.CoreType ?? ECoreType.sing_box;
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(preContext, fileName);
if (result.Success)
{
var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box;
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
if (result.Success)
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
if (proc is null)
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
if (proc is null)
{
return;
}
_processPreService = proc;
return;
}
_processPreService = proc;
}
}
}
@@ -226,7 +233,7 @@ public class CoreManager
{
if (mayNeedSudo
&& _config.TunModeItem.EnableTun
&& coreInfo.CoreType == ECoreType.sing_box
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo)
&& Utils.IsNonWindows())
{
_linuxSudo = true;

View File

@@ -0,0 +1,169 @@
namespace ServiceLib.Manager;
public class GroupProfileManager
{
public static async Task<bool> HasCycle(ProfileItem item)
{
return await HasCycle(item.IndexId, item.GetProtocolExtra());
}
public static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo)
{
return await HasCycle(indexId, extraInfo, new HashSet<string>(), new HashSet<string>());
}
private static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo, HashSet<string> visited, HashSet<string> stack)
{
if (indexId.IsNullOrEmpty() || extraInfo == null)
{
return false;
}
if (stack.Contains(indexId))
{
return true;
}
if (visited.Contains(indexId))
{
return false;
}
visited.Add(indexId);
stack.Add(indexId);
try
{
if (extraInfo.GroupType.IsNullOrEmpty())
{
return false;
}
if (extraInfo.ChildItems.IsNullOrEmpty())
{
return false;
}
var childIds = Utils.String2List(extraInfo.ChildItems)
?.Where(p => !string.IsNullOrEmpty(p))
.ToList();
if (childIds == null)
{
return false;
}
foreach (var child in childIds)
{
var childItem = await AppManager.Instance.GetProfileItem(child);
if (await HasCycle(child, childItem?.GetProtocolExtra(), visited, stack))
{
return true;
}
}
return false;
}
finally
{
stack.Remove(indexId);
}
}
public static async Task<(List<ProfileItem> Items, ProtocolExtraItem? Extra)> GetChildProfileItems(ProfileItem profileItem)
{
var protocolExtra = profileItem?.GetProtocolExtra();
return (await GetChildProfileItemsByProtocolExtra(protocolExtra), protocolExtra);
}
public static async Task<List<ProfileItem>> GetChildProfileItemsByProtocolExtra(ProtocolExtraItem? protocolExtra)
{
if (protocolExtra == null)
{
return [];
}
var items = new List<ProfileItem>();
items.AddRange(await GetSubChildProfileItems(protocolExtra));
items.AddRange(await GetSelectedChildProfileItems(protocolExtra));
return items;
}
private static async Task<List<ProfileItem>> GetSelectedChildProfileItems(ProtocolExtraItem? extra)
{
if (extra == null || extra.ChildItems.IsNullOrEmpty())
{
return [];
}
var childProfileIds = Utils.String2List(extra.ChildItems)
?.Where(p => !string.IsNullOrEmpty(p))
.ToList() ?? [];
if (childProfileIds.Count == 0)
{
return [];
}
var childProfiles = await AppManager.Instance.GetProfileItemsByIndexIds(childProfileIds);
if (childProfiles == null || childProfiles.Count == 0)
{
return [];
}
var profileMap = childProfiles
.Where(p => p != null && !p.IndexId.IsNullOrEmpty())
.GroupBy(p => p!.IndexId!)
.ToDictionary(g => g.Key, g => g.First());
var ordered = new List<ProfileItem>(childProfileIds.Count);
foreach (var id in childProfileIds)
{
if (id != null && profileMap.TryGetValue(id, out var item) && item != null)
{
ordered.Add(item);
}
}
return ordered;
}
private static async Task<List<ProfileItem>> GetSubChildProfileItems(ProtocolExtraItem? extra)
{
if (extra == null || extra.SubChildItems.IsNullOrEmpty())
{
return [];
}
var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty);
return childProfiles?.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
)
.ToList() ?? [];
}
public static async Task<List<ProfileItem>> GetAllChildProfileItems(ProfileItem profileItem)
{
var allChildItems = new List<ProfileItem>();
var visited = new HashSet<string>();
await CollectChildItems(profileItem, allChildItems, visited);
return allChildItems;
}
private static async Task CollectChildItems(ProfileItem profileItem, List<ProfileItem> allChildItems, HashSet<string> visited)
{
var (childItems, _) = await GetChildProfileItems(profileItem);
foreach (var child in childItems.Where(child => visited.Add(child.IndexId)))
{
allChildItems.Add(child);
if (child.ConfigType.IsGroupType())
{
await CollectChildItems(child, allChildItems, visited);
}
}
}
}

View File

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

View File

@@ -43,9 +43,12 @@ public sealed class WebDavManager
_webDir = _config.WebDavItem.DirName.TrimEx();
}
// Ensure BaseAddress URL ends with a trailing slash
var baseUrl = _config.WebDavItem.Url.Trim().TrimEnd('/') + "/";
var clientParams = new WebDavClientParams
{
BaseAddress = new Uri(_config.WebDavItem.Url),
BaseAddress = new Uri(baseUrl),
Credentials = new NetworkCredential(_config.WebDavItem.UserName, _config.WebDavItem.Password)
};
_client = new WebDavClient(clientParams);

View File

@@ -8,21 +8,6 @@ public class Config
public string IndexId { get; set; }
public string SubIndexId { get; set; }
public ECoreType RunningCoreType { get; set; }
public bool IsRunningCore(ECoreType type)
{
switch (type)
{
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
return true;
default:
return false;
}
}
#endregion property
#region other entities

View File

@@ -100,7 +100,6 @@ public class UIItem
public bool DoubleClick2Activate { get; set; }
public bool AutoHideStartup { get; set; }
public bool Hide2TrayWhenClose { get; set; }
public bool ShowInTaskbar { get; set; }
public bool MacOSShowInDock { get; set; }
public List<ColumnItem> MainColumnItem { get; set; }
public List<WindowSizeItem> WindowSizeItem { get; set; }
@@ -145,7 +144,6 @@ public class TunModeItem
public bool StrictRoute { get; set; } = true;
public string Stack { get; set; }
public int Mtu { get; set; }
public bool EnableExInbound { get; set; }
public bool EnableIPv6Address { get; set; }
}
@@ -266,9 +264,10 @@ public class SimpleDNSItem
public string? DirectDNS { get; set; }
public string? RemoteDNS { get; set; }
public string? BootstrapDNS { get; set; }
public string? RayStrategy4Freedom { get; set; }
public string? SingboxStrategy4Direct { get; set; }
public string? SingboxStrategy4Proxy { get; set; }
public string? Strategy4Freedom { get; set; }
public string? Strategy4Proxy { get; set; }
public bool? ServeStale { get; set; }
public bool? ParallelQuery { get; set; }
public string? Hosts { get; set; }
public string? DirectExpectedIPs { get; set; }
}

View File

@@ -0,0 +1,24 @@
namespace ServiceLib.Models;
public record CoreConfigContext
{
public required ProfileItem Node { get; init; }
public RoutingItem? RoutingItem { get; init; }
public DNSItem? RawDnsItem { get; init; }
public SimpleDNSItem SimpleDnsItem { get; init; } = new();
public Dictionary<string, ProfileItem> AllProxiesMap { get; init; } = new();
public Config AppConfig { get; init; } = new();
public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new();
// Test ServerTestItem Map
public Dictionary<string, string> ServerTestItemMap { get; init; } = new();
// TUN Compatibility
public bool IsTunEnabled { get; init; } = false;
public HashSet<string> ProtectDomainList { get; init; } = new();
// -> tun inbound --(if routing proxy)--> relay outbound
// -> proxy core (relay inbound --> proxy outbound --(dialerProxy)--> protect outbound)
// -> protect inbound -> direct proxy outbound data -> internet
public int TunProtectSsPort { get; init; } = 0;
public int ProxyRelaySsPort { get; init; } = 0;
}

View File

@@ -1,5 +1,6 @@
namespace ServiceLib.Models;
[Obsolete("Use ProtocolExtraItem instead.")]
[Serializable]
public class ProfileGroupItem
{

View File

@@ -1,18 +1,20 @@
namespace ServiceLib.Models;
[Serializable]
public class ProfileItem : ReactiveObject
public class ProfileItem
{
private ProtocolExtraItem? _protocolExtraCache;
public ProfileItem()
{
IndexId = string.Empty;
ConfigType = EConfigType.VMess;
ConfigVersion = 2;
ConfigVersion = 3;
Subid = string.Empty;
Address = string.Empty;
Port = 0;
Id = string.Empty;
AlterId = 0;
Security = string.Empty;
Password = string.Empty;
Username = string.Empty;
Network = string.Empty;
Remarks = string.Empty;
HeaderType = string.Empty;
@@ -20,8 +22,6 @@ public class ProfileItem : ReactiveObject
Path = string.Empty;
StreamSecurity = string.Empty;
AllowInsecure = string.Empty;
Subid = string.Empty;
Flow = string.Empty;
}
#region function
@@ -81,7 +81,7 @@ public class ProfileItem : ReactiveObject
switch (ConfigType)
{
case EConfigType.VMess:
if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id))
if (Password.IsNullOrEmpty() || !Utils.IsGuidByParse(Password))
{
return false;
}
@@ -89,12 +89,12 @@ public class ProfileItem : ReactiveObject
break;
case EConfigType.VLESS:
if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30))
if (Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(Password) && Password.Length > 30))
{
return false;
}
if (!Global.Flows.Contains(Flow))
if (!Global.Flows.Contains(GetProtocolExtra().Flow ?? string.Empty))
{
return false;
}
@@ -102,12 +102,13 @@ public class ProfileItem : ReactiveObject
break;
case EConfigType.Shadowsocks:
if (Id.IsNullOrEmpty())
if (Password.IsNullOrEmpty())
{
return false;
}
if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security))
if (string.IsNullOrEmpty(GetProtocolExtra().SsMethod)
|| !Global.SsSecuritiesInSingbox.Contains(GetProtocolExtra().SsMethod))
{
return false;
}
@@ -125,35 +126,48 @@ public class ProfileItem : ReactiveObject
return true;
}
public void SetProtocolExtra(ProtocolExtraItem extraItem)
{
_protocolExtraCache = extraItem;
ProtoExtra = JsonUtils.Serialize(extraItem, false);
}
public void SetProtocolExtra()
{
ProtoExtra = JsonUtils.Serialize(_protocolExtraCache, false);
}
public ProtocolExtraItem GetProtocolExtra()
{
return _protocolExtraCache ??= JsonUtils.Deserialize<ProtocolExtraItem>(ProtoExtra) ?? new ProtocolExtraItem();
}
#endregion function
[PrimaryKey]
public string IndexId { get; set; }
public EConfigType ConfigType { get; set; }
public ECoreType? CoreType { get; set; }
public int ConfigVersion { get; set; }
public string Subid { get; set; }
public bool IsSub { get; set; } = true;
public int? PreSocksPort { get; set; }
public bool DisplayLog { get; set; } = true;
public string Remarks { get; set; }
public string Address { get; set; }
public int Port { get; set; }
public string Ports { get; set; }
public string Id { get; set; }
public int AlterId { get; set; }
public string Security { get; set; }
public string Password { get; set; }
public string Username { get; set; }
public string Network { get; set; }
public string Remarks { get; set; }
public string HeaderType { get; set; }
public string RequestHost { get; set; }
public string Path { get; set; }
public string StreamSecurity { get; set; }
public string AllowInsecure { get; set; }
public string Subid { get; set; }
public bool IsSub { get; set; } = true;
public string Flow { get; set; }
public string Sni { get; set; }
public string Alpn { get; set; } = string.Empty;
public ECoreType? CoreType { get; set; }
public int? PreSocksPort { get; set; }
public string Fingerprint { get; set; }
public bool DisplayLog { get; set; } = true;
public string PublicKey { get; set; }
public string ShortId { get; set; }
public string SpiderX { get; set; }
@@ -161,4 +175,25 @@ public class ProfileItem : ReactiveObject
public string Extra { get; set; }
public bool? MuxEnabled { get; set; }
public string Cert { get; set; }
public string CertSha { get; set; }
public string EchConfigList { get; set; }
public string EchForceQuery { get; set; }
public string Finalmask { get; set; }
public string ProtoExtra { get; set; }
[Obsolete("Use ProtocolExtraItem.Ports instead.")]
public string Ports { get; set; }
[Obsolete("Use ProtocolExtraItem.AlterId instead.")]
public int AlterId { get; set; }
[Obsolete("Use ProtocolExtraItem.Flow instead.")]
public string Flow { get; set; }
[Obsolete("Use ProfileItem.Password instead.")]
public string Id { get; set; }
[Obsolete("Use ProtocolExtraItem.xxx instead.")]
public string Security { get; set; }
}

View File

@@ -1,16 +1,24 @@
namespace ServiceLib.Models;
[Serializable]
public class ProfileItemModel : ProfileItem
public class ProfileItemModel : ReactiveObject
{
public bool IsActive { get; set; }
public string IndexId { get; set; }
public EConfigType ConfigType { get; set; }
public string Remarks { get; set; }
public string Address { get; set; }
public int Port { get; set; }
public string Network { get; set; }
public string StreamSecurity { get; set; }
public string Subid { get; set; }
public string SubRemarks { get; set; }
public int Sort { get; set; }
[Reactive]
public int Delay { get; set; }
public decimal Speed { get; set; }
public int Sort { get; set; }
[Reactive]
public string DelayVal { get; set; }
@@ -29,4 +37,15 @@ public class ProfileItemModel : ProfileItem
[Reactive]
public string TotalDown { get; set; }
public string GetSummary()
{
var summary = $"[{ConfigType}] {Remarks}";
if (!ConfigType.IsComplexType())
{
summary += $"({Address}:{Port})";
}
return summary;
}
}

View File

@@ -0,0 +1,38 @@
namespace ServiceLib.Models;
public record ProtocolExtraItem
{
// vmess
public string? AlterId { get; init; }
public string? VmessSecurity { get; init; }
// vless
public string? Flow { get; init; }
public string? VlessEncryption { get; init; }
//public string? VisionSeed { get; init; }
// shadowsocks
//public string? PluginArgs { get; init; }
public string? SsMethod { get; init; }
// wireguard
public string? WgPublicKey { get; init; }
public string? WgPresharedKey { get; init; }
public string? WgInterfaceAddress { get; init; }
public string? WgReserved { get; init; }
public int? WgMtu { get; init; }
// hysteria2
public string? SalamanderPass { get; init; }
public int? UpMbps { get; init; }
public int? DownMbps { get; init; }
public string? Ports { get; init; }
public string? HopInterval { get; init; }
// group profile
public string? GroupType { get; init; }
public string? ChildItems { get; init; }
public string? SubChildItems { get; init; }
public string? Filter { get; init; }
public EMultipleLoad? MultipleLoad { get; init; }
}

View File

@@ -68,6 +68,7 @@ public class Rule4Sbox
public List<string>? ip_cidr { get; set; }
public List<string>? source_ip_cidr { get; set; }
public List<string>? process_name { get; set; }
public List<string>? process_path { get; set; }
public List<string>? rule_set { get; set; }
public List<Rule4Sbox>? rules { get; set; }
public string? action { get; set; }
@@ -182,6 +183,14 @@ public class Tls4Sbox
public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; }
public List<string>? certificate { get; set; }
public Ech4Sbox? ech { get; set; }
}
public class Ech4Sbox
{
public bool enabled { get; set; }
public List<string>? config { get; set; }
public string? query_server_name { get; set; }
}
public class Multiplex4Sbox
@@ -216,6 +225,8 @@ public class Transport4Sbox
public string? idle_timeout { get; set; }
public string? ping_timeout { get; set; }
public bool? permit_without_stream { get; set; }
public int? max_early_data { get; set; }
public string? early_data_header_name { get; set; }
}
public class Headers4Sbox
@@ -244,7 +255,7 @@ public class Server4Sbox : BaseServer4Sbox
// public List<string>? path { get; set; } // hosts
public Dictionary<string, List<string>>? predefined { get; set; }
// Deprecated
// Deprecated in sing-box 1.12.0 , kept for backward compatibility
public string? address { get; set; }
public string? address_resolver { get; set; }

View File

@@ -0,0 +1,21 @@
namespace ServiceLib.Models;
public class UpdateResult
{
public bool Success { get; set; }
public string? Msg { get; set; }
public SemanticVersion? Version { get; set; }
public string? Url { get; set; }
public UpdateResult(bool success, string? msg)
{
Success = success;
Msg = msg;
}
public UpdateResult(bool success, SemanticVersion? version)
{
Success = success;
Version = version;
}
}

View File

@@ -3,7 +3,7 @@ namespace ServiceLib.Models;
public class V2rayConfig
{
public Log4Ray log { get; set; }
public Dns4Ray dns { get; set; }
public object dns { get; set; }
public List<Inbounds4Ray> inbounds { get; set; }
public List<Outbounds4Ray> outbounds { get; set; }
public Routing4Ray routing { get; set; }
@@ -105,6 +105,8 @@ public class Outbounds4Ray
public string protocol { get; set; }
public string? targetStrategy { get; set; }
public Outboundsettings4Ray settings { get; set; }
public StreamSettings4Ray streamSettings { get; set; }
@@ -128,7 +130,8 @@ public class Outboundsettings4Ray
public string? secretKey { get; set; }
public List<string>? address { get; set; }
public Object? address { get; set; }
public int? port { get; set; }
public List<WireguardPeer4Ray>? peers { get; set; }
@@ -139,6 +142,8 @@ public class Outboundsettings4Ray
public List<int>? reserved { get; set; }
public int? workers { get; set; }
public int? version { get; set; }
}
public class WireguardPeer4Ray
@@ -203,12 +208,8 @@ public class Dns4Ray
{
public Dictionary<string, object>? hosts { get; set; }
public List<object> servers { get; set; }
public string? clientIp { get; set; }
public string? queryStrategy { get; set; }
public bool? disableCache { get; set; }
public bool? disableFallback { get; set; }
public bool? disableFallbackIfMatch { get; set; }
public bool? useSystemHosts { get; set; }
public bool? serveStale { get; set; }
public bool? enableParallelQuery { get; set; }
public string? tag { get; set; }
}
@@ -256,6 +257,8 @@ public class RulesItem4Ray
public List<string>? domain { get; set; }
public List<string>? protocol { get; set; }
public List<string>? process { get; set; }
}
public class BalancersItem4Ray
@@ -336,6 +339,10 @@ public class StreamSettings4Ray
public GrpcSettings4Ray? grpcSettings { get; set; }
public HysteriaSettings4Ray? hysteriaSettings { get; set; }
public Finalmask4Ray? finalmask { get; set; }
public Sockopt4Ray? sockopt { get; set; }
}
@@ -355,7 +362,10 @@ public class TlsSettings4Ray
public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; }
public List<CertificateSettings4Ray>? certificates { get; set; }
public string? pinnedPeerCertSha256 { get; set; }
public bool? disableSystemRoot { get; set; }
public string? echConfigList { get; set; }
public string? echForceQuery { get; set; }
}
public class CertificateSettings4Ray
@@ -376,8 +386,6 @@ public class Header4Ray
public object request { get; set; }
public object response { get; set; }
public string? domain { get; set; }
}
public class KcpSettings4Ray
@@ -395,10 +403,6 @@ public class KcpSettings4Ray
public int readBufferSize { get; set; }
public int writeBufferSize { get; set; }
public Header4Ray header { get; set; }
public string seed { get; set; }
}
public class WsSettings4Ray
@@ -411,8 +415,6 @@ public class WsSettings4Ray
public class Headers4Ray
{
public string Host { get; set; }
[JsonPropertyName("User-Agent")]
public string UserAgent { get; set; }
}
@@ -459,6 +461,39 @@ public class GrpcSettings4Ray
public int? initial_windows_size { get; set; }
}
public class HysteriaSettings4Ray
{
public int version { get; set; }
public string? auth { get; set; }
public string? up { get; set; }
public string? down { get; set; }
public HysteriaUdpHop4Ray? udphop { get; set; }
}
public class HysteriaUdpHop4Ray
{
public string? port { get; set; }
public string? interval { get; set; }
}
public class Finalmask4Ray
{
public List<Mask4Ray>? tcp { get; set; }
public List<Mask4Ray>? udp { get; set; }
}
public class Mask4Ray
{
public string type { get; set; }
public object? settings { get; set; }
}
public class MaskSettings4Ray
{
public string? password { get; set; }
public string? domain { get; set; }
}
public class AccountsItem4Ray
{
public string user { get; set; }

View File

@@ -19,7 +19,7 @@ namespace ServiceLib.Resx {
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ResUI {
@@ -529,7 +529,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Next proxy Configuration remarks 的本地化字符串。
/// 查找类似 Next proxy remarks 的本地化字符串。
/// </summary>
public static string LvNextProfile {
get {
@@ -547,7 +547,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Previous proxy Configuration remarks 的本地化字符串。
/// 查找类似 Previous proxy remarks 的本地化字符串。
/// </summary>
public static string LvPrevProfile {
get {
@@ -736,7 +736,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Anytls] Configuration 的本地化字符串。
/// 查找类似 Add [Anytls] 的本地化字符串。
/// </summary>
public static string menuAddAnytlsServer {
get {
@@ -745,7 +745,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add Child Configuration 的本地化字符串。
/// 查找类似 Add Child 的本地化字符串。
/// </summary>
public static string menuAddChildServer {
get {
@@ -754,7 +754,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。
/// 查找类似 Add a custom configuration 的本地化字符串。
/// </summary>
public static string menuAddCustomServer {
get {
@@ -763,7 +763,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [HTTP] Configuration 的本地化字符串。
/// 查找类似 Add [HTTP] 的本地化字符串。
/// </summary>
public static string menuAddHttpServer {
get {
@@ -772,7 +772,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Hysteria2] Configuration 的本地化字符串。
/// 查找类似 Add [Hysteria2] 的本地化字符串。
/// </summary>
public static string menuAddHysteria2Server {
get {
@@ -781,7 +781,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add Policy Group Configuration 的本地化字符串。
/// 查找类似 Add Policy Group 的本地化字符串。
/// </summary>
public static string menuAddPolicyGroupServer {
get {
@@ -790,7 +790,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add Proxy Chain Configuration 的本地化字符串。
/// 查找类似 Add Proxy Chain 的本地化字符串。
/// </summary>
public static string menuAddProxyChainServer {
get {
@@ -799,7 +799,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。
/// 查找类似 Import Share Links from clipboard 的本地化字符串。
/// </summary>
public static string menuAddServerViaClipboard {
get {
@@ -817,7 +817,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Scan QR code on the screen (Ctrl+S) 的本地化字符串。
/// 查找类似 Scan QR code on the screen 的本地化字符串。
/// </summary>
public static string menuAddServerViaScan {
get {
@@ -826,7 +826,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。
/// 查找类似 Add [Shadowsocks] 的本地化字符串。
/// </summary>
public static string menuAddShadowsocksServer {
get {
@@ -835,7 +835,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [SOCKS] Configuration 的本地化字符串。
/// 查找类似 Add [SOCKS] 的本地化字符串。
/// </summary>
public static string menuAddSocksServer {
get {
@@ -844,7 +844,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [Trojan] Configuration 的本地化字符串。
/// 查找类似 Add [Trojan] 的本地化字符串。
/// </summary>
public static string menuAddTrojanServer {
get {
@@ -853,7 +853,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [TUIC] Configuration 的本地化字符串。
/// 查找类似 Add [TUIC] 的本地化字符串。
/// </summary>
public static string menuAddTuicServer {
get {
@@ -862,7 +862,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [VLESS] Configuration 的本地化字符串。
/// 查找类似 Add [VLESS] 的本地化字符串。
/// </summary>
public static string menuAddVlessServer {
get {
@@ -871,7 +871,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [VMess] Configuration 的本地化字符串。
/// 查找类似 Add [VMess] 的本地化字符串。
/// </summary>
public static string menuAddVmessServer {
get {
@@ -880,7 +880,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Add [WireGuard] Configuration 的本地化字符串。
/// 查找类似 Add [WireGuard] 的本地化字符串。
/// </summary>
public static string menuAddWireguardServer {
get {
@@ -952,7 +952,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Clone selected Configuration 的本地化字符串。
/// 查找类似 Clone selected 的本地化字符串。
/// </summary>
public static string menuCopyServer {
get {
@@ -970,7 +970,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Edit Configuration (Ctrl+D) 的本地化字符串。
/// 查找类似 Edit 的本地化字符串。
/// </summary>
public static string menuEditServer {
get {
@@ -997,7 +997,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export selected Configuration for complete configuration 的本地化字符串。
/// 查找类似 Export selected for complete configuration 的本地化字符串。
/// </summary>
public static string menuExport2ClientConfig {
get {
@@ -1006,7 +1006,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export selected Configuration for complete configuration to clipboard 的本地化字符串。
/// 查找类似 Export selected for complete configuration to clipboard 的本地化字符串。
/// </summary>
public static string menuExport2ClientConfigClipboard {
get {
@@ -1015,7 +1015,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export Share Link to Clipboard (Ctrl+C) 的本地化字符串。
/// 查找类似 Export Share Link to Clipboard 的本地化字符串。
/// </summary>
public static string menuExport2ShareUrl {
get {
@@ -1033,7 +1033,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Export Configuration 的本地化字符串。
/// 查找类似 Export 的本地化字符串。
/// </summary>
public static string menuExportConfig {
get {
@@ -1069,7 +1069,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration Fallback by sing-box 的本地化字符串。
/// 查找类似 Fallback by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxFallback {
get {
@@ -1078,7 +1078,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
/// 查找类似 LeastPing by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxLeastPing {
get {
@@ -1087,7 +1087,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration Fallback by Xray 的本地化字符串。
/// 查找类似 Fallback by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayFallback {
get {
@@ -1096,7 +1096,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
/// 查找类似 LeastLoad by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastLoad {
get {
@@ -1105,7 +1105,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
/// 查找类似 LeastPing by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastPing {
get {
@@ -1114,7 +1114,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
/// 查找类似 Random by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRandom {
get {
@@ -1123,7 +1123,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
/// 查找类似 RoundRobin by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRoundRobin {
get {
@@ -1249,7 +1249,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Move to bottom (B) 的本地化字符串。
/// 查找类似 Move to bottom 的本地化字符串。
/// </summary>
public static string menuMoveBottom {
get {
@@ -1258,7 +1258,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Down (D) 的本地化字符串。
/// 查找类似 Down 的本地化字符串。
/// </summary>
public static string menuMoveDown {
get {
@@ -1285,7 +1285,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Move to top (T) 的本地化字符串。
/// 查找类似 Move to top 的本地化字符串。
/// </summary>
public static string menuMoveTop {
get {
@@ -1294,7 +1294,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Up (U) 的本地化字符串。
/// 查找类似 Up 的本地化字符串。
/// </summary>
public static string menuMoveUp {
get {
@@ -1312,7 +1312,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Copy (Ctrl+C) 的本地化字符串。
/// 查找类似 Copy 的本地化字符串。
/// </summary>
public static string menuMsgViewCopy {
get {
@@ -1330,7 +1330,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Select all (Ctrl+A) 的本地化字符串。
/// 查找类似 Select all 的本地化字符串。
/// </summary>
public static string menuMsgViewSelectAll {
get {
@@ -1402,7 +1402,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Select active node (Enter) 的本地化字符串。
/// 查找类似 Select active node 的本地化字符串。
/// </summary>
public static string menuProxiesSelectActivity {
get {
@@ -1411,7 +1411,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Test Configurations real delay (Ctrl+R) 的本地化字符串。
/// 查找类似 Test real delay 的本地化字符串。
/// </summary>
public static string menuRealPingServer {
get {
@@ -1501,7 +1501,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove Child Configuration 的本地化字符串。
/// 查找类似 Remove Child 的本地化字符串。
/// </summary>
public static string menuRemoveChildServer {
get {
@@ -1510,7 +1510,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove duplicate Configurations 的本地化字符串。
/// 查找类似 Remove duplicate 的本地化字符串。
/// </summary>
public static string menuRemoveDuplicateServer {
get {
@@ -1528,7 +1528,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove selected Configurations (Delete) 的本地化字符串。
/// 查找类似 Remove selected 的本地化字符串。
/// </summary>
public static string menuRemoveServer {
get {
@@ -1564,7 +1564,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove selected (Delete) 的本地化字符串。
/// 查找类似 Remove selected 的本地化字符串。
/// </summary>
public static string menuRoutingAdvancedRemove {
get {
@@ -1573,7 +1573,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Set as active rule (Enter) 的本地化字符串。
/// 查找类似 Set as active rule 的本地化字符串。
/// </summary>
public static string menuRoutingAdvancedSetDefault {
get {
@@ -1645,7 +1645,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Remove Rule (Delete) 的本地化字符串。
/// 查找类似 Remove Rule 的本地化字符串。
/// </summary>
public static string menuRuleRemove {
get {
@@ -1654,7 +1654,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Select all (Ctrl+A) 的本地化字符串。
/// 查找类似 Select all 的本地化字符串。
/// </summary>
public static string menuSelectAll {
get {
@@ -1663,7 +1663,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Configuration List 的本地化字符串。
/// 查找类似 Configuration item 1, Auto add from subscription group 的本地化字符串。
/// </summary>
public static string menuServerList {
get {
@@ -1672,7 +1672,25 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Configurations 的本地化字符串。
/// 查找类似 Configuration Item 2, Select and add from self-built 的本地化字符串。
/// </summary>
public static string menuServerList2 {
get {
return ResourceManager.GetString("menuServerList2", resourceCulture);
}
}
/// <summary>
/// 查找类似 Configuration item preview 的本地化字符串。
/// </summary>
public static string menuServerListPreview {
get {
return ResourceManager.GetString("menuServerListPreview", resourceCulture);
}
}
/// <summary>
/// 查找类似 Configuration 的本地化字符串。
/// </summary>
public static string menuServers {
get {
@@ -1681,7 +1699,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Set as active Configuration (Enter) 的本地化字符串。
/// 查找类似 Set as active 的本地化字符串。
/// </summary>
public static string menuSetDefaultServer {
get {
@@ -1699,7 +1717,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Share Configuration (Ctrl+F) 的本地化字符串。
/// 查找类似 Share 的本地化字符串。
/// </summary>
public static string menuShareServer {
get {
@@ -1726,7 +1744,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Test Configurations download speed (Ctrl+T) 的本地化字符串。
/// 查找类似 Test download speed 的本地化字符串。
/// </summary>
public static string menuSpeedServer {
get {
@@ -1870,7 +1888,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Test Configurations with tcping (Ctrl+O) 的本地化字符串。
/// 查找类似 Test tcping 的本地化字符串。
/// </summary>
public static string menuTcpingServer {
get {
@@ -1978,7 +1996,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Configuration filter, press Enter to execute 的本地化字符串。
/// 查找类似 Filter, press Enter to execute 的本地化字符串。
/// </summary>
public static string MsgServerTitle {
get {
@@ -2275,7 +2293,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Are you sure you want to remove the Configuration? 的本地化字符串。
/// 查找类似 Are you sure you want to remove? 的本地化字符串。
/// </summary>
public static string RemoveServer {
get {
@@ -2355,6 +2373,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Press ESC to terminate the test 的本地化字符串。
/// </summary>
public static string SpeedtestingPressEscToExit {
get {
return ResourceManager.GetString("SpeedtestingPressEscToExit", resourceCulture);
}
}
/// <summary>
/// 查找类似 Skip test 的本地化字符串。
/// </summary>
@@ -2383,7 +2410,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Waiting for testing (press ESC to terminate)... 的本地化字符串。
/// 查找类似 Waiting... 的本地化字符串。
/// </summary>
public static string SpeedtestingWait {
get {
@@ -2599,7 +2626,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Server Certificate (PEM format, optional)
/// 查找类似 Pinned certificate (fill in either one)
///When specified, the certificate will be pinned, and &quot;Allow Insecure&quot; will be disabled.
///
///The &quot;Get Certificate&quot; action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. 的本地化字符串。
@@ -2610,6 +2637,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Certificate fingerprint (SHA-256) 的本地化字符串。
/// </summary>
public static string TbCertSha256Tips {
get {
return ResourceManager.GetString("TbCertSha256Tips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Clear system proxy 的本地化字符串。
/// </summary>
@@ -2700,6 +2736,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Direct Target Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbDirectResolveStrategy {
get {
return ResourceManager.GetString("TbDirectResolveStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 If unset or &quot;AsIs&quot;, DNS resolution uses the system DNS; otherwise, the internal DNS module is used. 的本地化字符串。
/// </summary>
public static string TbDirectResolveStrategyTips {
get {
return ResourceManager.GetString("TbDirectResolveStrategyTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Display GUI 的本地化字符串。
/// </summary>
@@ -2772,6 +2826,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 By default, invoked only during routing for resolution 的本地化字符串。
/// </summary>
public static string TbDomesticDNSTips {
get {
return ResourceManager.GetString("TbDomesticDNSTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 EchConfigList 的本地化字符串。
/// </summary>
public static string TbEchConfigList {
get {
return ResourceManager.GetString("TbEchConfigList", resourceCulture);
}
}
/// <summary>
/// 查找类似 EchForceQuery 的本地化字符串。
/// </summary>
public static string TbEchForceQuery {
get {
return ResourceManager.GetString("TbEchForceQuery", resourceCulture);
}
}
/// <summary>
/// 查找类似 Edit 的本地化字符串。
/// </summary>
@@ -2835,6 +2916,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Finalmask 的本地化字符串。
/// </summary>
public static string TbFinalmask {
get {
return ResourceManager.GetString("TbFinalmask", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fingerprint 的本地化字符串。
/// </summary>
@@ -2853,6 +2943,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Full certificate (chain), PEM format 的本地化字符串。
/// </summary>
public static string TbFullCertTips {
get {
return ResourceManager.GetString("TbFullCertTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core&apos;s basic settings, DNS settings, and routing settings. You must ensure that the system proxy port, traffic statistics, and other related configurations are set correctly — everything will be configured by you. 的本地化字符串。
/// </summary>
@@ -2916,6 +3015,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Port hopping interval 的本地化字符串。
/// </summary>
public static string TbHopInt7 {
get {
return ResourceManager.GetString("TbHopInt7", resourceCulture);
}
}
/// <summary>
/// 查找类似 UUID(id) 的本地化字符串。
/// </summary>
@@ -3006,6 +3114,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Parallel Query 的本地化字符串。
/// </summary>
public static string TbParallelQuery {
get {
return ResourceManager.GetString("TbParallelQuery", resourceCulture);
}
}
/// <summary>
/// 查找类似 Path 的本地化字符串。
/// </summary>
@@ -3160,7 +3277,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Via proxy — please ensure remote availability 的本地化字符串。
/// 查找类似 By default, invoked only during routing for resolution; ensure the remote server can reach this DNS 的本地化字符串。
/// </summary>
public static string TbRemoteDNSTips {
get {
@@ -3168,6 +3285,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Proxy Target Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbRemoteResolveStrategy {
get {
return ResourceManager.GetString("TbRemoteResolveStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 If unset or &quot;AsIs&quot;, DNS resolution is performed by the remote server&apos;s DNS; otherwise, the internal DNS module is used. 的本地化字符串。
/// </summary>
public static string TbRemoteResolveStrategyTips {
get {
return ResourceManager.GetString("TbRemoteResolveStrategyTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Camouflage domain(host) 的本地化字符串。
/// </summary>
@@ -3232,7 +3367,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Full process name (Tun mode) 的本地化字符串。
/// 查找类似 Process (Tun mode) 的本地化字符串。
/// </summary>
public static string TbRoutingRuleProcess {
get {
@@ -3303,15 +3438,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbSBDirectResolveStrategy {
get {
return ResourceManager.GetString("TbSBDirectResolveStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Full Config Template 的本地化字符串。
/// </summary>
@@ -3330,15 +3456,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 sing-box Remote Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbSBRemoteResolveStrategy {
get {
return ResourceManager.GetString("TbSBRemoteResolveStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 Encryption method (security) 的本地化字符串。
/// </summary>
@@ -3384,6 +3501,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Serve Stale 的本地化字符串。
/// </summary>
public static string TbServeStale {
get {
return ResourceManager.GetString("TbServeStale", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set system proxy 的本地化字符串。
/// </summary>
@@ -3654,15 +3780,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Enable additional Inbound 的本地化字符串。
/// </summary>
public static string TbSettingsEnableExInbound {
get {
return ResourceManager.GetString("TbSettingsEnableExInbound", resourceCulture);
}
}
/// <summary>
/// 查找类似 Enable fragment 的本地化字符串。
/// </summary>
@@ -3672,15 +3789,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 which conflicts with the group previous proxy 的本地化字符串。
/// </summary>
public static string TbSettingsEnableFragmentTips {
get {
return ResourceManager.GetString("TbSettingsEnableFragmentTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Enable hardware acceleration (requires restart) 的本地化字符串。
/// </summary>
@@ -4096,7 +4204,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Tray right-click menu Configurations display limit 的本地化字符串。
/// 查找类似 Tray right-click menu display limit 的本地化字符串。
/// </summary>
public static string TbSettingsTrayMenuServersLimit {
get {
@@ -4357,7 +4465,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs 的本地化字符串。
/// 查找类似 When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs 的本地化字符串。
/// </summary>
public static string TbValidateDirectExpectedIPsDesc {
get {
@@ -4365,15 +4473,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 xray Freedom Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbXrayFreedomStrategy {
get {
return ResourceManager.GetString("TbXrayFreedomStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 The delay: {0} ms, {1} 的本地化字符串。
/// </summary>

View File

@@ -472,10 +472,10 @@
<value>زبان</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>وارد کردن URL انبوه از کلیپ بورد (Ctrl+V)</value>
<value>وارد کردن URL انبوه از کلیپ بورد</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>اسکن کد QR روی صفحه (Ctrl+S)</value>
<value>اسکن کد QR روی صفحه</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>سرور انتخاب شده را شبیه سازی کنید</value>
@@ -484,31 +484,31 @@
<value>سرورهای تکراری را حذف کنید</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>حذف سرورهای انتخابی (Delete)</value>
<value>حذف سرورهای انتخابی</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>به عنوان سرور فعال تنظیم کنید (Enter)</value>
<value>به عنوان سرور فعال تنظیم کنید</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>تمام آمار خدمات را پاک کنید</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>آزمایش سرورها با تاخیر واقعی (Ctrl+R)</value>
<value>آزمایش سرورها با تاخیر واقعی</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>مرتب سازی بر اساس نتیجه تست</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>تست سرعت دانلود سرورها (Ctrl+T)</value>
<value>تست سرعت دانلود سرورها</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>تست سرورها با tcping (Ctrl+O)</value>
<value>تست سرورها با tcping</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>سرور انتخابی را برای پیکربندی کلاینت صادر کنید</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>URL های اشتراک گذاری را به کلیپ بورد صادر کنید (Ctrl+C)</value>
<value>URL های اشتراک گذاری را به کلیپ بورد صادر کنید</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>یک سرور پیکربندی سفارشی اضافه شود</value>
@@ -529,19 +529,19 @@
<value>سرور [VMess] را اضافه کنید</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>انتخاب همه (Ctrl+A)</value>
<value>انتخاب همه</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>همه را پاک کن</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>کپی (Ctrl+C)</value>
<value>کپی</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>کپی همه</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>انتخاب همه (Ctrl+A)</value>
<value>انتخاب همه</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>اضافه کردن</value>
@@ -796,13 +796,13 @@
<value>به پایین حرکت شود(B)</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>پایین (D)</value>
<value>پایین</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>حرکت به بالا (T)</value>
<value>حرکت به بالا</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>بالا (U)</value>
<value>بالا</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>فیلتر، از عبارات منظم پشتیبانی می کند</value>
@@ -922,7 +922,7 @@
<value>رد شدن از آزمون</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>ویرایش سرور (Ctrl+D)</value>
<value>ویرایش سرور</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>دوبار کلیک کردن سرور باعث فعال شدن آن می شود</value>
@@ -976,7 +976,10 @@
<value>فعال‌ سازی شتاب‌ دهنده سخت‌ افزاری (نیاز به راه‌اندازی مجدد)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>در انتظار آزمایش (برای پایان دادن به ESC فشار دهید)...</value>
<value>در انتظار آزمایش...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>برای پایان دادن به ESC فشار دهید</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>لطفاً در صورت قطع غیرعادی آن را خاموش کنید</value>
@@ -1024,7 +1027,7 @@
<value>پروتکل sing-box Mux</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>نام کامل فرانید (حالت Tun)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP or IP CIDR</value>
@@ -1068,9 +1071,6 @@
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>فعال سازی additional Inbound</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
<value>فعال سازی آدرس IPv6</value>
</data>
@@ -1110,9 +1110,6 @@
<data name="menuAddHttpServer" xml:space="preserve">
<value>افزودن سرور [HTTP]</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>which conflicts with the group previous proxy</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>فعال کردن فرگمنت</value>
</data>
@@ -1204,7 +1201,7 @@
<value>تازه سازی پروکسی ها</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>انتخاب گره فعال (Enter)</value>
<value>انتخاب گره فعال</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>استراتژی دامنه پیش فرض برای خروجی</value>
@@ -1416,17 +1413,11 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>Domestic DNS</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>Via proxy — please ensure remote availability</value>
<data name="TbDirectResolveStrategy" xml:space="preserve">
<value>Direct Target Resolution Strategy</value>
</data>
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray Freedom Resolution Strategy</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>sing-box Direct Resolution Strategy</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>sing-box Remote Resolution Strategy</value>
<data name="TbRemoteResolveStrategy" xml:space="preserve">
<value>Proxy Target Resolution Strategy</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
@@ -1450,7 +1441,7 @@
<value>Validate Regional Domain IPs</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Enable Custom DNS</value>
@@ -1537,7 +1528,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1635,4 +1626,46 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
</data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>

View File

@@ -472,10 +472,10 @@
<value>Langue (redémarrage requis)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>Importer liens depuis le presse-papiers (Ctrl+V)</value>
<value>Importer liens depuis le presse-papiers</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>Scanner le QR code à lécran (Ctrl+S)</value>
<value>Scanner le QR code à lécran</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>Cloner la sélection</value>
@@ -484,7 +484,7 @@
<value>Supprimer les doublons</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>Supprimer la sélection (multi-sélection) (Delete)</value>
<value>Supprimer la sélection (multi-sélection)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>Définir comme actif (Entrée)</value>
@@ -493,22 +493,22 @@
<value>Effacer toutes les statistiques de service</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>Tester la latence de connexion réelle (multi-sélect) (Ctrl+R)</value>
<value>Tester la latence de connexion réelle (multi-sélect)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>Trier selon les résultats de test</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>Tester la vitesse (multi-sélection) (Ctrl+T)</value>
<value>Tester la vitesse (multi-sélection)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>Tester la latence Tcping (multi-sélection) (Ctrl+O)</value>
<value>Tester la latence Tcping (multi-sélection)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>Exporter la configuration complète sélectionnée</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>Exporter les liens de partage vers le presse-papiers (multi-sélection) (Ctrl+C)</value>
<value>Exporter les liens de partage vers le presse-papiers (multi-sélection)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>Ajouter une configuration personnalisée</value>
@@ -529,19 +529,19 @@
<value>Ajouter [VMess]</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>Tout sélectionner (Ctrl+A)</value>
<value>Tout sélectionner</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>Tout effacer</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>Copier (Ctrl+C)</value>
<value>Copier</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>Tout copier</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>Tout sélect (Ctrl+A)</value>
<value>Tout sélect</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>Ajouter</value>
@@ -781,7 +781,7 @@
<value>Mode PAC</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>Partager (Ctrl+F)</value>
<value>Partager</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>Routage</value>
@@ -793,16 +793,16 @@
<value>Exécuter en tant quadministrateur</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>Déplacer tout en bas (B)</value>
<value>Déplacer tout en bas</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>Descendre (D)</value>
<value>Descendre</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>Déplacer tout en haut (T)</value>
<value>Déplacer tout en haut</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>Monter (U)</value>
<value>Monter</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>Filtre (regex pris en charge)</value>
@@ -817,7 +817,7 @@
<value>Importer 1-clic du jeu de règles</value>
</data>
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
<value>Suppr. règles sélectionnées (Delete)</value>
<value>Suppr. règles sélectionnées</value>
</data>
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>Définir comme règles actives (Entrée)</value>
@@ -853,7 +853,7 @@
<value>Liste des règles</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>Supprimer les règles sélectionnées (Delete)</value>
<value>Supprimer les règles sélectionnées</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>Paramètres détaillés des règles de routage</value>
@@ -922,7 +922,7 @@
<value>Ignorer le test</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>Éditer (Ctrl+D)</value>
<value>Éditer</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>Double-cliquer sur linterface principale pour activer</value>
@@ -976,7 +976,10 @@
<value>Activer laccélération matérielle (redémarrage requis)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>En attente du test (appuyer sur Échap pour arrêter)...</value>
<value>En attente du test...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>Appuyer sur Échap pour arrêter</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>Désactiver cette option si coupure anormale</value>
@@ -1021,7 +1024,7 @@
<value>Protocole de multiplexage Mux (sing-box)</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Nom complet du processus (mode Tun)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP ou IP CIDR</value>
@@ -1065,9 +1068,6 @@
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>Activer un port découte supplémentaire</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
<value>Activer IPv6</value>
</data>
@@ -1107,9 +1107,6 @@
<data name="menuAddHttpServer" xml:space="preserve">
<value>Ajouter [HTTP]</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>En conflit avec le proxy amont de groupe</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>Activer le fragmentation (Fragment)</value>
</data>
@@ -1413,17 +1410,11 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>DNS direct</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>Via le proxy ; assurez-vous que le serveur distant est disponible</value>
<data name="TbDirectResolveStrategy" xml:space="preserve">
<value>Direct Target Resolution Strategy</value>
</data>
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>Stratégie de résolution xray freedom</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>Stratégie de résolution directe sing-box</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>Stratégie de résolution distante sing-box</value>
<data name="TbRemoteResolveStrategy" xml:space="preserve">
<value>Proxy Target Resolution Strategy</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Ajouter des hôtes DNS courants</value>
@@ -1447,7 +1438,7 @@
<value>Valider les IP des domaines de la région concernée</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>Après config, les IP renvoyées des domaines régionaux (ex. geosite:cn) seront vérifiées ; seules les IP attendues seront retournées.</value>
<value>Après config, les IP renvoyées des domaines régionaux (ex. geosite:cn - geoip:cn) seront vérifiées ; seules les IP attendues seront retournées.</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Activer le DNS personnalisé</value>
@@ -1534,7 +1525,7 @@
<value>Supprimer une sous-configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Liste des configurations</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Basculement (failover)</value>
@@ -1603,10 +1594,10 @@
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Certificat serveur (format PEM, facultatif)
Si le certificat est défini, il est fixé et loption « Ignorer la vérification » est désactivée.
<value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
Si un certificat auto-signé est utilisé ou si le système contient une CA non fiable ou malveillante, laction « Obtenir le certificat » peut échouer.</value>
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Obtenir le certificat</value>
@@ -1632,4 +1623,46 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>Afficher dans le Dock de macOS (redém. requis)</value>
</data>
</root>
<data name="menuServerList2" xml:space="preserve">
<value>Élément de config 2 : choisir et ajouter depuis self-hosted</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
</data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>

View File

@@ -472,10 +472,10 @@
<value>Nyelv (Újraindítás)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>Megosztási linkek importálása vágólapról (Ctrl+V)</value>
<value>Megosztási linkek importálása vágólapról</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>QR kód beolvasása a képernyőről (Ctrl+S)</value>
<value>QR kód beolvasása a képernyőről</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>Kijelölt konfiguráció klónozása</value>
@@ -484,31 +484,31 @@
<value>Ismétlődő konfigurációk eltávolítása</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>Kijelölt konfigurációk eltávolítása (Delete)</value>
<value>Kijelölt konfigurációk eltávolítása</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>Beállítás aktív konfigurációként (Enter)</value>
<value>Beállítás aktív konfigurációként</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>Összes szolgáltatás statisztika törlése</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>Konfigurációk valós késleltetésének tesztelése (Ctrl+R)</value>
<value>Konfigurációk valós késleltetésének tesztelése</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>Rendezés teszteredmény szerint</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>Konfigurációk letöltési sebességének tesztelése (Ctrl+T)</value>
<value>Konfigurációk letöltési sebességének tesztelése</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>Konfigurációk tesztelése tcpinggel (Ctrl+O)</value>
<value>Konfigurációk tesztelése tcpinggel</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>Kijelölt konfiguráció exportálása teljes konfigurációként</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>Megosztási link exportálása vágólapra (Ctrl+C)</value>
<value>Megosztási link exportálása vágólapra</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>Egyéni konfiguráció hozzáadása</value>
@@ -529,19 +529,19 @@
<value>[VMess] konfiguráció hozzáadása</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>Összes kijelölése (Ctrl+A)</value>
<value>Összes kijelölése</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>Összes törlése</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>Másolás (Ctrl+C)</value>
<value>Másolás</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>Összes másolása</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>Összes kijelölése (Ctrl+A)</value>
<value>Összes kijelölése</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>Hozzáadás</value>
@@ -781,7 +781,7 @@
<value>PAC mód</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>Konfiguráció megosztása (Ctrl+F)</value>
<value>Konfiguráció megosztása</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>Útválasztás</value>
@@ -793,16 +793,16 @@
<value>Futtatás rendszergazdaként</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>Mozgatás alulra (B)</value>
<value>Mozgatás alulra</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>Le (D)</value>
<value>Le</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>Mozgatás felülre (T)</value>
<value>Mozgatás felülre</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>Fel (U)</value>
<value>Fel</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>Szűrő, támogatja a reguláris kifejezéseket</value>
@@ -817,10 +817,10 @@
<value>Szabályok importálása</value>
</data>
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
<value>Kijelölt eltávolítása (Delete)</value>
<value>Kijelölt eltávolítása</value>
</data>
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>Beállítás aktív szabályként (Enter)</value>
<value>Beállítás aktív szabályként</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>Tartomány stratégia</value>
@@ -853,7 +853,7 @@
<value>Szabálylista</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>Szabály eltávolítása (Delete)</value>
<value>Szabály eltávolítása</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>Útválasztási szabály részleteinek beállítása</value>
@@ -922,7 +922,7 @@
<value>Teszt kihagyása</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>Konfiguráció szerkesztése (Ctrl+D)</value>
<value>Konfiguráció szerkesztése</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>Dupla kattintás a konfigurációra aktiválja</value>
@@ -976,7 +976,10 @@
<value>Hardveres gyorsítás engedélyezése (újraindítást igényel)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>Tesztelésre vár (ESC megnyomásával megszakítható)...</value>
<value>Tesztelésre vár...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>ESC megnyomásával megszakítható</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>Kérjük, kapcsolja ki rendellenes megszakadás esetén</value>
@@ -1024,7 +1027,7 @@
<value>sing-box Mux protokoll</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Teljes folyamatnév (Tun mód)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP vagy IP CIDR</value>
@@ -1068,9 +1071,6 @@
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>További bejövő engedélyezése</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
<value>IPv6 cím engedélyezése</value>
</data>
@@ -1110,9 +1110,6 @@
<data name="menuAddHttpServer" xml:space="preserve">
<value>HTTP konfiguráció hozzáadása</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>which conflicts with the group previous proxy</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>Fragment engedélyezése</value>
</data>
@@ -1204,7 +1201,7 @@
<value>Proxyk frissítése</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>Aktív csomópont kiválasztása (Enter)</value>
<value>Aktív csomópont kiválasztása</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Alapértelmezett tartomány stratégia kimenő forgalomhoz</value>
@@ -1416,17 +1413,11 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>Domestic DNS</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>Via proxy — please ensure remote availability</value>
<data name="TbDirectResolveStrategy" xml:space="preserve">
<value>Direct Target Resolution Strategy</value>
</data>
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray Freedom Resolution Strategy</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>sing-box Direct Resolution Strategy</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>sing-box Remote Resolution Strategy</value>
<data name="TbRemoteResolveStrategy" xml:space="preserve">
<value>Proxy Target Resolution Strategy</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
@@ -1450,7 +1441,7 @@
<value>Validate Regional Domain IPs</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Enable Custom DNS</value>
@@ -1537,7 +1528,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1606,7 +1597,7 @@
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
<value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
@@ -1635,4 +1626,46 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
</data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>

View File

@@ -271,7 +271,7 @@
<value>Configurations deduplication completed. Old: {0}, New: {1}.</value>
</data>
<data name="RemoveServer" xml:space="preserve">
<value>Are you sure you want to remove the Configuration?</value>
<value>Are you sure you want to remove?</value>
</data>
<data name="SaveClientConfigurationIn" xml:space="preserve">
<value>The client configuration file is saved at: {0}</value>
@@ -397,7 +397,7 @@
<value>Local</value>
</data>
<data name="MsgServerTitle" xml:space="preserve">
<value>Configuration filter, press Enter to execute</value>
<value>Filter, press Enter to execute</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve">
<value>Check Update</value>
@@ -427,7 +427,7 @@
<value>Routing Setting</value>
</data>
<data name="menuServers" xml:space="preserve">
<value>Configurations</value>
<value>Configuration</value>
</data>
<data name="menuSetting" xml:space="preserve">
<value>Settings</value>
@@ -472,76 +472,76 @@
<value>Language (Restart)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>Import Share Links from clipboard (Ctrl+V)</value>
<value>Import Share Links from clipboard</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>Scan QR code on the screen (Ctrl+S)</value>
<value>Scan QR code on the screen</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>Clone selected Configuration</value>
<value>Clone selected</value>
</data>
<data name="menuRemoveDuplicateServer" xml:space="preserve">
<value>Remove duplicate Configurations</value>
<value>Remove duplicate</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>Remove selected Configurations (Delete)</value>
<value>Remove selected</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>Set as active Configuration (Enter)</value>
<value>Set as active</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>Clear all service statistics</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>Test Configurations real delay (Ctrl+R)</value>
<value>Test real delay</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>Sort by test result</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>Test Configurations download speed (Ctrl+T)</value>
<value>Test download speed</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>Test Configurations with tcping (Ctrl+O)</value>
<value>Test tcping</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>Export selected Configuration for complete configuration</value>
<value>Export selected for complete configuration</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>Export Share Link to Clipboard (Ctrl+C)</value>
<value>Export Share Link to Clipboard</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>Add a custom configuration Configuration</value>
<value>Add a custom configuration</value>
</data>
<data name="menuAddShadowsocksServer" xml:space="preserve">
<value>Add [Shadowsocks] Configuration</value>
<value>Add [Shadowsocks] </value>
</data>
<data name="menuAddSocksServer" xml:space="preserve">
<value>Add [SOCKS] Configuration</value>
<value>Add [SOCKS] </value>
</data>
<data name="menuAddTrojanServer" xml:space="preserve">
<value>Add [Trojan] Configuration</value>
<value>Add [Trojan] </value>
</data>
<data name="menuAddVlessServer" xml:space="preserve">
<value>Add [VLESS] Configuration</value>
<value>Add [VLESS] </value>
</data>
<data name="menuAddVmessServer" xml:space="preserve">
<value>Add [VMess] Configuration</value>
<value>Add [VMess] </value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>Select all (Ctrl+A)</value>
<value>Select all</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>Clear all</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>Copy (Ctrl+C)</value>
<value>Copy</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>Copy all</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>Select all (Ctrl+A)</value>
<value>Select all</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>Add</value>
@@ -748,7 +748,7 @@
<value>System proxy settings</value>
</data>
<data name="TbSettingsTrayMenuServersLimit" xml:space="preserve">
<value>Tray right-click menu Configurations display limit</value>
<value>Tray right-click menu display limit</value>
</data>
<data name="TbSettingsUdpEnabled" xml:space="preserve">
<value>Enable UDP</value>
@@ -781,7 +781,7 @@
<value>PAC mode</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>Share Configuration (Ctrl+F)</value>
<value>Share</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>Routing</value>
@@ -793,16 +793,16 @@
<value>Run as Admin</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>Move to bottom (B)</value>
<value>Move to bottom</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>Down (D)</value>
<value>Down</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>Move to top (T)</value>
<value>Move to top</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>Up (U)</value>
<value>Up</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>Filter, supports regular expressions</value>
@@ -817,10 +817,10 @@
<value>Import Rules</value>
</data>
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
<value>Remove selected (Delete)</value>
<value>Remove selected</value>
</data>
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>Set as active rule (Enter)</value>
<value>Set as active rule</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>Domain strategy</value>
@@ -853,7 +853,7 @@
<value>Rule List</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>Remove Rule (Delete)</value>
<value>Remove Rule</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>Routing Rule Details Setting</value>
@@ -922,7 +922,7 @@
<value>Skip test</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>Edit Configuration (Ctrl+D)</value>
<value>Edit </value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>Double-clicking Configuration makes it active</value>
@@ -976,7 +976,10 @@
<value>Enable hardware acceleration (requires restart)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>Waiting for testing (press ESC to terminate)...</value>
<value>Waiting...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>Press ESC to terminate the test</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>Please turn off when there is an abnormal disconnection</value>
@@ -1024,7 +1027,7 @@
<value>sing-box Mux Protocol</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Full process name (Tun mode)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP or IP CIDR</value>
@@ -1033,7 +1036,7 @@
<value>Domain</value>
</data>
<data name="menuAddHysteria2Server" xml:space="preserve">
<value>Add [Hysteria2] Configuration</value>
<value>Add [Hysteria2] </value>
</data>
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
<value>Hysteria Max bandwidth (Up/Down)</value>
@@ -1042,16 +1045,16 @@
<value>Use System Hosts</value>
</data>
<data name="menuAddTuicServer" xml:space="preserve">
<value>Add [TUIC] Configuration</value>
<value>Add [TUIC] </value>
</data>
<data name="TbHeaderType8" xml:space="preserve">
<value>Congestion control</value>
</data>
<data name="LvPrevProfile" xml:space="preserve">
<value>Previous proxy Configuration remarks</value>
<value>Previous proxy remarks</value>
</data>
<data name="LvNextProfile" xml:space="preserve">
<value>Next proxy Configuration remarks</value>
<value>Next proxy remarks</value>
</data>
<data name="LvPrevProfileTip" xml:space="preserve">
<value>Please make sure the Configuration remarks exist and are unique</value>
@@ -1068,14 +1071,11 @@
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>Enable additional Inbound</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
<value>Enable IPv6 Address</value>
</data>
<data name="menuAddWireguardServer" xml:space="preserve">
<value>Add [WireGuard] Configuration</value>
<value>Add [WireGuard] </value>
</data>
<data name="TbPrivateKey" xml:space="preserve">
<value>Private Key</value>
@@ -1108,10 +1108,7 @@
<value>*grpc Authority</value>
</data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>Add [HTTP] Configuration</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>which conflicts with the group previous proxy</value>
<value>Add [HTTP]</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>Enable fragment</value>
@@ -1204,7 +1201,7 @@
<value>Refresh Proxies</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>Select active node (Enter)</value>
<value>Select active node</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Default domain strategy for outbound</value>
@@ -1222,7 +1219,7 @@
<value>Export Base64-encoded Share Links to Clipboard</value>
</data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>Export selected Configuration for complete configuration to clipboard</value>
<value>Export selected for complete configuration to clipboard</value>
</data>
<data name="menuShowOrHideMainWindow" xml:space="preserve">
<value>Show or hide the main window</value>
@@ -1378,22 +1375,22 @@
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Multi-Configuration Random by Xray</value>
<value>Random by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Multi-Configuration RoundRobin by Xray</value>
<value>RoundRobin by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by Xray</value>
<value>LeastPing by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Multi-Configuration LeastLoad by Xray</value>
<value>LeastLoad by Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by sing-box</value>
<value>LeastPing by sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
<value>Export Configuration</value>
<value>Export</value>
</data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>Current connection info test URL</value>
@@ -1408,7 +1405,7 @@
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls] Configuration</value>
<value>Add [Anytls]</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>Remote DNS</value>
@@ -1416,17 +1413,11 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>Domestic DNS</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>Via proxy — please ensure remote availability</value>
<data name="TbDirectResolveStrategy" xml:space="preserve">
<value>Direct Target Resolution Strategy</value>
</data>
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray Freedom Resolution Strategy</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>sing-box Direct Resolution Strategy</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>sing-box Remote Resolution Strategy</value>
<data name="TbRemoteResolveStrategy" xml:space="preserve">
<value>Proxy Target Resolution Strategy</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
@@ -1450,7 +1441,7 @@
<value>Validate Regional Domain IPs</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs</value>
<value>When configured, validates IPs returned for regional domains (e.g., geosite:cn - geoip:cn), returning only expected IPs</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Enable Custom DNS</value>
@@ -1525,28 +1516,28 @@
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group Configuration</value>
<value>Add Policy Group </value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
<value>Add Proxy Chain</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
<value>Add Child </value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
<value>Remove Child </value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
<value>Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
<value>Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
@@ -1606,7 +1597,7 @@
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
<value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
@@ -1635,4 +1626,46 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
</data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>

View File

@@ -472,10 +472,10 @@
<value>Язык (требуется перезапуск)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>Импорт массива URL из буфера обмена (Ctrl+V)</value>
<value>Импорт массива URL из буфера обмена</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>Сканировать QR-код с экрана (Ctrl+S)</value>
<value>Сканировать QR-код с экрана</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>Клонировать выбранный сервер</value>
@@ -484,31 +484,31 @@
<value>Удалить дубликаты серверов</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>Удалить выбранные серверы (Delete)</value>
<value>Удалить выбранные серверы</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>Установить как активный сервер (Enter)</value>
<value>Установить как активный сервер</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>Очистить всю статистику</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>Тест на реальную задержку сервера (Ctrl+R)</value>
<value>Тест на реальную задержку сервера</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>Сортировать по результату теста</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>Тест на скорость загрузки сервера (Ctrl+T)</value>
<value>Тест на скорость загрузки сервера</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>Тест задержки с tcping (Ctrl+O)</value>
<value>Тест задержки с tcping</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>Экспортировать выбранный сервер для клиента</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>Экспорт URL-адресов общего доступа в буфер обмена (Ctrl+C)</value>
<value>Экспорт URL-адресов общего доступа в буфер обмена</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>Добавить сервер пользовательской конфигурации</value>
@@ -529,19 +529,19 @@
<value>Добавить сервер [VMess]</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>Выбрать все (Ctrl+A)</value>
<value>Выбрать все</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>Очистить все</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>Скопировать (Ctrl+C)</value>
<value>Скопировать</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>Скопировать все</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>Выбрать все (Ctrl+A)</value>
<value>Выбрать все</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>Добавить</value>
@@ -781,7 +781,7 @@
<value>Режим PAC</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>Поделиться сервером (Ctrl+F)</value>
<value>Поделиться сервером</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>Маршрутизация</value>
@@ -793,16 +793,16 @@
<value>Администратор</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>Спуститься вниз (B)</value>
<value>Спуститься вниз</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>Вниз (D)</value>
<value>Вниз</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>Подняться наверх (T)</value>
<value>Подняться наверх</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>Вверх (U)</value>
<value>Вверх</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>Фильтр, поддерживает regex</value>
@@ -853,7 +853,7 @@
<value>Список правил</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>Удалить правила (Delete)</value>
<value>Удалить правила</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>Детальные настройки правил маршрутизации</value>
@@ -922,7 +922,7 @@
<value>Пропустить тест</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>Редактировать сервер (Ctrl+D)</value>
<value>Редактировать сервер</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>Двойной клик чтобы сделать сервер активным</value>
@@ -976,7 +976,10 @@
<value>Включить аппаратное ускорение (требуется перезагрузка)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>Ожидание тестирования (нажмите ESC для отмены)…</value>
<value>Ожидание тестирования…</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>нажмите ESC для отмены</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>Отключите при аномальном разрыве соединения</value>
@@ -1024,7 +1027,7 @@
<value>Протокол Mux для sing-box</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Полное имя процесса (режим TUN)</value>
<value>Process (Tun mode)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP-адрес или сеть CIDR</value>
@@ -1068,9 +1071,6 @@
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>Включить дополнительный входящий канал</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
<value>Включить IPv6 адреса</value>
</data>
@@ -1110,9 +1110,6 @@
<data name="menuAddHttpServer" xml:space="preserve">
<value>Добавить сервер [HTTP]</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>что конфликтует с предыдущим прокси группы</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>Включить фрагментацию (Fragment)</value>
</data>
@@ -1204,7 +1201,7 @@
<value>Обновить прокси</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>Сделать узел активным (Enter)</value>
<value>Сделать узел активным</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Стратегия домена по умолчанию для исходящих</value>
@@ -1416,17 +1413,11 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>Внутренний DNS</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>Via proxy — please ensure remote availability</value>
<data name="TbDirectResolveStrategy" xml:space="preserve">
<value>Direct Target Resolution Strategy</value>
</data>
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>Стратегия резолвинга Freedom (Xray)</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>Стратегия прямого резолвинга (sing-box)</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>Стратегия удалённого резолвинга (sing-box)</value>
<data name="TbRemoteResolveStrategy" xml:space="preserve">
<value>Proxy Target Resolution Strategy</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Добавить стандартные записи hosts (DNS)</value>
@@ -1450,7 +1441,7 @@
<value>Проверять IP-адреса региональных доменов</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn), и оставляет только ожидаемые IP-адреса</value>
<value>При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn - geoip:cn), и оставляет только ожидаемые IP-адреса</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Включить пользовательский DNS</value>
@@ -1537,7 +1528,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Configuration List</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1606,7 +1597,7 @@
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server Certificate (PEM format, optional)
<value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
@@ -1635,4 +1626,46 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
</data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>

View File

@@ -472,10 +472,10 @@
<value>语言 (需重启)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>从剪贴板导入分享链接 (Ctrl+V)</value>
<value>从剪贴板导入分享链接</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>扫描屏幕上的二维码 (Ctrl+S)</value>
<value>扫描屏幕上的二维码</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>克隆所选</value>
@@ -484,31 +484,31 @@
<value>移除重复</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>移除所选 (多选) (Delete)</value>
<value>移除所选 (多选)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>设为活动 (Enter)</value>
<value>设为活动</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>清除所有服务统计数据</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>测试真连接延迟 (多选) (Ctrl+R)</value>
<value>测试真连接延迟 (多选)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>按测试结果排序</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>测试速度 (多选) (Ctrl+T)</value>
<value>测试速度 (多选)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>测试延迟 Tcping (多选) (Ctrl+O)</value>
<value>测试延迟 Tcping (多选)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>导出所选完整配置</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>导出分享链接至剪贴板 (多选) (Ctrl+C)</value>
<value>导出分享链接至剪贴板 (多选)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>添加自定义配置</value>
@@ -529,19 +529,19 @@
<value>添加 [VMess] </value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>全选 (Ctrl+A)</value>
<value>全选</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>清除所有</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>复制 (Ctrl+C)</value>
<value>复制</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>复制所有</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>全选 (Ctrl+A)</value>
<value>全选</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>添加</value>
@@ -781,7 +781,7 @@
<value>Pac 模式</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>分享 (Ctrl+F)</value>
<value>分享</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>路由</value>
@@ -793,16 +793,16 @@
<value>以管理员身份运行</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>下移至底 (B)</value>
<value>下移至底</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>下移 (D)</value>
<value>下移</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>上移至顶 (T)</value>
<value>上移至顶</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>上移 (U)</value>
<value>上移</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>过滤器 (支持正则)</value>
@@ -817,10 +817,10 @@
<value>一键导入规则集</value>
</data>
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
<value>移除所选规则 (Delete)</value>
<value>移除所选规则</value>
</data>
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>设为活动规则 (Enter)</value>
<value>设为活动规则</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>域名解析策略</value>
@@ -853,7 +853,7 @@
<value>规则列表</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>移除所选规则 (Delete)</value>
<value>移除所选规则</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>路由规则详情设置</value>
@@ -922,7 +922,7 @@
<value>跳过测试</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>编辑 (Ctrl+D)</value>
<value>编辑</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>主界面双击设为活动</value>
@@ -976,7 +976,10 @@
<value>启用硬件加速 (需重启)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>等待测试中 (按 ESC 终止)...</value>
<value>等待测试...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>按 ESC 可终止测试</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>当有异常断流时请关闭</value>
@@ -1021,7 +1024,7 @@
<value>sing-box Mux 多路复用协议</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>进程名全称 (Tun 模式)</value>
<value>进程 (Tun 模式)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP 或 IP CIDR</value>
@@ -1065,9 +1068,6 @@
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>启用额外监听端口</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
<value>启用 IPv6</value>
</data>
@@ -1107,9 +1107,6 @@
<data name="menuAddHttpServer" xml:space="preserve">
<value>添加 [HTTP] </value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>和分组前置代理冲突</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>启用分片 (Fragment)</value>
</data>
@@ -1201,7 +1198,7 @@
<value>刷新</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>设为活动 (Enter)</value>
<value>设为活动</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Outbound 默认解析策略</value>
@@ -1413,17 +1410,11 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>直连 DNS</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>通过代理,请确保远程可用</value>
<data name="TbDirectResolveStrategy" xml:space="preserve">
<value>直连目标解析策略</value>
</data>
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray freedom 解析策略</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>sing-box 直连解析策略</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>sing-box 远程解析策略</value>
<data name="TbRemoteResolveStrategy" xml:space="preserve">
<value>代理目标解析策略</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>添加常用 DNS Hosts</value>
@@ -1447,7 +1438,7 @@
<value>校验相应地区域名 IP</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>配置后,会对相应地区域名(如 geosite:cn的返回 IP 进行校验,仅返回期望 IP</value>
<value>配置后,会对相应地区域名(如 geosite:cn - geoip:cn)的返回 IP 进行校验,仅返回期望 IP</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>启用自定义 DNS</value>
@@ -1534,7 +1525,7 @@
<value>删除子配置</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>子配置项</value>
<value>子配置项一,从订阅分组中自动添加</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>故障转移</value>
@@ -1603,10 +1594,10 @@
<value>固定证书</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>服务器证书PEM 格式,可选
<value>固定证书(二选一填写即可
当指定此证书后,将固定该证书,并禁用“跳过证书验证”选项。
“获取证书”操作可能失败,原因可能是使用了自签证书,或系统中存在不受信任恶意的 CA。</value>
“获取证书”操作可能失败,原因包括使用了自签证书,或系统中存在不受信任甚至恶意的 CA。</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>获取证书</value>
@@ -1632,4 +1623,46 @@
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS 在 Dock 栏中显示 (需重启)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>子配置项二,从自建中选择添加</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>完整证书PEM 格式</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>证书指纹SHA-256</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>乐观缓存</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>并行查询</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>默认仅在路由阶段被调用解析</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>默认仅在路由阶段被调用解析;请确保远程服务器可访问该 DNS</value>
</data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>当未选择或 "AsIs" 时,使用系统 DNS 进行解析;否则,使用内部 DNS 模块解析。</value>
</data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>当未选择或 "AsIs" 时,由远程服务器端 DNS 解析;否则,使用内部 DNS 模块解析。</value>
</data>
<data name="TbHopInt7" xml:space="preserve">
<value>端口跳跃间隔</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>子配置项预览</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>

View File

@@ -472,10 +472,10 @@
<value>語言 (需重啟)</value>
</data>
<data name="menuAddServerViaClipboard" xml:space="preserve">
<value>從剪貼簿導入分享連結 (Ctrl+V)</value>
<value>從剪貼簿導入分享連結</value>
</data>
<data name="menuAddServerViaScan" xml:space="preserve">
<value>掃描螢幕上的二維碼 (Ctrl+S)</value>
<value>掃描螢幕上的二維碼</value>
</data>
<data name="menuCopyServer" xml:space="preserve">
<value>複製所選</value>
@@ -484,31 +484,31 @@
<value>移除重複</value>
</data>
<data name="menuRemoveServer" xml:space="preserve">
<value>移除所選 (多選) (Delete)</value>
<value>移除所選 (多選)</value>
</data>
<data name="menuSetDefaultServer" xml:space="preserve">
<value>設為活動 (Enter)</value>
<value>設為活動</value>
</data>
<data name="menuClearServerStatistics" xml:space="preserve">
<value>清除所有服務統計資料</value>
</data>
<data name="menuRealPingServer" xml:space="preserve">
<value>測試真連線延遲 (多選) (Ctrl+R)</value>
<value>測試真連線延遲 (多選)</value>
</data>
<data name="menuSortServerResult" xml:space="preserve">
<value>按測試結果排序</value>
</data>
<data name="menuSpeedServer" xml:space="preserve">
<value>測試速度 (多選) (Ctrl+T)</value>
<value>測試速度 (多選)</value>
</data>
<data name="menuTcpingServer" xml:space="preserve">
<value>測試延遲 Tcping (多選) (Ctrl+O)</value>
<value>測試延遲 Tcping (多選)</value>
</data>
<data name="menuExport2ClientConfig" xml:space="preserve">
<value>匯出所選完整設定</value>
</data>
<data name="menuExport2ShareUrl" xml:space="preserve">
<value>匯出分享連結至剪貼簿 (多選) (Ctrl+C)</value>
<value>匯出分享連結至剪貼簿 (多選)</value>
</data>
<data name="menuAddCustomServer" xml:space="preserve">
<value>新增自訂節點</value>
@@ -529,19 +529,19 @@
<value>新增 [VMess] 節點</value>
</data>
<data name="menuSelectAll" xml:space="preserve">
<value>全選 (Ctrl+A)</value>
<value>全選</value>
</data>
<data name="menuMsgViewClear" xml:space="preserve">
<value>清除所有</value>
</data>
<data name="menuMsgViewCopy" xml:space="preserve">
<value>複製 (Ctrl+C)</value>
<value>複製</value>
</data>
<data name="menuMsgViewCopyAll" xml:space="preserve">
<value>複製所有</value>
</data>
<data name="menuMsgViewSelectAll" xml:space="preserve">
<value>全選 (Ctrl+A)</value>
<value>全選</value>
</data>
<data name="menuSubAdd" xml:space="preserve">
<value>新增</value>
@@ -781,7 +781,7 @@
<value>PAC 模式</value>
</data>
<data name="menuShareServer" xml:space="preserve">
<value>分享 (Ctrl+F)</value>
<value>分享</value>
</data>
<data name="menuRouting" xml:space="preserve">
<value>路由</value>
@@ -793,16 +793,16 @@
<value>以管理員身份執行</value>
</data>
<data name="menuMoveBottom" xml:space="preserve">
<value>下移至底部 (B)</value>
<value>下移至底部</value>
</data>
<data name="menuMoveDown" xml:space="preserve">
<value>下移 (D)</value>
<value>下移</value>
</data>
<data name="menuMoveTop" xml:space="preserve">
<value>上移至頂部 (T)</value>
<value>上移至頂部</value>
</data>
<data name="menuMoveUp" xml:space="preserve">
<value>上移 (U)</value>
<value>上移</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>過濾 (允許正則)</value>
@@ -817,10 +817,10 @@
<value>一鍵匯入規則集</value>
</data>
<data name="menuRoutingAdvancedRemove" xml:space="preserve">
<value>移除所選規則 (Delete)</value>
<value>移除所選規則</value>
</data>
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>設為活動規則 (Enter)</value>
<value>設為活動規則</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>域名解析策略</value>
@@ -853,7 +853,7 @@
<value>規則列表</value>
</data>
<data name="menuRuleRemove" xml:space="preserve">
<value>移除所選規則 (Delete)</value>
<value>移除所選規則</value>
</data>
<data name="menuRoutingRuleDetailsSetting" xml:space="preserve">
<value>路由規則詳情設定</value>
@@ -922,7 +922,7 @@
<value>跳過測試</value>
</data>
<data name="menuEditServer" xml:space="preserve">
<value>編輯 (Ctrl+D)</value>
<value>編輯</value>
</data>
<data name="TbSettingsDoubleClick2Activate" xml:space="preserve">
<value>主介面輕按兩下設為活動</value>
@@ -976,7 +976,10 @@
<value>啟用硬體加速 (需重啟)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>等待測試中(按 ESC 終止)...</value>
<value>等待測試中...</value>
</data>
<data name="SpeedtestingPressEscToExit" xml:space="preserve">
<value>按 ECS 以終止測試</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>當有異常斷流時請關閉</value>
@@ -1021,7 +1024,7 @@
<value>sing-box Mux 多路復用協定</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>行程名全稱 (Tun 模式)</value>
<value>行程 (Tun 模式)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP 或 IP CIDR</value>
@@ -1065,9 +1068,6 @@
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>啟用額外偵聽連接埠</value>
</data>
<data name="TbSettingsEnableIPv6Address" xml:space="preserve">
<value>啟用 IPv6</value>
</data>
@@ -1107,9 +1107,6 @@
<data name="menuAddHttpServer" xml:space="preserve">
<value>新增 [HTTP] 節點</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>和分組前置代理衝突</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>啟用分片Fragment</value>
</data>
@@ -1201,7 +1198,7 @@
<value>重新整理</value>
</data>
<data name="menuProxiesSelectActivity" xml:space="preserve">
<value>設為活動節點 (Enter)</value>
<value>設為活動節點</value>
</data>
<data name="TbSettingsDomainStrategy4Out" xml:space="preserve">
<value>Outbound 預設解析策略</value>
@@ -1413,17 +1410,11 @@
<data name="TbDomesticDNS" xml:space="preserve">
<value>直連 DNS</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>通过代理,请确保远程可用</value>
<data name="TbDirectResolveStrategy" xml:space="preserve">
<value>直連目標解析策略</value>
</data>
<data name="TbXrayFreedomStrategy" xml:space="preserve">
<value>xray freedom 解析策略</value>
</data>
<data name="TbSBDirectResolveStrategy" xml:space="preserve">
<value>sing-box 直連解析策略</value>
</data>
<data name="TbSBRemoteResolveStrategy" xml:space="preserve">
<value>sing-box 遠程解析策略</value>
<data name="TbRemoteResolveStrategy" xml:space="preserve">
<value>代理目標解析策略</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>新增常用 DNS Hosts</value>
@@ -1447,7 +1438,7 @@
<value>校驗相應地區域名 IP</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>配置後,會對相應地區域名(如 geosite:cn的返回 IP 進行校驗,僅返回期望 IP</value>
<value>配置後,會對相應地區域名(如 geosite:cn - geoip:cn)的返回 IP 進行校驗,僅返回期望 IP</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>啟用自訂 DNS</value>
@@ -1534,7 +1525,7 @@
<value>刪除子配置</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>子配置項</value>
<value>子配置項目一,從訂閱分組中自動新增</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>容錯移轉</value>
@@ -1603,7 +1594,7 @@
<value>憑證綁定</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>伺服器憑證PEM 格式,可選
<value>固定憑證(二選一填寫即可
若已指定,憑證將會被綁定,並且「跳過憑證驗證」將被停用。
若使用自簽憑證,或系統中存在不受信任或惡意的 CA「取得憑證」動作可能會失敗。</value>
@@ -1632,4 +1623,46 @@
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>macOS 在 Dock 欄顯示 (需重啟)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>子配置項二,從自建中選擇新增</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
</data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
</root>

View File

@@ -1,4 +1,4 @@
{
{
"log": {
"access": "Vaccess.log",
"error": "Verror.log",
@@ -6,34 +6,6 @@
},
"inbounds": [],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [{
"address": "",
"port": 0,
"users": [{
"id": "",
"security": "auto"
}]
}],
"servers": [{
"address": "",
"method": "",
"ota": false,
"password": "",
"port": 0,
"level": 1
}]
},
"streamSettings": {
"network": "tcp"
},
"mux": {
"enabled": false
}
},
{
"protocol": "freedom",
"tag": "direct"

View File

@@ -5,19 +5,12 @@
},
"inbounds": [],
"outbounds": [
{
"type": "vless",
"tag": "proxy",
"server": "",
"server_port": 443
},
{
"type": "direct",
"tag": "direct"
}
],
"route": {
"rules": [
]
"rules": []
}
}

View File

@@ -1,43 +1,34 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService(Config config)
public partial class CoreConfigSingboxService(CoreConfigContext context)
{
private readonly Config _config = config;
private static readonly string _tag = "CoreConfigSingboxService";
private readonly Config _config = context.AppConfig;
private readonly ProfileItem _node = context.Node;
private SingboxConfig _coreConfig = new();
#region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
public RetResult GenerateClientConfigContent()
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
if (_node == null
|| !_node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
if (node.ConfigType.IsGroupType())
{
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(node);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(node);
}
}
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
if (result.IsNullOrEmpty())
{
@@ -45,44 +36,76 @@ public partial class CoreConfigSingboxService(Config config)
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(singboxConfig);
GenLog();
await GenInbounds(singboxConfig);
GenInbounds();
if (node.ConfigType == EConfigType.WireGuard)
{
singboxConfig.outbounds.RemoveAt(0);
var endpoints = new Endpoints4Sbox();
await GenEndpoint(node, endpoints);
endpoints.tag = Global.ProxyTag;
singboxConfig.endpoints = new() { endpoints };
}
else
{
await GenOutbound(node, singboxConfig.outbounds.First());
}
GenOutbounds();
await GenMoreOutbounds(node, singboxConfig);
GenRouting();
await GenRouting(singboxConfig);
GenDns();
await GenDns(node, singboxConfig);
GenExperimental();
await GenExperimental(singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ConvertGeo2Ruleset();
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
ret.Data = ApplyFullConfigTemplate();
if (context.TunProtectSsPort is > 0 and <= 65535)
{
var ssInbound = new
{
type = "shadowsocks",
tag = "tun-protect-ss",
listen = Global.Loopback,
listen_port = context.TunProtectSsPort,
method = "none",
password = "none",
};
var directRule = new Rule4Sbox()
{
inbound = new List<string> { ssInbound.tag },
outbound = Global.DirectTag,
};
var singboxConfigNode = JsonUtils.ParseJson(ret.Data.ToString())!.AsObject();
var inboundsNode = singboxConfigNode["inbounds"]!.AsArray();
inboundsNode.Add(JsonUtils.SerializeToNode(ssInbound, new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
}));
var routeNode = singboxConfigNode["route"]?.AsObject();
var rulesNode = routeNode?["rules"]?.AsArray();
var protectRuleNode = JsonUtils.SerializeToNode(directRule,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
if (rulesNode != null)
{
rulesNode.Insert(0, protectRuleNode);
}
else
{
var newRulesNode = new JsonArray() { protectRuleNode };
if (routeNode is null)
{
var newRouteNode = new JsonObject() { ["rules"] = newRulesNode };
singboxConfigNode["route"] = newRouteNode;
}
else
{
routeNode["rules"] = newRulesNode;
}
}
ret.Data = JsonUtils.Serialize(singboxConfigNode);
}
return ret;
}
catch (Exception ex)
@@ -93,17 +116,11 @@ public partial class CoreConfigSingboxService(Config config)
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
public RetResult GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
@@ -114,29 +131,19 @@ public partial class CoreConfigSingboxService(Config config)
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
List<IPEndPoint> lstIpEndPoints = new();
List<TcpConnectionInformation> lstTcpConns = new();
try
{
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
await GenLog(singboxConfig);
//GenDns(new(), singboxConfig);
singboxConfig.inbounds.Clear();
singboxConfig.outbounds.RemoveAt(0);
var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo();
GenLog();
GenMinimizedDns();
_coreConfig.inbounds.Clear();
_coreConfig.outbounds.RemoveAt(0);
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
@@ -150,8 +157,9 @@ public partial class CoreConfigSingboxService(Config config)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null || item.IsComplex() || !item.IsValid())
var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId);
var item = context.AllProxiesMap.GetValueOrDefault(actIndexId);
if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid())
{
continue;
}
@@ -190,26 +198,11 @@ public partial class CoreConfigSingboxService(Config config)
type = EInboundProtocol.mixed.ToString(),
};
inbound.tag = inbound.type + inbound.listen_port.ToString();
singboxConfig.inbounds.Add(inbound);
_coreConfig.inbounds.Add(inbound);
//outbound
var server = await GenServer(item);
if (server is null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
var tag = Global.ProxyTag + inbound.listen_port.ToString();
server.tag = tag;
if (server is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (server is Outbound4Sbox outbound)
{
singboxConfig.outbounds.Add(outbound);
}
var serverList = new CoreConfigSingboxService(context with { Node = item }).BuildAllProxyOutbounds(tag);
FillRangeProxy(serverList, _coreConfig, false);
//rule
Rule4Sbox rule = new()
@@ -217,25 +210,11 @@ public partial class CoreConfigSingboxService(Config config)
inbound = new List<string> { inbound.tag },
outbound = tag
};
singboxConfig.route.rules.Add(rule);
_coreConfig.route.rules.Add(rule);
}
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (rawDNSItem != null && rawDNSItem.Enabled == true)
{
await GenDnsDomainsCompatible(singboxConfig, rawDNSItem);
}
else
{
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
}
singboxConfig.route.default_domain_resolver = new()
{
server = Global.SingboxLocalDNSTag,
};
ret.Success = true;
ret.Data = JsonUtils.Serialize(singboxConfig);
ret.Data = JsonUtils.Serialize(_coreConfig);
return ret;
}
catch (Exception ex)
@@ -246,20 +225,20 @@ public partial class CoreConfigSingboxService(Config config)
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
public RetResult GenerateClientSpeedtestConfig(int port)
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
if (_node == null
|| !_node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret;
}
@@ -272,44 +251,20 @@ public partial class CoreConfigSingboxService(Config config)
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
_coreConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(singboxConfig);
if (node.ConfigType == EConfigType.WireGuard)
{
singboxConfig.outbounds.RemoveAt(0);
var endpoints = new Endpoints4Sbox();
await GenEndpoint(node, endpoints);
endpoints.tag = Global.ProxyTag;
singboxConfig.endpoints = new() { endpoints };
}
else
{
await GenOutbound(node, singboxConfig.outbounds.First());
}
await GenMoreOutbounds(node, singboxConfig);
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (item != null && item.Enabled == true)
{
await GenDnsDomainsCompatible(singboxConfig, item);
}
else
{
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
}
singboxConfig.route.default_domain_resolver = new()
{
server = Global.SingboxLocalDNSTag,
};
GenLog();
GenOutbounds();
GenMinimizedDns();
singboxConfig.route.rules.Clear();
singboxConfig.inbounds.Clear();
singboxConfig.inbounds.Add(new()
_coreConfig.route.rules.Clear();
_coreConfig.inbounds.Clear();
_coreConfig.inbounds.Add(new()
{
tag = $"{EInboundProtocol.mixed}{port}",
listen = Global.Loopback,
@@ -319,202 +274,7 @@ public partial class CoreConfigSingboxService(Config config)
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = JsonUtils.Serialize(singboxConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
singboxConfig.outbounds.RemoveAt(0);
await GenLog(singboxConfig);
await GenInbounds(singboxConfig);
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
singboxConfig.outbounds.RemoveAt(0);
await GenLog(singboxConfig);
await GenInbounds(singboxConfig);
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
{
var ret = new RetResult();
if (node == null || fileName is null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
try
{
if (node == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (File.Exists(fileName))
{
File.Delete(fileName);
}
var addressFileName = node.Address;
if (addressFileName.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
if (!File.Exists(addressFileName))
{
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
}
if (!File.Exists(addressFileName))
{
ret.Msg = ResUI.FailedReadConfiguration + "1";
return ret;
}
if (node.Address == Global.CoreMultipleLoadConfigFileName)
{
var txtFile = File.ReadAllText(addressFileName);
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(txtFile);
if (singboxConfig == null)
{
File.Copy(addressFileName, fileName);
}
else
{
await GenInbounds(singboxConfig);
await GenExperimental(singboxConfig);
var content = JsonUtils.Serialize(singboxConfig, true);
await File.WriteAllTextAsync(fileName, content);
}
}
else
{
File.Copy(addressFileName, fileName);
}
//check again
if (!File.Exists(fileName))
{
ret.Msg = ResUI.FailedReadConfiguration + "2";
return ret;
}
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = JsonUtils.Serialize(_coreConfig);
return ret;
}
catch (Exception ex)

View File

@@ -2,29 +2,29 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig)
private string ApplyFullConfigTemplate()
{
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled)
var fullConfigTemplate = context.FullConfigTemplate;
if (fullConfigTemplate is not { Enabled: true })
{
return JsonUtils.Serialize(singboxConfig);
return JsonUtils.Serialize(_coreConfig);
}
var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
if (fullConfigTemplateItem.IsNullOrEmpty())
{
return JsonUtils.Serialize(singboxConfig);
return JsonUtils.Serialize(_coreConfig);
}
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
if (fullConfigTemplateNode == null)
{
return JsonUtils.Serialize(singboxConfig);
return JsonUtils.Serialize(_coreConfig);
}
// Process outbounds
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
foreach (var outbound in singboxConfig.outbounds)
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : [];
foreach (var outbound in _coreConfig.outbounds)
{
if (outbound.type.ToLower() is "direct" or "block")
{
@@ -42,10 +42,10 @@ public partial class CoreConfigSingboxService
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
// Process endpoints
if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0)
if (_coreConfig.endpoints != null && _coreConfig.endpoints.Count > 0)
{
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray();
foreach (var endpoint in singboxConfig.endpoints)
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : [];
foreach (var endpoint in _coreConfig.endpoints)
{
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
{
@@ -56,6 +56,6 @@ public partial class CoreConfigSingboxService
fullConfigTemplateNode["endpoints"] = customEndpointsNode;
}
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
return JsonUtils.Serialize(fullConfigTemplateNode);
}
}

View File

@@ -2,25 +2,25 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
private void GenDns()
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (item != null && item.Enabled == true)
var item = context.RawDnsItem;
if (item is { Enabled: true })
{
return await GenDnsCompatible(node, singboxConfig);
GenDnsCustom();
return;
}
var simpleDNSItem = _config.SimpleDNSItem;
await GenDnsServers(singboxConfig, simpleDNSItem);
await GenDnsRules(singboxConfig, simpleDNSItem);
GenDnsServers();
GenDnsRules();
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.independent_cache = true;
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.independent_cache = true;
// final dns
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
var useDirectDns = false;
if (routing != null)
{
@@ -32,35 +32,34 @@ public partial class CoreConfigSingboxService
lastRule.Network == "tcp,udp" ||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
}
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
if ((!useDirectDns) && simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
_coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
var simpleDnsItem = context.SimpleDnsItem;
if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{
singboxConfig.dns.rules.Add(new()
_coreConfig.dns.rules.Add(new()
{
server = Global.SingboxFakeDNSTag,
query_type = new List<int> { 1, 28 }, // A and AAAA
rewrite_ttl = 1,
});
}
await GenOutboundDnsRule(node, singboxConfig);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
private void GenDnsServers()
{
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
var simpleDnsItem = context.SimpleDnsItem;
var finalDns = GenBootstrapDns();
var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS);
var directDns = ParseDnsAddress(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First());
directDns.tag = Global.SingboxDirectDNSTag;
directDns.domain_resolver = Global.SingboxLocalDNSTag;
var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS);
var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First());
remoteDns.tag = Global.SingboxRemoteDNSTag;
remoteDns.detour = Global.ProxyTag;
remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
@@ -71,12 +70,12 @@ public partial class CoreConfigSingboxService
type = "hosts",
predefined = new(),
};
if (simpleDNSItem.AddCommonHosts == true)
if (simpleDnsItem.AddCommonHosts == true)
{
hostsDns.predefined = Global.PredefinedHosts;
}
if (simpleDNSItem.UseSystemHosts == true)
if (simpleDnsItem.UseSystemHosts == true)
{
var systemHosts = Utils.GetSystemHosts();
if (systemHosts != null && systemHosts.Count > 0)
@@ -88,14 +87,9 @@ public partial class CoreConfigSingboxService
}
}
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
{
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
foreach (var kvp in userHostsMap)
{
hostsDns.predefined[kvp.Key] = kvp.Value;
}
hostsDns.predefined[kvp.Key] = kvp.Value.Where(Utils.IsIpAddress).ToList();
}
foreach (var host in hostsDns.predefined)
@@ -114,14 +108,14 @@ public partial class CoreConfigSingboxService
}
}
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= new List<Server4Sbox>();
singboxConfig.dns.servers.Add(remoteDns);
singboxConfig.dns.servers.Add(directDns);
singboxConfig.dns.servers.Add(hostsDns);
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.servers ??= [];
_coreConfig.dns.servers.Add(remoteDns);
_coreConfig.dns.servers.Add(directDns);
_coreConfig.dns.servers.Add(hostsDns);
// fake ip
if (simpleDNSItem.FakeIP == true)
if (simpleDnsItem.FakeIP == true)
{
var fakeip = new Server4Sbox
{
@@ -130,55 +124,103 @@ public partial class CoreConfigSingboxService
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
};
singboxConfig.dns.servers.Add(fakeip);
_coreConfig.dns.servers.Add(fakeip);
}
return await Task.FromResult(0);
}
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
private Server4Sbox GenBootstrapDns()
{
var finalDns = ParseDnsAddress(simpleDNSItem.BootstrapDNS);
var finalDns = ParseDnsAddress(context.SimpleDnsItem?.BootstrapDNS ?? Global.DomainPureIPDNSAddress.First());
finalDns.tag = Global.SingboxLocalDNSTag;
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= new List<Server4Sbox>();
singboxConfig.dns.servers.Add(finalDns);
return await Task.FromResult(finalDns);
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.servers ??= [];
_coreConfig.dns.servers.Add(finalDns);
return finalDns;
}
private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
private void GenDnsRules()
{
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
var simpleDnsItem = context.SimpleDnsItem;
_coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.rules ??= [];
singboxConfig.dns.rules.AddRange(new[]
{
_coreConfig.dns.rules.AddRange(new[]
{
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
domain = context.ProtectDomainList.ToList(),
},
new Rule4Sbox
{
server = Global.SingboxRemoteDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy),
clash_mode = ERuleMode.Global.ToString()
},
new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
clash_mode = ERuleMode.Direct.ToString()
}
});
if (simpleDNSItem.BlockBindingQuery == true)
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts))
{
singboxConfig.dns.rules.Add(new()
var predefined = kvp.Value.First();
if (predefined.IsNullOrEmpty() || Utils.IsIpAddress(predefined))
{
query_type = new List<int> { 64, 65 },
continue;
}
if (predefined.StartsWith('#') && int.TryParse(predefined.AsSpan(1), out var rcode))
{
// xray syntactic sugar for predefined
// etc. #0 -> NOERROR
_coreConfig.dns.rules.Add(new()
{
query_type = [1, 28],
domain = [kvp.Key],
action = "predefined",
rcode = rcode switch
{
0 => "NOERROR",
1 => "FORMERR",
2 => "SERVFAIL",
3 => "NXDOMAIN",
4 => "NOTIMP",
5 => "REFUSED",
_ => "NOERROR",
},
});
continue;
}
// CNAME record
Rule4Sbox rule = new()
{
query_type = [1, 28],
action = "predefined",
rcode = "NOTIMP"
rcode = "NOERROR",
answer = [$"*. IN CNAME {predefined}."],
};
if (ParseV2Domain(kvp.Key, rule))
{
_coreConfig.dns.rules.Add(rule);
}
}
if (simpleDnsItem.BlockBindingQuery == true)
{
_coreConfig.dns.rules.Add(new()
{
query_type = [64, 65],
action = "predefined",
rcode = "NOERROR"
});
}
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == true)
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true)
{
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
fakeipFilterRule.invert = true;
@@ -188,22 +230,23 @@ public partial class CoreConfigSingboxService
type = "logical",
mode = "and",
rewrite_ttl = 1,
rules = new List<Rule4Sbox>
{
new() {
query_type = new List<int> { 1, 28 }, // A and AAAA
rules =
[
new()
{
query_type = [1, 28], // A and AAAA
},
fakeipFilterRule,
}
fakeipFilterRule
]
};
singboxConfig.dns.rules.Add(rule4Fake);
_coreConfig.dns.rules.Add(rule4Fake);
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
if (routing == null)
{
return 0;
return;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
@@ -211,9 +254,9 @@ public partial class CoreConfigSingboxService
var expectedIPsRegions = new List<string>();
var regionNames = new HashSet<string>();
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs))
{
var ipItems = simpleDNSItem.DirectExpectedIPs
var ipItems = simpleDnsItem.DirectExpectedIPs
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s))
@@ -261,7 +304,7 @@ public partial class CoreConfigSingboxService
if (item.OutboundTag == Global.DirectTag)
{
rule.server = Global.SingboxDirectDNSTag;
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Direct) ? null : simpleDNSItem.SingboxStrategy4Direct;
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
{
@@ -286,31 +329,46 @@ public partial class CoreConfigSingboxService
}
else
{
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false)
{
var rule4Fake = JsonUtils.DeepCopy(rule);
rule4Fake.server = Global.SingboxFakeDNSTag;
rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA
rule4Fake.rewrite_ttl = 1;
singboxConfig.dns.rules.Add(rule4Fake);
_coreConfig.dns.rules.Add(rule4Fake);
}
rule.server = Global.SingboxRemoteDNSTag;
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Proxy) ? null : simpleDNSItem.SingboxStrategy4Proxy;
rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy);
}
singboxConfig.dns.rules.Add(rule);
_coreConfig.dns.rules.Add(rule);
}
return 0;
}
private async Task<int> GenDnsCompatible(ProfileItem? node, SingboxConfig singboxConfig)
private void GenMinimizedDns()
{
GenDnsServers();
foreach (var server in _coreConfig.dns!.servers.Where(s => !string.IsNullOrEmpty(s.detour)).ToList())
{
_coreConfig.dns.servers.Remove(server);
}
_coreConfig.dns ??= new();
_coreConfig.dns.rules ??= [];
_coreConfig.dns.rules.Clear();
_coreConfig.dns.final = Global.SingboxDirectDNSTag;
_coreConfig.route.default_domain_resolver = new()
{
server = Global.SingboxDirectDNSTag,
};
}
private void GenDnsCustom()
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
var item = context.RawDnsItem;
var strDNS = string.Empty;
if (_config.TunModeItem.EnableTun)
if (context.IsTunEnabled)
{
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
}
@@ -322,61 +380,33 @@ public partial class CoreConfigSingboxService
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
if (dns4Sbox is null)
{
return 0;
return;
}
singboxConfig.dns = dns4Sbox;
if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty())
_coreConfig.dns = dns4Sbox;
if (dns4Sbox.servers?.Count > 0 &&
dns4Sbox.servers.First().address.IsNullOrEmpty())
{
await GenDnsDomainsCompatible(singboxConfig, item);
GenDnsProtectCustom();
}
else
{
await GenDnsDomainsLegacyCompatible(singboxConfig, item);
GenDnsProtectCustomLegacy();
}
await GenOutboundDnsRule(node, singboxConfig);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem)
private void GenDnsProtectCustom()
{
var dns4Sbox = singboxConfig.dns ?? new();
var dnsItem = context.RawDnsItem;
var dns4Sbox = _coreConfig.dns ?? new();
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
var tag = Global.SingboxLocalDNSTag;
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
var localDnsServer = ParseDnsAddress(finalDnsAddress);
localDnsServer.tag = tag;
dns4Sbox.servers.Add(localDnsServer);
singboxConfig.dns = dns4Sbox;
return await Task.FromResult(0);
}
private async Task<int> GenDnsDomainsLegacyCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem)
{
var dns4Sbox = singboxConfig.dns ?? new();
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
var tag = Global.SingboxLocalDNSTag;
dns4Sbox.servers.Add(new()
{
tag = tag,
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
detour = Global.DirectTag,
strategy = string.IsNullOrEmpty(dnsItem?.DomainStrategy4Freedom) ? null : dnsItem?.DomainStrategy4Freedom,
});
dns4Sbox.rules.Insert(0, new()
{
server = tag,
@@ -388,56 +418,41 @@ public partial class CoreConfigSingboxService
clash_mode = ERuleMode.Global.ToString()
});
var lstDomain = singboxConfig.outbounds
.Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server))
.Select(t => t.server)
.Distinct()
.ToList();
if (lstDomain != null && lstDomain.Count > 0)
{
dns4Sbox.rules.Insert(0, new()
{
server = tag,
domain = lstDomain
});
}
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
singboxConfig.dns = dns4Sbox;
return await Task.FromResult(0);
var localDnsServer = ParseDnsAddress(finalDnsAddress);
localDnsServer.tag = tag;
dns4Sbox.servers.Add(localDnsServer);
dns4Sbox.rules.Insert(0, BuildProtectDomainRule());
_coreConfig.dns = dns4Sbox;
}
private async Task<int> GenOutboundDnsRule(ProfileItem? node, SingboxConfig singboxConfig)
private void GenDnsProtectCustomLegacy()
{
if (node == null)
{
return 0;
}
GenDnsProtectCustom();
List<string> domain = new();
if (Utils.IsDomain(node.Address)) // normal outbound
_coreConfig.dns?.servers?.RemoveAll(s => s.tag == Global.SingboxLocalDNSTag);
var dnsItem = context.RawDnsItem;
var localDnsServer = new Server4Sbox()
{
domain.Add(node.Address);
}
if (node.Address == Global.Loopback && node.SpiderX.IsNotEmpty()) // Tun2SocksAddress
{
domain.AddRange(Utils.String2List(node.SpiderX)
.Where(Utils.IsDomain)
.Distinct()
.ToList());
}
if (domain.Count == 0)
{
return 0;
}
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress)
? Global.DomainPureIPDNSAddress.FirstOrDefault()
: dnsItem?.DomainDNSAddress,
tag = Global.SingboxLocalDNSTag,
detour = Global.DirectTag,
};
_coreConfig.dns?.servers?.Add(localDnsServer);
}
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
private Rule4Sbox BuildProtectDomainRule()
{
return new()
{
server = Global.SingboxLocalDNSTag,
domain = domain,
});
return await Task.FromResult(0);
domain = context.ProtectDomainList.ToList(),
};
}
private static Server4Sbox? ParseDnsAddress(string address)

View File

@@ -2,15 +2,16 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenInbounds(SingboxConfig singboxConfig)
private void GenInbounds()
{
try
{
var listen = "0.0.0.0";
singboxConfig.inbounds = [];
var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
_coreConfig.inbounds = [];
if (!_config.TunModeItem.EnableTun
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box))
if (!context.IsTunEnabled
|| (context.IsTunEnabled && _node.Port != listenPort))
{
var inbound = new Inbound4Sbox()
{
@@ -18,23 +19,23 @@ public partial class CoreConfigSingboxService
tag = EInboundProtocol.socks.ToString(),
listen = Global.Loopback,
};
singboxConfig.inbounds.Add(inbound);
_coreConfig.inbounds.Add(inbound);
inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
inbound.listen_port = listenPort;
if (_config.Inbound.First().SecondLocalPortEnabled)
{
var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true);
singboxConfig.inbounds.Add(inbound2);
var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true);
_coreConfig.inbounds.Add(inbound2);
}
if (_config.Inbound.First().AllowLANConn)
{
if (_config.Inbound.First().NewPort4LAN)
{
var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true);
var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true);
inbound3.listen = listen;
singboxConfig.inbounds.Add(inbound3);
_coreConfig.inbounds.Add(inbound3);
//auth
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
@@ -49,7 +50,7 @@ public partial class CoreConfigSingboxService
}
}
if (_config.TunModeItem.EnableTun)
if (context.IsTunEnabled)
{
if (_config.TunModeItem.Mtu <= 0)
{
@@ -71,17 +72,16 @@ public partial class CoreConfigSingboxService
tunInbound.address = ["172.18.0.1/30"];
}
singboxConfig.inbounds.Add(tunInbound);
_coreConfig.inbounds.Add(tunInbound);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
private Inbound4Sbox BuildInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
{
var inbound = JsonUtils.DeepCopy(inItem);
inbound.tag = protocol.ToString();

View File

@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenLog(SingboxConfig singboxConfig)
private void GenLog()
{
try
{
@@ -11,11 +11,11 @@ public partial class CoreConfigSingboxService
case "debug":
case "info":
case "error":
singboxConfig.log.level = _config.CoreBasicItem.Loglevel;
_coreConfig.log.level = _config.CoreBasicItem.Loglevel;
break;
case "warning":
singboxConfig.log.level = "warn";
_coreConfig.log.level = "warn";
break;
default:
@@ -23,18 +23,17 @@ public partial class CoreConfigSingboxService
}
if (_config.CoreBasicItem.Loglevel == Global.None)
{
singboxConfig.log.disabled = true;
_coreConfig.log.disabled = true;
}
if (_config.CoreBasicItem.LogEnabled)
{
var dtNow = DateTime.Now;
singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
_coreConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
}

View File

@@ -2,47 +2,47 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenRouting(SingboxConfig singboxConfig)
private void GenRouting()
{
try
{
singboxConfig.route.final = Global.ProxyTag;
var item = _config.SimpleDNSItem;
_coreConfig.route.final = Global.ProxyTag;
var simpleDnsItem = context.SimpleDnsItem;
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
var directDNSStrategy = item.SingboxStrategy4Direct.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : item.SingboxStrategy4Direct;
var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (rawDNSItem != null && rawDNSItem.Enabled == true)
var rawDNSItem = context.RawDnsItem;
if (rawDNSItem is { Enabled: true })
{
defaultDomainResolverTag = Global.SingboxLocalDNSTag;
directDNSStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : rawDNSItem.DomainStrategy4Freedom;
directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
}
singboxConfig.route.default_domain_resolver = new()
_coreConfig.route.default_domain_resolver = new()
{
server = defaultDomainResolverTag,
strategy = directDNSStrategy
strategy = directDnsStrategy
};
if (_config.TunModeItem.EnableTun)
{
singboxConfig.route.auto_detect_interface = true;
_coreConfig.route.auto_detect_interface = true;
var tunRules = JsonUtils.Deserialize<List<Rule4Sbox>>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName));
if (tunRules != null)
{
singboxConfig.route.rules.AddRange(tunRules);
_coreConfig.route.rules.AddRange(tunRules);
}
GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe);
singboxConfig.route.rules.Add(new()
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
_coreConfig.route.rules.Add(new()
{
port = new() { 53 },
port = [53],
action = "hijack-dns",
process_name = lstDnsExe
});
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
outbound = Global.DirectTag,
process_name = lstDirectExe
@@ -51,73 +51,62 @@ public partial class CoreConfigSingboxService
if (_config.Inbound.First().SniffingEnabled)
{
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
action = "sniff"
});
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
protocol = new() { "dns" },
protocol = ["dns"],
action = "hijack-dns"
});
}
else
{
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
port = new() { 53 },
network = new() { "udp" },
port = [53],
network = ["udp"],
action = "hijack-dns"
});
}
var hostsDomains = new List<string>();
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (dnsItem == null || dnsItem.Enabled == false)
if (rawDNSItem is not { Enabled: true })
{
var simpleDNSItem = _config.SimpleDNSItem;
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
{
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
foreach (var kvp in userHostsMap)
{
hostsDomains.Add(kvp.Key);
}
}
if (simpleDNSItem.UseSystemHosts == true)
var userHostsMap = Utils.ParseHostsToDictionary(simpleDnsItem.Hosts);
hostsDomains.AddRange(userHostsMap.Select(kvp => kvp.Key));
if (simpleDnsItem.UseSystemHosts == true)
{
var systemHostsMap = Utils.GetSystemHosts();
foreach (var kvp in systemHostsMap)
{
hostsDomains.Add(kvp.Key);
}
hostsDomains.AddRange(systemHostsMap.Select(kvp => kvp.Key));
}
}
if (hostsDomains.Count > 0)
{
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
action = "resolve",
domain = hostsDomains,
});
}
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
outbound = Global.DirectTag,
clash_mode = ERuleMode.Direct.ToString()
});
singboxConfig.route.rules.Add(new()
_coreConfig.route.rules.Add(new()
{
outbound = Global.ProxyTag,
clash_mode = ERuleMode.Global.ToString()
});
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty();
var routing = context.RoutingItem;
if (routing.DomainStrategy4Singbox.IsNotEmpty())
{
domainStrategy = defaultRouting.DomainStrategy4Singbox;
domainStrategy = routing.DomainStrategy4Singbox;
}
var resolveRule = new Rule4Sbox
{
@@ -126,10 +115,9 @@ public partial class CoreConfigSingboxService
};
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
{
singboxConfig.route.rules.Add(resolveRule);
_coreConfig.route.rules.Add(resolveRule);
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
var ipRules = new List<RulesItem>();
if (routing != null)
{
@@ -146,7 +134,7 @@ public partial class CoreConfigSingboxService
continue;
}
await GenRoutingUserRule(item1, singboxConfig);
GenRoutingUserRule(item1);
if (item1.Ip?.Count > 0)
{
@@ -156,10 +144,10 @@ public partial class CoreConfigSingboxService
}
if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch)
{
singboxConfig.route.rules.Add(resolveRule);
_coreConfig.route.rules.Add(resolveRule);
foreach (var item2 in ipRules)
{
await GenRoutingUserRule(item2, singboxConfig);
GenRoutingUserRule(item2);
}
}
}
@@ -167,10 +155,9 @@ public partial class CoreConfigSingboxService
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private void GenRoutingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe)
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
{
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
@@ -194,20 +181,22 @@ public partial class CoreConfigSingboxService
}
}
lstDnsExe = new List<string>(dnsExeSet);
lstDirectExe = new List<string>(directExeSet);
var lstDnsExe = new List<string>(dnsExeSet);
var lstDirectExe = new List<string>(directExeSet);
return (lstDnsExe, lstDirectExe);
}
private async Task<int> GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig)
private void GenRoutingUserRule(RulesItem? item)
{
try
{
if (item == null)
{
return 0;
return;
}
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
var rules = singboxConfig.route.rules;
item.OutboundTag = GenRoutingUserRuleOutbound(item.OutboundTag ?? Global.ProxyTag);
var rules = _coreConfig.route.rules;
var rule = new Rule4Sbox();
if (item.OutboundTag == "block")
@@ -278,11 +267,49 @@ public partial class CoreConfigSingboxService
}
}
if (_config.TunModeItem.EnableTun && item.Process?.Count > 0)
if (item.Process?.Count > 0)
{
rule3.process_name = item.Process;
rules.Add(rule3);
hasDomainIp = true;
var ruleProcName = JsonUtils.DeepCopy(rule3);
ruleProcName.process_name ??= [];
var ruleProcPath = JsonUtils.DeepCopy(rule3);
ruleProcPath.process_path ??= [];
foreach (var process in item.Process)
{
// sing-box doesn't support this, fall back to process name match
if (process is "self/" or "xray/")
{
ruleProcName.process_name.Add(Utils.GetExeName("sing-box"));
continue;
}
if (process.Contains('/') || process.Contains('\\'))
{
var procPath = process;
if (Utils.IsWindows())
{
procPath = procPath.Replace('/', '\\');
}
ruleProcPath.process_path.Add(procPath);
continue;
}
// sing-box strictly matches the exe suffix on Windows
var procName = Utils.GetExeName(process);
ruleProcName.process_name.Add(procName);
}
if (ruleProcName.process_name.Count > 0)
{
rules.Add(ruleProcName);
hasDomainIp = true;
}
if (ruleProcPath.process_path.Count > 0)
{
rules.Add(ruleProcPath);
hasDomainIp = true;
}
}
if (!hasDomainIp
@@ -295,12 +322,11 @@ public partial class CoreConfigSingboxService
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private bool ParseV2Domain(string domain, Rule4Sbox rule)
private static bool ParseV2Domain(string domain, Rule4Sbox rule)
{
if (domain.StartsWith("#") || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
if (domain.StartsWith('#') || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
{
return false;
}
@@ -337,7 +363,7 @@ public partial class CoreConfigSingboxService
return true;
}
private bool ParseV2Address(string address, Rule4Sbox rule)
private static bool ParseV2Address(string address, Rule4Sbox rule)
{
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
{
@@ -370,14 +396,14 @@ public partial class CoreConfigSingboxService
return true;
}
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig)
private string GenRoutingUserRuleOutbound(string outboundTag)
{
if (Global.OutboundTags.Contains(outboundTag))
{
return outboundTag;
}
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
if (node == null
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
@@ -387,39 +413,15 @@ public partial class CoreConfigSingboxService
}
var tag = $"{node.IndexId}-{Global.ProxyTag}";
if (singboxConfig.outbounds.Any(o => o.tag == tag)
|| (singboxConfig.endpoints != null && singboxConfig.endpoints.Any(e => e.tag == tag)))
if (_coreConfig.outbounds.Any(o => o.tag.StartsWith(tag))
|| (_coreConfig.endpoints != null && _coreConfig.endpoints.Any(e => e.tag.StartsWith(tag))))
{
return tag;
}
if (node.ConfigType.IsGroupType())
{
var ret = await GenGroupOutbound(node, singboxConfig, tag);
if (ret == 0)
{
return tag;
}
return Global.ProxyTag;
}
var proxyOutbounds = new CoreConfigSingboxService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
FillRangeProxy(proxyOutbounds, _coreConfig, false);
var server = await GenServer(node);
if (server is null)
{
return Global.ProxyTag;
}
server.tag = tag;
if (server is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (server is Outbound4Sbox outbound)
{
singboxConfig.outbounds.Add(outbound);
}
return server.tag;
return tag;
}
}

View File

@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> ConvertGeo2Ruleset(SingboxConfig singboxConfig)
private void ConvertGeo2Ruleset()
{
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
{
@@ -16,14 +16,14 @@ public partial class CoreConfigSingboxService
var ruleSets = new List<string>();
//convert route geosite & geoip to ruleset
foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
foreach (var rule in _coreConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
{
rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
rule.geosite = null;
AddRuleSets(ruleSets, rule.rule_set);
}
foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
foreach (var rule in _coreConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
{
rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
@@ -32,24 +32,24 @@ public partial class CoreConfigSingboxService
}
//convert dns geosite & geoip to ruleset
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
{
rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
rule.geosite = null;
}
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
{
rule.rule_set ??= new List<string>();
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
rule.geoip = null;
}
foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
foreach (var dnsRule in _coreConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
{
AddRuleSets(ruleSets, dnsRule.rule_set);
}
//rules in rules
foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? [])
foreach (var item in _coreConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? [])
{
foreach (var item2 in item ?? [])
{
@@ -60,7 +60,7 @@ public partial class CoreConfigSingboxService
//load custom ruleset file
List<Ruleset4Sbox> customRulesets = [];
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
if (routing.CustomRulesetPath4Singbox.IsNotEmpty())
{
var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox);
@@ -78,7 +78,7 @@ public partial class CoreConfigSingboxService
var localSrss = Utils.GetBinPath("srss");
//Add ruleset srs
singboxConfig.route.rule_set = [];
_coreConfig.route.rule_set = [];
foreach (var item in new HashSet<string>(ruleSets))
{
if (item.IsNullOrEmpty())
@@ -113,9 +113,7 @@ public partial class CoreConfigSingboxService
};
}
}
singboxConfig.route.rule_set.Add(customRuleset);
_coreConfig.route.rule_set.Add(customRuleset);
}
return 0;
}
}

View File

@@ -2,12 +2,12 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenExperimental(SingboxConfig singboxConfig)
private void GenExperimental()
{
//if (_config.guiItem.enableStatistics)
{
singboxConfig.experimental ??= new Experimental4Sbox();
singboxConfig.experimental.clash_api = new Clash_Api4Sbox()
_coreConfig.experimental ??= new Experimental4Sbox();
_coreConfig.experimental.clash_api = new Clash_Api4Sbox()
{
external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}",
};
@@ -15,15 +15,13 @@ public partial class CoreConfigSingboxService
if (_config.CoreBasicItem.EnableCacheFile4Sbox)
{
singboxConfig.experimental ??= new Experimental4Sbox();
singboxConfig.experimental.cache_file = new CacheFile4Sbox()
_coreConfig.experimental ??= new Experimental4Sbox();
_coreConfig.experimental.cache_file = new CacheFile4Sbox()
{
enabled = true,
path = Utils.GetBinPath("cache.db"),
store_fakeip = _config.SimpleDNSItem.FakeIP == true
store_fakeip = context.SimpleDnsItem.FakeIP == true
};
}
return await Task.FromResult(0);
}
}

View File

@@ -1,44 +1,39 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService(Config config)
public partial class CoreConfigV2rayService(CoreConfigContext context)
{
private readonly Config _config = config;
private static readonly string _tag = "CoreConfigV2rayService";
private readonly Config _config = context.AppConfig;
private readonly ProfileItem _node = context.Node;
private V2rayConfig _coreConfig = new();
#region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
public RetResult GenerateClientConfigContent()
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
if (context.IsTunEnabled && context.TunProtectSsPort > 0 && context.ProxyRelaySsPort > 0)
{
return GenerateClientProxyRelayConfig();
}
if (_node == null
|| !_node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.quic))
if (_node.GetNetwork() is nameof(ETransport.quic))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
if (node.ConfigType.IsGroupType())
{
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(node);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(node);
}
}
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
if (result.IsNullOrEmpty())
{
@@ -46,30 +41,28 @@ public partial class CoreConfigV2rayService(Config config)
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(v2rayConfig);
GenLog();
await GenInbounds(v2rayConfig);
GenInbounds();
await GenOutbound(node, v2rayConfig.outbounds.First());
GenOutbounds();
await GenMoreOutbounds(node, v2rayConfig);
GenRouting();
await GenRouting(v2rayConfig);
GenDns();
await GenDns(node, v2rayConfig);
await GenStatistic(v2rayConfig);
GenStatistic();
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
ret.Data = ApplyFullConfigTemplate();
return ret;
}
catch (Exception ex)
@@ -80,18 +73,11 @@ public partial class CoreConfigV2rayService(Config config)
}
}
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode)
public RetResult GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
@@ -102,193 +88,19 @@ public partial class CoreConfigV2rayService(Config config)
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
v2rayConfig.outbounds.RemoveAt(0);
await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig);
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
if (groupRet != 0)
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(v2rayConfig);
await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig);
var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo();
var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}";
//add rule
var rules = v2rayConfig.routing.rules;
if (rules?.Count > 0 && ((v2rayConfig.routing.balancers?.Count ?? 0) > 0))
{
var balancerTagSet = v2rayConfig.routing.balancers
.Select(b => b.tag)
.ToHashSet();
foreach (var rule in rules)
{
if (rule.outboundTag == null)
{
continue;
}
if (balancerTagSet.Contains(rule.outboundTag))
{
rule.balancerTag = rule.outboundTag;
rule.outboundTag = null;
continue;
}
var outboundWithSuffix = rule.outboundTag + Global.BalancerTagSuffix;
if (balancerTagSet.Contains(outboundWithSuffix))
{
rule.balancerTag = outboundWithSuffix;
rule.outboundTag = null;
}
}
}
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
v2rayConfig.routing.rules.Add(new()
{
ip = ["0.0.0.0/0", "::/0"],
balancerTag = defaultBalancerTag,
type = "field"
});
}
else
{
v2rayConfig.routing.rules.Add(new()
{
network = "tcp,udp",
balancerTag = defaultBalancerTag,
type = "field"
});
}
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
v2rayConfig.outbounds.RemoveAt(0);
await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig);
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenRouting(v2rayConfig);
await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig);
ret.Success = true;
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();
try
{
if (_config == null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
List<IPEndPoint> lstIpEndPoints = new();
List<TcpConnectionInformation> lstTcpConns = new();
try
{
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
await GenLog(v2rayConfig);
v2rayConfig.inbounds.Clear();
v2rayConfig.outbounds.Clear();
v2rayConfig.routing.rules.Clear();
GenLog();
_coreConfig.inbounds.Clear();
_coreConfig.outbounds.Clear();
_coreConfig.routing.rules.Clear();
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
@@ -302,8 +114,9 @@ public partial class CoreConfigV2rayService(Config config)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null || item.IsComplex() || !item.IsValid())
var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId);
var item = context.AllProxiesMap.GetValueOrDefault(actIndexId);
if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid())
{
continue;
}
@@ -342,27 +155,40 @@ public partial class CoreConfigV2rayService(Config config)
protocol = EInboundProtocol.mixed.ToString(),
};
inbound.tag = inbound.protocol + inbound.port.ToString();
v2rayConfig.inbounds.Add(inbound);
_coreConfig.inbounds.Add(inbound);
var tag = Global.ProxyTag + inbound.port.ToString();
var isBalancer = false;
//outbound
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(item, outbound);
outbound.tag = Global.ProxyTag + inbound.port.ToString();
v2rayConfig.outbounds.Add(outbound);
var proxyOutbounds =
new CoreConfigV2rayService(context with { Node = item }).BuildAllProxyOutbounds(tag);
_coreConfig.outbounds.AddRange(proxyOutbounds);
if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1)
{
isBalancer = true;
var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
GenObservatory(multipleLoad, tag);
GenBalancer(multipleLoad, tag);
}
//rule
RulesItem4Ray rule = new()
{
inboundTag = new List<string> { inbound.tag },
outboundTag = outbound.tag,
outboundTag = tag,
type = "field"
};
v2rayConfig.routing.rules.Add(rule);
if (isBalancer)
{
rule.balancerTag = tag;
rule.outboundTag = null;
}
_coreConfig.routing.rules.Add(rule);
}
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
ret.Success = true;
ret.Data = JsonUtils.Serialize(v2rayConfig);
ret.Data = JsonUtils.Serialize(_coreConfig);
return ret;
}
catch (Exception ex)
@@ -373,21 +199,21 @@ public partial class CoreConfigV2rayService(Config config)
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
public RetResult GenerateClientSpeedtestConfig(int port)
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
if (_node == null
|| !_node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.quic))
if (_node.GetNetwork() is nameof(ETransport.quic))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret;
}
@@ -398,20 +224,19 @@ public partial class CoreConfigV2rayService(Config config)
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(v2rayConfig);
await GenOutbound(node, v2rayConfig.outbounds.First());
await GenMoreOutbounds(node, v2rayConfig);
GenLog();
GenOutbounds();
v2rayConfig.routing.rules.Clear();
v2rayConfig.inbounds.Clear();
v2rayConfig.inbounds.Add(new()
_coreConfig.routing.rules.Clear();
_coreConfig.inbounds.Clear();
_coreConfig.inbounds.Add(new()
{
tag = $"{EInboundProtocol.socks}{port}",
listen = Global.Loopback,
@@ -421,7 +246,108 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = JsonUtils.Serialize(v2rayConfig);
ret.Data = JsonUtils.Serialize(_coreConfig);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public RetResult GenerateClientProxyRelayConfig()
{
var ret = new RetResult();
try
{
if (_node == null
|| !_node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (_node.GetNetwork() is nameof(ETransport.quic))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}";
return ret;
}
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
if (result.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
_coreConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (_coreConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
GenLog();
_coreConfig.outbounds.Clear();
GenOutbounds();
var protectNode = new ProfileItem()
{
CoreType = ECoreType.Xray,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = context.TunProtectSsPort,
Password = Global.None,
};
protectNode.SetProtocolExtra(protectNode.GetProtocolExtra() with
{
SsMethod = Global.None,
});
foreach (var outbound in _coreConfig.outbounds.Where(outbound => outbound.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
{
outbound.streamSettings ??= new StreamSettings4Ray();
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
outbound.streamSettings.sockopt.dialerProxy = "tun-project-ss";
}
_coreConfig.outbounds.Add(new CoreConfigV2rayService(context with
{
Node = protectNode,
}).BuildProxyOutbound("tun-project-ss"));
var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 };
_coreConfig.routing.rules =
[
new()
{
inboundTag = new List<string> { "proxy-relay-ss" },
outboundTag = hasBalancer ? null : Global.ProxyTag,
balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix: null,
type = "field"
}
];
_coreConfig.inbounds.Clear();
var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!;
configNode["inbounds"]!.AsArray().Add(new
{
listen = Global.Loopback,
port = context.ProxyRelaySsPort,
protocol = "shadowsocks",
settings = new
{
network = "tcp,udp",
method = Global.None,
password = Global.None,
},
tag = "proxy-relay-ss",
});
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
ret.Data = JsonUtils.Serialize(configNode);
return ret;
}
catch (Exception ex)

View File

@@ -2,17 +2,17 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
private void GenObservatory(EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
{
// Collect all existing subject selectors from both observatories
var subjectSelectors = new List<string>();
subjectSelectors.AddRange(v2rayConfig.burstObservatory?.subjectSelector ?? []);
subjectSelectors.AddRange(v2rayConfig.observatory?.subjectSelector ?? []);
subjectSelectors.AddRange(_coreConfig.burstObservatory?.subjectSelector ?? []);
subjectSelectors.AddRange(_coreConfig.observatory?.subjectSelector ?? []);
// Case 1: exact match already exists -> nothing to do
if (subjectSelectors.Any(baseTagName.StartsWith))
{
return await Task.FromResult(0);
return;
}
// Case 2: prefix match exists -> reuse it and move to the first position
@@ -21,28 +21,28 @@ public partial class CoreConfigV2rayService
{
baseTagName = matched;
if (v2rayConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true)
if (_coreConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true)
{
v2rayConfig.burstObservatory.subjectSelector.Remove(baseTagName);
v2rayConfig.burstObservatory.subjectSelector.Insert(0, baseTagName);
_coreConfig.burstObservatory.subjectSelector.Remove(baseTagName);
_coreConfig.burstObservatory.subjectSelector.Insert(0, baseTagName);
}
if (v2rayConfig.observatory?.subjectSelector?.Contains(baseTagName) == true)
if (_coreConfig.observatory?.subjectSelector?.Contains(baseTagName) == true)
{
v2rayConfig.observatory.subjectSelector.Remove(baseTagName);
v2rayConfig.observatory.subjectSelector.Insert(0, baseTagName);
_coreConfig.observatory.subjectSelector.Remove(baseTagName);
_coreConfig.observatory.subjectSelector.Insert(0, baseTagName);
}
return await Task.FromResult(0);
return;
}
// Case 3: need to create or insert based on multipleLoad type
if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback)
{
if (v2rayConfig.burstObservatory is null)
if (_coreConfig.burstObservatory is null)
{
// Create new burst observatory with default ping config
v2rayConfig.burstObservatory = new BurstObservatory4Ray
_coreConfig.burstObservatory = new BurstObservatory4Ray
{
subjectSelector = [baseTagName],
pingConfig = new()
@@ -56,16 +56,16 @@ public partial class CoreConfigV2rayService
}
else
{
v2rayConfig.burstObservatory.subjectSelector ??= new();
v2rayConfig.burstObservatory.subjectSelector.Add(baseTagName);
_coreConfig.burstObservatory.subjectSelector ??= new();
_coreConfig.burstObservatory.subjectSelector.Add(baseTagName);
}
}
else if (multipleLoad is EMultipleLoad.LeastPing)
{
if (v2rayConfig.observatory is null)
if (_coreConfig.observatory is null)
{
// Create new observatory with default probe config
v2rayConfig.observatory = new Observatory4Ray
_coreConfig.observatory = new Observatory4Ray
{
subjectSelector = [baseTagName],
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
@@ -75,15 +75,13 @@ public partial class CoreConfigV2rayService
}
else
{
v2rayConfig.observatory.subjectSelector ??= new();
v2rayConfig.observatory.subjectSelector.Add(baseTagName);
_coreConfig.observatory.subjectSelector ??= new();
_coreConfig.observatory.subjectSelector.Add(baseTagName);
}
}
return await Task.FromResult(0);
}
private async Task<string> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
private void GenBalancer(EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
{
var strategyType = multipleLoad switch
{
@@ -107,8 +105,7 @@ public partial class CoreConfigV2rayService
},
tag = balancerTag,
};
v2rayConfig.routing.balancers ??= new();
v2rayConfig.routing.balancers.Add(balancer);
return await Task.FromResult(balancerTag);
_coreConfig.routing.balancers ??= new();
_coreConfig.routing.balancers.Add(balancer);
}
}

View File

@@ -2,35 +2,39 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig)
private string ApplyFullConfigTemplate()
{
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
var fullConfigTemplate = context.FullConfigTemplate;
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
{
return JsonUtils.Serialize(v2rayConfig);
return JsonUtils.Serialize(_coreConfig);
}
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config);
if (fullConfigTemplateNode == null)
{
return JsonUtils.Serialize(v2rayConfig);
return JsonUtils.Serialize(_coreConfig);
}
// Handle balancer and rules modifications (for multiple load scenarios)
if (v2rayConfig.routing?.balancers?.Count > 0)
if (_coreConfig.routing?.balancers?.Count > 0)
{
var balancer = v2rayConfig.routing.balancers.First();
var balancer =
_coreConfig.routing.balancers.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null);
// Modify existing rules in custom config
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
if (rulesNode != null)
if (balancer != null)
{
foreach (var rule in rulesNode.AsArray())
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
if (rulesNode != null)
{
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
foreach (var rule in rulesNode.AsArray())
{
rule.AsObject().Remove("outboundTag");
rule["balancerTag"] = balancer.tag;
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
{
rule.AsObject().Remove("outboundTag");
rule["balancerTag"] = balancer.tag;
}
}
}
}
@@ -44,7 +48,7 @@ public partial class CoreConfigV2rayService
// Handle balancers - append instead of override
if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode)
{
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers)
if (JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)) is JsonArray newBalancers)
{
foreach (var balancerNode in newBalancers)
{
@@ -54,33 +58,33 @@ public partial class CoreConfigV2rayService
}
else
{
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers));
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers));
}
}
if (v2rayConfig.observatory != null)
if (_coreConfig.observatory != null)
{
if (fullConfigTemplateNode["observatory"] == null)
{
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.observatory));
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.observatory));
}
else
{
var subjectSelector = v2rayConfig.observatory.subjectSelector;
var subjectSelector = _coreConfig.observatory.subjectSelector;
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
}
}
if (v2rayConfig.burstObservatory != null)
if (_coreConfig.burstObservatory != null)
{
if (fullConfigTemplateNode["burstObservatory"] == null)
{
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.burstObservatory));
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.burstObservatory));
}
else
{
var subjectSelector = v2rayConfig.burstObservatory.subjectSelector;
var subjectSelector = _coreConfig.burstObservatory.subjectSelector;
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
}
@@ -88,7 +92,7 @@ public partial class CoreConfigV2rayService
var customOutboundsNode = new JsonArray();
foreach (var outbound in v2rayConfig.outbounds)
foreach (var outbound in _coreConfig.outbounds)
{
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
{
@@ -97,8 +101,8 @@ public partial class CoreConfigV2rayService
continue;
}
}
else if ((!fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
&& ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true) == true))
else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()
&& (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true))
{
var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address
?? outbound.settings?.vnext?.FirstOrDefault()?.address
@@ -123,6 +127,6 @@ public partial class CoreConfigV2rayService
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
return JsonUtils.Serialize(fullConfigTemplateNode);
}
}

View File

@@ -2,70 +2,97 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenDns(ProfileItem? node, V2rayConfig v2rayConfig)
private void GenDns()
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
if (item != null && item.Enabled == true)
var item = context.RawDnsItem;
if (item is { Enabled: true })
{
var result = await GenDnsCompatible(node, v2rayConfig);
GenDnsCustom();
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
if (_coreConfig.routing.domainStrategy != Global.IPIfNonMatch)
{
// DNS routing
v2rayConfig.dns.tag = Global.DnsTag;
v2rayConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
outboundTag = Global.ProxyTag,
});
return;
}
return result;
}
var simpleDNSItem = _config.SimpleDNSItem;
var domainStrategy4Freedom = simpleDNSItem?.RayStrategy4Freedom;
// DNS routing
var dnsObj = JsonUtils.SerializeToNode(_coreConfig.dns);
if (dnsObj == null)
{
return;
}
dnsObj["tag"] = Global.DnsTag;
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(dnsObj));
_coreConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
outboundTag = Global.ProxyTag,
});
return;
}
var simpleDnsItem = context.SimpleDnsItem;
var dnsItem = _coreConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray();
var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs;
//Outbound Freedom domainStrategy
if (domainStrategy4Freedom.IsNotEmpty())
if (strategy4Freedom.IsNotEmpty() && strategy4Freedom != Global.AsIs)
{
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null)
{
outbound.settings = new()
{
domainStrategy = domainStrategy4Freedom,
domainStrategy = strategy4Freedom,
userLevel = 0
};
}
}
await GenDnsServers(node, v2rayConfig, simpleDNSItem);
await GenDnsHosts(v2rayConfig, simpleDNSItem);
var strategy4Proxy = simpleDnsItem?.Strategy4Proxy ?? Global.AsIs;
//Outbound Proxy domainStrategy
if (strategy4Proxy.IsNotEmpty() && strategy4Proxy != 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 => outbound.targetStrategy = strategy4Proxy);
}
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
FillDnsServers(dnsItem);
FillDnsHosts(dnsItem);
dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null;
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
if (_coreConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
// DNS routing
v2rayConfig.dns.tag = Global.DnsTag;
v2rayConfig.routing.rules.Add(new RulesItem4Ray
dnsItem.tag = Global.DnsTag;
_coreConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
outboundTag = Global.ProxyTag,
});
}
_coreConfig.dns = dnsItem;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
private void FillDnsServers(Dns4Ray dnsItem)
{
var simpleDNSItem = context.SimpleDnsItem;
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
{
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
@@ -77,7 +104,7 @@ public partial class CoreConfigV2rayService
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
}
static object CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
static object? CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
{
var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress);
var domainFinal = dnsAddress;
@@ -106,8 +133,8 @@ public partial class CoreConfigV2rayService
});
}
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.FirstOrDefault());
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.FirstOrDefault());
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.First());
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.First());
var directDomainList = new List<string>();
var directGeositeList = new List<string>();
@@ -117,7 +144,7 @@ public partial class CoreConfigV2rayService
var expectedIPs = new List<string>();
var regionNames = new HashSet<string>();
var bootstrapDNSAddress = ParseDnsAddresses(simpleDNSItem?.BootstrapDNS, Global.DomainPureIPDNSAddress.FirstOrDefault());
var bootstrapDNSAddress = ParseDnsAddresses(simpleDNSItem?.BootstrapDNS, Global.DomainPureIPDNSAddress.First());
var dnsServerDomains = new List<string>();
foreach (var dns in directDNSAddress)
@@ -169,83 +196,61 @@ public partial class CoreConfigV2rayService
}
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
List<RulesItem>? rules = null;
if (routing != null)
rules = JsonUtils.Deserialize<List<RulesItem>>(routing?.RuleSet) ?? [];
foreach (var item in rules)
{
rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
foreach (var item in rules)
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
{
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
continue;
}
if (item.RuleType == ERuleType.Routing)
{
continue;
}
foreach (var domain in item.Domain)
{
if (domain.StartsWith('#'))
{
continue;
}
if (item.RuleType == ERuleType.Routing)
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
if (item.OutboundTag == Global.DirectTag)
{
continue;
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
{
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
}
else
{
directDomainList.Add(normalizedDomain);
}
}
foreach (var domain in item.Domain)
else if (item.OutboundTag != Global.BlockTag)
{
if (domain.StartsWith('#'))
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
{
continue;
proxyGeositeList.Add(normalizedDomain);
}
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
if (item.OutboundTag == Global.DirectTag)
else
{
if (normalizedDomain.StartsWith("geosite:"))
{
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
}
else
{
directDomainList.Add(normalizedDomain);
}
}
else if (item.OutboundTag != Global.BlockTag)
{
if (normalizedDomain.StartsWith("geosite:"))
{
proxyGeositeList.Add(normalizedDomain);
}
else
{
proxyDomainList.Add(normalizedDomain);
}
proxyDomainList.Add(normalizedDomain);
}
}
}
}
if (Utils.IsDomain(node?.Address))
if (context.ProtectDomainList.Count > 0)
{
directDomainList.Add(node.Address);
directDomainList.AddRange(context.ProtectDomainList);
}
if (node?.Subid is not null)
{
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem is not null)
{
foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile })
{
var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile);
if (profileNode is not null
&& Global.XraySupportConfigType.Contains(profileNode.ConfigType)
&& Utils.IsDomain(profileNode.Address))
{
directDomainList.Add(profileNode.Address);
}
}
}
}
v2rayConfig.dns ??= new Dns4Ray();
v2rayConfig.dns.servers ??= new List<object>();
dnsItem.servers ??= [];
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
{
@@ -253,7 +258,7 @@ public partial class CoreConfigV2rayService
{
foreach (var dnsAddress in dnsAddresses)
{
v2rayConfig.dns.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
}
}
}
@@ -275,22 +280,20 @@ public partial class CoreConfigV2rayService
|| lastRule.Ip?.Contains("0.0.0.0/0") == true);
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
v2rayConfig.dns.servers.AddRange(defaultDnsServers);
return 0;
dnsItem.servers.AddRange(defaultDnsServers);
}
private async Task<int> GenDnsHosts(V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
private void FillDnsHosts(Dns4Ray dnsItem)
{
var simpleDNSItem = context.SimpleDnsItem;
if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty())
{
return await Task.FromResult(0);
return;
}
v2rayConfig.dns ??= new Dns4Ray();
v2rayConfig.dns.hosts ??= new Dictionary<string, object>();
dnsItem.hosts ??= new Dictionary<string, object>();
if (simpleDNSItem.AddCommonHosts == true)
{
v2rayConfig.dns.hosts = Global.PredefinedHosts.ToDictionary(
dnsItem.hosts = Global.PredefinedHosts.ToDictionary(
kvp => kvp.Key,
kvp => (object)kvp.Value
);
@@ -299,7 +302,7 @@ public partial class CoreConfigV2rayService
if (simpleDNSItem.UseSystemHosts == true)
{
var systemHosts = Utils.GetSystemHosts();
var normalHost = v2rayConfig?.dns?.hosts;
var normalHost = dnsItem.hosts;
if (normalHost != null && systemHosts?.Count > 0)
{
@@ -310,23 +313,17 @@ public partial class CoreConfigV2rayService
}
}
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts))
{
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
foreach (var kvp in userHostsMap)
{
v2rayConfig.dns.hosts[kvp.Key] = kvp.Value;
}
dnsItem.hosts[kvp.Key] = kvp.Value;
}
return await Task.FromResult(0);
}
private async Task<int> GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig)
private void GenDnsCustom()
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
var item = context.RawDnsItem;
var normalDNS = item?.NormalDNS;
var domainStrategy4Freedom = item?.DomainStrategy4Freedom;
if (normalDNS.IsNullOrEmpty())
@@ -337,7 +334,7 @@ public partial class CoreConfigV2rayService
//Outbound Freedom domainStrategy
if (domainStrategy4Freedom.IsNotEmpty())
{
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null)
{
outbound.settings = new();
@@ -392,63 +389,37 @@ public partial class CoreConfigV2rayService
}
}
await GenDnsDomainsCompatible(node, obj, item);
FillDnsDomainsCustom(obj);
v2rayConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dnsItem)
private void FillDnsDomainsCustom(JsonNode dns)
{
if (node == null)
{
return 0;
}
var servers = dns["servers"];
if (servers != null)
if (servers == null)
{
var domainList = new List<string>();
if (Utils.IsDomain(node.Address))
{
domainList.Add(node.Address);
}
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
if (subItem is not null)
{
// Previous proxy
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode is not null
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)
&& Utils.IsDomain(prevNode.Address))
{
domainList.Add(prevNode.Address);
}
// Next proxy
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)
&& Utils.IsDomain(nextNode.Address))
{
domainList.Add(nextNode.Address);
}
}
if (domainList.Count > 0)
{
var dnsServer = new DnsServer4Ray()
{
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
skipFallback = true,
domains = domainList
};
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
}
return;
}
return await Task.FromResult(0);
var domainList = context.ProtectDomainList;
if (domainList.Count <= 0)
{
return;
}
var dnsItem = context.RawDnsItem;
var dnsServer = new DnsServer4Ray()
{
address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress,
skipFallback = true,
domains = domainList.ToList(),
};
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
}
}

View File

@@ -2,35 +2,35 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenInbounds(V2rayConfig v2rayConfig)
private void GenInbounds()
{
try
{
var listen = "0.0.0.0";
v2rayConfig.inbounds = [];
_coreConfig.inbounds = [];
var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
v2rayConfig.inbounds.Add(inbound);
var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
_coreConfig.inbounds.Add(inbound);
if (_config.Inbound.First().SecondLocalPortEnabled)
{
var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
v2rayConfig.inbounds.Add(inbound2);
var inbound2 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
_coreConfig.inbounds.Add(inbound2);
}
if (_config.Inbound.First().AllowLANConn)
{
if (_config.Inbound.First().NewPort4LAN)
{
var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
var inbound3 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
inbound3.listen = listen;
v2rayConfig.inbounds.Add(inbound3);
_coreConfig.inbounds.Add(inbound3);
//auth
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
{
inbound3.settings.auth = "password";
inbound3.settings.accounts = new List<AccountsItem4Ray> { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } };
inbound3.settings.accounts = new List<AccountsItem4Ray> { new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } };
}
}
else
@@ -43,10 +43,9 @@ public partial class CoreConfigV2rayService
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
private Inbounds4Ray BuildInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
{
var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
if (result.IsNullOrEmpty())

View File

@@ -2,28 +2,27 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenLog(V2rayConfig v2rayConfig)
private void GenLog()
{
try
{
if (_config.CoreBasicItem.LogEnabled)
{
var dtNow = DateTime.Now;
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
_coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
_coreConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
_coreConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
}
else
{
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
v2rayConfig.log.access = null;
v2rayConfig.log.error = null;
_coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
_coreConfig.log.access = null;
_coreConfig.log.error = null;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
}

View File

@@ -2,20 +2,20 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenRouting(V2rayConfig v2rayConfig)
private void GenRouting()
{
try
{
if (v2rayConfig.routing?.rules != null)
if (_coreConfig.routing?.rules != null)
{
v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
_coreConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
var routing = await ConfigHandler.GetDefaultRouting(_config);
var routing = context.RoutingItem;
if (routing != null)
{
if (routing.DomainStrategy.IsNotEmpty())
{
v2rayConfig.routing.domainStrategy = routing.DomainStrategy;
_coreConfig.routing.domainStrategy = routing.DomainStrategy;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item in rules)
@@ -31,7 +31,18 @@ public partial class CoreConfigV2rayService
}
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
await GenRoutingUserRule(item2, v2rayConfig);
GenRoutingUserRule(item2);
}
}
var balancerTagList = _coreConfig.routing.balancers
?.Select(p => p.tag)
.ToList() ?? [];
if (balancerTagList.Count > 0)
{
foreach (var rulesItem in _coreConfig.routing.rules.Where(r => balancerTagList.Contains(r.outboundTag + Global.BalancerTagSuffix)))
{
rulesItem.balancerTag = rulesItem.outboundTag + Global.BalancerTagSuffix;
rulesItem.outboundTag = null;
}
}
}
@@ -40,80 +51,94 @@ public partial class CoreConfigV2rayService
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig)
private void GenRoutingUserRule(RulesItem4Ray? userRule)
{
try
{
if (rule == null)
if (userRule == null)
{
return 0;
return;
}
rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig);
userRule.outboundTag = GenRoutingUserRuleOutbound(userRule.outboundTag ?? Global.ProxyTag);
if (rule.port.IsNullOrEmpty())
if (userRule.port.IsNullOrEmpty())
{
rule.port = null;
userRule.port = null;
}
if (rule.network.IsNullOrEmpty())
if (userRule.network.IsNullOrEmpty())
{
rule.network = null;
userRule.network = null;
}
if (rule.domain?.Count == 0)
if (userRule.domain?.Count == 0)
{
rule.domain = null;
userRule.domain = null;
}
if (rule.ip?.Count == 0)
if (userRule.ip?.Count == 0)
{
rule.ip = null;
userRule.ip = null;
}
if (rule.protocol?.Count == 0)
if (userRule.protocol?.Count == 0)
{
rule.protocol = null;
userRule.protocol = null;
}
if (rule.inboundTag?.Count == 0)
if (userRule.inboundTag?.Count == 0)
{
rule.inboundTag = null;
userRule.inboundTag = null;
}
if (userRule.process?.Count == 0)
{
userRule.process = null;
}
var hasDomainIp = false;
if (rule.domain?.Count > 0)
if (userRule.domain?.Count > 0)
{
var it = JsonUtils.DeepCopy(rule);
var it = JsonUtils.DeepCopy(userRule);
it.ip = null;
it.process = null;
it.type = "field";
for (var k = it.domain.Count - 1; k >= 0; k--)
{
if (it.domain[k].StartsWith("#"))
if (it.domain[k].StartsWith('#'))
{
it.domain.RemoveAt(k);
}
it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ",");
}
v2rayConfig.routing.rules.Add(it);
_coreConfig.routing.rules.Add(it);
hasDomainIp = true;
}
if (rule.ip?.Count > 0)
if (userRule.ip?.Count > 0)
{
var it = JsonUtils.DeepCopy(rule);
var it = JsonUtils.DeepCopy(userRule);
it.domain = null;
it.process = null;
it.type = "field";
v2rayConfig.routing.rules.Add(it);
_coreConfig.routing.rules.Add(it);
hasDomainIp = true;
}
if (userRule.process?.Count > 0)
{
var it = JsonUtils.DeepCopy(userRule);
it.domain = null;
it.ip = null;
it.type = "field";
_coreConfig.routing.rules.Add(it);
hasDomainIp = true;
}
if (!hasDomainIp)
{
if (rule.port.IsNotEmpty()
|| rule.protocol?.Count > 0
|| rule.inboundTag?.Count > 0
|| rule.network != null
if (userRule.port.IsNotEmpty()
|| userRule.protocol?.Count > 0
|| userRule.inboundTag?.Count > 0
|| userRule.network != null
)
{
var it = JsonUtils.DeepCopy(rule);
var it = JsonUtils.DeepCopy(userRule);
it.type = "field";
v2rayConfig.routing.rules.Add(it);
_coreConfig.routing.rules.Add(it);
}
}
}
@@ -121,17 +146,16 @@ public partial class CoreConfigV2rayService
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig)
private string GenRoutingUserRuleOutbound(string outboundTag)
{
if (Global.OutboundTags.Contains(outboundTag))
{
return outboundTag;
}
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}");
if (node == null
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
@@ -141,27 +165,20 @@ public partial class CoreConfigV2rayService
}
var tag = $"{node.IndexId}-{Global.ProxyTag}";
if (v2rayConfig.outbounds.Any(p => p.tag == tag))
if (_coreConfig.outbounds.Any(p => p.tag.StartsWith(tag)))
{
return tag;
}
if (node.ConfigType.IsGroupType())
var proxyOutbounds = new CoreConfigV2rayService(context with { Node = node, }).BuildAllProxyOutbounds(tag);
_coreConfig.outbounds.AddRange(proxyOutbounds);
if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1)
{
var ret = await GenGroupOutbound(node, v2rayConfig, tag);
if (ret == 0)
{
return tag;
}
return Global.ProxyTag;
var multipleLoad = node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing;
GenObservatory(multipleLoad, tag);
GenBalancer(multipleLoad, tag);
}
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(node, outbound);
outbound.tag = tag;
v2rayConfig.outbounds.Add(outbound);
return outbound.tag;
return tag;
}
}

View File

@@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenStatistic(V2rayConfig v2rayConfig)
private void GenStatistic()
{
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
{
@@ -11,17 +11,17 @@ public partial class CoreConfigV2rayService
Policy4Ray policyObj = new();
SystemPolicy4Ray policySystemSetting = new();
v2rayConfig.stats = new Stats4Ray();
_coreConfig.stats = new Stats4Ray();
apiObj.tag = tag;
v2rayConfig.metrics = apiObj;
_coreConfig.metrics = apiObj;
policySystemSetting.statsOutboundDownlink = true;
policySystemSetting.statsOutboundUplink = true;
policyObj.system = policySystemSetting;
v2rayConfig.policy = policyObj;
_coreConfig.policy = policyObj;
if (!v2rayConfig.inbounds.Exists(item => item.tag == tag))
if (!_coreConfig.inbounds.Exists(item => item.tag == tag))
{
Inbounds4Ray apiInbound = new();
Inboundsettings4Ray apiInboundSettings = new();
@@ -31,10 +31,10 @@ public partial class CoreConfigV2rayService
apiInbound.protocol = Global.InboundAPIProtocol;
apiInboundSettings.address = Global.Loopback;
apiInbound.settings = apiInboundSettings;
v2rayConfig.inbounds.Add(apiInbound);
_coreConfig.inbounds.Add(apiInbound);
}
if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag))
if (!_coreConfig.routing.rules.Exists(item => item.outboundTag == tag))
{
RulesItem4Ray apiRoutingRule = new()
{
@@ -43,9 +43,8 @@ public partial class CoreConfigV2rayService
type = "field"
};
v2rayConfig.routing.rules.Add(apiRoutingRule);
_coreConfig.routing.rules.Add(apiRoutingRule);
}
}
return await Task.FromResult(0);
}
}

View File

@@ -7,7 +7,7 @@ namespace ServiceLib.Services;
/// </summary>
public class DownloadService
{
public event EventHandler<RetResult>? UpdateCompleted;
public event EventHandler<UpdateResult>? UpdateCompleted;
public event ErrorEventHandler? Error;
@@ -40,10 +40,10 @@ public class DownloadService
{
try
{
UpdateCompleted?.Invoke(this, new RetResult(false, $"{ResUI.Downloading} {url}"));
UpdateCompleted?.Invoke(this, new UpdateResult(false, $"{ResUI.Downloading} {url}"));
var progress = new Progress<double>();
progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new RetResult(value > 100, $"...{value}%"));
progress.ProgressChanged += (sender, value) => UpdateCompleted?.Invoke(this, new UpdateResult(value > 100, $"...{value}%"));
var webProxy = await GetWebProxy(blProxy);
await DownloaderHelper.Instance.DownloadFileAsync(webProxy,

View File

@@ -19,7 +19,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
public void ExitLoop()
{
if (_lstExitLoop.Count > 0)
if (!_lstExitLoop.IsEmpty)
{
_ = UpdateFunc("", ResUI.SpeedtestingStop);
@@ -27,6 +27,11 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
}
}
private static bool ShouldStopTest(string exitLoopKey)
{
return !_lstExitLoop.Any(p => p == exitLoopKey);
}
private async Task RunAsync(ESpeedActionType actionType, List<ProfileItem> selecteds)
{
var exitLoopKey = Utils.GetGuid(false);
@@ -103,6 +108,11 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
}
}
if (lstSelected.Count > 1 && (actionType == ESpeedActionType.Speedtest || actionType == ESpeedActionType.Mixedtest))
{
NoticeManager.Instance.Enqueue(ResUI.SpeedtestingPressEscToExit);
}
return lstSelected;
}
@@ -152,7 +162,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
var pageSizeNext = pageSize / 2;
if (lstFailed.Count > 0 && pageSizeNext > 0)
{
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
if (ShouldStopTest(exitLoopKey))
{
await UpdateFunc("", ResUI.SpeedtestingSkip);
return;
@@ -188,8 +198,15 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{
if (!it.AllowTest)
{
await UpdateFunc(it.IndexId, ResUI.SpeedtestingSkip);
continue;
}
if (ShouldStopTest(exitLoopKey))
{
return false;
}
tasks.Add(Task.Run(async () =>
{
await DoRealPing(it);
@@ -218,7 +235,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
List<Task> tasks = new();
foreach (var it in selecteds)
{
if (_lstExitLoop.Any(p => p == exitLoopKey) == false)
if (ShouldStopTest(exitLoopKey))
{
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
continue;
@@ -234,21 +251,27 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
if (processService is null)
{
await UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
return;
}
else
await Task.Delay(1000);
var delay = await DoRealPing(it);
if (blSpeedTest)
{
await Task.Delay(1000);
var delay = await DoRealPing(it);
if (blSpeedTest)
if (ShouldStopTest(exitLoopKey))
{
if (delay > 0)
{
await DoSpeedTest(downloadHandle, it);
}
else
{
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
}
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
return;
}
if (delay > 0)
{
await DoSpeedTest(downloadHandle, it);
}
else
{
await UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
}
}
}
@@ -301,31 +324,28 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{
var responseTime = -1;
if (!IPAddress.TryParse(url, out var ipAddress))
{
var ipHostInfo = await Dns.GetHostEntryAsync(url);
ipAddress = ipHostInfo.AddressList.First();
}
IPEndPoint endPoint = new(ipAddress, port);
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
var timer = Stopwatch.StartNew();
try
{
if (!IPAddress.TryParse(url, out var ipAddress))
{
var ipHostInfo = await Dns.GetHostEntryAsync(url);
ipAddress = ipHostInfo.AddressList.First();
}
IPEndPoint endPoint = new(ipAddress, port);
using Socket clientSocket = new(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
var timer = Stopwatch.StartNew();
var result = clientSocket.BeginConnect(endPoint, null, null);
if (!result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)))
{
throw new TimeoutException("connect timeout (5s): " + url);
}
timer.Stop();
responseTime = (int)timer.Elapsed.TotalMilliseconds;
clientSocket.EndConnect(result);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await clientSocket.ConnectAsync(endPoint, cts.Token).ConfigureAwait(false);
responseTime = (int)timer.ElapsedMilliseconds;
}
catch (Exception ex)
catch (OperationCanceledException)
{
Logging.SaveLog(_tag, ex);
}
finally
{
timer.Stop();
}
return responseTime;
}

View File

@@ -61,7 +61,7 @@ public class StatisticsSingboxService
await Task.Delay(1000);
try
{
if (!_config.IsRunningCore(ECoreType.sing_box))
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box))
{
continue;
}

View File

@@ -30,7 +30,7 @@ public class StatisticsXrayService
await Task.Delay(1000);
try
{
if (_config.RunningCoreType != ECoreType.Xray)
if (AppManager.Instance.RunningCoreType != ECoreType.Xray)
{
continue;
}

View File

@@ -37,7 +37,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, ECoreType.v2rayN));
await UpdateFunc(false, result.Msg);
url = result.Data?.ToString();
url = result.Url.ToString();
fileName = Utils.GetTempPath(Utils.GetGuid());
await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout);
}
@@ -86,7 +86,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
await UpdateFunc(false, string.Format(ResUI.MsgParsingSuccessfully, type));
await UpdateFunc(false, result.Msg);
url = result.Data?.ToString();
url = result.Url.ToString();
var ext = url.Contains(".tar.gz") ? ".tar.gz" : Path.GetExtension(url);
fileName = Utils.GetTempPath(Utils.GetGuid() + ext);
await downloadHandle.DownloadFileAsync(url, fileName, true, _timeout);
@@ -110,26 +110,26 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
#region CheckUpdate private
private async Task<RetResult> CheckUpdateAsync(DownloadService downloadHandle, ECoreType type, bool preRelease)
private async Task<UpdateResult> CheckUpdateAsync(DownloadService downloadHandle, ECoreType type, bool preRelease)
{
try
{
var result = await GetRemoteVersion(downloadHandle, type, preRelease);
if (!result.Success || result.Data is null)
if (!result.Success || result.Version is null)
{
return result;
}
return await ParseDownloadUrl(type, (SemanticVersion)result.Data);
return await ParseDownloadUrl(type, result);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
await UpdateFunc(false, ex.Message);
return new RetResult(false, ex.Message);
return new UpdateResult(false, ex.Message);
}
}
private async Task<RetResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease)
private async Task<UpdateResult> GetRemoteVersion(DownloadService downloadHandle, ECoreType type, bool preRelease)
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
var tagName = string.Empty;
@@ -139,7 +139,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
var result = await downloadHandle.TryDownloadString(url, true, Global.AppName);
if (result.IsNullOrEmpty())
{
return new RetResult(false, "");
return new UpdateResult(false, "");
}
var gitHubReleases = JsonUtils.Deserialize<List<GitHubRelease>>(result);
@@ -153,12 +153,12 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
var lastUrl = await downloadHandle.UrlRedirectAsync(url, true);
if (lastUrl == null)
{
return new RetResult(false, "");
return new UpdateResult(false, "");
}
tagName = lastUrl?.Split("/tag/").LastOrDefault();
}
return new RetResult(true, "", new SemanticVersion(tagName));
return new UpdateResult(true, new SemanticVersion(tagName));
}
private async Task<SemanticVersion> GetCoreVersion(ECoreType type)
@@ -213,10 +213,11 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
}
}
private async Task<RetResult> ParseDownloadUrl(ECoreType type, SemanticVersion version)
private async Task<UpdateResult> ParseDownloadUrl(ECoreType type, UpdateResult result)
{
try
{
var version = result.Version ?? new SemanticVersion(0, 0, 0);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(type);
var coreUrl = await GetUrlFromCore(coreInfo) ?? string.Empty;
SemanticVersion curVersion;
@@ -260,16 +261,17 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
if (curVersion >= version && version != new SemanticVersion(0, 0, 0))
{
return new RetResult(false, message);
return new UpdateResult(false, message);
}
return new RetResult(true, "", url);
result.Url = url;
return result;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
await UpdateFunc(false, ex.Message);
return new RetResult(false, ex.Message);
return new UpdateResult(false, ex.Message);
}
}
@@ -289,13 +291,6 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
return url;
}
//Check for standalone windows .Net version
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "wpfgfx_cor3.dll"))
&& File.Exists(Path.Combine(Utils.GetBaseDirectory(), "D3DCompiler_47_cor3.dll")))
{
return url?.Replace(".zip", "-SelfContained.zip");
}
//Check for avalonia desktop windows version
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll")))
{

View File

@@ -27,6 +27,8 @@ public class AddGroupServerViewModel : MyReactiveObject
public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
public IObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
//public ReactiveCommand<Unit, Unit> AddCmd { get; }
public ReactiveCommand<Unit, Unit> RemoveCmd { get; }
@@ -79,8 +81,8 @@ public class AddGroupServerViewModel : MyReactiveObject
public async Task Init()
{
ProfileGroupItemManager.Instance.TryGet(SelectedSource.IndexId, out var profileGroup);
PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
var protocolExtra = SelectedSource.GetProtocolExtra();
PolicyGroupType = (protocolExtra?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
{
EMultipleLoad.LeastPing => ResUI.TbLeastPing,
EMultipleLoad.Fallback => ResUI.TbFallback,
@@ -93,22 +95,18 @@ public class AddGroupServerViewModel : MyReactiveObject
var subs = await AppManager.Instance.SubItems();
subs.Add(new SubItem());
SubItems.AddRange(subs);
SelectedSubItem = SubItems.Where(s => s.Id == profileGroup?.SubChildItems).FirstOrDefault();
Filter = profileGroup?.Filter;
SelectedSubItem = SubItems.FirstOrDefault(s => s.Id == protocolExtra?.SubChildItems);
Filter = protocolExtra?.Filter;
var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId);
if (childItemMulti != null)
var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? [];
foreach (var item in childIndexIds)
{
var childIndexIds = Utils.String2List(childItemMulti.ChildItems) ?? [];
foreach (var item in childIndexIds)
var child = await AppManager.Instance.GetProfileItem(item);
if (child == null)
{
var child = await AppManager.Instance.GetProfileItem(item);
if (child == null)
{
continue;
}
ChildItemsObs.Add(child);
continue;
}
ChildItemsObs.Add(child);
}
}
@@ -186,6 +184,32 @@ public class AddGroupServerViewModel : MyReactiveObject
await Task.CompletedTask;
}
private ProtocolExtraItem GetUpdatedProtocolExtra()
{
return SelectedSource.GetProtocolExtra() with
{
ChildItems =
Utils.List2String(ChildItemsObs.Where(s => !s.IndexId.IsNullOrEmpty()).Select(s => s.IndexId).ToList()),
MultipleLoad = PolicyGroupType switch
{
var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing,
var s when s == ResUI.TbFallback => EMultipleLoad.Fallback,
var s when s == ResUI.TbRandom => EMultipleLoad.Random,
var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin,
var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad,
_ => EMultipleLoad.LeastPing,
},
SubChildItems = SelectedSubItem?.Id,
Filter = Filter,
};
}
public async Task UpdatePreviewList()
{
AllProfilePreviewItemsObs.Clear();
AllProfilePreviewItemsObs.AddRange(await GroupProfileManager.GetChildProfileItemsByProtocolExtra(GetUpdatedProtocolExtra()));
}
private async Task SaveServerAsync()
{
var remarks = SelectedSource.Remarks;
@@ -205,38 +229,19 @@ public class AddGroupServerViewModel : MyReactiveObject
{
return;
}
var childIndexIds = new List<string>();
foreach (var item in ChildItemsObs)
{
if (item.IndexId.IsNullOrEmpty())
{
continue;
}
childIndexIds.Add(item.IndexId);
}
var profileGroup = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource.IndexId);
profileGroup.ChildItems = Utils.List2String(childIndexIds);
profileGroup.MultipleLoad = PolicyGroupType switch
{
var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing,
var s when s == ResUI.TbFallback => EMultipleLoad.Fallback,
var s when s == ResUI.TbRandom => EMultipleLoad.Random,
var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin,
var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad,
_ => EMultipleLoad.LeastPing,
};
profileGroup.SubChildItems = SelectedSubItem?.Id;
profileGroup.Filter = Filter;
var protocolExtra = GetUpdatedProtocolExtra();
var hasCycle = ProfileGroupItemManager.HasCycle(profileGroup.IndexId);
SelectedSource.SetProtocolExtra(protocolExtra);
var hasCycle = await GroupProfileManager.HasCycle(SelectedSource.IndexId, protocolExtra);
if (hasCycle)
{
NoticeManager.Instance.Enqueue(string.Format(ResUI.GroupSelfReference, remarks));
return;
}
if (await ConfigHandler.AddGroupServerCommon(_config, SelectedSource, profileGroup, true) == 0)
if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0)
{
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null);

View File

@@ -14,6 +14,53 @@ public class AddServerViewModel : MyReactiveObject
[Reactive]
public string CertTip { get; set; }
[Reactive]
public string CertSha { get; set; }
[Reactive]
public string SalamanderPass { get; set; }
[Reactive]
public int AlterId { get; set; }
[Reactive]
public string Ports { get; set; }
[Reactive]
public int UpMbps { get; set; }
[Reactive]
public int DownMbps { get; set; }
[Reactive]
public string HopInterval { get; set; }
[Reactive]
public string Flow { get; set; }
[Reactive]
public string VmessSecurity { get; set; }
[Reactive]
public string VlessEncryption { get; set; }
[Reactive]
public string SsMethod { get; set; }
[Reactive]
public string WgPublicKey { get; set; }
//[Reactive]
//public string WgPresharedKey { get; set; }
[Reactive]
public string WgInterfaceAddress { get; set; }
[Reactive]
public string WgReserved { get; set; }
[Reactive]
public int WgMtu { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
@@ -39,6 +86,12 @@ public class AddServerViewModel : MyReactiveObject
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertTip());
this.WhenAnyValue(x => x.CertSha)
.Subscribe(_ => UpdateCertTip());
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertSha());
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.Network = Global.DefaultNetwork;
@@ -53,6 +106,23 @@ public class AddServerViewModel : MyReactiveObject
}
CoreType = SelectedSource?.CoreType?.ToString();
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty;
CertSha = SelectedSource?.CertSha?.ToString() ?? string.Empty;
var protocolExtra = SelectedSource?.GetProtocolExtra();
Ports = protocolExtra?.Ports ?? string.Empty;
AlterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0;
Flow = protocolExtra?.Flow ?? string.Empty;
SalamanderPass = protocolExtra?.SalamanderPass ?? string.Empty;
UpMbps = protocolExtra?.UpMbps ?? _config.HysteriaItem.UpMbps;
DownMbps = protocolExtra?.DownMbps ?? _config.HysteriaItem.DownMbps;
HopInterval = protocolExtra?.HopInterval.IsNullOrEmpty() ?? true ? Global.Hysteria2DefaultHopInt.ToString() : protocolExtra.HopInterval;
VmessSecurity = protocolExtra?.VmessSecurity?.IsNullOrEmpty() == false ? protocolExtra.VmessSecurity : Global.DefaultSecurity;
VlessEncryption = protocolExtra?.VlessEncryption.IsNullOrEmpty() == false ? protocolExtra.VlessEncryption : Global.None;
SsMethod = protocolExtra?.SsMethod ?? string.Empty;
WgPublicKey = protocolExtra?.WgPublicKey ?? string.Empty;
WgInterfaceAddress = protocolExtra?.WgInterfaceAddress ?? string.Empty;
WgReserved = protocolExtra?.WgReserved ?? string.Empty;
WgMtu = protocolExtra?.WgMtu ?? 1280;
}
private async Task SaveServerAsync()
@@ -77,12 +147,12 @@ public class AddServerViewModel : MyReactiveObject
}
if (SelectedSource.ConfigType == EConfigType.Shadowsocks)
{
if (SelectedSource.Id.IsNullOrEmpty())
if (SelectedSource.Password.IsNullOrEmpty())
{
NoticeManager.Instance.Enqueue(ResUI.FillPassword);
return;
}
if (SelectedSource.Security.IsNullOrEmpty())
if (SsMethod.IsNullOrEmpty())
{
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectEncryption);
return;
@@ -90,14 +160,32 @@ public class AddServerViewModel : MyReactiveObject
}
if (SelectedSource.ConfigType is not EConfigType.SOCKS and not EConfigType.HTTP)
{
if (SelectedSource.Id.IsNullOrEmpty())
if (SelectedSource.Password.IsNullOrEmpty())
{
NoticeManager.Instance.Enqueue(ResUI.FillUUID);
return;
}
}
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
SelectedSource.Cert = Cert.IsNullOrEmpty() ? null : Cert;
SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert;
SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha;
SelectedSource.SetProtocolExtra(SelectedSource.GetProtocolExtra() with
{
Ports = Ports.NullIfEmpty(),
AlterId = AlterId > 0 ? AlterId.ToString() : null,
Flow = Flow.NullIfEmpty(),
SalamanderPass = SalamanderPass.NullIfEmpty(),
UpMbps = UpMbps >= 0 ? UpMbps : null,
DownMbps = DownMbps >= 0 ? DownMbps : null,
HopInterval = HopInterval.NullIfEmpty(),
VmessSecurity = VmessSecurity.NullIfEmpty(),
VlessEncryption = VlessEncryption.NullIfEmpty(),
SsMethod = SsMethod.NullIfEmpty(),
WgPublicKey = WgPublicKey.NullIfEmpty(),
WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(),
WgReserved = WgReserved.NullIfEmpty(),
WgMtu = WgMtu >= 576 ? WgMtu : null,
});
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
{
@@ -113,10 +201,36 @@ public class AddServerViewModel : MyReactiveObject
private void UpdateCertTip(string? errorMessage = null)
{
CertTip = errorMessage.IsNullOrEmpty()
? (Cert.IsNullOrEmpty() ? ResUI.CertNotSet : ResUI.CertSet)
? ((Cert.IsNullOrEmpty() && CertSha.IsNullOrEmpty()) ? ResUI.CertNotSet : ResUI.CertSet)
: errorMessage;
}
private void UpdateCertSha()
{
if (Cert.IsNullOrEmpty())
{
return;
}
var certList = CertPemManager.ParsePemChain(Cert);
if (certList.Count == 0)
{
return;
}
List<string> shaList = [];
foreach (var cert in certList)
{
var sha = CertPemManager.GetCertSha256Thumbprint(cert);
if (sha.IsNullOrEmpty())
{
return;
}
shaList.Add(sha);
}
CertSha = string.Join(',', shaList);
}
private async Task FetchCert()
{
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
@@ -133,17 +247,12 @@ public class AddServerViewModel : MyReactiveObject
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
UpdateCertTip(ResUI.ServerNameMustBeValidDomain);
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
}
string certError;
(Cert, certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
(Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
UpdateCertTip(certError);
}
@@ -163,17 +272,12 @@ public class AddServerViewModel : MyReactiveObject
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
UpdateCertTip(ResUI.ServerNameMustBeValidDomain);
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
}
string certError;
(var certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
Cert = CertPemManager.ConcatenatePemChain(certs);
UpdateCertTip(certError);
}

View File

@@ -128,7 +128,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
{
await Task.Delay(1000 * 5);
numOfExecuted++;
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
{
continue;
}

View File

@@ -437,7 +437,7 @@ public class ClashProxiesViewModel : MyReactiveObject
{
await Task.Delay(1000 * 60);
numOfExecuted++;
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
{
continue;
}

View File

@@ -9,11 +9,12 @@ public class DNSSettingViewModel : MyReactiveObject
[Reactive] public string? DirectDNS { get; set; }
[Reactive] public string? RemoteDNS { get; set; }
[Reactive] public string? BootstrapDNS { get; set; }
[Reactive] public string? RayStrategy4Freedom { get; set; }
[Reactive] public string? SingboxStrategy4Direct { get; set; }
[Reactive] public string? SingboxStrategy4Proxy { 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; }
[Reactive] public bool UseSystemHostsCompatible { get; set; }
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; }
@@ -70,11 +71,12 @@ public class DNSSettingViewModel : MyReactiveObject
DirectDNS = item.DirectDNS;
RemoteDNS = item.RemoteDNS;
BootstrapDNS = item.BootstrapDNS;
RayStrategy4Freedom = item.RayStrategy4Freedom;
SingboxStrategy4Direct = item.SingboxStrategy4Direct;
SingboxStrategy4Proxy = item.SingboxStrategy4Proxy;
Strategy4Freedom = item.Strategy4Freedom;
Strategy4Proxy = item.Strategy4Proxy;
Hosts = item.Hosts;
DirectExpectedIPs = item.DirectExpectedIPs;
ParallelQuery = item.ParallelQuery;
ServeStale = item.ServeStale;
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
RayCustomDNSEnableCompatible = item1.Enabled;
@@ -100,11 +102,12 @@ public class DNSSettingViewModel : MyReactiveObject
_config.SimpleDNSItem.DirectDNS = DirectDNS;
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
_config.SimpleDNSItem.RayStrategy4Freedom = RayStrategy4Freedom;
_config.SimpleDNSItem.SingboxStrategy4Direct = SingboxStrategy4Direct;
_config.SimpleDNSItem.SingboxStrategy4Proxy = SingboxStrategy4Proxy;
_config.SimpleDNSItem.Strategy4Freedom = Strategy4Freedom;
_config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy;
_config.SimpleDNSItem.Hosts = Hosts;
_config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs;
_config.SimpleDNSItem.ParallelQuery = ParallelQuery;
_config.SimpleDNSItem.ServeStale = ServeStale;
if (NormalDNSCompatible.IsNotEmpty())
{

View File

@@ -253,13 +253,12 @@ public class MainWindowViewModel : MyReactiveObject
private async Task Init()
{
_config.UiItem.ShowInTaskbar = true;
AppManager.Instance.ShowInTaskbar = true;
//await ConfigHandler.InitBuiltinRouting(_config);
await ConfigHandler.InitBuiltinDNS(_config);
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
await ProfileExManager.Instance.Init();
await ProfileGroupItemManager.Instance.Init();
await CoreManager.Instance.Init(_config, UpdateHandler);
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);
@@ -306,7 +305,7 @@ public class MainWindowViewModel : MyReactiveObject
private async Task UpdateStatisticsHandler(ServerSpeedItem update)
{
if (!_config.UiItem.ShowInTaskbar)
if (!AppManager.Instance.ShowInTaskbar)
{
return;
}
@@ -560,7 +559,7 @@ public class MainWindowViewModel : MyReactiveObject
});
AppEvents.TestServerRequested.Publish();
var showClashUI = _config.IsRunningCore(ECoreType.sing_box);
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box);
if (showClashUI)
{
AppEvents.ProxiesReloadRequested.Publish();

View File

@@ -44,7 +44,7 @@ public class MsgViewModel : MyReactiveObject
EnqueueQueueMsg(msg);
if (!_config.UiItem.ShowInTaskbar)
if (!AppManager.Instance.ShowInTaskbar)
{
return;
}

View File

@@ -95,7 +95,6 @@ public class OptionSettingViewModel : MyReactiveObject
[Reactive] public bool TunStrictRoute { get; set; }
[Reactive] public string TunStack { get; set; }
[Reactive] public int TunMtu { get; set; }
[Reactive] public bool TunEnableExInbound { get; set; }
[Reactive] public bool TunEnableIPv6Address { get; set; }
#endregion Tun mode
@@ -108,6 +107,7 @@ public class OptionSettingViewModel : MyReactiveObject
[Reactive] public string CoreType4 { get; set; }
[Reactive] public string CoreType5 { get; set; }
[Reactive] public string CoreType6 { get; set; }
[Reactive] public string CoreType7 { get; set; }
[Reactive] public string CoreType9 { get; set; }
#endregion CoreType
@@ -219,7 +219,6 @@ public class OptionSettingViewModel : MyReactiveObject
TunStrictRoute = _config.TunModeItem.StrictRoute;
TunStack = _config.TunModeItem.Stack;
TunMtu = _config.TunModeItem.Mtu;
TunEnableExInbound = _config.TunModeItem.EnableExInbound;
TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address;
#endregion Tun mode
@@ -276,6 +275,10 @@ public class OptionSettingViewModel : MyReactiveObject
CoreType6 = type;
break;
case 7:
CoreType7 = type;
break;
case 9:
CoreType9 = type;
break;
@@ -375,7 +378,6 @@ public class OptionSettingViewModel : MyReactiveObject
_config.TunModeItem.StrictRoute = TunStrictRoute;
_config.TunModeItem.Stack = TunStack;
_config.TunModeItem.Mtu = TunMtu;
_config.TunModeItem.EnableExInbound = TunEnableExInbound;
_config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address;
//coreType
@@ -427,6 +429,10 @@ public class OptionSettingViewModel : MyReactiveObject
type = CoreType6;
break;
case 7:
type = CoreType7;
break;
case 9:
type = CoreType9;
break;

View File

@@ -200,7 +200,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
{
var lstModel = await AppManager.Instance.ProfileItems(_subIndexId, filter);
var lstModel = await AppManager.Instance.ProfileModels(_subIndexId, filter);
lstModel = (from t in lstModel
select new ProfileItemModel
{
@@ -209,7 +209,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
Remarks = t.Remarks,
Address = t.Address,
Port = t.Port,
Security = t.Security,
//Security = t.Security,
Network = t.Network,
StreamSecurity = t.StreamSecurity,
Subid = t.Subid,

View File

@@ -57,6 +57,7 @@ public class ProfilesViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
public ReactiveCommand<SubItem, Unit> MoveToGroupCmd { get; }
//servers ping
public ReactiveCommand<Unit, Unit> MixedTestServerCmd { get; }
@@ -77,6 +78,7 @@ public class ProfilesViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> AddSubCmd { get; }
public ReactiveCommand<Unit, Unit> EditSubCmd { get; }
public ReactiveCommand<Unit, Unit> DeleteSubCmd { get; }
#endregion Menu
@@ -178,6 +180,10 @@ public class ProfilesViewModel : MyReactiveObject
{
await MoveServer(EMove.Bottom);
}, canEditRemove);
MoveToGroupCmd = ReactiveCommand.CreateFromTask<SubItem>(async sub =>
{
SelectedMoveToGroup = sub;
});
//servers ping
FastRealPingCmd = ReactiveCommand.CreateFromTask(async () =>
@@ -235,6 +241,10 @@ public class ProfilesViewModel : MyReactiveObject
{
await EditSubAsync(false);
});
DeleteSubCmd = ReactiveCommand.CreateFromTask(async () =>
{
await DeleteSubAsync();
});
#endregion WhenAnyValue && ReactiveCommand
@@ -418,7 +428,7 @@ public class ProfilesViewModel : MyReactiveObject
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
{
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, filter);
var lstModel = await AppManager.Instance.ProfileModels(_config.SubIndexId, filter);
await ConfigHandler.SetDefaultServer(_config, lstModel);
@@ -436,7 +446,7 @@ public class ProfilesViewModel : MyReactiveObject
Remarks = t.Remarks,
Address = t.Address,
Port = t.Port,
Security = t.Security,
//Security = t.Security,
Network = t.Network,
StreamSecurity = t.StreamSecurity,
Subid = t.Subid,
@@ -553,6 +563,11 @@ public class ProfilesViewModel : MyReactiveObject
private async Task RemoveDuplicateServer()
{
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
{
return;
}
var tuple = await ConfigHandler.DedupServerList(_config, _config.SubIndexId);
if (tuple.Item1 > 0 || tuple.Item2 > 0)
{
@@ -786,7 +801,8 @@ public class ProfilesViewModel : MyReactiveObject
if (blClipboard)
{
var result = await CoreConfigHandler.GenerateClientConfig(item, null);
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, item);
var result = await CoreConfigHandler.GenerateClientConfig(context, null);
if (result.Success != true)
{
NoticeManager.Instance.Enqueue(result.Msg);
@@ -809,7 +825,8 @@ public class ProfilesViewModel : MyReactiveObject
{
return;
}
var result = await CoreConfigHandler.GenerateClientConfig(item, fileName);
var context = await CoreConfigHandler.BuildCoreConfigContext(_config, item);
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true)
{
NoticeManager.Instance.Enqueue(result.Msg);
@@ -879,5 +896,23 @@ public class ProfilesViewModel : MyReactiveObject
}
}
private async Task DeleteSubAsync()
{
var item = await AppManager.Instance.GetSubItem(_config.SubIndexId);
if (item is null)
{
return;
}
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
{
return;
}
await ConfigHandler.DeleteSubItem(_config, item.Id);
await RefreshSubscriptions();
await SubSelectedChangedAsync(true);
}
#endregion Subscription
}

View File

@@ -106,7 +106,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
Network = item.Network,
Protocols = Utils.List2String(item.Protocol),
InboundTags = Utils.List2String(item.InboundTag),
Domains = Utils.List2String((item.Domain ?? []).Concat(item.Ip ?? []).ToList()),
Domains = Utils.List2String((item.Domain ?? []).Concat(item.Ip ?? []).ToList().Concat(item.Process ?? []).ToList()),
Enabled = item.Enabled,
Remarks = item.Remarks,
};

View File

@@ -303,7 +303,7 @@ public class StatusBarViewModel : MyReactiveObject
private async Task RefreshServersMenu()
{
var lstModel = await AppManager.Instance.ProfileItems(_config.SubIndexId, "");
var lstModel = await AppManager.Instance.ProfileModels(_config.SubIndexId, "");
Servers.Clear();
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
@@ -315,7 +315,7 @@ public class StatusBarViewModel : MyReactiveObject
BlServers = true;
for (var k = 0; k < lstModel.Count; k++)
{
ProfileItem it = lstModel[k];
var it = lstModel[k];
var name = it.GetSummary();
var item = new ComboItem() { ID = it.IndexId, Text = name };
@@ -549,7 +549,7 @@ public class StatusBarViewModel : MyReactiveObject
try
{
if (_config.IsRunningCore(ECoreType.sing_box))
if (AppManager.Instance.IsRunningCore(ECoreType.sing_box))
{
SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown));
SpeedDirectDisplay = string.Empty;

View File

@@ -4,11 +4,22 @@ public static class AppBuilderExtension
{
public static AppBuilder WithFontByDefault(this AppBuilder appBuilder)
{
var uri = Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC");
return appBuilder.With(new FontManagerOptions()
var fallbacks = new List<FontFallback>();
var notoSansSc = new FontFamily(Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC"));
fallbacks.Add(new FontFallback { FontFamily = notoSansSc });
if (OperatingSystem.IsLinux())
{
//DefaultFamilyName = uri,
FontFallbacks = new[] { new FontFallback { FontFamily = new FontFamily(uri) } }
fallbacks.Add(new FontFallback
{
FontFamily = new FontFamily("Noto Color Emoji")
});
}
return appBuilder.With(new FontManagerOptions
{
FontFallbacks = fallbacks.ToArray()
});
}
}

View File

@@ -34,8 +34,7 @@
</StackPanel>
<Grid
Grid.Row="0"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
DockPanel.Dock="Top"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock
@@ -75,7 +74,7 @@
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="3"
ColumnDefinitions="180,Auto,Auto">
ColumnDefinitions="300,Auto,Auto">
<TextBlock
Grid.Column="0"
Margin="{StaticResource Margin4}"
@@ -89,11 +88,14 @@
</Grid>
</Grid>
<TabControl DockPanel.Dock="Top">
<TabControl
x:Name="tabControl"
HorizontalContentAlignment="Stretch"
DockPanel.Dock="Top">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid
Margin="{StaticResource Margin8}"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
RowDefinitions="Auto,Auto,Auto">
<TextBlock
@@ -131,13 +133,10 @@
VerticalAlignment="Center" />
</Grid>
</TabItem>
</TabControl>
<TabControl>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList2}">
<DataGrid
x:Name="lstChild"
Grid.Row="1"
AutoGenerateColumns="False"
Background="Transparent"
BorderThickness="1"
@@ -152,13 +151,31 @@
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuAddChildServer" Header="{x:Static resx:ResUI.menuAddChildServer}" />
<MenuItem x:Name="menuRemoveChildServer" Header="{x:Static resx:ResUI.menuRemoveChildServer}" />
<MenuItem x:Name="menuSelectAllChild" Header="{x:Static resx:ResUI.menuSelectAll}" />
<MenuItem
x:Name="menuRemoveChildServer"
Header="{x:Static resx:ResUI.menuRemoveChildServer}"
InputGesture="Back" />
<MenuItem
x:Name="menuSelectAllChild"
Header="{x:Static resx:ResUI.menuSelectAll}"
InputGesture="Ctrl+A" />
<Separator />
<MenuItem x:Name="menuMoveTop" Header="{x:Static resx:ResUI.menuMoveTop}" />
<MenuItem x:Name="menuMoveUp" Header="{x:Static resx:ResUI.menuMoveUp}" />
<MenuItem x:Name="menuMoveDown" Header="{x:Static resx:ResUI.menuMoveDown}" />
<MenuItem x:Name="menuMoveBottom" Header="{x:Static resx:ResUI.menuMoveBottom}" />
<MenuItem
x:Name="menuMoveTop"
Header="{x:Static resx:ResUI.menuMoveTop}"
InputGesture="T" />
<MenuItem
x:Name="menuMoveUp"
Header="{x:Static resx:ResUI.menuMoveUp}"
InputGesture="U" />
<MenuItem
x:Name="menuMoveDown"
Header="{x:Static resx:ResUI.menuMoveDown}"
InputGesture="D" />
<MenuItem
x:Name="menuMoveBottom"
Header="{x:Static resx:ResUI.menuMoveBottom}"
InputGesture="B" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
@@ -189,6 +206,48 @@
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerListPreview}">
<DataGrid
x:Name="lstPreviewChild"
AutoGenerateColumns="False"
Background="Transparent"
BorderThickness="1"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
CanUserSortColumns="False"
GridLinesVisibility="All"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding AllProfilePreviewItemsObs}"
SelectionMode="Extended">
<DataGrid.Columns>
<DataGridTextColumn
Width="150"
Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn
Width="150"
Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn
Width="120"
Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn
Width="100"
Binding="{Binding Port}"
Header="{x:Static resx:ResUI.LvPort}" />
<DataGridTextColumn
Width="100"
Binding="{Binding Network}"
Header="{x:Static resx:ResUI.LvTransportProtocol}" />
<DataGridTextColumn
Width="100"
Binding="{Binding StreamSecurity}"
Header="{x:Static resx:ResUI.LvTLS}" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</DockPanel>
</Window>

View File

@@ -16,6 +16,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close();
lstChild.SelectionChanged += LstChild_SelectionChanged;
tabControl.SelectionChanged += TabControl_SelectionChanged;
ViewModel = new AddGroupServerViewModel(profileItem, UpdateViewHandler);
@@ -38,6 +39,10 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
case EConfigType.ProxyChain:
Title = ResUI.TbConfigTypeProxyChain;
gridPolicyGroup.IsVisible = false;
if (tabControl.Items.Count > 0)
{
tabControl.Items.RemoveAt(0);
}
break;
}
@@ -50,7 +55,6 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RemoveCmd, v => v.menuRemoveChildServer).DisposeWith(disposables);
@@ -132,6 +136,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
break;
case Key.Delete:
case Key.Back:
ViewModel?.ChildRemoveAsync();
e.Handled = true;
break;
@@ -166,4 +171,29 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
ViewModel.SelectedChildren = lstChild.SelectedItems.Cast<ProfileItem>().ToList();
}
}
private async void TabControl_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
try
{
if (e.Source is not TabControl tc)
{
return;
}
if (!(tc.SelectedIndex == tc.Items.Count - 1 && tc.Items.Count > 0))
{
return;
}
if (ViewModel == null)
{
return;
}
await ViewModel.UpdatePreviewList();
}
catch
{
// ignored
}
}
}

View File

@@ -32,11 +32,11 @@
IsCancel="True" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid
Grid.Row="0"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock
@@ -50,7 +50,7 @@
Orientation="Horizontal">
<ComboBox
x:Name="cmbCoreType"
Width="100"
Width="200"
Margin="{StaticResource Margin4}"
ToolTip.Tip="{x:Static resx:ResUI.TbCoreType}" />
</StackPanel>
@@ -101,7 +101,7 @@
<Grid
x:Name="gridVMess"
Grid.Row="2"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
@@ -167,7 +167,7 @@
<Grid
x:Name="gridSs"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
@@ -213,7 +213,7 @@
<Grid
x:Name="gridSocks"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto">
@@ -246,7 +246,7 @@
<Grid
x:Name="gridVLESS"
Grid.Row="2"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
@@ -312,7 +312,7 @@
<Grid
x:Name="gridTrojan"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
@@ -358,9 +358,9 @@
<Grid
x:Name="gridHysteria2"
Grid.Row="2"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
@@ -407,11 +407,46 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPorts7Tips}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbHopInt7}" />
<TextBox
x:Name="txtHopInt7"
Grid.Row="4"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsHysteriaBandwidth}" />
<StackPanel
Grid.Row="5"
Grid.Column="1"
Orientation="Horizontal">
<TextBox
x:Name="txtUpMbps7"
Width="90"
Margin="{StaticResource Margin4}"
Watermark="Up" />
<TextBox
x:Name="txtDownMbps7"
Width="90"
Margin="{StaticResource Margin4}"
Watermark="Down" />
</StackPanel>
</Grid>
<Grid
x:Name="gridTuic"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto">
@@ -457,7 +492,7 @@
<Grid
x:Name="gridWireguard"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
@@ -534,7 +569,7 @@
<Grid
x:Name="gridAnytls"
Grid.Row="2"
ColumnDefinitions="180,Auto"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto">
@@ -560,7 +595,7 @@
<Grid
x:Name="gridTransport"
Grid.Row="4"
ColumnDefinitions="180,Auto,Auto"
ColumnDefinitions="300,Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
@@ -687,12 +722,52 @@
Text="{x:Static resx:ResUI.TbPath}" />
</Grid>
<Separator Grid.Row="5" Margin="{StaticResource MarginTb8}" />
<Grid
x:Name="gridFinalmask"
Grid.Row="5"
ColumnDefinitions="300,Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFinalmask}" />
<Button
Grid.Row="0"
Grid.Column="1"
Margin="{StaticResource MarginLr4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="IconButton">
<Button.Content>
<PathIcon Data="{StaticResource SemiIconMore}">
<PathIcon.RenderTransform>
<RotateTransform Angle="90" />
</PathIcon.RenderTransform>
</PathIcon>
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBox
x:Name="txtFinalmask"
Width="400"
Margin="{StaticResource Margin4}"
AcceptsReturn="True"
Classes="TextArea"
TextWrapping="NoWrap" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
<Separator Grid.Row="6" Margin="{StaticResource MarginTb8}" />
<Grid
x:Name="gridTls"
Grid.Row="6"
ColumnDefinitions="180,Auto"
Grid.Row="7"
ColumnDefinitions="300,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock
@@ -710,10 +785,10 @@
</Grid>
<Grid
x:Name="gridTlsMore"
Grid.Row="7"
ColumnDefinitions="180,Auto"
Grid.Row="8"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
@@ -768,15 +843,41 @@
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbEchConfigList}" />
<TextBox
x:Name="txtEchConfigList"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbEchForceQuery}" />
<ComboBox
x:Name="cmbEchForceQuery"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertPinning}" />
<StackPanel
Grid.Row="5"
Grid.Row="7"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Horizontal">
@@ -815,6 +916,23 @@
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}" />
</StackPanel>
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertSha256Tips}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtCertSha256Pinning"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFullCertTips}"
TextWrapping="Wrap" />
<TextBox
x:Name="txtCert"
Width="400"
@@ -830,8 +948,8 @@
</Grid>
<Grid
x:Name="gridRealityMore"
Grid.Row="7"
ColumnDefinitions="180,Auto"
Grid.Row="8"
ColumnDefinitions="300,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
@@ -918,7 +1036,7 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
</Grid>
<Separator Grid.Row="8" Margin="{StaticResource MarginTb8}" />
<Separator Grid.Row="9" Margin="{StaticResource MarginTb8}" />
</Grid>
</ScrollViewer>
</DockPanel>

View File

@@ -28,6 +28,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
cmbFingerprint2.ItemsSource = Global.Fingerprints;
cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
cmbAlpn.ItemsSource = Global.Alpns;
cmbEchForceQuery.ItemsSource = Global.EchForceQuerys;
var lstStreamSecurity = new List<string>();
lstStreamSecurity.Add(string.Empty);
@@ -38,10 +39,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
case EConfigType.VMess:
gridVMess.IsVisible = true;
cmbSecurity.ItemsSource = Global.VmessSecurities;
if (profileItem.Security.IsNullOrEmpty())
{
profileItem.Security = Global.DefaultSecurity;
}
break;
case EConfigType.Shadowsocks:
@@ -58,10 +55,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridVLESS.IsVisible = true;
lstStreamSecurity.Add(Global.StreamSecurityReality);
cmbFlow5.ItemsSource = Global.Flows;
if (profileItem.Security.IsNullOrEmpty())
{
profileItem.Security = Global.None;
}
break;
case EConfigType.Trojan:
@@ -74,7 +67,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridHysteria2.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
break;
@@ -86,6 +78,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
gridFinalmask.IsVisible = false;
cmbHeaderType8.ItemsSource = Global.TuicCongestionControls;
break;
@@ -103,6 +96,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridAnytls.IsVisible = true;
lstStreamSecurity.Add(Global.StreamSecurityReality);
cmbCoreType.IsEnabled = false;
gridFinalmask.IsVisible = false;
break;
}
cmbStreamSecurity.ItemsSource = lstStreamSecurity;
@@ -119,59 +113,62 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
switch (profileItem.ConfigType)
{
case EConfigType.VMess:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
break;
case EConfigType.Shadowsocks:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
break;
case EConfigType.SOCKS:
case EConfigType.HTTP:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId4.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity4.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId4.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtSecurity4.Text).DisposeWith(disposables);
break;
case EConfigType.VLESS:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
break;
case EConfigType.Trojan:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
break;
case EConfigType.Hysteria2:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SalamanderPass, v => v.txtPath7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(disposables);
break;
case EConfigType.TUIC:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables);
break;
case EConfigType.WireGuard:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.PublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(disposables);
break;
case EConfigType.Anytls:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId10.Text).DisposeWith(disposables);
break;
}
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
@@ -185,8 +182,12 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.SelectedValue).DisposeWith(disposables);
//reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);
@@ -195,6 +196,8 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);

View File

@@ -110,7 +110,10 @@
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuProxiesDelaytestPart" Header="{x:Static resx:ResUI.menuProxiesDelaytestPart}" />
<MenuItem x:Name="menuProxiesSelectActivity" Header="{x:Static resx:ResUI.menuProxiesSelectActivity}" />
<MenuItem
x:Name="menuProxiesSelectActivity"
Header="{x:Static resx:ResUI.menuProxiesSelectActivity}"
InputGesture="Enter" />
</ContextMenu>
</ItemsControl.ContextMenu>
<ItemsControl.ItemsPanel>

View File

@@ -61,7 +61,15 @@
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
IsEditable="True" />
<TextBlock
Grid.Row="1"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbDomesticDNSTips}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="2"
@@ -75,6 +83,7 @@
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
IsEditable="True" />
<TextBlock
Grid.Row="2"
@@ -83,7 +92,7 @@
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRemoteDNSTips}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
@@ -96,6 +105,7 @@
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
IsEditable="True" />
<TextBlock
Grid.Row="3"
@@ -106,59 +116,76 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="4"
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbXrayFreedomStrategy}" />
Text="{x:Static resx:ResUI.TbDirectResolveStrategy}" />
<ComboBox
x:Name="cmbRayFreedomDNSStrategy"
Grid.Row="4"
x:Name="cmbDirectDNSStrategy"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
PlaceholderText="Default" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSBDirectResolveStrategy}" />
<ComboBox
x:Name="cmbSBDirectDNSStrategy"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
PlaceholderText="Default" />
Text="{x:Static resx:ResUI.TbDirectResolveStrategyTips}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSBRemoteResolveStrategy}" />
Text="{x:Static resx:ResUI.TbRemoteResolveStrategy}" />
<ComboBox
x:Name="cmbSBRemoteDNSStrategy"
x:Name="cmbRemoteDNSStrategy"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
PlaceholderText="Default" />
<TextBlock
Grid.Row="6"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRemoteResolveStrategyTips}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
Text="{x:Static resx:ResUI.TbParallelQuery}" />
<ToggleSwitch
x:Name="togAddCommonHosts"
x:Name="togParallelQuery"
Grid.Row="7"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbServeStale}" />
<ToggleSwitch
x:Name="togServeStale"
Grid.Row="8"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
</Grid>
</ScrollViewer>
</TabItem>
@@ -169,7 +196,7 @@
x:Name="gridAdvancedDNSSettings"
Margin="{StaticResource Margin8}"
ColumnDefinitions="Auto,Auto,*"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,*">
<TextBlock
x:Name="txtAdvancedDNSSettingsInvalid"
@@ -190,22 +217,38 @@
Grid.Row="1"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFakeIP}" />
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
<ToggleSwitch
x:Name="togFakeIP"
x:Name="togAddCommonHosts"
Grid.Row="2"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock
Grid.Row="2"
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFakeIP}" />
<ToggleSwitch
x:Name="togFakeIP"
Grid.Row="3"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock
Grid.Row="3"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
@@ -213,19 +256,20 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="3"
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbBlockSVCBHTTPSQueries}" />
<ToggleSwitch
x:Name="togBlockBindingQuery"
Grid.Row="3"
Grid.Row="4"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock
Grid.Row="3"
Grid.Row="4"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
@@ -233,20 +277,21 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="4"
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
<ComboBox
x:Name="cmbDirectExpectedIPs"
Grid.Row="4"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
IsEditable="True" />
<TextBlock
Grid.Row="4"
Grid.Row="5"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
@@ -254,7 +299,7 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="5"
Grid.Row="6"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="{StaticResource Margin4}"
@@ -263,7 +308,7 @@
<TextBox
x:Name="txtHosts"
Grid.Row="6"
Grid.Row="7"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="{StaticResource Margin4}"

View File

@@ -15,16 +15,15 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
btnCancel.Click += (s, e) => Close();
ViewModel = new DNSSettingViewModel(UpdateViewHandler);
cmbRayFreedomDNSStrategy.ItemsSource = Global.DomainStrategy4Freedoms;
cmbSBDirectDNSStrategy.ItemsSource = Global.SingboxDomainStrategy4Out;
cmbSBRemoteDNSStrategy.ItemsSource = Global.SingboxDomainStrategy4Out;
cmbDirectDNSStrategy.ItemsSource = Global.DomainStrategy;
cmbRemoteDNSStrategy.ItemsSource = Global.DomainStrategy;
cmbDirectDNS.ItemsSource = Global.DomainDirectDNSAddress;
cmbRemoteDNS.ItemsSource = Global.DomainRemoteDNSAddress;
cmbBootstrapDNS.ItemsSource = Global.DomainPureIPDNSAddress;
cmbDirectExpectedIPs.ItemsSource = Global.ExpectedIPs;
cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy4Freedoms;
cmbdomainStrategy4OutCompatible.ItemsSource = Global.SingboxDomainStrategy4Out;
cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy;
cmbdomainStrategy4OutCompatible.ItemsSource = Global.DomainStrategies4Sbox;
cmbdomainDNSAddressCompatible.ItemsSource = Global.DomainPureIPDNSAddress;
cmbdomainDNSAddress2Compatible.ItemsSource = Global.DomainPureIPDNSAddress;
@@ -37,11 +36,12 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RayStrategy4Freedom, v => v.cmbRayFreedomDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SingboxStrategy4Direct, v => v.cmbSBDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SingboxStrategy4Proxy, v => v.cmbSBRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Freedom, v => v.cmbDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);

View File

@@ -9,9 +9,9 @@
xmlns:view="using:v2rayN.Desktop.Views"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="v2rayN"
Width="1000"
Height="600"
MinWidth="900"
Width="1200"
Height="800"
MinWidth="600"
x:DataType="vms:MainWindowViewModel"
Icon="/Assets/NotifyIcon1.ico"
ShowInTaskbar="True"
@@ -26,8 +26,14 @@
<ContentControl x:Name="conTheme" DockPanel.Dock="Right" />
<Menu Margin="{StaticResource Margin4}">
<MenuItem Header="{x:Static resx:ResUI.menuServers}">
<MenuItem x:Name="menuAddServerViaClipboard" Header="{x:Static resx:ResUI.menuAddServerViaClipboard}" />
<MenuItem x:Name="menuAddServerViaScan" Header="{x:Static resx:ResUI.menuAddServerViaScan}" />
<MenuItem
x:Name="menuAddServerViaClipboard"
Header="{x:Static resx:ResUI.menuAddServerViaClipboard}"
InputGesture="Ctrl+V" />
<MenuItem
x:Name="menuAddServerViaScan"
Header="{x:Static resx:ResUI.menuAddServerViaScan}"
InputGesture="Ctrl+S" />
<MenuItem x:Name="menuAddServerViaImage" Header="{x:Static resx:ResUI.menuAddServerViaImage}" />
<MenuItem x:Name="menuAddCustomServer" Header="{x:Static resx:ResUI.menuAddCustomServer}" />
<MenuItem x:Name="menuAddPolicyGroupServer" Header="{x:Static resx:ResUI.menuAddPolicyGroupServer}" />
@@ -37,11 +43,11 @@
<MenuItem x:Name="menuAddVlessServer" Header="{x:Static resx:ResUI.menuAddVlessServer}" />
<MenuItem x:Name="menuAddShadowsocksServer" Header="{x:Static resx:ResUI.menuAddShadowsocksServer}" />
<MenuItem x:Name="menuAddTrojanServer" Header="{x:Static resx:ResUI.menuAddTrojanServer}" />
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem x:Name="menuAddWireguardServer" Header="{x:Static resx:ResUI.menuAddWireguardServer}" />
<MenuItem x:Name="menuAddSocksServer" Header="{x:Static resx:ResUI.menuAddSocksServer}" />
<MenuItem x:Name="menuAddHttpServer" Header="{x:Static resx:ResUI.menuAddHttpServer}" />
<Separator />
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem x:Name="menuAddTuicServer" Header="{x:Static resx:ResUI.menuAddTuicServer}" />
<MenuItem x:Name="menuAddAnytlsServer" Header="{x:Static resx:ResUI.menuAddAnytlsServer}" />
</MenuItem>
@@ -84,13 +90,13 @@
<MenuItem x:Name="menuOpenTheFileLocation" Header="{x:Static resx:ResUI.menuOpenTheFileLocation}" />
</MenuItem>
<MenuItem x:Name="menuReload" Header="{x:Static resx:ResUI.menuReload}" />
<MenuItem x:Name="menuHelp" Header="{x:Static resx:ResUI.menuHelp}">
<MenuItem x:Name="menuCheckUpdate" Header="{x:Static resx:ResUI.menuCheckUpdate}" />
<Separator />
</MenuItem>
<MenuItem x:Name="menuReload" Header="{x:Static resx:ResUI.menuReload}" />
<MenuItem x:Name="menuPromotion" Header="{x:Static resx:ResUI.menuPromotion}" />
<MenuItem x:Name="menuClose" Header="{x:Static resx:ResUI.menuExit}" />

View File

@@ -409,8 +409,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{
var bl = blShow ??
(Utils.IsLinux()
? (!_config.UiItem.ShowInTaskbar ^ (WindowState == WindowState.Minimized))
: !_config.UiItem.ShowInTaskbar);
? (!AppManager.Instance.ShowInTaskbar ^ (WindowState == WindowState.Minimized))
: !AppManager.Instance.ShowInTaskbar);
if (bl)
{
Show();
@@ -436,7 +436,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
Hide();
}
_config.UiItem.ShowInTaskbar = bl;
AppManager.Instance.ShowInTaskbar = bl;
}
protected override void OnLoaded(object? sender, RoutedEventArgs e)

View File

@@ -72,18 +72,20 @@
VerticalScrollBarVisibility="Auto"
WordWrap="True">
<avaloniaEdit:TextEditor.Options>
<avaloniaEdit:TextEditorOptions AllowScrollBelowDocument="False"/>
<avaloniaEdit:TextEditorOptions AllowScrollBelowDocument="False" />
</avaloniaEdit:TextEditor.Options>
<avaloniaEdit:TextEditor.ContextFlyout>
<MenuFlyout>
<MenuItem
x:Name="menuMsgViewSelectAll"
Click="menuMsgViewSelectAll_Click"
InputGesture="Ctrl+A"
Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" />
<MenuItem
x:Name="menuMsgViewCopy"
Click="menuMsgViewCopy_Click"
Header="{x:Static resx:ResUI.menuMsgViewCopy}" />
Header="{x:Static resx:ResUI.menuMsgViewCopy}"
InputGesture="Ctrl+C" />
<MenuItem
x:Name="menuMsgViewCopyAll"
Click="menuMsgViewCopyAll_Click"

View File

@@ -325,12 +325,6 @@
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="20"
Grid.Column="2"
Margin="{StaticResource Margin4}"
Text="{x:Static resx:ResUI.TbSettingsEnableFragmentTips}"
TextWrapping="Wrap" />
</Grid>
</ScrollViewer>
</TabItem>
@@ -463,7 +457,7 @@
VerticalAlignment="Center"
IsVisible="{Binding BlIsLinux}"
Text="{x:Static resx:ResUI.TbSettingsHide2TrayWhenCloseTip}" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
@@ -477,7 +471,7 @@
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
IsVisible="{Binding BlIsIsMacOS}"/>
IsVisible="{Binding BlIsIsMacOS}" />
<TextBlock
Grid.Row="11"
@@ -592,7 +586,8 @@
Grid.Row="20"
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin4}" />
Margin="{StaticResource Margin4}"
IsEditable="True" />
<TextBlock
Grid.Row="21"
@@ -842,19 +837,6 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsEnableExInbound}" />
<ToggleSwitch
x:Name="togEnableExInbound"
Grid.Row="6"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
@@ -875,7 +857,7 @@
<Grid
Margin="{StaticResource Margin4}"
ColumnDefinitions="Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
@@ -959,10 +941,23 @@
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Hysteria2" />
<ComboBox
x:Name="cmbCoreType7"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Wireguard" />
<ComboBox
x:Name="cmbCoreType9"
Grid.Row="7"
Grid.Row="8"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />

View File

@@ -41,6 +41,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
cmbCoreType4.ItemsSource = Global.CoreTypes;
cmbCoreType5.ItemsSource = Global.CoreTypes;
cmbCoreType6.ItemsSource = Global.CoreTypes;
cmbCoreType7.ItemsSource = Global.CoreTypes;
cmbCoreType9.ItemsSource = Global.CoreTypes;
cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList();
@@ -101,7 +102,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);
@@ -113,7 +114,6 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableExInbound, v => v.togEnableExInbound.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.SelectedValue).DisposeWith(disposables);
@@ -122,6 +122,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.CoreType4, v => v.cmbCoreType4.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType5, v => v.cmbCoreType5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType7, v => v.cmbCoreType7.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.SelectedValue).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);

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