Compare commits

...

218 commits

Author SHA1 Message Date
DHR60
4e042295d2
Add global fakeip and fakeip filter (#7919)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-09-13 14:55:30 +08:00
2dust
33d9c5db6c up GlobalUsings 2025-09-13 14:46:35 +08:00
DHR60
cb182125f6
Fix (#7946)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-09-10 19:43:11 +08:00
2dust
b54c67d6f1 up 7.14.9
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/issues/7914
2025-09-09 17:55:15 +08:00
2dust
6ffb3bd30c up 7.14.8
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* 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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-09-07 16:39:54 +08:00
2dust
6391667c15 up 7.14.7
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-09-05 18:11:16 +08:00
2dust
a2917b3ce8 Update Directory.Packages.props
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-09-04 20:50:25 +08:00
2dust
d094370209 up 7.14.6
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-09-03 19:05:45 +08:00
2dust
1a6fbf782d Using RxApp replace ViewAction
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-09-02 17:12:38 +08:00
2dust
3f67a23f8b up 7.14.5
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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.
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-08-25 17:43:53 +08:00
2dust
45c987fd86 up 7.14.3
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
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
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-08-21 19:32:39 +08:00
JieXu
0035e836d7
Update build-linux.yml, Add RPM package for RHEL. (#7813)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* 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
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-08-15 17:18:16 +08:00
2dust
dc2877d817 Refactor UpdateSubscriptionProcess ,add SubscriptionHandler
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-08-14 20:59:15 +08:00
2dust
89d6af8fc9 Added global constants such as IPIfNonMatch
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-08-13 21:19:51 +08:00
2dust
0fb6b2e54b Bug fix for DNS setting
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-08-12 20:23:37 +08:00
Miheichev Aleksandr Sergeevich
a2028623e7
refactor: improve Russian localization, fix placeholders and typos (#7740)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* 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
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
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
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
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
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
https://github.com/2dust/v2rayN/issues/7698
2025-08-05 19:31:48 +08:00
2dust
6973272dd0
up 7.13.6
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
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)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-08-02 21:06:14 +08:00
2dust
6fa5ca5aa9 Revert "Fix missing hysteria2 arguments (#7673)"
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
This reverts commit 3f79df21d9.
2025-08-01 12:16:19 +08:00
2dust
1d1f5641eb up 7.13.5 2025-07-31 20:43:00 +08:00
DHR60
3f79df21d9
Fix missing hysteria2 arguments (#7673) 2025-07-31 16:56:39 +08:00
2dust
ac1231ad54 sing-box LAN listening address changed to 0.0.0.0
https://github.com/2dust/v2rayN/discussions/7669
2025-07-30 21:08:37 +08:00
2dust
8662d94ab6 Fixed bug for macos kill_as_sudo 2025-07-30 20:33:51 +08:00
2dust
3d23f3e3a2 Optimized and improved the code
Optimized and improved the code for killing core processes in non-Windows environments. Now uses a shell script for precise processing.
2025-07-30 19:52:45 +08:00
2dust
6715d7dce6 up 7.13.4 2025-07-29 20:44:43 +08:00
2dust
dad35f57d0 Fixed an issue where root processes could not be exited on macOS 2025-07-29 20:23:42 +08:00
2dust
f779e311ed Optimize code and remove unused resources 2025-07-29 19:42:59 +08:00
maximilionus
ce7c41e3ff
Unix platform elevation enhancements v2 (#7658)
* Remove multiple send password actions on Unix elev

* Remove CoreAdminHandler password verification

This is useless since already handled in
v2rayN/v2rayN.Desktop/Views/SudoPasswordInputView.axaml.cs with
CheckSudoPasswordAsync().

* Disable caching and prompt for sudo call

* Cleanup CoreAdminHandler pwd verify remains

* Migrate sudo opts to initial pwd verification
2025-07-29 19:28:09 +08:00
DHR60
74bb01d044
Improves private IP address detection (#7657) 2025-07-29 19:18:13 +08:00
DHR60
82f9698c0d
Supports IPv6 addresses in profile summary (#7656) 2025-07-29 19:16:50 +08:00
2dust
6911883995 Fixed the issue where process does not accept sudo password input stream for the first time 2025-07-27 14:53:27 +08:00
2dust
47c509faf6 up 7.13.3 2025-07-27 10:59:23 +08:00
2dust
8704942209 Improve sudo password interaction experience 2025-07-27 10:56:58 +08:00
2dust
e8cdc29bb5 Add sudo password verification success message prompt 2025-07-26 20:55:55 +08:00
DHR60
191a7a6574
Fixes Hysteria2 ports (#7649) 2025-07-26 15:07:07 +08:00
2dust
ad5d21db5a Upgrade Downloader package 2025-07-20 15:06:08 +08:00
2dust
569e939492 Optimizing and improving code 2025-07-20 14:16:19 +08:00
2dust
6a17c539d1 up 7.13.2 2025-07-18 20:07:10 +08:00
2dust
f8a4f946e4 Fixed the issue of missing files when updating GeoFiles
https://github.com/2dust/v2rayN/issues/7585
2025-07-18 19:56:18 +08:00
trojan-uma
0715fa85ce
改进 zh-Hans 描述 (#7579)
* 统一 zh-Hans 文字描述的括号

* 改进描述
2025-07-16 20:35:09 +08:00
2dust
1360051f0c Improve and optimize 2025-07-15 20:17:01 +08:00
2dust
42c4f9a6c6 Bug fix
https://github.com/2dust/v2rayN/issues/7582
2025-07-15 18:37:10 +08:00
2dust
11691d0128 up 7.13.1 2025-07-14 16:32:48 +08:00
2dust
26fe9c63a3 Bug fix
https://github.com/2dust/v2rayN/issues/7537
2025-07-14 16:32:10 +08:00
2dust
30cd033b42 up 7.13.0 2025-07-14 13:23:37 +08:00
2dust
e21c0b4d62 The outbound tag of the route rule can enter a config remarks
https://github.com/2dust/v2rayN/issues/7537
2025-07-13 20:25:53 +08:00
maximilionus
916055d8bd
Linux proxy control script improvement (#7558)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
* Proper Unix files new-line termination

* Fallback for proxy configuration on Linux

This introduces a special fallback for platform detection that helps
with configuring the proxy settings on minimal (DE-less) setups.

Also unifies the check for proper $MODE value.
2025-07-09 20:59:24 +08:00
happytrudy
683ca8af14
add shadowquic (#7554) 2025-07-09 20:41:20 +08:00
2dust
70151db91b Add tray menu to display the main window
https://github.com/2dust/v2rayN/issues/7549
2025-07-09 20:31:37 +08:00
2dust
da3d4c36a9 Improve and optimize the active rules code in routing settings
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-07-06 18:15:07 +08:00
2dust
1d01476523 Adjust UI 2025-07-06 17:51:09 +08:00
2dust
75ceba1b08 Remove unused 2025-07-06 14:11:34 +08:00
2dust
493c37e7d5 Update Directory.Packages.props 2025-07-06 12:27:38 +08:00
2dust
6d686b284d Fix macOS scaling size
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-07-04 20:48:32 +08:00
Nelson Lai
60fcf6174e
Update zh hant localization (#7528)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* Update Traditional Chinese localization

* Remove newline at end of Traditional Chinese localization file
2025-07-03 20:46:22 +08:00
2dust
4141f451b7 The window height and width variable type is changed from double to int
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-07-02 20:53:53 +08:00
2dust
7a9ee6e9e2 Each window can remember its size
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-07-01 19:39:27 +08:00
2dust
cb28c31519 Remove unused 2025-07-01 19:19:15 +08:00
DHR60
84f93f2ae6
Optimize proxy chain handling (#7515)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* Optimize proxy chain handling

* Avoids duplicate proxy chain generation
2025-06-30 20:26:10 +08:00
2dust
30c09a7b54 Add mux settings for per-server, VMess/Shadowsocks/VLESS/Trojan
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
If you want to use global settings, do not set per-server
2025-06-29 15:06:02 +08:00
2dust
b3874a78b9 Improved order of items in settings
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-06-29 11:16:21 +08:00
2dust
3e71965cd4 Optimization and Improvement,DataGrid list only updates some attribute values
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
https://github.com/2dust/v2rayN/issues/7489
2025-06-26 14:23:30 +08:00
2dust
3df57f74ba Update winget-publish.yml
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-06-22 10:36:05 +08:00
2dust
7972cb8e1f Update winget-publish.yml 2025-06-22 10:31:41 +08:00
2dust
0d74452c6c Added parameter MacOSShowInDock to control whether MacOS platform app is displayed in the Dock
https://github.com/2dust/v2rayN/issues/7465
2025-06-21 17:00:49 +08:00
2dust
f947f63e6d Display or hide the main window menu in the tray and move it to the top
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-06-21 16:54:36 +08:00
DHR60
fefa7ded5a
Optimize proxy chain handling for multiple nodes (#7468)
* Improves outbound proxy chain handling.

* Improves sing-box outbound proxy chain handling.

* AI-optimized code
2025-06-21 16:45:17 +08:00
2dust
a46a4ad7c1 up 7.12.7
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-06-19 11:59:20 +08:00
2dust
e46f680651 Optimize the UI for dns settings 2025-06-19 11:24:33 +08:00
2dust
93a20852f5 Optimize the UI for routing settings 2025-06-19 10:48:47 +08:00
2dust
298bb64e66 Routing rules do not determine whether it is version V3 2025-06-19 10:04:48 +08:00
2dust
0e5ac65f55 Improved network speed display when using clash api without proxy and direct
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-06-16 15:06:18 +08:00
2dust
cb6122f872 First scroll horizontally to the initial position to avoid the control crash bug
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/issues/7387
2025-06-16 10:48:35 +08:00
2dust
06500e0218 When testing, start the core and then delay 1s before starting the test
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/issues/7391
2025-06-15 17:33:11 +08:00
DHR60
9ddf0b42e7
Fix Observatory (#7437)
* Enables concurrency for observatory

* Adds burst observatory for least load balancer
2025-06-15 14:41:47 +08:00
2dust
2056377f55 up 7.12.6 2025-06-15 14:39:02 +08:00
2dust
7065dabc94 If it is not in China area, no update is required
https://github.com/2dust/v2rayN/issues/7417
2025-06-15 14:35:59 +08:00
2dust
9e2336a71e The notification pop-up position is changed to the top right
https://github.com/2dust/v2rayN/issues/7421
2025-06-15 14:16:30 +08:00
2dust
d239c627f3 Use File.SetUnixFileMode to set the execute permission first. If that fails, use chmod to set the execute permission.
https://github.com/2dust/v2rayN/issues/7416
2025-06-15 12:33:21 +08:00
2dust
984b36d34e Update Directory.Packages.props 2025-06-15 12:33:02 +08:00
DHR60
c81bc2f536
Fix (#7405)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
#7404
2025-06-08 09:30:08 +08:00
Miheichev Aleksandr Sergeevich
87f7e65076
docs: improve README.md formatting and readability (#7376)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
- Split long line for better readability
- Add consistent spacing between sections
- Remove extra blank lines
- Update sing-box link to point to main repo instead of releases
2025-06-02 09:52:49 +08:00
duolaameng
9985b68b6b
Update LICENSE (#7327)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
完善版权信息:项目名称、时间、作者
2025-05-28 20:04:12 +08:00
Miheichev Aleksandr Sergeevich
fb4b8b2789
Revision of the Russian translation (#7342)
* Revision of the Russian translation

* Fix: remove duplicate TbSettingsSocksPortTip and refine three Russian strings

* refactor: improve Russian translations and fix punctuation

- Standardized punctuation in tooltips and messages (replaced semicolons with commas where appropriate)
- Improved consistency in server type labels (moved square brackets in "[TUIC] server" and similar)
- Enhanced clarity in "Speed Ping Test URL" to "URL for real ping test"
- Simplified "NeedRebootTips" message
- Fixed minor grammatical and formatting issues in various UI strings
- Removed duplicate "TbSettingsStartBootTip" entry
- Improved tooltip for port settings
2025-05-28 20:03:50 +08:00
Yuri Tukhachevsky
3812ccc780
Add support for DDE and MATE (#7353)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* add support for UKUI

* Add support for DDE and MATE
2025-05-28 09:05:55 +08:00
Yuri Tukhachevsky
608a6c387a
add support for UKUI (#7348)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-05-26 13:48:31 +08:00
2dust
4875b37f70 up 7.12.5
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-05-25 19:12:06 +08:00
2dust
2f3fba73de Bug fix
https://github.com/2dust/v2rayN/issues/7333
2025-05-25 19:11:42 +08:00
2dust
2ab1b9068f up 7.12.4
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-05-21 20:00:41 +08:00
DHR60
b9613875ce
Determine .exe suffix based on OS in GenRoutingDirectExe (#7322)
* Determine .exe suffix based on OS in GenRoutingDirectExe

* Uses Utils.GetExeName
2025-05-21 19:57:23 +08:00
2dust
5d2aea6b4f Change the inbound of the xray configuration file from socks to mixed
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-05-17 14:51:18 +08:00
Pk-web6936
5824e18ed6
Update Persian translate (#7273)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-05-15 19:21:53 +08:00
2dust
4f8648cbc9 Fix
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
https://github.com/2dust/v2rayN/issues/7270
2025-05-12 20:29:34 +08:00
2dust
01b021b2c3 Bug fix
https://github.com/2dust/v2rayN/issues/7279
2025-05-12 20:28:28 +08:00
2dust
331e8ce960 up 7.12.3
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-05-11 19:19:26 +08:00
2dust
a2cfe6fa51 Added the current connection information test url option
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/discussions/7268
2025-05-11 16:59:00 +08:00
2dust
8381fefb78 up 7.12.2
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-05-11 10:39:02 +08:00
2dust
d3b95d781a Removed the function of displaying the current connection IP
https://github.com/2dust/v2rayN/discussions/7268
2025-05-11 10:38:48 +08:00
2dust
3a4a96f87a Fix
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/issues/7258
2025-05-09 14:33:41 +08:00
2dust
3d462c4be3 Fix
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
https://github.com/2dust/v2rayN/issues/7247
2025-05-08 15:56:52 +08:00
2dust
82b366cd9b Fix
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/issues/7244
2025-05-07 14:28:55 +08:00
DHR60
897a4e5635
Move exe direct rule before clash_mode (#7236)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-05-05 13:59:14 +08:00
2dust
8ea76fd318 up 7.12.1
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-05-04 17:44:36 +08:00
2dust
693a96fff2 Update Directory.Packages.props 2025-05-04 17:44:04 +08:00
DHR60
8b4e2f8f23
Fix DNS (#7233)
* Fix DNS

* Removes expectIPs from remote DNS
2025-05-04 17:28:57 +08:00
2dust
13b164acac Enhanced sorting function, can sort by statistics
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-05-03 11:29:36 +08:00
2dust
e590547b30 Revert "Bug fix"
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
This reverts commit 5a0fdd971a.
2025-05-02 10:44:20 +08:00
2dust
5a0fdd971a Bug fix
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
https://github.com/2dust/v2rayN/issues/7211
2025-04-30 14:12:02 +08:00
2dust
514dce960a up 7.12.0
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-04-28 15:57:09 +08:00
Reza Bakhshi Laktasaraei
6ee6fb1706
Improve Accessibility in StatusBarView and ProfilesView, Update Package Versions (#7199)
* Fix Tab navigation in ToolBar by setting KeyboardNavigation to Continue

* Improve accessibility for ComboBoxes in StatusBarView.xaml

Added ItemContainerStyle to cmbRoutings2 and cmbRoutings to bind AutomationProperties.Name to Remarks, ensuring screen readers announce the correct values instead of the default object type.

* Improve accessibility for cmbServers in StatusBarView.xaml

Added ItemContainerStyle to cmbServers to bind AutomationProperties.Name to Text, ensuring screen readers announce the correct values instead of the default object type.

* Update package versions and fix accessibility in ProfilesView.xaml

- Updated package versions in Directory.Packages.props:
  - Semi.Avalonia and Semi.Avalonia.DataGrid from 11.2.1.6 to 11.2.1.7.
  - ZXing.Net.Bindings.SkiaSharp from 0.16.14 to 0.16.21.
- Fixed MC3024 error in ProfilesView.xaml by creating AccessibleMyChipListBoxItem style:
  - Added AccessibleMyChipListBoxItem style based on MyChipListBoxItem to set AutomationProperties.Name.
  - Replaced ItemContainerStyle with AccessibleMyChipListBoxItem to preserve original appearance.
  - Updated AutomationProperties.Name to use resx:ResUI.menuSubscription for better localization.
  - Removed duplicate AutomationProperties.Name from TextBlock as it's now handled by the style.

* Update Directory.Packages.props

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2025-04-28 15:25:57 +08:00
2dust
6985328653 Optimize and improve code 2025-04-28 15:16:58 +08:00
DHR60
41c406b84d
Revert "Fix xray wireguard chained proxies not working (2dust#7113)" (#7194)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-04-27 09:37:46 +08:00
2dust
30f7f2c563 Update Directory.Packages.props
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-04-26 10:32:20 +08:00
Reza Bakhshi Laktasaraei
be3dbfb8e3
Fix Tab navigation in ToolBar by setting KeyboardNavigation to Continue (#7185) 2025-04-26 10:31:00 +08:00
Pk-web6936
9b92259e80
Update Persian translation (#7191)
0032a3d27a
2025-04-26 10:30:05 +08:00
2dust
1c04144573 Code clean
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-04-26 09:53:19 +08:00
2dust
adf3b955d6 Refactor Linux to run Tun as sudo 2025-04-26 09:50:31 +08:00
2dust
0032a3d27a Fix kill process for linux
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-04-25 16:36:28 +08:00
2dust
c6d347d49a Refactor the Linux version to not store sudo password 2025-04-25 15:19:49 +08:00
2dust
ea42246d1b Improved AmazTool 2025-04-25 14:38:33 +08:00
2dust
3f19958c75 Bug fix
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/issues/7179
2025-04-24 11:50:24 +08:00
2dust
35788158bc up 7.11.3
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-04-21 10:17:37 +08:00
2dust
4fd494ded4 Update Directory.Packages.props 2025-04-21 10:15:49 +08:00
2dust
23eeb8ff55 Try to fix
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
https://github.com/2dust/v2rayN/issues/7128
2025-04-18 12:31:46 +08:00
232 changed files with 14431 additions and 7293 deletions

View file

@ -26,13 +26,13 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v5.0.0
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: '0' fetch-depth: '0'
- name: Setup - name: Setup
uses: actions/setup-dotnet@v4.3.1 uses: actions/setup-dotnet@v5.0.0
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
@ -99,3 +99,37 @@ jobs:
tag: ${{ github.event.inputs.release_tag }} tag: ${{ github.event.inputs.release_tag }}
file_glob: true 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: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v5.0.0
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: '0' fetch-depth: '0'
- name: Setup - name: Setup
uses: actions/setup-dotnet@v4.3.1 uses: actions/setup-dotnet@v5.0.0
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'

View file

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

View file

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

View file

@ -22,10 +22,18 @@ jobs:
$github = Invoke-RestMethod -uri "https://api.github.com/repos/2dust/v2rayN/releases" $github = Invoke-RestMethod -uri "https://api.github.com/repos/2dust/v2rayN/releases"
$targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1 $targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1
$installerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip*' | Select -ExpandProperty browser_download_url
$x64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip' | Select -ExpandProperty browser_download_url
$arm64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-arm64\.zip' | Select -ExpandProperty browser_download_url
$ver = $targetRelease.tag_name $ver = $targetRelease.tag_name
# getting latest wingetcreate file # getting latest wingetcreate file
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
.\wingetcreate.exe update $wingetPackage -s -v $ver -u "$installerUrl|x64" -t $gitToken
Write-Host "Updating with both x64 and arm64 installers"
Write-Host "Version: $ver"
Write-Host "x64 URL: $x64InstallerUrl"
Write-Host "arm64 URL: $arm64InstallerUrl"
.\wingetcreate.exe update $wingetPackage -s -v $ver -u "$x64InstallerUrl|x64" "$arm64InstallerUrl|arm64" -t $gitToken

1
.gitignore vendored
View file

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

View file

@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.> <one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author> Copyright (C) 2019-Present 2dust
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode: notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author> v2rayN Copyright (C) 2019-Present 2dust
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.

View file

@ -1,16 +1,18 @@
# v2rayN # v2rayN
A GUI client for Windows, Linux and macOS, support [Xray](https://github.com/XTLS/Xray-core) and [sing-box](https://github.com/SagerNet/sing-box/releases) and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
A GUI client for Windows, Linux and macOS, support [Xray](https://github.com/XTLS/Xray-core)
and [sing-box](https://github.com/SagerNet/sing-box)
and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayN)](https://github.com/2dust/v2rayN/commits/master) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayN)](https://github.com/2dust/v2rayN/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayn/badge)](https://www.codefactor.io/repository/github/2dust/v2rayn) [![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayn/badge)](https://www.codefactor.io/repository/github/2dust/v2rayn)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayN/latest/total?logo=github)](https://github.com/2dust/v2rayN/releases) [![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayN/latest/total?logo=github)](https://github.com/2dust/v2rayN/releases)
[![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn) [![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn)
## How to use ## How to use
Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details. Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details.
## Telegram Channel ## Telegram Channel
[github_2dust](https://t.me/github_2dust) [github_2dust](https://t.me/github_2dust)

View file

@ -1,14 +1,67 @@
#!/bin/bash #!/bin/bash
set -euo pipefail
# Install deps
sudo apt update -y sudo apt update -y
sudo apt install -y libfuse2 sudo apt install -y libfuse2 wget file
wget -O pkg2appimage https://github.com/AppImageCommunity/pkg2appimage/releases/download/continuous/pkg2appimage-1eceb30-x86_64.AppImage
chmod a+x pkg2appimage # Get tools
export AppImageOutputArch=$OutputArch wget -qO appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
export OutputPath=$OutputPath64 chmod +x appimagetool
./pkg2appimage ./pkg2appimage.yml
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage # x86_64 AppDir
export AppImageOutputArch=$OutputArchArm APPDIR_X64="AppDir-x86_64"
export OutputPath=$OutputPathArm64 rm -rf "$APPDIR_X64"
./pkg2appimage ./pkg2appimage.yml mkdir -p "$APPDIR_X64/usr/lib/v2rayN" "$APPDIR_X64/usr/bin" "$APPDIR_X64/usr/share/applications" "$APPDIR_X64/usr/share/pixmaps"
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage 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 Version: $Version
Architecture: $Arch2 Architecture: $Arch2
Maintainer: https://github.com/2dust/v2rayN 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 Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
EOF EOF
@ -52,7 +53,17 @@ sudo chmod 0755 "${PackagePath}/DEBIAN/postinst"
sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN" sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN"
sudo chmod 0755 "${PackagePath}/opt/v2rayN/AmazTool" 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 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

@ -5,21 +5,83 @@ internal static class Program
[STAThread] [STAThread]
private static void Main(string[] args) private static void Main(string[] args)
{ {
try
{
// If no arguments are provided, display usage guidelines and exit
if (args.Length == 0) if (args.Length == 0)
{ {
Console.WriteLine(Resx.Resource.Guidelines); ShowHelp();
Thread.Sleep(5000);
return; return;
} }
var argData = Uri.UnescapeDataString(string.Join(" ", args)); // Log all arguments for debugging purposes
if (argData.Equals("rebootas")) foreach (var arg in args)
{ {
Console.WriteLine(arg);
}
// Parse command based on first argument
switch (args[0].ToLowerInvariant())
{
case "rebootas":
// Handle application restart
HandleRebootAsync();
break;
case "help":
case "--help":
case "-h":
case "/?":
// Display help information
ShowHelp();
break;
default:
// Default behavior: handle as upgrade data
// Maintain backward compatibility with existing usage pattern
var argData = Uri.UnescapeDataString(string.Join(" ", args));
HandleUpgrade(argData);
break;
}
}
catch (Exception ex)
{
// Global exception handling
Console.WriteLine($"An error occurred: {ex.Message}");
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
/// <summary>
/// Display help information and usage guidelines
/// </summary>
private static void ShowHelp()
{
Console.WriteLine(Resx.Resource.Guidelines);
Console.WriteLine("Available commands:");
Console.WriteLine(" rebootas - Restart the application");
Console.WriteLine(" help - Display this help information");
Thread.Sleep(5000);
}
/// <summary>
/// Handle application restart
/// </summary>
private static void HandleRebootAsync()
{
Console.WriteLine("Restarting application...");
Thread.Sleep(1000); Thread.Sleep(1000);
Utils.StartV2RayN(); Utils.StartV2RayN();
return;
} }
UpgradeApp.Upgrade(argData); /// <summary>
/// Handle application upgrade with the provided data
/// </summary>
/// <param name="upgradeData">Data for the upgrade process</param>
private static void HandleUpgrade(string upgradeData)
{
Console.WriteLine("Upgrading application...");
UpgradeApp.Upgrade(upgradeData);
} }
} }

View file

@ -79,15 +79,7 @@ internal class UpgradeApp
continue; continue;
} }
try TryExtractToFile(entry, entryOutputPath);
{
entry.ExtractToFile(entryOutputPath, true);
}
catch
{
Thread.Sleep(1000);
entry.ExtractToFile(entryOutputPath, true);
}
Console.WriteLine(entryOutputPath); Console.WriteLine(entryOutputPath);
} }
@ -113,4 +105,24 @@ internal class UpgradeApp
Utils.StartV2RayN(); 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> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.11.2</Version> <Version>7.14.10</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -5,24 +5,24 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled> <CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.2.7" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.2.7" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.2.7" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.2.7" /> <PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.6" />
<PackageVersion Include="CliWrap" Version="3.8.2" /> <PackageVersion Include="CliWrap" Version="3.9.0" />
<PackageVersion Include="Downloader" Version="3.3.4" /> <PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" /> <PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
<PackageVersion Include="MaterialDesignThemes" Version="5.2.1" /> <PackageVersion Include="MaterialDesignThemes" Version="5.2.1" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" /> <PackageVersion Include="MessageBox.Avalonia" Version="3.2.0" />
<PackageVersion Include="QRCoder" Version="1.6.0" /> <PackageVersion Include="QRCoder" Version="1.6.0" />
<PackageVersion Include="ReactiveUI" Version="20.2.45" /> <PackageVersion Include="ReactiveUI" Version="20.4.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" /> <PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="20.2.45" /> <PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.6" /> <PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.6" /> <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
<PackageVersion Include="Splat.NLog" Version="15.3.1" /> <PackageVersion Include="Splat.NLog" Version="16.2.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.1" /> <PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" /> <PackageVersion Include="WebDav.Client" Version="2.9.0" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" /> <PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" /> <PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />

@ -1 +1 @@
Subproject commit ef73fa22c46cfc7d1ec192ffe8497f6e61b4f0db Subproject commit 270f023fcca4ef9149480c575d502815efd83531

View file

@ -1,100 +0,0 @@
using System.Security.Cryptography;
using System.Text;
namespace ServiceLib.Common;
public class AesUtils
{
private const int KeySize = 256; // AES-256
private const int IvSize = 16; // AES block size
private const int Iterations = 10000;
private static readonly byte[] Salt = Encoding.ASCII.GetBytes("saltysalt".PadRight(16, ' '));
private static readonly string DefaultPassword = Utils.GetMd5(Utils.GetHomePath() + "AesUtils");
/// <summary>
/// Encrypt
/// </summary>
/// <param name="text">Plain text</param>
/// <param name="password">Password for key derivation or direct key in ASCII bytes</param>
/// <returns>Base64 encoded cipher text with IV</returns>
public static string Encrypt(string text, string? password = null)
{
if (string.IsNullOrEmpty(text))
return string.Empty;
var plaintext = Encoding.UTF8.GetBytes(text);
var key = GetKey(password);
var iv = GenerateIv();
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
using var ms = new MemoryStream();
ms.Write(iv, 0, iv.Length);
using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(plaintext, 0, plaintext.Length);
cs.FlushFinalBlock();
}
var cipherTextWithIv = ms.ToArray();
return Convert.ToBase64String(cipherTextWithIv);
}
/// <summary>
/// Decrypt
/// </summary>
/// <param name="cipherTextWithIv">Base64 encoded cipher text with IV</param>
/// <param name="password">Password for key derivation or direct key in ASCII bytes</param>
/// <returns>Plain text</returns>
public static string Decrypt(string cipherTextWithIv, string? password = null)
{
if (string.IsNullOrEmpty(cipherTextWithIv))
return string.Empty;
var cipherTextWithIvBytes = Convert.FromBase64String(cipherTextWithIv);
var key = GetKey(password);
var iv = new byte[IvSize];
Buffer.BlockCopy(cipherTextWithIvBytes, 0, iv, 0, IvSize);
var cipherText = new byte[cipherTextWithIvBytes.Length - IvSize];
Buffer.BlockCopy(cipherTextWithIvBytes, IvSize, cipherText, 0, cipherText.Length);
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
using var ms = new MemoryStream();
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherText, 0, cipherText.Length);
cs.FlushFinalBlock();
}
var plainText = ms.ToArray();
return Encoding.UTF8.GetString(plainText);
}
private static byte[] GetKey(string? password)
{
if (password.IsNullOrEmpty())
{
password = DefaultPassword;
}
using var pbkdf2 = new Rfc2898DeriveBytes(password, Salt, Iterations, HashAlgorithmName.SHA256);
return pbkdf2.GetBytes(KeySize / 8);
}
private static byte[] GenerateIv()
{
var randomNumber = new byte[IvSize];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return randomNumber;
}
}

View file

@ -1,74 +0,0 @@
using System.Security.Cryptography;
using System.Text;
namespace ServiceLib.Common;
public class DesUtils
{
/// <summary>
/// Encrypt
/// </summary>
/// <param name="text"></param>
/// /// <param name="key"></param>
/// <returns></returns>
public static string Encrypt(string? text, string? key = null)
{
if (text.IsNullOrEmpty())
{
return string.Empty;
}
GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv);
var dsp = DES.Create();
using var memStream = new MemoryStream();
using var cryStream = new CryptoStream(memStream, dsp.CreateEncryptor(rgbKey, rgbIv), CryptoStreamMode.Write);
using var sWriter = new StreamWriter(cryStream);
sWriter.Write(text);
sWriter.Flush();
cryStream.FlushFinalBlock();
memStream.Flush();
return Convert.ToBase64String(memStream.GetBuffer(), 0, (int)memStream.Length);
}
/// <summary>
/// Decrypt
/// </summary>
/// <param name="encryptText"></param>
/// <param name="key"></param>
/// <returns></returns>
public static string Decrypt(string? encryptText, string? key = null)
{
if (encryptText.IsNullOrEmpty())
{
return string.Empty;
}
GetKeyIv(key ?? GetDefaultKey(), out var rgbKey, out var rgbIv);
var dsp = DES.Create();
var buffer = Convert.FromBase64String(encryptText);
using var memStream = new MemoryStream();
using var cryStream = new CryptoStream(memStream, dsp.CreateDecryptor(rgbKey, rgbIv), CryptoStreamMode.Write);
cryStream.Write(buffer, 0, buffer.Length);
cryStream.FlushFinalBlock();
return Encoding.UTF8.GetString(memStream.ToArray());
}
private static void GetKeyIv(string key, out byte[] rgbKey, out byte[] rgbIv)
{
if (key.IsNullOrEmpty())
{
throw new ArgumentNullException("The key cannot be null");
}
if (key.Length <= 8)
{
throw new ArgumentNullException("The key length cannot be less than 8 characters.");
}
rgbKey = Encoding.ASCII.GetBytes(key.Substring(0, 8));
rgbIv = Encoding.ASCII.GetBytes(key.Insert(0, "w").Substring(0, 8));
}
private static string GetDefaultKey()
{
return Utils.GetMd5(Utils.GetHomePath() + "DesUtils");
}
}

View file

@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis;
namespace ServiceLib.Common; namespace ServiceLib.Common;
public static class StringEx public static class Extension
{ {
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
{ {
@ -79,4 +79,9 @@ public static class StringEx
{ {
return int.TryParse(value, out var result) ? result : defaultValue; return int.TryParse(value, out var result) ? result : defaultValue;
} }
public static List<string> AppendEmpty(this IEnumerable<string> source)
{
return source.Concat(new[] { string.Empty }).ToList();
}
} }

View file

@ -223,4 +223,28 @@ public static class FileManager
// ignored // ignored
} }
} }
/// <summary>
/// Creates a Linux shell file with the specified contents.
/// </summary>
/// <param name="fileName"></param>
/// <param name="contents"></param>
/// <param name="overwrite"></param>
/// <returns></returns>
public static async Task<string> CreateLinuxShellFile(string fileName, string contents, bool overwrite)
{
var shFilePath = Utils.GetBinConfigPath(fileName);
// Check if the file already exists and if we should overwrite it
if (!overwrite && File.Exists(shFilePath))
{
return shFilePath;
}
File.Delete(shFilePath);
await File.WriteAllTextAsync(shFilePath, contents);
await Utils.SetLinuxChmod(shFilePath);
return shFilePath;
}
} }

View file

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

@ -4,7 +4,7 @@ using ZXing.SkiaSharp;
namespace ServiceLib.Common; namespace ServiceLib.Common;
public class QRCodeHelper public class QRCodeUtils
{ {
public static byte[]? GenQRCode(string? url) public static byte[]? GenQRCode(string? url)
{ {

View file

@ -323,6 +323,40 @@ public class Utils
return text.Replace("", ",").Replace(Environment.NewLine, ","); return text.Replace("", ",").Replace(Environment.NewLine, ",");
} }
public static List<string> GetEnumNames<TEnum>() where TEnum : Enum
{
return Enum.GetValues(typeof(TEnum))
.Cast<TEnum>()
.Select(e => e.ToString())
.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 #endregion
#region #region
@ -382,7 +416,14 @@ public class Utils
{ {
if (IPAddress.TryParse(ip, out var address)) if (IPAddress.TryParse(ip, out var address))
{ {
// Loopback address check (127.0.0.1 for IPv4, ::1 for IPv6)
if (IPAddress.IsLoopback(address))
return true;
var ipBytes = address.GetAddressBytes(); var ipBytes = address.GetAddressBytes();
if (address.AddressFamily == AddressFamily.InterNetwork)
{
// IPv4 private address check
if (ipBytes[0] == 10) if (ipBytes[0] == 10)
return true; return true;
if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31) if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31)
@ -390,6 +431,30 @@ public class Utils
if (ipBytes[0] == 192 && ipBytes[1] == 168) if (ipBytes[0] == 192 && ipBytes[1] == 168)
return true; return true;
} }
else if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
// IPv6 private address check
// 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)
{
var ipv4Bytes = ipBytes.Skip(12).ToArray();
if (ipv4Bytes[0] == 10)
return true;
if (ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31)
return true;
if (ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168)
return true;
}
}
}
return false; return false;
} }
@ -427,11 +492,11 @@ public class Utils
return false; return false;
} }
public static int GetFreePort(int defaultPort = 9090) public static int GetFreePort(int defaultPort = 0)
{ {
try try
{ {
if (!Utils.PortInUse(defaultPort)) if (!(defaultPort == 0 || Utils.PortInUse(defaultPort)))
{ {
return defaultPort; return defaultPort;
} }
@ -543,9 +608,9 @@ public class Utils
if (host.StartsWith("#")) if (host.StartsWith("#"))
continue; continue;
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (hostItem.Length != 2) if (hostItem.Length < 2)
continue; continue;
systemHosts.Add(hostItem.Last(), hostItem.First()); systemHosts.Add(hostItem[1], hostItem[0]);
} }
} }
} }
@ -582,7 +647,7 @@ public class Utils
var result = await cmd.ExecuteBufferedAsync(); var result = await cmd.ExecuteBufferedAsync();
if (result.IsSuccess) if (result.IsSuccess)
{ {
return result.StandardOutput.ToString(); return result.StandardOutput ?? "";
} }
Logging.SaveLog(result.ToString() ?? ""); Logging.SaveLog(result.ToString() ?? "");
@ -816,18 +881,55 @@ public class Utils
return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
} }
return false; return false;
//else }
//{
// var id = GetLinuxUserId().Result ?? "1000"; public static bool IsPackagedInstall()
// if (int.TryParse(id, out var userId)) {
// { try
// return userId == 0; {
// } if (IsWindows() || IsOSX())
// else {
// { return false;
// 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() private static async Task<string?> GetLinuxUserId()
@ -839,14 +941,46 @@ public class Utils
public static async Task<string?> SetLinuxChmod(string? fileName) public static async Task<string?> SetLinuxChmod(string? fileName)
{ {
if (fileName.IsNullOrEmpty()) if (fileName.IsNullOrEmpty())
{
return null; return null;
}
if (SetUnixFileMode(fileName))
{
Logging.SaveLog($"Successfully set the file execution permission, {fileName}");
return "";
}
if (fileName.Contains(' ')) if (fileName.Contains(' '))
{
fileName = fileName.AppendQuotes(); fileName = fileName.AppendQuotes();
//File.SetUnixFileMode(fileName, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); }
var arg = new List<string>() { "-c", $"chmod +x {fileName}" }; var arg = new List<string>() { "-c", $"chmod +x {fileName}" };
return await GetCliWrapOutput(Global.LinuxBash, arg); return await GetCliWrapOutput(Global.LinuxBash, arg);
} }
public static bool SetUnixFileMode(string? fileName)
{
try
{
if (fileName.IsNullOrEmpty())
{
return false;
}
if (File.Exists(fileName))
{
var currentMode = File.GetUnixFileMode(fileName);
File.SetUnixFileMode(fileName, currentMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute);
return true;
}
}
catch (Exception ex)
{
Logging.SaveLog("SetUnixFileMode", ex);
}
return false;
}
public static async Task<string?> GetLinuxFontFamily(string lang) public static async Task<string?> GetLinuxFontFamily(string lang)
{ {
// var arg = new List<string>() { "-c", $"fc-list :lang={lang} family" }; // var arg = new List<string>() { "-c", $"fc-list :lang={lang} family" };

View file

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

View file

@ -14,5 +14,7 @@ public enum ECoreType
hysteria2 = 26, hysteria2 = 26,
brook = 27, brook = 27,
overtls = 28, overtls = 28,
shadowquic = 29,
mieru = 30,
v2rayN = 99 v2rayN = 99
} }

View file

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

View file

@ -6,7 +6,6 @@ public enum EViewAction
ShowYesNo, ShowYesNo,
SaveFileDialog, SaveFileDialog,
AddBatchRoutingRulesYesNo, AddBatchRoutingRulesYesNo,
AdjustMainLvColWidth,
SetClipboardData, SetClipboardData,
AddServerViaClipboard, AddServerViaClipboard,
ImportRulesFromClipboard, ImportRulesFromClipboard,
@ -16,10 +15,10 @@ public enum EViewAction
ShowHideWindow, ShowHideWindow,
ScanScreenTask, ScanScreenTask,
ScanImageTask, ScanImageTask,
Shutdown,
BrowseServer, BrowseServer,
ImportRulesFromFile, ImportRulesFromFile,
InitSettingFont, InitSettingFont,
PasswordInput,
SubEditWindow, SubEditWindow,
RoutingRuleSettingWindow, RoutingRuleSettingWindow,
RoutingRuleDetailsWindow, RoutingRuleDetailsWindow,
@ -28,18 +27,10 @@ public enum EViewAction
DNSSettingWindow, DNSSettingWindow,
RoutingSettingWindow, RoutingSettingWindow,
OptionSettingWindow, OptionSettingWindow,
FullConfigTemplateWindow,
GlobalHotkeySettingWindow, GlobalHotkeySettingWindow,
SubSettingWindow, SubSettingWindow,
DispatcherSpeedTest,
DispatcherRefreshConnections,
DispatcherRefreshProxyGroups,
DispatcherProxiesDelayTest,
DispatcherStatistics,
DispatcherServerAvailability,
DispatcherReload,
DispatcherRefreshServersBiz, DispatcherRefreshServersBiz,
DispatcherRefreshIcon, DispatcherRefreshIcon,
DispatcherCheckUpdate,
DispatcherCheckUpdateFinished,
DispatcherShowMsg, DispatcherShowMsg,
} }

View file

@ -8,9 +8,7 @@ public class Global
public const string GithubUrl = "https://github.com"; public const string GithubUrl = "https://github.com";
public const string GithubApiUrl = "https://api.github.com/repos"; public const string GithubApiUrl = "https://api.github.com/repos";
public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat"; public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat";
public const string SpeedPingTestUrl = @"https://www.google.com/generate_204";
public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs"; public const string SingboxRulesetUrl = @"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-{0}/{1}.srs";
public const string IPAPIUrl = "https://api.ip.sb/geoip";
public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="; public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw=";
public const string ConfigFileName = "guiNConfig.json"; public const string ConfigFileName = "guiNConfig.json";
@ -40,6 +38,9 @@ public class Global
public const string PacFileName = NamespaceSample + "pac"; public const string PacFileName = NamespaceSample + "pac";
public const string ProxySetOSXShellFileName = NamespaceSample + "proxy_set_osx_sh"; public const string ProxySetOSXShellFileName = NamespaceSample + "proxy_set_osx_sh";
public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh"; 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 DefaultSecurity = "auto";
public const string DefaultNetwork = "tcp"; public const string DefaultNetwork = "tcp";
@ -48,6 +49,7 @@ public class Global
public const string ProxyTag = "proxy"; public const string ProxyTag = "proxy";
public const string DirectTag = "direct"; public const string DirectTag = "direct";
public const string BlockTag = "block"; public const string BlockTag = "block";
public const string DnsTag = "dns-module";
public const string StreamSecurity = "tls"; public const string StreamSecurity = "tls";
public const string StreamSecurityReality = "reality"; public const string StreamSecurityReality = "reality";
public const string Loopback = "127.0.0.1"; public const string Loopback = "127.0.0.1";
@ -56,6 +58,9 @@ public class Global
public const string HttpsProtocol = "https://"; public const string HttpsProtocol = "https://";
public const string SocksProtocol = "socks://"; public const string SocksProtocol = "socks://";
public const string Socks5Protocol = "socks5://"; 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 UserEMail = "t@t.tt";
public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run"; public const string AutoRunRegPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
@ -76,6 +81,13 @@ public class Global
public const int SpeedTestPageSize = 1000; public const int SpeedTestPageSize = 1000;
public const string LinuxBash = "/bin/bash"; public const string LinuxBash = "/bin/bash";
public const string SingboxDirectDNSTag = "direct_dns";
public const string SingboxRemoteDNSTag = "remote_dns";
public const string SingboxOutboundResolverTag = "outbound_resolver";
public const string SingboxFinalResolverTag = "final_resolver";
public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns";
public static readonly List<string> IEProxyProtocols = public static readonly List<string> IEProxyProtocols =
[ [
"{ip}:{http_port}", "{ip}:{http_port}",
@ -129,24 +141,24 @@ public class Global
]; ];
public static readonly List<string> SingboxRulesetSources = 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://raw.githubusercontent.com/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/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
]; ];
public static readonly List<string> RoutingRulesSources = public static readonly List<string> RoutingRulesSources =
[ [
"", "",
@"https://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-custom-routing-list@main/v2rayN/template.json", @"https://raw.githubusercontent.com/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/Chocolate4U/Iran-v2ray-rules/main/v2rayN/template.json"
]; ];
public static readonly List<string> DNSTemplateSources = public static readonly List<string> DNSTemplateSources =
[ [
"", "",
@"https://cdn.jsdelivr.net/gh/runetfreedom/russia-v2ray-custom-routing-list@main/v2rayN/", @"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-custom-routing-list/main/v2rayN/",
@"https://cdn.jsdelivr.net/gh/Chocolate4U/Iran-v2ray-rules@main/v2rayN/" @"https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/main/v2rayN/"
]; ];
public static readonly Dictionary<string, string> UserAgentTexts = new() public static readonly Dictionary<string, string> UserAgentTexts = new()
@ -169,7 +181,8 @@ public class Global
{ EConfigType.Trojan, "trojan://" }, { EConfigType.Trojan, "trojan://" },
{ EConfigType.Hysteria2, "hysteria2://" }, { EConfigType.Hysteria2, "hysteria2://" },
{ EConfigType.TUIC, "tuic://" }, { EConfigType.TUIC, "tuic://" },
{ EConfigType.WireGuard, "wireguard://" } { EConfigType.WireGuard, "wireguard://" },
{ EConfigType.Anytls, "anytls://" }
}; };
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new() public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
@ -182,7 +195,8 @@ public class Global
{ EConfigType.Trojan, "trojan" }, { EConfigType.Trojan, "trojan" },
{ EConfigType.Hysteria2, "hysteria2" }, { EConfigType.Hysteria2, "hysteria2" },
{ EConfigType.TUIC, "tuic" }, { EConfigType.TUIC, "tuic" },
{ EConfigType.WireGuard, "wireguard" } { EConfigType.WireGuard, "wireguard" },
{ EConfigType.Anytls, "anytls" }
}; };
public static readonly List<string> VmessSecurities = public static readonly List<string> VmessSecurities =
@ -276,11 +290,36 @@ public class Global
"sing_box" "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 List<string> DomainStrategies = public static readonly List<string> DomainStrategies =
[ [
"AsIs", AsIs,
"IPIfNonMatch", IPIfNonMatch,
"IPOnDemand" IPOnDemand
]; ];
public static readonly List<string> DomainStrategies4Singbox = public static readonly List<string> DomainStrategies4Singbox =
@ -292,13 +331,6 @@ public class Global
"" ""
]; ];
public static readonly List<string> DomainMatchers =
[
"linear",
"mph",
""
];
public static readonly List<string> Fingerprints = public static readonly List<string> Fingerprints =
[ [
"chrome", "chrome",
@ -349,25 +381,42 @@ public class Global
public static readonly List<string> SingboxDomainStrategy4Out = public static readonly List<string> SingboxDomainStrategy4Out =
[ [
"",
"ipv4_only", "ipv4_only",
"prefer_ipv4", "prefer_ipv4",
"prefer_ipv6", "prefer_ipv6",
"ipv6_only", "ipv6_only"
""
]; ];
public static readonly List<string> DomainDNSAddress = public static readonly List<string> DomainDirectDNSAddress =
[ [
"https://dns.alidns.com/dns-query",
"https://doh.pub/dns-query",
"223.5.5.5", "223.5.5.5",
"223.6.6.6", "119.29.29.29",
"localhost" "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.5.5.5",
"223.6.6.6", "119.29.29.29",
"dhcp://auto" "localhost"
]; ];
public static readonly List<string> Languages = public static readonly List<string> Languages =
@ -436,7 +485,9 @@ public class Global
1280, 1280,
1408, 1408,
1500, 1500,
9000 4064,
9000,
65535
]; ];
public static readonly List<string> TunStacks = public static readonly List<string> TunStacks =
@ -509,6 +560,8 @@ public class Global
{ ECoreType.juicity, "juicity/juicity" }, { ECoreType.juicity, "juicity/juicity" },
{ ECoreType.brook, "txthinking/brook" }, { ECoreType.brook, "txthinking/brook" },
{ ECoreType.overtls, "ShadowsocksR-Live/overtls" }, { ECoreType.overtls, "ShadowsocksR-Live/overtls" },
{ ECoreType.shadowquic, "spongebob888/shadowquic" },
{ ECoreType.mieru, "enfein/mieru" },
{ ECoreType.v2rayN, "2dust/v2rayN" }, { ECoreType.v2rayN, "2dust/v2rayN" },
}; };
@ -519,5 +572,47 @@ public class Global
@"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb" @"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb"
]; ];
public static readonly List<string> IPAPIUrls =
[
@"https://speed.cloudflare.com/meta",
@"https://api.ip.sb/geoip",
@"https://api-ipv4.ip.sb/geoip",
@"https://api-ipv6.ip.sb/geoip",
@"https://api.ipapi.is",
@""
];
public static readonly List<string> OutboundTags =
[
ProxyTag,
DirectTag,
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" } },
{ "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 #endregion const
} }

View file

@ -1,7 +1,9 @@
global using ServiceLib.Base; global using ServiceLib.Base;
global using ServiceLib.Common; global using ServiceLib.Common;
global using ServiceLib.Enums; global using ServiceLib.Enums;
global using ServiceLib.Handler; global using ServiceLib.Handler;
global using ServiceLib.Helper;
global using ServiceLib.Manager;
global using ServiceLib.Handler.Fmt; global using ServiceLib.Handler.Fmt;
global using ServiceLib.Services; global using ServiceLib.Services;
global using ServiceLib.Services.Statistics; global using ServiceLib.Services.Statistics;

View file

@ -0,0 +1,21 @@
using System.Reactive;
using System.Reactive.Subjects;
namespace ServiceLib.Handler;
public static class AppEvents
{
public static readonly Subject<Unit> ProfilesRefreshRequested = new();
public static readonly Subject<string> SendSnackMsgRequested = new();
public static readonly Subject<string> SendMsgViewRequested = new();
public static readonly Subject<Unit> AppExitRequested = new();
public static readonly Subject<bool> ShutdownRequested = new();
public static readonly Subject<Unit> AdjustMainLvColWidthRequested = new();
public static readonly Subject<ServerSpeedItem> DispatcherStatisticsRequested = new();
}

View file

@ -3,7 +3,7 @@ using System.Text.RegularExpressions;
namespace ServiceLib.Handler; namespace ServiceLib.Handler;
public class ConfigHandler public static class ConfigHandler
{ {
private static readonly string _configRes = Global.ConfigFileName; private static readonly string _configRes = Global.ConfigFileName;
private static readonly string _tag = "ConfigHandler"; private static readonly string _tag = "ConfigHandler";
@ -101,6 +101,7 @@ public class ConfigHandler
EnableAutoAdjustMainLvColWidth = true EnableAutoAdjustMainLvColWidth = true
}; };
config.UiItem.MainColumnItem ??= new(); config.UiItem.MainColumnItem ??= new();
config.UiItem.WindowSizeItem ??= new();
if (config.UiItem.CurrentLanguage.IsNullOrEmpty()) if (config.UiItem.CurrentLanguage.IsNullOrEmpty())
{ {
@ -111,6 +112,12 @@ public class ConfigHandler
config.ConstItem ??= new ConstItem(); config.ConstItem ??= new ConstItem();
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
if (config.SimpleDNSItem.GlobalFakeIp is null)
{
config.SimpleDNSItem.GlobalFakeIp = true;
}
config.SpeedTestItem ??= new(); config.SpeedTestItem ??= new();
if (config.SpeedTestItem.SpeedTestTimeout < 10) if (config.SpeedTestItem.SpeedTestTimeout < 10)
{ {
@ -122,7 +129,7 @@ public class ConfigHandler
} }
if (config.SpeedTestItem.SpeedPingTestUrl.IsNullOrEmpty()) if (config.SpeedTestItem.SpeedPingTestUrl.IsNullOrEmpty())
{ {
config.SpeedTestItem.SpeedPingTestUrl = Global.SpeedPingTestUrl; config.SpeedTestItem.SpeedPingTestUrl = Global.SpeedPingTestUrls.First();
} }
if (config.SpeedTestItem.MixedConcurrencyCount < 1) if (config.SpeedTestItem.MixedConcurrencyCount < 1)
{ {
@ -213,7 +220,7 @@ public class ConfigHandler
/// <returns>Result of the operation (0 if successful, -1 if failed)</returns> /// <returns>Result of the operation (0 if successful, -1 if failed)</returns>
public static async Task<int> AddServer(Config config, ProfileItem profileItem) 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) if (item is null)
{ {
item = profileItem; item = profileItem;
@ -245,7 +252,9 @@ public class ConfigHandler
item.PublicKey = profileItem.PublicKey; item.PublicKey = profileItem.PublicKey;
item.ShortId = profileItem.ShortId; item.ShortId = profileItem.ShortId;
item.SpiderX = profileItem.SpiderX; item.SpiderX = profileItem.SpiderX;
item.Mldsa65Verify = profileItem.Mldsa65Verify;
item.Extra = profileItem.Extra; item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled;
} }
var ret = item.ConfigType switch var ret = item.ConfigType switch
@ -259,6 +268,7 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, item), EConfigType.Hysteria2 => await AddHysteria2Server(config, item),
EConfigType.TUIC => await AddTuicServer(config, item), EConfigType.TUIC => await AddTuicServer(config, item),
EConfigType.WireGuard => await AddWireguardServer(config, item), EConfigType.WireGuard => await AddWireguardServer(config, item),
EConfigType.Anytls => await AddAnytlsServer(config, item),
_ => -1, _ => -1,
}; };
return ret; return ret;
@ -330,7 +340,7 @@ public class ConfigHandler
{ {
foreach (var it in indexes) 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) if (item is null)
{ {
continue; continue;
@ -412,7 +422,7 @@ public class ConfigHandler
/// <returns>The default profile item or null if none exists</returns> /// <returns>The default profile item or null if none exists</returns>
public static async Task<ProfileItem?> GetDefaultServer(Config config) 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) if (item is null)
{ {
var item2 = await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(); var item2 = await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync();
@ -443,7 +453,7 @@ public class ConfigHandler
for (int i = 0; i < lstProfile.Count; i++) 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; var sort = 0;
@ -455,7 +465,7 @@ public class ConfigHandler
{ {
return 0; return 0;
} }
sort = ProfileExHandler.Instance.GetSort(lstProfile.First().IndexId) - 1; sort = ProfileExManager.Instance.GetSort(lstProfile.First().IndexId) - 1;
break; break;
} }
@ -465,7 +475,7 @@ public class ConfigHandler
{ {
return 0; return 0;
} }
sort = ProfileExHandler.Instance.GetSort(lstProfile[index - 1].IndexId) - 1; sort = ProfileExManager.Instance.GetSort(lstProfile[index - 1].IndexId) - 1;
break; break;
} }
@ -476,7 +486,7 @@ public class ConfigHandler
{ {
return 0; return 0;
} }
sort = ProfileExHandler.Instance.GetSort(lstProfile[index + 1].IndexId) + 1; sort = ProfileExManager.Instance.GetSort(lstProfile[index + 1].IndexId) + 1;
break; break;
} }
@ -486,7 +496,7 @@ public class ConfigHandler
{ {
return 0; return 0;
} }
sort = ProfileExHandler.Instance.GetSort(lstProfile[^1].IndexId) + 1; sort = ProfileExManager.Instance.GetSort(lstProfile[^1].IndexId) + 1;
break; break;
} }
@ -495,7 +505,7 @@ public class ConfigHandler
break; break;
} }
ProfileExHandler.Instance.SetSort(lstProfile[index].IndexId, sort); ProfileExManager.Instance.SetSort(lstProfile[index].IndexId, sort);
return await Task.FromResult(0); return await Task.FromResult(0);
} }
@ -553,7 +563,7 @@ public class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns> /// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> EditCustomServer(Config config, ProfileItem profileItem) 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) if (item is null)
{ {
item = profileItem; item = profileItem;
@ -595,7 +605,7 @@ public class ConfigHandler
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx(); profileItem.Security = profileItem.Security.TrimEx();
if (!AppHandler.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security)) if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security))
{ {
return -1; return -1;
} }
@ -783,6 +793,35 @@ public class ConfigHandler
return 0; 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> /// <summary>
/// Sort the server list by the specified column /// Sort the server list by the specified column
/// Updates the sort order in the profile extension data /// Updates the sort order in the profile extension data
@ -794,13 +833,16 @@ public class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns> /// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> SortServers(Config config, string subId, string colName, bool asc) 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) if (lstModel.Count <= 0)
{ {
return -1; return -1;
} }
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 var lstProfile = (from t in lstModel
join t2 in lstServerStat on t.IndexId equals t2.IndexId into t2b
from t22 in t2b.DefaultIfEmpty()
join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b join t3 in lstProfileExs on t.IndexId equals t3.IndexId into t3b
from t33 in t3b.DefaultIfEmpty() from t33 in t3b.DefaultIfEmpty()
select new ProfileItemModel select new ProfileItemModel
@ -815,7 +857,11 @@ public class ConfigHandler
StreamSecurity = t.StreamSecurity, StreamSecurity = t.StreamSecurity,
Delay = t33?.Delay ?? 0, Delay = t33?.Delay ?? 0,
Speed = t33?.Speed ?? 0, Speed = t33?.Speed ?? 0,
Sort = t33?.Sort ?? 0 Sort = t33?.Sort ?? 0,
TodayDown = (t22?.TodayDown ?? 0).ToString("D16"),
TodayUp = (t22?.TodayUp ?? 0).ToString("D16"),
TotalDown = (t22?.TotalDown ?? 0).ToString("D16"),
TotalUp = (t22?.TotalUp ?? 0).ToString("D16"),
}).ToList(); }).ToList();
Enum.TryParse(colName, true, out EServerColName name); Enum.TryParse(colName, true, out EServerColName name);
@ -833,6 +879,10 @@ public class ConfigHandler
EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(), EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(),
EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(), EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(),
EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(), EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(),
EServerColName.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(),
EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).ToList(),
EServerColName.TotalDown => lstProfile.OrderBy(t => t.TotalDown).ToList(),
EServerColName.TotalUp => lstProfile.OrderBy(t => t.TotalUp).ToList(),
_ => lstProfile _ => lstProfile
}; };
} }
@ -849,13 +899,17 @@ public class ConfigHandler
EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(), EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(),
EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(), EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(),
EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(), EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(),
EServerColName.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(),
EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).ToList(),
EServerColName.TotalDown => lstProfile.OrderByDescending(t => t.TotalDown).ToList(),
EServerColName.TotalUp => lstProfile.OrderByDescending(t => t.TotalUp).ToList(),
_ => lstProfile _ => lstProfile
}; };
} }
for (var i = 0; i < lstProfile.Count; i++) 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) switch (name)
{ {
@ -864,7 +918,7 @@ public class ConfigHandler
var maxSort = lstProfile.Max(t => t.Sort) + 10; var maxSort = lstProfile.Max(t => t.Sort) + 10;
foreach (var item in lstProfile.Where(item => item.Delay <= 0)) foreach (var item in lstProfile.Where(item => item.Delay <= 0))
{ {
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort); ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
} }
break; break;
@ -874,7 +928,7 @@ public class ConfigHandler
var maxSort = lstProfile.Max(t => t.Sort) + 10; var maxSort = lstProfile.Max(t => t.Sort) + 10;
foreach (var item in lstProfile.Where(item => item.Speed <= 0)) foreach (var item in lstProfile.Where(item => item.Speed <= 0))
{ {
ProfileExHandler.Instance.SetSort(item.IndexId, maxSort); ProfileExManager.Instance.SetSort(item.IndexId, maxSort);
} }
break; break;
@ -913,7 +967,7 @@ public class ConfigHandler
{ {
return -1; return -1;
} }
if (profileItem.Security.IsNotEmpty() && profileItem.Security != Global.None) if (profileItem.Security.IsNullOrEmpty())
{ {
profileItem.Security = Global.None; profileItem.Security = Global.None;
} }
@ -932,7 +986,7 @@ public class ConfigHandler
/// <returns>Tuple with total count and remaining count after deduplication</returns> /// <returns>Tuple with total count and remaining count after deduplication</returns>
public static async Task<Tuple<int, int>> DedupServerList(Config config, string subId) 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) if (lstProfile == null)
{ {
return new Tuple<int, int>(0, 0); return new Tuple<int, int>(0, 0);
@ -1002,15 +1056,15 @@ public class ConfigHandler
if (profileItem.IndexId.IsNullOrEmpty()) if (profileItem.IndexId.IsNullOrEmpty())
{ {
profileItem.IndexId = Utils.GetGuid(false); profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExHandler.Instance.GetMaxSort(); maxSort = ProfileExManager.Instance.GetMaxSort();
} }
if (!toFile && maxSort < 0) if (!toFile && maxSort < 0)
{ {
maxSort = ProfileExHandler.Instance.GetMaxSort(); maxSort = ProfileExManager.Instance.GetMaxSort();
} }
if (maxSort > 0) if (maxSort > 0)
{ {
ProfileExHandler.Instance.SetSort(profileItem.IndexId, maxSort + 1); ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
} }
if (toFile) if (toFile)
@ -1070,7 +1124,7 @@ public class ConfigHandler
{ {
try try
{ {
var item = await AppHandler.Instance.GetProfileItem(indexId); var item = await AppManager.Instance.GetProfileItem(indexId);
if (item == null) if (item == null)
{ {
return 0; return 0;
@ -1115,7 +1169,7 @@ public class ConfigHandler
return result; return result;
} }
var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new(); var profileItem = await AppManager.Instance.GetProfileItem(indexId) ?? new();
profileItem.IndexId = indexId; profileItem.IndexId = indexId;
if (coreType == ECoreType.Xray) if (coreType == ECoreType.Xray)
{ {
@ -1161,7 +1215,7 @@ public class ConfigHandler
ConfigType = EConfigType.SOCKS, ConfigType = EConfigType.SOCKS,
Address = Global.Loopback, Address = Global.Loopback,
Sni = node.Address, //Tun2SocksAddress Sni = node.Address, //Tun2SocksAddress
Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks) Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
}; };
} }
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)) else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
@ -1188,12 +1242,12 @@ public class ConfigHandler
/// <returns>Number of removed servers or -1 if failed</returns> /// <returns>Number of removed servers or -1 if failed</returns>
public static async Task<int> RemoveInvalidServerResult(Config config, string subid) 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 }) if (lstModel is { Count: <= 0 })
{ {
return -1; return -1;
} }
var lstProfileExs = await ProfileExHandler.Instance.GetProfileExs(); var lstProfileExs = await ProfileExManager.Instance.GetProfileExs();
var lstProfile = (from t in lstModel var lstProfile = (from t in lstModel
join t2 in lstProfileExs on t.IndexId equals t2.IndexId join t2 in lstProfileExs on t.IndexId equals t2.IndexId
where t2.Delay == -1 where t2.Delay == -1
@ -1229,7 +1283,7 @@ public class ConfigHandler
if (isSub && subid.IsNotEmpty()) if (isSub && subid.IsNotEmpty())
{ {
await RemoveServersViaSubid(config, subid, isSub); await RemoveServersViaSubid(config, subid, isSub);
subFilter = (await AppHandler.Instance.GetSubItem(subid))?.Filter ?? ""; subFilter = (await AppManager.Instance.GetSubItem(subid))?.Filter ?? "";
} }
var countServers = 0; var countServers = 0;
@ -1277,6 +1331,7 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false), EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false),
EConfigType.TUIC => await AddTuicServer(config, profileItem, false), EConfigType.TUIC => await AddTuicServer(config, profileItem, false),
EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false),
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
_ => -1, _ => -1,
}; };
@ -1312,7 +1367,7 @@ public class ConfigHandler
return -1; return -1;
} }
var subItem = await AppHandler.Instance.GetSubItem(subid); var subItem = await AppManager.Instance.GetSubItem(subid);
var subRemarks = subItem?.Remarks; var subRemarks = subItem?.Remarks;
var preSocksPort = subItem?.PreSocksPort; var preSocksPort = subItem?.PreSocksPort;
@ -1361,6 +1416,11 @@ public class ConfigHandler
{ {
profileItem = V2rayFmt.ResolveFull(strData, subRemarks); profileItem = V2rayFmt.ResolveFull(strData, subRemarks);
} }
//Is Html Page
if (profileItem is null && HtmlPageFmt.IsHtmlPage(strData))
{
return -1;
}
//Is Clash configuration //Is Clash configuration
if (profileItem is null) if (profileItem is null)
{ {
@ -1463,7 +1523,7 @@ public class ConfigHandler
ProfileItem? activeProfile = null; ProfileItem? activeProfile = null;
if (isSub && subid.IsNotEmpty()) if (isSub && subid.IsNotEmpty())
{ {
lstOriSub = await AppHandler.Instance.ProfileItems(subid); lstOriSub = await AppManager.Instance.ProfileItems(subid);
activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId); activeProfile = lstOriSub?.FirstOrDefault(t => t.IndexId == config.IndexId);
} }
@ -1495,7 +1555,7 @@ public class ConfigHandler
//Select active node //Select active node
if (activeProfile != null) 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)); var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true));
if (existItem != null) if (existItem != null)
{ {
@ -1506,13 +1566,13 @@ public class ConfigHandler
//Keep the last traffic statistics //Keep the last traffic statistics
if (lstOriSub != null) if (lstOriSub != null)
{ {
var lstSub = await AppHandler.Instance.ProfileItems(subid); var lstSub = await AppManager.Instance.ProfileItems(subid);
foreach (var item in lstSub) foreach (var item in lstSub)
{ {
var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true)); var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true));
if (existItem != null) if (existItem != null)
{ {
await StatisticsHandler.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId); await StatisticsManager.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId);
} }
} }
} }
@ -1552,7 +1612,7 @@ public class ConfigHandler
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost)) if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{ {
//TODO Temporary reminder to be removed later //TODO Temporary reminder to be removed later
NoticeHandler.Instance.Enqueue(ResUI.InsecureUrlProtocol); NoticeManager.Instance.Enqueue(ResUI.InsecureUrlProtocol);
//return -1; //return -1;
} }
@ -1570,7 +1630,7 @@ public class ConfigHandler
/// <returns>0 if successful, -1 if failed</returns> /// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> AddSubItem(Config config, SubItem subItem) 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) if (item is null)
{ {
item = subItem; item = subItem;
@ -1602,7 +1662,7 @@ public class ConfigHandler
var maxSort = 0; var maxSort = 0;
if (await SQLiteHelper.Instance.TableAsync<SubItem>().CountAsync() > 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; maxSort = lstSubs.LastOrDefault()?.Sort ?? 0;
} }
item.Sort = maxSort + 1; item.Sort = maxSort + 1;
@ -1656,7 +1716,7 @@ public class ConfigHandler
/// <returns>0 if successful</returns> /// <returns>0 if successful</returns>
public static async Task<int> DeleteSubItem(Config config, string id) 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) if (item is null)
{ {
return 0; return 0;
@ -1840,12 +1900,25 @@ public class ConfigHandler
/// <returns>0 if successful</returns> /// <returns>0 if successful</returns>
public static async Task<int> SetDefaultRouting(Config config, RoutingItem routingItem) public static async Task<int> SetDefaultRouting(Config config, RoutingItem routingItem)
{ {
if (await SQLiteHelper.Instance.TableAsync<RoutingItem>().Where(t => t.Id == routingItem.Id).CountAsync() > 0) var items = await AppManager.Instance.RoutingItems();
if (items.Any(t => t.Id == routingItem.Id && t.IsActive == true))
{ {
config.RoutingBasicItem.RoutingIndexId = routingItem.Id; return -1;
} }
await SaveConfig(config); foreach (var item in items)
{
if (item.Id == routingItem.Id)
{
item.IsActive = true;
}
else
{
item.IsActive = false;
}
}
await SQLiteHelper.Instance.UpdateAllAsync(items);
return 0; return 0;
} }
@ -1858,7 +1931,7 @@ public class ConfigHandler
/// <returns>The default routing item</returns> /// <returns>The default routing item</returns>
public static async Task<RoutingItem> GetDefaultRouting(Config config) public static async Task<RoutingItem> GetDefaultRouting(Config config)
{ {
var item = await AppHandler.Instance.GetRoutingItem(config.RoutingBasicItem.RoutingIndexId); var item = await SQLiteHelper.Instance.TableAsync<RoutingItem>().FirstOrDefaultAsync(it => it.IsActive == true);
if (item is null) if (item is null)
{ {
var item2 = await SQLiteHelper.Instance.TableAsync<RoutingItem>().FirstOrDefaultAsync(); var item2 = await SQLiteHelper.Instance.TableAsync<RoutingItem>().FirstOrDefaultAsync();
@ -1907,7 +1980,7 @@ public class ConfigHandler
if (template == null) if (template == null)
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
var items = await AppHandler.Instance.RoutingItems(); var items = await AppManager.Instance.RoutingItems();
var maxSort = items.Count; var maxSort = items.Count;
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0) if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(template.Version)).ToList().Count > 0)
{ {
@ -1954,18 +2027,30 @@ public class ConfigHandler
public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false) public static async Task<int> InitBuiltinRouting(Config config, bool blImportAdvancedRules = false)
{ {
var ver = "V3-"; var ver = "V3-";
var items = await AppHandler.Instance.RoutingItems(); var items = await AppManager.Instance.RoutingItems();
//TODO Temporary code to be removed later //TODO Temporary code to be removed later
var lockItem = items?.FirstOrDefault(t => t.Locked == true); var lockItem = items?.FirstOrDefault(t => t.Locked == true);
if (lockItem != null) if (lockItem != null)
{ {
await ConfigHandler.RemoveRoutingItem(lockItem); await ConfigHandler.RemoveRoutingItem(lockItem);
items = await AppHandler.Instance.RoutingItems(); items = await AppManager.Instance.RoutingItems();
} }
if (!blImportAdvancedRules && items.Where(t => t.Remarks.StartsWith(ver)).ToList().Count > 0) if (!blImportAdvancedRules && items.Count > 0)
{ {
//migrate
//TODO Temporary code to be removed later
if (config.RoutingBasicItem.RoutingIndexId.IsNotEmpty())
{
var item = items.FirstOrDefault(t => t.Id == config.RoutingBasicItem.RoutingIndexId);
if (item != null)
{
await SetDefaultRouting(config, item);
}
config.RoutingBasicItem.RoutingIndexId = string.Empty;
}
return 0; return 0;
} }
@ -2020,18 +2105,38 @@ public class ConfigHandler
/// <summary> /// <summary>
/// Initialize built-in DNS configurations /// Initialize built-in DNS configurations
/// Creates default DNS items for V2Ray and sing-box /// Creates default DNS items for V2Ray and sing-box
/// Also checks existing DNS items and disables those with empty NormalDNS
/// </summary> /// </summary>
/// <param name="config">Current configuration</param> /// <param name="config">Current configuration</param>
/// <returns>0 if successful</returns> /// <returns>0 if successful</returns>
public static async Task<int> InitBuiltinDNS(Config config) 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) if (items.Count <= 0)
{ {
var item = new DNSItem() var item = new DNSItem()
{ {
Remarks = "V2ray", Remarks = "V2ray",
CoreType = ECoreType.Xray, CoreType = ECoreType.Xray,
Enabled = false,
}; };
await SaveDNSItems(config, item); await SaveDNSItems(config, item);
@ -2039,6 +2144,7 @@ public class ConfigHandler
{ {
Remarks = "sing-box", Remarks = "sing-box",
CoreType = ECoreType.sing_box, CoreType = ECoreType.sing_box,
Enabled = false,
}; };
await SaveDNSItems(config, item2); await SaveDNSItems(config, item2);
} }
@ -2083,7 +2189,7 @@ public class ConfigHandler
/// <returns>DNS item with configuration from the URL</returns> /// <returns>DNS item with configuration from the URL</returns>
public static async Task<DNSItem> GetExternalDNSItem(ECoreType type, string url) 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 downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, ""); var templateContent = await downloadHandle.TryDownloadString(url, true, "");
@ -2110,6 +2216,87 @@ public class ConfigHandler
#endregion DNS #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(),
SingboxOutboundsResolveDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
SingboxFinalResolveDNS = Global.DomainPureIPDNSAddress.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 #region Regional Presets
/// <summary> /// <summary>
@ -2131,31 +2318,98 @@ public class ConfigHandler
await SQLiteHelper.Instance.DeleteAllAsync<DNSItem>(); await SQLiteHelper.Instance.DeleteAllAsync<DNSItem>();
await InitBuiltinDNS(config); await InitBuiltinDNS(config);
return true; config.SimpleDNSItem = InitBuiltinSimpleDNS();
break;
case EPresetType.Russia: case EPresetType.Russia:
config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[1]; config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[1];
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1]; config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[1];
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1]; config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[1];
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json")); var xrayDnsRussia = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[1] + "v2ray.json");
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[1] + "sing_box.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: case EPresetType.Iran:
config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[2]; config.ConstItem.GeoSourceUrl = Global.GeoFilesSources[2];
config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2]; config.ConstItem.SrsSourceUrl = Global.SingboxRulesetSources[2];
config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2]; config.ConstItem.RouteRulesTemplateSourceUrl = Global.RoutingRulesSources[2];
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json")); var xrayDnsIran = await GetExternalDNSItem(ECoreType.Xray, Global.DNSTemplateSources[2] + "v2ray.json");
await SaveDNSItems(config, await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json")); var singboxDnsIran = await GetExternalDNSItem(ECoreType.sing_box, Global.DNSTemplateSources[2] + "sing_box.json");
var simpleDnsIran = await GetExternalSimpleDNSItem(Global.DNSTemplateSources[2] + "simple_dns.json");
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 true; return true;
} }
return false; #endregion Regional Presets
#region UIItem
public static WindowSizeItem? GetWindowSizeItem(Config config, string typeName)
{
var sizeItem = config?.UiItem?.WindowSizeItem?.FirstOrDefault(t => t.TypeName == typeName);
if (sizeItem == null || sizeItem.Width <= 0 || sizeItem.Height <= 0)
{
return null;
} }
#endregion Regional Presets return sizeItem;
}
public static int SaveWindowSizeItem(Config config, string typeName, double width, double height)
{
var sizeItem = config?.UiItem?.WindowSizeItem?.FirstOrDefault(t => t.TypeName == typeName);
if (sizeItem == null)
{
sizeItem = new WindowSizeItem { TypeName = typeName };
config.UiItem.WindowSizeItem.Add(sizeItem);
}
sizeItem.Width = (int)width;
sizeItem.Height = (int)height;
return 0;
}
public static int SaveMainGirdHeight(Config config, double height1, double height2)
{
var uiItem = config.UiItem ?? new();
uiItem.MainGirdHeight1 = (int)(height1 + 0.1);
uiItem.MainGirdHeight2 = (int)(height2 + 0.1);
return 0;
}
#endregion UIItem
} }

View file

@ -0,0 +1,70 @@
using System.Net;
namespace ServiceLib.Handler;
public static class ConnectionHandler
{
private static readonly string _tag = "ConnectionHandler";
public static async Task<string> RunAvailabilityCheck()
{
var time = await GetRealPingTime();
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
return string.Format(ResUI.TestMeOutput, time, ip);
}
private static async Task<string?> GetIPInfo()
{
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)
{
return null;
}
var ipInfo = JsonUtils.Deserialize<IPAPIInfo>(result);
if (ipInfo == null)
{
return null;
}
var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query;
var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code;
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

@ -3,13 +3,13 @@ namespace ServiceLib.Handler;
/// <summary> /// <summary>
/// Core configuration file processing class /// Core configuration file processing class
/// </summary> /// </summary>
public class CoreConfigHandler public static class CoreConfigHandler
{ {
private static readonly string _tag = "CoreConfigHandler"; private static readonly string _tag = "CoreConfigHandler";
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName) public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
{ {
var config = AppHandler.Instance.Config; var config = AppManager.Instance.Config;
var result = new RetResult(); var result = new RetResult();
if (node.ConfigType == EConfigType.Custom) if (node.ConfigType == EConfigType.Custom)
@ -21,7 +21,7 @@ public class CoreConfigHandler
_ => await GenerateClientCustomConfig(node, fileName) _ => 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); 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) public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
{ {
var result = new RetResult(); 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); var port = Utils.GetFreePort(initPort + testItem.QueueNum);
testItem.Port = port; 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); result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
} }

View file

@ -1,409 +0,0 @@
using System.Diagnostics;
using System.Text;
namespace ServiceLib.Handler;
/// <summary>
/// Core process processing class
/// </summary>
public class CoreHandler
{
private static readonly Lazy<CoreHandler> _instance = new(() => new());
public static CoreHandler Instance => _instance.Value;
private Config _config;
private Process? _process;
private Process? _processPre;
private int _linuxSudoPid = -1;
private Action<bool, string>? _updateFunc;
private const string _tag = "CoreHandler";
public async Task Init(Config config, Action<bool, string> 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")
{
var fromPath = Utils.GetBaseDirectory("bin");
var toPath = Utils.GetBinPath("");
if (fromPath != toPath)
{
FileManager.CopyDirectory(fromPath, toPath, true, false);
}
}
if (Utils.IsNonWindows())
{
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo();
foreach (var it in coreInfo)
{
if (it.CoreType == ECoreType.v2rayN)
{
if (Utils.UpgradeAppExists(out var upgradeFileName))
{
await Utils.SetLinuxChmod(upgradeFileName);
}
continue;
}
foreach (var name in it.CoreExes)
{
var exe = Utils.GetBinPath(Utils.GetExeName(name), it.CoreType.ToString());
if (File.Exists(exe))
{
await Utils.SetLinuxChmod(exe);
}
}
}
}
}
public async Task LoadCore(ProfileItem? node)
{
if (node == null)
{
UpdateFunc(false, ResUI.CheckServerSettings);
return;
}
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
if (result.Success != true)
{
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 CoreStop();
await Task.Delay(100);
if (Utils.IsWindows() && _config.TunModeItem.EnableTun)
{
await Task.Delay(100);
await WindowsUtils.RemoveTunDevice();
}
await CoreStart(node);
await CoreStartPreService(node);
if (_process != null)
{
UpdateFunc(true, $"{node.GetSummary()}");
}
}
public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
{
var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC) ? 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);
if (result.Success != true)
{
return -1;
}
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
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;
}
public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem)
{
var node = await AppHandler.Instance.GetProfileItem(testItem.IndexId);
if (node is null)
{
return -1;
}
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
if (result.Success != true)
{
return -1;
}
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;
}
public async Task CoreStop()
{
try
{
if (_process != null)
{
await ProcUtils.ProcessKill(_process, true);
_process = null;
}
if (_processPre != null)
{
await ProcUtils.ProcessKill(_processPre, true);
_processPre = null;
}
if (_linuxSudoPid > 0)
{
await KillProcessAsLinuxSudo();
}
_linuxSudoPid = -1;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#region Private
private async Task CoreStart(ProfileItem node)
{
var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
if (proc is null)
{
return;
}
_process = proc;
}
private async Task CoreStartPreService(ProfileItem node)
{
if (_process != null && !_process.HasExited)
{
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
if (itemSocks != null)
{
var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box;
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
if (result.Success)
{
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType);
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
if (proc is null)
{
return;
}
_processPre = proc;
}
}
}
}
private void UpdateFunc(bool notify, string msg)
{
_updateFunc?.Invoke(notify, msg);
}
private bool IsNeedSudo(ECoreType eCoreType)
{
return _config.TunModeItem.EnableTun
&& eCoreType == ECoreType.sing_box
&& (Utils.IsNonWindows())
//&& _config.TunModeItem.LinuxSudoPwd.IsNotEmpty()
;
}
#endregion Private
#region Process
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
{
var fileName = CoreInfoHandler.Instance.GetCoreExecFile(coreInfo, out var msg);
if (fileName.IsNullOrEmpty())
{
UpdateFunc(false, msg);
return null;
}
try
{
Process proc = new()
{
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,
}
};
var isNeedSudo = mayNeedSudo && IsNeedSudo(coreInfo.CoreType);
if (isNeedSudo)
{
await RunProcessAsLinuxSudo(proc, fileName, coreInfo, configPath);
}
if (displayLog)
{
proc.OutputDataReceived += (sender, e) =>
{
if (e.Data.IsNullOrEmpty())
return;
UpdateFunc(false, e.Data + Environment.NewLine);
};
proc.ErrorDataReceived += (sender, e) =>
{
if (e.Data.IsNullOrEmpty())
return;
UpdateFunc(false, e.Data + Environment.NewLine);
};
}
proc.Start();
if (isNeedSudo && _config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
{
var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd);
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(pwd);
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(pwd);
}
if (isNeedSudo)
_linuxSudoPid = proc.Id;
if (displayLog)
{
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
}
await Task.Delay(500);
AppHandler.Instance.AddProcess(proc.Handle);
if (proc is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
return proc;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
UpdateFunc(mayNeedSudo, ex.Message);
return null;
}
}
#endregion Process
#region Linux
private async Task RunProcessAsLinuxSudo(Process proc, string fileName, CoreInfo coreInfo, string configPath)
{
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh");
proc.StartInfo.FileName = shFilePath;
proc.StartInfo.Arguments = "";
proc.StartInfo.WorkingDirectory = "";
if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
{
proc.StartInfo.StandardInputEncoding = Encoding.UTF8;
proc.StartInfo.RedirectStandardInput = true;
}
}
private async Task KillProcessAsLinuxSudo()
{
var cmdLine = $"kill {_linuxSudoPid}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh");
Process proc = new()
{
StartInfo = new()
{
FileName = shFilePath,
UseShellExecute = false,
CreateNoWindow = true,
StandardInputEncoding = Encoding.UTF8,
RedirectStandardInput = true
}
};
proc.Start();
if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
{
try
{
var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd);
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(pwd);
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(pwd);
}
catch (Exception)
{
// ignored
}
}
var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10));
await proc.WaitForExitAsync(timeout.Token);
await Task.Delay(3000);
}
private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName)
{
//Shell scripts
var shFilePath = Utils.GetBinConfigPath(AppHandler.Instance.IsAdministrator ? "root_" + fileName : fileName);
File.Delete(shFilePath);
var sb = new StringBuilder();
sb.AppendLine("#!/bin/sh");
if (AppHandler.Instance.IsAdministrator)
{
sb.AppendLine($"{cmdLine}");
}
else if (_config.TunModeItem.LinuxSudoPwd.IsNullOrEmpty())
{
sb.AppendLine($"pkexec {cmdLine}");
}
else
{
sb.AppendLine($"sudo -S {cmdLine}");
}
await File.WriteAllTextAsync(shFilePath, sb.ToString());
await Utils.SetLinuxChmod(shFilePath);
Logging.SaveLog(shFilePath);
return shFilePath;
}
#endregion Linux
}

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)); dicQuery.Add("spx", Utils.UrlEncode(item.SpiderX));
} }
if (item.Mldsa65Verify.IsNotEmpty())
{
dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify));
}
if (item.AllowInsecure.Equals("true")) if (item.AllowInsecure.Equals("true"))
{ {
dicQuery.Add("allowInsecure", "1"); dicQuery.Add("allowInsecure", "1");
@ -159,6 +163,7 @@ public class BaseFmt
item.PublicKey = Utils.UrlDecode(query["pbk"] ?? ""); item.PublicKey = Utils.UrlDecode(query["pbk"] ?? "");
item.ShortId = Utils.UrlDecode(query["sid"] ?? ""); item.ShortId = Utils.UrlDecode(query["sid"] ?? "");
item.SpiderX = Utils.UrlDecode(query["spx"] ?? ""); item.SpiderX = Utils.UrlDecode(query["spx"] ?? "");
item.Mldsa65Verify = Utils.UrlDecode(query["pqv"] ?? "");
item.AllowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : ""; item.AllowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : "";
item.Network = query["type"] ?? nameof(ETransport.tcp); item.Network = query["type"] ?? nameof(ETransport.tcp);
@ -215,14 +220,7 @@ public class BaseFmt
protected static bool Contains(string str, params string[] s) protected static bool Contains(string str, params string[] s)
{ {
foreach (var item in s) return s.All(item => str.Contains(item, StringComparison.OrdinalIgnoreCase));
{
if (str.Contains(item, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
} }
protected static string WriteAllText(string strData, string ext = "json") protected static string WriteAllText(string strData, string ext = "json")

View file

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

View file

@ -18,6 +18,7 @@ public class FmtHandler
EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item), EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item),
EConfigType.TUIC => TuicFmt.ToUri(item), EConfigType.TUIC => TuicFmt.ToUri(item),
EConfigType.WireGuard => WireguardFmt.ToUri(item), EConfigType.WireGuard => WireguardFmt.ToUri(item),
EConfigType.Anytls => AnytlsFmt.ToUri(item),
_ => null, _ => null,
}; };
@ -75,6 +76,10 @@ public class FmtHandler
{ {
return WireguardFmt.Resolve(str, out msg); return WireguardFmt.Resolve(str, out msg);
} }
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls]))
{
return AnytlsFmt.Resolve(str, out msg);
}
else else
{ {
msg = ResUI.NonvmessOrssProtocol; 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

@ -24,7 +24,7 @@ public class Hysteria2Fmt : BaseFmt
item.Path = Utils.UrlDecode(query["obfs-password"] ?? ""); item.Path = Utils.UrlDecode(query["obfs-password"] ?? "");
item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false"; item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false";
item.Ports = Utils.UrlDecode(query["mport"] ?? "").Replace('-', ':'); item.Ports = Utils.UrlDecode(query["mport"] ?? "");
return item; return item;
} }

View file

@ -6,9 +6,9 @@ public class VmessFmt : BaseFmt
{ {
msg = ResUI.ConfigurationFormatIncorrect; msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem? item; ProfileItem? item;
if (str.IndexOf('?') > 0 && str.IndexOf('&') > 0) if (str.IndexOf('@') > 0)
{ {
item = ResolveStdVmess(str); item = ResolveStdVmess(str) ?? ResolveVmess(str, out msg);
} }
else else
{ {

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; namespace ServiceLib.Handler.SysProxy;
public class ProxySettingLinux public static class ProxySettingLinux
{ {
private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh"; private static readonly string _proxySetFileName = $"{Global.ProxySetLinuxShellFileName.Replace(Global.NamespaceSample, "")}.sh";
@ -18,14 +18,7 @@ public class ProxySettingLinux
private static async Task ExecCmd(List<string> args) private static async Task ExecCmd(List<string> args)
{ {
var fileName = Utils.GetBinConfigPath(_proxySetFileName); var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false);
if (!File.Exists(fileName))
{
var contents = EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName);
await File.AppendAllTextAsync(fileName, contents);
await Utils.SetLinuxChmod(fileName);
}
await Utils.GetCliWrapOutput(fileName, args); await Utils.GetCliWrapOutput(fileName, args);
} }

View file

@ -1,6 +1,6 @@
namespace ServiceLib.Handler.SysProxy; namespace ServiceLib.Handler.SysProxy;
public class ProxySettingOSX public static class ProxySettingOSX
{ {
private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh"; private static readonly string _proxySetFileName = $"{Global.ProxySetOSXShellFileName.Replace(Global.NamespaceSample, "")}.sh";
@ -23,14 +23,7 @@ public class ProxySettingOSX
private static async Task ExecCmd(List<string> args) private static async Task ExecCmd(List<string> args)
{ {
var fileName = Utils.GetBinConfigPath(_proxySetFileName); var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false);
if (!File.Exists(fileName))
{
var contents = EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName);
await File.AppendAllTextAsync(fileName, contents);
await Utils.SetLinuxChmod(fileName);
}
await Utils.GetCliWrapOutput(fileName, args); await Utils.GetCliWrapOutput(fileName, args);
} }

View file

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

View file

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

View file

@ -1,7 +1,7 @@
using System.Net; using System.Net;
using Downloader; using Downloader;
namespace ServiceLib.Common; namespace ServiceLib.Helper;
public class DownloaderHelper public class DownloaderHelper
{ {
@ -26,7 +26,7 @@ public class DownloaderHelper
var downloadOpt = new DownloadConfiguration() var downloadOpt = new DownloadConfiguration()
{ {
Timeout = timeout * 1000, Timeout = timeout * 1000,
MaxTryAgainOnFailover = 2, MaxTryAgainOnFailure = 2,
RequestConfiguration = RequestConfiguration =
{ {
Headers = headers, Headers = headers,
@ -64,7 +64,7 @@ public class DownloaderHelper
var downloadOpt = new DownloadConfiguration() var downloadOpt = new DownloadConfiguration()
{ {
Timeout = timeout * 1000, Timeout = timeout * 1000,
MaxTryAgainOnFailover = 2, MaxTryAgainOnFailure = 2,
RequestConfiguration = RequestConfiguration =
{ {
Timeout= timeout * 1000, Timeout= timeout * 1000,
@ -135,7 +135,7 @@ public class DownloaderHelper
var downloadOpt = new DownloadConfiguration() var downloadOpt = new DownloadConfiguration()
{ {
Timeout = timeout * 1000, Timeout = timeout * 1000,
MaxTryAgainOnFailover = 2, MaxTryAgainOnFailure = 2,
RequestConfiguration = RequestConfiguration =
{ {
Timeout= timeout * 1000, Timeout= timeout * 1000,

View file

@ -1,8 +1,10 @@
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Mime; using System.Net.Mime;
using System.Text; using System.Text;
namespace ServiceLib.Common; namespace ServiceLib.Helper;
/// <summary> /// <summary>
/// </summary> /// </summary>
@ -202,4 +204,35 @@ public class HttpClientHelper
} }
} while (isMoreToRead); } 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 System.Collections;
using SQLite; using SQLite;
namespace ServiceLib.Common; namespace ServiceLib.Helper;
public sealed class SQLiteHelper public sealed class SQLiteHelper
{ {
@ -26,7 +26,7 @@ public sealed class SQLiteHelper
public async Task<int> InsertAllAsync(IEnumerable models) 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) public async Task<int> InsertAsync(object model)
@ -46,7 +46,7 @@ public sealed class SQLiteHelper
public async Task<int> UpdateAllAsync(IEnumerable models) 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) public async Task<int> DeleteAsync(object model)

View file

@ -1,16 +1,17 @@
namespace ServiceLib.Handler; using System.Reactive;
public sealed class AppHandler namespace ServiceLib.Manager;
public sealed class AppManager
{ {
#region Property #region Property
private static readonly Lazy<AppHandler> _instance = new(() => new()); private static readonly Lazy<AppManager> _instance = new(() => new());
private Config _config; private Config _config;
private int? _statePort; private int? _statePort;
private int? _statePort2; private int? _statePort2;
private Job? _processJob; private Job? _processJob;
private bool? _isAdministrator; public static AppManager Instance => _instance.Value;
public static AppHandler Instance => _instance.Value;
public Config Config => _config; public Config Config => _config;
public int StatePort public int StatePort
@ -31,18 +32,11 @@ public sealed class AppHandler
} }
} }
public bool IsAdministrator public string LinuxSudoPwd { get; set; }
{
get
{
_isAdministrator ??= Utils.IsAdministrator();
return _isAdministrator.Value;
}
}
#endregion Property #endregion Property
#region Init #region App
public bool InitApp() public bool InitApp()
{ {
@ -72,6 +66,7 @@ public sealed class AppHandler
SQLiteHelper.Instance.CreateTable<RoutingItem>(); SQLiteHelper.Instance.CreateTable<RoutingItem>();
SQLiteHelper.Instance.CreateTable<ProfileExItem>(); SQLiteHelper.Instance.CreateTable<ProfileExItem>();
SQLiteHelper.Instance.CreateTable<DNSItem>(); SQLiteHelper.Instance.CreateTable<DNSItem>();
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
return true; return true;
} }
@ -94,7 +89,40 @@ public sealed class AppHandler
return true; return true;
} }
#endregion Init public async Task AppExitAsync(bool needShutdown)
{
try
{
Logging.SaveLog("AppExitAsync Begin");
await SysProxyHandler.UpdateSysProxy(_config, true);
AppEvents.AppExitRequested.OnNext(Unit.Default);
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.OnNext(byUser);
}
#endregion App
#region Config #region Config
@ -104,7 +132,7 @@ public sealed class AppHandler
return localPort + (int)protocol; return localPort + (int)protocol;
} }
public void AddProcess(IntPtr processHandle) public void AddProcess(nint processHandle)
{ {
if (Utils.IsWindows()) if (Utils.IsWindows())
{ {
@ -211,6 +239,16 @@ public sealed class AppHandler
return await SQLiteHelper.Instance.TableAsync<DNSItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType); 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 #endregion SqliteHelper
#region Core Type #region Core Type

View file

@ -1,11 +1,11 @@
using static ServiceLib.Models.ClashProxies; 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()); private static readonly Lazy<ClashApiManager> instance = new(() => new());
public static ClashApiHandler Instance => instance.Value; public static ClashApiManager Instance => instance.Value;
private static readonly string _tag = "ClashApiHandler"; private static readonly string _tag = "ClashApiHandler";
private Dictionary<string, ProxiesItem>? _proxies; private Dictionary<string, ProxiesItem>? _proxies;
@ -35,7 +35,7 @@ public sealed class ClashApiHandler
return null; 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 () => Task.Run(async () =>
{ {
@ -65,7 +65,7 @@ public sealed class ClashApiHandler
return; return;
} }
var urlBase = $"{GetApiUrl()}/proxies"; 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>(); var tasks = new List<Task>();
foreach (var it in lstProxy) foreach (var it in lstProxy)
@ -79,12 +79,12 @@ public sealed class ClashApiHandler
tasks.Add(Task.Run(async () => tasks.Add(Task.Run(async () =>
{ {
var result = await HttpClientHelper.Instance.TryGetAsync(url); var result = await HttpClientHelper.Instance.TryGetAsync(url);
updateFunc?.Invoke(it, result); await updateFunc?.Invoke(it, result);
})); }));
} }
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
await Task.Delay(1000); await Task.Delay(1000);
updateFunc?.Invoke(null, ""); await updateFunc?.Invoke(null, "");
}); });
} }
@ -182,6 +182,6 @@ public sealed class ClashApiHandler
private string GetApiUrl() private string GetApiUrl()
{ {
return $"{Global.HttpProtocol}{Global.Loopback}:{AppHandler.Instance.StatePort2}"; return $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort2}";
} }
} }

View file

@ -0,0 +1,118 @@
using System.Diagnostics;
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<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(AppManager.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(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; private List<CoreInfo>? _coreInfo;
public static CoreInfoHandler Instance => _instance.Value; public static CoreInfoManager Instance => _instance.Value;
public CoreInfoHandler() public CoreInfoManager()
{ {
InitCoreInfo(); InitCoreInfo();
} }
@ -80,6 +80,10 @@ public sealed class CoreInfoHandler
Url = GetCoreUrl(ECoreType.v2fly), Url = GetCoreUrl(ECoreType.v2fly),
Match = "V2Ray", Match = "V2Ray",
VersionArg = "-version", VersionArg = "-version",
Environment = new Dictionary<string, string?>()
{
{ Global.V2RayLocalAsset, Utils.GetBinPath("") },
},
}, },
new CoreInfo new CoreInfo
@ -90,6 +94,10 @@ public sealed class CoreInfoHandler
Url = GetCoreUrl(ECoreType.v2fly_v5), Url = GetCoreUrl(ECoreType.v2fly_v5),
Match = "V2Ray", Match = "V2Ray",
VersionArg = "version", VersionArg = "version",
Environment = new Dictionary<string, string?>()
{
{ Global.V2RayLocalAsset, Utils.GetBinPath("") },
},
}, },
new CoreInfo new CoreInfo
@ -107,20 +115,25 @@ public sealed class CoreInfoHandler
DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip", DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip",
Match = "Xray", Match = "Xray",
VersionArg = "-version", VersionArg = "-version",
Environment = new Dictionary<string, string?>()
{
{ Global.XrayLocalAsset, Utils.GetBinPath("") },
{ Global.XrayLocalCert, Utils.GetBinPath("") },
},
}, },
new CoreInfo new CoreInfo
{ {
CoreType = ECoreType.mihomo, 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(), Arguments = "-f {0}" + PortableMode(),
Url = GetCoreUrl(ECoreType.mihomo), Url = GetCoreUrl(ECoreType.mihomo),
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl), 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", 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", 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", DownloadUrlOSXArm64 = urlMihomo + "/download/{0}/mihomo-darwin-arm64-{0}.gz",
Match = "Mihomo", Match = "Mihomo",
VersionArg = "-v", VersionArg = "-v",
@ -200,8 +213,29 @@ public sealed class CoreInfoHandler
Arguments = "-r client -c {0}", Arguments = "-r client -c {0}",
Url = GetCoreUrl(ECoreType.overtls), Url = GetCoreUrl(ECoreType.overtls),
AbsolutePath = false, AbsolutePath = false,
} },
new CoreInfo
{
CoreType = ECoreType.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

@ -0,0 +1,311 @@
using System.Diagnostics;
using System.Text;
namespace ServiceLib.Manager;
/// <summary>
/// Core process processing class
/// </summary>
public class CoreManager
{
private static readonly Lazy<CoreManager> _instance = new(() => new());
public static CoreManager Instance => _instance.Value;
private Config _config;
private Process? _process;
private Process? _processPre;
private bool _linuxSudo = false;
private Func<bool, string, Task>? _updateFunc;
private const string _tag = "CoreHandler";
public async Task Init(Config config, Func<bool, string, Task> updateFunc)
{
_config = config;
_updateFunc = updateFunc;
//Copy the bin folder to the storage location (for init)
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
{
var fromPath = Utils.GetBaseDirectory("bin");
var toPath = Utils.GetBinPath("");
if (fromPath != toPath)
{
FileManager.CopyDirectory(fromPath, toPath, true, false);
}
}
if (Utils.IsNonWindows())
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
foreach (var it in coreInfo)
{
if (it.CoreType == ECoreType.v2rayN)
{
if (Utils.UpgradeAppExists(out var upgradeFileName))
{
await Utils.SetLinuxChmod(upgradeFileName);
}
continue;
}
foreach (var name in it.CoreExes)
{
var exe = Utils.GetBinPath(Utils.GetExeName(name), it.CoreType.ToString());
if (File.Exists(exe))
{
await Utils.SetLinuxChmod(exe);
}
}
}
}
}
public async Task LoadCore(ProfileItem? node)
{
if (node == null)
{
await UpdateFunc(false, ResUI.CheckServerSettings);
return;
}
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
if (result.Success != true)
{
await UpdateFunc(true, result.Msg);
return;
}
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);
if (Utils.IsWindows() && _config.TunModeItem.EnableTun)
{
await Task.Delay(100);
await WindowsUtils.RemoveTunDevice();
}
await CoreStart(node);
await CoreStartPreService(node);
if (_process != null)
{
await UpdateFunc(true, $"{node.GetSummary()}");
}
}
public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
{
var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? 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);
await UpdateFunc(false, result.Msg);
if (result.Success != true)
{
return -1;
}
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
await UpdateFunc(false, configPath);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null)
{
return -1;
}
return proc.Id;
}
public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem)
{
var node = await AppManager.Instance.GetProfileItem(testItem.IndexId);
if (node is null)
{
return -1;
}
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
if (result.Success != true)
{
return -1;
}
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null)
{
return -1;
}
return proc.Id;
}
public async Task CoreStop()
{
try
{
if (_linuxSudo)
{
await CoreAdminManager.Instance.KillProcessAsLinuxSudo();
_linuxSudo = false;
}
if (_process != null)
{
await ProcUtils.ProcessKill(_process, Utils.IsWindows());
_process = null;
}
if (_processPre != null)
{
await ProcUtils.ProcessKill(_processPre, Utils.IsWindows());
_processPre = null;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#region Private
private async Task CoreStart(ProfileItem node)
{
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);
if (proc is null)
{
return;
}
_process = proc;
}
private async Task CoreStartPreService(ProfileItem node)
{
if (_process != null && !_process.HasExited)
{
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
if (itemSocks != null)
{
var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box;
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
if (result.Success)
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType);
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
if (proc is null)
{
return;
}
_processPre = proc;
}
}
}
}
private async Task UpdateFunc(bool notify, string msg)
{
await _updateFunc?.Invoke(notify, msg);
}
#endregion Private
#region Process
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
{
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
if (fileName.IsNullOrEmpty())
{
await UpdateFunc(false, msg);
return null;
}
try
{
if (mayNeedSudo
&& _config.TunModeItem.EnableTun
&& coreInfo.CoreType == ECoreType.sing_box
&& Utils.IsNonWindows())
{
_linuxSudo = true;
await CoreAdminManager.Instance.Init(_config, _updateFunc);
return await CoreAdminManager.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
}
return await RunProcessNormal(fileName, coreInfo, configPath, displayLog);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
await UpdateFunc(mayNeedSudo, ex.Message);
return null;
}
}
private async Task<Process?> RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog)
{
Process proc = new()
{
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,
}
};
foreach (var kv in coreInfo.Environment)
{
proc.StartInfo.Environment[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath);
}
if (displayLog)
{
void dataHandler(object sender, DataReceivedEventArgs e)
{
if (e.Data.IsNotEmpty())
{
_ = UpdateFunc(false, e.Data + Environment.NewLine);
}
}
proc.OutputDataReceived += dataHandler;
proc.ErrorDataReceived += dataHandler;
}
proc.Start();
if (displayLog)
{
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
}
await Task.Delay(100);
AppManager.Instance.AddProcess(proc.Handle);
if (proc is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
return proc;
}
#endregion Process
}

View file

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

View file

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

View file

@ -2,17 +2,17 @@ using System.Collections.Concurrent;
//using System.Reactive.Linq; //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 ConcurrentBag<ProfileExItem> _lstProfileEx = [];
private readonly Queue<string> _queIndexIds = new(); private readonly Queue<string> _queIndexIds = new();
public static ProfileExHandler Instance => _instance.Value; public static ProfileExManager Instance => _instance.Value;
private static readonly string _tag = "ProfileExHandler"; private static readonly string _tag = "ProfileExHandler";
public ProfileExHandler() public ProfileExManager()
{ {
//Init(); //Init();
} }

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()); private static readonly Lazy<StatisticsManager> instance = new(() => new());
public static StatisticsHandler Instance => instance.Value; public static StatisticsManager Instance => instance.Value;
private Config _config; private Config _config;
private ServerStatItem? _serverStatItem; private ServerStatItem? _serverStatItem;
private List<ServerStatItem> _lstServerStat; private List<ServerStatItem> _lstServerStat;
private Action<ServerSpeedItem>? _updateFunc; private Func<ServerSpeedItem, Task>? _updateFunc;
private StatisticsXrayService? _statisticsXray; private StatisticsXrayService? _statisticsXray;
private StatisticsSingboxService? _statisticsSingbox; private StatisticsSingboxService? _statisticsSingbox;
private static readonly string _tag = "StatisticsHandler"; private static readonly string _tag = "StatisticsHandler";
public List<ServerStatItem> ServerStat => _lstServerStat; 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; _config = config;
_updateFunc = updateFunc; _updateFunc = updateFunc;
@ -91,15 +91,15 @@ public class StatisticsHandler
{ {
await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem where indexId not in ( select indexId from ProfileItem )"); 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}"); await SQLiteHelper.Instance.ExecuteAsync($"update ServerStatItem set todayUp = 0,todayDown=0,dateNow={ticks} where dateNow<>{ticks}");
_lstServerStat = await SQLiteHelper.Instance.TableAsync<ServerStatItem>().ToListAsync(); _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) private async Task UpdateServerStat(ServerSpeedItem server)
@ -123,12 +123,12 @@ public class StatisticsHandler
server.TodayDown = _serverStatItem.TodayDown; server.TodayDown = _serverStatItem.TodayDown;
server.TotalUp = _serverStatItem.TotalUp; server.TotalUp = _serverStatItem.TotalUp;
server.TotalDown = _serverStatItem.TotalDown; server.TotalDown = _serverStatItem.TotalDown;
_updateFunc?.Invoke(server); await _updateFunc?.Invoke(server);
} }
private async Task GetServerStatItem(string indexId) private async Task GetServerStatItem(string indexId)
{ {
long ticks = DateTime.Now.Date.Ticks; var ticks = DateTime.Now.Date.Ticks;
if (_serverStatItem != null && _serverStatItem.IndexId != indexId) if (_serverStatItem != null && _serverStatItem.IndexId != indexId)
{ {
_serverStatItem = null; _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()); private static readonly Lazy<TaskManager> _instance = new(() => new());
public static TaskHandler Instance => _instance.Value; 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"); Logging.SaveLog("Setup Scheduled Tasks");
@ -21,15 +26,15 @@ public class TaskHandler
await Task.Delay(1000 * 60); await Task.Delay(1000 * 60);
//Execute once 1 minute //Execute once 1 minute
await UpdateTaskRunSubscription(config, updateFunc); await UpdateTaskRunSubscription();
//Execute once 20 minute //Execute once 20 minute
if (numOfExecuted % 20 == 0) if (numOfExecuted % 20 == 0)
{ {
//Logging.SaveLog("Execute save config"); //Logging.SaveLog("Execute save config");
await ConfigHandler.SaveConfig(config); await ConfigHandler.SaveConfig(_config);
await ProfileExHandler.Instance.SaveTo(); await ProfileExManager.Instance.SaveTo();
} }
//Execute once 1 hour //Execute once 1 hour
@ -42,17 +47,17 @@ public class TaskHandler
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1)); FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
//Check once 1 hour //Check once 1 hour
await UpdateTaskRunGeo(config, numOfExecuted / 60, updateFunc); await UpdateTaskRunGeo(numOfExecuted / 60);
} }
numOfExecuted++; numOfExecuted++;
} }
} }
private async Task UpdateTaskRunSubscription(Config config, Action<bool, string> updateFunc) private async Task UpdateTaskRunSubscription()
{ {
var updateTime = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds(); 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 => t.AutoUpdateInterval > 0)
.Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60) .Where(t => updateTime - t.UpdateTime >= t.AutoUpdateInterval * 60)
.ToList(); .ToList();
@ -63,34 +68,33 @@ public class TaskHandler
} }
Logging.SaveLog("Execute update subscription"); Logging.SaveLog("Execute update subscription");
var updateHandle = new UpdateService();
foreach (var item in lstSubs) 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) if (success)
{ {
Logging.SaveLog($"Update subscription end. {msg}"); Logging.SaveLog($"Update subscription end. {msg}");
} }
}); });
item.UpdateTime = updateTime; item.UpdateTime = updateTime;
await ConfigHandler.AddSubItem(config, item); await ConfigHandler.AddSubItem(_config, item);
await Task.Delay(1000); 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"); Logging.SaveLog("Execute update geo files");
var updateHandle = new UpdateService(); 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 System.Net;
using WebDav; using WebDav;
namespace ServiceLib.Handler; namespace ServiceLib.Manager;
public sealed class WebDavHandler public sealed class WebDavManager
{ {
private static readonly Lazy<WebDavHandler> _instance = new(() => new()); private static readonly Lazy<WebDavManager> _instance = new(() => new());
public static WebDavHandler Instance => _instance.Value; public static WebDavManager Instance => _instance.Value;
private readonly Config? _config; private readonly Config? _config;
private WebDavClient? _client; private WebDavClient? _client;
@ -15,9 +15,9 @@ public sealed class WebDavHandler
private readonly string _webFileName = "backup.zip"; private readonly string _webFileName = "backup.zip";
private readonly string _tag = "WebDav--"; private readonly string _tag = "WebDav--";
public WebDavHandler() public WebDavManager()
{ {
_config = AppHandler.Instance.Config; _config = AppManager.Instance.Config;
} }
private async Task<bool> GetClient() private async Task<bool> GetClient()

View file

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

View file

@ -1,7 +1,10 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models; namespace ServiceLib.Models;
[Serializable] [Serializable]
public class ClashProxyModel public class ClashProxyModel : ReactiveObject
{ {
public string? Name { get; set; } public string? Name { get; set; }
@ -9,9 +12,9 @@ public class ClashProxyModel
public string? Now { get; set; } 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; } public bool IsActive { get; set; }
} }

View file

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

View file

@ -89,10 +89,8 @@ public class UIItem
{ {
public bool EnableAutoAdjustMainLvColWidth { get; set; } public bool EnableAutoAdjustMainLvColWidth { get; set; }
public bool EnableUpdateSubOnlyRemarksExist { get; set; } public bool EnableUpdateSubOnlyRemarksExist { get; set; }
public double MainWidth { get; set; } public int MainGirdHeight1 { get; set; }
public double MainHeight { get; set; } public int MainGirdHeight2 { get; set; }
public double MainGirdHeight1 { get; set; }
public double MainGirdHeight2 { get; set; }
public EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical; public EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical;
public string? ColorPrimaryName { get; set; } public string? ColorPrimaryName { get; set; }
public string? CurrentTheme { get; set; } public string? CurrentTheme { get; set; }
@ -103,8 +101,10 @@ public class UIItem
public bool DoubleClick2Activate { get; set; } public bool DoubleClick2Activate { get; set; }
public bool AutoHideStartup { get; set; } public bool AutoHideStartup { get; set; }
public bool Hide2TrayWhenClose { get; set; } public bool Hide2TrayWhenClose { get; set; }
public List<ColumnItem> MainColumnItem { get; set; }
public bool ShowInTaskbar { get; set; } public bool ShowInTaskbar { get; set; }
public bool MacOSShowInDock { get; set; }
public List<ColumnItem> MainColumnItem { get; set; }
public List<WindowSizeItem> WindowSizeItem { get; set; }
} }
[Serializable] [Serializable]
@ -142,12 +142,12 @@ public class CoreTypeItem
public class TunModeItem public class TunModeItem
{ {
public bool EnableTun { get; set; } public bool EnableTun { get; set; }
public bool AutoRoute { get; set; } = true;
public bool StrictRoute { get; set; } = true; public bool StrictRoute { get; set; } = true;
public string Stack { get; set; } public string Stack { get; set; }
public int Mtu { get; set; } public int Mtu { get; set; }
public bool EnableExInbound { get; set; } public bool EnableExInbound { get; set; }
public bool EnableIPv6Address { get; set; } public bool EnableIPv6Address { get; set; }
public string? LinuxSudoPwd { get; set; }
} }
[Serializable] [Serializable]
@ -157,6 +157,7 @@ public class SpeedTestItem
public string SpeedTestUrl { get; set; } public string SpeedTestUrl { get; set; }
public string SpeedPingTestUrl { get; set; } public string SpeedPingTestUrl { get; set; }
public int MixedConcurrencyCount { get; set; } public int MixedConcurrencyCount { get; set; }
public string IPAPIUrl { get; set; }
} }
[Serializable] [Serializable]
@ -164,7 +165,6 @@ public class RoutingBasicItem
{ {
public string DomainStrategy { get; set; } public string DomainStrategy { get; set; }
public string DomainStrategy4Singbox { get; set; } public string DomainStrategy4Singbox { get; set; }
public string DomainMatcher { get; set; }
public string RoutingIndexId { get; set; } public string RoutingIndexId { get; set; }
} }
@ -245,3 +245,30 @@ public class Fragment4RayItem
public string? Length { get; set; } public string? Length { get; set; }
public string? Interval { get; set; } public string? Interval { get; set; }
} }
[Serializable]
public class WindowSizeItem
{
public string TypeName { get; set; }
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? SingboxOutboundsResolveDNS { get; set; }
public string? SingboxFinalResolveDNS { 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? Match { get; set; }
public string? VersionArg { get; set; } public string? VersionArg { get; set; }
public bool AbsolutePath { 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 Id { get; set; }
public string Remarks { 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 ECoreType CoreType { get; set; }
public bool UseSystemHosts { get; set; } public bool UseSystemHosts { get; set; }
public string? NormalDNS { 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

@ -3,10 +3,17 @@ namespace ServiceLib.Models;
internal class IPAPIInfo internal class IPAPIInfo
{ {
public string? ip { get; set; } public string? ip { get; set; }
public string? city { get; set; } public string? clientIp { get; set; }
public string? region { get; set; } public string? ip_addr { get; set; }
public string? region_code { get; set; } public string? query { get; set; }
public string? country { get; set; } public string? country { get; set; }
public string? country_name { get; set; } public string? country_name { get; set; }
public string? country_code { get; set; } public string? country_code { get; set; }
public string? countryCode { get; set; }
public LocationInfo? location { get; set; }
}
public class LocationInfo
{
public string? country_code { get; set; }
} }

View file

@ -1,9 +1,10 @@
using ReactiveUI;
using SQLite; using SQLite;
namespace ServiceLib.Models; namespace ServiceLib.Models;
[Serializable] [Serializable]
public class ProfileItem public class ProfileItem : ReactiveObject
{ {
public ProfileItem() public ProfileItem()
{ {
@ -31,7 +32,7 @@ public class ProfileItem
public string GetSummary() public string GetSummary()
{ {
var summary = $"[{(ConfigType).ToString()}] "; var summary = $"[{(ConfigType).ToString()}] ";
var arrAddr = Address.Split('.'); var arrAddr = Address.Contains(':') ? Address.Split(':') : Address.Split('.');
var addr = arrAddr.Length switch var addr = arrAddr.Length switch
{ {
> 2 => $"{arrAddr.First()}***{arrAddr.Last()}", > 2 => $"{arrAddr.First()}***{arrAddr.Last()}",
@ -92,5 +93,7 @@ public class ProfileItem
public string PublicKey { get; set; } public string PublicKey { get; set; }
public string ShortId { get; set; } public string ShortId { get; set; }
public string SpiderX { get; set; } public string SpiderX { get; set; }
public string Mldsa65Verify { get; set; }
public string Extra { get; set; } public string Extra { get; set; }
public bool? MuxEnabled { get; set; }
} }

View file

@ -1,3 +1,5 @@
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models; namespace ServiceLib.Models;
[Serializable] [Serializable]
@ -5,13 +7,28 @@ public class ProfileItemModel : ProfileItem
{ {
public bool IsActive { get; set; } public bool IsActive { get; set; }
public string SubRemarks { get; set; } public string SubRemarks { get; set; }
[Reactive]
public int Delay { get; set; } public int Delay { get; set; }
public decimal Speed { get; set; } public decimal Speed { get; set; }
public int Sort { get; set; } public int Sort { get; set; }
[Reactive]
public string DelayVal { get; set; } public string DelayVal { get; set; }
[Reactive]
public string SpeedVal { get; set; } public string SpeedVal { get; set; }
[Reactive]
public string TodayUp { get; set; } public string TodayUp { get; set; }
[Reactive]
public string TodayDown { get; set; } public string TodayDown { get; set; }
[Reactive]
public string TotalUp { get; set; } public string TotalUp { get; set; }
[Reactive]
public string TotalDown { get; set; } public string TotalDown { get; set; }
} }

View file

@ -19,4 +19,5 @@ public class RoutingItem
public string DomainStrategy { get; set; } public string DomainStrategy { get; set; }
public string DomainStrategy4Singbox { get; set; } public string DomainStrategy4Singbox { get; set; }
public int Sort { get; set; } public int Sort { get; set; }
public bool IsActive { get; set; }
} }

View file

@ -3,5 +3,4 @@ namespace ServiceLib.Models;
[Serializable] [Serializable]
public class RoutingItemModel : RoutingItem public class RoutingItemModel : RoutingItem
{ {
public bool IsActive { get; set; }
} }

View file

@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models; namespace ServiceLib.Models;
public class SingboxConfig public class SingboxConfig
@ -6,6 +8,7 @@ public class SingboxConfig
public Dns4Sbox? dns { get; set; } public Dns4Sbox? dns { get; set; }
public List<Inbound4Sbox> inbounds { get; set; } public List<Inbound4Sbox> inbounds { get; set; }
public List<Outbound4Sbox> outbounds { get; set; } public List<Outbound4Sbox> outbounds { get; set; }
public List<Endpoints4Sbox>? endpoints { get; set; }
public Route4Sbox route { get; set; } public Route4Sbox route { get; set; }
public Experimental4Sbox? experimental { get; set; } public Experimental4Sbox? experimental { get; set; }
} }
@ -29,14 +32,15 @@ public class Dns4Sbox
public bool? independent_cache { get; set; } public bool? independent_cache { get; set; }
public bool? reverse_mapping { get; set; } public bool? reverse_mapping { get; set; }
public string? client_subnet { get; set; } public string? client_subnet { get; set; }
public Fakeip4Sbox? fakeip { get; set; }
} }
public class Route4Sbox public class Route4Sbox
{ {
public Rule4Sbox? default_domain_resolver { get; set; } // or string
public bool? auto_detect_interface { get; set; } public bool? auto_detect_interface { get; set; }
public List<Rule4Sbox> rules { get; set; } public List<Rule4Sbox> rules { get; set; }
public List<Ruleset4Sbox>? rule_set { get; set; } public List<Ruleset4Sbox>? rule_set { get; set; }
public string? final { get; set; }
} }
[Serializable] [Serializable]
@ -49,6 +53,7 @@ public class Rule4Sbox
public string? mode { get; set; } public string? mode { get; set; }
public bool? ip_is_private { get; set; } public bool? ip_is_private { get; set; }
public string? client_subnet { get; set; } public string? client_subnet { get; set; }
public int? rewrite_ttl { get; set; }
public bool? invert { get; set; } public bool? invert { get; set; }
public string? clash_mode { get; set; } public string? clash_mode { get; set; }
public List<string>? inbound { 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>? process_name { get; set; }
public List<string>? rule_set { get; set; } public List<string>? rule_set { get; set; }
public List<Rule4Sbox>? rules { 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] [Serializable]
@ -76,7 +102,6 @@ public class Inbound4Sbox
public string tag { get; set; } public string tag { get; set; }
public string listen { get; set; } public string listen { get; set; }
public int? listen_port { get; set; } public int? listen_port { get; set; }
public string? domain_strategy { get; set; }
public string interface_name { get; set; } public string interface_name { get; set; }
public List<string>? address { get; set; } public List<string>? address { get; set; }
public int? mtu { get; set; } public int? mtu { get; set; }
@ -84,8 +109,6 @@ public class Inbound4Sbox
public bool? strict_route { get; set; } public bool? strict_route { get; set; }
public bool? endpoint_independent_nat { get; set; } public bool? endpoint_independent_nat { get; set; }
public string? stack { 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; } public List<User4Sbox> users { get; set; }
} }
@ -95,10 +118,8 @@ public class User4Sbox
public string password { get; set; } 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 string? server { get; set; }
public int? server_port { get; set; } public int? server_port { get; set; }
public List<string>? server_ports { 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_conn { get; set; }
public int? recv_window { get; set; } public int? recv_window { get; set; }
public bool? disable_mtu_discovery { get; set; } public bool? disable_mtu_discovery { get; set; }
public string? detour { get; set; }
public string? method { get; set; } public string? method { get; set; }
public string? username { get; set; } public string? username { get; set; }
public string? password { get; set; } public string? password { get; set; }
@ -121,21 +141,36 @@ public class Outbound4Sbox
public string? version { get; set; } public string? version { get; set; }
public string? network { get; set; } public string? network { get; set; }
public string? packet_encoding { 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 { get; set; }
public string? plugin_opts { 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 List<string>? outbounds { get; set; }
public bool? interrupt_exist_connections { get; set; } public bool? interrupt_exist_connections { 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 public class Tls4Sbox
{ {
public bool enabled { get; set; } public bool enabled { get; set; }
@ -144,6 +179,9 @@ public class Tls4Sbox
public List<string>? alpn { get; set; } public List<string>? alpn { get; set; }
public Utls4Sbox? utls { get; set; } public Utls4Sbox? utls { get; set; }
public Reality4Sbox? reality { 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 public class Multiplex4Sbox
@ -191,15 +229,28 @@ public class HyObfs4Sbox
public string? password { get; set; } 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 { get; set; }
public string? address_resolver { get; set; } public string? address_resolver { get; set; }
public string? address_strategy { get; set; } public string? address_strategy { get; set; }
public string? strategy { get; set; } public string? strategy { get; set; }
public string? detour { get; set; } // Deprecated End
public string? client_subnet { get; set; }
} }
public class Experimental4Sbox public class Experimental4Sbox
@ -229,13 +280,6 @@ public class Stats4Sbox
public List<string>? users { get; set; } 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 class CacheFile4Sbox
{ {
public bool enabled { get; set; } public bool enabled { get; set; }
@ -254,3 +298,33 @@ public class Ruleset4Sbox
public string? download_detour { get; set; } public string? download_detour { get; set; }
public string? update_interval { 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 class V2rayConfig
{ {
public Log4Ray log { get; set; } public Log4Ray log { get; set; }
public object dns { get; set; } public Dns4Ray dns { get; set; }
public List<Inbounds4Ray> inbounds { get; set; } public List<Inbounds4Ray> inbounds { get; set; }
public List<Outbounds4Ray> outbounds { get; set; } public List<Outbounds4Ray> outbounds { get; set; }
public Routing4Ray routing { get; set; } public Routing4Ray routing { get; set; }
@ -203,7 +203,15 @@ public class Response4Ray
public class Dns4Ray 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 public class DnsServer4Ray
@ -211,14 +219,20 @@ public class DnsServer4Ray
public string? address { get; set; } public string? address { get; set; }
public List<string>? domains { get; set; } public List<string>? domains { get; set; }
public bool? skipFallback { 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 class Routing4Ray
{ {
public string domainStrategy { get; set; } public string domainStrategy { get; set; }
public string? domainMatcher { get; set; }
public List<RulesItem4Ray> rules { get; set; } public List<RulesItem4Ray> rules { get; set; }
public List<BalancersItem4Ray>? balancers { get; set; } public List<BalancersItem4Ray>? balancers { get; set; }
@ -340,6 +354,7 @@ public class TlsSettings4Ray
public string? publicKey { get; set; } public string? publicKey { get; set; }
public string? shortId { get; set; } public string? shortId { get; set; }
public string? spiderX { get; set; } public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; }
} }
public class TcpSettings4Ray public class TcpSettings4Ray

View file

@ -132,24 +132,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Download speed 的本地化字符串。
/// </summary>
public static string downloadSpeed {
get {
return ResourceManager.GetString("downloadSpeed", resourceCulture);
}
}
/// <summary>
/// 查找类似 Do you want to download {0}? 的本地化字符串。
/// </summary>
public static string DownloadYesNo {
get {
return ResourceManager.GetString("DownloadYesNo", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Failed to convert configuration file 的本地化字符串。 /// 查找类似 Failed to convert configuration file 的本地化字符串。
/// </summary> /// </summary>
@ -204,6 +186,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Please fill in the correct config template 的本地化字符串。
/// </summary>
public static string FillCorrectConfigTemplateText {
get {
return ResourceManager.GetString("FillCorrectConfigTemplateText", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Please fill in the correct custom DNS 的本地化字符串。 /// 查找类似 Please fill in the correct custom DNS 的本地化字符串。
/// </summary> /// </summary>
@ -672,6 +663,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Add [Anytls] Configuration 的本地化字符串。
/// </summary>
public static string menuAddAnytlsServer {
get {
return ResourceManager.GetString("menuAddAnytlsServer", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。 /// 查找类似 Add a custom configuration Configuration 的本地化字符串。
/// </summary> /// </summary>
@ -942,6 +942,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Full Config Template Setting 的本地化字符串。
/// </summary>
public static string menuFullConfigTemplate {
get {
return ResourceManager.GetString("menuFullConfigTemplate", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Global Hotkey Setting 的本地化字符串。 /// 查找类似 Global Hotkey Setting 的本地化字符串。
/// </summary> /// </summary>
@ -1347,15 +1356,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Advanced Function 的本地化字符串。
/// </summary>
public static string menuRoutingAdvanced {
get {
return ResourceManager.GetString("menuRoutingAdvanced", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Add 的本地化字符串。 /// 查找类似 Add 的本地化字符串。
/// </summary> /// </summary>
@ -1860,6 +1860,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Start parsing and processing subscription content 的本地化字符串。
/// </summary>
public static string MsgStartParsingSubscription {
get {
return ResourceManager.GetString("MsgStartParsingSubscription", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Started updating {0}... 的本地化字符串。 /// 查找类似 Started updating {0}... 的本地化字符串。
/// </summary> /// </summary>
@ -2229,6 +2238,33 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Incorrect password, please try again. 的本地化字符串。
/// </summary>
public static string SudoIncorrectPasswordTip {
get {
return ResourceManager.GetString("SudoIncorrectPasswordTip", resourceCulture);
}
}
/// <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> /// <summary>
/// 查找类似 Address 的本地化字符串。 /// 查找类似 Address 的本地化字符串。
/// </summary> /// </summary>
@ -2292,6 +2328,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Block SVCB and HTTPS Queries 的本地化字符串。
/// </summary>
public static string TbBlockSVCBHTTPSQueries {
get {
return ResourceManager.GetString("TbBlockSVCBHTTPSQueries", resourceCulture);
}
}
/// <summary>
/// 查找类似 Prevent domain-based routing rules from failing 的本地化字符串。
/// </summary>
public static string TbBlockSVCBHTTPSQueriesTips {
get {
return ResourceManager.GetString("TbBlockSVCBHTTPSQueriesTips", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Browse 的本地化字符串。 /// 查找类似 Browse 的本地化字符串。
/// </summary> /// </summary>
@ -2346,6 +2400,24 @@ 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> /// <summary>
/// 查找类似 Display GUI 的本地化字符串。 /// 查找类似 Display GUI 的本地化字符串。
/// </summary> /// </summary>
@ -2364,6 +2436,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> /// <summary>
/// 查找类似 Supports DNS Object; Click to view documentation 的本地化字符串。 /// 查找类似 Supports DNS Object; Click to view documentation 的本地化字符串。
/// </summary> /// </summary>
@ -2382,15 +2463,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Domain Matcher 的本地化字符串。
/// </summary>
public static string TbdomainMatcher {
get {
return ResourceManager.GetString("TbdomainMatcher", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Domain strategy 的本地化字符串。 /// 查找类似 Domain strategy 的本地化字符串。
/// </summary> /// </summary>
@ -2409,6 +2481,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Domestic DNS 的本地化字符串。
/// </summary>
public static string TbDomesticDNS {
get {
return ResourceManager.GetString("TbDomesticDNS", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Edit 的本地化字符串。 /// 查找类似 Edit 的本地化字符串。
/// </summary> /// </summary>
@ -2427,6 +2508,24 @@ 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> /// <summary>
/// 查找类似 Fingerprint 的本地化字符串。 /// 查找类似 Fingerprint 的本地化字符串。
/// </summary> /// </summary>
@ -2445,6 +2544,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> /// <summary>
/// 查找类似 Global Hotkey Settings 的本地化字符串。 /// 查找类似 Global Hotkey Settings 的本地化字符串。
/// </summary> /// </summary>
@ -2535,6 +2652,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Mldsa65Verify 的本地化字符串。
/// </summary>
public static string TbMldsa65Verify {
get {
return ResourceManager.GetString("TbMldsa65Verify", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Transport protocol(network) 的本地化字符串。 /// 查找类似 Transport protocol(network) 的本地化字符串。
/// </summary> /// </summary>
@ -2643,6 +2769,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <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> /// <summary>
/// 查找类似 Alias (remarks) 的本地化字符串。 /// 查找类似 Alias (remarks) 的本地化字符串。
/// </summary> /// </summary>
@ -2652,6 +2796,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Remote DNS 的本地化字符串。
/// </summary>
public static string TbRemoteDNS {
get {
return ResourceManager.GetString("TbRemoteDNS", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Camouflage domain(host) 的本地化字符串。 /// 查找类似 Camouflage domain(host) 的本地化字符串。
/// </summary> /// </summary>
@ -2715,33 +2868,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 3. Block Domain or IP 的本地化字符串。
/// </summary>
public static string TbRoutingTabBlock {
get {
return ResourceManager.GetString("TbRoutingTabBlock", resourceCulture);
}
}
/// <summary>
/// 查找类似 2. Direct Domain or IP 的本地化字符串。
/// </summary>
public static string TbRoutingTabDirect {
get {
return ResourceManager.GetString("TbRoutingTabDirect", resourceCulture);
}
}
/// <summary>
/// 查找类似 1. Proxy Domain or IP 的本地化字符串。
/// </summary>
public static string TbRoutingTabProxy {
get {
return ResourceManager.GetString("TbRoutingTabProxy", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Pre-defined Rule Set List 的本地化字符串。 /// 查找类似 Pre-defined Rule Set List 的本地化字符串。
/// </summary> /// </summary>
@ -2778,6 +2904,96 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Can fill in the configuration remarks, please make sure it exist and are unique 的本地化字符串。
/// </summary>
public static string TbRuleOutboundTagTip {
get {
return ResourceManager.GetString("TbRuleOutboundTagTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbSBDirectResolveStrategy {
get {
return ResourceManager.GetString("TbSBDirectResolveStrategy", resourceCulture);
}
}
/// <summary>
/// 查找类似 The sing-box DoH resolution server can be overwritten 的本地化字符串。
/// </summary>
public static string TbSBDoHOverride {
get {
return ResourceManager.GetString("TbSBDoHOverride", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box DoH Resolver Server 的本地化字符串。
/// </summary>
public static string TbSBDoHResolverServer {
get {
return ResourceManager.GetString("TbSBDoHResolverServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fallback DNS Resolution, Suggest IP 的本地化字符串。
/// </summary>
public static string TbSBFallbackDNSResolve {
get {
return ResourceManager.GetString("TbSBFallbackDNSResolve", 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>
/// 查找类似 Resolve Outbound Domains 的本地化字符串。
/// </summary>
public static string TbSBOutboundDomainResolve {
get {
return ResourceManager.GetString("TbSBOutboundDomainResolve", resourceCulture);
}
}
/// <summary>
/// 查找类似 Outbound DNS Resolution (sing-box) 的本地化字符串。
/// </summary>
public static string TbSBOutboundsResolverDNS {
get {
return ResourceManager.GetString("TbSBOutboundsResolverDNS", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box Remote Resolution Strategy 的本地化字符串。
/// </summary>
public static string TbSBRemoteResolveStrategy {
get {
return ResourceManager.GetString("TbSBRemoteResolveStrategy", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Encryption method (security) 的本地化字符串。 /// 查找类似 Encryption method (security) 的本地化字符串。
/// </summary> /// </summary>
@ -2814,6 +3030,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Select Profile 的本地化字符串。
/// </summary>
public static string TbSelectProfile {
get {
return ResourceManager.GetString("TbSelectProfile", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Set system proxy 的本地化字符串。 /// 查找类似 Set system proxy 的本地化字符串。
/// </summary> /// </summary>
@ -3103,7 +3328,7 @@ namespace ServiceLib.Resx {
} }
/// <summary> /// <summary>
/// 查找类似 Use Xray and enable non-Tun mode, which conflicts with the group previous proxy 的本地化字符串。 /// 查找类似 which conflicts with the group previous proxy 的本地化字符串。
/// </summary> /// </summary>
public static string TbSettingsEnableFragmentTips { public static string TbSettingsEnableFragmentTips {
get { get {
@ -3201,6 +3426,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Current connection info test URL 的本地化字符串。
/// </summary>
public static string TbSettingsIPAPIUrl {
get {
return ResourceManager.GetString("TbSettingsIPAPIUrl", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Keep older entries when de-duplicating 的本地化字符串。 /// 查找类似 Keep older entries when de-duplicating 的本地化字符串。
/// </summary> /// </summary>
@ -3229,25 +3463,7 @@ namespace ServiceLib.Resx {
} }
/// <summary> /// <summary>
/// 查找类似 Please set the sudo password in Tun mode settings first 的本地化字符串。 /// 查找类似 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. 的本地化字符串。
/// </summary>
public static string TbSettingsLinuxSudoPasswordIsEmpty {
get {
return ResourceManager.GetString("TbSettingsLinuxSudoPasswordIsEmpty", resourceCulture);
}
}
/// <summary>
/// 查找类似 Please do not run this app with sudo 的本地化字符串。
/// </summary>
public static string TbSettingsLinuxSudoPasswordNotSudoRunApp {
get {
return ResourceManager.GetString("TbSettingsLinuxSudoPasswordNotSudoRunApp", resourceCulture);
}
}
/// <summary>
/// 查找类似 The password is encrypted and stored only in local files 的本地化字符串。
/// </summary> /// </summary>
public static string TbSettingsLinuxSudoPasswordTip { public static string TbSettingsLinuxSudoPasswordTip {
get { get {
@ -3534,6 +3750,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Auto Route 的本地化字符串。
/// </summary>
public static string TbSettingsTunAutoRoute {
get {
return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Tun Mode settings 的本地化字符串。 /// 查找类似 Tun Mode settings 的本地化字符串。
/// </summary> /// </summary>
@ -3543,6 +3768,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> /// <summary>
/// 查找类似 Enable UDP 的本地化字符串。 /// 查找类似 Enable UDP 的本地化字符串。
/// </summary> /// </summary>
@ -3570,6 +3822,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Set Upstream Proxy Tag 的本地化字符串。
/// </summary>
public static string TbSetUpstreamProxyDetour {
get {
return ResourceManager.GetString("TbSetUpstreamProxyDetour", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Short Id 的本地化字符串。 /// 查找类似 Short Id 的本地化字符串。
/// </summary> /// </summary>
@ -3732,6 +3993,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 TbXrayFreedomResolveStrategy {
get {
return ResourceManager.GetString("TbXrayFreedomResolveStrategy", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 The delay: {0} ms, {1} 的本地化字符串。 /// 查找类似 The delay: {0} ms, {1} 的本地化字符串。
/// </summary> /// </summary>
@ -3741,6 +4029,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> /// <summary>
/// 查找类似 Active 的本地化字符串。 /// 查找类似 Active 的本地化字符串。
/// </summary> /// </summary>
@ -3939,15 +4245,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Ungrouped 的本地化字符串。
/// </summary>
public static string UngroupedServers {
get {
return ResourceManager.GetString("UngroupedServers", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Upgrade App does not exist 的本地化字符串。 /// 查找类似 Upgrade App does not exist 的本地化字符串。
/// </summary> /// </summary>

View file

@ -132,12 +132,6 @@
<data name="Downloading" xml:space="preserve"> <data name="Downloading" xml:space="preserve">
<value>درحال دانلود...</value> <value>درحال دانلود...</value>
</data> </data>
<data name="downloadSpeed" xml:space="preserve">
<value>دانلود</value>
</data>
<data name="DownloadYesNo" xml:space="preserve">
<value>آیا می خواهید {0} را دانلود کنید؟</value>
</data>
<data name="FailedConversionConfiguration" xml:space="preserve"> <data name="FailedConversionConfiguration" xml:space="preserve">
<value>تبدیل فایل پیکربندی انجام نشد</value> <value>تبدیل فایل پیکربندی انجام نشد</value>
</data> </data>
@ -387,9 +381,6 @@
<data name="RegisterGlobalHotkeySuccessfully" xml:space="preserve"> <data name="RegisterGlobalHotkeySuccessfully" xml:space="preserve">
<value>کلید میانبر جهانی {0} با موفقیت ثبت شد</value> <value>کلید میانبر جهانی {0} با موفقیت ثبت شد</value>
</data> </data>
<data name="UngroupedServers" xml:space="preserve">
<value>گروه بندی نشده</value>
</data>
<data name="AllGroupServers" xml:space="preserve"> <data name="AllGroupServers" xml:space="preserve">
<value>همه سرورها</value> <value>همه سرورها</value>
</data> </data>
@ -822,9 +813,6 @@
<data name="menuWebsiteItem" xml:space="preserve"> <data name="menuWebsiteItem" xml:space="preserve">
<value>{0} وب سایت</value> <value>{0} وب سایت</value>
</data> </data>
<data name="menuRoutingAdvanced" xml:space="preserve">
<value>عملکرد پیشرفته</value>
</data>
<data name="menuRoutingAdvancedAdd" xml:space="preserve"> <data name="menuRoutingAdvancedAdd" xml:space="preserve">
<value>اضافه کردن</value> <value>اضافه کردن</value>
</data> </data>
@ -837,21 +825,9 @@
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve"> <data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>تنظیم کردن به عنوان قانون فعال</value> <value>تنظیم کردن به عنوان قانون فعال</value>
</data> </data>
<data name="TbdomainMatcher" xml:space="preserve">
<value>تطبیق دامنه</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve"> <data name="TbdomainStrategy" xml:space="preserve">
<value>استراتژی دامنه</value> <value>استراتژی دامنه</value>
</data> </data>
<data name="TbRoutingTabBlock" xml:space="preserve">
<value>3. مسدود کردن دامنه یا آیپی</value>
</data>
<data name="TbRoutingTabDirect" xml:space="preserve">
<value>2. دایرکت کردن دامنه یا IP</value>
</data>
<data name="TbRoutingTabProxy" xml:space="preserve">
<value>1. پروکسی کردن دامنه یا IP</value>
</data>
<data name="TbRoutingTabRuleList" xml:space="preserve"> <data name="TbRoutingTabRuleList" xml:space="preserve">
<value>لیست مجموعه قوانین از پیش تعریف شده</value> <value>لیست مجموعه قوانین از پیش تعریف شده</value>
</data> </data>
@ -1083,6 +1059,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value> <value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value>
</data> </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"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>فعال سازی additional Inbound</value> <value>فعال سازی additional Inbound</value>
</data> </data>
@ -1126,7 +1114,7 @@
<value>افزودن سرور [HTTP]</value> <value>افزودن سرور [HTTP]</value>
</data> </data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve"> <data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>از Xray استفاده کنید و حالت non-Tun را فعال کنید، که با پراکسی قبلی گروه در تضاد است</value> <value>which conflicts with the group previous proxy</value>
</data> </data>
<data name="TbSettingsEnableFragment" xml:space="preserve"> <data name="TbSettingsEnableFragment" xml:space="preserve">
<value>فعال کردن فرگمنت</value> <value>فعال کردن فرگمنت</value>
@ -1339,13 +1327,7 @@
<value>رمز عبور sudo سیستم</value> <value>رمز عبور sudo سیستم</value>
</data> </data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve"> <data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>رمز عبور رمزگذاری شده و فقط در فایل های محلی ذخیره می شود.</value> <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>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>لطفاً ابتدا رمز عبور sudo را در تنظیمات حالت Tun تنظیم کنید</value>
</data>
<data name="TbSettingsLinuxSudoPasswordNotSudoRunApp" xml:space="preserve">
<value>لطفا این برنامه را با sudo اجرا نکنید</value>
</data> </data>
<data name="TransportHeaderTypeTip5" xml:space="preserve"> <data name="TransportHeaderTypeTip5" xml:space="preserve">
<value>*حالت xhttp</value> <value>*حالت xhttp</value>
@ -1416,4 +1398,121 @@
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>صادر کردن سرور</value> <value>صادر کردن سرور</value>
</data> </data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<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>
</data>
<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="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>Outbound DNS Resolution (sing-box)</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>Resolve Outbound Domains</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH Resolver Server</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>Fallback DNS Resolution, Suggest IP</value>
</data>
<data name="TbXrayFreedomResolveStrategy" 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="TbSBDoHOverride" xml:space="preserve">
<value>The sing-box DoH resolution server can be overwritten</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>Prevent domain-based routing rules from failing</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>
</root> </root>

File diff suppressed because it is too large Load diff

View file

@ -132,12 +132,6 @@
<data name="Downloading" xml:space="preserve"> <data name="Downloading" xml:space="preserve">
<value>Downloading...</value> <value>Downloading...</value>
</data> </data>
<data name="downloadSpeed" xml:space="preserve">
<value>Download speed</value>
</data>
<data name="DownloadYesNo" xml:space="preserve">
<value>Do you want to download {0}?</value>
</data>
<data name="FailedConversionConfiguration" xml:space="preserve"> <data name="FailedConversionConfiguration" xml:space="preserve">
<value>Failed to convert configuration file</value> <value>Failed to convert configuration file</value>
</data> </data>
@ -387,9 +381,6 @@
<data name="RegisterGlobalHotkeySuccessfully" xml:space="preserve"> <data name="RegisterGlobalHotkeySuccessfully" xml:space="preserve">
<value>Global hotkey {0} registered successfully</value> <value>Global hotkey {0} registered successfully</value>
</data> </data>
<data name="UngroupedServers" xml:space="preserve">
<value>Ungrouped</value>
</data>
<data name="AllGroupServers" xml:space="preserve"> <data name="AllGroupServers" xml:space="preserve">
<value>All</value> <value>All</value>
</data> </data>
@ -822,9 +813,6 @@
<data name="menuWebsiteItem" xml:space="preserve"> <data name="menuWebsiteItem" xml:space="preserve">
<value>{0} Website</value> <value>{0} Website</value>
</data> </data>
<data name="menuRoutingAdvanced" xml:space="preserve">
<value>Advanced Function</value>
</data>
<data name="menuRoutingAdvancedAdd" xml:space="preserve"> <data name="menuRoutingAdvancedAdd" xml:space="preserve">
<value>Add</value> <value>Add</value>
</data> </data>
@ -837,21 +825,9 @@
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve"> <data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>Set as active rule (Enter)</value> <value>Set as active rule (Enter)</value>
</data> </data>
<data name="TbdomainMatcher" xml:space="preserve">
<value>Domain Matcher</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve"> <data name="TbdomainStrategy" xml:space="preserve">
<value>Domain strategy</value> <value>Domain strategy</value>
</data> </data>
<data name="TbRoutingTabBlock" xml:space="preserve">
<value>3. Block Domain or IP</value>
</data>
<data name="TbRoutingTabDirect" xml:space="preserve">
<value>2. Direct Domain or IP</value>
</data>
<data name="TbRoutingTabProxy" xml:space="preserve">
<value>1. Proxy Domain or IP</value>
</data>
<data name="TbRoutingTabRuleList" xml:space="preserve"> <data name="TbRoutingTabRuleList" xml:space="preserve">
<value>Pre-defined Rule Set List</value> <value>Pre-defined Rule Set List</value>
</data> </data>
@ -1083,6 +1059,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>Please make sure the Configuration remarks exist and are unique</value> <value>Please make sure the Configuration remarks exist and are unique</value>
</data> </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"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>Enable additional Inbound</value> <value>Enable additional Inbound</value>
</data> </data>
@ -1126,7 +1114,7 @@
<value>Add [HTTP] Configuration</value> <value>Add [HTTP] Configuration</value>
</data> </data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve"> <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>
<data name="TbSettingsEnableFragment" xml:space="preserve"> <data name="TbSettingsEnableFragment" xml:space="preserve">
<value>Enable fragment</value> <value>Enable fragment</value>
@ -1339,13 +1327,7 @@
<value>System sudo password</value> <value>System sudo password</value>
</data> </data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve"> <data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>The password is encrypted and stored only in local files</value> <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>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>Please set the sudo password in Tun mode settings first</value>
</data>
<data name="TbSettingsLinuxSudoPasswordNotSudoRunApp" xml:space="preserve">
<value>Please do not run this app with sudo</value>
</data> </data>
<data name="TransportHeaderTypeTip5" xml:space="preserve"> <data name="TransportHeaderTypeTip5" xml:space="preserve">
<value>*xhttp mode</value> <value>*xhttp mode</value>
@ -1416,4 +1398,121 @@
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>Export Configuration</value> <value>Export Configuration</value>
</data> </data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>Current connection info test 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>
</data>
<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="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>Outbound DNS Resolution (sing-box)</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>Resolve Outbound Domains</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH Resolver Server</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>Fallback DNS Resolution, Suggest IP</value>
</data>
<data name="TbXrayFreedomResolveStrategy" 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="TbSBDoHOverride" xml:space="preserve">
<value>The sing-box DoH resolution server can be overwritten</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>Prevent domain-based routing rules from failing</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>
</root> </root>

File diff suppressed because it is too large Load diff

View file

@ -132,12 +132,6 @@
<data name="Downloading" xml:space="preserve"> <data name="Downloading" xml:space="preserve">
<value>下载开始...</value> <value>下载开始...</value>
</data> </data>
<data name="downloadSpeed" xml:space="preserve">
<value>下载</value>
</data>
<data name="DownloadYesNo" xml:space="preserve">
<value>是否下载?{0}</value>
</data>
<data name="FailedConversionConfiguration" xml:space="preserve"> <data name="FailedConversionConfiguration" xml:space="preserve">
<value>转换配置文件失败</value> <value>转换配置文件失败</value>
</data> </data>
@ -387,9 +381,6 @@
<data name="RegisterGlobalHotkeySuccessfully" xml:space="preserve"> <data name="RegisterGlobalHotkeySuccessfully" xml:space="preserve">
<value>注册全局热键 {0} 成功</value> <value>注册全局热键 {0} 成功</value>
</data> </data>
<data name="UngroupedServers" xml:space="preserve">
<value>未分组配置文件</value>
</data>
<data name="AllGroupServers" xml:space="preserve"> <data name="AllGroupServers" xml:space="preserve">
<value>所有</value> <value>所有</value>
</data> </data>
@ -712,7 +703,7 @@
<value>例外:对于下列字符开头的地址,不使用代理配置文件。使用分号 (;) 分隔。</value> <value>例外:对于下列字符开头的地址,不使用代理配置文件。使用分号 (;) 分隔。</value>
</data> </data>
<data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve"> <data name="TbSettingsDisplayRealTimeSpeed" xml:space="preserve">
<value>显示实时速度(需重启)</value> <value>显示实时速度 (需重启)</value>
</data> </data>
<data name="TbSettingsKeepOlderDedupl" xml:space="preserve"> <data name="TbSettingsKeepOlderDedupl" xml:space="preserve">
<value>去重时保留序号较小的项</value> <value>去重时保留序号较小的项</value>
@ -748,7 +739,7 @@
<value>开机启动 (可能会不成功)</value> <value>开机启动 (可能会不成功)</value>
</data> </data>
<data name="TbSettingsStatistics" xml:space="preserve"> <data name="TbSettingsStatistics" xml:space="preserve">
<value>启用流量统计(需重启)</value> <value>启用流量统计 (需重启)</value>
</data> </data>
<data name="TbSettingsSubConvert" xml:space="preserve"> <data name="TbSettingsSubConvert" xml:space="preserve">
<value>订阅转换网址 (可选)</value> <value>订阅转换网址 (可选)</value>
@ -822,9 +813,6 @@
<data name="menuWebsiteItem" xml:space="preserve"> <data name="menuWebsiteItem" xml:space="preserve">
<value>{0} 官网</value> <value>{0} 官网</value>
</data> </data>
<data name="menuRoutingAdvanced" xml:space="preserve">
<value>高级功能</value>
</data>
<data name="menuRoutingAdvancedAdd" xml:space="preserve"> <data name="menuRoutingAdvancedAdd" xml:space="preserve">
<value>添加规则集</value> <value>添加规则集</value>
</data> </data>
@ -837,21 +825,9 @@
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve"> <data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>设为活动规则 (Enter)</value> <value>设为活动规则 (Enter)</value>
</data> </data>
<data name="TbdomainMatcher" xml:space="preserve">
<value>域名匹配算法</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve"> <data name="TbdomainStrategy" xml:space="preserve">
<value>域名解析策略</value> <value>域名解析策略</value>
</data> </data>
<data name="TbRoutingTabBlock" xml:space="preserve">
<value>3.阻止的 Domain 或 IP</value>
</data>
<data name="TbRoutingTabDirect" xml:space="preserve">
<value>2.直连的 Domain 或 IP</value>
</data>
<data name="TbRoutingTabProxy" xml:space="preserve">
<value>1.代理的 Domain 或 IP</value>
</data>
<data name="TbRoutingTabRuleList" xml:space="preserve"> <data name="TbRoutingTabRuleList" xml:space="preserve">
<value>预定义规则集列表</value> <value>预定义规则集列表</value>
</data> </data>
@ -907,7 +883,7 @@
<value>仅限路由 (routeOnly)</value> <value>仅限路由 (routeOnly)</value>
</data> </data>
<data name="TbSettingsNotProxyLocalAddress" xml:space="preserve"> <data name="TbSettingsNotProxyLocalAddress" xml:space="preserve">
<value>请勿将代理服务器用于本地Intranet地址</value> <value>请勿将代理服务器用于本地 (Intranet) 地址</value>
</data> </data>
<data name="menuMixedTestServer" xml:space="preserve"> <data name="menuMixedTestServer" xml:space="preserve">
<value>一键多线程测试延迟和速度 (Ctrl+E)</value> <value>一键多线程测试延迟和速度 (Ctrl+E)</value>
@ -940,7 +916,7 @@
<value>移至订阅分组</value> <value>移至订阅分组</value>
</data> </data>
<data name="TbSettingsEnableDragDropSort" xml:space="preserve"> <data name="TbSettingsEnableDragDropSort" xml:space="preserve">
<value>启用配置文件拖放排序(需重启)</value> <value>启用配置文件拖放排序 (需重启)</value>
</data> </data>
<data name="TbAutoRefresh" xml:space="preserve"> <data name="TbAutoRefresh" xml:space="preserve">
<value>自动刷新</value> <value>自动刷新</value>
@ -967,10 +943,10 @@
<value>仅对 tcp/http、ws 协议生效</value> <value>仅对 tcp/http、ws 协议生效</value>
</data> </data>
<data name="TbSettingsCurrentFontFamily" xml:space="preserve"> <data name="TbSettingsCurrentFontFamily" xml:space="preserve">
<value>当前字体(需重启)</value> <value>当前字体 (需重启)</value>
</data> </data>
<data name="TbSettingsCurrentFontFamilyTip" xml:space="preserve"> <data name="TbSettingsCurrentFontFamilyTip" xml:space="preserve">
<value>拷贝字体 TTF/TTC 文件到目录 guiFonts重启设置</value> <value>拷贝字体 TTF/TTC 文件到目录 guiFonts重启生效</value>
</data> </data>
<data name="TbSettingsSocksPortTip" xml:space="preserve"> <data name="TbSettingsSocksPortTip" xml:space="preserve">
<value>Pac 端口 = +3Xray API 端口 = +4mihomo API 端口 = +5</value> <value>Pac 端口 = +3Xray API 端口 = +4mihomo API 端口 = +5</value>
@ -1000,10 +976,10 @@
<value>SpiderX</value> <value>SpiderX</value>
</data> </data>
<data name="TbSettingsEnableHWA" xml:space="preserve"> <data name="TbSettingsEnableHWA" xml:space="preserve">
<value>启用硬件加速(需重启)</value> <value>启用硬件加速 (需重启)</value>
</data> </data>
<data name="SpeedtestingWait" xml:space="preserve"> <data name="SpeedtestingWait" xml:space="preserve">
<value>等待测试中(按 ESC 终止)...</value> <value>等待测试中 (按 ESC 终止)...</value>
</data> </data>
<data name="TipDisplayLog" xml:space="preserve"> <data name="TipDisplayLog" xml:space="preserve">
<value>当有异常断流时请关闭</value> <value>当有异常断流时请关闭</value>
@ -1080,6 +1056,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>请确保配置文件别名存在并唯一</value> <value>请确保配置文件别名存在并唯一</value>
</data> </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"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>启用额外监听端口</value> <value>启用额外监听端口</value>
</data> </data>
@ -1096,7 +1084,7 @@
<value>Reserved (2,3,4)</value> <value>Reserved (2,3,4)</value>
</data> </data>
<data name="TbLocalAddress" xml:space="preserve"> <data name="TbLocalAddress" xml:space="preserve">
<value>Address (Ipv4,Ipv6)</value> <value>Address (IPv4,IPv6)</value>
</data> </data>
<data name="TbPath7" xml:space="preserve"> <data name="TbPath7" xml:space="preserve">
<value>混淆密码 (obfs password)</value> <value>混淆密码 (obfs password)</value>
@ -1123,13 +1111,13 @@
<value>添加 [HTTP] 配置文件</value> <value>添加 [HTTP] 配置文件</value>
</data> </data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve"> <data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>使用 Xray 且非 Tun 模式启用,和分组前置代理冲突</value> <value>和分组前置代理冲突</value>
</data> </data>
<data name="TbSettingsEnableFragment" xml:space="preserve"> <data name="TbSettingsEnableFragment" xml:space="preserve">
<value>启用分片Fragment</value> <value>启用分片 (Fragment)</value>
</data> </data>
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve"> <data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
<value>启用 sing-box(规则集文件)的缓存文件</value> <value>启用 sing-box (规则集文件) 的缓存文件</value>
</data> </data>
<data name="LvCustomRulesetPath4Singbox" xml:space="preserve"> <data name="LvCustomRulesetPath4Singbox" xml:space="preserve">
<value>自定义 sing-box rule-set</value> <value>自定义 sing-box rule-set</value>
@ -1222,7 +1210,7 @@
<value>Outbound 默认解析策略</value> <value>Outbound 默认解析策略</value>
</data> </data>
<data name="TbSettingsMainGirdOrientation" xml:space="preserve"> <data name="TbSettingsMainGirdOrientation" xml:space="preserve">
<value>主界面布局方向(需重启)</value> <value>主界面布局方向 (需重启)</value>
</data> </data>
<data name="TbSettingsDomainDNSAddress" xml:space="preserve"> <data name="TbSettingsDomainDNSAddress" xml:space="preserve">
<value>Outbound 域名解析地址</value> <value>Outbound 域名解析地址</value>
@ -1324,7 +1312,7 @@
<value>请不要使用不安全的 HTTP 协议订阅地址</value> <value>请不要使用不安全的 HTTP 协议订阅地址</value>
</data> </data>
<data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve"> <data name="TbSettingsCurrentFontFamilyLinuxTip" xml:space="preserve">
<value>安装字体到系统中,选择或填入字体名称,重启设置</value> <value>安装字体到系统中,选择或填入字体名称,重启生效</value>
</data> </data>
<data name="menuExitTips" xml:space="preserve"> <data name="menuExitTips" xml:space="preserve">
<value>是否确定退出?</value> <value>是否确定退出?</value>
@ -1336,16 +1324,10 @@
<value>系统的 sudo 密码</value> <value>系统的 sudo 密码</value>
</data> </data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve"> <data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>密码已加密且只存储在本地文件中,无密码则每次都要输入</value> <value>密码将调用命令行校验,如果因为校验错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。</value>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>请先在 Tun 模式设置中设置 sudo 密码</value>
</data>
<data name="TbSettingsLinuxSudoPasswordNotSudoRunApp" xml:space="preserve">
<value>请不要用 sudo 运行本程序</value>
</data> </data>
<data name="TransportHeaderTypeTip5" xml:space="preserve"> <data name="TransportHeaderTypeTip5" xml:space="preserve">
<value>*xhttp 模式</value> <value>*XHTTP 模式</value>
</data> </data>
<data name="TransportExtraTip" xml:space="preserve"> <data name="TransportExtraTip" xml:space="preserve">
<value>XHTTP Extra 原始 JSON格式 { XHTTPObject }</value> <value>XHTTP Extra 原始 JSON格式 { XHTTPObject }</value>
@ -1366,7 +1348,7 @@
<value>开启第二个本地监听端口</value> <value>开启第二个本地监听端口</value>
</data> </data>
<data name="TbRoutingInboundTagTips" xml:space="preserve"> <data name="TbRoutingInboundTagTips" xml:space="preserve">
<value>socks本地端口socks2第二个本地端口socks3局域网端口</value> <value>Socks本地端口Socks2第二个本地端口Socks3局域网端口</value>
</data> </data>
<data name="TbSettingsTheme" xml:space="preserve"> <data name="TbSettingsTheme" xml:space="preserve">
<value>主题</value> <value>主题</value>
@ -1399,13 +1381,13 @@
<value>多配置文件随机 Xray</value> <value>多配置文件随机 Xray</value>
</data> </data>
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve"> <data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多配置文件负载均衡  Xray</value> <value>多配置文件负载均衡 Xray</value>
</data> </data>
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve"> <data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>多配置文件最低延迟  Xray</value> <value>多配置文件最低延迟 Xray</value>
</data> </data>
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve"> <data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多配置文件最稳定  Xray</value> <value>多配置文件最稳定 Xray</value>
</data> </data>
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve"> <data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多配置文件最低延迟 sing-box</value> <value>多配置文件最低延迟 sing-box</value>
@ -1413,4 +1395,121 @@
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>导出配置文件</value> <value>导出配置文件</value>
</data> </data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>当前连接信息测试地址</value>
</data>
<data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>可以填写配置文件别名,请确保存在并唯一</value>
</data>
<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="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>出站 DNS 解析sing-box</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>解析出站域名</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH 解析服务器</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>兜底解析其他 DNS 域名,建议设为 ip</value>
</data>
<data name="TbXrayFreedomResolveStrategy" 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="TbSBDoHOverride" xml:space="preserve">
<value>开启后可覆盖 sing-box DoH 解析服务器</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>避免域名分流规则失效</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>
</root> </root>

View file

@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="BatchExportURLSuccessfully" xml:space="preserve"> <data name="BatchExportURLSuccessfully" xml:space="preserve">
<value>匯出分享链接至剪貼簿成功</value> <value>匯出分享連結至剪貼簿成功</value>
</data> </data>
<data name="CheckServerSettings" xml:space="preserve"> <data name="CheckServerSettings" xml:space="preserve">
<value>請先檢查設定檔設定</value> <value>請先檢查設定檔設定</value>
@ -132,12 +132,6 @@
<data name="Downloading" xml:space="preserve"> <data name="Downloading" xml:space="preserve">
<value>下載開始...</value> <value>下載開始...</value>
</data> </data>
<data name="downloadSpeed" xml:space="preserve">
<value>下載</value>
</data>
<data name="DownloadYesNo" xml:space="preserve">
<value>是否下載?{0}</value>
</data>
<data name="FailedConversionConfiguration" xml:space="preserve"> <data name="FailedConversionConfiguration" xml:space="preserve">
<value>轉換設定檔失敗</value> <value>轉換設定檔失敗</value>
</data> </data>
@ -295,7 +289,7 @@
<value>成功從剪貼簿匯入 {0} 個設定檔</value> <value>成功從剪貼簿匯入 {0} 個設定檔</value>
</data> </data>
<data name="SuccessfullyImportedServerViaScan" xml:space="preserve"> <data name="SuccessfullyImportedServerViaScan" xml:space="preserve">
<value>掃描匯入分享链接成功</value> <value>掃描匯入分享連結成功</value>
</data> </data>
<data name="TestMeOutput" xml:space="preserve"> <data name="TestMeOutput" xml:space="preserve">
<value>目前延遲: {0} ms{1}</value> <value>目前延遲: {0} ms{1}</value>
@ -387,9 +381,6 @@
<data name="RegisterGlobalHotkeySuccessfully" xml:space="preserve"> <data name="RegisterGlobalHotkeySuccessfully" xml:space="preserve">
<value>註冊全域快速鍵 {0} 成功</value> <value>註冊全域快速鍵 {0} 成功</value>
</data> </data>
<data name="UngroupedServers" xml:space="preserve">
<value>未分組設定檔</value>
</data>
<data name="AllGroupServers" xml:space="preserve"> <data name="AllGroupServers" xml:space="preserve">
<value>所有</value> <value>所有</value>
</data> </data>
@ -481,7 +472,7 @@
<value>語言 (需重啟)</value> <value>語言 (需重啟)</value>
</data> </data>
<data name="menuAddServerViaClipboard" xml:space="preserve"> <data name="menuAddServerViaClipboard" xml:space="preserve">
<value>從剪貼簿導入分享鏈接 (Ctrl+V)</value> <value>從剪貼簿導入分享連結 (Ctrl+V)</value>
</data> </data>
<data name="menuAddServerViaScan" xml:space="preserve"> <data name="menuAddServerViaScan" xml:space="preserve">
<value>掃描螢幕上的二維碼 (Ctrl+S)</value> <value>掃描螢幕上的二維碼 (Ctrl+S)</value>
@ -517,7 +508,7 @@
<value>匯出所選設定檔完整設定</value> <value>匯出所選設定檔完整設定</value>
</data> </data>
<data name="menuExport2ShareUrl" xml:space="preserve"> <data name="menuExport2ShareUrl" xml:space="preserve">
<value>匯出分享链接至剪貼簿 (多選) (Ctrl+C)</value> <value>匯出分享連結至剪貼簿 (多選) (Ctrl+C)</value>
</data> </data>
<data name="menuAddCustomServer" xml:space="preserve"> <data name="menuAddCustomServer" xml:space="preserve">
<value>新增自訂設定設定檔</value> <value>新增自訂設定設定檔</value>
@ -822,9 +813,6 @@
<data name="menuWebsiteItem" xml:space="preserve"> <data name="menuWebsiteItem" xml:space="preserve">
<value>{0} 官網</value> <value>{0} 官網</value>
</data> </data>
<data name="menuRoutingAdvanced" xml:space="preserve">
<value>進階功能</value>
</data>
<data name="menuRoutingAdvancedAdd" xml:space="preserve"> <data name="menuRoutingAdvancedAdd" xml:space="preserve">
<value>新增規則集</value> <value>新增規則集</value>
</data> </data>
@ -837,21 +825,9 @@
<data name="menuRoutingAdvancedSetDefault" xml:space="preserve"> <data name="menuRoutingAdvancedSetDefault" xml:space="preserve">
<value>設為活動規則 (Enter)</value> <value>設為活動規則 (Enter)</value>
</data> </data>
<data name="TbdomainMatcher" xml:space="preserve">
<value>域名匹配演算法</value>
</data>
<data name="TbdomainStrategy" xml:space="preserve"> <data name="TbdomainStrategy" xml:space="preserve">
<value>域名解析策略</value> <value>域名解析策略</value>
</data> </data>
<data name="TbRoutingTabBlock" xml:space="preserve">
<value>3.阻止的 Domain 或 IP</value>
</data>
<data name="TbRoutingTabDirect" xml:space="preserve">
<value>2.直連的 Domain 或 IP</value>
</data>
<data name="TbRoutingTabProxy" xml:space="preserve">
<value>1.代理的 Domain 或 IP</value>
</data>
<data name="TbRoutingTabRuleList" xml:space="preserve"> <data name="TbRoutingTabRuleList" xml:space="preserve">
<value>預定義規則集列表</value> <value>預定義規則集列表</value>
</data> </data>
@ -892,7 +868,7 @@
<value>規則詳細說明檔案</value> <value>規則詳細說明檔案</value>
</data> </data>
<data name="TbDnsObjectDoc" xml:space="preserve"> <data name="TbDnsObjectDoc" xml:space="preserve">
<value>支援填寫 DnsObjectJSON 格式,點擊查看明</value> <value>支援填寫 DnsObjectJSON 格式,點擊查看明</value>
</data> </data>
<data name="SubUrlTips" xml:space="preserve"> <data name="SubUrlTips" xml:space="preserve">
<value>普通分組此處請留空</value> <value>普通分組此處請留空</value>
@ -1003,7 +979,7 @@
<value>啟用硬體加速 (需重啟)</value> <value>啟用硬體加速 (需重啟)</value>
</data> </data>
<data name="SpeedtestingWait" xml:space="preserve"> <data name="SpeedtestingWait" xml:space="preserve">
<value>等待测试中(按 ESC 终止)...</value> <value>等待測試中(按 ESC 終止)...</value>
</data> </data>
<data name="TipDisplayLog" xml:space="preserve"> <data name="TipDisplayLog" xml:space="preserve">
<value>當有異常斷流時請關閉</value> <value>當有異常斷流時請關閉</value>
@ -1080,6 +1056,18 @@
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>請確保設定檔別名存在並且唯一</value> <value>請確保設定檔別名存在並且唯一</value>
</data> </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"> <data name="TbSettingsEnableExInbound" xml:space="preserve">
<value>啟用額外偵聽連接埠</value> <value>啟用額外偵聽連接埠</value>
</data> </data>
@ -1102,10 +1090,10 @@
<value>混淆密碼 (obfs password)</value> <value>混淆密碼 (obfs password)</value>
</data> </data>
<data name="TbRuleMatchingTips" xml:space="preserve"> <data name="TbRuleMatchingTips" xml:space="preserve">
<value>(Domain 或 IP 或 行程名) 与 Port 与 Protocol 与 InboundTag =&gt; OutboundTag</value> <value>(Domain 或 IP 或 行程名) 與 Port 與 Protocol 與 InboundTag =&gt; OutboundTag</value>
</data> </data>
<data name="TbAutoScrollToEnd" xml:space="preserve"> <data name="TbAutoScrollToEnd" xml:space="preserve">
<value>自动滚动到末尾</value> <value>自動滾動到末尾</value>
</data> </data>
<data name="TbSettingsSpeedPingTestUrl" xml:space="preserve"> <data name="TbSettingsSpeedPingTestUrl" xml:space="preserve">
<value>真連線測試位址</value> <value>真連線測試位址</value>
@ -1123,7 +1111,7 @@
<value>新增 [HTTP] 設定檔</value> <value>新增 [HTTP] 設定檔</value>
</data> </data>
<data name="TbSettingsEnableFragmentTips" xml:space="preserve"> <data name="TbSettingsEnableFragmentTips" xml:space="preserve">
<value>使用 Xray 且非 Tun 模式啟用,和分組前置代理衝突</value> <value>和分組前置代理衝突</value>
</data> </data>
<data name="TbSettingsEnableFragment" xml:space="preserve"> <data name="TbSettingsEnableFragment" xml:space="preserve">
<value>啟用分片Fragment</value> <value>啟用分片Fragment</value>
@ -1201,7 +1189,7 @@
<value>全局</value> <value>全局</value>
</data> </data>
<data name="menuModeNothing" xml:space="preserve"> <data name="menuModeNothing" xml:space="preserve">
<value>原配置</value> <value>原配置</value>
</data> </data>
<data name="menuModeRule" xml:space="preserve"> <data name="menuModeRule" xml:space="preserve">
<value>規則</value> <value>規則</value>
@ -1231,7 +1219,7 @@
<value>自動調整列寬</value> <value>自動調整列寬</value>
</data> </data>
<data name="menuExport2ShareUrlBase64" xml:space="preserve"> <data name="menuExport2ShareUrlBase64" xml:space="preserve">
<value>匯出分享链接至剪貼簿 (多選) Base64 编码</value> <value>匯出分享連結至剪貼簿 (多選) Base64 編碼</value>
</data> </data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve"> <data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>匯出所選設定檔完整設定至剪貼簿</value> <value>匯出所選設定檔完整設定至剪貼簿</value>
@ -1267,7 +1255,7 @@
<value>WebDav 伺服器位址</value> <value>WebDav 伺服器位址</value>
</data> </data>
<data name="LvWebDavUserName" xml:space="preserve"> <data name="LvWebDavUserName" xml:space="preserve">
<value>WebDav 戶</value> <value>WebDav 戶</value>
</data> </data>
<data name="LvWebDavPassword" xml:space="preserve"> <data name="LvWebDavPassword" xml:space="preserve">
<value>WebDav 密碼</value> <value>WebDav 密碼</value>
@ -1336,13 +1324,7 @@
<value>系統的 sudo 密碼</value> <value>系統的 sudo 密碼</value>
</data> </data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve"> <data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>密碼已加密且只儲存在本機檔案中,無密碼則每次都要輸入</value> <value>密碼將調用命令行校驗,如果因為校驗錯誤導致無法正常運行時,請重啟本應用。密碼不會存儲,每次重啟後都需要再次輸入。</value>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>請先在 Tun 模式設定中設定 sudo 密碼</value>
</data>
<data name="TbSettingsLinuxSudoPasswordNotSudoRunApp" xml:space="preserve">
<value>請不要用 sudo 來運行此 App</value>
</data> </data>
<data name="TransportHeaderTypeTip5" xml:space="preserve"> <data name="TransportHeaderTypeTip5" xml:space="preserve">
<value>*xhttp 模式</value> <value>*xhttp 模式</value>
@ -1366,7 +1348,7 @@
<value>開啟第二個本機監聽埠</value> <value>開啟第二個本機監聽埠</value>
</data> </data>
<data name="TbRoutingInboundTagTips" xml:space="preserve"> <data name="TbRoutingInboundTagTips" xml:space="preserve">
<value>socks本地端口socks2第二個本地端口socks3區域網路端口</value> <value>socks本地socks2第二個本地埠socks3區域網路埠</value>
</data> </data>
<data name="TbSettingsTheme" xml:space="preserve"> <data name="TbSettingsTheme" xml:space="preserve">
<value>主題</value> <value>主題</value>
@ -1387,10 +1369,10 @@
<value>移除無效測試結果 {0} 個。</value> <value>移除無效測試結果 {0} 個。</value>
</data> </data>
<data name="TbPorts7" xml:space="preserve"> <data name="TbPorts7" xml:space="preserve">
<value>跳躍端口範圍</value> <value>跳躍範圍</value>
</data> </data>
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>會覆蓋端口,多組時用逗號 (,) 隔開</value> <value>會覆蓋,多組時用逗號 (,) 隔開</value>
</data> </data>
<data name="menuSetDefaultMultipleServer" xml:space="preserve"> <data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>多設定檔產生自訂配置 (多選)</value> <value>多設定檔產生自訂配置 (多選)</value>
@ -1413,4 +1395,121 @@
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>匯出設定檔</value> <value>匯出設定檔</value>
</data> </data>
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
<value>目前連接資訊測試地址</value>
</data>
<data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>可以填寫設定檔別名,請確保存在並唯一</value>
</data>
<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="TbSBOutboundsResolverDNS" xml:space="preserve">
<value>Outbound DNS Resolution (sing-box)</value>
</data>
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
<value>Resolve Outbound Domains</value>
</data>
<data name="TbSBDoHResolverServer" xml:space="preserve">
<value>sing-box DoH Resolver Server</value>
</data>
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
<value>Fallback DNS Resolution, Suggest IP</value>
</data>
<data name="TbXrayFreedomResolveStrategy" 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="TbSBDoHOverride" xml:space="preserve">
<value>The sing-box DoH resolution server can be overwritten</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>Prevent domain-based routing rules from failing</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>
</root> </root>

View file

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

View file

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

View file

@ -6,11 +6,10 @@
"servers": [ "servers": [
{ {
"address": "1.1.1.1", "address": "1.1.1.1",
"skipFallback": true,
"domains": [ "domains": [
"geosite:geolocation-!cn" "domain:googleapis.cn",
], "domain:gstatic.com"
"expectIPs": [
"geoip:!cn"
] ]
}, },
{ {
@ -23,6 +22,7 @@
"geoip:cn" "geoip:cn"
] ]
}, },
"1.1.1.1",
"8.8.8.8", "8.8.8.8",
"https://dns.google/dns-query" "https://dns.google/dns-query"
] ]

View file

@ -0,0 +1,61 @@
#!/bin/bash
#
# Process Terminator Script for Linux
# This script forcibly terminates a process and all its child processes
#
# Check if PID argument is provided
if [ $# -ne 1 ]; then
echo "Usage: $0 <PID>"
exit 1
fi
PID=$1
# Validate that input is a valid PID (numeric)
if ! [[ "$PID" =~ ^[0-9]+$ ]]; then
echo "Error: The PID must be a numeric value"
exit 1
fi
# Check if the process exists
if ! ps -p $PID > /dev/null; then
echo "Warning: No process found with PID $PID"
exit 0
fi
# Recursive function to find and kill all child processes
kill_children() {
local parent=$1
local children=$(ps -o pid --no-headers --ppid "$parent")
# Output information about processes being terminated
echo "Processing children of PID: $parent..."
# Process each child
for child in $children; do
# Recursively find and kill child's children first
kill_children "$child"
# Force kill the child process
echo "Terminating child process: $child"
kill -9 "$child" 2>/dev/null || true
done
}
echo "============================================"
echo "Starting termination of process $PID and all its children"
echo "============================================"
# Find and kill all child processes
kill_children "$PID"
# Finally kill the main process
echo "Terminating main process: $PID"
kill -9 "$PID" 2>/dev/null || true
echo "============================================"
echo "Process $PID and all its children have been terminated"
echo "============================================"
exit 0

View file

@ -0,0 +1,56 @@
#!/bin/bash
#
# Process Terminator Script for macOS
# This script forcibly terminates a process and all its descendant processes
#
# Check if PID argument is provided
if [ $# -ne 1 ]; then
echo "Usage: $0 <PID>"
exit 1
fi
PID=$1
# Validate that input is a valid PID (numeric)
if ! [[ "$PID" =~ ^[0-9]+$ ]]; then
echo "Error: The PID must be a numeric value"
exit 1
fi
# Check if the process exists - using kill -0 which is more reliable on macOS
if ! kill -0 $PID 2>/dev/null; then
echo "Warning: No process found with PID $PID"
exit 0
fi
# Recursive function to find and kill all descendant processes
kill_descendants() {
local parent=$1
# Use ps -axo for macOS to ensure all processes are included
local children=$(ps -axo pid=,ppid= | awk -v ppid=$parent '$2==ppid {print $1}')
echo "Processing children of PID: $parent..."
for child in $children; do
kill_descendants "$child"
echo "Terminating child process: $child"
kill -9 "$child" 2>/dev/null || true
done
}
echo "============================================"
echo "Starting termination of process $PID and all its descendants"
echo "============================================"
# Find and kill all descendant processes
kill_descendants "$PID"
# Finally kill the main process
echo "Terminating main process: $PID"
kill -9 "$PID" 2>/dev/null || true
echo "============================================"
echo "Process $PID and all its descendants have been terminated"
echo "============================================"
exit 0

View file

@ -29,9 +29,6 @@ set_gnome_proxy() {
echo "Ignored Hosts: $IGNORE_HOSTS" echo "Ignored Hosts: $IGNORE_HOSTS"
elif [ "$MODE" == "none" ]; then elif [ "$MODE" == "none" ]; then
echo "GNOME: Proxy disabled." echo "GNOME: Proxy disabled."
else
echo "GNOME: Invalid mode. Use 'none' or 'manual'."
exit 1
fi fi
} }
@ -69,9 +66,6 @@ set_kde_proxy() {
# Disable proxy # Disable proxy
$KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key ProxyType 0 $KWRITECONFIG --file kioslaverc --group "Proxy Settings" --key ProxyType 0
echo "KDE: Proxy disabled." echo "KDE: Proxy disabled."
else
echo "KDE: Invalid mode. Use 'none' or 'manual'."
exit 1
fi fi
# Apply changes by restarting KDE's network settings # Apply changes by restarting KDE's network settings
@ -95,6 +89,21 @@ detect_desktop_environment() {
return return
fi fi
if [[ "$XDG_CURRENT_DESKTOP" == *"UKUI"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"ukui"* ]]; then
echo "gnome"
return
fi
if [[ "$XDG_CURRENT_DESKTOP" == *"DDE"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"dde"* ]]; then
echo "gnome"
return
fi
if [[ "$XDG_CURRENT_DESKTOP" == *"MATE"* ]] || [[ "$XDG_SESSION_DESKTOP" == *"mate"* ]]; then
echo "gnome"
return
fi
local KDE_ENVIRONMENTS=("KDE" "plasma") local KDE_ENVIRONMENTS=("KDE" "plasma")
for ENV in "${KDE_ENVIRONMENTS[@]}"; do for ENV in "${KDE_ENVIRONMENTS[@]}"; do
if [ "$XDG_CURRENT_DESKTOP" == "$ENV" ] || [ "$XDG_SESSION_DESKTOP" == "$ENV" ]; then if [ "$XDG_CURRENT_DESKTOP" == "$ENV" ] || [ "$XDG_SESSION_DESKTOP" == "$ENV" ]; then
@ -102,6 +111,15 @@ detect_desktop_environment() {
return return
fi fi
done done
# Fallback to GNOME method if CLI utility is available. This solves the
# proxy configuration issues on minimal installation systems, like setups
# with only window managers, that borrow some parts from big DEs.
if command -v gsettings >/dev/null 2>&1; then
echo "gnome"
return
fi
echo "unsupported" echo "unsupported"
} }
@ -119,6 +137,11 @@ PROXY_IP=$2
PROXY_PORT=$3 PROXY_PORT=$3
IGNORE_HOSTS=$4 IGNORE_HOSTS=$4
if ! [[ "$MODE" =~ ^(manual|none)$ ]]; then
echo "Invalid mode. Use 'none' or 'manual'." >&2
exit 1
fi
# Detect desktop environment # Detect desktop environment
DE=$(detect_desktop_environment) DE=$(detect_desktop_environment)
@ -129,6 +152,6 @@ elif [ "$DE" == "kde" ]; then
set_gnome_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS" set_gnome_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS"
set_kde_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS" set_kde_proxy "$MODE" "$PROXY_IP" "$PROXY_PORT" "$IGNORE_HOSTS"
else else
echo "Unsupported desktop environment: $DE" echo "Unsupported desktop environment: $DE" >&2
exit 1 exit 1
fi fi

View file

@ -0,0 +1,92 @@
{
"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",
"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": [ "servers": [
{ {
"tag": "remote", "tag": "remote",
"address": "tcp://8.8.8.8", "type": "tcp",
"strategy": "prefer_ipv4", "server": "8.8.8.8",
"detour": "proxy" "detour": "proxy"
}, },
{ {
"tag": "local", "tag": "local",
"address": "223.5.5.5", "type": "udp",
"strategy": "prefer_ipv4", "server": "223.5.5.5"
"detour": "direct"
},
{
"tag": "block",
"address": "rcode://success"
} }
], ],
"rules": [ "rules": [
{ {
"rule_set": [ "domain_suffix": [
"geosite-cn", "googleapis.cn",
"geosite-geolocation-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, 139,
5353 5353
], ],
"outbound": "block" "action": "reject"
}, },
{ {
"ip_cidr": [ "ip_cidr": [
"224.0.0.0/3", "224.0.0.0/3",
"ff00::/8" "ff00::/8"
], ],
"outbound": "block" "action": "reject"
} }
] ]

View file

@ -28,6 +28,8 @@
<EmbeddedResource Include="Sample\custom_routing_white" /> <EmbeddedResource Include="Sample\custom_routing_white" />
<EmbeddedResource Include="Sample\dns_singbox_normal" /> <EmbeddedResource Include="Sample\dns_singbox_normal" />
<EmbeddedResource Include="Sample\dns_v2ray_normal" /> <EmbeddedResource Include="Sample\dns_v2ray_normal" />
<EmbeddedResource Include="Sample\kill_as_sudo_linux_sh" />
<EmbeddedResource Include="Sample\kill_as_sudo_osx_sh" />
<EmbeddedResource Include="Sample\pac" /> <EmbeddedResource Include="Sample\pac" />
<EmbeddedResource Include="Sample\proxy_set_linux_sh" /> <EmbeddedResource Include="Sample\proxy_set_linux_sh" />
<EmbeddedResource Include="Sample\proxy_set_osx_sh" /> <EmbeddedResource Include="Sample\proxy_set_osx_sh" />
@ -42,6 +44,7 @@
<EmbeddedResource Include="Sample\tun_singbox_inbound" /> <EmbeddedResource Include="Sample\tun_singbox_inbound" />
<EmbeddedResource Include="Sample\tun_singbox_rules" /> <EmbeddedResource Include="Sample\tun_singbox_rules" />
<EmbeddedResource Include="Sample\linux_autostart_config" /> <EmbeddedResource Include="Sample\linux_autostart_config" />
<EmbeddedResource Include="Sample\singbox_fakeip_filter" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,522 @@
using System.Net;
using System.Net.NetworkInformation;
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.Port <= 0)
{
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);
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 (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
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
if (item is null)
{
continue;
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS
&& !Global.Flows.Contains(item.Flow))
{
continue;
}
if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan
&& item.StreamSecurity == Global.StreamSecurityReality
&& item.PublicKey.IsNullOrEmpty())
{
continue;
}
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.SingboxFinalResolverTag
};
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 is not { Port: > 0 })
{
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.SingboxFinalResolverTag
};
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(List<ProfileItem> 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;
}
await GenLog(singboxConfig);
await GenInbounds(singboxConfig);
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
singboxConfig.outbounds.RemoveAt(0);
var proxyProfiles = new List<ProfileItem>();
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)
{
continue;
}
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
{
continue;
}
//outbound
proxyProfiles.Add(item);
}
if (proxyProfiles.Count <= 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenOutboundsList(proxyProfiles, 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,519 @@
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,
});
}
// Tun2SocksAddress
if (node != null && Utils.IsDomain(node.Address))
{
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
{
server = Global.SingboxOutboundResolverTag,
domain = [node.Address],
});
}
}
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.SingboxFinalResolverTag;
var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS);
remoteDns.tag = Global.SingboxRemoteDNSTag;
remoteDns.detour = Global.ProxyTag;
remoteDns.domain_resolver = Global.SingboxFinalResolverTag;
var resolverDns = ParseDnsAddress(simpleDNSItem.SingboxOutboundsResolveDNS);
resolverDns.tag = Global.SingboxOutboundResolverTag;
resolverDns.domain_resolver = Global.SingboxFinalResolverTag;
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 (resolverDns.server == host.Key)
{
resolverDns.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(resolverDns);
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 finalDns = ParseDnsAddress(simpleDNSItem.SingboxFinalResolveDNS);
finalDns.tag = Global.SingboxFinalResolverTag;
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;
}
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);
}
// Tun2SocksAddress
if (node != null && Utils.IsDomain(node.Address))
{
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
{
server = Global.SingboxFinalResolverTag,
domain = [node.Address],
});
}
}
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.SingboxFinalResolverTag;
var localDnsAddress = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress;
var localDnsServer = ParseDnsAddress(localDnsAddress);
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.SingboxFinalResolverTag;
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 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;
}
}

View file

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

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