Compare commits

...

175 Commits

Author SHA1 Message Date
2dust
899b3fc97b up 7.15.3 2025-10-14 19:52:42 +08:00
DHR60
a1490d0ac1 Update singbox_fakeip_filter (#8117) 2025-10-12 09:59:30 +08:00
DHR60
b23f49ffce Remove unnecessary settings (#8107) 2025-10-11 19:22:26 +08:00
2dust
9a9e28e494 Update GlobalHotKeys 2025-10-10 19:25:54 +08:00
2dust
65ee5eb510 Fix,remove NaiveproxyFmt HysteriaFmt ,adjust ClashFmt
https://github.com/2dust/v2rayN/issues/8102
2025-10-10 17:12:45 +08:00
DHR60
1f42d32e1a Fix Freedom Resolver (#8100) 2025-10-10 16:58:18 +08:00
2dust
f2ed8c1d6b up 7.15.2 2025-10-09 20:29:45 +08:00
2dust
308b216d1b Adjust ActionPrecheckManager 2025-10-09 20:29:25 +08:00
2dust
c713f5c8f5 Update Directory.Packages.props 2025-10-09 20:22:41 +08:00
2dust
6771eb25d1 Adjust ActionPrecheckManager 2025-10-09 20:22:35 +08:00
2dust
91af50f99a Optimize code ,add IsGroupType extension. Adjust EConfigType 2025-10-08 17:13:54 +08:00
2dust
a559586e71 Code clean 2025-10-08 15:48:51 +08:00
2dust
929520775d Bug fix 2025-10-08 15:48:45 +08:00
2dust
4eaf31bbf8 Fix
https://github.com/2dust/v2rayN/issues/8092
2025-10-08 14:36:47 +08:00
2dust
1607525539 Optimize the ruletype UI 2025-10-08 14:12:16 +08:00
DHR60
31b5b4ca0c Add rule type selection to routing rules (#8007)
* Add rule type selection to routing rules

* Use enum

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2025-10-08 10:40:26 +08:00
2dust
64c7fea2bc Update Directory.Packages.props 2025-10-07 19:17:35 +08:00
2dust
f76fd364a2 Rename ProfileGroupItem.ParentIndexId to IndexId
Because ProfileGroupItem is an extension of ProfileItem, it is better to name the fields the same way.
2025-10-07 14:01:36 +08:00
2dust
0a1d6db9d1 Add cycle check for AddGroupServerViewModel 2025-10-07 13:54:31 +08:00
2dust
7a750a127e Rename ActionPrecheckService 2025-10-07 13:53:33 +08:00
2dust
fce4a7b74c Optimization and improvement, tray, etc.
https://github.com/2dust/v2rayN/pull/8083
2025-10-07 11:16:20 +08:00
DHR60
fec7353703 PreCheck (#7902)
* PreCheck

* Fix
2025-10-07 10:03:20 +08:00
Weheal
40c90d5b3b Fix: AutoHideStartup's bug of displaying window before hiding it. (#8083)
* Fix: AutoHideStartup's bug of displaying window before hiding it.

* Disable AutoHideStartup for Linux

* Revert "Disable AutoHideStartup for Linux"

This reverts commit 09f27e345575f9044c035a7fea055a83f5136fa2.

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2025-10-07 10:02:53 +08:00
2dust
9c58fec8d4 Bug fix 2025-10-05 19:55:52 +08:00
DHR60
11343a30fd Multi profile (#7929)
* Multi Profile

* VM and wpf

* avalonia

* Fix right click not working

* Exclude specific profile types from selection

* Rename

* Add Policy Group support

* Add generate policy group

* Adjust UI

* Add Proxy Chain support

* Fix

* Add fallback support

* Add PolicyGroup include other Group support

* Add group in traffic splitting support

* Avoid duplicate tags

* Refactor

* Adjust chained proxy, actual outbound is at the top

Based on actual network flow instead of data packets

* Add helper function

* Refactor

* Add chain selection control to group outbounds

* Avoid self-reference

* Fix

* Improves Tun2Socks address handling

* Avoids circular dependency in profile groups

Adds cycle detection to prevent infinite loops when evaluating profile groups.

This ensures that profile group configurations don't result in stack overflow errors when groups reference each other, directly or indirectly.

* Fix

* Fix

* Update ProfileGroupItem.cs

* Refactor

* Remove unnecessary checks

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2025-10-05 16:27:34 +08:00
2dust
3693a7fee6 up 7.15.1 2025-10-05 09:22:47 +08:00
2dust
a452bbe140 Fix
https://github.com/2dust/v2rayN/issues/8061
2025-10-04 19:54:15 +08:00
DHR60
185c5e4bfb Fix (#8057) 2025-10-04 16:17:39 +08:00
2dust
bbe64aa970 Remove AutoCompleteBox
https://github.com/2dust/v2rayN/pull/8067
2025-10-04 16:16:32 +08:00
DHR60
513662d89a Use editable ComboBox instead of AutoCompleteBox (#8067)
* Update Avalonia

* Use editable ComboBox instead of AutoCompleteBox
2025-10-04 15:18:37 +08:00
2dust
22f0d04f01 Fix
https://github.com/2dust/v2rayN/issues/8060
2025-10-03 14:13:03 +08:00
2dust
d7c5161431 Optimize and improve 2025-10-02 19:55:49 +08:00
2dust
12cc09d0c9 Bug fix 2025-10-01 20:17:26 +08:00
2dust
5b12c36da5 Optimize and improve, encapsulate ProcessService 2025-10-01 19:49:28 +08:00
DHR60
e970372a9f Fix some minor UI bugs (#8053) 2025-10-01 16:47:22 +08:00
2dust
5d6c5da9d9 up 7.15.0 2025-09-28 19:12:58 +08:00
2dust
ade2db3903 Code clean 2025-09-28 19:12:17 +08:00
Wydy
7f07279a4c Update pac (#7991) 2025-09-28 19:08:29 +08:00
2dust
b25d4d57bd Fix ProfilesSelectWindow 2025-09-27 19:46:31 +08:00
2dust
46edd8f9a4 Bug fix 2025-09-27 18:07:20 +08:00
JieXu
ebb95b5ee8 Update MsgView.axaml.cs (#8042) 2025-09-27 17:02:49 +08:00
2dust
dc4611a258 Adjust qrcode width 2025-09-26 20:36:27 +08:00
2dust
03d5b7a05b Bug fix 2025-09-26 17:11:48 +08:00
2dust
a652fd879b Added simple highlight function to the message view 2025-09-26 15:29:46 +08:00
2dust
326bf334e7 Optimize and improve MsgView 2025-09-26 15:07:33 +08:00
JieXu
21a773f400 Update MsgView.axaml.cs Plan C (#8035)
* Add avaloniaEdit for test

* Adjust avaloniaEdit

* Optimize and improve message function

* Update build-linux.yml

* Update MsgView.axaml

* Update MsgView.axaml.cs

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2025-09-26 13:55:35 +08:00
2dust
d86003df55 Optimize and improve the Subject 2025-09-25 10:56:10 +08:00
2dust
faff8e4ea2 Remove secret data from mihomo configuration 2025-09-24 18:41:00 +08:00
2dust
6b85aa0b03 Remove Splat.NLog package 2025-09-24 10:57:23 +08:00
2dust
671678724b Optimization and improvement, using event subscribers 2025-09-24 10:57:06 +08:00
2dust
e96a4818c4 Optimization and improvement 2025-09-23 15:31:19 +08:00
2dust
0377e7ce19 Optimization and improvement, using event subscribers 2025-09-23 14:27:42 +08:00
2dust
6929886b3e Optimization and improvement, using event subscribers 2025-09-23 12:08:43 +08:00
2dust
721d70c8c7 Update Directory.Packages.props 2025-09-23 11:39:57 +08:00
2dust
27b45aee83 Optimization and improvement, using event subscribers 2025-09-23 11:39:55 +08:00
2dust
18ac76e683 up 7.14.12 2025-09-21 14:50:01 +08:00
2dust
3e1e23a524 Update Directory.Packages.props 2025-09-21 14:48:54 +08:00
2dust
534c7ab444 Optimize and improve QR code display 2025-09-21 14:35:49 +08:00
2dust
c2c13ad318 Create v2rayN.slnx
https://github.com/2dust/v2rayN/pull/7969
2025-09-21 12:12:24 +08:00
2dust
3a21596d95 Fix node domain resolving in TUN mode
https://github.com/2dust/v2rayN/pull/7989
2025-09-21 12:05:06 +08:00
2dust
ef30d389dc up 7.14.11 2025-09-20 14:06:55 +08:00
2dust
bf8783fed7 Update CheckUpdateViewModel.cs 2025-09-20 14:06:41 +08:00
DHR60
4e042295d2 Add global fakeip and fakeip filter (#7919) 2025-09-13 14:55:30 +08:00
2dust
33d9c5db6c up GlobalUsings 2025-09-13 14:46:35 +08:00
DHR60
cb182125f6 Fix (#7946)
https://github.com/2dust/v2rayN/pull/7937
2025-09-13 11:13:09 +08:00
2dust
ec627bdb82 up 7.14.10 2025-09-13 09:53:03 +08:00
2dust
4606e78570 Update Directory.Packages.props 2025-09-13 09:46:28 +08:00
2dust
f00e968b8f Bug fix
https://github.com/2dust/v2rayN/issues/7944
2025-09-13 09:41:34 +08:00
DHR60
a87a015c03 Fix some minor UI bugs (#7941) 2025-09-12 20:28:24 +08:00
2dust
c559914ff7 Fix
https://github.com/2dust/v2rayN/issues/7938
2025-09-12 17:01:53 +08:00
2dust
436d95576e Optimization and improvement JsonUtils 2025-09-12 16:45:55 +08:00
DHR60
54e83391d0 Pre-resolve to apply hosts (#7937) 2025-09-12 16:28:31 +08:00
JieXu
3e0578f775 Update CheckUpdateViewModel.cs (#7932)
* Update CheckUpdateViewModel.cs

* Update Utils.cs

* Update Utils.cs

* Update Utils.cs

* Update CheckUpdateViewModel.cs

* Update CheckUpdateViewModel.cs

* Update Utils.cs
2025-09-12 16:24:59 +08:00
2dust
29a5abf4d6 Optimization and improvement 2025-09-10 19:43:11 +08:00
2dust
b54c67d6f1 up 7.14.9 2025-09-09 20:18:55 +08:00
2dust
b49486cc23 Update ProfilesSelectWindow.axaml 2025-09-09 20:00:00 +08:00
JieXu
b95830b3d5 Update package-rhel.sh package-debian.sh MainWindowViewModel.cs (#7910)
* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update MainWindowViewModel.cs

* Update package-rhel.sh

* Update package-debian.sh
2025-09-09 19:51:10 +08:00
2dust
8e0c5cb9aa Bug fix
https://github.com/2dust/v2rayN/issues/7914
2025-09-09 17:55:15 +08:00
2dust
6ffb3bd30c up 7.14.8 2025-09-08 18:48:56 +08:00
2dust
2826444ffc Code clean 2025-09-08 18:45:21 +08:00
JieXu
56c3e9c46d Fix package-appimage.sh bugs. (#7904)
* Update package-appimage.sh

* Delete pkg2appimage.yml
2025-09-08 18:02:54 +08:00
th1nker
0770e30034 fix: 修正获取系统hosts (#7903)
- 修复当host的记录存在行尾注释时,无法将其添加到dns.host中

示例hosts
```
127.0.0.1 test1.com
127.0.0.1 test2.com # test
```
在之前仅仅会添加`127.0.0.1 test1.com`这条记录,而忽略另一条
2025-09-08 18:02:44 +08:00
DHR60
04195c2957 Profiles Select Window (#7891)
* Profiles Select Window

* Sort

* wpf

* avalonia

* Allow single select

* Fix

* Add Config Type Filter

* Remove unnecessary
2025-09-07 18:58:59 +08:00
JieXu
d18d74ac1c Update package-debian.sh (#7899) 2025-09-07 16:39:54 +08:00
2dust
6391667c15 up 7.14.7 2025-09-06 16:59:54 +08:00
2dust
7f26445327 Update Directory.Packages.props 2025-09-06 16:59:38 +08:00
2dust
291d4bd8e5 Update Directory.Packages.props 2025-09-06 16:52:57 +08:00
dependabot[bot]
f2f3a7eb5f Bump actions/setup-dotnet from 4.3.1 to 5.0.0 (#7883)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.3.1 to 5.0.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4.3.1...v5.0.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 5.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-09-06 15:46:34 +08:00
JieXu
e7609619d4 Update package-debian.sh (#7888) 2025-09-06 15:46:20 +08:00
DHR60
84bf9ecfaf Fix DNS Regional Presets (#7885) 2025-09-05 18:11:16 +08:00
2dust
a2917b3ce8 Update Directory.Packages.props 2025-09-04 20:50:25 +08:00
2dust
d094370209 up 7.14.6 2025-09-03 19:05:45 +08:00
2dust
1a6fbf782d Using RxApp replace ViewAction 2025-09-02 17:12:38 +08:00
2dust
3f67a23f8b up 7.14.5 2025-08-31 19:55:17 +08:00
2dust
b8eb7e7b29 Optimization and Improvement. 2025-08-31 15:41:25 +08:00
2dust
1d69916410 Update GlobalHotKeys 2025-08-31 14:21:22 +08:00
2dust
49fa103077 Optimize UI 2025-08-31 14:08:05 +08:00
2dust
e3a63db966 Using RxApp replace ViewAction 2025-08-30 20:36:16 +08:00
DHR60
ef4a1903ec Update mihomo download url (#7852) 2025-08-30 19:44:54 +08:00
2dust
5a3286dad1 Using RxApp replace ViewAction 2025-08-30 19:32:07 +08:00
2dust
058c6e4a85 Use Rx event subscription instead of MessageBus to send information 2025-08-29 15:46:09 +08:00
2dust
ea1d438e40 Use Rx event subscription to replace MessageBus refresh configuration file function 2025-08-29 14:46:08 +08:00
2dust
a108eaf34b Optimization and Improvement.
Changed callback from synchronous Action<bool, string> to asynchronous Func<bool, string, Task>
2025-08-29 11:53:30 +08:00
2dust
da28c639b3 Optimization and Improvement.
Changed callback from synchronous Action<bool, string> to asynchronous Func<bool, string, Task>
2025-08-29 11:40:08 +08:00
2dust
8ef68127d4 Optimization and Improvement.
Changed callback from synchronous Action<bool, string> to asynchronous Func<bool, string, Task>
2025-08-29 10:53:57 +08:00
2dust
f39d966a33 Optimization and Improvement.
Changed callback from synchronous Action<bool, string> to asynchronous Func<bool, string, Task>
2025-08-29 10:31:09 +08:00
2dust
f83e83de13 Optimization and Improvement
Changed callback from synchronous Action<bool, string> to asynchronous Func<bool, string, Task>
2025-08-29 09:49:30 +08:00
2dust
abdafc9b3b up 7.14.4 2025-08-27 20:29:16 +08:00
2dust
8f93c50151 Bug fix 2025-08-27 17:22:13 +08:00
2dust
fe7c505cc9 Update subscription using Task.Run 2025-08-27 17:14:24 +08:00
2dust
0d5afa4ff5 Optimizing SQLite performance
https://github.com/2dust/v2rayN/issues/7835
2025-08-26 20:56:28 +08:00
2dust
2ad716a4ad Remove Cursor="Hand" 2025-08-26 17:46:43 +08:00
DHR60
cddf88730f Fix dns (#7834) 2025-08-26 17:34:12 +08:00
DHR60
3eb49aa24c Add mieru support (#7828) 2025-08-25 17:43:53 +08:00
2dust
45c987fd86 up 7.14.3 2025-08-23 16:36:12 +08:00
2dust
7bec05ec23 Fix
https://github.com/2dust/v2rayN/issues/7819
2025-08-23 16:28:52 +08:00
2dust
606b216cd0 Press the Esc button to close the window
https://github.com/2dust/v2rayN/issues/7819
2025-08-23 16:23:30 +08:00
2dust
bb4f33559f Code clean 2025-08-21 19:55:17 +08:00
2dust
c7f3e53f28 Customize MenuFlyoutMaxHeight for desktop version 2025-08-21 19:32:39 +08:00
JieXu
0035e836d7 Update build-linux.yml, Add RPM package for RHEL. (#7813)
* Update build-linux.yml

* Update build-linux.yml

* Update build-linux.yml

* Update build-linux.yml

* Update package-rhel.sh

* Update package-rhel.sh. Change describe information

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh
2025-08-21 17:18:19 +08:00
2dust
e6da14f4a8 up 7.14.2 2025-08-19 19:05:55 +08:00
2dust
f748f1849c Update Directory.Packages.props 2025-08-19 19:05:20 +08:00
DHR60
f8995b78f6 Passes srsName as third format argument (#7805) 2025-08-19 17:00:27 +08:00
JieXu
a861020828 Update package-rhel.sh (#7806) 2025-08-19 16:59:52 +08:00
DHR60
dc94962900 Fix tun (#7802) 2025-08-19 09:10:54 +08:00
JieXu
4a40b87bba Update package-rhel.sh (#7799) 2025-08-19 09:09:52 +08:00
DHR60
4853e2348d Fix dns (#7797) 2025-08-19 09:09:35 +08:00
2dust
e104f9f9b2 Rename Manager 2025-08-18 20:09:58 +08:00
JieXu
876381a7fb Create package-rhel.sh (#7770)
* Create package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh

* Update package-rhel.sh
2025-08-18 17:29:10 +08:00
Miheichev Aleksandr Sergeevich
4f711b1bd3 i18n(ru/zh-Hans/zh-Hant/hu/fa): translate TUN settings, unify MTU, use resx (#7787)
* feat(i18n,ui): externalize TUN settings labels, add translations

- Replace hard-coded labels "Auto Route", "Strict Route", "Stack",
  and "Mtu/mtu" with resource keys in both Avalonia and WPF views:
  - v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml
  - v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml
  - v2rayN/v2rayN/Views/AddServerWindow.xaml
  - v2rayN/v2rayN/Views/OptionSettingWindow.xaml
- Add new resource keys in ResUI:
  TbSettingsTunAutoRoute, TbSettingsTunStrictRoute,
  TbSettingsTunStack, TbSettingsTunMtu (unified casing as "MTU").
  Files:
  - v2rayN/ServiceLib/Resx/ResUI.resx
- Provide translations in:
  - v2rayN/ServiceLib/Resx/ResUI.ru.resx
  - v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx
  - v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx
  - v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx
  - v2rayN/ServiceLib/Resx/ResUI.hu.resx
- Normalize XML comments/whitespace in .resx files.
- Update submodule v2rayN/GlobalHotKeys to 5201dd5.

No breaking changes.

* i18n: TUN labels across locales; unify MTU

* chore: ignore local IDE/venv files

* chore(resx): regenerate ResUI.Designer with TUN string accessors

- Add strongly-typed accessors in ServiceLib.Resx.ResUI:
  - TbSettingsTunAutoRoute → "Auto Route"
  - TbSettingsTunStrictRoute → "Strict Route"
  - TbSettingsTunStack → "Stack"
  - TbSettingsTunMtu → "MTU"
- Keep auto-generated structure intact; normalize minor whitespace.

Refs: v2rayN/ServiceLib/Resx/ResUI.resx
No functional changes beyond exposing new i18n keys.

* chore(gitignore): ignore JetBrains Rider artifacts (.idea/, *.sln.iml)

---------

Co-authored-by: Aleksandr Miheichev <alexandr.gmail@tuta.com>
2025-08-18 17:28:59 +08:00
DHR60
89893c0945 Adds Xray and Singbox support config type (#7789)
* Adds Xray and Singbox config type support

* Unify multiline logical expression formatting
2025-08-18 17:28:49 +08:00
2dust
7b7fe0ef46 Refactoring GetRealPingTime 2025-08-17 20:51:49 +08:00
2dust
f66226c103 Simple refactoring of CoreConfig generated code 2025-08-17 20:09:41 +08:00
2dust
d5c50ef27c Rename Manager 2025-08-17 17:31:55 +08:00
2dust
2060ac18fd Add Manager folder 2025-08-17 16:52:51 +08:00
2dust
c9c1cd8cbb Add Helper folder 2025-08-17 16:26:13 +08:00
2dust
5201dd5ad0 up 7.14.1 2025-08-17 14:26:13 +08:00
2dust
4c3c1e0b5f Optimization and upgrade tools 2025-08-17 14:12:40 +08:00
DHR60
c27651b7b7 Fixed Failed Gen Default Configuration (#7785) 2025-08-17 13:52:57 +08:00
2dust
06636d04ac PacHandler is changed to singleton mode 2025-08-17 11:00:13 +08:00
DHR60
6979e21628 Remove DomainMatcher (#7781) 2025-08-17 09:32:02 +08:00
DHR60
310d266745 Add VLESS encryption support (#7782) 2025-08-17 09:15:06 +08:00
2dust
120e8d0686 Fixed a bug in parsing subscription result
https://github.com/2dust/v2rayN/issues/7734
2025-08-16 20:05:56 +08:00
2dust
186b56aed9 Refactor SubscriptionHandler and add exception capture 2025-08-16 18:01:12 +08:00
2dust
c560fe13fe Adjust the tun mtu value list
https://github.com/2dust/v2rayN/issues/7775
2025-08-16 17:18:17 +08:00
2dust
95e3ebd815 up 7.14.0 2025-08-15 17:18:16 +08:00
2dust
dc2877d817 Refactor UpdateSubscriptionProcess ,add SubscriptionHandler 2025-08-14 20:59:15 +08:00
2dust
89d6af8fc9 Added global constants such as IPIfNonMatch 2025-08-14 20:18:22 +08:00
DHR60
dcc9c9fa14 Fixes DNS (#7757)
* Adds properties

* Adds DNS routing
2025-08-14 17:32:48 +08:00
DHR60
a73906505c Improves domain blocking and proxy handling (#7754) 2025-08-14 09:30:58 +08:00
2dust
f45290eb3a Fixed a bug in dns rule processing when outbound in routing rules is a other server 2025-08-13 21:19:51 +08:00
2dust
0fb6b2e54b Bug fix for DNS setting 2025-08-13 18:43:15 +08:00
2dust
9cc99c5c63 Update Directory.Packages.props 2025-08-13 18:41:57 +08:00
dependabot[bot]
46801ce339 Bump actions/checkout from 4.2.2 to 5.0.0 (#7749)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 5.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/v4.2.2...v5.0.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.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-08-13 08:39:18 +08:00
2dust
4345c58b45 Bug fix for FullConfigTemplate xray 2025-08-12 20:23:37 +08:00
Miheichev Aleksandr Sergeevich
a2028623e7 refactor: improve Russian localization, fix placeholders and typos (#7740)
Co-authored-by: Aleksandr Miheichev <alexandr.gmail@tuta.com>
2025-08-12 19:28:40 +08:00
Summpot
8314ff3271 Add Auto Route option to Tun mode settings (#7737)
Introduces a new 'Auto Route' option for Tun mode in the configuration, view models, and UI. Updates both Avalonia and WPF option setting windows to allow users to enable or disable automatic routing for Tun mode. Also ensures the new setting is properly bound and saved in the configuration.
2025-08-12 19:18:26 +08:00
DHR60
6a9408fe9b Fixes sing-box system hosts and ui (#7733) 2025-08-11 20:46:35 +08:00
DHR60
b5e0a77401 Full Config Template (#7576)
* Feat. custom config

* Fixes TypeInfoResolver Exception

* Adjust UI

* Fixes

* Adjust Avalonia UI

* Add Detour Feature

* Avoids detour for private networks

* Rename

* Adds Documents
2025-08-11 20:01:48 +08:00
2dust
dffc6d9a9b Fixed DNS bug with region switch 2025-08-10 21:08:49 +08:00
2dust
c9989108bd Use raw.githubusercontent.com instead of cdn.jsdelivr.net (#7732)
https://github.com/2dust/v2rayN/issues/7682
2025-08-10 20:22:04 +08:00
2dust
386c86bfa6 Code clean 2025-08-10 20:12:57 +08:00
DHR60
925cf16c50 Adds sing-box fragment support (#7729) 2025-08-10 13:39:51 +08:00
DHR60
c561916b67 Fixes select proxy outbound server (#7727) 2025-08-10 11:58:07 +08:00
DHR60
d41a73b44b Simplify DNS Settings (#7572)
* Simplify DNS Settings

* fix

* ExpectedIPs

* Optimize ExpectedIPs Logic

* Fixes geoip overrides rule_set when geosite is also set

* rename DNSItem to SimpleDNSItem

* Compatible

* Fixes Combobox for desktop

* Regional Preset

* Fix

* Refactor DNS tags handling

* Uses correct DNS strategy for direct connections

* auto-disable DNS items with empty NormalDNS on startup
2025-08-10 11:57:42 +08:00
DHR60
7995bdd4df Migrate to sing-box 1.12 support (#7521)
* Revert "Temporary addition to support proper use of sing-box v1.12"

This reverts commit 508eb24fc3.

* Migrating to singbox 1.11 support

* Removes unnecessary sniffer

* Migrating to singbox 1.12 support

* Adds Google cn dns rules

* Improves geoip rule handling in singbox

* add anytls support

* Simplifies local DNS address handling

* Enables dhcp interface configuration

* Fetches DNS strategy for domain resolution

* support Wireguard endpoint
Refactors Singbox config classes for dial fields

* Utils.GetFreePort() default port to be zero

* Adds Sing-box legacy DNS config support

* Adds IPv4 preference to DNS configurations

对应原dns.servers[].strategy = prefer_ipv4

* Refactors DNS address parsing

* Fixes config generation

* fix singbox endpoints proxy chain not work

* Fixes wrong field

* Removes direct clash_mode domain strategy

* Improves DNS address parsing in Singbox

DNS type, host, port, and path

* Adds properties to Rule4Sbox class

* Removes Wireguard listen port

* Support sing-box hosts

* Adds tag resolver supports

* Adds sing-box DomainStrategy support

* Deletes Duplicate Rules

* Adds anytls reality support

* Fixes

* Updates sing-box documentation link

* Updates translations
2025-08-10 10:15:32 +08:00
2dust
df95cc6af7 Code clean 2025-08-10 09:17:15 +08:00
2dust
c669e72189 up 7.13.7 2025-08-07 13:35:16 +08:00
2dust
46db5efef3 Update Directory.Packages.props 2025-08-07 13:30:36 +08:00
2dust
610418b42b In the Desktop version, the information box uses SelectableTextBlock to replace TextBox
https://github.com/2dust/v2rayN/issues/7644
2025-08-06 21:01:06 +08:00
2dust
508eb24fc3 Temporary addition to support proper use of sing-box v1.12
https://github.com/2dust/v2rayN/issues/7698
2025-08-05 19:31:48 +08:00
2dust
6973272dd0 up 7.13.6 2025-08-03 10:49:28 +08:00
2dust
d820c4367e Add Mldsa65Verify,Xray-core v25.7.26+
https://github.com/XTLS/Xray-core/pull/4915
2025-08-03 10:34:21 +08:00
Internetezoo
96e1f85d6f Update ResUI.hu.resx (#7679) 2025-08-02 21:06:14 +08:00
2dust
6fa5ca5aa9 Revert "Fix missing hysteria2 arguments (#7673)"
This reverts commit 3f79df21d9.
2025-08-01 12:16:19 +08:00
239 changed files with 16336 additions and 6799 deletions

View File

@@ -22,17 +22,17 @@ jobs:
matrix:
configuration: [Release]
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v4.3.1
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: '8.0.x'
@@ -98,4 +98,38 @@ jobs:
file: ${{ github.workspace }}/v2rayN*.zip
tag: ${{ github.event.inputs.release_tag }}
file_glob: true
prerelease: true
prerelease: true
# release RHEL package
- name: Package RPM (RHEL-family)
if: github.event.inputs.release_tag != ''
run: |
chmod 755 package-rhel.sh
# Build for both x86_64 and aarch64 in one go (explicit version passed; no --buildfrom)
./package-rhel.sh "${{ github.event.inputs.release_tag }}" --arch all
- name: Collect RPMs into workspace
if: github.event.inputs.release_tag != ''
run: |
mkdir -p "${{ github.workspace }}/dist/rpm"
rsync -av "$HOME/rpmbuild/RPMS/" "${{ github.workspace }}/dist/rpm/"
# Rename to requested filenames
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.x86_64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-x64.rpm" \; || true
find "${{ github.workspace }}/dist/rpm" -name "v2rayN-*-1.aarch64.rpm" -exec mv {} "${{ github.workspace }}/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
- name: Upload RPM artifacts
if: github.event.inputs.release_tag != ''
uses: actions/upload-artifact@v4.6.2
with:
name: v2rayN-rpm
path: |
${{ github.workspace }}/dist/rpm/**/*.rpm
- name: Upload RPMs to release
uses: svenstaro/upload-release-action@v2
if: github.event.inputs.release_tag != ''
with:
file: ${{ github.workspace }}/dist/rpm/**/*.rpm
tag: ${{ github.event.inputs.release_tag }}
file_glob: true
prerelease: true

View File

@@ -26,13 +26,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v4.3.1
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: '8.0.x'

View File

@@ -26,13 +26,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
with:
submodules: 'recursive'
fetch-depth: '0'
- name: Setup
uses: actions/setup-dotnet@v4.3.1
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: '8.0.x'

View File

@@ -27,10 +27,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Setup
uses: actions/setup-dotnet@v4.3.1
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: '8.0.x'

3
.gitignore vendored
View File

@@ -397,4 +397,5 @@ FodyWeavers.xsd
*.msp
# JetBrains Rider
*.sln.iml
.idea/
*.sln.iml

View File

@@ -1,14 +1,67 @@
#!/bin/bash
set -euo pipefail
# Install deps
sudo apt update -y
sudo apt install -y libfuse2
wget -O pkg2appimage https://github.com/AppImageCommunity/pkg2appimage/releases/download/continuous/pkg2appimage-1eceb30-x86_64.AppImage
chmod a+x pkg2appimage
export AppImageOutputArch=$OutputArch
export OutputPath=$OutputPath64
./pkg2appimage ./pkg2appimage.yml
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage
export AppImageOutputArch=$OutputArchArm
export OutputPath=$OutputPathArm64
./pkg2appimage ./pkg2appimage.yml
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage
sudo apt install -y libfuse2 wget file
# Get tools
wget -qO appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool
# x86_64 AppDir
APPDIR_X64="AppDir-x86_64"
rm -rf "$APPDIR_X64"
mkdir -p "$APPDIR_X64/usr/lib/v2rayN" "$APPDIR_X64/usr/bin" "$APPDIR_X64/usr/share/applications" "$APPDIR_X64/usr/share/pixmaps"
cp -rf "$OutputPath64"/* "$APPDIR_X64/usr/lib/v2rayN" || true
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/usr/share/pixmaps/v2rayN.png" || true
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/v2rayN.png" || true
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_X64/AppRun"
chmod +x "$APPDIR_X64/AppRun"
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_X64/usr/bin/v2rayN"
cat > "$APPDIR_X64/v2rayN.desktop" <<EOF
[Desktop Entry]
Name=v2rayN
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
Exec=v2rayN
Icon=v2rayN
Terminal=false
Type=Application
Categories=Network;
EOF
install -Dm644 "$APPDIR_X64/v2rayN.desktop" "$APPDIR_X64/usr/share/applications/v2rayN.desktop"
ARCH=x86_64 ./appimagetool "$APPDIR_X64" "v2rayN-${OutputArch}.AppImage"
file "v2rayN-${OutputArch}.AppImage" | grep -q 'x86-64'
# aarch64 AppDir
APPDIR_ARM64="AppDir-aarch64"
rm -rf "$APPDIR_ARM64"
mkdir -p "$APPDIR_ARM64/usr/lib/v2rayN" "$APPDIR_ARM64/usr/bin" "$APPDIR_ARM64/usr/share/applications" "$APPDIR_ARM64/usr/share/pixmaps"
cp -rf "$OutputPathArm64"/* "$APPDIR_ARM64/usr/lib/v2rayN" || true
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/usr/share/pixmaps/v2rayN.png" || true
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/v2rayN.png" || true
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_ARM64/AppRun"
chmod +x "$APPDIR_ARM64/AppRun"
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_ARM64/usr/bin/v2rayN"
cat > "$APPDIR_ARM64/v2rayN.desktop" <<EOF
[Desktop Entry]
Name=v2rayN
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
Exec=v2rayN
Icon=v2rayN
Terminal=false
Type=Application
Categories=Network;
EOF
install -Dm644 "$APPDIR_ARM64/v2rayN.desktop" "$APPDIR_ARM64/usr/share/applications/v2rayN.desktop"
# aarch64 runtime
wget -qO runtime-aarch64 https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-aarch64
chmod +x runtime-aarch64
# build aarch64 AppImage
ARCH=aarch64 ./appimagetool --runtime-file ./runtime-aarch64 "$APPDIR_ARM64" "v2rayN-${OutputArchArm}.AppImage"
file "v2rayN-${OutputArchArm}.AppImage" | grep -q 'ARM aarch64'

View File

@@ -28,6 +28,7 @@ Package: v2rayN
Version: $Version
Architecture: $Arch2
Maintainer: https://github.com/2dust/v2rayN
Depends: desktop-file-utils, xdg-utils
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
EOF
@@ -52,7 +53,17 @@ sudo chmod 0755 "${PackagePath}/DEBIAN/postinst"
sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN"
sudo chmod 0755 "${PackagePath}/opt/v2rayN/AmazTool"
# desktop && PATH
# Patch
# set owner to root:root
sudo chown -R root:root "${PackagePath}"
# set all directories to 755 (readable & traversable by all users)
sudo find "${PackagePath}/opt/v2rayN" -type d -exec chmod 755 {} +
# set all regular files to 644 (readable by all users)
sudo find "${PackagePath}/opt/v2rayN" -type f -exec chmod 644 {} +
# ensure main binaries are 755 (executable by all users)
sudo chmod 755 "${PackagePath}/opt/v2rayN/v2rayN" 2>/dev/null || true
sudo chmod 755 "${PackagePath}/opt/v2rayN/AmazTool" 2>/dev/null || true
# build deb package
sudo dpkg-deb -Zxz --build $PackagePath
sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb"
sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb"

808
package-rhel.sh Normal file
View File

@@ -0,0 +1,808 @@
#!/usr/bin/env bash
set -euo pipefail
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
if [[ -r /etc/os-release ]]; then
. /etc/os-release
case "$ID" in
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
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."
exit 1
;;
esac
else
echo "[ERROR] Cannot detect system (missing /etc/os-release)."
exit 1
fi
# ===== Config & Parse arguments =========================================================
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
WITH_CORE="both" # Default: bundle both xray+sing-box
AUTOSTART=0 # 1 = enable system-wide autostart (/etc/xdg/autostart)
FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads
ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target)
BUILD_FROM="" # --buildfrom 1|2|3 to select channel non-interactively
# If the first argument starts with --, do not treat it as a version number
if [[ "${VERSION_ARG:-}" == --* ]]; then
VERSION_ARG=""
fi
# Take the first non --* argument as version, discard it
if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi
# Parse remaining optional arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--with-core) WITH_CORE="${2:-both}"; shift 2;;
--autostart) AUTOSTART=1; shift;;
--xray-ver) XRAY_VER="${2:-}"; shift 2;;
--singbox-ver) SING_VER="${2:-}"; shift 2;;
--netcore) FORCE_NETCORE=1; shift;;
--arch) ARCH_OVERRIDE="${2:-}"; shift 2;;
--buildfrom) BUILD_FROM="${2:-}"; shift 2;;
*)
if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi
shift;;
esac
done
# Conflict: version number AND --buildfrom cannot be used together
if [[ -n "${VERSION_ARG:-}" && -n "${BUILD_FROM:-}" ]]; then
echo "[ERROR] You cannot specify both an explicit version and --buildfrom at the same time."
echo " Provide either a version (e.g. 7.14.0) OR --buildfrom 1|2|3."
exit 1
fi
# ===== Environment check + Dependencies ========================================
host_arch="$(uname -m)"
[[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; }
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 || \
sudo dnf -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
install_ok=1
elif command -v yum >/dev/null 2>&1; then
sudo yum -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
sudo yum -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync
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
if [[ "$install_ok" -ne 1 ]]; then
echo "[WARN] Could not auto-install dependencies for '$ID'. Make sure these are available:"
echo " dotnet-sdk 8.x, curl, unzip, tar, rsync, rpm, rpmdevtools, rpm-build (on RPM-based distros)"
fi
command -v curl >/dev/null
# Root directory = the script's location
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Git submodules (best effort)
if [[ -f .gitmodules ]]; then
git submodule sync --recursive || true
git submodule update --init --recursive || true
fi
# ===== 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 ============================================
VERSION=""
choose_channel() {
# If --buildfrom provided, map it directly and skip interaction.
if [[ -n "${BUILD_FROM:-}" ]]; then
case "$BUILD_FROM" in
1) echo "latest"; return 0;;
2) echo "prerelease"; return 0;;
3) echo "keep"; return 0;;
*) echo "[ERROR] Invalid --buildfrom value: ${BUILD_FROM}. Use 1|2|3." >&2; exit 1;;
esac
fi
# Print menu to stderr and read from /dev/tty so stdout only carries the token.
local ch="latest" sel=""
if [[ -t 0 ]]; then
echo "[?] Choose v2rayN release channel:" >&2
echo " 1) Latest (stable) [default]" >&2
echo " 2) Pre-release (preview)" >&2
echo " 3) Keep current (do nothing)" >&2
printf "Enter 1, 2 or 3 [default 1]: " >&2
if read -r sel </dev/tty; then
case "${sel:-}" in
2) ch="prerelease" ;;
3) ch="keep" ;;
*) ch="latest" ;;
esac
else
ch="latest"
fi
else
ch="latest"
fi
echo "$ch"
}
get_latest_tag_latest() {
# Resolve /releases/latest → tag_name
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \
| grep -Eo '"tag_name":\s*"v?[^"]+"' \
| head -n1 \
| sed -E 's/.*"tag_name":\s*"v?([^"]+)".*/\1/'
}
get_latest_tag_prerelease() {
# Resolve newest prerelease=true tag; prefer jq, fallback to sed/grep (no awk)
local json tag
json="$(curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20")" || return 1
# 1) Use jq if present
if command -v jq >/dev/null 2>&1; then
tag="$(printf '%s' "$json" \
| jq -r '[.[] | select(.prerelease==true)][0].tag_name' 2>/dev/null \
| sed 's/^v//')" || true
fi
# 2) Fallback to sed/grep only
if [[ -z "${tag:-}" || "${tag:-}" == "null" ]]; then
tag="$(printf '%s' "$json" \
| tr '\n' ' ' \
| sed 's/},[[:space:]]*{/\n/g' \
| grep -m1 -E '"prerelease"[[:space:]]*:[[:space:]]*true' \
| grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"v?[^"]+"' \
| head -n1 \
| sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/')" || true
fi
[[ -n "${tag:-}" && "${tag:-}" != "null" ]] || return 1
printf '%s\n' "$tag"
}
git_try_checkout() {
# Try a series of refs and checkout when found.
local want="$1" ref=""
if git rev-parse --git-dir >/dev/null 2>&1; then
git fetch --tags --force --prune --depth=1 || true
if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then
ref="v${want}"
elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then
ref="${want}"
elif git rev-parse --verify "${want}" >/dev/null 2>&1; then
ref="${want}"
fi
if [[ -n "$ref" ]]; then
echo "[OK] Found ref '${ref}', checking out..."
git checkout -f "${ref}"
if [[ -f .gitmodules ]]; then
git submodule sync --recursive || true
git submodule update --init --recursive || true
fi
return 0
fi
fi
return 1
}
if git rev-parse --git-dir >/dev/null 2>&1; then
if [[ -n "${VERSION_ARG:-}" ]]; then
echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}"
if git_try_checkout "${VERSION_ARG#v}"; then
VERSION="${VERSION_ARG#v}"
else
echo "[WARN] Tag '${VERSION_ARG}' not found."
ch="$(choose_channel)"
if [[ "$ch" == "keep" ]]; then
echo "[*] Keep current repository state (no checkout)."
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
VERSION="$(git describe --tags --abbrev=0)"
else
VERSION="0.0.0+git"
fi
VERSION="${VERSION#v}"
else
echo "[*] Resolving ${ch} tag from GitHub releases..."
tag=""
if [[ "$ch" == "prerelease" ]]; then
tag="$(get_latest_tag_prerelease || true)"
if [[ -z "$tag" ]]; then
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
tag="$(get_latest_tag_latest || true)"
fi
else
tag="$(get_latest_tag_latest || true)"
fi
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
echo "[*] Latest tag for '${ch}': ${tag}"
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
VERSION="${tag#v}"
fi
fi
else
ch="$(choose_channel)"
if [[ "$ch" == "keep" ]]; then
echo "[*] Keep current repository state (no checkout)."
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
VERSION="$(git describe --tags --abbrev=0)"
else
VERSION="0.0.0+git"
fi
VERSION="${VERSION#v}"
else
echo "[*] Resolving ${ch} tag from GitHub releases..."
tag=""
if [[ "$ch" == "prerelease" ]]; then
tag="$(get_latest_tag_prerelease || true)"
if [[ -z "$tag" ]]; then
echo "[WARN] Failed to resolve prerelease tag, falling back to latest."
tag="$(get_latest_tag_latest || true)"
fi
else
tag="$(get_latest_tag_latest || true)"
fi
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
echo "[*] Latest tag for '${ch}': ${tag}"
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
VERSION="${tag#v}"
fi
fi
else
echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree."
VERSION="${VERSION_ARG:-}"
if [[ -z "$VERSION" ]]; then
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
VERSION="$(git describe --tags --abbrev=0)"
else
VERSION="0.0.0+git"
fi
fi
VERSION="${VERSION#v}"
fi
echo "[*] GUI version resolved as: ${VERSION}"
# ===== Helpers for core/rules download (use RID_DIR for arch sync) =====================
download_xray() {
# Download Xray core and install to outdir/xray
local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip"
mkdir -p "$outdir"
if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi
if [[ -z "$ver" ]]; then
ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
fi
[[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; }
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip"
else
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip"
fi
echo "[+] Download xray: $url"
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fL "$url" -o "$tmp/$zipname"
unzip -q "$tmp/$zipname" -d "$tmp"
install -Dm755 "$tmp/xray" "$outdir/xray"
}
download_singbox() {
# Download sing-box core and install to outdir/sing-box
local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin
mkdir -p "$outdir"
if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi
if [[ -z "$ver" ]]; then
ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
fi
[[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; }
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz"
else
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz"
fi
echo "[+] Download sing-box: $url"
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fL "$url" -o "$tmp/$tarname"
tar -C "$tmp" -xzf "$tmp/$tarname"
bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)"
[[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; return 1; }
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"
mkdir -p "$outroot/bin"
local names=( \
"geosite.dat" \
"geoip.dat" \
"geoip-only-cn-private.dat" \
"Country.mmdb" \
"geoip.metadb" \
)
for n in "${names[@]}"; do
# If file exists under bin/xray/, move it up to bin/
if [[ -f "$outroot/bin/xray/$n" ]]; then
mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n"
fi
# If file already in bin/, leave it as-is
if [[ -f "$outroot/bin/$n" ]]; then
:
fi
done
}
# Download geo/rule assets; then unify to bin/
download_geo_assets() {
local outroot="$1"
local bin_dir="$outroot/bin"
local srss_dir="$bin_dir/srss"
mkdir -p "$bin_dir" "$srss_dir"
echo "[+] Download Xray Geo to ${bin_dir}"
curl -fsSL -o "$bin_dir/geosite.dat" \
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat"
curl -fsSL -o "$bin_dir/geoip.dat" \
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat"
curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat"
curl -fsSL -o "$bin_dir/Country.mmdb" \
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
echo "[+] Download sing-box rule DB & rule-sets"
curl -fsSL -o "$bin_dir/geoip.metadb" \
"https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true
for f in \
geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \
geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do
curl -fsSL -o "$srss_dir/$f" \
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
done
for f in \
geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
curl -fsSL -o "$srss_dir/$f" \
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
done
# Unify to bin/
unify_geo_layout "$outroot"
}
# Prefer the prebuilt v2rayN core bundle; then unify geo layout
download_v2rayn_bundle() {
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.zip"
else
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip"
fi
echo "[+] Try v2rayN bundle archive: $url"
local tmp zipname
tmp="$(mktemp -d)"; zipname="$tmp/v2rayn.zip"
curl -fL "$url" -o "$zipname" || { echo "[!] Bundle download failed"; return 1; }
unzip -q "$zipname" -d "$tmp" || { echo "[!] Bundle unzip failed"; return 1; }
if [[ -d "$tmp/bin" ]]; then
mkdir -p "$outroot/bin"
rsync -a "$tmp/bin/" "$outroot/bin/"
else
rsync -a "$tmp/" "$outroot/"
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
local nested_dir
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
if [[ -n "${nested_dir:-}" && -d "$nested_dir/bin" ]]; then
mkdir -p "$outroot/bin"
rsync -a "$nested_dir/bin/" "$outroot/bin/"
rm -rf "$nested_dir"
fi
# Unify to bin/
unify_geo_layout "$outroot"
echo "[+] Bundle extracted to $outroot"
}
# ===== Build results collection for --arch all ========================================
BUILT_RPMS=() # Will collect absolute paths of built RPMs
BUILT_ALL=0 # Flag to know if we should print the final summary
# ===== Build (single-arch) function ====================================================
build_for_arch() {
# $1: target short arch: x64 | arm64
local short="$1"
local rid rpm_target archdir
case "$short" in
x64) rid="linux-x64"; rpm_target="x86_64"; archdir="x86_64" ;;
arm64) rid="linux-arm64"; rpm_target="aarch64"; archdir="aarch64" ;;
*) echo "[ERROR] Unknown arch '$short' (use x64|arm64)"; return 1;;
esac
echo "[*] Building for target: $short (RID=$rid, RPM --target $rpm_target)"
# .NET publish (self-contained) for this RID
dotnet clean "$PROJECT" -c Release
rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true
dotnet restore "$PROJECT"
dotnet publish "$PROJECT" \
-c Release -r "$rid" \
-p:PublishSingleFile=false \
-p:SelfContained=true \
-p:IncludeNativeLibrariesForSelfExtract=true
# Per-arch variables (scoped)
local RID_DIR="$rid"
local PUBDIR
PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish"
[[ -d "$PUBDIR" ]]
# Make RID_DIR visible to download helpers (they read this var)
export RID_DIR
# Per-arch working area
local PKGROOT="v2rayN-publish"
local WORKDIR
WORKDIR="$(mktemp -d)"
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
# Stage publish content
mkdir -p "$WORKDIR/$PKGROOT"
cp -a "$PUBDIR/." "$WORKDIR/$PKGROOT/"
# Optional icon
local ICON_CANDIDATE
ICON_CANDIDATE="$(dirname "$PROJECT")/../v2rayN.Desktop/v2rayN.png"
[[ -f "$ICON_CANDIDATE" ]] && cp "$ICON_CANDIDATE" "$WORKDIR/$PKGROOT/v2rayn.png" || true
# Prepare bin structure
mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box"
# Bundle / cores per-arch
if [[ "$FORCE_NETCORE" -eq 0 ]]; then
if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then
echo "[*] Using v2rayN bundle archive."
else
echo "[*] Bundle failed, fallback to separate core + rules."
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
fi
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
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)"
fi
else
echo "[*] --netcore specified: use separate core + rules."
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
fi
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
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
mkdir -p "$SOURCEDIR"
tar -C "$WORKDIR" -czf "$SOURCEDIR/$PKGROOT.tar.gz" "$PKGROOT"
# SPEC
local SPECFILE="$SPECDIR/v2rayN.spec"
mkdir -p "$SPECDIR"
cat > "$SPECFILE" <<'SPEC'
%global debug_package %{nil}
%undefine _debuginfo_subpackages
%undefine _debugsource_packages
# Ignore outdated LTTng dependencies incorrectly reported by the .NET runtime (to avoid installation failures)
%global __requires_exclude ^liblttng-ust\.so\..*$
Name: v2rayN
Version: __VERSION__
Release: 1%{?dist}
Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64)
License: GPL-3.0-only
URL: https://github.com/2dust/v2rayN
BugURL: https://github.com/2dust/v2rayN/issues
ExclusiveArch: aarch64 x86_64
Source0: __PKGROOT__.tar.gz
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL, xdg-utils
%description
v2rayN Linux for Red Hat Enterprise Linux
Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / Shadowsocks / tuic / WireGuard
Support Red Hat Enterprise Linux / Fedora Linux / Rocky Linux / AlmaLinux / CentOS
For more information, Please visit our website
https://github.com/2dust/v2rayN
%prep
%setup -q -n __PKGROOT__
%build
# no build
%install
install -dm0755 %{buildroot}/opt/v2rayN
cp -a * %{buildroot}/opt/v2rayN/
# Launcher (prefer native ELF first, then DLL fallback)
install -dm0755 %{buildroot}%{_bindir}
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
#!/usr/bin/bash
set -euo pipefail
DIR="/opt/v2rayN"
# Prefer native apphost
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi
# DLL fallback
for dll in v2rayN.Desktop.dll v2rayN.dll; do
if [[ -f "$DIR/$dll" ]]; then exec /usr/bin/dotnet "$DIR/$dll" "$@"; fi
done
echo "v2rayN launcher: no executable found in $DIR" >&2
ls -l "$DIR" >&2 || true
exit 1
EOF
chmod 0755 %{buildroot}%{_bindir}/v2rayn
# Desktop file
install -dm0755 %{buildroot}%{_datadir}/applications
cat > %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF'
[Desktop Entry]
Type=Application
Name=v2rayN
Comment=v2rayN for Red Hat Enterprise Linux
Exec=v2rayn
Icon=v2rayn
Terminal=false
Categories=Network;
EOF
# Icon
if [ -f "%{_builddir}/__PKGROOT__/v2rayn.png" ]; then
install -dm0755 %{buildroot}%{_datadir}/icons/hicolor/256x256/apps
install -m0644 %{_builddir}/__PKGROOT__/v2rayn.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
fi
%post
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
%postun
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
%files
%{_bindir}/v2rayn
/opt/v2rayN
%{_datadir}/applications/v2rayn.desktop
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
SPEC
# Autostart injection (inside %install) and %files entry
if [[ "$AUTOSTART" -eq 1 ]]; then
awk '
BEGIN{ins=0}
/^%post$/ && !ins {
print "# --- Autostart (.desktop) ---"
print "install -dm0755 %{buildroot}/etc/xdg/autostart"
print "cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << '\''EOF'\''"
print "[Desktop Entry]"
print "Type=Application"
print "Name=v2rayN (Autostart)"
print "Exec=v2rayn"
print "X-GNOME-Autostart-enabled=true"
print "NoDisplay=false"
print "EOF"
ins=1
}
{print}
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
awk '
BEGIN{infiles=0; done=0}
/^%files$/ {infiles=1}
infiles && done==0 && $0 ~ /%{_datadir}\/icons\/hicolor\/256x256\/apps\/v2rayn\.png/ {
print
print "%config(noreplace) /etc/xdg/autostart/v2rayn.desktop"
done=1
next
}
{print}
' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE"
fi
# Replace placeholders
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
echo "Build done for $short. RPM at:"
local f
for f in "${TOPDIR}/RPMS/${archdir}/v2rayN-${VERSION}-1"*.rpm; do
[[ -e "$f" ]] || continue
echo " $f"
BUILT_RPMS+=("$f")
done
}
# ===== Arch selection and build orchestration =========================================
case "${ARCH_OVERRIDE:-}" in
"")
# No --arch: use host architecture
if [[ "$host_arch" == "aarch64" ]]; then
build_for_arch arm64
else
build_for_arch x64
fi
;;
x64|amd64)
build_for_arch x64
;;
arm64|aarch64)
build_for_arch arm64
;;
all)
BUILT_ALL=1
# Build x64 and arm64 separately; each package contains its own arch-only binaries.
build_for_arch x64
build_for_arch arm64
;;
*)
echo "[ERROR] Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."
exit 1
;;
esac
# ===== Final summary if building both arches ==========================================
if [[ "$BUILT_ALL" -eq 1 ]]; then
echo ""
echo "================ Build Summary (both architectures) ================"
if [[ "${#BUILT_RPMS[@]}" -gt 0 ]]; then
for rp in "${BUILT_RPMS[@]}"; do
echo "$rp"
done
else
echo "[WARN] No RPMs detected in summary (check build logs above)."
fi
echo "==================================================================="
fi

View File

@@ -1,37 +0,0 @@
app: v2rayN
binpatch: true
ingredients:
script:
- export FileName="v2rayN-${AppImageOutputArch}.zip"
- wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/${FileName}"
- 7z x $FileName -aoa
- cp -rf v2rayN-${AppImageOutputArch}/* $OutputPath
script:
- mkdir -p usr/bin usr/lib
- cp -rf $OutputPath usr/lib/v2rayN
- echo "When this file exists, app will not store configs under this folder" > usr/lib/v2rayN/NotStoreConfigHere.txt
- ln -sf usr/lib/v2rayN/v2rayN usr/bin/v2rayN
- chmod a+x usr/lib/v2rayN/v2rayN
- find usr -type f -exec sh -c 'file "{}" | grep -qi "executable" && chmod +x "{}"' \;
- install -Dm644 usr/lib/v2rayN/v2rayN.png v2rayN.png
- install -Dm644 usr/lib/v2rayN/v2rayN.png usr/share/pixmaps/v2rayN.png
- cat > v2rayN.desktop <<EOF
- [Desktop Entry]
- Name=v2rayN
- Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
- Exec=v2rayN
- Icon=v2rayN
- Terminal=false
- Type=Application
- Categories=Network;
- EOF
- install -Dm644 v2rayN.desktop usr/share/applications/v2rayN.desktop
- cat > AppRun <<\EOF
- #!/bin/sh
- HERE="$(dirname "$(readlink -f "${0}")")"
- cd ${HERE}/usr/lib/v2rayN
- exec ${HERE}/usr/lib/v2rayN/v2rayN $@
- EOF
- chmod a+x AppRun

View File

@@ -79,15 +79,7 @@ internal class UpgradeApp
continue;
}
try
{
entry.ExtractToFile(entryOutputPath, true);
}
catch
{
Thread.Sleep(1000);
entry.ExtractToFile(entryOutputPath, true);
}
TryExtractToFile(entry, entryOutputPath);
Console.WriteLine(entryOutputPath);
}
@@ -113,4 +105,24 @@ internal class UpgradeApp
Utils.StartV2RayN();
}
private static bool TryExtractToFile(ZipArchiveEntry entry, string outputPath)
{
var retryCount = 5;
var delayMs = 1000;
for (var i = 1; i <= retryCount; i++)
{
try
{
entry.ExtractToFile(outputPath, true);
return true;
}
catch
{
Thread.Sleep(delayMs * i);
}
}
return false;
}
}

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.13.5</Version>
<Version>7.15.3</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -5,22 +5,24 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.2" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.2" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.2" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.2" />
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.7" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.7" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.7" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.7" />
<PackageVersion Include="CliWrap" Version="3.9.0" />
<PackageVersion Include="Downloader" Version="4.0.2" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
<PackageVersion Include="MaterialDesignThemes" Version="5.2.1" />
<PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
<PackageVersion Include="QRCoder" Version="1.6.0" />
<PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="ReactiveUI" Version="20.4.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
<PackageVersion Include="Splat.NLog" Version="15.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.7" />
<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.5" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" />

View File

@@ -84,4 +84,14 @@ public static class Extension
{
return source.Concat(new[] { string.Empty }).ToList();
}
public static bool IsGroupType(this EConfigType configType)
{
return configType is EConfigType.PolicyGroup or EConfigType.ProxyChain;
}
public static bool IsComplexType(this EConfigType configType)
{
return configType is EConfigType.Custom or EConfigType.PolicyGroup or EConfigType.ProxyChain;
}
}

View File

@@ -9,6 +9,31 @@ public class JsonUtils
{
private static readonly string _tag = "JsonUtils";
private static readonly JsonSerializerOptions _defaultDeserializeOptions = new()
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
private static readonly JsonSerializerOptions _defaultSerializeOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonSerializerOptions _nullValueSerializeOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonDocumentOptions _defaultDocumentOptions = new()
{
CommentHandling = JsonCommentHandling.Skip
};
/// <summary>
/// DeepCopy
/// </summary>
@@ -34,11 +59,7 @@ public class JsonUtils
{
return default;
}
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<T>(strJson, options);
return JsonSerializer.Deserialize<T>(strJson, _defaultDeserializeOptions);
}
catch
{
@@ -59,7 +80,7 @@ public class JsonUtils
{
return null;
}
return JsonNode.Parse(strJson);
return JsonNode.Parse(strJson, nodeOptions: null, _defaultDocumentOptions);
}
catch
{
@@ -84,12 +105,7 @@ public class JsonUtils
{
return result;
}
var options = new JsonSerializerOptions
{
WriteIndented = indented,
DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions;
result = JsonSerializer.Serialize(obj, options);
}
catch (Exception ex)
@@ -128,5 +144,8 @@ public class JsonUtils
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static JsonNode? SerializeToNode(object? obj) => JsonSerializer.SerializeToNode(obj);
public static JsonNode? SerializeToNode(object? obj, JsonSerializerOptions? options = null)
{
return JsonSerializer.SerializeToNode(obj, options);
}
}

View File

@@ -67,116 +67,4 @@ public static class ProcUtils
Logging.SaveLog(_tag, ex);
}
}
public static async Task ProcessKill(int pid)
{
try
{
await ProcessKill(Process.GetProcessById(pid), false);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
public static async Task ProcessKill(Process? proc, bool review)
{
if (proc is null)
{
return;
}
GetProcessKeyInfo(proc, review, out var procId, out var fileName, out var processName);
try
{
if (Utils.IsNonWindows())
{
proc?.Kill(true);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
try
{
proc?.Kill();
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
try
{
proc?.Close();
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
try
{
proc?.Dispose();
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
await Task.Delay(300);
await ProcessKillByKeyInfo(review, procId, fileName, processName);
}
private static void GetProcessKeyInfo(Process? proc, bool review, out int? procId, out string? fileName, out string? processName)
{
procId = null;
fileName = null;
processName = null;
if (!review)
{
return;
}
try
{
procId = proc?.Id;
fileName = proc?.MainModule?.FileName;
processName = proc?.ProcessName;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private static async Task ProcessKillByKeyInfo(bool review, int? procId, string? fileName, string? processName)
{
if (review && procId != null && fileName != null)
{
try
{
var lstProc = Process.GetProcessesByName(processName);
foreach (var proc2 in lstProc)
{
if (proc2.Id == procId)
{
Logging.SaveLog($"{_tag}, KillProcess not completing the job, procId");
await ProcessKill(proc2, false);
}
if (proc2.MainModule != null && proc2.MainModule?.FileName == fileName)
{
Logging.SaveLog($"{_tag}, KillProcess not completing the job, fileName");
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
}
}

View File

@@ -1,17 +1,53 @@
using QRCoder;
using QRCoder.Exceptions;
using SkiaSharp;
using ZXing.SkiaSharp;
namespace ServiceLib.Common;
public class QRCodeHelper
public class QRCodeUtils
{
public static byte[]? GenQRCode(string? url)
{
if (url.IsNullOrEmpty())
{
return null;
}
using QRCodeGenerator qrGenerator = new();
using var qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q);
using PngByteQRCode qrCode = new(qrCodeData);
return qrCode.GetGraphic(20);
DataTooLongException? lastDtle = null;
var levels = new[]
{
QRCodeGenerator.ECCLevel.H,
QRCodeGenerator.ECCLevel.Q,
QRCodeGenerator.ECCLevel.M,
QRCodeGenerator.ECCLevel.L
};
foreach (var level in levels)
{
try
{
using var qrCodeData = qrGenerator.CreateQrCode(url, level);
using PngByteQRCode qrCode = new(qrCodeData);
return qrCode.GetGraphic(20);
}
catch (DataTooLongException ex)
{
lastDtle = ex;
continue;
}
catch
{
throw;
}
}
if (lastDtle != null)
{
throw lastDtle;
}
return null;
}
public static string? ParseBarcode(string? fileName)

View File

@@ -85,13 +85,19 @@ public class Utils
/// Base64 Encode
/// </summary>
/// <param name="plainText"></param>
/// <param name="removePadding"></param>
/// <returns></returns>
public static string Base64Encode(string plainText)
public static string Base64Encode(string plainText, bool removePadding = false)
{
try
{
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
return Convert.ToBase64String(plainTextBytes);
var base64 = Convert.ToBase64String(plainTextBytes);
if (removePadding)
{
base64 = base64.TrimEnd('=');
}
return base64;
}
catch (Exception ex)
{
@@ -112,7 +118,7 @@ public class Utils
{
if (plainText.IsNullOrEmpty())
{
return "";
return string.Empty;
}
plainText = plainText.Trim()
@@ -331,6 +337,32 @@ public class Utils
.ToList();
}
public static Dictionary<string, List<string>> ParseHostsToDictionary(string hostsContent)
{
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;
})
// ensure line still contains valid parts
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
.Where(parts => parts.Length >= 2)
.GroupBy(parts => parts[0])
.ToDictionary(
group => group.Key,
group => group.SelectMany(parts => parts.Skip(1)).ToList()
);
return userHostsMap;
}
#endregion
#region
@@ -411,11 +443,11 @@ public class Utils
// Link-local address fe80::/10
if (ipBytes[0] == 0xfe && (ipBytes[1] & 0xc0) == 0x80)
return true;
// Unique local address fc00::/7 (typically fd00::/8)
if ((ipBytes[0] & 0xfe) == 0xfc)
return true;
// Private portion in IPv4-mapped addresses ::ffff:0:0/96
if (address.IsIPv4MappedToIPv6)
{
@@ -466,11 +498,11 @@ public class Utils
return false;
}
public static int GetFreePort(int defaultPort = 9090)
public static int GetFreePort(int defaultPort = 0)
{
try
{
if (!Utils.PortInUse(defaultPort))
if (!(defaultPort == 0 || Utils.PortInUse(defaultPort)))
{
return defaultPort;
}
@@ -582,9 +614,9 @@ public class Utils
if (host.StartsWith("#"))
continue;
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (hostItem.Length != 2)
if (hostItem.Length < 2)
continue;
systemHosts.Add(hostItem.Last(), hostItem.First());
systemHosts.Add(hostItem[1], hostItem[0]);
}
}
}
@@ -857,6 +889,55 @@ public class Utils
return false;
}
public static bool IsPackagedInstall()
{
try
{
if (IsWindows() || IsOSX())
{
return false;
}
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE")))
{
return true;
}
var exePath = GetExePath();
var baseDir = string.IsNullOrEmpty(exePath) ? StartupPath() : Path.GetDirectoryName(exePath) ?? "";
var p = baseDir.Replace('\\', '/');
if (string.IsNullOrEmpty(p))
{
return false;
}
if (p.Contains("/.mount_", StringComparison.Ordinal))
{
return true;
}
if (p.StartsWith("/opt/v2rayN", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (p.StartsWith("/usr/lib/v2rayN", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (p.StartsWith("/usr/share/v2rayN", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
catch
{
}
return false;
}
private static async Task<string?> GetLinuxUserId()
{
var arg = new List<string>() { "-c", "id -u" };
@@ -872,7 +953,7 @@ public class Utils
if (SetUnixFileMode(fileName))
{
Logging.SaveLog($"Successfully set the file execution permission, {fileName}");
return "";
return string.Empty;
}
if (fileName.Contains(' '))

View File

@@ -7,11 +7,11 @@ namespace ServiceLib.Common;
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
*/
public sealed class Job : IDisposable
public sealed class WindowsJob : IDisposable
{
private IntPtr handle = IntPtr.Zero;
public Job()
public WindowsJob()
{
handle = CreateJobObject(IntPtr.Zero, null);
var extendedInfoPtr = IntPtr.Zero;
@@ -94,7 +94,7 @@ namespace ServiceLib.Common;
}
}
~Job()
~WindowsJob()
{
Dispose(false);
}

View File

@@ -11,5 +11,8 @@ public enum EConfigType
Hysteria2 = 7,
TUIC = 8,
WireGuard = 9,
HTTP = 10
HTTP = 10,
Anytls = 11,
PolicyGroup = 101,
ProxyChain = 102,
}

View File

@@ -15,5 +15,6 @@ public enum ECoreType
brook = 27,
overtls = 28,
shadowquic = 29,
mieru = 30,
v2rayN = 99
}

View File

@@ -1,10 +0,0 @@
namespace ServiceLib.Enums;
public enum EMsgCommand
{
ClearMsg,
SendMsgView,
SendSnackMsg,
RefreshProfiles,
AppExit
}

View File

@@ -2,8 +2,9 @@ namespace ServiceLib.Enums;
public enum EMultipleLoad
{
LeastPing,
Fallback,
Random,
RoundRobin,
LeastPing,
LeastLoad
}

View File

@@ -0,0 +1,8 @@
namespace ServiceLib.Enums;
public enum ERuleType
{
ALL = 0,
Routing = 1,
DNS = 2,
}

View File

@@ -6,17 +6,14 @@ public enum EViewAction
ShowYesNo,
SaveFileDialog,
AddBatchRoutingRulesYesNo,
AdjustMainLvColWidth,
SetClipboardData,
AddServerViaClipboard,
ImportRulesFromClipboard,
ProfilesFocus,
ShareSub,
ShareServer,
ShowHideWindow,
ScanScreenTask,
ScanImageTask,
Shutdown,
BrowseServer,
ImportRulesFromFile,
InitSettingFont,
@@ -26,21 +23,14 @@ public enum EViewAction
RoutingRuleDetailsWindow,
AddServerWindow,
AddServer2Window,
AddGroupServerWindow,
DNSSettingWindow,
RoutingSettingWindow,
OptionSettingWindow,
FullConfigTemplateWindow,
GlobalHotkeySettingWindow,
SubSettingWindow,
DispatcherSpeedTest,
DispatcherRefreshConnections,
DispatcherRefreshProxyGroups,
DispatcherProxiesDelayTest,
DispatcherStatistics,
DispatcherServerAvailability,
DispatcherReload,
DispatcherRefreshServersBiz,
DispatcherRefreshIcon,
DispatcherCheckUpdate,
DispatcherCheckUpdateFinished,
DispatcherShowMsg,
}

View File

@@ -0,0 +1,32 @@
using System.Reactive;
namespace ServiceLib.Events;
public static class AppEvents
{
public static readonly EventChannel<Unit> ReloadRequested = new();
public static readonly EventChannel<bool?> ShowHideWindowRequested = new();
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
public static readonly EventChannel<Unit> ProxiesReloadRequested = new();
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
public static readonly EventChannel<string> SendSnackMsgRequested = new();
public static readonly EventChannel<string> SendMsgViewRequested = new();
public static readonly EventChannel<Unit> AppExitRequested = new();
public static readonly EventChannel<bool> ShutdownRequested = new();
public static readonly EventChannel<Unit> AdjustMainLvColWidthRequested = new();
public static readonly EventChannel<string> SetDefaultServerRequested = new();
public static readonly EventChannel<Unit> RoutingsMenuRefreshRequested = new();
public static readonly EventChannel<Unit> TestServerRequested = new();
public static readonly EventChannel<Unit> InboundDisplayRequested = new();
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
}

View File

@@ -0,0 +1,29 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace ServiceLib.Events;
public sealed class EventChannel<T>
{
private readonly ISubject<T> _subject = Subject.Synchronize(new Subject<T>());
public IObservable<T> AsObservable()
{
return _subject.AsObservable();
}
public void Publish(T value)
{
_subject.OnNext(value);
}
public void Publish()
{
if (typeof(T) != typeof(Unit))
{
throw new InvalidOperationException("Publish() without value is only valid for EventChannel<Unit>.");
}
_subject.OnNext((T)(object)Unit.Default);
}
}

View File

@@ -40,6 +40,7 @@ public class Global
public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh";
public const string KillAsSudoOSXShellFileName = NamespaceSample + "kill_as_sudo_osx_sh";
public const string KillAsSudoLinuxShellFileName = NamespaceSample + "kill_as_sudo_linux_sh";
public const string SingboxFakeIPFilterFileName = NamespaceSample + "singbox_fakeip_filter";
public const string DefaultSecurity = "auto";
public const string DefaultNetwork = "tcp";
@@ -48,6 +49,8 @@ public class Global
public const string ProxyTag = "proxy";
public const string DirectTag = "direct";
public const string BlockTag = "block";
public const string DnsTag = "dns-module";
public const string BalancerTagSuffix = "-round";
public const string StreamSecurity = "tls";
public const string StreamSecurityReality = "reality";
public const string Loopback = "127.0.0.1";
@@ -56,6 +59,9 @@ public class Global
public const string HttpsProtocol = "https://";
public const string SocksProtocol = "socks://";
public const string Socks5Protocol = "socks5://";
public const string AsIs = "AsIs";
public const string IPIfNonMatch = "IPIfNonMatch";
public const string IPOnDemand = "IPOnDemand";
public const string UserEMail = "t@t.tt";
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
@@ -76,6 +82,12 @@ public class Global
public const int SpeedTestPageSize = 1000;
public const string LinuxBash = "/bin/bash";
public const string SingboxDirectDNSTag = "direct_dns";
public const string SingboxRemoteDNSTag = "remote_dns";
public const string SingboxLocalDNSTag = "local_local";
public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns";
public static readonly List<string> IEProxyProtocols =
[
"{ip}:{http_port}",
@@ -129,24 +141,24 @@ public class Global
];
public static readonly List<string> SingboxRulesetSources =
[
"",
@"https://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-rules-dat@release/sing-box/rule-set-{0}/{1}.srs",
@"https://cdn.jsdelivr.net/gh/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://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-custom-routing-list@main/v2rayN/template.json",
@"https://cdn.jsdelivr.net/gh/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://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-custom-routing-list@main/v2rayN/",
@"https://cdn.jsdelivr.net/gh/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()
@@ -169,7 +181,8 @@ public class Global
{ EConfigType.Trojan, "trojan://" },
{ EConfigType.Hysteria2, "hysteria2://" },
{ EConfigType.TUIC, "tuic://" },
{ EConfigType.WireGuard, "wireguard://" }
{ EConfigType.WireGuard, "wireguard://" },
{ EConfigType.Anytls, "anytls://" }
};
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
@@ -182,7 +195,8 @@ public class Global
{ EConfigType.Trojan, "trojan" },
{ EConfigType.Hysteria2, "hysteria2" },
{ EConfigType.TUIC, "tuic" },
{ EConfigType.WireGuard, "wireguard" }
{ EConfigType.WireGuard, "wireguard" },
{ EConfigType.Anytls, "anytls" }
};
public static readonly List<string> VmessSecurities =
@@ -276,11 +290,38 @@ public class Global
"sing_box"
];
public static readonly HashSet<EConfigType> XraySupportConfigType =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
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,
];
public static readonly HashSet<EConfigType> SingboxOnlyConfigType = SingboxSupportConfigType.Except(XraySupportConfigType).ToHashSet();
public static readonly List<string> DomainStrategies =
[
"AsIs",
"IPIfNonMatch",
"IPOnDemand"
AsIs,
IPIfNonMatch,
IPOnDemand
];
public static readonly List<string> DomainStrategies4Singbox =
@@ -292,13 +333,6 @@ public class Global
""
];
public static readonly List<string> DomainMatchers =
[
"linear",
"mph",
""
];
public static readonly List<string> Fingerprints =
[
"chrome",
@@ -349,25 +383,42 @@ public class Global
public static readonly List<string> SingboxDomainStrategy4Out =
[
"ipv4_only",
"",
"ipv4_only",
"prefer_ipv4",
"prefer_ipv6",
"ipv6_only",
""
"ipv6_only"
];
public static readonly List<string> DomainDNSAddress =
public static readonly List<string> DomainDirectDNSAddress =
[
"223.5.5.5",
"223.6.6.6",
"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> SingboxDomainDNSAddress =
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"
];
public static readonly List<string> DomainPureIPDNSAddress =
[
"223.5.5.5",
"223.6.6.6",
"dhcp://auto"
"119.29.29.29",
"localhost"
];
public static readonly List<string> Languages =
@@ -400,6 +451,14 @@ public class Global
"none"
];
public static readonly Dictionary<string, string> LogLevelColors = new()
{
{ "debug", "#6C757D" },
{ "info", "#2ECC71" },
{ "warning", "#FFA500" },
{ "error", "#E74C3C" },
};
public static readonly List<string> InboundTags =
[
"socks",
@@ -434,9 +493,11 @@ public class Global
public static readonly List<int> TunMtus =
[
1280,
1408,
1500,
9000
1408,
1500,
4064,
9000,
65535
];
public static readonly List<string> TunStacks =
@@ -510,6 +571,7 @@ public class Global
{ ECoreType.brook, "txthinking/brook" },
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" },
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
{ ECoreType.mieru, "enfein/mieru" },
{ ECoreType.v2rayN, "2dust/v2rayN" },
};
@@ -537,5 +599,31 @@ public class Global
BlockTag
];
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" } }
};
public static readonly List<string> ExpectedIPs =
[
"geoip:cn",
"geoip:ir",
"geoip:ru",
""
];
#endregion const
}

View File

@@ -1,11 +1,14 @@
global using ServiceLib.Base;
global using ServiceLib.Base;
global using ServiceLib.Common;
global using ServiceLib.Enums;
global using ServiceLib.Events;
global using ServiceLib.Handler;
global using ServiceLib.Helper;
global using ServiceLib.Manager;
global using ServiceLib.Handler.Fmt;
global using ServiceLib.Services;
global using ServiceLib.Services.Statistics;
global using ServiceLib.Services.CoreConfig;
global using ServiceLib.Models;
global using ServiceLib.Resx;
global using ServiceLib.Handler.SysProxy;
global using ServiceLib.Handler.SysProxy;

View File

@@ -3,7 +3,7 @@ using System.Text.RegularExpressions;
namespace ServiceLib.Handler;
public class ConfigHandler
public static class ConfigHandler
{
private static readonly string _configRes = Global.ConfigFileName;
private static readonly string _tag = "ConfigHandler";
@@ -112,6 +112,12 @@ public class ConfigHandler
config.ConstItem ??= new ConstItem();
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
if (config.SimpleDNSItem.GlobalFakeIp is null)
{
config.SimpleDNSItem.GlobalFakeIp = true;
}
config.SpeedTestItem ??= new();
if (config.SpeedTestItem.SpeedTestTimeout < 10)
{
@@ -214,7 +220,7 @@ public class ConfigHandler
/// <returns>Result of the operation (0 if successful, -1 if failed)</returns>
public static async Task<int> AddServer(Config config, ProfileItem profileItem)
{
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId);
var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId);
if (item is null)
{
item = profileItem;
@@ -246,6 +252,7 @@ public class ConfigHandler
item.PublicKey = profileItem.PublicKey;
item.ShortId = profileItem.ShortId;
item.SpiderX = profileItem.SpiderX;
item.Mldsa65Verify = profileItem.Mldsa65Verify;
item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled;
}
@@ -261,6 +268,7 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, item),
EConfigType.TUIC => await AddTuicServer(config, item),
EConfigType.WireGuard => await AddWireguardServer(config, item),
EConfigType.Anytls => await AddAnytlsServer(config, item),
_ => -1,
};
return ret;
@@ -332,7 +340,7 @@ public class ConfigHandler
{
foreach (var it in indexes)
{
var item = await AppHandler.Instance.GetProfileItem(it.IndexId);
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null)
{
continue;
@@ -349,6 +357,11 @@ public 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);
@@ -414,7 +427,7 @@ public class ConfigHandler
/// <returns>The default profile item or null if none exists</returns>
public static async Task<ProfileItem?> GetDefaultServer(Config config)
{
var item = await AppHandler.Instance.GetProfileItem(config.IndexId);
var item = await AppManager.Instance.GetProfileItem(config.IndexId);
if (item is null)
{
var item2 = await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync();
@@ -445,7 +458,7 @@ public class ConfigHandler
for (int i = 0; i < lstProfile.Count; i++)
{
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
}
var sort = 0;
@@ -457,7 +470,7 @@ public class ConfigHandler
{
return 0;
}
sort = ProfileExHandler.Instance.GetSort(lstProfile.First().IndexId) - 1;
sort = ProfileExManager.Instance.GetSort(lstProfile.First().IndexId) - 1;
break;
}
@@ -467,7 +480,7 @@ public class ConfigHandler
{
return 0;
}
sort = ProfileExHandler.Instance.GetSort(lstProfile[index - 1].IndexId) - 1;
sort = ProfileExManager.Instance.GetSort(lstProfile[index - 1].IndexId) - 1;
break;
}
@@ -478,7 +491,7 @@ public class ConfigHandler
{
return 0;
}
sort = ProfileExHandler.Instance.GetSort(lstProfile[index + 1].IndexId) + 1;
sort = ProfileExManager.Instance.GetSort(lstProfile[index + 1].IndexId) + 1;
break;
}
@@ -488,7 +501,7 @@ public class ConfigHandler
{
return 0;
}
sort = ProfileExHandler.Instance.GetSort(lstProfile[^1].IndexId) + 1;
sort = ProfileExManager.Instance.GetSort(lstProfile[^1].IndexId) + 1;
break;
}
@@ -497,7 +510,7 @@ public class ConfigHandler
break;
}
ProfileExHandler.Instance.SetSort(lstProfile[index].IndexId, sort);
ProfileExManager.Instance.SetSort(lstProfile[index].IndexId, sort);
return await Task.FromResult(0);
}
@@ -555,7 +568,7 @@ public class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> EditCustomServer(Config config, ProfileItem profileItem)
{
var item = await AppHandler.Instance.GetProfileItem(profileItem.IndexId);
var item = await AppManager.Instance.GetProfileItem(profileItem.IndexId);
if (item is null)
{
item = profileItem;
@@ -597,7 +610,7 @@ public class ConfigHandler
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
if (!AppHandler.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
{
return -1;
}
@@ -785,6 +798,35 @@ public class ConfigHandler
return 0;
}
/// <summary>
/// Add or edit a Anytls server
/// Validates and processes Anytls-specific settings
/// </summary>
/// <param name="config">Current configuration</param>
/// <param name="profileItem">Anytls profile to add</param>
/// <param name="toFile">Whether to save to file</param>
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> AddAnytlsServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Anytls;
profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = string.Empty;
if (profileItem.StreamSecurity.IsNullOrEmpty())
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Sort the server list by the specified column
/// Updates the sort order in the profile extension data
@@ -796,13 +838,13 @@ public 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 AppHandler.Instance.ProfileItems(subId, "");
var lstModel = await AppManager.Instance.ProfileItems(subId, "");
if (lstModel.Count <= 0)
{
return -1;
}
var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsHandler.Instance.ServerStat : null) ?? [];
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
var lstServerStat = (config.GuiItem.EnableStatistics ? StatisticsManager.Instance.ServerStat : null) ?? [];
var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
var lstProfile = (from t in lstModel
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
from t22 in t2b.DefaultIfEmpty()
@@ -872,7 +914,7 @@ public class ConfigHandler
for (var i = 0; i < lstProfile.Count; i++)
{
ProfileExHandler.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
ProfileExManager.Instance.SetSort(lstProfile[i].IndexId, (i + 1) * 10);
}
switch (name)
{
@@ -881,7 +923,7 @@ public class ConfigHandler
var maxSort = lstProfile.Max(t => t.Sort) + 10;
foreach (var item in lstProfile.Where(item => item.Delay <= 0))
{
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort);
ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
}
break;
@@ -891,7 +933,7 @@ public class ConfigHandler
var maxSort = lstProfile.Max(t => t.Sort) + 10;
foreach (var item in lstProfile.Where(item => item.Speed <= 0))
{
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort);
ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
}
break;
@@ -930,7 +972,7 @@ public class ConfigHandler
{
return -1;
}
if (profileItem.Security.IsNotEmpty() && profileItem.Security != Global.None)
if (profileItem.Security.IsNullOrEmpty())
{
profileItem.Security = Global.None;
}
@@ -949,7 +991,7 @@ public class ConfigHandler
/// <returns>Tuple with total count and remaining count after deduplication</returns>
public static async Task<Tuple<int, int>> DedupServerList(Config config, string subId)
{
var lstProfile = await AppHandler.Instance.ProfileItems(subId);
var lstProfile = await AppManager.Instance.ProfileItems(subId);
if (lstProfile == null)
{
return new Tuple<int, int>(0, 0);
@@ -1019,15 +1061,15 @@ public class ConfigHandler
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExHandler.Instance.GetMaxSort();
maxSort = ProfileExManager.Instance.GetMaxSort();
}
if (!toFile && maxSort < 0)
{
maxSort = ProfileExHandler.Instance.GetMaxSort();
maxSort = ProfileExManager.Instance.GetMaxSort();
}
if (maxSort > 0)
{
ProfileExHandler.Instance.SetSort(profileItem.IndexId, maxSort + 1);
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
}
if (toFile)
@@ -1037,6 +1079,37 @@ public class ConfigHandler
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
@@ -1087,7 +1160,7 @@ public class ConfigHandler
{
try
{
var item = await AppHandler.Instance.GetProfileItem(indexId);
var item = await AppManager.Instance.GetProfileItem(indexId);
if (item == null)
{
return 0;
@@ -1108,7 +1181,7 @@ public class ConfigHandler
}
/// <summary>
/// Create a custom server that combines multiple servers for load balancing
/// Create a group server that combines multiple servers for load balancing
/// Generates a configuration file that references multiple servers
/// </summary>
/// <param name="config">Current configuration</param>
@@ -1116,45 +1189,54 @@ public class ConfigHandler
/// <param name="coreType">Core type to use (Xray or sing_box)</param>
/// <param name="multipleLoad">Load balancing algorithm</param>
/// <returns>Result object with success state and data</returns>
public static async Task<RetResult> AddCustomServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad)
public static async Task<RetResult> AddGroupServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad, string? subId)
{
var indexId = Utils.GetMd5(Global.CoreMultipleLoadConfigFileName);
var configPath = Utils.GetConfigPath(Global.CoreMultipleLoadConfigFileName);
var result = new RetResult();
var result = await CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, coreType, multipleLoad);
if (result.Success != true)
{
return result;
}
var indexId = Utils.GetGuid(false);
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
if (!File.Exists(configPath))
{
return result;
}
var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new();
profileItem.IndexId = indexId;
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId)).Remarks} ";
if (coreType == ECoreType.Xray)
{
profileItem.Remarks = multipleLoad switch
remark += multipleLoad switch
{
EMultipleLoad.Random => ResUI.menuSetDefaultMultipleServerXrayRandom,
EMultipleLoad.RoundRobin => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
EMultipleLoad.LeastPing => ResUI.menuSetDefaultMultipleServerXrayLeastPing,
EMultipleLoad.LeastLoad => ResUI.menuSetDefaultMultipleServerXrayLeastLoad,
_ => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback,
EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom,
EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad,
_ => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
};
}
else if (coreType == ECoreType.sing_box)
{
profileItem.Remarks = ResUI.menuSetDefaultMultipleServerSingBoxLeastPing;
remark += multipleLoad switch
{
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback,
_ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
};
}
profileItem.Address = Global.CoreMultipleLoadConfigFileName;
profileItem.ConfigType = EConfigType.Custom;
profileItem.CoreType = coreType;
await AddServerCommon(config, profileItem, true);
var profile = new ProfileItem
{
IndexId = indexId,
CoreType = coreType,
ConfigType = EConfigType.PolicyGroup,
Remarks = remark,
};
if (!subId.IsNullOrEmpty())
{
profile.Subid = subId;
}
var profileGroup = new ProfileGroupItem
{
ChildItems = childProfileIndexId,
MultipleLoad = multipleLoad,
IndexId = indexId,
};
var ret = await AddGroupServerCommon(config, profile, profileGroup, true);
result.Success = ret == 0;
result.Data = indexId;
return result;
}
@@ -1172,16 +1254,25 @@ public class ConfigHandler
ProfileItem? itemSocks = null;
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
{
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,
Sni = node.Address, //Tun2SocksAddress
Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)
SpiderX = tun2SocksAddress, // Tun2SocksAddress
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
};
}
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)
{
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem()
@@ -1205,12 +1296,12 @@ public 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 AppHandler.Instance.ProfileItems(subid, "");
var lstModel = await AppManager.Instance.ProfileItems(subid, "");
if (lstModel is { Count: <= 0 })
{
return -1;
}
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs();
var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
var lstProfile = (from t in lstModel
join t2 in lstProfileExs on t.IndexId equals t2.IndexId
where t2.Delay == -1
@@ -1246,7 +1337,7 @@ public class ConfigHandler
if (isSub && subid.IsNotEmpty())
{
await RemoveServersViaSubid(config, subid, isSub);
subFilter = (await AppHandler.Instance.GetSubItem(subid))?.Filter ?? "";
subFilter = (await AppManager.Instance.GetSubItem(subid))?.Filter ?? "";
}
var countServers = 0;
@@ -1294,6 +1385,7 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false),
EConfigType.TUIC => await AddTuicServer(config, profileItem, false),
EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false),
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
_ => -1,
};
@@ -1329,7 +1421,7 @@ public class ConfigHandler
return -1;
}
var subItem = await AppHandler.Instance.GetSubItem(subid);
var subItem = await AppManager.Instance.GetSubItem(subid);
var subRemarks = subItem?.Remarks;
var preSocksPort = subItem?.PreSocksPort;
@@ -1378,6 +1470,11 @@ public class ConfigHandler
{
profileItem = V2rayFmt.ResolveFull(strData, subRemarks);
}
//Is Html Page
if (profileItem is null && HtmlPageFmt.IsHtmlPage(strData))
{
return -1;
}
//Is Clash configuration
if (profileItem is null)
{
@@ -1387,16 +1484,7 @@ public class ConfigHandler
if (profileItem is null)
{
profileItem = Hysteria2Fmt.ResolveFull2(strData, subRemarks);
}
if (profileItem is null)
{
profileItem = Hysteria2Fmt.ResolveFull(strData, subRemarks);
}
//Is naiveproxy configuration
if (profileItem is null)
{
profileItem = NaiveproxyFmt.ResolveFull(strData, subRemarks);
}
}
if (profileItem is null || profileItem.Address.IsNullOrEmpty())
{
return -1;
@@ -1480,7 +1568,7 @@ public class ConfigHandler
ProfileItem? activeProfile = null;
if (isSub && subid.IsNotEmpty())
{
lstOriSub = await AppHandler.Instance.ProfileItems(subid);
lstOriSub = await AppManager.Instance.ProfileItems(subid);
activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId);
}
@@ -1512,7 +1600,7 @@ public class ConfigHandler
//Select active node
if (activeProfile != null)
{
var lstSub = await AppHandler.Instance.ProfileItems(subid);
var lstSub = await AppManager.Instance.ProfileItems(subid);
var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true));
if (existItem != null)
{
@@ -1523,13 +1611,13 @@ public class ConfigHandler
//Keep the last traffic statistics
if (lstOriSub != null)
{
var lstSub = await AppHandler.Instance.ProfileItems(subid);
var lstSub = await AppManager.Instance.ProfileItems(subid);
foreach (var item in lstSub)
{
var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true));
if (existItem != null)
{
await StatisticsHandler.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId);
await StatisticsManager.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId);
}
}
}
@@ -1569,7 +1657,7 @@ public class ConfigHandler
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{
//TODO Temporary reminder to be removed later
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol);
NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol);
//return -1;
}
@@ -1587,7 +1675,7 @@ public class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> AddSubItem(Config config, SubItem subItem)
{
var item = await AppHandler.Instance.GetSubItem(subItem.Id);
var item = await AppManager.Instance.GetSubItem(subItem.Id);
if (item is null)
{
item = subItem;
@@ -1619,7 +1707,7 @@ public class ConfigHandler
var maxSort = 0;
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 0)
{
var lstSubs = (await AppHandler.Instance.SubItems());
var lstSubs = (await AppManager.Instance.SubItems());
maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
}
item.Sort = maxSort + 1;
@@ -1673,7 +1761,7 @@ public class ConfigHandler
/// <returns>0 if successful</returns>
public static async Task<int> DeleteSubItem(Config config, string id)
{
var item = await AppHandler.Instance.GetSubItem(id);
var item = await AppManager.Instance.GetSubItem(id);
if (item is null)
{
return 0;
@@ -1857,7 +1945,7 @@ public class ConfigHandler
/// <returns>0 if successful</returns>
public static async Task<int> SetDefaultRouting(Config config, RoutingItem routingItem)
{
var items = await AppHandler.Instance.RoutingItems();
var items = await AppManager.Instance.RoutingItems();
if (items.Any(t => t.Id == routingItem.Id && t.IsActive == true))
{
return -1;
@@ -1937,7 +2025,7 @@ public class ConfigHandler
if (template == null)
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
var items = await AppHandler.Instance.RoutingItems();
var items = await AppManager.Instance.RoutingItems();
var maxSort = items.Count;
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0)
{
@@ -1984,19 +2072,19 @@ public class ConfigHandler
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
{
var ver = "V3-";
var items = await AppHandler.Instance.RoutingItems();
var items = await AppManager.Instance.RoutingItems();
//TODO Temporary code to be removed later
var lockItem = items?.FirstOrDefault(t => t.Locked == true);
if (lockItem != null)
{
await ConfigHandler.RemoveRoutingItem(lockItem);
items = await AppHandler.Instance.RoutingItems();
items = await AppManager.Instance.RoutingItems();
}
if (!blImportAdvancedRules && items.Count > 0)
{
//migrate
//migrate
//TODO Temporary code to be removed later
if (config.RoutingBasicItem.RoutingIndexId.IsNotEmpty())
{
@@ -2062,18 +2150,38 @@ public class ConfigHandler
/// <summary>
/// Initialize built-in DNS configurations
/// Creates default DNS items for V2Ray and sing-box
/// Also checks existing DNS items and disables those with empty NormalDNS
/// </summary>
/// <param name="config">Current configuration</param>
/// <returns>0 if successful</returns>
public static async Task<int> InitBuiltinDNS(Config config)
{
var items = await AppHandler.Instance.DNSItems();
var items = await AppManager.Instance.DNSItems();
// Check existing DNS items and disable those with empty NormalDNS
var needsUpdate = false;
foreach (var existingItem in items)
{
if (existingItem.NormalDNS.IsNullOrEmpty() && existingItem.Enabled)
{
existingItem.Enabled = false;
needsUpdate = true;
}
}
// Update items if any changes were made
if (needsUpdate)
{
await SQLiteHelper.Instance.UpdateAllAsync(items);
}
if (items.Count <= 0)
{
var item = new DNSItem()
{
Remarks = "V2ray",
CoreType = ECoreType.Xray,
Enabled = false,
};
await SaveDNSItems(config, item);
@@ -2081,6 +2189,7 @@ public class ConfigHandler
{
Remarks = "sing-box",
CoreType = ECoreType.sing_box,
Enabled = false,
};
await SaveDNSItems(config, item2);
}
@@ -2125,7 +2234,7 @@ public class ConfigHandler
/// <returns>DNS item with configuration from the URL</returns>
public static async Task<DNSItem> GetExternalDNSItem(ECoreType type, string url)
{
var currentItem = await AppHandler.Instance.GetDNSItem(type);
var currentItem = await AppManager.Instance.GetDNSItem(type);
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
@@ -2152,6 +2261,85 @@ public class ConfigHandler
#endregion DNS
#region Simple DNS
public static SimpleDNSItem InitBuiltinSimpleDNS()
{
return new SimpleDNSItem()
{
UseSystemHosts = false,
AddCommonHosts = true,
FakeIP = false,
GlobalFakeIp = true,
BlockBindingQuery = true,
DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(),
};
}
public static async Task<SimpleDNSItem> GetExternalSimpleDNSItem(string url)
{
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (templateContent.IsNullOrEmpty())
return null;
var template = JsonUtils.Deserialize<SimpleDNSItem>(templateContent);
if (template == null)
return null;
return template;
}
#endregion Simple DNS
#region Custom Config
public static async Task<int> InitBuiltinFullConfigTemplate(Config config)
{
var items = await AppManager.Instance.FullConfigTemplateItem();
if (items.Count <= 0)
{
var item = new FullConfigTemplateItem()
{
Remarks = "V2ray",
CoreType = ECoreType.Xray,
};
await SaveFullConfigTemplate(config, item);
var item2 = new FullConfigTemplateItem()
{
Remarks = "sing-box",
CoreType = ECoreType.sing_box,
};
await SaveFullConfigTemplate(config, item2);
}
return 0;
}
public static async Task<int> SaveFullConfigTemplate(Config config, FullConfigTemplateItem item)
{
if (item == null)
{
return -1;
}
if (item.Id.IsNullOrEmpty())
{
item.Id = Utils.GetGuid(false);
}
if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0)
{
return 0;
}
else
{
return -1;
}
}
#endregion Custom Config
#region Regional Presets
/// <summary>
@@ -2173,30 +2361,57 @@ public class ConfigHandler
await SQLiteHelper.Instance.DeleteAllAsync<DNSItem>();
await InitBuiltinDNS(config);
return true;
config.SimpleDNSItem = InitBuiltinSimpleDNS();
break;
case EPresetType.Russia:
config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[1];
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1];
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1];
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json"));
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json"));
var xrayDnsRussia = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json");
var singboxDnsRussia = await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.json");
var simpleDnsRussia = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[1] + "simple_dns.json");
return true;
if (simpleDnsRussia == null)
{
xrayDnsRussia.Enabled = true;
singboxDnsRussia.Enabled = true;
config.SimpleDNSItem = InitBuiltinSimpleDNS();
}
else
{
config.SimpleDNSItem = simpleDnsRussia;
}
await SaveDNSItems(config, xrayDnsRussia);
await SaveDNSItems(config, singboxDnsRussia);
break;
case EPresetType.Iran:
config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[2];
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2];
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2];
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json"));
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json"));
var xrayDnsIran = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json");
var singboxDnsIran = await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json");
var simpleDnsIran = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[2] + "simple_dns.json");
return true;
if (simpleDnsIran == null)
{
xrayDnsIran.Enabled = true;
singboxDnsIran.Enabled = true;
config.SimpleDNSItem = InitBuiltinSimpleDNS();
}
else
{
config.SimpleDNSItem = simpleDnsIran;
}
await SaveDNSItems(config, xrayDnsIran);
await SaveDNSItems(config, singboxDnsIran);
break;
}
return false;
return true;
}
#endregion Regional Presets

View File

@@ -1,27 +1,28 @@
using System.Net;
namespace ServiceLib.Handler;
public class ConnectionHandler
public static class ConnectionHandler
{
private static readonly Lazy<ConnectionHandler> _instance = new(() => new());
public static ConnectionHandler Instance => _instance.Value;
private static readonly string _tag = "ConnectionHandler";
public async Task<string> RunAvailabilityCheck()
public static async Task<string> RunAvailabilityCheck()
{
var downloadHandle = new DownloadService();
var time = await downloadHandle.RunAvailabilityCheck(null);
var ip = time > 0 ? await GetIPInfo(downloadHandle) ?? Global.None : Global.None;
var time = await GetRealPingTime();
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
return string.Format(ResUI.TestMeOutput, time, ip);
}
private async Task<string?> GetIPInfo(DownloadService downloadHandle)
private static async Task<string?> GetIPInfo()
{
var url = AppHandler.Instance.Config.SpeedTestItem.IPAPIUrl;
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
if (url.IsNullOrEmpty())
{
return null;
}
var downloadHandle = new DownloadService();
var result = await downloadHandle.TryDownloadString(url, true, "");
if (result == null)
{
@@ -39,4 +40,31 @@ public class ConnectionHandler
return $"({country ?? "unknown"}) {ip}";
}
private static async Task<int> GetRealPingTime()
{
var responseTime = -1;
try
{
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}");
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
for (var i = 0; i < 2; i++)
{
responseTime = await HttpClientHelper.Instance.GetRealPingTime(url, webProxy, 10);
if (responseTime > 0)
{
break;
}
await Task.Delay(500);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return -1;
}
return responseTime;
}
}

View File

@@ -1,118 +0,0 @@
using System.Diagnostics;
using System.Text;
using CliWrap;
using CliWrap.Buffered;
namespace ServiceLib.Handler;
public class CoreAdminHandler
{
private static readonly Lazy<CoreAdminHandler> _instance = new(() => new());
public static CoreAdminHandler Instance => _instance.Value;
private Config _config;
private Action<bool, string>? _updateFunc;
private int _linuxSudoPid = -1;
private const string _tag = "CoreAdminHandler";
public async Task Init(Config config, Action<bool, string> updateFunc)
{
if (_config != null)
{
return;
}
_config = config;
_updateFunc = updateFunc;
await Task.CompletedTask;
}
private void UpdateFunc(bool notify, string msg)
{
_updateFunc?.Invoke(notify, msg);
}
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
{
StringBuilder sb = new();
sb.AppendLine("#!/bin/bash");
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
sb.AppendLine($"sudo -S {cmdLine}");
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
Process proc = new()
{
StartInfo = new()
{
FileName = shFilePath,
Arguments = "",
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
}
};
void dataHandler(object sender, DataReceivedEventArgs e)
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
}
proc.OutputDataReceived += dataHandler;
proc.ErrorDataReceived += dataHandler;
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd);
await Task.Delay(100);
if (proc is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
_linuxSudoPid = proc.Id;
return proc;
}
public async Task KillProcessAsLinuxSudo()
{
if (_linuxSudoPid < 0)
{
return;
}
try
{
var shellFileName = Utils.IsOSX() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName;
var shFilePath = await FileManager.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
if (shFilePath.Contains(' '))
{
shFilePath = shFilePath.AppendQuotes();
}
var arg = new List<string>() { "-c", $"sudo -S {shFilePath} {_linuxSudoPid}" };
var result = await Cli.Wrap(Global.LinuxBash)
.WithArguments(arg)
.WithStandardInputPipe(PipeSource.FromString(AppHandler.Instance.LinuxSudoPwd))
.ExecuteBufferedAsync();
UpdateFunc(false, result.StandardOutput.ToString());
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
_linuxSudoPid = -1;
}
}

View File

@@ -3,13 +3,13 @@ namespace ServiceLib.Handler;
/// <summary>
/// Core configuration file processing class
/// </summary>
public class CoreConfigHandler
public static class CoreConfigHandler
{
private static readonly string _tag = "CoreConfigHandler";
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
{
var config = AppHandler.Instance.Config;
var config = AppManager.Instance.Config;
var result = new RetResult();
if (node.ConfigType == EConfigType.Custom)
@@ -21,7 +21,7 @@ public class CoreConfigHandler
_ => await GenerateClientCustomConfig(node, fileName)
};
}
else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
}
@@ -112,11 +112,11 @@ public class CoreConfigHandler
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
{
var result = new RetResult();
var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest);
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
testItem.Port = port;
if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
}
@@ -132,24 +132,4 @@ public class CoreConfigHandler
await File.WriteAllTextAsync(fileName, result.Data.ToString());
return result;
}
public static async Task<RetResult> GenerateClientMultipleLoadConfig(Config config, string fileName, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad)
{
var result = new RetResult();
if (coreType == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds);
}
else
{
result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds, multipleLoad);
}
if (result.Success != true)
{
return result;
}
await File.WriteAllTextAsync(fileName, result.Data.ToString());
return result;
}
}

View File

@@ -0,0 +1,48 @@
namespace ServiceLib.Handler.Fmt;
public class AnytlsFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
var parsedUrl = Utils.TryUri(str);
if (parsedUrl == null)
{
return null;
}
ProfileItem item = new()
{
ConfigType = EConfigType.Anytls,
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
Address = parsedUrl.IdnHost,
Port = parsedUrl.Port,
};
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
item.Id = rawUserInfo;
var query = Utils.ParseQueryString(parsedUrl.Query);
_ = ResolveStdTransport(query, ref item);
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
}
var remark = string.Empty;
if (item.Remarks.IsNotEmpty())
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var pw = item.Id;
var dicQuery = new Dictionary<string, string>();
_ = GetStdTransport(item, Global.None, ref dicQuery);
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
}
}

View File

@@ -59,6 +59,10 @@ public class BaseFmt
{
dicQuery.Add("spx", Utils.UrlEncode(item.SpiderX));
}
if (item.Mldsa65Verify.IsNotEmpty())
{
dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify));
}
if (item.AllowInsecure.Equals("true"))
{
dicQuery.Add("allowInsecure", "1");
@@ -151,60 +155,60 @@ public class BaseFmt
protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item)
{
item.Flow = query["flow"] ?? "";
item.StreamSecurity = query["security"] ?? "";
item.Sni = query["sni"] ?? "";
item.Alpn = Utils.UrlDecode(query["alpn"] ?? "");
item.Fingerprint = Utils.UrlDecode(query["fp"] ?? "");
item.PublicKey = Utils.UrlDecode(query["pbk"] ?? "");
item.ShortId = Utils.UrlDecode(query["sid"] ?? "");
item.SpiderX = Utils.UrlDecode(query["spx"] ?? "");
item.AllowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : "";
item.Flow = GetQueryValue(query, "flow");
item.StreamSecurity = GetQueryValue(query, "security");
item.Sni = GetQueryValue(query, "sni");
item.Alpn = GetQueryDecoded(query, "alpn");
item.Fingerprint = GetQueryDecoded(query, "fp");
item.PublicKey = GetQueryDecoded(query, "pbk");
item.ShortId = GetQueryDecoded(query, "sid");
item.SpiderX = GetQueryDecoded(query, "spx");
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
item.AllowInsecure = new[] { "allowInsecure", "allow_insecure", "insecure" }.Any(k => (query[k] ?? "") == "1") ? "true" : "";
item.Network = query["type"] ?? nameof(ETransport.tcp);
item.Network = GetQueryValue(query, "type", nameof(ETransport.tcp));
switch (item.Network)
{
case nameof(ETransport.tcp):
item.HeaderType = query["headerType"] ?? Global.None;
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
item.RequestHost = GetQueryDecoded(query, "host");
break;
case nameof(ETransport.kcp):
item.HeaderType = query["headerType"] ?? Global.None;
item.Path = Utils.UrlDecode(query["seed"] ?? "");
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
item.Path = GetQueryDecoded(query, "seed");
break;
case nameof(ETransport.ws):
case nameof(ETransport.httpupgrade):
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
item.Path = Utils.UrlDecode(query["path"] ?? "/");
item.RequestHost = GetQueryDecoded(query, "host");
item.Path = GetQueryDecoded(query, "path", "/");
break;
case nameof(ETransport.xhttp):
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
item.Path = Utils.UrlDecode(query["path"] ?? "/");
item.HeaderType = Utils.UrlDecode(query["mode"] ?? "");
item.Extra = Utils.UrlDecode(query["extra"] ?? "");
item.RequestHost = GetQueryDecoded(query, "host");
item.Path = GetQueryDecoded(query, "path", "/");
item.HeaderType = GetQueryDecoded(query, "mode");
item.Extra = GetQueryDecoded(query, "extra");
break;
case nameof(ETransport.http):
case nameof(ETransport.h2):
item.Network = nameof(ETransport.h2);
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
item.Path = Utils.UrlDecode(query["path"] ?? "/");
item.RequestHost = GetQueryDecoded(query, "host");
item.Path = GetQueryDecoded(query, "path", "/");
break;
case nameof(ETransport.quic):
item.HeaderType = query["headerType"] ?? Global.None;
item.RequestHost = query["quicSecurity"] ?? Global.None;
item.Path = Utils.UrlDecode(query["key"] ?? "");
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
item.RequestHost = GetQueryValue(query, "quicSecurity", Global.None);
item.Path = GetQueryDecoded(query, "key");
break;
case nameof(ETransport.grpc):
item.RequestHost = Utils.UrlDecode(query["authority"] ?? "");
item.Path = Utils.UrlDecode(query["serviceName"] ?? "");
item.HeaderType = Utils.UrlDecode(query["mode"] ?? Global.GrpcGunMode);
item.RequestHost = GetQueryDecoded(query, "authority");
item.Path = GetQueryDecoded(query, "serviceName");
item.HeaderType = GetQueryDecoded(query, "mode", Global.GrpcGunMode);
break;
default:
@@ -215,14 +219,7 @@ public class BaseFmt
protected static bool Contains(string str, params string[] s)
{
foreach (var item in s)
{
if (str.Contains(item, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
return s.All(item => str.Contains(item, StringComparison.OrdinalIgnoreCase));
}
protected static string WriteAllText(string strData, string ext = "json")
@@ -241,4 +238,14 @@ public class BaseFmt
var url = $"{Utils.UrlEncode(userInfo)}@{GetIpv6(address)}:{port}";
return $"{Global.ProtocolShares[eConfigType]}{url}{query}{remark}";
}
protected static string GetQueryValue(NameValueCollection query, string key, string defaultValue = "")
{
return query[key] ?? defaultValue;
}
protected static string GetQueryDecoded(NameValueCollection query, string key, string defaultValue = "")
{
return Utils.UrlDecode(GetQueryValue(query, key, defaultValue));
}
}

View File

@@ -4,7 +4,7 @@ public class ClashFmt : BaseFmt
{
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "port", "socks-port", "proxies"))
if (Contains(strData, "rules", "-port", "proxies"))
{
var fileName = WriteAllText(strData, "yaml");

View File

@@ -18,6 +18,7 @@ public class FmtHandler
EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item),
EConfigType.TUIC => TuicFmt.ToUri(item),
EConfigType.WireGuard => WireguardFmt.ToUri(item),
EConfigType.Anytls => AnytlsFmt.ToUri(item),
_ => null,
};
@@ -26,7 +27,7 @@ public class FmtHandler
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return "";
return string.Empty;
}
}
@@ -75,6 +76,10 @@ public class FmtHandler
{
return WireguardFmt.Resolve(str, out msg);
}
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls]))
{
return AnytlsFmt.Resolve(str, out msg);
}
else
{
msg = ResUI.NonvmessOrssProtocol;

View File

@@ -0,0 +1,9 @@
namespace ServiceLib.Handler.Fmt;
public class HtmlPageFmt : BaseFmt
{
public static bool IsHtmlPage(string strData)
{
return Contains(strData, "<html", "<!doctype html", "<head");
}
}

View File

@@ -21,10 +21,10 @@ public class Hysteria2Fmt : BaseFmt
var query = Utils.ParseQueryString(url.Query);
ResolveStdTransport(query, ref item);
item.Path = Utils.UrlDecode(query["obfs-password"] ?? "");
item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false";
item.Path = GetQueryDecoded(query, "obfs-password");
item.AllowInsecure = GetQueryValue(query, "insecure") == "1" ? "true" : "false";
item.Ports = Utils.UrlDecode(query["mport"] ?? "");
item.Ports = GetQueryDecoded(query, "mport");
return item;
}
@@ -63,24 +63,6 @@ public class Hysteria2Fmt : BaseFmt
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark);
}
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "server", "up", "down", "listen", "<html>", "<body>"))
{
var fileName = WriteAllText(strData);
var profileItem = new ProfileItem
{
CoreType = ECoreType.hysteria,
Address = fileName,
Remarks = subRemarks ?? "hysteria_custom"
};
return profileItem;
}
return null;
}
public static ProfileItem? ResolveFull2(string strData, string? subRemarks)
{
if (Contains(strData, "server", "auth", "up", "down", "listen"))

View File

@@ -1,22 +0,0 @@
namespace ServiceLib.Handler.Fmt;
public class NaiveproxyFmt : BaseFmt
{
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "listen", "proxy", "<html>", "<body>"))
{
var fileName = WriteAllText(strData);
var profileItem = new ProfileItem
{
CoreType = ECoreType.naiveproxy,
Address = fileName,
Remarks = subRemarks ?? "naiveproxy_custom"
};
return profileItem;
}
return null;
}
}

View File

@@ -42,7 +42,7 @@ public class ShadowsocksFmt : BaseFmt
// item.port);
//url = Utile.Base64Encode(url);
//new Sip002
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}");
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark);
}

View File

@@ -33,7 +33,7 @@ public class SocksFmt : BaseFmt
remark = "#" + Utils.UrlEncode(item.Remarks);
}
//new
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}");
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
}

View File

@@ -30,7 +30,7 @@ public class TuicFmt : BaseFmt
var query = Utils.ParseQueryString(url.Query);
ResolveStdTransport(query, ref item);
item.HeaderType = query["congestion_control"] ?? "";
item.HeaderType = GetQueryValue(query, "congestion_control");
return item;
}

View File

@@ -24,8 +24,8 @@ public class VLESSFmt : BaseFmt
item.Id = Utils.UrlDecode(url.UserInfo);
var query = Utils.ParseQueryString(url.Query);
item.Security = query["encryption"] ?? Global.None;
item.StreamSecurity = query["security"] ?? "";
item.Security = GetQueryValue(query, "encryption", Global.None);
item.StreamSecurity = GetQueryValue(query, "security");
_ = ResolveStdTransport(query, ref item);
return item;

View File

@@ -24,10 +24,10 @@ public class WireguardFmt : BaseFmt
var query = Utils.ParseQueryString(url.Query);
item.PublicKey = Utils.UrlDecode(query["publickey"] ?? "");
item.Path = Utils.UrlDecode(query["reserved"] ?? "");
item.RequestHost = Utils.UrlDecode(query["address"] ?? "");
item.ShortId = Utils.UrlDecode(query["mtu"] ?? "");
item.PublicKey = GetQueryDecoded(query, "publickey");
item.Path = GetQueryDecoded(query, "reserved");
item.RequestHost = GetQueryDecoded(query, "address");
item.ShortId = GetQueryDecoded(query, "mtu");
return item;
}

View File

@@ -0,0 +1,221 @@
namespace ServiceLib.Handler;
public static class SubscriptionHandler
{
public static async Task UpdateProcess(Config config, string subId, bool blProxy, Func<bool, string, Task> updateFunc)
{
await updateFunc?.Invoke(false, ResUI.MsgUpdateSubscriptionStart);
var subItem = await AppManager.Instance.SubItems();
if (subItem is not { Count: > 0 })
{
await updateFunc?.Invoke(false, ResUI.MsgNoValidSubscription);
return;
}
var successCount = 0;
foreach (var item in subItem)
{
try
{
if (!IsValidSubscription(item, subId))
{
continue;
}
var hashCode = $"{item.Remarks}->";
if (item.Enabled == false)
{
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSkipSubscriptionUpdate}");
continue;
}
// Create download handler
var downloadHandle = CreateDownloadHandler(hashCode, updateFunc);
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartGettingSubscriptions}");
// Get all subscription content (main subscription + additional subscriptions)
var result = await DownloadAllSubscriptions(config, item, blProxy, downloadHandle);
// Process download result
if (await ProcessDownloadResult(config, item.Id, result, hashCode, updateFunc))
{
successCount++;
}
await updateFunc?.Invoke(false, "-------------------------------------------------------");
}
catch (Exception ex)
{
var hashCode = $"{item.Remarks}->";
Logging.SaveLog("UpdateSubscription", ex);
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgFailedImportSubscription}: {ex.Message}");
await updateFunc?.Invoke(false, "-------------------------------------------------------");
}
}
await updateFunc?.Invoke(successCount > 0, $"{ResUI.MsgUpdateSubscriptionEnd}");
}
private static bool IsValidSubscription(SubItem item, string subId)
{
var id = item.Id.TrimEx();
var url = item.Url.TrimEx();
if (id.IsNullOrEmpty() || url.IsNullOrEmpty())
{
return false;
}
if (subId.IsNotEmpty() && item.Id != subId)
{
return false;
}
if (!url.StartsWith(Global.HttpsProtocol) && !url.StartsWith(Global.HttpProtocol))
{
return false;
}
return true;
}
private static DownloadService CreateDownloadHandler(string hashCode, Func<bool, string, Task> updateFunc)
{
var downloadHandle = new DownloadService();
downloadHandle.Error += (sender2, args) =>
{
updateFunc?.Invoke(false, $"{hashCode}{args.GetException().Message}");
};
return downloadHandle;
}
private static async Task<string> DownloadSubscriptionContent(DownloadService downloadHandle, string url, bool blProxy, string userAgent)
{
var result = await downloadHandle.TryDownloadString(url, blProxy, userAgent);
// If download with proxy fails, try direct connection
if (blProxy && result.IsNullOrEmpty())
{
result = await downloadHandle.TryDownloadString(url, false, userAgent);
}
return result ?? string.Empty;
}
private static async Task<string> DownloadAllSubscriptions(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
{
// Download main subscription content
var result = await DownloadMainSubscription(config, item, blProxy, downloadHandle);
// Process additional subscription links (if any)
if (item.ConvertTarget.IsNullOrEmpty() && item.MoreUrl.TrimEx().IsNotEmpty())
{
result = await DownloadAdditionalSubscriptions(item, result, blProxy, downloadHandle);
}
return result;
}
private static async Task<string> DownloadMainSubscription(Config config, SubItem item, bool blProxy, DownloadService downloadHandle)
{
// Prepare subscription URL and download directly
var url = Utils.GetPunycode(item.Url.TrimEx());
// If conversion is needed
if (item.ConvertTarget.IsNotEmpty())
{
var subConvertUrl = config.ConstItem.SubConvertUrl.IsNullOrEmpty()
? Global.SubConvertUrls.FirstOrDefault()
: config.ConstItem.SubConvertUrl;
url = string.Format(subConvertUrl!, Utils.UrlEncode(url));
if (!url.Contains("target="))
{
url += string.Format("&target={0}", item.ConvertTarget);
}
if (!url.Contains("config="))
{
url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault());
}
}
// Download and return result directly
return await DownloadSubscriptionContent(downloadHandle, url, blProxy, item.UserAgent);
}
private static async Task<string> DownloadAdditionalSubscriptions(SubItem item, string mainResult, bool blProxy, DownloadService downloadHandle)
{
var result = mainResult;
// If main subscription result is Base64 encoded, decode it first
if (result.IsNotEmpty() && Utils.IsBase64String(result))
{
result = Utils.Base64Decode(result);
}
// Process additional URL list
var lstUrl = item.MoreUrl.TrimEx().Split(",") ?? [];
foreach (var it in lstUrl)
{
var url2 = Utils.GetPunycode(it);
if (url2.IsNullOrEmpty())
{
continue;
}
var additionalResult = await DownloadSubscriptionContent(downloadHandle, url2, blProxy, item.UserAgent);
if (additionalResult.IsNotEmpty())
{
// Process additional subscription results, add to main result
if (Utils.IsBase64String(additionalResult))
{
result += Environment.NewLine + Utils.Base64Decode(additionalResult);
}
else
{
result += Environment.NewLine + additionalResult;
}
}
}
return result;
}
private static async Task<bool> ProcessDownloadResult(Config config, string id, string result, string hashCode, Func<bool, string, Task> updateFunc)
{
if (result.IsNullOrEmpty())
{
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgSubscriptionDecodingFailed}");
return false;
}
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgGetSubscriptionSuccessfully}");
// If result is too short, display content directly
if (result.Length < 99)
{
await updateFunc?.Invoke(false, $"{hashCode}{result}");
}
await updateFunc?.Invoke(false, $"{hashCode}{ResUI.MsgStartParsingSubscription}");
// Add servers to configuration
var ret = await ConfigHandler.AddBatchServers(config, result, id, true);
if (ret <= 0)
{
Logging.SaveLog("FailedImportSubscription");
Logging.SaveLog(result);
}
// Update completion message
await updateFunc?.Invoke(false, ret > 0
? $"{hashCode}{ResUI.MsgUpdateSubscriptionEnd}"
: $"{hashCode}{ResUI.MsgFailedImportSubscription}");
return ret > 0;
}
}

View File

@@ -1,6 +1,6 @@
namespace ServiceLib.Handler.SysProxy;
public class ProxySettingLinux
public static class ProxySettingLinux
{
private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh";

View File

@@ -1,6 +1,6 @@
namespace ServiceLib.Handler.SysProxy;
public class ProxySettingOSX
public static class ProxySettingOSX
{
private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh";

View File

@@ -3,7 +3,7 @@ using static ServiceLib.Handler.SysProxy.ProxySettingWindows.InternetConnectionO
namespace ServiceLib.Handler.SysProxy;
public class ProxySettingWindows
public static class ProxySettingWindows
{
private const string _regPath = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";

View File

@@ -15,7 +15,7 @@ public static class SysProxyHandler
try
{
var port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks);
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
if (port <= 0)
{
@@ -56,7 +56,7 @@ public static class SysProxyHandler
if (type != ESysProxyType.Pac && Utils.IsWindows())
{
PacHandler.Stop();
PacManager.Instance.Stop();
}
}
catch (Exception ex)
@@ -90,8 +90,8 @@ public static class SysProxyHandler
private static async Task SetWindowsProxyPac(int port)
{
var portPac = AppHandler.Instance.GetLocalPort(EInboundProtocol.pac);
await PacHandler.Start(Utils.GetConfigPath(), port, portPac);
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
await PacManager.Instance.StartAsync(Utils.GetConfigPath(), port, portPac);
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
ProxySettingWindows.SetProxy(strProxy, "", 4);
}

View File

@@ -1,7 +1,7 @@
using System.Net;
using Downloader;
namespace ServiceLib.Common;
namespace ServiceLib.Helper;
public class DownloaderHelper
{

View File

@@ -1,8 +1,10 @@
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
namespace ServiceLib.Common;
namespace ServiceLib.Helper;
/// <summary>
/// </summary>
@@ -202,4 +204,35 @@ public class HttpClientHelper
}
} while (isMoreToRead);
}
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
{
var responseTime = -1;
try
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
using var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null
});
List<int> oneTime = new();
for (var i = 0; i < 2; i++)
{
var timer = Stopwatch.StartNew();
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
timer.Stop();
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
await Task.Delay(100);
}
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
}
catch //(Exception ex)
{
//Utile.SaveLog(ex.Message, ex);
}
return responseTime;
}
}

View File

@@ -1,7 +1,7 @@
using System.Collections;
using SQLite;
namespace ServiceLib.Common;
namespace ServiceLib.Helper;
public sealed class SQLiteHelper
{
@@ -26,7 +26,7 @@ public sealed class SQLiteHelper
public async Task<int> InsertAllAsync(IEnumerable models)
{
return await _dbAsync.InsertAllAsync(models);
return await _dbAsync.InsertAllAsync(models, runInTransaction: true).ConfigureAwait(false);
}
public async Task<int> InsertAsync(object model)
@@ -46,7 +46,7 @@ public sealed class SQLiteHelper
public async Task<int> UpdateAllAsync(IEnumerable models)
{
return await _dbAsync.UpdateAllAsync(models);
return await _dbAsync.UpdateAllAsync(models, runInTransaction: true).ConfigureAwait(false);
}
public async Task<int> DeleteAsync(object model)

View File

@@ -0,0 +1,282 @@
namespace ServiceLib.Manager;
/// <summary>
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
/// </summary>
public class ActionPrecheckManager(Config config)
{
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
public static ActionPrecheckManager Instance => _instance.Value;
private readonly Config _config = config;
public async Task<List<string>> Check(string? indexId)
{
if (indexId.IsNullOrEmpty())
{
return [ResUI.PleaseSelectServer];
}
var item = await AppManager.Instance.GetProfileItem(indexId);
if (item is null)
{
return [ResUI.PleaseSelectServer];
}
return await Check(item);
}
public async Task<List<string>> Check(ProfileItem? item)
{
if (item is null)
{
return [ResUI.PleaseSelectServer];
}
var errors = new List<string>();
errors.AddRange(await ValidateCurrentNodeAndCoreSupport(item));
errors.AddRange(await ValidateRelatedNodesExistAndValid(item));
return errors;
}
private async Task<List<string>> ValidateCurrentNodeAndCoreSupport(ProfileItem item)
{
if (item.ConfigType == EConfigType.Custom)
{
return [];
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
return await ValidateNodeAndCoreSupport(item, coreType);
}
private async Task<List<string>> ValidateNodeAndCoreSupport(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
coreType ??= AppManager.Instance.GetCoreType(item, item.ConfigType);
if (item.ConfigType is EConfigType.Custom)
{
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
return errors;
}
if (!item.IsComplex())
{
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;
}
}
if (item.ConfigType.IsGroupType())
{
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.ChildItems.IsNullOrEmpty())
{
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;
}
foreach (var child in Utils.String2List(group.ChildItems))
{
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);
}
return errors;
}
var net = item.GetNetwork() ?? item.Network;
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))
{
errors.Add(string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net));
return errors;
}
if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan))
{
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;
}
}
}
else if (coreType is ECoreType.Xray)
{
// Xray core does not support these protocols
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
&& !item.IsComplex())
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
return errors;
}
}
return errors;
}
private async Task<List<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
errors.AddRange(await ValidateProxyChainedNodeExistAndValid(item));
errors.AddRange(await ValidateRoutingNodeExistAndValid(item));
return errors;
}
private async Task<List<string>> ValidateProxyChainedNodeExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
if (item is null)
{
return errors;
}
// prev node and next node
var subItem = await AppManager.Instance.GetSubItem(item.Subid);
if (subItem is null)
{
return errors;
}
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
await CollectProxyChainedNodeValidation(prevNode, subItem.PrevProfile, coreType, errors);
await CollectProxyChainedNodeValidation(nextNode, subItem.NextProfile, coreType, errors);
return errors;
}
private async Task CollectProxyChainedNodeValidation(ProfileItem? node, string tag, ECoreType coreType, List<string> errors)
{
if (node is not null)
{
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
}
else if (tag.IsNotEmpty())
{
errors.Add(ResUI.ProxyChainedPrefix + string.Format(ResUI.NodeTagNotExist, tag));
}
}
private async Task<List<string>> ValidateRoutingNodeExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
if (item is null)
{
return errors;
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null)
{
return errors;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var ruleItem in rules ?? [])
{
if (!ruleItem.Enabled)
{
continue;
}
var outboundTag = ruleItem.OutboundTag;
if (outboundTag.IsNullOrEmpty() || Global.OutboundTags.Contains(outboundTag))
{
continue;
}
var tagItem = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (tagItem is null)
{
errors.Add(ResUI.RoutingRuleOutboundPrefix + string.Format(ResUI.NodeTagNotExist, outboundTag));
continue;
}
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
}
return errors;
}
}

View File

@@ -1,15 +1,14 @@
namespace ServiceLib.Handler;
namespace ServiceLib.Manager;
public sealed class AppHandler
public sealed class AppManager
{
#region Property
private static readonly Lazy<AppHandler> _instance = new(() => new());
private static readonly Lazy<AppManager> _instance = new(() => new());
private Config _config;
private int? _statePort;
private int? _statePort2;
private Job? _processJob;
public static AppHandler Instance => _instance.Value;
public static AppManager Instance => _instance.Value;
public Config Config => _config;
public int StatePort
@@ -34,7 +33,7 @@ public sealed class AppHandler
#endregion Property
#region Init
#region App
public bool InitApp()
{
@@ -64,6 +63,8 @@ public sealed class AppHandler
SQLiteHelper.Instance.CreateTable<RoutingItem>();
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
SQLiteHelper.Instance.CreateTable<DNSItem>();
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
return true;
}
@@ -86,7 +87,46 @@ public sealed class AppHandler
return true;
}
#endregion Init
public async Task AppExitAsync(bool needShutdown)
{
try
{
Logging.SaveLog("AppExitAsync Begin");
await SysProxyHandler.UpdateSysProxy(_config, true);
AppEvents.AppExitRequested.Publish();
await Task.Delay(50); //Wait for AppExitRequested to be processed
await ConfigHandler.SaveConfig(_config);
await ProfileExManager.Instance.SaveTo();
await StatisticsManager.Instance.SaveTo();
await CoreManager.Instance.CoreStop();
StatisticsManager.Instance.Close();
Logging.SaveLog("AppExitAsync End");
}
catch { }
finally
{
if (needShutdown)
{
Shutdown(false);
}
}
}
public void Shutdown(bool byUser)
{
AppEvents.ShutdownRequested.Publish(byUser);
}
public async Task RebootAsAdmin()
{
ProcUtils.RebootAsAdmin();
await AppManager.Instance.AppExitAsync(true);
}
#endregion App
#region Config
@@ -96,21 +136,6 @@ public sealed class AppHandler
return localPort + (int)protocol;
}
public void AddProcess(IntPtr processHandle)
{
if (Utils.IsWindows())
{
_processJob ??= new();
try
{
_processJob?.AddProcess(processHandle);
}
catch
{
}
}
}
#endregion Config
#region SqliteHelper
@@ -183,6 +208,15 @@ public sealed class AppHandler
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();
@@ -203,6 +237,16 @@ public sealed class AppHandler
return await SQLiteHelper.Instance.TableAsync<DNSItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
}
public async Task<List<FullConfigTemplateItem>?> FullConfigTemplateItem()
{
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().ToListAsync();
}
public async Task<FullConfigTemplateItem?> GetFullConfigTemplateItem(ECoreType eCoreType)
{
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
}
#endregion SqliteHelper
#region Core Type

View File

@@ -1,11 +1,11 @@
using static ServiceLib.Models.ClashProxies;
namespace ServiceLib.Handler;
namespace ServiceLib.Manager;
public sealed class ClashApiHandler
public sealed class ClashApiManager
{
private static readonly Lazy<ClashApiHandler> instance = new(() => new());
public static ClashApiHandler Instance => instance.Value;
private static readonly Lazy<ClashApiManager> instance = new(() => new());
public static ClashApiManager Instance => instance.Value;
private static readonly string _tag = "ClashApiHandler";
private Dictionary<string, ProxiesItem>? _proxies;
@@ -35,7 +35,7 @@ public sealed class ClashApiHandler
return null;
}
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Action<ClashProxyModel?, string> updateFunc)
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Func<ClashProxyModel?, string, Task> updateFunc)
{
Task.Run(async () =>
{
@@ -65,7 +65,7 @@ public sealed class ClashApiHandler
return;
}
var urlBase = $"{GetApiUrl()}/proxies";
urlBase += @"/{0}/delay?timeout=10000&url=" + AppHandler.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
urlBase += @"/{0}/delay?timeout=10000&url=" + AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
var tasks = new List<Task>();
foreach (var it in lstProxy)
@@ -79,12 +79,12 @@ public sealed class ClashApiHandler
tasks.Add(Task.Run(async () =>
{
var result = await HttpClientHelper.Instance.TryGetAsync(url);
updateFunc?.Invoke(it, result);
await updateFunc?.Invoke(it, result);
}));
}
await Task.WhenAll(tasks);
await Task.Delay(1000);
updateFunc?.Invoke(null, "");
await updateFunc?.Invoke(null, "");
});
}
@@ -182,6 +182,6 @@ public sealed class ClashApiHandler
private string GetApiUrl()
{
return $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort2}";
return $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort2}";
}
}

View File

@@ -0,0 +1,92 @@
using System.Text;
using CliWrap;
using CliWrap.Buffered;
namespace ServiceLib.Manager;
public class CoreAdminManager
{
private static readonly Lazy<CoreAdminManager> _instance = new(() => new());
public static CoreAdminManager Instance => _instance.Value;
private Config _config;
private Func<bool, string, Task>? _updateFunc;
private int _linuxSudoPid = -1;
private const string _tag = "CoreAdminHandler";
public async Task Init(Config config, Func<bool, string, Task> updateFunc)
{
if (_config != null)
{
return;
}
_config = config;
_updateFunc = updateFunc;
await Task.CompletedTask;
}
private async Task UpdateFunc(bool notify, string msg)
{
await _updateFunc?.Invoke(notify, msg);
}
public async Task<ProcessService?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
{
StringBuilder sb = new();
sb.AppendLine("#!/bin/bash");
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
sb.AppendLine($"sudo -S {cmdLine}");
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
var procService = new ProcessService(
fileName: shFilePath,
arguments: "",
workingDirectory: Utils.GetBinConfigPath(),
displayLog: true,
redirectInput: true,
environmentVars: null,
updateFunc: _updateFunc
);
await procService.StartAsync(AppManager.Instance.LinuxSudoPwd);
if (procService is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
_linuxSudoPid = procService.Id;
return procService;
}
public async Task KillProcessAsLinuxSudo()
{
if (_linuxSudoPid < 0)
{
return;
}
try
{
var shellFileName = Utils.IsOSX() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName;
var shFilePath = await FileManager.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
if (shFilePath.Contains(' '))
{
shFilePath = shFilePath.AppendQuotes();
}
var arg = new List<string>() { "-c", $"sudo -S {shFilePath} {_linuxSudoPid}" };
var result = await Cli.Wrap(Global.LinuxBash)
.WithArguments(arg)
.WithStandardInputPipe(PipeSource.FromString(AppManager.Instance.LinuxSudoPwd))
.ExecuteBufferedAsync();
await UpdateFunc(false, result.StandardOutput.ToString());
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
_linuxSudoPid = -1;
}
}

View File

@@ -1,12 +1,12 @@
namespace ServiceLib.Handler;
namespace ServiceLib.Manager;
public sealed class CoreInfoHandler
public sealed class CoreInfoManager
{
private static readonly Lazy<CoreInfoHandler> _instance = new(() => new());
private static readonly Lazy<CoreInfoManager> _instance = new(() => new());
private List<CoreInfo>? _coreInfo;
public static CoreInfoHandler Instance => _instance.Value;
public static CoreInfoManager Instance => _instance.Value;
public CoreInfoHandler()
public CoreInfoManager()
{
InitCoreInfo();
}
@@ -80,6 +80,10 @@ public sealed class CoreInfoHandler
Url = GetCoreUrl(ECoreType.v2fly),
Match = "V2Ray",
VersionArg = "-version",
Environment = new Dictionary<string, string?>()
{
{ Global.V2RayLocalAsset, Utils.GetBinPath("") },
},
},
new CoreInfo
@@ -90,6 +94,10 @@ public sealed class CoreInfoHandler
Url = GetCoreUrl(ECoreType.v2fly_v5),
Match = "V2Ray",
VersionArg = "version",
Environment = new Dictionary<string, string?>()
{
{ Global.V2RayLocalAsset, Utils.GetBinPath("") },
},
},
new CoreInfo
@@ -107,20 +115,25 @@ public sealed class CoreInfoHandler
DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip",
Match = "Xray",
VersionArg = "-version",
Environment = new Dictionary<string, string?>()
{
{ Global.XrayLocalAsset, Utils.GetBinPath("") },
{ Global.XrayLocalCert, Utils.GetBinPath("") },
},
},
new CoreInfo
{
CoreType = ECoreType.mihomo,
CoreExes = ["mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
CoreExes = ["mihomo-windows-amd64-v1", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
Arguments = "-f {0}" + PortableMode(),
Url = GetCoreUrl(ECoreType.mihomo),
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
DownloadUrlWin64 = urlMihomo + "/download/{0}/mihomo-windows-amd64-compatible-{0}.zip",
DownloadUrlWin64 = urlMihomo + "/download/{0}/mihomo-windows-amd64-v1-{0}.zip",
DownloadUrlWinArm64 = urlMihomo + "/download/{0}/mihomo-windows-arm64-{0}.zip",
DownloadUrlLinux64 = urlMihomo + "/download/{0}/mihomo-linux-amd64-compatible-{0}.gz",
DownloadUrlLinux64 = urlMihomo + "/download/{0}/mihomo-linux-amd64-v1-{0}.gz",
DownloadUrlLinuxArm64 = urlMihomo + "/download/{0}/mihomo-linux-arm64-{0}.gz",
DownloadUrlOSX64 = urlMihomo + "/download/{0}/mihomo-darwin-amd64-compatible-{0}.gz",
DownloadUrlOSX64 = urlMihomo + "/download/{0}/mihomo-darwin-amd64-v1-{0}.gz",
DownloadUrlOSXArm64 = urlMihomo + "/download/{0}/mihomo-darwin-arm64-{0}.gz",
Match = "Mihomo",
VersionArg = "-v",
@@ -130,7 +143,7 @@ public sealed class CoreInfoHandler
{
CoreType = ECoreType.hysteria,
CoreExes = ["hysteria"],
Arguments = "-c {0}",
Arguments = "",
Url = GetCoreUrl(ECoreType.hysteria),
},
@@ -180,7 +193,7 @@ public sealed class CoreInfoHandler
{
CoreType = ECoreType.hysteria2,
CoreExes = ["hysteria-windows-amd64", "hysteria-linux-amd64", "hysteria"],
Arguments = "-c {0}",
Arguments = "",
Url = GetCoreUrl(ECoreType.hysteria2),
},
@@ -205,12 +218,24 @@ public sealed class CoreInfoHandler
new CoreInfo
{
CoreType = ECoreType.shadowquic,
CoreExes = [ "shadowquic", "shadowquic"],
CoreExes = [ "shadowquic" ],
Arguments = "-c {0}",
Url = GetCoreUrl(ECoreType.shadowquic),
AbsolutePath = false,
}
},
new CoreInfo
{
CoreType = ECoreType.mieru,
CoreExes = [ "mieru" ],
Arguments = "run",
Url = GetCoreUrl(ECoreType.mieru),
AbsolutePath = false,
Environment = new Dictionary<string, string?>()
{
{ "MIERU_CONFIG_JSON_FILE", "{0}" },
},
},
];
}

View File

@@ -1,31 +1,25 @@
using System.Diagnostics;
using System.Text;
namespace ServiceLib.Handler;
namespace ServiceLib.Manager;
/// <summary>
/// Core process processing class
/// </summary>
public class CoreHandler
public class CoreManager
{
private static readonly Lazy<CoreHandler> _instance = new(() => new());
public static CoreHandler Instance => _instance.Value;
private static readonly Lazy<CoreManager> _instance = new(() => new());
public static CoreManager Instance => _instance.Value;
private Config _config;
private Process? _process;
private Process? _processPre;
private WindowsJob? _processJob;
private ProcessService? _processService;
private ProcessService? _processPreService;
private bool _linuxSudo = false;
private Action<bool, string>? _updateFunc;
private Func<bool, string, Task>? _updateFunc;
private const string _tag = "CoreHandler";
public async Task Init(Config config, Action<bool, string> updateFunc)
public async Task Init(Config config, Func<bool, string, Task> updateFunc)
{
_config = config;
_updateFunc = updateFunc;
Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable(Global.XrayLocalCert, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
//Copy the bin folder to the storage location (for init)
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
{
@@ -39,7 +33,7 @@ public class CoreHandler
if (Utils.IsNonWindows())
{
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
foreach (var it in coreInfo)
{
if (it.CoreType == ECoreType.v2rayN)
@@ -67,7 +61,7 @@ public class CoreHandler
{
if (node == null)
{
UpdateFunc(false, ResUI.CheckServerSettings);
await UpdateFunc(false, ResUI.CheckServerSettings);
return;
}
@@ -75,13 +69,13 @@ public class CoreHandler
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
if (result.Success != true)
{
UpdateFunc(true, result.Msg);
await UpdateFunc(true, result.Msg);
return;
}
UpdateFunc(false, $"{node.GetSummary()}");
UpdateFunc(false, $"{Utils.GetRuntimeInfo()}");
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
await UpdateFunc(false, $"{node.GetSummary()}");
await UpdateFunc(false, $"{Utils.GetRuntimeInfo()}");
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
await CoreStop();
await Task.Delay(100);
@@ -93,43 +87,37 @@ public class CoreHandler
await CoreStart(node);
await CoreStartPreService(node);
if (_process != null)
if (_processService != null)
{
UpdateFunc(true, $"{node.GetSummary()}");
await UpdateFunc(true, $"{node.GetSummary()}");
}
}
public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
public async Task<ProcessService?> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
{
var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC) ? ECoreType.sing_box : ECoreType.Xray;
var coreType = selecteds.Any(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)) ? ECoreType.sing_box : ECoreType.Xray;
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
UpdateFunc(false, result.Msg);
await UpdateFunc(false, result.Msg);
if (result.Success != true)
{
return -1;
return null;
}
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
UpdateFunc(false, configPath);
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
await UpdateFunc(false, configPath);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null)
{
return -1;
}
return proc.Id;
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
return await RunProcess(coreInfo, fileName, true, false);
}
public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem)
public async Task<ProcessService?> LoadCoreConfigSpeedtest(ServerTestItem testItem)
{
var node = await AppHandler.Instance.GetProfileItem(testItem.IndexId);
var node = await AppManager.Instance.GetProfileItem(testItem.IndexId);
if (node is null)
{
return -1;
return null;
}
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
@@ -137,18 +125,12 @@ public class CoreHandler
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
if (result.Success != true)
{
return -1;
return null;
}
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null)
{
return -1;
}
return proc.Id;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
return await RunProcess(coreInfo, fileName, true, false);
}
public async Task CoreStop()
@@ -157,20 +139,22 @@ public class CoreHandler
{
if (_linuxSudo)
{
await CoreAdminHandler.Instance.KillProcessAsLinuxSudo();
await CoreAdminManager.Instance.KillProcessAsLinuxSudo();
_linuxSudo = false;
}
if (_process != null)
if (_processService != null)
{
await ProcUtils.ProcessKill(_process, Utils.IsWindows());
_process = null;
await _processService.StopAsync();
_processService.Dispose();
_processService = null;
}
if (_processPre != null)
if (_processPreService != null)
{
await ProcUtils.ProcessKill(_processPre, Utils.IsWindows());
_processPre = null;
await _processPreService.StopAsync();
_processPreService.Dispose();
_processPreService = null;
}
}
catch (Exception ex)
@@ -183,8 +167,8 @@ public class CoreHandler
private async Task CoreStart(ProfileItem node)
{
var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
var coreType = _config.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
@@ -192,14 +176,14 @@ public class CoreHandler
{
return;
}
_process = proc;
_processService = proc;
}
private async Task CoreStartPreService(ProfileItem node)
{
if (_process != null && !_process.HasExited)
if (_processService != null && !_processService.HasExited)
{
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
if (itemSocks != null)
{
@@ -208,33 +192,33 @@ public class CoreHandler
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
if (result.Success)
{
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
if (proc is null)
{
return;
}
_processPre = proc;
_processPreService = proc;
}
}
}
}
private void UpdateFunc(bool notify, string msg)
private async Task UpdateFunc(bool notify, string msg)
{
_updateFunc?.Invoke(notify, msg);
await _updateFunc?.Invoke(notify, msg);
}
#endregion Private
#region Process
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
private async Task<ProcessService?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
{
var fileName = CoreInfoHandler.Instance.GetCoreExecFile(coreInfo, out var msg);
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
if (fileName.IsNullOrEmpty())
{
UpdateFunc(false, msg);
await UpdateFunc(false, msg);
return null;
}
@@ -246,8 +230,8 @@ public class CoreHandler
&& Utils.IsNonWindows())
{
_linuxSudo = true;
await CoreAdminHandler.Instance.Init(_config, _updateFunc);
return await CoreAdminHandler.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
await CoreAdminManager.Instance.Init(_config, _updateFunc);
return await CoreAdminManager.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
}
return await RunProcessNormal(fileName, coreInfo, configPath, displayLog);
@@ -255,56 +239,53 @@ public class CoreHandler
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
UpdateFunc(mayNeedSudo, ex.Message);
await UpdateFunc(mayNeedSudo, ex.Message);
return null;
}
}
private async Task<Process?> RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog)
private async Task<ProcessService?> RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog)
{
Process proc = new()
var environmentVars = new Dictionary<string, string>();
foreach (var kv in coreInfo.Environment)
{
StartInfo = new()
{
FileName = fileName,
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardOutput = displayLog,
RedirectStandardError = displayLog,
CreateNoWindow = true,
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
}
};
if (displayLog)
{
void dataHandler(object sender, DataReceivedEventArgs e)
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
}
proc.OutputDataReceived += dataHandler;
proc.ErrorDataReceived += dataHandler;
environmentVars[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath);
}
proc.Start();
if (displayLog)
{
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
}
var procService = new ProcessService(
fileName: fileName,
arguments: string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
workingDirectory: Utils.GetBinConfigPath(),
displayLog: displayLog,
redirectInput: false,
environmentVars: environmentVars,
updateFunc: _updateFunc
);
await procService.StartAsync();
await Task.Delay(100);
AppHandler.Instance.AddProcess(proc.Handle);
if (proc is null or { HasExited: true })
if (procService is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
return proc;
AddProcessJob(procService.Handle);
return procService;
}
private void AddProcessJob(nint processHandle)
{
if (Utils.IsWindows())
{
_processJob ??= new();
try
{
_processJob?.AddProcess(processHandle);
}
catch { }
}
}
#endregion Process

View File

@@ -1,11 +1,9 @@
using ReactiveUI;
namespace ServiceLib.Manager;
namespace ServiceLib.Handler;
public class NoticeHandler
public class NoticeManager
{
private static readonly Lazy<NoticeHandler> _instance = new(() => new());
public static NoticeHandler Instance => _instance.Value;
private static readonly Lazy<NoticeManager> _instance = new(() => new());
public static NoticeManager Instance => _instance.Value;
public void Enqueue(string? content)
{
@@ -13,7 +11,7 @@ public class NoticeHandler
{
return;
}
MessageBus.Current.SendMessage(content, EMsgCommand.SendSnackMsg.ToString());
AppEvents.SendSnackMsgRequested.Publish(content);
}
public void SendMessage(string? content)
@@ -22,7 +20,7 @@ public class NoticeHandler
{
return;
}
MessageBus.Current.SendMessage(content, EMsgCommand.SendMsgView.ToString());
AppEvents.SendMsgViewRequested.Publish(content);
}
public void SendMessageEx(string? content)

View File

@@ -1,19 +1,22 @@
using System.Net.Sockets;
using System.Text;
namespace ServiceLib.Handler;
namespace ServiceLib.Manager;
public class PacHandler
public class PacManager
{
private static string _configPath;
private static int _httpPort;
private static int _pacPort;
private static TcpListener? _tcpListener;
private static byte[] _writeContent;
private static bool _isRunning;
private static bool _needRestart = true;
private static readonly Lazy<PacManager> _instance = new(() => new PacManager());
public static PacManager Instance => _instance.Value;
public static async Task Start(string configPath, int httpPort, int pacPort)
private string _configPath;
private int _httpPort;
private int _pacPort;
private TcpListener? _tcpListener;
private byte[] _writeContent;
private bool _isRunning;
private bool _needRestart = true;
public async Task StartAsync(string configPath, int httpPort, int pacPort)
{
_needRestart = configPath != _configPath || httpPort != _httpPort || pacPort != _pacPort || !_isRunning;
@@ -30,7 +33,7 @@ public class PacHandler
}
}
private static async Task InitText()
private async Task InitText()
{
var path = Path.Combine(_configPath, "pac.txt");
@@ -59,7 +62,7 @@ public class PacHandler
_writeContent = Encoding.UTF8.GetBytes(sb.ToString());
}
private static void RunListener()
private void RunListener()
{
_tcpListener = TcpListener.Create(_pacPort);
_isRunning = true;
@@ -87,14 +90,14 @@ public class PacHandler
}, TaskCreationOptions.LongRunning);
}
private static void WriteContent(TcpClient client)
private void WriteContent(TcpClient client)
{
var stream = client.GetStream();
stream.Write(_writeContent, 0, _writeContent.Length);
stream.Flush();
}
public static void Stop()
public void Stop()
{
if (_tcpListener == null)
{

View File

@@ -2,17 +2,17 @@ using System.Collections.Concurrent;
//using System.Reactive.Linq;
namespace ServiceLib.Handler;
namespace ServiceLib.Manager;
public class ProfileExHandler
public class ProfileExManager
{
private static readonly Lazy<ProfileExHandler> _instance = new(() => new());
private static readonly Lazy<ProfileExManager> _instance = new(() => new());
private ConcurrentBag<ProfileExItem> _lstProfileEx = [];
private readonly Queue<string> _queIndexIds = new();
public static ProfileExHandler Instance => _instance.Value;
public static ProfileExManager Instance => _instance.Value;
private static readonly string _tag = "ProfileExHandler";
public ProfileExHandler()
public ProfileExManager()
{
//Init();
}

View File

@@ -0,0 +1,286 @@
using System.Collections.Concurrent;
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.ChildItems.IsNullOrEmpty())
{
return (new List<ProfileItem>(), profileGroupItem);
}
var items = await GetChildProfileItems(profileGroupItem);
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<HashSet<string>> GetAllChildDomainAddresses(string indexId)
{
// include grand children
var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem.ChildItems.IsNullOrEmpty())
return childAddresses;
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

@@ -1,21 +1,21 @@
namespace ServiceLib.Handler;
namespace ServiceLib.Manager;
public class StatisticsHandler
public class StatisticsManager
{
private static readonly Lazy<StatisticsHandler> instance = new(() => new());
public static StatisticsHandler Instance => instance.Value;
private static readonly Lazy<StatisticsManager> instance = new(() => new());
public static StatisticsManager Instance => instance.Value;
private Config _config;
private ServerStatItem? _serverStatItem;
private List<ServerStatItem> _lstServerStat;
private Action<ServerSpeedItem>? _updateFunc;
private Func<ServerSpeedItem, Task>? _updateFunc;
private StatisticsXrayService? _statisticsXray;
private StatisticsSingboxService? _statisticsSingbox;
private static readonly string _tag = "StatisticsHandler";
public List<ServerStatItem> ServerStat => _lstServerStat;
public async Task Init(Config config, Action<ServerSpeedItem> updateFunc)
public async Task Init(Config config, Func<ServerSpeedItem, Task> updateFunc)
{
_config = config;
_updateFunc = updateFunc;
@@ -91,15 +91,15 @@ public class StatisticsHandler
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )");
long ticks = DateTime.Now.Date.Ticks;
var ticks = DateTime.Now.Date.Ticks;
await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
_lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync();
}
private void UpdateServerStatHandler(ServerSpeedItem server)
private async Task UpdateServerStatHandler(ServerSpeedItem server)
{
_ = UpdateServerStat(server);
await UpdateServerStat(server);
}
private async Task UpdateServerStat(ServerSpeedItem server)
@@ -123,12 +123,12 @@ public class StatisticsHandler
server.TodayDown = _serverStatItem.TodayDown;
server.TotalUp = _serverStatItem.TotalUp;
server.TotalDown = _serverStatItem.TotalDown;
_updateFunc?.Invoke(server);
await _updateFunc?.Invoke(server);
}
private async Task GetServerStatItem(string indexId)
{
long ticks = DateTime.Now.Date.Ticks;
var ticks = DateTime.Now.Date.Ticks;
if (_serverStatItem != null && _serverStatItem.IndexId != indexId)
{
_serverStatItem = null;

View File

@@ -1,16 +1,21 @@
namespace ServiceLib.Handler;
namespace ServiceLib.Manager;
public class TaskHandler
public class TaskManager
{
private static readonly Lazy<TaskHandler> _instance = new(() => new());
public static TaskHandler Instance => _instance.Value;
private static readonly Lazy<TaskManager> _instance = new(() => new());
public static TaskManager Instance => _instance.Value;
private Config _config;
private Func<bool, string, Task>? _updateFunc;
public void RegUpdateTask(Config config, Action<bool, string> updateFunc)
public void RegUpdateTask(Config config, Func<bool, string, Task> updateFunc)
{
Task.Run(() => ScheduledTasks(config, updateFunc));
_config = config;
_updateFunc = updateFunc;
Task.Run(ScheduledTasks);
}
private async Task ScheduledTasks(Config config, Action<bool, string> updateFunc)
private async Task ScheduledTasks()
{
Logging.SaveLog("Setup Scheduled Tasks");
@@ -21,15 +26,15 @@ public class TaskHandler
await Task.Delay(1000 * 60);
//Execute once 1 minute
await UpdateTaskRunSubscription(config, updateFunc);
await UpdateTaskRunSubscription();
//Execute once 20 minute
if (numOfExecuted % 20 == 0)
{
//Logging.SaveLog("Execute save config");
await ConfigHandler.SaveConfig(config);
await ProfileExHandler.Instance.SaveTo();
await ConfigHandler.SaveConfig(_config);
await ProfileExManager.Instance.SaveTo();
}
//Execute once 1 hour
@@ -42,17 +47,17 @@ public class TaskHandler
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
//Check once 1 hour
await UpdateTaskRunGeo(config, numOfExecuted / 60, updateFunc);
await UpdateTaskRunGeo(numOfExecuted / 60);
}
numOfExecuted++;
}
}
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> updateFunc)
private async Task UpdateTaskRunSubscription()
{
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds();
var lstSubs = (await AppHandler.Instance.SubItems())?
var lstSubs = (await AppManager.Instance.SubItems())?
.Where(t => t.AutoUpdateInterval > 0)
.Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60)
.ToList();
@@ -63,34 +68,33 @@ public class TaskHandler
}
Logging.SaveLog("Execute update subscription");
var updateHandle = new UpdateService();
foreach (var item in lstSubs)
{
await updateHandle.UpdateSubscriptionProcess(config, item.Id, true, (bool success, string msg) =>
await SubscriptionHandler.UpdateProcess(_config, item.Id, true, async (success, msg) =>
{
updateFunc?.Invoke(success, msg);
await _updateFunc?.Invoke(success, msg);
if (success)
{
Logging.SaveLog($"Update subscription end. {msg}");
}
});
item.UpdateTime = updateTime;
await ConfigHandler.AddSubItem(config, item);
await ConfigHandler.AddSubItem(_config, item);
await Task.Delay(1000);
}
}
private async Task UpdateTaskRunGeo(Config config, int hours, Action<bool, string> updateFunc)
private async Task UpdateTaskRunGeo(int hours)
{
if (config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % config.GuiItem.AutoUpdateInterval == 0)
if (_config.GuiItem.AutoUpdateInterval > 0 && hours > 0 && hours % _config.GuiItem.AutoUpdateInterval == 0)
{
Logging.SaveLog("Execute update geo files");
var updateHandle = new UpdateService();
await updateHandle.UpdateGeoFileAll(config, (bool success, string msg) =>
await updateHandle.UpdateGeoFileAll(_config, async (success, msg) =>
{
updateFunc?.Invoke(false, msg);
await _updateFunc?.Invoke(false, msg);
});
}
}

View File

@@ -1,12 +1,12 @@
using System.Net;
using WebDav;
namespace ServiceLib.Handler;
namespace ServiceLib.Manager;
public sealed class WebDavHandler
public sealed class WebDavManager
{
private static readonly Lazy<WebDavHandler> _instance = new(() => new());
public static WebDavHandler Instance => _instance.Value;
private static readonly Lazy<WebDavManager> _instance = new(() => new());
public static WebDavManager Instance => _instance.Value;
private readonly Config? _config;
private WebDavClient? _client;
@@ -15,9 +15,9 @@ public sealed class WebDavHandler
private readonly string _webFileName = "backup.zip";
private readonly string _tag = "WebDav--";
public WebDavHandler()
public WebDavManager()
{
_config = AppHandler.Instance.Config;
_config = AppManager.Instance.Config;
}
private async Task<bool> GetClient()

View File

@@ -1,10 +1,13 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models;
public class CheckUpdateModel
public class CheckUpdateModel : ReactiveObject
{
public bool? IsSelected { get; set; }
public string? CoreType { get; set; }
public string? Remarks { get; set; }
[Reactive] public string? Remarks { get; set; }
public string? FileName { get; set; }
public bool? IsFinished { get; set; }
}

View File

@@ -1,7 +1,10 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models;
[Serializable]
public class ClashProxyModel
public class ClashProxyModel : ReactiveObject
{
public string? Name { get; set; }
@@ -9,9 +12,9 @@ public class ClashProxyModel
public string? Now { get; set; }
public int Delay { get; set; }
[Reactive] public int Delay { get; set; }
public string? DelayName { get; set; }
[Reactive] public string? DelayName { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -48,6 +48,7 @@ public class Config
public List<InItem> Inbound { get; set; }
public List<KeyEventItem> GlobalHotkeys { get; set; }
public List<CoreTypeItem> CoreTypeItem { get; set; }
public SimpleDNSItem SimpleDNSItem { get; set; }
#endregion other entities
}

View File

@@ -142,6 +142,7 @@ public class CoreTypeItem
public class TunModeItem
{
public bool EnableTun { get; set; }
public bool AutoRoute { get; set; } = true;
public bool StrictRoute { get; set; } = true;
public string Stack { get; set; }
public int Mtu { get; set; }
@@ -164,7 +165,6 @@ public class RoutingBasicItem
{
public string DomainStrategy { get; set; }
public string DomainStrategy4Singbox { get; set; }
public string DomainMatcher { get; set; }
public string RoutingIndexId { get; set; }
}
@@ -253,3 +253,20 @@ public class WindowSizeItem
public int Width { get; set; }
public int Height { get; set; }
}
[Serializable]
public class SimpleDNSItem
{
public bool? UseSystemHosts { get; set; }
public bool? AddCommonHosts { get; set; }
public bool? FakeIP { get; set; }
public bool? GlobalFakeIp { get; set; }
public bool? BlockBindingQuery { get; set; }
public string? DirectDNS { get; set; }
public string? RemoteDNS { get; set; }
public string? RayStrategy4Freedom { get; set; }
public string? SingboxStrategy4Direct { get; set; }
public string? SingboxStrategy4Proxy { get; set; }
public string? Hosts { get; set; }
public string? DirectExpectedIPs { get; set; }
}

View File

@@ -17,4 +17,5 @@ public class CoreInfo
public string? Match { get; set; }
public string? VersionArg { get; set; }
public bool AbsolutePath { get; set; }
public IDictionary<string, string?> Environment { get; set; } = new Dictionary<string, string?>();
}

View File

@@ -9,7 +9,7 @@ public class DNSItem
public string Id { get; set; }
public string Remarks { get; set; }
public bool Enabled { get; set; } = true;
public bool Enabled { get; set; } = false;
public ECoreType CoreType { get; set; }
public bool UseSystemHosts { get; set; }
public string? NormalDNS { get; set; }

View File

@@ -0,0 +1,18 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]
public class FullConfigTemplateItem
{
[PrimaryKey]
public string Id { get; set; }
public string Remarks { get; set; }
public bool Enabled { get; set; } = false;
public ECoreType CoreType { get; set; }
public string? Config { get; set; }
public string? TunConfig { get; set; }
public bool? AddProxyOnly { get; set; } = false;
public string? ProxyDetour { get; set; }
}

View File

@@ -0,0 +1,14 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]
public class ProfileGroupItem
{
[PrimaryKey]
public string IndexId { get; set; }
public string ChildItems { get; set; }
public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing;
}

View File

@@ -32,18 +32,21 @@ public class ProfileItem : ReactiveObject
public string GetSummary()
{
var summary = $"[{(ConfigType).ToString()}] ";
var arrAddr = Address.Contains(':') ? Address.Split(':') : Address.Split('.');
var addr = arrAddr.Length switch
if (IsComplex())
{
> 2 => $"{arrAddr.First()}***{arrAddr.Last()}",
> 1 => $"***{arrAddr.Last()}",
_ => Address
};
summary += ConfigType switch
summary += $"[{CoreType.ToString()}]{Remarks}";
}
else
{
EConfigType.Custom => $"[{CoreType.ToString()}]{Remarks}",
_ => $"{Remarks}({addr}:{Port})"
};
var arrAddr = Address.Contains(':') ? Address.Split(':') : Address.Split('.');
var addr = arrAddr.Length switch
{
> 2 => $"{arrAddr.First()}***{arrAddr.Last()}",
> 1 => $"***{arrAddr.Last()}",
_ => Address
};
summary += $"{Remarks}({addr}:{Port})";
}
return summary;
}
@@ -61,6 +64,51 @@ public class ProfileItem : ReactiveObject
return Network.TrimEx();
}
public bool IsComplex()
{
return ConfigType.IsComplexType();
}
public bool IsValid()
{
if (IsComplex())
return true;
if (Address.IsNullOrEmpty() || Port is <= 0 or >= 65536)
return false;
switch (ConfigType)
{
case EConfigType.VMess:
if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id))
return false;
break;
case EConfigType.VLESS:
if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30))
return false;
if (!Global.Flows.Contains(Flow))
return false;
break;
case EConfigType.Shadowsocks:
if (Id.IsNullOrEmpty())
return false;
if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security))
return false;
break;
}
if ((ConfigType is EConfigType.VLESS or EConfigType.Trojan)
&& StreamSecurity == Global.StreamSecurityReality
&& PublicKey.IsNullOrEmpty())
{
return false;
}
return true;
}
#endregion function
[PrimaryKey]
@@ -93,6 +141,7 @@ public class ProfileItem : ReactiveObject
public string PublicKey { get; set; }
public string ShortId { get; set; }
public string SpiderX { get; set; }
public string Mldsa65Verify { get; set; }
public string Extra { get; set; }
public bool? MuxEnabled { get; set; }
}

View File

@@ -15,4 +15,5 @@ public class RulesItem
public List<string>? Process { get; set; }
public bool Enabled { get; set; } = true;
public string? Remarks { get; set; }
public ERuleType? RuleType { get; set; }
}

View File

@@ -7,4 +7,5 @@ public class RulesItemModel : RulesItem
public string Ips { get; set; }
public string Domains { get; set; }
public string Protocols { get; set; }
public string RuleTypeName { get; set; }
}

View File

@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models;
public class SingboxConfig
@@ -6,6 +8,7 @@ public class SingboxConfig
public Dns4Sbox? dns { get; set; }
public List<Inbound4Sbox> inbounds { get; set; }
public List<Outbound4Sbox> outbounds { get; set; }
public List<Endpoints4Sbox>? endpoints { get; set; }
public Route4Sbox route { get; set; }
public Experimental4Sbox? experimental { get; set; }
}
@@ -29,14 +32,15 @@ public class Dns4Sbox
public bool? independent_cache { get; set; }
public bool? reverse_mapping { get; set; }
public string? client_subnet { get; set; }
public Fakeip4Sbox? fakeip { get; set; }
}
public class Route4Sbox
{
public Rule4Sbox? default_domain_resolver { get; set; } // or string
public bool? auto_detect_interface { get; set; }
public List<Rule4Sbox> rules { get; set; }
public List<Ruleset4Sbox>? rule_set { get; set; }
public string? final { get; set; }
}
[Serializable]
@@ -49,6 +53,7 @@ public class Rule4Sbox
public string? mode { get; set; }
public bool? ip_is_private { get; set; }
public string? client_subnet { get; set; }
public int? rewrite_ttl { get; set; }
public bool? invert { get; set; }
public string? clash_mode { get; set; }
public List<string>? inbound { get; set; }
@@ -67,6 +72,27 @@ public class Rule4Sbox
public List<string>? process_name { get; set; }
public List<string>? rule_set { get; set; }
public List<Rule4Sbox>? rules { get; set; }
public string? action { get; set; }
public string? strategy { get; set; }
public List<string>? sniffer { get; set; }
public string? rcode { get; set; }
public List<int>? query_type { get; set; }
public List<string>? answer { get; set; }
public List<string>? ns { get; set; }
public List<string>? extra { get; set; }
public string? method { get; set; }
public bool? no_drop { get; set; }
public bool? source_ip_is_private { get; set; }
public bool? ip_accept_any { get; set; }
public int? source_port { get; set; }
public List<string>? source_port_range { get; set; }
public List<string>? network_type { get; set; }
public bool? network_is_expensive { get; set; }
public bool? network_is_constrained { get; set; }
public List<string>? wifi_ssid { get; set; }
public List<string>? wifi_bssid { get; set; }
public bool? rule_set_ip_cidr_match_source { get; set; }
public bool? rule_set_ip_cidr_accept_empty { get; set; }
}
[Serializable]
@@ -76,7 +102,6 @@ public class Inbound4Sbox
public string tag { get; set; }
public string listen { get; set; }
public int? listen_port { get; set; }
public string? domain_strategy { get; set; }
public string interface_name { get; set; }
public List<string>? address { get; set; }
public int? mtu { get; set; }
@@ -84,8 +109,6 @@ public class Inbound4Sbox
public bool? strict_route { get; set; }
public bool? endpoint_independent_nat { get; set; }
public string? stack { get; set; }
public bool? sniff { get; set; }
public bool? sniff_override_destination { get; set; }
public List<User4Sbox> users { get; set; }
}
@@ -95,10 +118,8 @@ public class User4Sbox
public string password { get; set; }
}
public class Outbound4Sbox
public class Outbound4Sbox : BaseServer4Sbox
{
public string type { get; set; }
public string tag { get; set; }
public string? server { get; set; }
public int? server_port { get; set; }
public List<string>? server_ports { get; set; }
@@ -113,7 +134,6 @@ public class Outbound4Sbox
public int? recv_window_conn { get; set; }
public int? recv_window { get; set; }
public bool? disable_mtu_discovery { get; set; }
public string? detour { get; set; }
public string? method { get; set; }
public string? username { get; set; }
public string? password { get; set; }
@@ -121,19 +141,35 @@ public class Outbound4Sbox
public string? version { get; set; }
public string? network { get; set; }
public string? packet_encoding { get; set; }
public List<string>? local_address { get; set; }
public string? private_key { get; set; }
public string? peer_public_key { get; set; }
public List<int>? reserved { get; set; }
public int? mtu { get; set; }
public string? plugin { get; set; }
public string? plugin_opts { get; set; }
public Tls4Sbox? tls { get; set; }
public Multiplex4Sbox? multiplex { get; set; }
public Transport4Sbox? transport { get; set; }
public HyObfs4Sbox? obfs { get; set; }
public List<string>? outbounds { get; set; }
public bool? interrupt_exist_connections { get; set; }
public int? tolerance { get; set; }
}
public class Endpoints4Sbox : BaseServer4Sbox
{
public bool? system { get; set; }
public string? name { get; set; }
public int? mtu { get; set; }
public List<string> address { get; set; }
public string private_key { get; set; }
public int? listen_port { get; set; }
public string? udp_timeout { get; set; }
public int? workers { get; set; }
public List<Peer4Sbox> peers { get; set; }
}
public class Peer4Sbox
{
public string address { get; set; }
public int port { get; set; }
public string public_key { get; set; }
public string? pre_shared_key { get; set; }
public List<string> allowed_ips { get; set; }
public int? persistent_keepalive_interval { get; set; }
public List<int> reserved { get; set; }
}
public class Tls4Sbox
@@ -144,6 +180,9 @@ public class Tls4Sbox
public List<string>? alpn { get; set; }
public Utls4Sbox? utls { get; set; }
public Reality4Sbox? reality { get; set; }
public bool? fragment { get; set; }
public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; }
}
public class Multiplex4Sbox
@@ -191,15 +230,28 @@ public class HyObfs4Sbox
public string? password { get; set; }
}
public class Server4Sbox
public class Server4Sbox : BaseServer4Sbox
{
public string? tag { get; set; }
public string? inet4_range { get; set; }
public string? inet6_range { get; set; }
public string? client_subnet { get; set; }
public string? server { get; set; }
public new string? domain_resolver { get; set; }
[JsonPropertyName("interface")] public string? Interface { get; set; }
public int? server_port { get; set; }
public string? path { get; set; }
public Headers4Sbox? headers { get; set; }
// public List<string>? path { get; set; } // hosts
public Dictionary<string, List<string>>? predefined { get; set; }
// Deprecated
public string? address { get; set; }
public string? address_resolver { get; set; }
public string? address_strategy { get; set; }
public string? strategy { get; set; }
public string? detour { get; set; }
public string? client_subnet { get; set; }
// Deprecated End
}
public class Experimental4Sbox
@@ -229,13 +281,6 @@ public class Stats4Sbox
public List<string>? users { get; set; }
}
public class Fakeip4Sbox
{
public bool enabled { get; set; }
public string inet4_range { get; set; }
public string inet6_range { get; set; }
}
public class CacheFile4Sbox
{
public bool enabled { get; set; }
@@ -254,3 +299,33 @@ public class Ruleset4Sbox
public string? download_detour { get; set; }
public string? update_interval { get; set; }
}
public abstract class DialFields4Sbox
{
public string? detour { get; set; }
public string? bind_interface { get; set; }
public string? inet4_bind_address { get; set; }
public string? inet6_bind_address { get; set; }
public int? routing_mark { get; set; }
public bool? reuse_addr { get; set; }
public string? netns { get; set; }
public string? connect_timeout { get; set; }
public bool? tcp_fast_open { get; set; }
public bool? tcp_multi_path { get; set; }
public bool? udp_fragment { get; set; }
public Rule4Sbox? domain_resolver { get; set; } // or string
public string? network_strategy { get; set; }
public List<string>? network_type { get; set; }
public List<string>? fallback_network_type { get; set; }
public string? fallback_delay { get; set; }
public Tls4Sbox? tls { get; set; }
public Multiplex4Sbox? multiplex { get; set; }
public Transport4Sbox? transport { get; set; }
public HyObfs4Sbox? obfs { get; set; }
}
public abstract class BaseServer4Sbox : DialFields4Sbox
{
public string type { get; set; }
public string tag { get; set; }
}

View File

@@ -5,7 +5,7 @@ namespace ServiceLib.Models;
public class V2rayConfig
{
public Log4Ray log { get; set; }
public object dns { get; set; }
public Dns4Ray dns { get; set; }
public List<Inbounds4Ray> inbounds { get; set; }
public List<Outbounds4Ray> outbounds { get; set; }
public Routing4Ray routing { get; set; }
@@ -203,7 +203,15 @@ public class Response4Ray
public class Dns4Ray
{
public List<string> servers { get; set; }
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 string? tag { get; set; }
}
public class DnsServer4Ray
@@ -211,14 +219,20 @@ public class DnsServer4Ray
public string? address { get; set; }
public List<string>? domains { get; set; }
public bool? skipFallback { get; set; }
public List<string>? expectedIPs { get; set; }
public List<string>? unexpectedIPs { get; set; }
public string? clientIp { get; set; }
public string? queryStrategy { get; set; }
public int? timeoutMs { get; set; }
public bool? disableCache { get; set; }
public bool? finalQuery { get; set; }
public string? tag { get; set; }
}
public class Routing4Ray
{
public string domainStrategy { get; set; }
public string? domainMatcher { get; set; }
public List<RulesItem4Ray> rules { get; set; }
public List<BalancersItem4Ray>? balancers { get; set; }
@@ -340,6 +354,7 @@ public class TlsSettings4Ray
public string? publicKey { get; set; }
public string? shortId { get; set; }
public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; }
}
public class TcpSettings4Ray

View File

@@ -114,6 +114,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support network type &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string CoreNotSupportNetwork {
get {
return ResourceManager.GetString("CoreNotSupportNetwork", resourceCulture);
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support protocol &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string CoreNotSupportProtocol {
get {
return ResourceManager.GetString("CoreNotSupportProtocol", resourceCulture);
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support protocol &apos;{1}&apos; when using transport &apos;{2}&apos;. 的本地化字符串。
/// </summary>
public static string CoreNotSupportProtocolTransport {
get {
return ResourceManager.GetString("CoreNotSupportProtocolTransport", resourceCulture);
}
}
/// <summary>
/// 查找类似 Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. 的本地化字符串。
/// </summary>
@@ -186,6 +213,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Please fill in the correct config template 的本地化字符串。
/// </summary>
public static string FillCorrectConfigTemplateText {
get {
return ResourceManager.GetString("FillCorrectConfigTemplateText", resourceCulture);
}
}
/// <summary>
/// 查找类似 Please fill in the correct custom DNS 的本地化字符串。
/// </summary>
@@ -258,6 +294,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Group &apos;{0}&apos; is empty. Please add at least one node. 的本地化字符串。
/// </summary>
public static string GroupEmpty {
get {
return ResourceManager.GetString("GroupEmpty", resourceCulture);
}
}
/// <summary>
/// 查找类似 {0} Group cannot reference itself or have a circular reference 的本地化字符串。
/// </summary>
public static string GroupSelfReference {
get {
return ResourceManager.GetString("GroupSelfReference", resourceCulture);
}
}
/// <summary>
/// 查找类似 This is not the correct configuration, please check 的本地化字符串。
/// </summary>
@@ -285,6 +339,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 The {0} property is invalid, please check. 的本地化字符串。
/// </summary>
public static string InvalidProperty {
get {
return ResourceManager.GetString("InvalidProperty", resourceCulture);
}
}
/// <summary>
/// 查找类似 Invalid address (URL) 的本地化字符串。
/// </summary>
@@ -654,6 +717,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Add [Anytls] Configuration 的本地化字符串。
/// </summary>
public static string menuAddAnytlsServer {
get {
return ResourceManager.GetString("menuAddAnytlsServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add Child Configuration 的本地化字符串。
/// </summary>
public static string menuAddChildServer {
get {
return ResourceManager.GetString("menuAddChildServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。
/// </summary>
@@ -681,6 +762,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Add Policy Group Configuration 的本地化字符串。
/// </summary>
public static string menuAddPolicyGroupServer {
get {
return ResourceManager.GetString("menuAddPolicyGroupServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add Proxy Chain Configuration 的本地化字符串。
/// </summary>
public static string menuAddProxyChainServer {
get {
return ResourceManager.GetString("menuAddProxyChainServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。
/// </summary>
@@ -924,6 +1023,87 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Full Config Template Setting 的本地化字符串。
/// </summary>
public static string menuFullConfigTemplate {
get {
return ResourceManager.GetString("menuFullConfigTemplate", resourceCulture);
}
}
/// <summary>
/// 查找类似 Generate Policy Group from Multiple Profiles 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServer {
get {
return ResourceManager.GetString("menuGenGroupMultipleServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration Fallback by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxFallback {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxLeastPing {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration Fallback by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayFallback {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastLoad {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastLoad", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastPing {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRandom {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRandom", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRoundRobin {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRoundRobin", resourceCulture);
}
}
/// <summary>
/// 查找类似 Global Hotkey Setting 的本地化字符串。
/// </summary>
@@ -1293,6 +1473,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Remove Child Configuration 的本地化字符串。
/// </summary>
public static string menuRemoveChildServer {
get {
return ResourceManager.GetString("menuRemoveChildServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Remove duplicate Configurations 的本地化字符串。
/// </summary>
@@ -1446,6 +1635,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Server List 的本地化字符串。
/// </summary>
public static string menuServerList {
get {
return ResourceManager.GetString("menuServerList", resourceCulture);
}
}
/// <summary>
/// 查找类似 Configurations 的本地化字符串。
/// </summary>
@@ -1455,60 +1653,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Multi-Configuration to custom configuration 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServer {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerSingBoxLeastPing {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerSingBoxLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerXrayLeastLoad {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayLeastLoad", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerXrayLeastPing {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerXrayRandom {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayRandom", resourceCulture);
}
}
/// <summary>
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
/// </summary>
public static string menuSetDefaultMultipleServerXrayRoundRobin {
get {
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayRoundRobin", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set as active Configuration (Enter) 的本地化字符串。
/// </summary>
@@ -1833,6 +1977,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Start parsing and processing subscription content 的本地化字符串。
/// </summary>
public static string MsgStartParsingSubscription {
get {
return ResourceManager.GetString("MsgStartParsingSubscription", resourceCulture);
}
}
/// <summary>
/// 查找类似 Started updating {0}... 的本地化字符串。
/// </summary>
@@ -1905,6 +2058,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Node alias &apos;{0}&apos; does not exist. 的本地化字符串。
/// </summary>
public static string NodeTagNotExist {
get {
return ResourceManager.GetString("NodeTagNotExist", resourceCulture);
}
}
/// <summary>
/// 查找类似 Non-VMess or SS protocol 的本地化字符串。
/// </summary>
@@ -1932,6 +2094,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Not support protocol &apos;{0}&apos;. 的本地化字符串。
/// </summary>
public static string NotSupportProtocol {
get {
return ResourceManager.GetString("NotSupportProtocol", resourceCulture);
}
}
/// <summary>
/// 查找类似 Scan completed, no valid QR code found 的本地化字符串。
/// </summary>
@@ -1959,6 +2130,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Please Add At Least One Configuration 的本地化字符串。
/// </summary>
public static string PleaseAddAtLeastOneServer {
get {
return ResourceManager.GetString("PleaseAddAtLeastOneServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Please fill Remarks 的本地化字符串。
/// </summary>
@@ -2004,6 +2184,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Policy group: 的本地化字符串。
/// </summary>
public static string PolicyGroupPrefix {
get {
return ResourceManager.GetString("PolicyGroupPrefix", resourceCulture);
}
}
/// <summary>
/// 查找类似 Proxy chained: 的本地化字符串。
/// </summary>
public static string ProxyChainedPrefix {
get {
return ResourceManager.GetString("ProxyChainedPrefix", resourceCulture);
}
}
/// <summary>
/// 查找类似 Global hotkey {0} registration failed, reason: {1} 的本地化字符串。
/// </summary>
@@ -2067,6 +2265,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Routing rule outbound: 的本地化字符串。
/// </summary>
public static string RoutingRuleOutboundPrefix {
get {
return ResourceManager.GetString("RoutingRuleOutboundPrefix", resourceCulture);
}
}
/// <summary>
/// 查找类似 Run as Admin 的本地化字符串。
/// </summary>
@@ -2211,6 +2418,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Add Common DNS Hosts 的本地化字符串。
/// </summary>
public static string TbAddCommonDNSHosts {
get {
return ResourceManager.GetString("TbAddCommonDNSHosts", resourceCulture);
}
}
/// <summary>
/// 查找类似 Do Not Add Non-Proxy Protocol Outbound 的本地化字符串。
/// </summary>
public static string TbAddProxyProtocolOutboundOnly {
get {
return ResourceManager.GetString("TbAddProxyProtocolOutboundOnly", resourceCulture);
}
}
/// <summary>
/// 查找类似 Address 的本地化字符串。
/// </summary>
@@ -2274,6 +2499,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Block SVCB and HTTPS Queries 的本地化字符串。
/// </summary>
public static string TbBlockSVCBHTTPSQueries {
get {
return ResourceManager.GetString("TbBlockSVCBHTTPSQueries", resourceCulture);
}
}
/// <summary>
/// 查找类似 Block ECH and HTTP/3 availability checks when enabled 的本地化字符串。
/// </summary>
public static string TbBlockSVCBHTTPSQueriesTips {
get {
return ResourceManager.GetString("TbBlockSVCBHTTPSQueriesTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Browse 的本地化字符串。
/// </summary>
@@ -2301,6 +2544,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Policy Group 的本地化字符串。
/// </summary>
public static string TbConfigTypePolicyGroup {
get {
return ResourceManager.GetString("TbConfigTypePolicyGroup", resourceCulture);
}
}
/// <summary>
/// 查找类似 Proxy Chain 的本地化字符串。
/// </summary>
public static string TbConfigTypeProxyChain {
get {
return ResourceManager.GetString("TbConfigTypeProxyChain", resourceCulture);
}
}
/// <summary>
/// 查找类似 Confirm 的本地化字符串。
/// </summary>
@@ -2328,6 +2589,42 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Enable Custom DNS 的本地化字符串。
/// </summary>
public static string TbCustomDNSEnable {
get {
return ResourceManager.GetString("TbCustomDNSEnable", resourceCulture);
}
}
/// <summary>
/// 查找类似 Custom DNS Enabled, This Page&apos;s Settings Invalid 的本地化字符串。
/// </summary>
public static string TbCustomDNSEnabledPageInvalid {
get {
return ResourceManager.GetString("TbCustomDNSEnabledPageInvalid", resourceCulture);
}
}
/// <summary>
/// 查找类似 V2ray Custom DNS 的本地化字符串。
/// </summary>
public static string TbCustomDnsRay {
get {
return ResourceManager.GetString("TbCustomDnsRay", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Custom DNS 的本地化字符串。
/// </summary>
public static string TbCustomDnsSingbox {
get {
return ResourceManager.GetString("TbCustomDnsSingbox", resourceCulture);
}
}
/// <summary>
/// 查找类似 Display GUI 的本地化字符串。
/// </summary>
@@ -2346,6 +2643,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 DNS Hosts: (&quot;domain1 ip1 ip2&quot; per line) 的本地化字符串。
/// </summary>
public static string TbDNSHostsConfig {
get {
return ResourceManager.GetString("TbDNSHostsConfig", resourceCulture);
}
}
/// <summary>
/// 查找类似 Supports DNS Object; Click to view documentation 的本地化字符串。
/// </summary>
@@ -2364,15 +2670,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Domain Matcher 的本地化字符串。
/// </summary>
public static string TbdomainMatcher {
get {
return ResourceManager.GetString("TbdomainMatcher", resourceCulture);
}
}
/// <summary>
/// 查找类似 Domain strategy 的本地化字符串。
/// </summary>
@@ -2391,6 +2688,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Domestic DNS 的本地化字符串。
/// </summary>
public static string TbDomesticDNS {
get {
return ResourceManager.GetString("TbDomesticDNS", resourceCulture);
}
}
/// <summary>
/// 查找类似 Edit 的本地化字符串。
/// </summary>
@@ -2409,6 +2715,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 FakeIP 的本地化字符串。
/// </summary>
public static string TbFakeIP {
get {
return ResourceManager.GetString("TbFakeIP", resourceCulture);
}
}
/// <summary>
/// 查找类似 Applies globally by default, with built-in FakeIP filtering (sing-box only). 的本地化字符串。
/// </summary>
public static string TbFakeIPTips {
get {
return ResourceManager.GetString("TbFakeIPTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fallback 的本地化字符串。
/// </summary>
public static string TbFallback {
get {
return ResourceManager.GetString("TbFallback", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fingerprint 的本地化字符串。
/// </summary>
@@ -2427,6 +2760,24 @@ namespace ServiceLib.Resx {
}
}
/// <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>
public static string TbFullConfigTemplateDesc {
get {
return ResourceManager.GetString("TbFullConfigTemplateDesc", resourceCulture);
}
}
/// <summary>
/// 查找类似 Enable Full Config Template 的本地化字符串。
/// </summary>
public static string TbFullConfigTemplateEnable {
get {
return ResourceManager.GetString("TbFullConfigTemplateEnable", resourceCulture);
}
}
/// <summary>
/// 查找类似 Global Hotkey Settings 的本地化字符串。
/// </summary>
@@ -2508,6 +2859,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Most Stable 的本地化字符串。
/// </summary>
public static string TbLeastLoad {
get {
return ResourceManager.GetString("TbLeastLoad", resourceCulture);
}
}
/// <summary>
/// 查找类似 Lowest Latency 的本地化字符串。
/// </summary>
public static string TbLeastPing {
get {
return ResourceManager.GetString("TbLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Address (IPv4, IPv6) 的本地化字符串。
/// </summary>
@@ -2517,6 +2886,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Mldsa65Verify 的本地化字符串。
/// </summary>
public static string TbMldsa65Verify {
get {
return ResourceManager.GetString("TbMldsa65Verify", resourceCulture);
}
}
/// <summary>
/// 查找类似 Transport protocol(network) 的本地化字符串。
/// </summary>
@@ -2553,6 +2931,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Policy Group Type 的本地化字符串。
/// </summary>
public static string TbPolicyGroupType {
get {
return ResourceManager.GetString("TbPolicyGroupType", resourceCulture);
}
}
/// <summary>
/// 查找类似 Port 的本地化字符串。
/// </summary>
@@ -2625,6 +3012,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Random 的本地化字符串。
/// </summary>
public static string TbRandom {
get {
return ResourceManager.GetString("TbRandom", resourceCulture);
}
}
/// <summary>
/// 查找类似 v2ray Full Config Template 的本地化字符串。
/// </summary>
public static string TbRayFullConfigTemplate {
get {
return ResourceManager.GetString("TbRayFullConfigTemplate", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document 的本地化字符串。
/// </summary>
public static string TbRayFullConfigTemplateDesc {
get {
return ResourceManager.GetString("TbRayFullConfigTemplateDesc", resourceCulture);
}
}
/// <summary>
/// 查找类似 Alias (remarks) 的本地化字符串。
/// </summary>
@@ -2634,6 +3048,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Remote DNS 的本地化字符串。
/// </summary>
public static string TbRemoteDNS {
get {
return ResourceManager.GetString("TbRemoteDNS", resourceCulture);
}
}
/// <summary>
/// 查找类似 Via proxy — please ensure remote availability 的本地化字符串。
/// </summary>
public static string TbRemoteDNSTips {
get {
return ResourceManager.GetString("TbRemoteDNSTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Camouflage domain(host) 的本地化字符串。
/// </summary>
@@ -2661,6 +3093,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Round Robin 的本地化字符串。
/// </summary>
public static string TbRoundRobin {
get {
return ResourceManager.GetString("TbRoundRobin", resourceCulture);
}
}
/// <summary>
/// 查找类似 socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。
/// </summary>
@@ -2742,6 +3183,60 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Rule Type 的本地化字符串。
/// </summary>
public static string TbRuleType {
get {
return ResourceManager.GetString("TbRuleType", resourceCulture);
}
}
/// <summary>
/// 查找类似 You can set separate rules for Routing and DNS, or select &quot;ALL&quot; to apply to both 的本地化字符串。
/// </summary>
public static string TbRuleTypeTips {
get {
return ResourceManager.GetString("TbRuleTypeTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbSBDirectResolveStrategy {
get {
return ResourceManager.GetString("TbSBDirectResolveStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Full Config Template 的本地化字符串。
/// </summary>
public static string TbSBFullConfigTemplate {
get {
return ResourceManager.GetString("TbSBFullConfigTemplate", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add Outbound and Endpoint Config Only, Click to view the document 的本地化字符串。
/// </summary>
public static string TbSBFullConfigTemplateDesc {
get {
return ResourceManager.GetString("TbSBFullConfigTemplateDesc", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Remote Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbSBRemoteResolveStrategy {
get {
return ResourceManager.GetString("TbSBRemoteResolveStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 Encryption method (security) 的本地化字符串。
/// </summary>
@@ -2778,6 +3273,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Select Profile 的本地化字符串。
/// </summary>
public static string TbSelectProfile {
get {
return ResourceManager.GetString("TbSelectProfile", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set system proxy 的本地化字符串。
/// </summary>
@@ -2859,24 +3363,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 V2ray DNS settings 的本地化字符串。
/// </summary>
public static string TbSettingsCoreDns {
get {
return ResourceManager.GetString("TbSettingsCoreDns", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box DNS settings 的本地化字符串。
/// </summary>
public static string TbSettingsCoreDnsSingbox {
get {
return ResourceManager.GetString("TbSettingsCoreDnsSingbox", resourceCulture);
}
}
/// <summary>
/// 查找类似 Core: KCP settings 的本地化字符串。
/// </summary>
@@ -3067,7 +3553,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Use Xray and enable non-Tun mode, which conflicts with the group previous proxy 的本地化字符串。
/// 查找类似 which conflicts with the group previous proxy 的本地化字符串。
/// </summary>
public static string TbSettingsEnableFragmentTips {
get {
@@ -3156,6 +3642,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 If the system does not have a tray function, please do not enable it 的本地化字符串。
/// </summary>
public static string TbSettingsHide2TrayWhenCloseTip {
get {
return ResourceManager.GetString("TbSettingsHide2TrayWhenCloseTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 Hysteria Max bandwidth (Up/Down) 的本地化字符串。
/// </summary>
@@ -3489,6 +3984,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Auto Route 的本地化字符串。
/// </summary>
public static string TbSettingsTunAutoRoute {
get {
return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture);
}
}
/// <summary>
/// 查找类似 Tun Mode settings 的本地化字符串。
/// </summary>
@@ -3498,6 +4002,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 MTU 的本地化字符串。
/// </summary>
public static string TbSettingsTunMtu {
get {
return ResourceManager.GetString("TbSettingsTunMtu", resourceCulture);
}
}
/// <summary>
/// 查找类似 Stack 的本地化字符串。
/// </summary>
public static string TbSettingsTunStack {
get {
return ResourceManager.GetString("TbSettingsTunStack", resourceCulture);
}
}
/// <summary>
/// 查找类似 Strict Route 的本地化字符串。
/// </summary>
public static string TbSettingsTunStrictRoute {
get {
return ResourceManager.GetString("TbSettingsTunStrictRoute", resourceCulture);
}
}
/// <summary>
/// 查找类似 Enable UDP 的本地化字符串。
/// </summary>
@@ -3525,6 +4056,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Set Upstream Proxy Tag 的本地化字符串。
/// </summary>
public static string TbSetUpstreamProxyDetour {
get {
return ResourceManager.GetString("TbSetUpstreamProxyDetour", resourceCulture);
}
}
/// <summary>
/// 查找类似 Short Id 的本地化字符串。
/// </summary>
@@ -3687,6 +4227,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Validate Regional Domain IPs 的本地化字符串。
/// </summary>
public static string TbValidateDirectExpectedIPs {
get {
return ResourceManager.GetString("TbValidateDirectExpectedIPs", resourceCulture);
}
}
/// <summary>
/// 查找类似 When configured, validates IPs returned for regional domains (e.g., geosite:cn), returning only expected IPs 的本地化字符串。
/// </summary>
public static string TbValidateDirectExpectedIPsDesc {
get {
return ResourceManager.GetString("TbValidateDirectExpectedIPsDesc", resourceCulture);
}
}
/// <summary>
/// 查找类似 xray Freedom Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbXrayFreedomStrategy {
get {
return ResourceManager.GetString("TbXrayFreedomStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 The delay: {0} ms, {1} 的本地化字符串。
/// </summary>
@@ -3696,6 +4263,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Advanced DNS Settings 的本地化字符串。
/// </summary>
public static string ThAdvancedDNSSettings {
get {
return ResourceManager.GetString("ThAdvancedDNSSettings", resourceCulture);
}
}
/// <summary>
/// 查找类似 Basic DNS Settings 的本地化字符串。
/// </summary>
public static string ThBasicDNSSettings {
get {
return ResourceManager.GetString("ThBasicDNSSettings", resourceCulture);
}
}
/// <summary>
/// 查找类似 Active 的本地化字符串。
/// </summary>

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -675,8 +675,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>هسته: تنظیمات اولیه</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>تنظیمات V2ray DNS</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>V2ray Custom DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>هسته: تنظیمات KCP</value>
@@ -825,9 +825,6 @@
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>تنظیم کردن به عنوان قانون فعال</value>
</data>
<data name="TbdomainMatcher" xml:space="preserve">
<value>تطبیق دامنه</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>استراتژی دامنه</value>
</data>
@@ -1014,8 +1011,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>تنظیمات DNS</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>تنظیمات DNS sing-box</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box Custom DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>لطفا ساختار DNS را پر کنید، برای مشاهده سند کلیک کنید</value>
@@ -1062,6 +1059,18 @@
<data name="LvPrevProfileTip" xml:space="preserve">
<value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value>
</data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>مسیریابی خودکار</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>مسیریابی سخت‌گیرانه</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>پشته شبکه</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>فعال سازی additional Inbound</value>
</data>
@@ -1105,7 +1114,7 @@
<value>افزودن سرور [HTTP]</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>از Xray استفاده کنید و حالت non-Tun را فعال کنید، که با پراکسی قبلی گروه در تضاد است</value>
<value>which conflicts with the group previous proxy</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>فعال کردن فرگمنت</value>
@@ -1368,22 +1377,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>مخفی و پورت می شود، با کاما (،) جدا می شود</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>چند سرور به پیکربندی سفارشی</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>چند سرور تصادفی توسط Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>چند سرور RoundRobin توسط Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>چند سرور LeastPing توسط Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>چند سرور LeastLoad توسط Xray</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>LeastPing چند سرور توسط sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@@ -1398,4 +1407,190 @@
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>Incorrect password, please try again.</value>
</data>
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls] Configuration</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>Remote DNS</value>
</data>
<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>
<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>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
<value>Block SVCB and HTTPS Queries</value>
</data>
<data name="TbDNSHostsConfig" xml:space="preserve">
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
</data>
<data name="ThBasicDNSSettings" xml:space="preserve">
<value>Basic DNS Settings</value>
</data>
<data name="ThAdvancedDNSSettings" xml:space="preserve">
<value>Advanced DNS Settings</value>
</data>
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
<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>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Enable Custom DNS</value>
</data>
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Please fill in the correct config template</value>
</data>
<data name="menuFullConfigTemplate" xml:space="preserve">
<value>Full Config Template Setting</value>
</data>
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
<value>Enable Full Config Template</value>
</data>
<data name="TbRayFullConfigTemplate" xml:space="preserve">
<value>v2ray Full Config Template</value>
</data>
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
</data>
<data name="TbSBFullConfigTemplate" xml:space="preserve">
<value>sing-box Full Config Template</value>
</data>
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
</data>
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core'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.</value>
</data>
<data name="MsgStartParsingSubscription" xml:space="preserve">
<value>Start parsing and processing subscription content</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Server List</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>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>The {0} property is invalid, please check.</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} Group cannot reference itself or have a circular reference</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>Not support protocol '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>If the system does not have a tray function, please do not enable it</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -675,8 +675,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>Core: basic settings</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>V2ray DNS settings</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>V2ray Custom DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Core: KCP settings</value>
@@ -825,9 +825,6 @@
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>Set as active rule (Enter)</value>
</data>
<data name="TbdomainMatcher" xml:space="preserve">
<value>Domain Matcher</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>Domain strategy</value>
</data>
@@ -1014,8 +1011,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>DNS Settings</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>sing-box DNS settings</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box Custom DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>Please fill in DNS Structure, Click to view the document</value>
@@ -1062,6 +1059,18 @@
<data name="LvPrevProfileTip" xml:space="preserve">
<value>Please make sure the Configuration remarks exist and are unique</value>
</data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>Auto Route</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>Strict Route</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>Stack</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>Enable additional Inbound</value>
</data>
@@ -1105,7 +1114,7 @@
<value>Add [HTTP] Configuration</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>Use Xray and enable non-Tun mode, which conflicts with the group previous proxy</value>
<value>which conflicts with the group previous proxy</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>Enable fragment</value>
@@ -1368,22 +1377,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>Multi-Configuration to custom configuration</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Multi-Configuration Random by Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Multi-Configuration RoundRobin by Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Multi-Configuration LeastLoad by Xray</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@@ -1398,4 +1407,190 @@
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>Incorrect password, please try again.</value>
</data>
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls] Configuration</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>Remote DNS</value>
</data>
<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>
<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>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
<value>Block SVCB and HTTPS Queries</value>
</data>
<data name="TbDNSHostsConfig" xml:space="preserve">
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
</data>
<data name="ThBasicDNSSettings" xml:space="preserve">
<value>Basic DNS Settings</value>
</data>
<data name="ThAdvancedDNSSettings" xml:space="preserve">
<value>Advanced DNS Settings</value>
</data>
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
<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>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Enable Custom DNS</value>
</data>
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Please fill in the correct config template</value>
</data>
<data name="menuFullConfigTemplate" xml:space="preserve">
<value>Full Config Template Setting</value>
</data>
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
<value>Enable Full Config Template</value>
</data>
<data name="TbRayFullConfigTemplate" xml:space="preserve">
<value>v2ray Full Config Template</value>
</data>
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
</data>
<data name="TbSBFullConfigTemplate" xml:space="preserve">
<value>sing-box Full Config Template</value>
</data>
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
</data>
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core'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.</value>
</data>
<data name="MsgStartParsingSubscription" xml:space="preserve">
<value>Start parsing and processing subscription content</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Server List</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>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>The {0} property is invalid, please check.</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} Group cannot reference itself or have a circular reference</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>Not support protocol '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>If the system does not have a tray function, please do not enable it</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
</root>

View File

@@ -675,8 +675,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>Ядро: базовые настройки</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>Настройки DNS V2ray</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>V2ray Custom DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Ядро: настройки KCP</value>
@@ -807,9 +807,6 @@
<data name="menuMoveUp" xml:space="preserve">
<value>Вверх (U)</value>
</data>
<data name="menuMoveTo" xml:space="preserve">
<value>Переместить вверх/вниз</value>
</data>
<data name="MsgFilterTitle" xml:space="preserve">
<value>Фильтр, поддерживает regex</value>
</data>
@@ -828,9 +825,6 @@
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>Установить как активное правило</value>
</data>
<data name="TbdomainMatcher" xml:space="preserve">
<value>Сопоставитель доменов</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>Доменная стратегия</value>
</data>
@@ -969,6 +963,9 @@
<data name="TbSettingsSpeedTestUrl" xml:space="preserve">
<value>URL для тестирования скорости</value>
</data>
<data name="menuMoveTo" xml:space="preserve">
<value>Переместить вверх/вниз</value>
</data>
<data name="TbPublicKey" xml:space="preserve">
<value>PublicKey</value>
</data>
@@ -1014,8 +1011,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>Настройки DNS</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>Настройки DNS sing-box</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box Custom DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>Заполните структуру DNS, нажмите, чтобы открыть документ</value>
@@ -1062,6 +1059,18 @@
<data name="LvPrevProfileTip" xml:space="preserve">
<value>Убедитесь, что примечание существует и является уникальным</value>
</data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>Автоматическая маршрутизация</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>Строгая маршрутизация</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>Сетевой стек</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>Включить дополнительный входящий канал</value>
</data>
@@ -1099,13 +1108,13 @@
<value>Отмена тестирования...</value>
</data>
<data name="TransportRequestHostTip5" xml:space="preserve">
<value>*gRPC Authority</value>
<value>* gRPC Authority (HTTP/2 псевдозаголовок :authority)</value>
</data>
<data name="menuAddHttpServer" xml:space="preserve">
<value>Добавить сервер [HTTP]</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>Используйте Xray и отключите режим TUN, так как он конфликтует с предыдущим прокси-сервером группы</value>
<value>что конфликтует с предыдущим прокси группы</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>Включить фрагментацию (Fragment)</value>
@@ -1318,13 +1327,13 @@
<value>Пароль sudo системы</value>
</data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart.</value>
<value>Пароль sudo будет проверен в терминале. Если из-за ошибки проверки приложение начнёт работать некорректно, перезапустите его. Пароль не сохраняется — его нужно вводить после каждого перезапуска.</value>
</data>
<data name="TransportHeaderTypeTip5" xml:space="preserve">
<value>*XHTTP-режим</value>
</data>
<data name="TransportExtraTip" xml:space="preserve">
<value>Дополнительный XHTTP сырой JSON, формат: { XHTTPObject }</value>
<value>Дополнительный сырой JSON для XHTTP, формат: { XHTTP Object }</value>
</data>
<data name="TbSettingsHide2TrayWhenClose" xml:space="preserve">
<value>Скрыть в трее при закрытии окна</value>
@@ -1368,22 +1377,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>Заменит указанный порт, перечисляйте через запятую (,)</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>От мультиконфигурации к пользовательской конфигурации</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Случайный (Xray)</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Круговой (Xray)</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (Xray)</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Минимальная нагрузка (Xray)</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (sing-box)</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@@ -1393,9 +1402,195 @@
<value>URL для тестирования текущего соединения</value>
</data>
<data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>Can fill in the configuration remarks, please make sure it exist and are unique</value>
<value>Можно указать название (Remarks) из конфигурации, убедитесь, что оно существует и уникально</value>
</data>
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>Incorrect password, please try again.</value>
<value>Неверный пароль, попробуйте ещё раз.</value>
</data>
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Добавить сервер [Anytls]</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>Удалённый DNS</value>
</data>
<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>
<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>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Добавить стандартные записи hosts (DNS)</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
<value>Блокировать DNS-запросы SVCB и HTTPS</value>
</data>
<data name="TbDNSHostsConfig" xml:space="preserve">
<value>DNS hosts: (каждая строка в формате "domain1 ip1 ip2")</value>
</data>
<data name="ThBasicDNSSettings" xml:space="preserve">
<value>Базовые настройки DNS</value>
</data>
<data name="ThAdvancedDNSSettings" xml:space="preserve">
<value>Расширенные настройки DNS</value>
</data>
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
<value>Проверять IP-адреса региональных доменов</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>При включении проверяет IP-адреса, возвращаемые для региональных доменов (например, geosite:cn), и оставляет только ожидаемые IP-адреса</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Включить пользовательский DNS</value>
</data>
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
<value>Включён пользовательский DNS — настройки на этой странице не применяются</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Пожалуйста, заполните корректный шаблон конфигурации</value>
</data>
<data name="menuFullConfigTemplate" xml:space="preserve">
<value>Настройка полного шаблона конфигурации</value>
</data>
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
<value>Включить полный шаблон конфигурации</value>
</data>
<data name="TbRayFullConfigTemplate" xml:space="preserve">
<value>Полный шаблон конфигурации v2ray</value>
</data>
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
<value>Добавляет только конфигурацию исходящих (outbound), а также routing.balancers и routing.rules.outboundTag. Нажмите, чтобы открыть документ</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Не добавлять исходящие для непрокси-протоколов</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Задать тег верхнего прокси (upstream)</value>
</data>
<data name="TbSBFullConfigTemplate" xml:space="preserve">
<value>Полный шаблон конфигурации sing-box</value>
</data>
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
<value>Добавляет только конфигурацию Outbound и Endpoint. Нажмите, чтобы открыть документ</value>
</data>
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
<value>Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную.</value>
</data>
<data name="MsgStartParsingSubscription" xml:space="preserve">
<value>Start parsing and processing subscription content</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Server List</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>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>The {0} property is invalid, please check.</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} Group cannot reference itself or have a circular reference</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>Not support protocol '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>If the system does not have a tray function, please do not enable it</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value>
</data>
</root>

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -675,8 +675,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>Core: 基础设置</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>v2ray DNS 设置</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>v2ray 自定义 DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Core: KCP 设置</value>
@@ -825,9 +825,6 @@
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>设为活动规则 (Enter)</value>
</data>
<data name="TbdomainMatcher" xml:space="preserve">
<value>域名匹配算法</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>域名解析策略</value>
</data>
@@ -1011,8 +1008,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>DNS 设置</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>sing-box DNS 设置</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box 自定义 DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>请填写 DNS JSON 结构,点击查看文档</value>
@@ -1059,6 +1056,18 @@
<data name="LvPrevProfileTip" xml:space="preserve">
<value>请确保配置文件别名存在并唯一</value>
</data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>自动路由</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>严格路由</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>协议栈</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>启用额外监听端口</value>
</data>
@@ -1102,7 +1111,7 @@
<value>添加 [HTTP] 配置文件</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>使用 Xray 且非 Tun 模式启用,和分组前置代理冲突</value>
<value>和分组前置代理冲突</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>启用分片 (Fragment)</value>
@@ -1365,22 +1374,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>会覆盖端口,多组时用逗号 (,) 隔开</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>多配置文件产生自定义配置 (多选)</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多配置文件生成策略组</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多配置文件随机 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多配置文件负载均衡 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多配置文件最低延迟 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多配置文件最稳定 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多配置文件最低延迟 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@@ -1395,4 +1404,190 @@
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>密码错误,请重试。</value>
</data>
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>添加 [Anytls] 配置文件</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>远程 DNS</value>
</data>
<data name="TbDomesticDNS" xml:space="preserve">
<value>直连 DNS</value>
</data>
<data name="TbRemoteDNSTips" 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>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>添加常用 DNS Hosts</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
<value>阻止 SVCB 和 HTTPS 查询</value>
</data>
<data name="TbDNSHostsConfig" xml:space="preserve">
<value>DNS Hosts“域名1 ip1 ip2” 一行一个)</value>
</data>
<data name="ThBasicDNSSettings" xml:space="preserve">
<value>DNS 基础设置</value>
</data>
<data name="ThAdvancedDNSSettings" xml:space="preserve">
<value>DNS 进阶设置</value>
</data>
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
<value>校验相应地区域名 IP</value>
</data>
<data name="TbValidateDirectExpectedIPsDesc" xml:space="preserve">
<value>配置后,会对相应地区域名(如 geosite:cn的返回 IP 进行校验,仅返回期望 IP</value>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>启用自定义 DNS</value>
</data>
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
<value>自定义 DNS 已启用,此页面配置将无效</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>开启后将阻止 ECH 和 HTTP/3 可用性查询</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>请填写正确的配置模板</value>
</data>
<data name="menuFullConfigTemplate" xml:space="preserve">
<value>完整配置模板设置</value>
</data>
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
<value>启用完整配置模板</value>
</data>
<data name="TbRayFullConfigTemplate" xml:space="preserve">
<value>v2ray 完整配置模板</value>
</data>
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
<value>仅添加出站配置routing.balancers 和 routing.rules.outboundTag点击查看文档</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>不添加非代理协议出站</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>设置上游代理 tag</value>
</data>
<data name="TbSBFullConfigTemplate" xml:space="preserve">
<value>sing-box 完整配置模板</value>
</data>
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
<value>仅添加出站和端点配置,点击查看文档</value>
</data>
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
<value>此功能供高级用户和有特殊需求的用户使用。 启用此功能后,将忽略 Core 的基础设置DNS 设置 ,路由设置。你需要保证系统代理的端口和流量统计等功能的配置正确,一切都由你来设置。</value>
</data>
<data name="MsgStartParsingSubscription" xml:space="preserve">
<value>开始解析和处理订阅内容</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>选择配置文件</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>请至少添加一个配置文件</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>策略组</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>链式代理</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>最低延迟</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>随机</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>负载均衡</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>最稳定</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>策略组类型</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>添加策略组配置文件</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>添加链式代理配置文件</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>添加子配置文件</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>删除子配置文件</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>服务器列表</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>故障转移</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>多配置文件故障转移 sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多配置文件故障转移 Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支持网络类型 '{1}'。</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>核心 '{0}' 在使用传输方式 '{2}' 时不支持协议 '{1}'。</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>核心 '{0}' 不支持协议 '{1}'。</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>代理链: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>路由规则出站: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>策略组: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>节点别名 '{0}' 不存在。</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>组“{0}”为空。请至少添加一个节点。</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>{0}属性无效,请检查</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} 分组不能引用自身或循环引用</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>不支持协议 '{0}'。</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>如果系统没有托盘功能,请不要开启</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>规则类型</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>可对 Routing 和 DNS 单独设定规则ALL 则都生效</value>
</data>
</root>

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -675,8 +675,8 @@
<data name="TbSettingsCore" xml:space="preserve">
<value>Core: 基礎設定</value>
</data>
<data name="TbSettingsCoreDns" xml:space="preserve">
<value>V2ray DNS 設定</value>
<data name="TbCustomDnsRay" xml:space="preserve">
<value>V2ray Custom DNS</value>
</data>
<data name="TbSettingsCoreKcp" xml:space="preserve">
<value>Core: KCP 設定</value>
@@ -825,9 +825,6 @@
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>設為活動規則 (Enter)</value>
</data>
<data name="TbdomainMatcher" xml:space="preserve">
<value>域名匹配演算法</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve">
<value>域名解析策略</value>
</data>
@@ -1011,8 +1008,8 @@
<data name="menuDNSSetting" xml:space="preserve">
<value>DNS 設定</value>
</data>
<data name="TbSettingsCoreDnsSingbox" xml:space="preserve">
<value>sing-box DNS 設定</value>
<data name="TbCustomDnsSingbox" xml:space="preserve">
<value>sing-box Custom DNS</value>
</data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>請填寫 DNS JSON 結構,點擊查看檔案</value>
@@ -1059,6 +1056,18 @@
<data name="LvPrevProfileTip" xml:space="preserve">
<value>請確保設定檔別名存在並且唯一</value>
</data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>自動路由</value>
</data>
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
<value>嚴格路由</value>
</data>
<data name="TbSettingsTunStack" xml:space="preserve">
<value>協定堆疊</value>
</data>
<data name="TbSettingsTunMtu" xml:space="preserve">
<value>MTU</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>啟用額外偵聽連接埠</value>
</data>
@@ -1102,7 +1111,7 @@
<value>新增 [HTTP] 設定檔</value>
</data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>使用 Xray 且非 Tun 模式啟用,和分組前置代理衝突</value>
<value>和分組前置代理衝突</value>
</data>
<data name="TbSettingsEnableFragment" xml:space="preserve">
<value>啟用分片Fragment</value>
@@ -1365,22 +1374,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
</data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>多設定檔產生自訂配置 (多選)</value>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多設定檔隨機 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多設定檔負載平衡 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多設定檔最低延遲 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多設定檔最穩定 Xray</value>
</data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多設定檔最低延遲 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@@ -1395,4 +1404,190 @@
<data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>密碼錯誤,請重試。</value>
</data>
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>新增 [Anytls] 設定檔</value>
</data>
<data name="TbRemoteDNS" xml:space="preserve">
<value>Remote DNS</value>
</data>
<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>
<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>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Add Common DNS Hosts</value>
</data>
<data name="TbFakeIP" xml:space="preserve">
<value>FakeIP</value>
</data>
<data name="TbBlockSVCBHTTPSQueries" xml:space="preserve">
<value>Block SVCB and HTTPS Queries</value>
</data>
<data name="TbDNSHostsConfig" xml:space="preserve">
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
</data>
<data name="ThBasicDNSSettings" xml:space="preserve">
<value>Basic DNS Settings</value>
</data>
<data name="ThAdvancedDNSSettings" xml:space="preserve">
<value>Advanced DNS Settings</value>
</data>
<data name="TbValidateDirectExpectedIPs" xml:space="preserve">
<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>
</data>
<data name="TbCustomDNSEnable" xml:space="preserve">
<value>Enable Custom DNS</value>
</data>
<data name="TbCustomDNSEnabledPageInvalid" xml:space="preserve">
<value>Custom DNS Enabled, This Page's Settings Invalid</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Please fill in the correct config template</value>
</data>
<data name="menuFullConfigTemplate" xml:space="preserve">
<value>Full Config Template Setting</value>
</data>
<data name="TbFullConfigTemplateEnable" xml:space="preserve">
<value>Enable Full Config Template</value>
</data>
<data name="TbRayFullConfigTemplate" xml:space="preserve">
<value>v2ray Full Config Template</value>
</data>
<data name="TbRayFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound Config Only, routing.balancers and routing.rules.outboundTag, Click to view the document</value>
</data>
<data name="TbAddProxyProtocolOutboundOnly" xml:space="preserve">
<value>Do Not Add Non-Proxy Protocol Outbound</value>
</data>
<data name="TbSetUpstreamProxyDetour" xml:space="preserve">
<value>Set Upstream Proxy Tag</value>
</data>
<data name="TbSBFullConfigTemplate" xml:space="preserve">
<value>sing-box Full Config Template</value>
</data>
<data name="TbSBFullConfigTemplateDesc" xml:space="preserve">
<value>Add Outbound and Endpoint Config Only, Click to view the document</value>
</data>
<data name="TbFullConfigTemplateDesc" xml:space="preserve">
<value>This feature is intended for advanced users and those with special requirements. Once enabled, it will ignore the Core'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.</value>
</data>
<data name="MsgStartParsingSubscription" xml:space="preserve">
<value>開始解析和處理訂閱內容</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Server List</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>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
</data>
<data name="InvalidProperty" xml:space="preserve">
<value>The {0} property is invalid, please check.</value>
</data>
<data name="GroupSelfReference" xml:space="preserve">
<value>{0} 分組不能引用自身或循環引用</value>
</data>
<data name="NotSupportProtocol" xml:space="preserve">
<value>Not support protocol '{0}'.</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>如果系統沒有托盤功能,請不要開啟</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>规则类型</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>可对 Routing 和 DNS 单独设定规则ALL 则都生效</value>
</data>
</root>

View File

@@ -1,4 +1,4 @@
{
{
"log": {
"level": "debug",
"timestamp": true
@@ -14,22 +14,10 @@
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"tag": "dns_out",
"type": "dns"
}
],
"route": {
"rules": [
{
"protocol": [ "dns" ],
"outbound": "dns_out"
}
]
}
}

View File

@@ -2,28 +2,33 @@
"servers": [
{
"tag": "remote",
"address": "tcp://8.8.8.8",
"strategy": "prefer_ipv4",
"type": "tcp",
"server": "8.8.8.8",
"detour": "proxy"
},
{
"tag": "local",
"address": "223.5.5.5",
"strategy": "prefer_ipv4",
"detour": "direct"
},
{
"tag": "block",
"address": "rcode://success"
"type": "udp",
"server": "223.5.5.5"
}
],
"rules": [
{
"domain_suffix": [
"googleapis.cn",
"gstatic.com"
],
"server": "remote",
"strategy": "prefer_ipv4"
},
{
"rule_set": [
"geosite-cn"
],
"server": "local"
"server": "local",
"strategy": "prefer_ipv4"
}
],
"final": "remote"
"final": "remote",
"strategy": "prefer_ipv4"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
{
"domain": [
"amobile.music.tc.qq.com",
"api-jooxtt.sanook.com",
"api.joox.com",
"aqqmusic.tc.qq.com",
"dl.stream.qqmusic.qq.com",
"ff.dorado.sdo.com",
"heartbeat.belkin.com",
"isure.stream.qqmusic.qq.com",
"joox.com",
"lens.l.google.com",
"localhost.ptlogin2.qq.com",
"localhost.sec.qq.com",
"mesu.apple.com",
"mobileoc.music.tc.qq.com",
"music.taihe.com",
"musicapi.taihe.com",
"na.b.g-tun.com",
"proxy.golang.org",
"ps.res.netease.com",
"shark007.net",
"songsearch.kugou.com",
"static.adtidy.org",
"streamoc.music.tc.qq.com",
"swcdn.apple.com",
"swdist.apple.com",
"swdownload.apple.com",
"swquery.apple.com",
"swscan.apple.com",
"turn.cloudflare.com",
"trackercdn.kugou.com",
"xnotify.xboxlive.com"
],
"domain_keyword": [
"ntp",
"stun",
"time"
],
"domain_regex": [
"^[^.]+$",
"^[^.]+\\.[^.]+\\.xboxlive\\.com$",
"^localhost\\.[^.]+\\.weixin\\.qq\\.com$",
"^mijia\\scloud$",
"^xbox\\.[^.]+\\.microsoft\\.com$",
"^xbox\\.[^.]+\\.[^.]+\\.microsoft\\.com$"
],
"domain_suffix": [
"126.net",
"3gppnetwork.org",
"battle.net",
"battlenet.com.cn",
"cdn.nintendo.net",
"cmbchina.com",
"cmbimg.com",
"ff14.sdo.com",
"ffxiv.com",
"finalfantasyxiv.com",
"gcloudcs.com",
"home.arpa",
"invalid",
"kuwo.cn",
"lan",
"linksys.com",
"linksyssmartwifi.com",
"local",
"localdomain",
"localhost",
"market.xiaomi.com",
"mcdn.bilivideo.cn",
"media.dssott.com",
"msftconnecttest.com",
"msftncsi.com",
"music.163.com",
"music.migu.cn",
"n0808.com",
"nflxvideo.net",
"oray.com",
"orayimg.com",
"router.asus.com",
"sandai.net",
"square-enix.com",
"srv.nintendo.net",
"steamcontent.com",
"uu.163.com",
"wargaming.net",
"wggames.cn",
"wotgame.cn",
"wowsgame.cn",
"xiami.com",
"y.qq.com"
]
}

View File

@@ -2,29 +2,33 @@
"servers": [
{
"tag": "remote",
"address": "tcp://8.8.8.8",
"strategy": "prefer_ipv4",
"type": "tcp",
"server": "8.8.8.8",
"detour": "proxy"
},
{
"tag": "local",
"address": "223.5.5.5",
"strategy": "prefer_ipv4",
"detour": "direct"
},
{
"tag": "block",
"address": "rcode://success"
"type": "udp",
"server": "223.5.5.5"
}
],
"rules": [
{
"rule_set": [
"geosite-cn",
"geosite-geolocation-cn"
"domain_suffix": [
"googleapis.cn",
"gstatic.com"
],
"server": "local"
"server": "remote",
"strategy": "prefer_ipv4"
},
{
"rule_set": [
"geosite-cn"
],
"server": "local",
"strategy": "prefer_ipv4"
}
],
"final": "remote"
}
"final": "remote",
"strategy": "prefer_ipv4"
}

View File

@@ -8,13 +8,13 @@
139,
5353
],
"outbound": "block"
"action": "reject"
},
{
"ip_cidr": [
"224.0.0.0/3",
"ff00::/8"
],
"outbound": "block"
"action": "reject"
}
]

View File

@@ -11,7 +11,7 @@
</PackageReference>
<PackageReference Include="ReactiveUI.Fody" />
<PackageReference Include="sqlite-net-pcl" />
<PackageReference Include="Splat.NLog" />
<PackageReference Include="NLog" />
<PackageReference Include="WebDav.Client" />
<PackageReference Include="YamlDotNet" />
<PackageReference Include="QRCoder" />
@@ -44,6 +44,7 @@
<EmbeddedResource Include="Sample\tun_singbox_inbound" />
<EmbeddedResource Include="Sample\tun_singbox_rules" />
<EmbeddedResource Include="Sample\linux_autostart_config" />
<EmbeddedResource Include="Sample\singbox_fakeip_filter" />
</ItemGroup>
<ItemGroup>

View File

@@ -12,7 +12,7 @@ public class CoreConfigClashService
{
_config = config;
}
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
{
var ret = new RetResult();
@@ -73,12 +73,13 @@ public class CoreConfigClashService
}
//mixed-port
fileContent["mixed-port"] = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks);
fileContent["mixed-port"] = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
//log-level
fileContent["log-level"] = GetLogLevel(_config.CoreBasicItem.Loglevel);
//external-controller
fileContent["external-controller"] = $"{Global.Loopback}:{AppHandler.Instance.StatePort2}";
fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
fileContent.Remove("secret");
//allow-lan
if (_config.Inbound.First().AllowLANConn)
{
@@ -139,7 +140,7 @@ public class CoreConfigClashService
return ret;
}
ClashApiHandler.Instance.ProfileContent = fileContent;
ClashApiManager.Instance.ProfileContent = fileContent;
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
ret.Success = true;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,533 @@
using System.Net;
using System.Net.NetworkInformation;
using ServiceLib.Common;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService(Config config)
{
private readonly Config _config = config;
private static readonly string _tag = "CoreConfigSingboxService";
#region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
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())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(singboxConfig);
await GenInbounds(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);
await GenRouting(singboxConfig);
await GenDns(node, singboxConfig);
await GenExperimental(singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
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> 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);
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;
}
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 initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
foreach (var it in selecteds)
{
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.Port <= 0)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null || item.IsComplex() || !item.IsValid())
{
continue;
}
//find unused port
var port = initPort;
for (var k = initPort; k < Global.MaxPort; k++)
{
if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0)
{
continue;
}
if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0)
{
continue;
}
//found
port = k;
initPort = port + 1;
break;
}
//Port In Used
if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0)
{
continue;
}
it.Port = port;
it.AllowTest = true;
//inbound
Inbound4Sbox inbound = new()
{
listen = Global.Loopback,
listen_port = port,
type = EInboundProtocol.mixed.ToString(),
};
inbound.tag = inbound.type + inbound.listen_port.ToString();
singboxConfig.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);
}
//rule
Rule4Sbox rule = new()
{
inbound = new List<string> { inbound.tag },
outbound = tag
};
singboxConfig.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);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node == null
|| !node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
if (result.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == 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,
};
singboxConfig.route.rules.Clear();
singboxConfig.inbounds.Clear();
singboxConfig.inbounds.Add(new()
{
tag = $"{EInboundProtocol.mixed}{port}",
listen = Global.Loopback,
listen_port = port,
type = EInboundProtocol.mixed.ToString(),
});
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;
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
#endregion public gen function
}

View File

@@ -0,0 +1,63 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig)
{
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled)
{
return JsonUtils.Serialize(singboxConfig);
}
var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
if (fullConfigTemplateItem.IsNullOrEmpty())
{
return JsonUtils.Serialize(singboxConfig);
}
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
if (fullConfigTemplateNode == null)
{
return JsonUtils.Serialize(singboxConfig);
}
// Process outbounds
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
foreach (var outbound in singboxConfig.outbounds)
{
if (outbound.type.ToLower() is "direct" or "block")
{
if (fullConfigTemplate.AddProxyOnly == true)
{
continue;
}
}
else if (outbound.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && !Utils.IsPrivateNetwork(outbound.server ?? string.Empty))
{
outbound.detour = fullConfigTemplate.ProxyDetour;
}
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
}
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
// Process endpoints
if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0)
{
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray();
foreach (var endpoint in singboxConfig.endpoints)
{
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
{
endpoint.detour = fullConfigTemplate.ProxyDetour;
}
customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint));
}
fullConfigTemplateNode["endpoints"] = customEndpointsNode;
}
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
}
}

View File

@@ -0,0 +1,537 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (item != null && item.Enabled == true)
{
return await GenDnsCompatible(node, singboxConfig);
}
var simpleDNSItem = _config.SimpleDNSItem;
await GenDnsServers(singboxConfig, simpleDNSItem);
await GenDnsRules(singboxConfig, simpleDNSItem);
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.independent_cache = true;
// final dns
var routing = await ConfigHandler.GetDefaultRouting(_config);
var useDirectDns = false;
if (routing != null)
{
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
useDirectDns = rules?.LastOrDefault() is { } lastRule &&
lastRule.OutboundTag == Global.DirectTag &&
(lastRule.Port == "0-65535" ||
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)
{
singboxConfig.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)
{
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS);
directDns.tag = Global.SingboxDirectDNSTag;
directDns.domain_resolver = Global.SingboxLocalDNSTag;
var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS);
remoteDns.tag = Global.SingboxRemoteDNSTag;
remoteDns.detour = Global.ProxyTag;
remoteDns.domain_resolver = Global.SingboxLocalDNSTag;
var hostsDns = new Server4Sbox
{
tag = Global.SingboxHostsDNSTag,
type = "hosts",
predefined = new(),
};
if (simpleDNSItem.AddCommonHosts == true)
{
hostsDns.predefined = Global.PredefinedHosts;
}
if (simpleDNSItem.UseSystemHosts == true)
{
var systemHosts = Utils.GetSystemHosts();
if (systemHosts != null && systemHosts.Count > 0)
{
foreach (var host in systemHosts)
{
hostsDns.predefined.TryAdd(host.Key, new List<string> { host.Value });
}
}
}
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
{
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
foreach (var kvp in userHostsMap)
{
hostsDns.predefined[kvp.Key] = kvp.Value;
}
}
foreach (var host in hostsDns.predefined)
{
if (finalDns.server == host.Key)
{
finalDns.domain_resolver = Global.SingboxHostsDNSTag;
}
if (remoteDns.server == host.Key)
{
remoteDns.domain_resolver = Global.SingboxHostsDNSTag;
}
if (directDns.server == host.Key)
{
directDns.domain_resolver = Global.SingboxHostsDNSTag;
}
}
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);
// fake ip
if (simpleDNSItem.FakeIP == true)
{
var fakeip = new Server4Sbox
{
tag = Global.SingboxFakeDNSTag,
type = "fakeip",
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
};
singboxConfig.dns.servers.Add(fakeip);
}
return await Task.FromResult(0);
}
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
{
var finalDnsAddress = "local";
if (_config.TunModeItem.EnableTun)
{
finalDnsAddress = "dhcp://auto";
}
var finalDns = ParseDnsAddress(finalDnsAddress);
finalDns.tag = Global.SingboxLocalDNSTag;
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= new List<Server4Sbox>();
singboxConfig.dns.servers.Add(finalDns);
return await Task.FromResult(finalDns);
}
private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
{
singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.AddRange(new[]
{
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
new Rule4Sbox
{
server = Global.SingboxRemoteDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy,
clash_mode = ERuleMode.Global.ToString()
},
new Rule4Sbox
{
server = Global.SingboxDirectDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct,
clash_mode = ERuleMode.Direct.ToString()
}
});
if (simpleDNSItem.BlockBindingQuery == true)
{
singboxConfig.dns.rules.Add(new()
{
query_type = new List<int> { 64, 65 },
action = "predefined",
rcode = "NOTIMP"
});
}
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == true)
{
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
fakeipFilterRule.invert = true;
var rule4Fake = new Rule4Sbox
{
server = Global.SingboxFakeDNSTag,
type = "logical",
mode = "and",
rewrite_ttl = 1,
rules = new List<Rule4Sbox>
{
new() {
query_type = new List<int> { 1, 28 }, // A and AAAA
},
fakeipFilterRule,
}
};
singboxConfig.dns.rules.Add(rule4Fake);
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null)
return 0;
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
var expectedIPCidr = new List<string>();
var expectedIPsRegions = new List<string>();
var regionNames = new HashSet<string>();
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
{
var ipItems = simpleDNSItem.DirectExpectedIPs
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s))
.ToList();
foreach (var ip in ipItems)
{
if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase))
{
var region = ip["geoip:".Length..];
if (!string.IsNullOrEmpty(region))
{
expectedIPsRegions.Add(region);
regionNames.Add(region);
regionNames.Add($"geolocation-{region}");
regionNames.Add($"tld-{region}");
}
}
else
{
expectedIPCidr.Add(ip);
}
}
}
foreach (var item in rules)
{
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
{
continue;
}
if (item.RuleType == ERuleType.Routing)
{
continue;
}
var rule = new Rule4Sbox();
var validDomains = item.Domain.Count(it => ParseV2Domain(it, rule));
if (validDomains <= 0)
{
continue;
}
if (item.OutboundTag == Global.DirectTag)
{
rule.server = Global.SingboxDirectDNSTag;
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Direct) ? null : simpleDNSItem.SingboxStrategy4Direct;
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
{
var geositeSet = new HashSet<string>(rule.geosite);
if (regionNames.Intersect(geositeSet).Any())
{
if (expectedIPsRegions.Count > 0)
{
rule.geoip = expectedIPsRegions;
}
if (expectedIPCidr.Count > 0)
{
rule.ip_cidr = expectedIPCidr;
}
}
}
}
else if (item.OutboundTag == Global.BlockTag)
{
rule.action = "predefined";
rule.rcode = "NXDOMAIN";
}
else
{
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);
}
rule.server = Global.SingboxRemoteDNSTag;
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Proxy) ? null : simpleDNSItem.SingboxStrategy4Proxy;
}
singboxConfig.dns.rules.Add(rule);
}
return 0;
}
private async Task<int> GenDnsCompatible(ProfileItem? node, SingboxConfig singboxConfig)
{
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
var strDNS = string.Empty;
if (_config.TunModeItem.EnableTun)
{
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
}
else
{
strDNS = string.IsNullOrEmpty(item?.NormalDNS) ? EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName) : item?.NormalDNS;
}
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
if (dns4Sbox is null)
{
return 0;
}
singboxConfig.dns = dns4Sbox;
if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty())
{
await GenDnsDomainsCompatible(singboxConfig, item);
}
else
{
await GenDnsDomainsLegacyCompatible(singboxConfig, item);
}
await GenOutboundDnsRule(node, singboxConfig);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return 0;
}
private async Task<int> GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem)
{
var dns4Sbox = singboxConfig.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,
clash_mode = ERuleMode.Direct.ToString()
});
dns4Sbox.rules.Insert(0, new()
{
server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote",
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
});
}
singboxConfig.dns = dns4Sbox;
return await Task.FromResult(0);
}
private async Task<int> GenOutboundDnsRule(ProfileItem? node, SingboxConfig singboxConfig)
{
if (node == null)
{
return 0;
}
List<string> domain = new();
if (Utils.IsDomain(node.Address)) // normal outbound
{
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;
}
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
{
server = Global.SingboxLocalDNSTag,
domain = domain,
});
return await Task.FromResult(0);
}
private static Server4Sbox? ParseDnsAddress(string address)
{
var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim();
if (string.IsNullOrEmpty(addressFirst))
{
return null;
}
var server = new Server4Sbox();
if (addressFirst is "local" or "localhost")
{
server.type = "local";
return server;
}
if (addressFirst.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase))
{
var interface_name = addressFirst.Substring(7);
server.type = "dhcp";
server.Interface = interface_name == "auto" ? null : interface_name;
return server;
}
if (!addressFirst.Contains("://"))
{
// udp dns
server.type = "udp";
server.server = addressFirst;
return server;
}
try
{
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal);
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
var uri = new Uri(addressFirst);
server.server = uri.Host;
if (!uri.IsDefaultPort)
{
server.server_port = uri.Port;
}
if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/")
{
server.path = uri.AbsolutePath;
}
}
catch (UriFormatException)
{
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal);
if (protocolEndIndex > 0)
{
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
var remaining = addressFirst.Substring(protocolEndIndex + 3);
var portIndex = remaining.IndexOf(':');
var pathIndex = remaining.IndexOf('/');
if (portIndex > 0)
{
server.server = remaining.Substring(0, portIndex);
var portPart = pathIndex > portIndex
? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1)
: remaining.Substring(portIndex + 1);
if (int.TryParse(portPart, out var parsedPort))
{
server.server_port = parsedPort;
}
}
else if (pathIndex > 0)
{
server.server = remaining.Substring(0, pathIndex);
}
else
{
server.server = remaining;
}
if (pathIndex > 0 && (server.type == "https" || server.type == "h3"))
{
server.path = remaining.Substring(pathIndex);
}
}
}
return server;
}
}

View File

@@ -0,0 +1,92 @@
namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigSingboxService
{
private async Task<int> GenInbounds(SingboxConfig singboxConfig)
{
try
{
var listen = "0.0.0.0";
singboxConfig.inbounds = [];
if (!_config.TunModeItem.EnableTun
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box))
{
var inbound = new Inbound4Sbox()
{
type = EInboundProtocol.mixed.ToString(),
tag = EInboundProtocol.socks.ToString(),
listen = Global.Loopback,
};
singboxConfig.inbounds.Add(inbound);
inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
if (_config.Inbound.First().SecondLocalPortEnabled)
{
var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true);
singboxConfig.inbounds.Add(inbound2);
}
if (_config.Inbound.First().AllowLANConn)
{
if (_config.Inbound.First().NewPort4LAN)
{
var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true);
inbound3.listen = listen;
singboxConfig.inbounds.Add(inbound3);
//auth
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
{
inbound3.users = new() { new() { username = _config.Inbound.First().User, password = _config.Inbound.First().Pass } };
}
}
else
{
inbound.listen = listen;
}
}
}
if (_config.TunModeItem.EnableTun)
{
if (_config.TunModeItem.Mtu <= 0)
{
_config.TunModeItem.Mtu = Global.TunMtus.First();
}
if (_config.TunModeItem.Stack.IsNullOrEmpty())
{
_config.TunModeItem.Stack = Global.TunStacks.First();
}
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun";
tunInbound.mtu = _config.TunModeItem.Mtu;
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
tunInbound.stack = _config.TunModeItem.Stack;
if (_config.TunModeItem.EnableIPv6Address == false)
{
tunInbound.address = ["172.18.0.1/30"];
}
singboxConfig.inbounds.Add(tunInbound);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
{
var inbound = JsonUtils.DeepCopy(inItem);
inbound.tag = protocol.ToString();
inbound.listen_port = inItem.listen_port + (int)protocol;
inbound.type = EInboundProtocol.mixed.ToString();
return inbound;
}
}

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