Compare commits

...
56 Commits
Author SHA1 Message Date
2dust da81101cfd up 7.22.7 2026-06-12 19:15:08 +08:00
DHR60 3d4f202cd7 Metrics listen directly (#9533) 2026-06-12 09:47:59 +08:00
2dust 35fbb137e8 Add vcn and pcs properties to VmessQRCode DTO 2026-06-12 09:47:03 +08:00
sparklelcm333 1869a95700 fix: Desktop(Avalonia) 移除子对话框最小化按钮,仅禁 CanMinimize (#9526) 2026-06-10 20:45:23 +08:00
JieXuand2dust 34359885ab Update AppBuilderExtension.cs (#9514)
* Update AppBuilderExtension.cs

* Update MainWindow.axaml.cs

* Update MacAppUtils.cs

* Update App.axaml.cs

* Add regions for MacOS activation and app events

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-06-09 17:00:51 +08:00
znah 93832656dd fix: disable .net9 CET (#9507) 2026-06-09 14:06:13 +08:00
2dust b1a400b393 binConfigs only deletes test files periodically 2026-06-08 14:22:11 +08:00
2dust 879c736924 Update Utils.cs
https://github.com/2dust/v2rayN/commit/5fed566a324569da59252d8ee8179e22ff21a035
2026-06-07 15:49:31 +08:00
sparklelcm333and2dust 8147b39723 fix: 在窗口初始化前设置保存的尺寸,消除启动时偏右下偏移 (#9498)
* fix: 在窗口初始化前设置保存的尺寸,消除启动时偏右下偏移

WindowStartupLocation="CenterScreen"(来自 XAML)能正确包含 chrome 居中窗口,
但只能使用生效时的 Width/Height。旧代码在 OnLoaded 中设置保存的尺寸为时已晚
(CenterScreen 已应用),随后又用不含 chrome 的手工计算覆盖 Position,
导致窗口每次启动偏右下。

修复方式:在 Initialized 事件中设置保存的 Width/Height。
Avalonia 中 Initialized 在 Show() 过程中触发,早于 WindowStartupLocation 生效,
因此 CenterScreen 能用正确的保存尺寸居中。

同时移除 OnLoaded 中重复的 Width/Height 设置(Initialized 中已设置)。

* Update WindowBase.cs

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-06-07 14:59:50 +08:00
2dust 5fed566a32 Add ReplaceLineBreaks extension and Fix bug
https://github.com/2dust/v2rayN/issues/9497
2026-06-07 14:49:39 +08:00
2dust f0ee792778 up 7.22.6 2026-06-07 11:23:36 +08:00
JieXu cd4aa5531c Fix (#9495)
* Update App.axaml.cs

* Update App.axaml.cs
2026-06-07 09:04:06 +08:00
DHR60 e6d08c2db1 Code clean (#9482) 2026-06-06 11:20:06 +08:00
JieXu 4dc9c60b85 Update AppBuilderExtension.cs && Fix Bug (#9483)
* Update AppBuilderExtension.cs

* Update OptionSettingWindow.axaml.cs

* Update OptionSettingWindow.axaml.cs

* Update Global.cs

* Rename ResUI.fa-Ir.resx to ResUI.fa.resx

* Update ServiceLib.csproj
2026-06-06 11:09:53 +08:00
tt2563 eb9c974436 Update Traditional Chinese translation (#9484)
* Update Traditional Chinese translation

Update Traditional Chinese translation

* Update ResUI.zh-Hant.resx

Update Traditional Chinese translation
2026-06-06 09:37:40 +08:00
DHR60 27b8f889fe Tail Fragmentation (#9476) 2026-06-05 19:45:49 +08:00
DHR60and2dust 0d5169e4cd Remove global AllowInsecure and MuxEnabled (#9473)
* Remove global AllowInsecure

* Remove global MuxEnabled

* Fix core config

* Centralize AllowInsecure handling

* Warn insecure configuration

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-06-05 15:31:23 +08:00
DHR60 abcdd9497c Support new kcp config (#9477) 2026-06-05 15:08:57 +08:00
DHR60 da65faa49f Set xray default tun dns (#9474) 2026-06-05 09:58:00 +08:00
DHR60and2dust b6cfce069a Xray Route Exclude Address (#9469)
* Add IPNetwork2 package

* Xray Route Exclude Address

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-06-05 09:54:57 +08:00
JieXu 39ec9239d2 Revert "Fix NUGET_CERT_REVOCATION_MODE=offline && Update Directory.Packages.props (#9478)
* Revert "Fix NUGET_CERT_REVOCATION_MODE=offline (#9470)"

This reverts commit a2a5940d91.

* Update Directory.Packages.props

* Update build-linux.yml

* Update Directory.Packages.props
2026-06-05 09:41:21 +08:00
JieXu a2a5940d91 Fix NUGET_CERT_REVOCATION_MODE=offline (#9470)
* Update build.yml

* Update package-rhel.sh

* Update package-rhel-riscv.sh

* Update package-debian.sh

* Update package-debian-riscv.sh

* Update package-debian-loong.sh

* Update build-linux.yml

* Update Directory.Packages.props
2026-06-04 20:00:32 +08:00
2dust ce8caa89a4 Add Route Exclude Address setting and UI for sing-box tun
https://github.com/2dust/v2rayN/issues/9456
2026-06-04 15:55:09 +08:00
DHR60 af3d579890 Fix (#9462)
* Remove outdated CA certificates

* Support verifyPeerCertByName

* Fix
2026-06-03 15:07:53 +08:00
2dust b2bbc865af Add VerifyPeerCertByName support
https://github.com/2dust/v2rayN/issues/9427
2026-06-03 09:56:34 +08:00
Miheichev Aleksandr Sergeevich 2eb325643a chore(deps): update NuGet dependencies (#9455)
- Avalonia.Desktop 11.3.16 -> 11.3.17
- Avalonia.Diagnostics 11.3.16 -> 11.3.17
- Downloader 5.5.0 -> 5.7.0
- Microsoft.NET.Test.Sdk 18.5.1 -> 18.6.0
- Tmds.DBus.Protocol 0.21.3 -> 0.94.1
- YamlDotNet 17.1.0 -> 18.0.0
2026-06-03 09:03:14 +08:00
Miheichev Aleksandr Sergeevich 8609e2b8d1 fix(ui): omit country flag emoji on Windows to avoid DE(DE) display (#9454) 2026-06-03 09:03:11 +08:00
2dust fcf6c1e3ae up 7.22.5 2026-06-02 19:14:15 +08:00
2dust ab907e371b Clarify deprecation message for allowInsecure 2026-06-02 19:13:52 +08:00
2dust ef4dda0996 Warn on deprecated allowInsecure usage
https://github.com/2dust/v2rayN/issues/9435
2026-06-02 16:42:04 +08:00
JieXu f2e0bd4eba Bump skiasharp version && Add loong64 Update && Translate FR (#9433)
* Update Directory.Packages.props

* Update CoreInfoManager.cs

* Update CoreInfo.cs

* Update UpdateService.cs

* Update ResUI.fr.resx

* Update ResUI.fr.resx

* Update ResUI.fr.resx
2026-06-01 13:46:57 +08:00
Miheichev Aleksandr Sergeevich bca07f801d i18n(ru): refine Russian localization against the zh-Hans source (#9431)
* i18n(ru): refine Russian localization against the zh-Hans source

Translate newly added UI strings and align existing Russian entries
with the authoritative Simplified Chinese (zh-Hans) source, since the
project is maintained by a Chinese-speaking author.

New strings (check-for-update flow):
- MsgCheckUpdateHasNewVersion, menuCheckOnly, menuNewUpdate,
  MsgNotSupport, LvTestIpInfo

Correctness fixes (changed meaning):
- TbSettingsHysteriaBandwidth: restore upload/download order (Up/Dw)
- TbSettingsMixedConcurrencyCount: "multi-threaded" (was "multi-stage")
- TransportRequestHostTip4: "QUIC encryption method" (was "security")
- TransportPathTip3: "QUIC encryption key"
- TbDomesticDNS: "DNS for direct connections"

Terminology and clarity:
- Unify the alias field to one consistent term across all rule/profile
  labels, distinct from the memo field
- allowInsecure -> "skip certificate verification" (+ cert-pinning ref)
- Restore details dropped from EN but present in zh-Hans: Port Hopping
  range, pre-SOCKS split routing, optional / "may fail" hints

Consistency and cleanup:
- Orthography, transport names (httpupgrade, XHTTPObject),
  move-to-top/bottom wording, trailing-space cleanup

Scope: ServiceLib/Resx/ResUI.ru.resx only. Technical abbreviations are
kept in English (LAN, UUID, MTU, TLS, ALPN, SNI, ...). Stale Chinese
strings vs. current behavior were judged case by case.

* i18n(ru): shorten update-check button labels to fit fixed-width buttons

The check-update window has two fixed-width (100px) buttons. The Russian
labels were faithful but too long, so both were truncated and looked almost
identical, hiding the difference between the two actions:
- the "Only Check" button only checks whether a newer version is available
- the "Check Update" button checks, downloads, installs and restarts

Shorten both so they fit the button and clearly distinguish check vs update.

Note: menuCheckUpdate is also reused as the default row placeholder in the
update list, which now shows the shorter label as well.
2026-06-01 13:46:42 +08:00
JieXu 54a3388c99 Update App.axaml.cs (#9421)
* Update App.axaml.cs

* Update App.axaml.cs

* Create MacOSAppHelper.cs

* Update App.axaml.cs

Removed unused macOS activation policy methods and constants.

* Update and rename MacOSAppHelper.cs to MacAppUtils.cs

* Update App.axaml.cs
2026-05-31 17:57:59 +08:00
direnquanbuKO 5140954b37 Correct multiple DNS Hosts ips (#9365)
* Correct dns.cloudflare.com ips

* Add ipv6 for engage.cloudflareclient.com

* Add new ips for dns.sb
2026-05-31 10:32:14 +08:00
JieXu c7efe39e1c Fix (#9418)
* Update package-osx.sh

* Update package-osx.sh
2026-05-30 20:50:20 +08:00
2dust a74ca3f9d0 up 7.22.4 2026-05-29 11:08:39 +08:00
2dust ed61f5c420 Rename ViewModel properties to PascalCase 2026-05-29 11:05:43 +08:00
2dust bf98c4007f Use configurable page size and delay in speedtests
https://github.com/2dust/v2rayN/pull/9392
2026-05-29 10:37:00 +08:00
dependabot[bot] 78dcf51c3c Bump actions/setup-dotnet from 5.2.0 to 5.3.0 (#9407)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5.2.0...v5.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-29 09:56:43 +08:00
2dust 1e59344074 Add balancer fallbackTag
https://github.com/2dust/v2rayN/issues/9401
2026-05-29 09:55:59 +08:00
JieXu 1d2442d58a Fix the problem of font select (#9405)
* Update OptionSettingWindow.axaml.cs

* Update OptionSettingWindow.axaml.cs
2026-05-28 15:31:06 +08:00
2dust 3448782925 Remove unused C# style setting from .editorconfig 2026-05-28 14:31:27 +08:00
2dust e6f4a57913 Limit update checks to selected core types
https://github.com/2dust/v2rayN/issues/9381
2026-05-28 14:31:01 +08:00
Roffildand2dust 4ae5c021fd Add in Global.cs speed and ping test URLs (#9374)
* Add in Global.cs speed and ping test URLs

* 100mb

* Update Global.cs

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-05-27 16:54:26 +08:00
xihan123 0a357fd1a7 Change the SelectedValue bound to Text #9388 (#9389) 2026-05-26 20:20:15 +08:00
2dust 5ba5a805ce Make MTU combobox editable
https://github.com/2dust/v2rayN/issues/9370
2026-05-24 14:54:06 +08:00
YFdyh000 807f0aba06 Make Admin AutoRun faster (#9366)
* Set Admin AutoRun task priority to Normal rather than Below Normal

* Remove 30s delay for Admin AutoRun
2026-05-23 13:58:00 +08:00
2dust f4a2086dfb Refine reload logic after RefreshServers
https://github.com/2dust/v2rayN/issues/9344
2026-05-22 10:07:36 +08:00
2dust ccb0ffb3b6 up 7.22.3 2026-05-21 17:25:50 +08:00
2dust 1fa1246c0b Bug fix
https://github.com/2dust/v2rayN/issues/9351
2026-05-21 14:15:07 +08:00
2dust 14cc37d07a Add 'New Update' notification flow
Introduce a small update-notification feature: add AppEvents.HasUpdateNotified event and have TaskManager publish it when update messages exist. Add localized resource key (menuNewUpdate) and expose it in the ResUI designer; update resource files for several languages. In the UI, add a New Update button in MainWindow.xaml, wire its click to the existing check-update handler, bind its visibility to a new BlNewUpdate property on MainWindowViewModel, and subscribe the viewmodel to the new event. Also reset the notification flag after showing the Check Update dialog.
2026-05-21 11:25:49 +08:00
2dust c7519f8ea7 Case-insensitive name checks and IpInfo visibility 2026-05-21 10:33:17 +08:00
2dust 0c796a157b Add IP info & flag emoji to test,add ip info column for main window
Fetch and display tested server IP and country (with emoji) in speed tests. Adds CountryExtension for country->emoji mapping and a new IpInfoResult type; ConnectionHandler now retrieves/parses IP API results and GetRealPingTime signature adjusted. Models and entities (ProfileItemModel, ProfileExItem, SpeedTestResult) gain IpInfo fields; ProfileExManager can store test IP info. UI/UX updated: new IpInfo column in ProfilesView (desktop and Avalonia), ResUI resource strings for "IP Info", and EServerColName ordering supports IpInfo. SpeedtestService now captures IP info and forwards it to the view model via the update function.
2026-05-20 19:16:58 +08:00
2dust a9824fe6ec Improve ConnectionHandler DownloadService 2026-05-20 15:42:47 +08:00
2dust f18758d4bf Support socks4/5 URIs and improve userinfo parsing 2026-05-20 14:28:01 +08:00
JieXu c1a009a409 bump dependencies (#9341)
* Update build-linux.yml

* Update Directory.Packages.props

* Update Directory.Packages.props
2026-05-20 09:01:22 +08:00
124 changed files with 2037 additions and 1079 deletions
-1
View File
@@ -100,7 +100,6 @@ csharp_style_prefer_tuple_swap = true:warning
csharp_style_prefer_utf8_string_literals = true:warning csharp_style_prefer_utf8_string_literals = true:warning
csharp_style_throw_expression = true:warning csharp_style_throw_expression = true:warning
csharp_style_unused_value_assignment_preference = discard_variable:warning csharp_style_unused_value_assignment_preference = discard_variable:warning
csharp_style_unused_value_expression_statement_preference = discard_variable:warning
csharp_using_directive_placement = outside_namespace:warning csharp_using_directive_placement = outside_namespace:warning
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:warning csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:warning
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:warning csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:warning
+4 -4
View File
@@ -335,13 +335,13 @@ jobs:
env: env:
RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }} RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}
QCOW2_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/debian13-loong64.qcow2 QCOW2_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/debian13-loong64.qcow2
EFI_CODE_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/edk2-loongarch64-code.fd EFI_CODE_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/edk2-loongarch64-code.fd
EFI_VARS_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/edk2-loongarch64-vars.fd EFI_VARS_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/edk2-loongarch64-vars.fd
QCOW2_IMAGE: debian13-loong64.qcow2 QCOW2_IMAGE: debian13-loong64.qcow2
EFI_CODE: edk2-loongarch64-code.fd EFI_CODE: edk2-loongarch64-code.fd
EFI_VARS: edk2-loongarch64-vars.fd EFI_VARS: edk2-loongarch64-vars.fd
QEMU_VERSION: 10.2.2 QEMU_VERSION: 10.2.3
steps: steps:
- name: Prepare host tools - name: Prepare host tools
+1 -1
View File
@@ -52,7 +52,7 @@ jobs:
fetch-depth: '0' fetch-depth: '0'
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v5.2.0 uses: actions/setup-dotnet@v5.3.0
with: with:
dotnet-version: '10.0.1xx' dotnet-version: '10.0.1xx'
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
fetch-depth: '0' fetch-depth: '0'
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v5.2.0 uses: actions/setup-dotnet@v5.3.0
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
+11 -1
View File
@@ -22,7 +22,17 @@ cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>English</string> <string>en</string>
<key>CFBundleLocalizations</key>
<array>
<string>zh-Hans</string>
<string>zh-Hant</string>
<string>en</string>
<string>fa</string>
<string>fr</string>
<string>ru</string>
<string>hu</string>
</array>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>v2rayN</string> <string>v2rayN</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
+1 -1
View File
@@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.22.2</Version> <Version>7.22.7</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
+11 -11
View File
@@ -7,33 +7,33 @@
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" /> <PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.15" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.17" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.15" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.17" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" /> <PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" /> <PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.12" /> <PackageVersion Include="IPNetwork2" Version="4.3.0" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.13" />
<PackageVersion Include="CliWrap" Version="3.10.1" /> <PackageVersion Include="CliWrap" Version="3.10.1" />
<PackageVersion Include="Downloader" Version="5.5.0" /> <PackageVersion Include="Downloader" Version="5.7.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" /> <PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" /> <PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
<PackageVersion Include="QRCoder" Version="1.8.0" /> <PackageVersion Include="QRCoder" Version="1.8.0" />
<PackageVersion Include="ReactiveUI" Version="23.2.27" /> <PackageVersion Include="ReactiveUI" Version="23.2.28" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" /> <PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.27" /> <PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.14" /> <PackageVersion Include="Semi.Avalonia" Version="11.3.14" />
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" /> <PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" /> <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
<PackageVersion Include="NLog" Version="6.1.3" /> <PackageVersion Include="NLog" Version="6.1.3" />
<PackageVersion Include="sqlite-net-e" Version="1.11.0" /> <PackageVersion Include="sqlite-net-e" Version="1.11.0" />
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.1.4" /> <PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.2" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" /> <PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.21.3" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" /> <PackageVersion Include="WebDav.Client" Version="2.9.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" /> <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="xunit.v3" Version="3.2.2" /> <PackageVersion Include="xunit.v3" Version="3.2.2" />
<PackageVersion Include="YamlDotNet" Version="17.1.0" /> <PackageVersion Include="YamlDotNet" Version="18.0.0" />
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" /> <PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4-preview.1.1" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -17,7 +17,7 @@ internal static class CoreConfigTestFactory
{ {
return new Config return new Config
{ {
CoreBasicItem = new CoreBasicItem { Loglevel = "warning", MuxEnabled = false }, CoreBasicItem = new CoreBasicItem { Loglevel = "warning" },
TunModeItem = new TunModeItem { EnableTun = false, IcmpRouting = "default" }, TunModeItem = new TunModeItem { EnableTun = false, IcmpRouting = "default" },
KcpItem = new KcpItem(), KcpItem = new KcpItem(),
GrpcItem = new GrpcItem(), GrpcItem = new GrpcItem(),
@@ -187,7 +187,7 @@ internal static class CoreConfigTestFactory
SimpleDnsItem = config.SimpleDNSItem, SimpleDnsItem = config.SimpleDNSItem,
AllProxiesMap = new Dictionary<string, ProfileItem> { [node.IndexId] = node }, AllProxiesMap = new Dictionary<string, ProfileItem> { [node.IndexId] = node },
FullConfigTemplate = null, FullConfigTemplate = null,
IsTunEnabled = false, IsTunEnabled = config.TunModeItem.EnableTun,
ProtectDomainList = [], ProtectDomainList = [],
}; };
} }
@@ -206,4 +206,12 @@ internal static class CoreConfigTestFactory
config.SimpleDNSItem.BootstrapDNS = bootstrapDns; config.SimpleDNSItem.BootstrapDNS = bootstrapDns;
return config; return config;
} }
public static Config CreateConfigWithTunRouteExcludeAddress(ECoreType coreType)
{
var config = CreateConfig(coreType);
config.TunModeItem.EnableTun = true;
config.TunModeItem.RouteExcludeAddress = ["10.0.0.1/32", "192.168.1.0/24", "fc00::/7"];
return config;
}
} }
@@ -554,7 +554,7 @@ public class CoreConfigSingboxServiceTests
cfg.dns.servers.Should().Contain(s => s.tag == "remote" && s.type == "udp" && s.server == "8.8.8.8"); cfg.dns.servers.Should().Contain(s => s.tag == "remote" && s.type == "udp" && s.server == "8.8.8.8");
cfg.dns.servers.Should().Contain(s => s.tag == Global.SingboxLocalDNSTag); cfg.dns.servers.Should().Contain(s => s.tag == Global.SingboxLocalDNSTag);
cfg.dns.rules.Should().Contain(r => r.clash_mode == ERuleMode.Global.ToString()); cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Global));
cfg.dns.rules.Should().Contain(r => r.clash_mode == ERuleMode.Direct.ToString()); cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Direct));
} }
} }
@@ -536,4 +536,27 @@ public class CoreConfigV2rayServiceTests
directOutbound.Should().NotBeNull(); directOutbound.Should().NotBeNull();
directOutbound!.settings.domainStrategy.Should().Be("UseIPv4"); directOutbound!.settings.domainStrategy.Should().Be("UseIPv4");
} }
[Fact]
public void GenerateClientConfigContent_TunRouteExcludeAddress()
{
var config = CoreConfigTestFactory.CreateConfigWithTunRouteExcludeAddress(ECoreType.Xray);
CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray);
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
result.Success.Should().BeTrue();
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
tunInbound.Should().NotBeNull();
tunInbound!.settings.autoSystemRoutingTable.Should().NotContain("0.0.0.0/0");
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.0/32");
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.2/31");
}
} }
@@ -0,0 +1,92 @@
namespace ServiceLib.Common;
/// <summary>
/// Extension methods for country code utilities
/// </summary>
public static class CountryExtension
{
/// <summary>
/// Country code to emoji flag mapping for common countries
/// </summary>
private static readonly Dictionary<string, string> CountryEmojiMap = new(StringComparer.OrdinalIgnoreCase)
{
// Asia
{ "CN", "🇨🇳" }, // China
{ "HK", "🇭🇰" }, // Hong Kong
{ "TW", "🇹🇼" }, // Taiwan
{ "JP", "🇯🇵" }, // Japan
{ "SG", "🇸🇬" }, // Singapore
{ "KR", "🇰🇷" }, // South Korea
{ "TH", "🇹🇭" }, // Thailand
{ "VN", "🇻🇳" }, // Vietnam
{ "ID", "🇮🇩" }, // Indonesia
{ "PH", "🇵🇭" }, // Philippines
{ "MY", "🇲🇾" }, // Malaysia
{ "IN", "🇮🇳" }, // India
{ "PK", "🇵🇰" }, // Pakistan
{ "BD", "🇧🇩" }, // Bangladesh
{ "LK", "🇱🇰" }, // Sri Lanka
{ "KH", "🇰🇭" }, // Cambodia
{ "LA", "🇱🇦" }, // Laos
{ "MM", "🇲🇲" }, // Myanmar
// Americas
{ "US", "🇺🇸" }, // United States
{ "CA", "🇨🇦" }, // Canada
{ "MX", "🇲🇽" }, // Mexico
{ "BR", "🇧🇷" }, // Brazil
{ "AR", "🇦🇷" }, // Argentina
{ "CL", "🇨🇱" }, // Chile
{ "CO", "🇨🇴" }, // Colombia
// Europe
{ "GB", "🇬🇧" }, // United Kingdom
{ "DE", "🇩🇪" }, // Germany
{ "FR", "🇫🇷" }, // France
{ "IT", "🇮🇹" }, // Italy
{ "ES", "🇪🇸" }, // Spain
{ "RU", "🇷🇺" }, // Russia
{ "NL", "🇳🇱" }, // Netherlands
{ "CH", "🇨🇭" }, // Switzerland
{ "SE", "🇸🇪" }, // Sweden
{ "NO", "🇳🇴" }, // Norway
{ "DK", "🇩🇰" }, // Denmark
{ "FI", "🇫🇮" }, // Finland
{ "PL", "🇵🇱" }, // Poland
{ "CZ", "🇨🇿" }, // Czech Republic
{ "AT", "🇦🇹" }, // Austria
{ "GR", "🇬🇷" }, // Greece
{ "PT", "🇵🇹" }, // Portugal
{ "TR", "🇹🇷" }, // Turkey
{ "UA", "🇺🇦" }, // Ukraine
{ "RO", "🇷🇴" }, // Romania
// Middle East & Central Asia
{ "AE", "🇦🇪" }, // United Arab Emirates
{ "SA", "🇸🇦" }, // Saudi Arabia
{ "IL", "🇮🇱" }, // Israel
{ "KZ", "🇰🇿" }, // Kazakhstan
// Oceania
{ "AU", "🇦🇺" }, // Australia
{ "NZ", "🇳🇿" }, // New Zealand
// Africa
{ "ZA", "🇿🇦" }, // South Africa
{ "EG", "🇪🇬" }, // Egypt
};
/// <summary>
/// Converts country code to flag emoji using predefined mapping
/// Example: "US" -> "🇺🇸", "CN" -> "🇨🇳"
/// </summary>
public static string? CountryToEmoji(this string? countryCode)
{
if (countryCode.IsNullOrEmpty())
{
return null;
}
return CountryEmojiMap.GetValueOrDefault(countryCode);
}
}
+17 -1
View File
@@ -47,7 +47,7 @@ public static class Extension
public static string TrimEx(this string? value) public static string TrimEx(this string? value)
{ {
return value == null ? string.Empty : value.Trim(); return value?.Trim() ?? string.Empty;
} }
public static string RemovePrefix(this string value, char prefix) public static string RemovePrefix(this string value, char prefix)
@@ -118,4 +118,20 @@ public static class Extension
destination.Add(item); destination.Add(item);
} }
} }
/// <summary>
/// Replace all cross-platform newline characters with the specified string
/// </summary>
public static string ReplaceLineBreaks(this string input, string replacement)
{
if (string.IsNullOrEmpty(input))
{
return input;
}
// You must replace \r\n first, and then replace the single characters \r and \n.
return input.Replace("\r\n", replacement)
.Replace("\r", replacement)
.Replace("\n", replacement);
}
} }
+5 -1
View File
@@ -202,7 +202,7 @@ public static class FileUtils
} }
} }
public static void DeleteExpiredFiles(string sourceDir, DateTime dtLine) public static void DeleteExpiredFiles(string sourceDir, DateTime dtLine, string? contains = null)
{ {
try try
{ {
@@ -214,6 +214,10 @@ public static class FileUtils
{ {
continue; continue;
} }
if (contains.IsNotEmpty() && !file.Name.Contains(contains))
{
continue;
}
file.Delete(); file.Delete();
} }
} }
+5 -5
View File
@@ -51,7 +51,7 @@ public class Utils
try try
{ {
str = str.Replace(Environment.NewLine, string.Empty); str = str.ReplaceLineBreaks(string.Empty);
return new List<string>(str.Split(',', StringSplitOptions.RemoveEmptyEntries)); return new List<string>(str.Split(',', StringSplitOptions.RemoveEmptyEntries));
} }
catch (Exception ex) catch (Exception ex)
@@ -114,9 +114,7 @@ public class Utils
} }
plainText = plainText.Trim() plainText = plainText.Trim()
.Replace(Environment.NewLine, "") .ReplaceLineBreaks("")
.Replace("\n", "")
.Replace("\r", "")
.Replace('_', '/') .Replace('_', '/')
.Replace('-', '+') .Replace('-', '+')
.Replace(" ", ""); .Replace(" ", "");
@@ -321,7 +319,9 @@ public class Utils
return text; return text;
} }
return text.Replace(",", ",").Replace(Environment.NewLine, ","); return text.Replace(",", ",")
.Replace(" ", "")
.ReplaceLineBreaks(",");
} }
public static List<string> GetEnumNames<TEnum>() where TEnum : Enum public static List<string> GetEnumNames<TEnum>() where TEnum : Enum
@@ -12,6 +12,7 @@ public enum EServerColName
SubRemarks, SubRemarks,
DelayVal, DelayVal,
SpeedVal, SpeedVal,
IpInfo,
TodayDown, TodayDown,
TodayUp, TodayUp,
+1
View File
@@ -7,6 +7,7 @@ public static class AppEvents
public static readonly EventChannel<Unit> AddServerViaScanRequested = new(); public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new(); public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new(); public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
public static readonly EventChannel<bool> HasUpdateNotified = new();
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new(); public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new(); public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
+22 -18
View File
@@ -55,7 +55,7 @@ public class Global
public const string DnsOutboundTag = "dns"; public const string DnsOutboundTag = "dns";
public const string DnsTag = "dns-module"; public const string DnsTag = "dns-module";
public const string DirectDnsTag = "direct-dns"; public const string DirectDnsTag = "direct-dns";
public const string BalancerTagSuffix = "-round"; public const string BalancerTagSuffix = "-balancer";
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";
@@ -90,6 +90,8 @@ public class Global
public const string XrayLocalCert = "XRAY_LOCATION_CERT"; public const string XrayLocalCert = "XRAY_LOCATION_CERT";
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 StringTrue = "true";
public const string StringFalse = "false";
public const string SingboxDirectDNSTag = "direct_dns"; public const string SingboxDirectDNSTag = "direct_dns";
public const string SingboxRemoteDNSTag = "remote_dns"; public const string SingboxRemoteDNSTag = "remote_dns";
@@ -149,6 +151,9 @@ public class Global
public static readonly List<string> SpeedTestUrls = public static readonly List<string> SpeedTestUrls =
[ [
@"https://cachefly.cachefly.net/50mb.test", @"https://cachefly.cachefly.net/50mb.test",
@"https://cachefly.cachefly.net/100mb.test",
@"https://cachefly.cachefly.net/1mb.test",
@"https://cachefly.cachefly.net/10mb.test",
@"https://speed.cloudflare.com/__down?bytes=10000000", @"https://speed.cloudflare.com/__down?bytes=10000000",
@"https://speed.cloudflare.com/__down?bytes=50000000", @"https://speed.cloudflare.com/__down?bytes=50000000",
@"https://speed.cloudflare.com/__down?bytes=99999999", @"https://speed.cloudflare.com/__down?bytes=99999999",
@@ -157,6 +162,8 @@ public class Global
public static readonly List<string> SpeedPingTestUrls = public static readonly List<string> SpeedPingTestUrls =
[ [
@"https://www.google.com/generate_204", @"https://www.google.com/generate_204",
@"https://www.youtube.com/generate_204",
@"https://www.googlevideo.com/generate_204",
@"https://www.gstatic.com/generate_204", @"https://www.gstatic.com/generate_204",
@"https://www.apple.com/library/test/success.html", @"https://www.apple.com/library/test/success.html",
@"http://www.msftconnecttest.com/connecttest.txt" @"http://www.msftconnecttest.com/connecttest.txt"
@@ -207,6 +214,10 @@ public class Global
public const string NaiveQuicProtocolShare = "naive+quic://"; public const string NaiveQuicProtocolShare = "naive+quic://";
public const string SOCKS5Protocol = "socks5://";
public const string SOCKS4Protocol = "socks4://";
public static readonly Dictionary<EConfigType, string> ProtocolShares = new() public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
{ {
{ EConfigType.VMess, "vmess://" }, { EConfigType.VMess, "vmess://" },
@@ -321,12 +332,12 @@ public class Global
public static readonly Dictionary<string, string> KcpHeaderMaskMap = new() public static readonly Dictionary<string, string> KcpHeaderMaskMap = new()
{ {
{ "srtp", "header-srtp" }, { "srtp", "srtp" },
{ "utp", "header-utp" }, { "utp", "utp" },
{ "wechat-video", "header-wechat" }, { "wechat-video", "wechat" },
{ "dtls", "header-dtls" }, { "dtls", "dtls" },
{ "wireguard", "header-wireguard" }, { "wireguard", "wireguard" },
{ "dns", "header-dns" } { "dns", "dns" }
}; };
public static readonly List<string> CoreTypes = public static readonly List<string> CoreTypes =
@@ -412,13 +423,6 @@ public class Global
"stream-one" "stream-one"
]; ];
public static readonly List<string> AllowInsecure =
[
"true",
"false",
""
];
public static readonly List<string> DomainStrategy = public static readonly List<string> DomainStrategy =
[ [
"AsIs", "AsIs",
@@ -469,7 +473,7 @@ public class Global
"zh-Hans", "zh-Hans",
"zh-Hant", "zh-Hant",
"en", "en",
"fa-Ir", "fa",
"fr", "fr",
"ru", "ru",
"hu" "hu"
@@ -676,15 +680,15 @@ public class Global
{ "one.one.one.one", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] }, { "one.one.one.one", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] },
{ "1dot1dot1dot1.cloudflare-dns.com", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] }, { "1dot1dot1dot1.cloudflare-dns.com", ["1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"] },
{ "cloudflare-dns.com", ["104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9"] }, { "cloudflare-dns.com", ["104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9"] },
{ "dns.cloudflare.com", ["104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5"] }, { "dns.cloudflare.com", ["162.159.61.8", "172.64.41.8", "2a06:98c1:52::8", "2803:f800:53::8"] },
{ "dot.pub", ["1.12.12.12", "120.53.53.53"] }, { "dot.pub", ["1.12.12.12", "120.53.53.53"] },
{ "doh.pub", ["1.12.12.12", "120.53.53.53"] }, { "doh.pub", ["1.12.12.12", "120.53.53.53"] },
{ "dns.quad9.net", ["9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9"] }, { "dns.quad9.net", ["9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9"] },
{ "dns.yandex.net", ["77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff"] }, { "dns.yandex.net", ["77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff"] },
{ "dns.sb", ["185.222.222.222", "2a09::"] }, { "dns.sb", ["45.11.45.11", "185.222.222.222", "2a09::", "2a11::"] },
{ "dns.umbrella.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] }, { "dns.umbrella.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] },
{ "dns.sse.cisco.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] }, { "dns.sse.cisco.com", ["208.67.220.220", "208.67.222.222", "2620:119:35::35", "2620:119:53::53"] },
{ "engage.cloudflareclient.com", ["162.159.192.1"] } { "engage.cloudflareclient.com", ["162.159.192.1", "2606:4700:d0::a29f:c001"] }
}; };
public static readonly List<string> ExpectedIPs = public static readonly List<string> ExpectedIPs =
@@ -111,7 +111,8 @@ public static class AutoStartupHandler
task.Settings.RunOnlyIfIdle = false; task.Settings.RunOnlyIfIdle = false;
task.Settings.IdleSettings.StopOnIdleEnd = false; task.Settings.IdleSettings.StopOnIdleEnd = false;
task.Settings.ExecutionTimeLimit = TimeSpan.Zero; task.Settings.ExecutionTimeLimit = TimeSpan.Zero;
task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(30) }); task.Settings.Priority = ProcessPriorityClass.Normal;
task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser });
task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest; task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest;
task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName))); task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName)));
@@ -91,6 +91,20 @@ public class CoreConfigContextBuilder
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode; context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode;
} }
} }
if (context.IsTunEnabled && context.AppConfig.TunModeItem.RouteExcludeAddress is { Count: > 0 })
{
foreach (var addr in context.AppConfig.TunModeItem.RouteExcludeAddress)
{
try
{
IPNetwork2.Parse(addr);
}
catch
{
validatorResult.Errors.Add(string.Format(ResUI.MsgTunRouteExcludeInvalidAddress, addr));
}
}
}
return new CoreConfigContextBuilderResult(context, validatorResult); return new CoreConfigContextBuilderResult(context, validatorResult);
} }
@@ -29,35 +29,6 @@ public class NodeValidator
return v.ToResult(); return v.ToResult();
} }
private class ValidationContext
{
public List<string> Errors { get; } = [];
public List<string> Warnings { get; } = [];
public void Error(string message)
{
Errors.Add(message);
}
public void Warning(string message)
{
Warnings.Add(message);
}
public void Assert(bool condition, string errorMsg)
{
if (!condition)
{
Error(errorMsg);
}
}
public NodeValidatorResult ToResult()
{
return new NodeValidatorResult(Errors, Warnings);
}
}
private static void ValidateNodeAndCoreSupport(ProfileItem item, ECoreType coreType, ValidationContext v) private static void ValidateNodeAndCoreSupport(ProfileItem item, ECoreType coreType, ValidationContext v)
{ {
if (item.ConfigType is EConfigType.Custom) if (item.ConfigType is EConfigType.Custom)
@@ -126,6 +97,21 @@ public class NodeValidator
break; break;
} }
if (coreType is ECoreType.Xray
&& (protocolExtra.Flow ?? string.Empty).StartsWith("xtls", StringComparison.OrdinalIgnoreCase)
&& item.MuxEnabled == true)
{
v.Warning(string.Format(ResUI.MsgOptionsConflict, "XTLS", "Mux.Cool"));
}
if (item.GetNetwork() is nameof(ETransport.ws)
&& item.EchConfigList.IsNullOrEmpty()
&& item.GetAlpn()?.FirstOrDefault() == "h3")
{
v.Warning(
"WebSocket but ALPN is set to h3, the core may ignore the ALPN setting or cause unexpected issues.");
}
// TLS & Security // TLS & Security
if (item.StreamSecurity == Global.StreamSecurity) if (item.StreamSecurity == Global.StreamSecurity)
{ {
@@ -134,6 +120,25 @@ public class NodeValidator
{ {
v.Error(string.Format(ResUI.MsgInvalidProperty, "TLS Certificate")); v.Error(string.Format(ResUI.MsgInvalidProperty, "TLS Certificate"));
} }
// Check for deprecated allowInsecure property when TLS is enabled
if (item.GetAllowInsecure()
&& item.Cert.IsNullOrEmpty()
&& item.CertSha.IsNullOrEmpty())
{
v.Warning(ResUI.MsgAllowInsecureDeprecated);
}
if ((coreType == ECoreType.Xray
&& item.GetAllowInsecure()
&& item.Cert.IsNullOrEmpty()
&& item.CertSha.IsNullOrEmpty())
|| (coreType == ECoreType.sing_box
&& item.GetAllowInsecure()
&& item.Cert.IsNullOrEmpty()))
{
v.Warning("Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks.");
}
} }
if (item.StreamSecurity == Global.StreamSecurityReality) if (item.StreamSecurity == Global.StreamSecurityReality)
@@ -183,4 +188,33 @@ public class NodeValidator
return null; return null;
} }
private class ValidationContext
{
public List<string> Errors { get; } = [];
public List<string> Warnings { get; } = [];
public void Error(string message)
{
Errors.Add(message);
}
public void Warning(string message)
{
Warnings.Add(message);
}
public void Assert(bool condition, string errorMsg)
{
if (!condition)
{
Error(errorMsg);
}
}
public NodeValidatorResult ToResult()
{
return new NodeValidatorResult(Errors, Warnings);
}
}
} }
+16 -17
View File
@@ -39,15 +39,14 @@ public static class ConfigHandler
{ {
LogEnabled = false, LogEnabled = false,
Loglevel = "warning", Loglevel = "warning",
MuxEnabled = false,
}; };
if (config.Inbound == null) if (config.Inbound == null)
{ {
config.Inbound = new List<InItem>(); config.Inbound = [];
InItem inItem = new() InItem inItem = new()
{ {
Protocol = EInboundProtocol.socks.ToString(), Protocol = nameof(EInboundProtocol.socks),
LocalPort = 10808, LocalPort = 10808,
UdpEnabled = true, UdpEnabled = true,
SniffingEnabled = true, SniffingEnabled = true,
@@ -60,7 +59,7 @@ public static class ConfigHandler
{ {
if (config.Inbound.Count > 0) if (config.Inbound.Count > 0)
{ {
config.Inbound.First().Protocol = EInboundProtocol.socks.ToString(); config.Inbound.First().Protocol = nameof(EInboundProtocol.socks);
} }
} }
@@ -99,8 +98,8 @@ public static class ConfigHandler
config.MsgUIItem ??= new(); config.MsgUIItem ??= new();
config.UiItem ??= new(); config.UiItem ??= new();
config.UiItem.MainColumnItem ??= new(); config.UiItem.MainColumnItem ??= [];
config.UiItem.WindowSizeItem ??= new(); config.UiItem.WindowSizeItem ??= [];
if (config.UiItem.CurrentLanguage.IsNullOrEmpty()) if (config.UiItem.CurrentLanguage.IsNullOrEmpty())
{ {
@@ -158,7 +157,7 @@ public static class ConfigHandler
DownMbps = 100 DownMbps = 100
}; };
config.ClashUIItem ??= new(); config.ClashUIItem ??= new();
config.ClashUIItem.ConnectionsColumnItem ??= new(); config.ClashUIItem.ConnectionsColumnItem ??= [];
config.SystemProxyItem ??= new(); config.SystemProxyItem ??= new();
config.WebDavItem ??= new(); config.WebDavItem ??= new();
config.CheckUpdateItem ??= new(); config.CheckUpdateItem ??= new();
@@ -168,7 +167,7 @@ public static class ConfigHandler
Length = "50-100", Length = "50-100",
Interval = "10-20" Interval = "10-20"
}; };
config.GlobalHotkeys ??= new(); config.GlobalHotkeys ??= [];
if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty()) if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty())
{ {
@@ -255,6 +254,7 @@ public static class ConfigHandler
item.Cert = profileItem.Cert; item.Cert = profileItem.Cert;
item.CertSha = profileItem.CertSha; item.CertSha = profileItem.CertSha;
item.EchConfigList = profileItem.EchConfigList; item.EchConfigList = profileItem.EchConfigList;
item.VerifyPeerCertByName = profileItem.VerifyPeerCertByName;
item.Finalmask = profileItem.Finalmask; item.Finalmask = profileItem.Finalmask;
item.ProtoExtra = profileItem.ProtoExtra; item.ProtoExtra = profileItem.ProtoExtra;
item.TransportExtra = profileItem.TransportExtra; item.TransportExtra = profileItem.TransportExtra;
@@ -874,7 +874,7 @@ public static class ConfigHandler
profileItem.Fingerprint = string.Empty; profileItem.Fingerprint = string.Empty;
profileItem.Alpn = string.Empty; profileItem.Alpn = string.Empty;
profileItem.Network = string.Empty; profileItem.Network = string.Empty;
profileItem.AllowInsecure = "false"; profileItem.AllowInsecure = string.Empty;
if (profileItem.StreamSecurity.IsNullOrEmpty()) if (profileItem.StreamSecurity.IsNullOrEmpty())
{ {
profileItem.StreamSecurity = Global.StreamSecurity; profileItem.StreamSecurity = Global.StreamSecurity;
@@ -923,6 +923,7 @@ public static class ConfigHandler
Delay = t33?.Delay ?? 0, Delay = t33?.Delay ?? 0,
Speed = t33?.Speed ?? 0, Speed = t33?.Speed ?? 0,
Sort = t33?.Sort ?? 0, Sort = t33?.Sort ?? 0,
IpInfo = t33?.IpInfo ?? string.Empty,
TodayDown = (t22?.TodayDown ?? 0).ToString("D16"), TodayDown = (t22?.TodayDown ?? 0).ToString("D16"),
TodayUp = (t22?.TodayUp ?? 0).ToString("D16"), TodayUp = (t22?.TodayUp ?? 0).ToString("D16"),
TotalDown = (t22?.TotalDown ?? 0).ToString("D16"), TotalDown = (t22?.TotalDown ?? 0).ToString("D16"),
@@ -943,6 +944,7 @@ public static class ConfigHandler
EServerColName.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(), EServerColName.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(),
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.IpInfo => lstProfile.OrderBy(t => t.IpInfo).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.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(),
EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).ToList(), EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).ToList(),
@@ -963,6 +965,7 @@ public static class ConfigHandler
EServerColName.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(), EServerColName.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(),
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.IpInfo => lstProfile.OrderByDescending(t => t.IpInfo).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.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(),
EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).ToList(), EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).ToList(),
@@ -1053,8 +1056,8 @@ public static class ConfigHandler
return new Tuple<int, int>(0, 0); return new Tuple<int, int>(0, 0);
} }
List<ProfileItem> lstKeep = new(); List<ProfileItem> lstKeep = [];
List<ProfileItem> lstRemove = new(); List<ProfileItem> lstRemove = [];
if (!config.GuiItem.KeepOlderDedupl) if (!config.GuiItem.KeepOlderDedupl)
{ {
lstProfile.Reverse(); lstProfile.Reverse();
@@ -1103,10 +1106,6 @@ public static class ConfigHandler
} }
else else
{ {
if (profileItem.AllowInsecure.IsNullOrEmpty())
{
profileItem.AllowInsecure = config.CoreBasicItem.DefAllowInsecure.ToString().ToLower();
}
if (profileItem.Fingerprint.IsNullOrEmpty() && profileItem.StreamSecurity == Global.StreamSecurityReality) if (profileItem.Fingerprint.IsNullOrEmpty() && profileItem.StreamSecurity == Global.StreamSecurityReality)
{ {
profileItem.Fingerprint = config.CoreBasicItem.DefFingerprint; profileItem.Fingerprint = config.CoreBasicItem.DefFingerprint;
@@ -1534,7 +1533,7 @@ public static class ConfigHandler
} }
var countServers = 0; var countServers = 0;
List<ProfileItem> lstAdd = new(); List<ProfileItem> lstAdd = [];
var arrData = strData.Split(Environment.NewLine.ToCharArray()).Where(t => !t.IsNullOrEmpty()); var arrData = strData.Split(Environment.NewLine.ToCharArray()).Where(t => !t.IsNullOrEmpty());
if (isSub) if (isSub)
{ {
@@ -1630,7 +1629,7 @@ public static class ConfigHandler
{ {
lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks); lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks);
} }
if (lstProfiles != null && lstProfiles.Count > 0) if (lstProfiles is { Count: > 0 })
{ {
var count = 0; var count = 0;
foreach (var it in lstProfiles) foreach (var it in lstProfiles)
+68 -30
View File
@@ -4,53 +4,41 @@ public static class ConnectionHandler
{ {
private static readonly string _tag = "ConnectionHandler"; private static readonly string _tag = "ConnectionHandler";
/// <summary>
/// Runs ping and IP checks and returns a formatted result string.
/// </summary>
public static async Task<string> RunAvailabilityCheck() public static async Task<string> RunAvailabilityCheck()
{ {
var time = await GetRealPingTimeInfo(); var time = await GetRealPingTimeInfo();
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None; var ip = time > 0 ? await GetIPInfo() : Global.None;
return string.Format(ResUI.TestMeOutput, time, ip); return string.Format(ResUI.TestMeOutput, time, ip);
} }
/// <summary>
/// Gets IP information using the default local proxy.
/// </summary>
private static async Task<string?> GetIPInfo() private static async Task<string?> GetIPInfo()
{ {
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl; var webProxy = await GetWebProxy();
if (url.IsNullOrEmpty())
{
return null;
}
var downloadHandle = new DownloadService(); var ipInfo = await GetIPInfo(webProxy);
var result = await downloadHandle.TryDownloadString(url, true, ""); return ipInfo?.ToString() ?? Global.None;
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}";
} }
/// <summary>
/// Measures real ping time using configured test URL.
/// </summary>
private static async Task<int> GetRealPingTimeInfo() private static async Task<int> GetRealPingTimeInfo()
{ {
var responseTime = -1; var responseTime = -1;
try try
{ {
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); var webProxy = await GetWebProxy();
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}");
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
for (var i = 0; i < 2; i++) for (var i = 0; i < 2; i++)
{ {
responseTime = await GetRealPingTime(url, webProxy, 10); responseTime = await GetRealPingTime(webProxy, 10);
if (responseTime > 0) if (responseTime > 0)
{ {
break; break;
@@ -66,8 +54,21 @@ public static class ConnectionHandler
return responseTime; return responseTime;
} }
public static async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout) /// <summary>
/// Creates local SOCKS proxy instance.
/// </summary>
private static async Task<WebProxy?> GetWebProxy()
{ {
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
return new WebProxy($"socks5://{Global.Loopback}:{port}");
}
/// <summary>
/// Measures response time by sending HTTP requests through proxy.
/// </summary>
public static async Task<int> GetRealPingTime(IWebProxy? webProxy, int downloadTimeout)
{
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
var responseTime = -1; var responseTime = -1;
try try
{ {
@@ -79,14 +80,14 @@ public static class ConnectionHandler
UseProxy = webProxy != null UseProxy = webProxy != null
}); });
List<int> oneTime = new(); List<int> oneTime = [];
for (var i = 0; i < 2; i++) for (var i = 0; i < 2; i++)
{ {
var timer = Stopwatch.StartNew(); var timer = Stopwatch.StartNew();
await client.GetAsync(url, cts.Token).ConfigureAwait(false); await client.GetAsync(url, cts.Token).ConfigureAwait(false);
timer.Stop(); timer.Stop();
oneTime.Add((int)timer.Elapsed.TotalMilliseconds); oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
await Task.Delay(100); await Task.Delay(100, cts.Token);
} }
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault(); responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
} }
@@ -95,4 +96,41 @@ public static class ConnectionHandler
} }
return responseTime; return responseTime;
} }
/// <summary>
/// Gets IP and country information through specified proxy.
/// </summary>
public static async Task<IpInfoResult?> GetIPInfo(IWebProxy? webProxy)
{
try
{
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
if (url.IsNullOrEmpty())
{
return null;
}
var downloadHandle = new DownloadService();
var result = await downloadHandle.TryDownloadString(url, webProxy, "");
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 ?? "unknown";
return new IpInfoResult(country, ip);
}
catch
{
return null;
}
}
} }
+8 -3
View File
@@ -73,6 +73,10 @@ public class BaseFmt
{ {
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList)); dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
} }
if (item.VerifyPeerCertByName.IsNotEmpty())
{
dicQuery.Add("vcn", Utils.UrlEncode(item.VerifyPeerCertByName));
}
if (item.CertSha.IsNotEmpty()) if (item.CertSha.IsNotEmpty())
{ {
dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha)); dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha));
@@ -199,7 +203,7 @@ public class BaseFmt
private static int ToUriQueryAllowInsecure(ProfileItem item, ref Dictionary<string, string> dicQuery) private static int ToUriQueryAllowInsecure(ProfileItem item, ref Dictionary<string, string> dicQuery)
{ {
if (item.AllowInsecure.Equals(Global.AllowInsecure.First())) if (item.GetAllowInsecure())
{ {
// Add two for compatibility // Add two for compatibility
dicQuery.Add("insecure", "1"); dicQuery.Add("insecure", "1");
@@ -227,6 +231,7 @@ public class BaseFmt
item.SpiderX = GetQueryDecoded(query, "spx"); item.SpiderX = GetQueryDecoded(query, "spx");
item.Mldsa65Verify = GetQueryDecoded(query, "pqv"); item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
item.EchConfigList = GetQueryDecoded(query, "ech"); item.EchConfigList = GetQueryDecoded(query, "ech");
item.VerifyPeerCertByName = GetQueryDecoded(query, "vcn");
item.CertSha = GetQueryDecoded(query, "pcs"); item.CertSha = GetQueryDecoded(query, "pcs");
var finalmaskDecoded = GetQueryDecoded(query, "fm"); var finalmaskDecoded = GetQueryDecoded(query, "fm");
@@ -249,11 +254,11 @@ public class BaseFmt
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1")) if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
{ {
item.AllowInsecure = Global.AllowInsecure.First(); item.AllowInsecure = Global.StringTrue;
} }
else if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "0")) else if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "0"))
{ {
item.AllowInsecure = Global.AllowInsecure.Skip(1).First(); item.AllowInsecure = Global.StringFalse;
} }
else else
{ {
+5 -2
View File
@@ -53,7 +53,9 @@ public class FmtHandler
{ {
return ShadowsocksFmt.Resolve(str, out msg); return ShadowsocksFmt.Resolve(str, out msg);
} }
else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS])) else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS])
|| str.StartsWith(Global.SOCKS5Protocol)
|| str.StartsWith(Global.SOCKS4Protocol))
{ {
return SocksFmt.Resolve(str, out msg); return SocksFmt.Resolve(str, out msg);
} }
@@ -65,7 +67,8 @@ public class FmtHandler
{ {
return VLESSFmt.Resolve(str, out msg); return VLESSFmt.Resolve(str, out msg);
} }
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2]) || str.StartsWith(Global.Hysteria2ProtocolShare)) else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2])
|| str.StartsWith(Global.Hysteria2ProtocolShare))
{ {
return Hysteria2Fmt.Resolve(str, out msg); return Hysteria2Fmt.Resolve(str, out msg);
} }
+1 -1
View File
@@ -185,7 +185,7 @@ public class InnerFmt
return null; return null;
} }
// Check Enum.IsDefined // Check Enum.IsDefined
if (!Enum.IsDefined(typeof(EConfigType), profileItem.ConfigType)) if (!Enum.IsDefined(profileItem.ConfigType))
{ {
return null; return null;
} }
+11 -6
View File
@@ -71,7 +71,7 @@ public class SocksFmt : BaseFmt
return null; return null;
} }
var arr21 = arr1.First().Split(':'); var arr21 = arr1.First().Split(':');
var indexPort = arr1.Last().LastIndexOf(":"); var indexPort = arr1.Last().LastIndexOf(':');
if (arr21.Length != 2 || indexPort < 0) if (arr21.Length != 2 || indexPort < 0)
{ {
return null; return null;
@@ -99,12 +99,17 @@ public class SocksFmt : BaseFmt
}; };
// parse base64 UserInfo // parse base64 UserInfo
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
var userInfo = Utils.Base64Decode(rawUserInfo); if (rawUserInfo.IsNotEmpty())
var userInfoParts = userInfo.Split([':'], 2);
if (userInfoParts.Length == 2)
{ {
item.Username = userInfoParts.First(); var userInfoParts = rawUserInfo.Contains(':')
item.Password = userInfoParts[1]; ? rawUserInfo.Split(":", 2)
: Utils.Base64Decode(rawUserInfo).Split(":", 2);
if (userInfoParts.Length == 2)
{
item.Username = userInfoParts.First();
item.Password = userInfoParts.Last();
}
} }
return item; return item;
+6 -2
View File
@@ -66,7 +66,9 @@ public class VmessFmt : BaseFmt
sni = item.Sni, sni = item.Sni,
alpn = item.Alpn, alpn = item.Alpn,
fp = item.Fingerprint, fp = item.Fingerprint,
insecure = item.AllowInsecure.Equals(Global.AllowInsecure.First()) ? "1" : "0" insecure = item.GetAllowInsecure() ? "1" : "0",
vcn = item.VerifyPeerCertByName,
pcs = item.CertSha
}; };
var url = JsonUtils.Serialize(vmessQRCode); var url = JsonUtils.Serialize(vmessQRCode);
@@ -140,7 +142,9 @@ public class VmessFmt : BaseFmt
item.Sni = Utils.ToString(vmessQRCode.sni); item.Sni = Utils.ToString(vmessQRCode.sni);
item.Alpn = Utils.ToString(vmessQRCode.alpn); item.Alpn = Utils.ToString(vmessQRCode.alpn);
item.Fingerprint = Utils.ToString(vmessQRCode.fp); item.Fingerprint = Utils.ToString(vmessQRCode.fp);
item.AllowInsecure = vmessQRCode.insecure == "1" ? Global.AllowInsecure.First() : string.Empty; item.AllowInsecure = vmessQRCode.insecure == "1" ? Global.StringTrue : string.Empty;
item.VerifyPeerCertByName = Utils.ToString(vmessQRCode.vcn);
item.CertSha = Utils.ToString(vmessQRCode.pcs);
return item; return item;
} }
@@ -133,12 +133,12 @@ public static class SubscriptionHandler
if (!url.Contains("target=")) if (!url.Contains("target="))
{ {
url += string.Format("&target={0}", item.ConvertTarget); url += $"&target={item.ConvertTarget}";
} }
if (!url.Contains("config=")) if (!url.Contains("config="))
{ {
url += string.Format("&config={0}", Global.SubConvertConfig.FirstOrDefault()); url += $"&config={Global.SubConvertConfig.FirstOrDefault()}";
} }
} }
+1 -1
View File
@@ -268,7 +268,7 @@ public sealed class AppManager
.ToListAsync(); .ToListAsync();
var itemMap = items.ToDictionary(it => it.IndexId); var itemMap = items.ToDictionary(it => it.IndexId);
return idList.Select(id => itemMap.GetValueOrDefault(id)) return idList.Select(itemMap.GetValueOrDefault)
.Where(item => item != null) .Where(item => item != null)
.ToList(); .ToList();
} }
+64 -55
View File
@@ -4,16 +4,15 @@ using System.Security.Cryptography.X509Certificates;
namespace ServiceLib.Manager; namespace ServiceLib.Manager;
/// <summary> /// <summary>
/// Manager for certificate operations with CA pinning to prevent MITM attacks /// Manager for certificate operations with CA pinning to prevent MITM attacks
/// </summary> /// </summary>
public class CertPemManager public class CertPemManager
{ {
private static readonly string _tag = "CertPemManager"; private static readonly string _tag = "CertPemManager";
private static readonly Lazy<CertPemManager> _instance = new(() => new()); private static readonly Lazy<CertPemManager> _instance = new(() => new());
public static CertPemManager Instance => _instance.Value;
/// <summary> /// <summary>
/// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks /// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks
/// </summary> /// </summary>
private static readonly HashSet<string> TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase)
{ {
@@ -24,21 +23,17 @@ public class CertPemManager
"D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root "D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root
"85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2 "85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2
"18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3 "18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3
"CECDDC905099D8DADFC5B1D209B737CBE2C18CFB2C10C0FF0BCF0D3286FC1AA2", // XRamp Global CA Root
"C3846BF24B9E93CA64274C0EC67C1ECC5E024FFCACD2D74019350E81FE546AE4", // Go Daddy Class 2 CA "C3846BF24B9E93CA64274C0EC67C1ECC5E024FFCACD2D74019350E81FE546AE4", // Go Daddy Class 2 CA
"1465FA205397B876FAA6F0A9958E5590E40FCC7FAA4FB7C2C8677521FB5FB658", // Starfield Class 2 CA "1465FA205397B876FAA6F0A9958E5590E40FCC7FAA4FB7C2C8677521FB5FB658", // Starfield Class 2 CA
"3E9099B5015E8F486C00BCEA9D111EE721FABA355A89BCF1DF69561E3DC6325C", // DigiCert Assured ID Root CA "3E9099B5015E8F486C00BCEA9D111EE721FABA355A89BCF1DF69561E3DC6325C", // DigiCert Assured ID Root CA
"4348A0E9444C78CB265E058D5E8944B4D84F9662BD26DB257F8934A443C70161", // DigiCert Global Root CA "4348A0E9444C78CB265E058D5E8944B4D84F9662BD26DB257F8934A443C70161", // DigiCert Global Root CA
"7431E5F4C3C1CE4690774F0B61E05440883BA9A01ED00BA6ABD7806ED3B118CF", // DigiCert High Assurance EV Root CA "7431E5F4C3C1CE4690774F0B61E05440883BA9A01ED00BA6ABD7806ED3B118CF", // DigiCert High Assurance EV Root CA
"62DD0BE9B9F50A163EA0F8E75C053B1ECA57EA55C8688F647C6881F2C8357B95", // SwissSign Gold CA - G2 "62DD0BE9B9F50A163EA0F8E75C053B1ECA57EA55C8688F647C6881F2C8357B95", // SwissSign Gold CA - G2
"F1C1B50AE5A20DD8030EC9F6BC24823DD367B5255759B4E71B61FCE9F7375D73", // SecureTrust CA
"4200F5043AC8590EBB527D209ED1503029FBCBD41CA1B506EC27F15ADE7DAC69", // Secure Global CA
"0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66", // COMODO Certification Authority "0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66", // COMODO Certification Authority
"1793927A0614549789ADCE2F8F34F7F0B66D0F3AE3A3B84D21EC15DBBA4FADC7", // COMODO ECC Certification Authority "1793927A0614549789ADCE2F8F34F7F0B66D0F3AE3A3B84D21EC15DBBA4FADC7", // COMODO ECC Certification Authority
"41C923866AB4CAD6B7AD578081582E020797A6CBDF4FFF78CE8396B38937D7F5", // OISTE WISeKey Global Root GA CA "41C923866AB4CAD6B7AD578081582E020797A6CBDF4FFF78CE8396B38937D7F5", // OISTE WISeKey Global Root GA CA
"E3B6A2DB2ED7CE48842F7AC53241C7B71D54144BFB40C11F3F1D0B42F5EEA12D", // Certigna "E3B6A2DB2ED7CE48842F7AC53241C7B71D54144BFB40C11F3F1D0B42F5EEA12D", // Certigna
"C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5", // ePKI Root Certification Authority "C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5", // ePKI Root Certification Authority
"EAA962C4FA4A6BAFEBE415196D351CCD888D4F53F3FA8AE6D7C466A94E6042BB", // certSIGN ROOT CA
"6C61DAC3A2DEF031506BE036D2A6FE401994FBD13DF9C8D466599274C446EC98", // NetLock Arany (Class Gold) Főtanúsítvány "6C61DAC3A2DEF031506BE036D2A6FE401994FBD13DF9C8D466599274C446EC98", // NetLock Arany (Class Gold) Főtanúsítvány
"3C5F81FEA5FAB82C64BFA2EAECAFCDE8E077FC8620A7CAE537163DF36EDBF378", // Microsec e-Szigno Root CA 2009 "3C5F81FEA5FAB82C64BFA2EAECAFCDE8E077FC8620A7CAE537163DF36EDBF378", // Microsec e-Szigno Root CA 2009
"CBB522D7B7F127AD6A0113865BDF1CD4102E7D0759AF635A7CF4720DC963C53B", // GlobalSign Root CA - R3 "CBB522D7B7F127AD6A0113865BDF1CD4102E7D0759AF635A7CF4720DC963C53B", // GlobalSign Root CA - R3
@@ -46,10 +41,6 @@ public class CertPemManager
"45140B3247EB9CC8C5B4F0D7B53091F73292089E6E5A63E2749DD3ACA9198EDA", // Go Daddy Root Certificate Authority - G2 "45140B3247EB9CC8C5B4F0D7B53091F73292089E6E5A63E2749DD3ACA9198EDA", // Go Daddy Root Certificate Authority - G2
"2CE1CB0BF9D2F9E102993FBE215152C3B2DD0CABDE1C68E5319B839154DBB7F5", // Starfield Root Certificate Authority - G2 "2CE1CB0BF9D2F9E102993FBE215152C3B2DD0CABDE1C68E5319B839154DBB7F5", // Starfield Root Certificate Authority - G2
"568D6905A2C88708A4B3025190EDCFEDB1974A606A13C6E5290FCB2AE63EDAB5", // Starfield Services Root Certificate Authority - G2 "568D6905A2C88708A4B3025190EDCFEDB1974A606A13C6E5290FCB2AE63EDAB5", // Starfield Services Root Certificate Authority - G2
"0376AB1D54C5F9803CE4B2E201A0EE7EEF7B57B636E8A93C9B8D4860C96F5FA7", // AffirmTrust Commercial
"0A81EC5A929777F145904AF38D5D509F66B5E2C58FCDB531058B0E17F3F0B41B", // AffirmTrust Networking
"70A73F7F376B60074248904534B11482D5BF0E698ECC498DF52577EBF2E93B9A", // AffirmTrust Premium
"BD71FDF6DA97E4CF62D1647ADD2581B07D79ADF8397EB4ECBA9C5E8488821423", // AffirmTrust Premium ECC
"5C58468D55F58E497E743982D2B50010B6D165374ACF83A7D4A32DB768C4408E", // Certum Trusted Network CA "5C58468D55F58E497E743982D2B50010B6D165374ACF83A7D4A32DB768C4408E", // Certum Trusted Network CA
"BFD88FE1101C41AE3E801BF8BE56350EE9BAD1A6B9BD515EDC5C6D5B8711AC44", // TWCA Root Certification Authority "BFD88FE1101C41AE3E801BF8BE56350EE9BAD1A6B9BD515EDC5C6D5B8711AC44", // TWCA Root Certification Authority
"513B2CECB810D4CDE5DD85391ADFC6C2DD60D87BB736D2B521484AA47A0EBEF6", // Security Communication RootCA2 "513B2CECB810D4CDE5DD85391ADFC6C2DD60D87BB736D2B521484AA47A0EBEF6", // Security Communication RootCA2
@@ -62,7 +53,6 @@ public class CertPemManager
"E23D4A036D7B70E9F595B1422079D2B91EDFBB1FB651A0633EAA8A9DC5F80703", // CA Disig Root R2 "E23D4A036D7B70E9F595B1422079D2B91EDFBB1FB651A0633EAA8A9DC5F80703", // CA Disig Root R2
"9A6EC012E1A7DA9DBE34194D478AD7C0DB1822FB071DF12981496ED104384113", // ACCVRAIZ1 "9A6EC012E1A7DA9DBE34194D478AD7C0DB1822FB071DF12981496ED104384113", // ACCVRAIZ1
"59769007F7685D0FCD50872F9F95D5755A5B2B457D81F3692B610A98672F0E1B", // TWCA Global Root CA "59769007F7685D0FCD50872F9F95D5755A5B2B457D81F3692B610A98672F0E1B", // TWCA Global Root CA
"DD6936FE21F8F077C123A1A521C12224F72255B73E03A7260693E8A24B0FA389", // TeliaSonera Root CA v1
"91E2F5788D5810EBA7BA58737DE1548A8ECACD014598BC0B143E041B17052552", // T-TeleSec GlobalRoot Class 2 "91E2F5788D5810EBA7BA58737DE1548A8ECACD014598BC0B143E041B17052552", // T-TeleSec GlobalRoot Class 2
"F356BEA244B7A91EB35D53CA9AD7864ACE018E2D35D5F8F96DDF68A6F41AA474", // Atos TrustedRoot 2011 "F356BEA244B7A91EB35D53CA9AD7864ACE018E2D35D5F8F96DDF68A6F41AA474", // Atos TrustedRoot 2011
"8A866FD1B276B57E578E921C65828A2BED58E9F2F288054134B7F1F4BFC9CC74", // QuoVadis Root CA 1 G3 "8A866FD1B276B57E578E921C65828A2BED58E9F2F288054134B7F1F4BFC9CC74", // QuoVadis Root CA 1 G3
@@ -116,16 +106,12 @@ public class CertPemManager
"C741F70F4B2A8D88BF2E71C14122EF53EF10EBA0CFA5E64CFA20F418853073E0", // Microsoft RSA Root Certificate Authority 2017 "C741F70F4B2A8D88BF2E71C14122EF53EF10EBA0CFA5E64CFA20F418853073E0", // Microsoft RSA Root Certificate Authority 2017
"BEB00B30839B9BC32C32E4447905950641F26421B15ED089198B518AE2EA1B99", // e-Szigno Root CA 2017 "BEB00B30839B9BC32C32E4447905950641F26421B15ED089198B518AE2EA1B99", // e-Szigno Root CA 2017
"657CFE2FA73FAA38462571F332A2363A46FCE7020951710702CDFBB6EEDA3305", // certSIGN Root CA G2 "657CFE2FA73FAA38462571F332A2363A46FCE7020951710702CDFBB6EEDA3305", // certSIGN Root CA G2
"97552015F5DDFC3C8788C006944555408894450084F100867086BC1A2BB58DC8", // Trustwave Global Certification Authority
"945BBC825EA554F489D1FD51A73DDF2EA624AC7019A05205225C22A78CCFA8B4", // Trustwave Global ECC P256 Certification Authority
"55903859C8C0C3EBB8759ECE4E2557225FF5758BBD38EBD48276601E1BD58097", // Trustwave Global ECC P384 Certification Authority
"88F438DCF8FFD1FA8F429115FFE5F82AE1E06E0C70C375FAAD717B34A49E7265", // NAVER Global Root Certification Authority "88F438DCF8FFD1FA8F429115FFE5F82AE1E06E0C70C375FAAD717B34A49E7265", // NAVER Global Root Certification Authority
"554153B13D2CF9DDB753BFBE1A4E0AE08D0AA4187058FE60A2B862B2E4B87BCB", // AC RAIZ FNMT-RCM SERVIDORES SEGUROS "554153B13D2CF9DDB753BFBE1A4E0AE08D0AA4187058FE60A2B862B2E4B87BCB", // AC RAIZ FNMT-RCM SERVIDORES SEGUROS
"319AF0A7729E6F89269C131EA6A3A16FCD86389FDCAB3C47A4A675C161A3F974", // GlobalSign Secure Mail Root R45 "319AF0A7729E6F89269C131EA6A3A16FCD86389FDCAB3C47A4A675C161A3F974", // GlobalSign Secure Mail Root R45
"5CBF6FB81FD417EA4128CD6F8172A3C9402094F74AB2ED3A06B4405D04F30B19", // GlobalSign Secure Mail Root E45 "5CBF6FB81FD417EA4128CD6F8172A3C9402094F74AB2ED3A06B4405D04F30B19", // GlobalSign Secure Mail Root E45
"4FA3126D8D3A11D1C4855A4F807CBAD6CF919D3A5A88B03BEA2C6372D93C40C9", // GlobalSign Root R46 "4FA3126D8D3A11D1C4855A4F807CBAD6CF919D3A5A88B03BEA2C6372D93C40C9", // GlobalSign Root R46
"CBB9C44D84B8043E1050EA31A69F514955D7BFD2E2C6B49301019AD61D9F5058", // GlobalSign Root E46 "CBB9C44D84B8043E1050EA31A69F514955D7BFD2E2C6B49301019AD61D9F5058", // GlobalSign Root E46
"9A296A5182D1D451A2E37F439B74DAAFA267523329F90F9A0D2007C334E23C9A", // GLOBALTRUST 2020
"FB8FEC759169B9106B1E511644C618C51304373F6C0643088D8BEFFD1B997599", // ANF Secure Server Root CA "FB8FEC759169B9106B1E511644C618C51304373F6C0643088D8BEFFD1B997599", // ANF Secure Server Root CA
"6B328085625318AA50D173C98D8BDA09D57E27413D114CF787A0F5D06C030CF6", // Certum EC-384 CA "6B328085625318AA50D173C98D8BDA09D57E27413D114CF787A0F5D06C030CF6", // Certum EC-384 CA
"FE7696573855773E37A95E7AD4D9CC96C30157C15D31765BA9B15704E1AE78FD", // Certum Trusted Root CA "FE7696573855773E37A95E7AD4D9CC96C30157C15D31765BA9B15704E1AE78FD", // Certum Trusted Root CA
@@ -179,7 +165,6 @@ public class CertPemManager
"578AF4DED0853F4E5998DB4AEAF9CBEA8D945F60B620A38D1A3C13B2BC7BA8E1", // Telekom Security TLS ECC Root 2020 "578AF4DED0853F4E5998DB4AEAF9CBEA8D945F60B620A38D1A3C13B2BC7BA8E1", // Telekom Security TLS ECC Root 2020
"78A656344F947E9CC0F734D9053D32F6742086B6B9CD2CAE4FAE1A2E4EFDE048", // Telekom Security SMIME RSA Root 2023 "78A656344F947E9CC0F734D9053D32F6742086B6B9CD2CAE4FAE1A2E4EFDE048", // Telekom Security SMIME RSA Root 2023
"EFC65CADBB59ADB6EFE84DA22311B35624B71B3B1EA0DA8B6655174EC8978646", // Telekom Security TLS RSA Root 2023 "EFC65CADBB59ADB6EFE84DA22311B35624B71B3B1EA0DA8B6655174EC8978646", // Telekom Security TLS RSA Root 2023
"BEF256DAF26E9C69BDEC1602359798F3CAF71821A03E018257C53C65617F3D4A", // FIRMAPROFESIONAL CA ROOT-A WEB
"3F63BB2814BE174EC8B6439CF08D6D56F0B7C405883A5648A334424D6B3EC558", // TWCA CYBER Root CA "3F63BB2814BE174EC8B6439CF08D6D56F0B7C405883A5648A334424D6B3EC558", // TWCA CYBER Root CA
"3A0072D49FFC04E996C59AEB75991D3C340F3615D6FD4DCE90AC0B3D88EAD4F4", // TWCA Global Root CA G2 "3A0072D49FFC04E996C59AEB75991D3C340F3615D6FD4DCE90AC0B3D88EAD4F4", // TWCA Global Root CA G2
"3F034BB5704D44B2D08545A02057DE93EBF3905FCE721ACBC730C06DDAEE904E", // SecureSign Root CA12 "3F034BB5704D44B2D08545A02057DE93EBF3905FCE721ACBC730C06DDAEE904E", // SecureSign Root CA12
@@ -200,10 +185,13 @@ public class CertPemManager
"B49141502D00663D740F2E7EC340C52800962666121A36D09CF7DD2B90384FB4", // e-Szigno TLS Root CA 2023 "B49141502D00663D740F2E7EC340C52800962666121A36D09CF7DD2B90384FB4", // e-Szigno TLS Root CA 2023
}; };
public static CertPemManager Instance => _instance.Value;
/// <summary> /// <summary>
/// Get certificate in PEM format from a server with CA pinning validation /// Get certificate in PEM format from a server with CA pinning validation
/// </summary> /// </summary>
public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4, bool allowInsecure = false) public async Task<(string?, string?)> GetCertPemAsync(string target, string serverName, int timeout = 4,
List<string>? verifyPeerCertByName = null, bool allowInsecure = false)
{ {
try try
{ {
@@ -216,13 +204,14 @@ public class CertPemManager
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) =>
ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, allowInsecure)); ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, verifyPeerCertByName ?? [],
allowInsecure));
await using var ssl = new SslStream(client.GetStream(), false, callback); await using var ssl = new SslStream(client.GetStream(), false, callback);
var sslOptions = new SslClientAuthenticationOptions var sslOptions = new SslClientAuthenticationOptions
{ {
TargetHost = serverName, TargetHost = serverName,
RemoteCertificateValidationCallback = callback RemoteCertificateValidationCallback = callback,
}; };
await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token); await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
@@ -249,9 +238,10 @@ public class CertPemManager
} }
/// <summary> /// <summary>
/// Get certificate chain in PEM format from a server with CA pinning validation /// Get certificate chain in PEM format from a server with CA pinning validation
/// </summary> /// </summary>
public async Task<(List<string>, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4, bool allowInsecure = false) public async Task<(List<string>, string?)> GetCertChainPemAsync(string target, string serverName, int timeout = 4,
List<string>? verifyPeerCertByName = null, bool allowInsecure = false)
{ {
var pemList = new List<string>(); var pemList = new List<string>();
try try
@@ -265,13 +255,14 @@ public class CertPemManager
await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token);
var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => var callback = new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) =>
ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, allowInsecure)); ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors, verifyPeerCertByName ?? [],
allowInsecure));
await using var ssl = new SslStream(client.GetStream(), false, callback); await using var ssl = new SslStream(client.GetStream(), false, callback);
var sslOptions = new SslClientAuthenticationOptions var sslOptions = new SslClientAuthenticationOptions
{ {
TargetHost = serverName, TargetHost = serverName,
RemoteCertificateValidationCallback = callback RemoteCertificateValidationCallback = callback,
}; };
await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token); await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
@@ -301,13 +292,14 @@ public class CertPemManager
} }
/// <summary> /// <summary>
/// Validate server certificate with CA pinning /// Validate server certificate with CA pinning
/// </summary> /// </summary>
private bool ValidateServerCertificate( private static bool ValidateServerCertificate(
object _, object _,
X509Certificate? certificate, X509Certificate? certificate,
X509Chain? chain, X509Chain? chain,
SslPolicyErrors sslPolicyErrors, SslPolicyErrors sslPolicyErrors,
List<string> verifyPeerCertByName,
bool allowInsecure) bool allowInsecure)
{ {
if (certificate == null) if (certificate == null)
@@ -321,22 +313,21 @@ public class CertPemManager
return true; return true;
} }
// Check certificate name mismatch
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
{
return false;
}
// Build certificate chain // Build certificate chain
var cert2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate); var cert2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate);
var certChain = chain ?? new X509Chain(); var certChain = chain ?? new X509Chain();
certChain.ChainPolicy.RevocationMode = X509RevocationMode.Online; if (chain == null)
certChain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; {
certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
certChain.ChainPolicy.VerificationTime = DateTime.Now; certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
certChain.ChainPolicy.VerificationTime = DateTime.UtcNow;
certChain.Build(cert2); if (!certChain.Build(cert2))
{
return false;
}
}
// Find root CA // Find root CA
if (certChain.ChainElements.Count == 0) if (certChain.ChainElements.Count == 0)
@@ -344,10 +335,37 @@ public class CertPemManager
return false; return false;
} }
var rootCert = certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate; var rootCert = certChain.ChainElements[^1].Certificate;
var rootThumbprint = rootCert.GetCertHashString(HashAlgorithmName.SHA256); var rootThumbprint = rootCert.GetCertHashString(HashAlgorithmName.SHA256);
return TrustedCaThumbprints.Contains(rootThumbprint); if (!TrustedCaThumbprints.Contains(rootThumbprint))
{
return false;
}
if (!sslPolicyErrors.HasFlag(
SslPolicyErrors.RemoteCertificateNameMismatch))
{
return true;
}
if (verifyPeerCertByName.Count == 0)
{
return false;
}
foreach (var ext in cert2.Extensions)
{
if (ext is not X509SubjectAlternativeNameExtension san)
{
continue;
}
return san.EnumerateDnsNames().Any(dnsName =>
verifyPeerCertByName.Contains(dnsName, StringComparer.OrdinalIgnoreCase));
}
return false;
} }
public static string ExportCertToPem(X509Certificate2 cert) public static string ExportCertToPem(X509Certificate2 cert)
@@ -358,8 +376,8 @@ public class CertPemManager
} }
/// <summary> /// <summary>
/// Parse concatenated PEM certificates string into a list of individual certificates /// Parse concatenated PEM certificates string into a list of individual certificates
/// Normalizes format: removes line breaks from base64 content for better compatibility /// Normalizes format: removes line breaks from base64 content for better compatibility
/// </summary> /// </summary>
/// <param name="pemChain">Concatenated PEM certificates string (supports both \r\n and \n line endings)</param> /// <param name="pemChain">Concatenated PEM certificates string (supports both \r\n and \n line endings)</param>
/// <returns>List of individual PEM certificate strings with normalized format</returns> /// <returns>List of individual PEM certificate strings with normalized format</returns>
@@ -411,18 +429,13 @@ public class CertPemManager
} }
/// <summary> /// <summary>
/// Concatenate a list of PEM certificates into a single string /// Concatenate a list of PEM certificates into a single string
/// </summary> /// </summary>
/// <param name="pemList">List of individual PEM certificate strings</param> /// <param name="pemList">List of individual PEM certificate strings</param>
/// <returns>Concatenated PEM certificates string</returns> /// <returns>Concatenated PEM certificates string</returns>
public static string ConcatenatePemChain(IEnumerable<string> pemList) public static string ConcatenatePemChain(IEnumerable<string>? pemList)
{ {
if (pemList == null) return pemList == null ? string.Empty : string.Concat(pemList);
{
return string.Empty;
}
return string.Concat(pemList);
} }
public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = false) public static string GetCertSha256Thumbprint(string pemCert, bool includeColon = false)
@@ -431,11 +444,7 @@ public class CertPemManager
{ {
var cert = X509Certificate2.CreateFromPem(pemCert); var cert = X509Certificate2.CreateFromPem(pemCert);
var thumbprint = cert.GetCertHashString(HashAlgorithmName.SHA256); var thumbprint = cert.GetCertHashString(HashAlgorithmName.SHA256);
if (includeColon) return includeColon ? string.Join(":", thumbprint.Chunk(2).Select(c => new string(c))) : thumbprint;
{
return string.Join(":", thumbprint.Chunk(2).Select(c => new string(c)));
}
return thumbprint;
} }
catch catch
{ {
+8 -13
View File
@@ -45,19 +45,14 @@ public sealed class ClashApiManager
{ {
await GetClashProxiesAsync(); await GetClashProxiesAsync();
} }
lstProxy = new List<ClashProxyModel>(); lstProxy = [];
foreach (var kv in _proxies ?? []) lstProxy.AddRange(from kv in _proxies ?? []
{ where !Global.notAllowTestType.Contains(kv.Value.type?.ToLower())
if (Global.notAllowTestType.Contains(kv.Value.type?.ToLower())) select new ClashProxyModel()
{ {
continue; Name = kv.Value.name,
} Type = kv.Value.type?.ToLower(),
lstProxy.Add(new ClashProxyModel() });
{
Name = kv.Value.name,
Type = kv.Value.type?.ToLower(),
});
}
} }
if (lstProxy is not { Count: > 0 }) if (lstProxy is not { Count: > 0 })
@@ -113,6 +113,7 @@ public sealed class CoreInfoManager
DownloadUrlLinux64 = urlN + "/download/{0}/v2rayN-linux-64.zip", DownloadUrlLinux64 = urlN + "/download/{0}/v2rayN-linux-64.zip",
DownloadUrlLinuxArm64 = urlN + "/download/{0}/v2rayN-linux-arm64.zip", DownloadUrlLinuxArm64 = urlN + "/download/{0}/v2rayN-linux-arm64.zip",
DownloadUrlLinuxRiscV64 = urlN + "/download/{0}/v2rayN-linux-riscv64.zip", DownloadUrlLinuxRiscV64 = urlN + "/download/{0}/v2rayN-linux-riscv64.zip",
DownloadUrlLinuxLoong64 = urlN + "/download/{0}/v2rayN-linux-loong64.zip",
DownloadUrlOSX64 = urlN + "/download/{0}/v2rayN-macos-64.zip", DownloadUrlOSX64 = urlN + "/download/{0}/v2rayN-macos-64.zip",
DownloadUrlOSXArm64 = urlN + "/download/{0}/v2rayN-macos-arm64.zip", DownloadUrlOSXArm64 = urlN + "/download/{0}/v2rayN-macos-arm64.zip",
}, },
@@ -157,6 +158,7 @@ public sealed class CoreInfoManager
DownloadUrlLinux64 = urlXray + "/download/{0}/Xray-linux-64.zip", DownloadUrlLinux64 = urlXray + "/download/{0}/Xray-linux-64.zip",
DownloadUrlLinuxArm64 = urlXray + "/download/{0}/Xray-linux-arm64-v8a.zip", DownloadUrlLinuxArm64 = urlXray + "/download/{0}/Xray-linux-arm64-v8a.zip",
DownloadUrlLinuxRiscV64 = urlXray + "/download/{0}/Xray-linux-riscv64.zip", DownloadUrlLinuxRiscV64 = urlXray + "/download/{0}/Xray-linux-riscv64.zip",
DownloadUrlLinuxLoong64 = urlXray + "/download/{0}/Xray-linux-loong64.zip",
DownloadUrlOSX64 = urlXray + "/download/{0}/Xray-macos-64.zip", DownloadUrlOSX64 = urlXray + "/download/{0}/Xray-macos-64.zip",
DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip", DownloadUrlOSXArm64 = urlXray + "/download/{0}/Xray-macos-arm64-v8a.zip",
Match = "Xray", Match = "Xray",
@@ -180,6 +182,7 @@ public sealed class CoreInfoManager
DownloadUrlLinux64 = urlMihomo + "/download/{0}/mihomo-linux-amd64-v1-{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",
DownloadUrlLinuxRiscV64 = urlMihomo + "/download/{0}/mihomo-linux-riscv64-{0}.gz", DownloadUrlLinuxRiscV64 = urlMihomo + "/download/{0}/mihomo-linux-riscv64-{0}.gz",
DownloadUrlLinuxLoong64 = urlMihomo + "/download/{0}/mihomo-linux-loong64-abi2-{0}.gz",
DownloadUrlOSX64 = urlMihomo + "/download/{0}/mihomo-darwin-amd64-v1-{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",
@@ -223,6 +226,7 @@ public sealed class CoreInfoManager
DownloadUrlLinux64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-amd64.tar.gz", DownloadUrlLinux64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-amd64.tar.gz",
DownloadUrlLinuxArm64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-arm64.tar.gz", DownloadUrlLinuxArm64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-arm64.tar.gz",
DownloadUrlLinuxRiscV64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-riscv64.tar.gz", DownloadUrlLinuxRiscV64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-riscv64.tar.gz",
DownloadUrlLinuxLoong64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-loong64.tar.gz",
DownloadUrlOSX64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-amd64.tar.gz", DownloadUrlOSX64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-amd64.tar.gz",
DownloadUrlOSXArm64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-arm64.tar.gz", DownloadUrlOSXArm64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-arm64.tar.gz",
Match = "sing-box", Match = "sing-box",
@@ -314,6 +318,7 @@ public sealed class CoreInfoManager
names.Add("mihomo-linux-amd64"); names.Add("mihomo-linux-amd64");
names.Add("mihomo-linux-arm64"); names.Add("mihomo-linux-arm64");
names.Add("mihomo-linux-riscv64"); names.Add("mihomo-linux-riscv64");
names.Add("mihomo-linux-loong64-abi2");
} }
else if (Utils.IsMacOS()) else if (Utils.IsMacOS())
{ {
@@ -9,7 +9,7 @@ public class GroupProfileManager
public static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo) public static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo)
{ {
return await HasCycle(indexId, extraInfo, new HashSet<string>(), new HashSet<string>()); return await HasCycle(indexId, extraInfo, [], []);
} }
private static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo, HashSet<string> visited, HashSet<string> stack) private static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo, HashSet<string> visited, HashSet<string> stack)
@@ -55,7 +55,7 @@ public class GroupProfileManager
var childItems = await AppManager.Instance.GetProfileItemsByIndexIds(childIds); var childItems = await AppManager.Instance.GetProfileItemsByIndexIds(childIds);
foreach (var childItem in childItems) foreach (var childItem in childItems)
{ {
if (await HasCycle(childItem.IndexId, childItem?.GetProtocolExtra(), visited, stack)) if (await HasCycle(childItem.IndexId, childItem.GetProtocolExtra(), visited, stack))
{ {
return true; return true;
} }
@@ -71,7 +71,7 @@ public class GroupProfileManager
public static async Task<(List<ProfileItem> Items, ProtocolExtraItem? Extra)> GetChildProfileItems(ProfileItem profileItem) public static async Task<(List<ProfileItem> Items, ProtocolExtraItem? Extra)> GetChildProfileItems(ProfileItem profileItem)
{ {
var protocolExtra = profileItem?.GetProtocolExtra(); var protocolExtra = profileItem.GetProtocolExtra();
return (await GetChildProfileItemsByProtocolExtra(protocolExtra), protocolExtra); return (await GetChildProfileItemsByProtocolExtra(protocolExtra), protocolExtra);
} }
+10 -2
View File
@@ -111,7 +111,7 @@ public class ProfileExManager
public async Task ClearAll() public async Task ClearAll()
{ {
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem "); await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileExItem ");
_lstProfileEx = new(); _lstProfileEx = [];
} }
public async Task SaveTo() public async Task SaveTo()
@@ -150,6 +150,14 @@ public class ProfileExManager
IndexIdEnqueue(indexId); IndexIdEnqueue(indexId);
} }
public void SetTestIpInfo(string indexId, string ipInfo)
{
var profileEx = GetProfileExItem(indexId);
profileEx.IpInfo = ipInfo;
IndexIdEnqueue(indexId);
}
public void SetSort(string indexId, int sort) public void SetSort(string indexId, int sort)
{ {
var profileEx = GetProfileExItem(indexId); var profileEx = GetProfileExItem(indexId);
@@ -174,6 +182,6 @@ public class ProfileExManager
{ {
return 0; return 0;
} }
return _lstProfileEx.Max(t => t == null ? 0 : t.Sort); return _lstProfileEx.Max(t => t?.Sort ?? 0);
} }
} }
@@ -45,7 +45,7 @@ public class StatisticsManager
{ {
await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem "); await SQLiteHelper.Instance.ExecuteAsync($"delete from ServerStatItem ");
_serverStatItem = null; _serverStatItem = null;
_lstServerStat = new(); _lstServerStat = [];
} }
public async Task SaveTo() public async Task SaveTo()
+6 -1
View File
@@ -56,7 +56,7 @@ public class TaskManager
{ {
//Logging.SaveLog("Execute delete expired files"); //Logging.SaveLog("Execute delete expired files");
FileUtils.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1)); FileUtils.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1), "Test");
FileUtils.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1)); FileUtils.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
FileUtils.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1)); FileUtils.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
@@ -142,5 +142,10 @@ public class TaskManager
await _updateFunc?.Invoke(false, msg); await _updateFunc?.Invoke(false, msg);
} }
NoticeManager.Instance.Enqueue(string.Join("\n", msgs)); NoticeManager.Instance.Enqueue(string.Join("\n", msgs));
if (msgs.Count > 0)
{
AppEvents.HasUpdateNotified.Publish(true);
}
} }
} }
@@ -7,10 +7,6 @@ public class CoreBasicItem
public string Loglevel { get; set; } public string Loglevel { get; set; }
public bool MuxEnabled { get; set; }
public bool DefAllowInsecure { get; set; }
public string DefFingerprint { get; set; } public string DefFingerprint { get; set; }
public string DefUserAgent { get; set; } public string DefUserAgent { get; set; }
@@ -21,6 +17,8 @@ public class CoreBasicItem
public bool EnableFragment { get; set; } public bool EnableFragment { get; set; }
public bool EnableFinalFragment { get; set; }
public bool EnableCacheFile4Sbox { get; set; } = true; public bool EnableCacheFile4Sbox { get; set; } = true;
} }
@@ -148,6 +146,7 @@ public class TunModeItem
public bool EnableIPv6Address { get; set; } public bool EnableIPv6Address { get; set; }
public string IcmpRouting { get; set; } public string IcmpRouting { get; set; }
public bool EnableLegacyProtect { get; set; } public bool EnableLegacyProtect { get; set; }
public List<string>? RouteExcludeAddress { get; set; }
} }
[Serializable] [Serializable]
@@ -159,6 +158,8 @@ public class SpeedTestItem
public int MixedConcurrencyCount { get; set; } public int MixedConcurrencyCount { get; set; }
public string IPAPIUrl { get; set; } public string IPAPIUrl { get; set; }
public string UdpTestTarget { get; set; } public string UdpTestTarget { get; set; }
public int? SpeedTestPageSize { get; set; }
public int? SpeedTestDelayInterval { get; set; }
} }
[Serializable] [Serializable]
@@ -13,6 +13,7 @@ public class CoreInfo
public string? DownloadUrlLinux64 { get; set; } public string? DownloadUrlLinux64 { get; set; }
public string? DownloadUrlLinuxArm64 { get; set; } public string? DownloadUrlLinuxArm64 { get; set; }
public string? DownloadUrlLinuxRiscV64 { get; set; } public string? DownloadUrlLinuxRiscV64 { get; set; }
public string? DownloadUrlLinuxLoong64 { get; set; }
public string? DownloadUrlOSX64 { get; set; } public string? DownloadUrlOSX64 { get; set; }
public string? DownloadUrlOSXArm64 { get; set; } public string? DownloadUrlOSXArm64 { get; set; }
public string? Match { get; set; } public string? Match { get; set; }
@@ -93,6 +93,7 @@ public class Rule4Sbox
public List<string>? wifi_bssid { 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_match_source { get; set; }
public bool? rule_set_ip_cidr_accept_empty { get; set; } public bool? rule_set_ip_cidr_accept_empty { get; set; }
public bool? tls_record_fragment { get; set; }
} }
[Serializable] [Serializable]
@@ -110,6 +111,7 @@ public class Inbound4Sbox
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 List<User4Sbox> users { get; set; } public List<User4Sbox> users { get; set; }
public List<string>? route_exclude_address { get; set; }
} }
public class User4Sbox public class User4Sbox
@@ -20,7 +20,7 @@ public class Stats4Ray
public class Metrics4Ray public class Metrics4Ray
{ {
public string tag { get; set; } public string listen { get; set; }
} }
public class Policy4Ray public class Policy4Ray
@@ -86,7 +86,7 @@ public class Inboundsettings4Ray
public string? autoOutboundsInterface { get; set; } public string? autoOutboundsInterface { get; set; }
// public List<string>? dns { get; set; } public List<string>? dns { get; set; }
} }
public class UsersItem4Ray public class UsersItem4Ray
@@ -136,7 +136,7 @@ public class Outboundsettings4Ray
public Response4Ray? response { get; set; } public Response4Ray? response { get; set; }
public string domainStrategy { get; set; } public string? domainStrategy { get; set; }
public int? userLevel { get; set; } public int? userLevel { get; set; }
@@ -276,6 +276,7 @@ public class BalancersItem4Ray
public List<string>? selector { get; set; } public List<string>? selector { get; set; }
public BalancersStrategy4Ray? strategy { get; set; } public BalancersStrategy4Ray? strategy { get; set; }
public string? tag { get; set; } public string? tag { get; set; }
public string? fallbackTag { get; set; }
} }
public class BalancersStrategy4Ray public class BalancersStrategy4Ray
@@ -372,6 +373,7 @@ public class TlsSettings4Ray
public string? spiderX { get; set; } public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; } public string? mldsa65Verify { get; set; }
public List<CertificateSettings4Ray>? certificates { get; set; } public List<CertificateSettings4Ray>? certificates { get; set; }
public string? verifyPeerCertByName { get; set; }
public string? pinnedPeerCertSha256 { get; set; } public string? pinnedPeerCertSha256 { get; set; }
public bool? disableSystemRoot { get; set; } public bool? disableSystemRoot { get; set; }
public string? echConfigList { get; set; } public string? echConfigList { get; set; }
@@ -487,6 +489,7 @@ public class UdpHop4Ray
public class Finalmask4Ray public class Finalmask4Ray
{ {
public List<Mask4Ray>? tcp { get; set; }
public List<Mask4Ray>? udp { get; set; } public List<Mask4Ray>? udp { get; set; }
public QuicParams4Ray? quicParams { get; set; } public QuicParams4Ray? quicParams { get; set; }
} }
@@ -499,8 +502,10 @@ public class Mask4Ray
public class MaskSettings4Ray public class MaskSettings4Ray
{ {
public string? header { get; set; }
public string? value { get; set; }
public string? password { get; set; } public string? password { get; set; }
public string? domain { get; set; }
// fragment // fragment
public string? packets { get; set; } public string? packets { get; set; }
@@ -17,3 +17,12 @@ public class LocationInfo
{ {
public string? country_code { get; set; } public string? country_code { get; set; }
} }
public readonly record struct IpInfoResult(string Country, string? Ip)
{
public override string ToString()
{
var emoji = Utils.IsWindows() ? null : Country.CountryToEmoji();
return $"{emoji}({Country}) {Ip}";
}
}
@@ -26,6 +26,9 @@ public class ProfileItemModel : ReactiveObject
[Reactive] [Reactive]
public string SpeedVal { get; set; } public string SpeedVal { get; set; }
[Reactive]
public string IpInfo { get; set; }
[Reactive] [Reactive]
public string TodayUp { get; set; } public string TodayUp { get; set; }
+6 -16
View File
@@ -1,26 +1,16 @@
namespace ServiceLib.Models.Dto; namespace ServiceLib.Models.Dto;
public class RetResult public class RetResult(bool success, string? msg, object? data)
{ {
public bool Success { get; set; } public RetResult(bool success = false) : this(success, null, null)
public string? Msg { get; set; }
public object? Data { get; set; }
public RetResult(bool success = false)
{ {
Success = success;
} }
public RetResult(bool success, string? msg) public RetResult(bool success, string? msg) : this(success, msg, null)
{ {
Success = success;
Msg = msg;
} }
public RetResult(bool success, string? msg, object? data) public bool Success { get; set; } = success;
{ public string? Msg { get; set; } = msg;
Success = success; public object? Data { get; set; } = data;
Msg = msg;
Data = data;
}
} }
@@ -8,4 +8,6 @@ public class SpeedTestResult
public string? Delay { get; set; } public string? Delay { get; set; }
public string? Speed { get; set; } public string? Speed { get; set; }
public string? IpInfo { get; set; }
} }
@@ -40,4 +40,8 @@ public class VmessQRCode
public string fp { get; set; } = string.Empty; public string fp { get; set; } = string.Empty;
public string insecure { get; set; } = string.Empty; public string insecure { get; set; } = string.Empty;
public string vcn { get; set; } = string.Empty;
public string pcs { get; set; } = string.Empty;
} }
@@ -10,4 +10,5 @@ public class ProfileExItem
public decimal Speed { get; set; } public decimal Speed { get; set; }
public int Sort { get; set; } public int Sort { get; set; }
public string? Message { get; set; } public string? Message { get; set; }
public string? IpInfo { get; set; }
} }
@@ -146,6 +146,11 @@ public class ProfileItem
TransportExtra = JsonUtils.Serialize(transportExtra, false); TransportExtra = JsonUtils.Serialize(transportExtra, false);
} }
public bool GetAllowInsecure()
{
return AllowInsecure == Global.StringTrue;
}
#endregion function #endregion function
[PrimaryKey] [PrimaryKey]
@@ -191,6 +196,7 @@ public class ProfileItem
public string Cert { get; set; } public string Cert { get; set; }
public string CertSha { get; set; } public string CertSha { get; set; }
public string EchConfigList { get; set; } public string EchConfigList { get; set; }
public string VerifyPeerCertByName { get; set; }
public string Finalmask { get; set; } public string Finalmask { get; set; }
public string ProtoExtra { get; set; } public string ProtoExtra { get; set; }
+90
View File
@@ -564,6 +564,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 IP Info 的本地化字符串。
/// </summary>
public static string LvTestIpInfo {
get {
return ResourceManager.GetString("LvTestIpInfo", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Speed (MB/s) 的本地化字符串。 /// 查找类似 Speed (MB/s) 的本地化字符串。
/// </summary> /// </summary>
@@ -1311,6 +1320,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 New Update 的本地化字符串。
/// </summary>
public static string menuNewUpdate {
get {
return ResourceManager.GetString("menuNewUpdate", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Open the storage location 的本地化字符串。 /// 查找类似 Open the storage location 的本地化字符串。
/// </summary> /// </summary>
@@ -1896,6 +1914,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Warning: Xray will disable allowInsecure (skip certificate verification) in August 2026. Please switch to pinnedPeerCertSha256 (fixed certificate fingerprint) as soon as possible. allowInsecure will not be usable after its expiration. 的本地化字符串。
/// </summary>
public static string MsgAllowInsecureDeprecated {
get {
return ResourceManager.GetString("MsgAllowInsecureDeprecated", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 {0} has a new version available: {1} 的本地化字符串。 /// 查找类似 {0} has a new version available: {1} 的本地化字符串。
/// </summary> /// </summary>
@@ -2085,6 +2112,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Conflict between {0} and {1} 的本地化字符串。
/// </summary>
public static string MsgOptionsConflict {
get {
return ResourceManager.GetString("MsgOptionsConflict", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Resolved {0} successfully 的本地化字符串。 /// 查找类似 Resolved {0} successfully 的本地化字符串。
/// </summary> /// </summary>
@@ -2202,6 +2238,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Invalid address in TUN route exclude list: {0} 的本地化字符串。
/// </summary>
public static string MsgTunRouteExcludeInvalidAddress {
get {
return ResourceManager.GetString("MsgTunRouteExcludeInvalidAddress", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Unpacking... 的本地化字符串。 /// 查找类似 Unpacking... 的本地化字符串。
/// </summary> /// </summary>
@@ -2988,6 +3033,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Enable Final Fragment 的本地化字符串。
/// </summary>
public static string TbEnableFinalFragment {
get {
return ResourceManager.GetString("TbEnableFinalFragment", resourceCulture);
}
}
/// <summary>
/// 查找类似 Split the tail of packets into smaller fragments. This may affect throughput and latency. 的本地化字符串。
/// </summary>
public static string TbEnableFinalFragmentTip {
get {
return ResourceManager.GetString("TbEnableFinalFragmentTip", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Enable Tun 的本地化字符串。 /// 查找类似 Enable Tun 的本地化字符串。
/// </summary> /// </summary>
@@ -3510,6 +3573,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Route Exclude Address 的本地化字符串。
/// </summary>
public static string TbRouteExcludeAddress {
get {
return ResourceManager.GetString("TbRouteExcludeAddress", resourceCulture);
}
}
/// <summary>
/// 查找类似 Use commas (,) to separate. 的本地化字符串。
/// </summary>
public static string TbRouteExcludeAddressTip {
get {
return ResourceManager.GetString("TbRouteExcludeAddressTip", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 tun: TUN inbound, socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。 /// 查找类似 tun: TUN inbound, socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。
/// </summary> /// </summary>
@@ -4680,6 +4761,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Verify Peer Cert By Name 的本地化字符串。
/// </summary>
public static string TbVerifyPeerCertByName {
get {
return ResourceManager.GetString("TbVerifyPeerCertByName", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 The delay: {0} ms, {1} 的本地化字符串。 /// 查找类似 The delay: {0} ms, {1} 的本地化字符串。
/// </summary> /// </summary>
@@ -1740,4 +1740,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Not Support</value>
</data> </data>
</root> <data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root>
+15 -9
View File
@@ -1675,7 +1675,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<value>Ajouter [NaïveProxy]</value> <value>Ajouter [NaïveProxy]</value>
</data> </data>
<data name="TbInsecureConcurrency" xml:space="preserve"> <data name="TbInsecureConcurrency" xml:space="preserve">
<value>Insecure Concurrency</value> <value>Concurrence non sûre</value>
</data> </data>
<data name="TbUsername" xml:space="preserve"> <data name="TbUsername" xml:space="preserve">
<value>Nom d’utilisateur</value> <value>Nom d’utilisateur</value>
@@ -1699,10 +1699,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<value>Allow insecure cert fetch (self-signed)</value> <value>Allow insecure cert fetch (self-signed)</value>
</data> </data>
<data name="TbAllowInsecureCertFetchTips" xml:space="preserve"> <data name="TbAllowInsecureCertFetchTips" xml:space="preserve">
<value>Only for fetching self-signed certificates. This may expose you to MITM risks.</value> <value>Pour obtenir des certificats auto-signés uniquement. Risque MITM.</value>
</data> </data>
<data name="TbSettingsSendThrough" xml:space="preserve"> <data name="TbSettingsSendThrough" xml:space="preserve">
<value>Adresse sortante locale (SendThrough)</value> <value>Adresse de sortie locale (SendThrough)</value>
</data> </data>
<data name="TbSettingsSendThroughTip" xml:space="preserve"> <data name="TbSettingsSendThroughTip" xml:space="preserve">
<value>Pour environnements multi-interfaces, entrez l'adresse IPv4 de la machine locale</value> <value>Pour environnements multi-interfaces, entrez l'adresse IPv4 de la machine locale</value>
@@ -1717,24 +1717,30 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<value>Pour les environnements multi-interfaces, entrez le nom de l'interface à lier. Ne fonctionne que sur les systèmes Windows et en mode TUN</value> <value>Pour les environnements multi-interfaces, entrez le nom de l'interface à lier. Ne fonctionne que sur les systèmes Windows et en mode TUN</value>
</data> </data>
<data name="menuUdpTestServer" xml:space="preserve"> <data name="menuUdpTestServer" xml:space="preserve">
<value>Test Configurations UDP Delay</value> <value>Tester la latence UDP des profils</value>
</data> </data>
<data name="TbSettingsUdpTestUrl" xml:space="preserve"> <data name="TbSettingsUdpTestUrl" xml:space="preserve">
<value>UDP Test Url</value> <value>URL de test UDP</value>
</data> </data>
<data name="TbPreSharedKey" xml:space="preserve"> <data name="TbPreSharedKey" xml:space="preserve">
<value>PreSharedKey</value> <value>PreSharedKey</value>
</data> </data>
<data name="menuExport2InnerUri" xml:space="preserve"> <data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value> <value>Exporter le lien interne v2rayN vers le presse-papiers</value>
</data> </data>
<data name="MsgCheckUpdateHasNewVersion" xml:space="preserve"> <data name="MsgCheckUpdateHasNewVersion" xml:space="preserve">
<value>{0} has a new version available: {1}</value> <value>{0} a une nouvelle version disponible: {1}</value>
</data> </data>
<data name="menuCheckOnly" xml:space="preserve"> <data name="menuCheckOnly" xml:space="preserve">
<value>Only Check</value> <value>Uniquement vérifier</value>
</data> </data>
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Non supporté</value>
</data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>Détails IP</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>Nouvelle MAJ</value>
</data> </data>
</root> </root>
+6
View File
@@ -1740,4 +1740,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Not Support</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>
+30
View File
@@ -1581,6 +1581,9 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbEchConfigList" xml:space="preserve"> <data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value> <value>EchConfigList</value>
</data> </data>
<data name="TbVerifyPeerCertByName" xml:space="preserve">
<value>Verify Peer Cert By Name</value>
</data>
<data name="TbFullCertTips" xml:space="preserve"> <data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value> <value>Full certificate (chain), PEM format</value>
</data> </data>
@@ -1740,4 +1743,31 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Not Support</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
<value>Warning: Xray will disable allowInsecure (skip certificate verification) in August 2026. Please switch to pinnedPeerCertSha256 (fixed certificate fingerprint) as soon as possible. allowInsecure will not be usable after its expiration.</value>
</data>
<data name="TbRouteExcludeAddress" xml:space="preserve">
<value>Route Exclude Address</value>
</data>
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
<value>Use commas (,) to separate.</value>
</data>
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
<value>Invalid address in TUN route exclude list: {0}</value>
</data>
<data name="MsgOptionsConflict" xml:space="preserve">
<value>Conflict between {0} and {1}</value>
</data>
<data name="TbEnableFinalFragment" xml:space="preserve">
<value>Enable Final Fragment</value>
</data>
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
<value>Split the tail of packets into smaller fragments. This may affect throughput and latency.</value>
</data>
</root> </root>
+60 -54
View File
@@ -121,7 +121,7 @@
<value>Ссылка успешно скопирована в буфер обмена</value> <value>Ссылка успешно скопирована в буфер обмена</value>
</data> </data>
<data name="CheckServerSettings" xml:space="preserve"> <data name="CheckServerSettings" xml:space="preserve">
<value>Сначала проверьте настройки сервера</value> <value>Недопустимая конфигурация, проверьте или выберите заново</value>
</data> </data>
<data name="ConfigurationFormatIncorrect" xml:space="preserve"> <data name="ConfigurationFormatIncorrect" xml:space="preserve">
<value>Недопустимый формат конфигурации</value> <value>Недопустимый формат конфигурации</value>
@@ -166,7 +166,7 @@
<value>Некорректная конфигурация. Проверьте</value> <value>Некорректная конфигурация. Проверьте</value>
</data> </data>
<data name="InitialConfiguration" xml:space="preserve"> <data name="InitialConfiguration" xml:space="preserve">
<value>Исходная конфигурация</value> <value>Инициализация конфигурации</value>
</data> </data>
<data name="IsLatestCore" xml:space="preserve"> <data name="IsLatestCore" xml:space="preserve">
<value>{0} {1} является последней версией</value> <value>{0} {1} является последней версией</value>
@@ -256,7 +256,7 @@
<value>Операция не удалась, проверьте и повторите попытку</value> <value>Операция не удалась, проверьте и повторите попытку</value>
</data> </data>
<data name="PleaseFillRemarks" xml:space="preserve"> <data name="PleaseFillRemarks" xml:space="preserve">
<value>Введите примечания</value> <value>Введите псевдоним</value>
</data> </data>
<data name="PleaseSelectEncryption" xml:space="preserve"> <data name="PleaseSelectEncryption" xml:space="preserve">
<value>Выберите метод шифрования</value> <value>Выберите метод шифрования</value>
@@ -307,7 +307,7 @@
<value>{0}: одно из обязательных полей</value> <value>{0}: одно из обязательных полей</value>
</data> </data>
<data name="LvRemarks" xml:space="preserve"> <data name="LvRemarks" xml:space="preserve">
<value>Примечания</value> <value>Псевдоним</value>
</data> </data>
<data name="LvUrl" xml:space="preserve"> <data name="LvUrl" xml:space="preserve">
<value>URL (необязательно)</value> <value>URL (необязательно)</value>
@@ -334,13 +334,13 @@
<value>Введите корректный пользовательский DNS</value> <value>Введите корректный пользовательский DNS</value>
</data> </data>
<data name="TransportPathTip1" xml:space="preserve"> <data name="TransportPathTip1" xml:space="preserve">
<value>*Путь ws/http upgrade/xhttp</value> <value>*Путь ws/httpupgrade/xhttp</value>
</data> </data>
<data name="TransportPathTip2" xml:space="preserve"> <data name="TransportPathTip2" xml:space="preserve">
<value>*HTTP2-путь</value> <value>*HTTP2-путь</value>
</data> </data>
<data name="TransportPathTip3" xml:space="preserve"> <data name="TransportPathTip3" xml:space="preserve">
<value>*QUIC-ключ / KCP-seed</value> <value>*Ключ шифрования QUIC</value>
</data> </data>
<data name="TransportPathTip4" xml:space="preserve"> <data name="TransportPathTip4" xml:space="preserve">
<value>Имя сервиса gRPC</value> <value>Имя сервиса gRPC</value>
@@ -349,13 +349,13 @@
<value>*http-хосты, разделённые запятыми (,)</value> <value>*http-хосты, разделённые запятыми (,)</value>
</data> </data>
<data name="TransportRequestHostTip2" xml:space="preserve"> <data name="TransportRequestHostTip2" xml:space="preserve">
<value>*Хост ws/http upgrade/xhttp</value> <value>*Хост ws/httpupgrade/xhttp</value>
</data> </data>
<data name="TransportRequestHostTip3" xml:space="preserve"> <data name="TransportRequestHostTip3" xml:space="preserve">
<value>*HTTP2-хосты, разделённые запятыми (,)</value> <value>*HTTP2-хосты, разделённые запятыми (,)</value>
</data> </data>
<data name="TransportRequestHostTip4" xml:space="preserve"> <data name="TransportRequestHostTip4" xml:space="preserve">
<value>Безопасность *QUIC</value> <value>*Метод шифрования QUIC</value>
</data> </data>
<data name="TransportHeaderType1" xml:space="preserve"> <data name="TransportHeaderType1" xml:space="preserve">
<value>Тип raw-камуфляжа</value> <value>Тип raw-камуфляжа</value>
@@ -400,7 +400,7 @@
<value>Фильтр, нажмите Enter для выполнения</value> <value>Фильтр, нажмите Enter для выполнения</value>
</data> </data>
<data name="menuCheckUpdate" xml:space="preserve"> <data name="menuCheckUpdate" xml:space="preserve">
<value>Проверить обновления</value> <value>Обновить</value>
</data> </data>
<data name="menuClose" xml:space="preserve"> <data name="menuClose" xml:space="preserve">
<value>Закрыть</value> <value>Закрыть</value>
@@ -415,13 +415,13 @@
<value>Помощь</value> <value>Помощь</value>
</data> </data>
<data name="menuOptionSetting" xml:space="preserve"> <data name="menuOptionSetting" xml:space="preserve">
<value>Настройки</value> <value>Параметры</value>
</data> </data>
<data name="menuPromotion" xml:space="preserve"> <data name="menuPromotion" xml:space="preserve">
<value>Продвижение</value> <value>Продвижение</value>
</data> </data>
<data name="menuReload" xml:space="preserve"> <data name="menuReload" xml:space="preserve">
<value>Перезагрузить</value> <value>Перезапустить службу</value>
</data> </data>
<data name="menuRoutingSetting" xml:space="preserve"> <data name="menuRoutingSetting" xml:space="preserve">
<value>Настройки маршрутизации</value> <value>Настройки маршрутизации</value>
@@ -445,10 +445,10 @@
<value>Настройки групп подписки</value> <value>Настройки групп подписки</value>
</data> </data>
<data name="menuSubUpdate" xml:space="preserve"> <data name="menuSubUpdate" xml:space="preserve">
<value>Обновить подписку без прокси</value> <value>Обновить все подписки без прокси</value>
</data> </data>
<data name="menuSubUpdateViaProxy" xml:space="preserve"> <data name="menuSubUpdateViaProxy" xml:space="preserve">
<value>Обновить подписку с прокси</value> <value>Обновить все подписки через прокси</value>
</data> </data>
<data name="menuSystemproxy" xml:space="preserve"> <data name="menuSystemproxy" xml:space="preserve">
<value>Системный прокси</value> <value>Системный прокси</value>
@@ -562,7 +562,7 @@
<value>Сортировка</value> <value>Сортировка</value>
</data> </data>
<data name="LvUserAgent" xml:space="preserve"> <data name="LvUserAgent" xml:space="preserve">
<value>User-Agent</value> <value>User-Agent (необязательно)</value>
</data> </data>
<data name="TbCancel" xml:space="preserve"> <data name="TbCancel" xml:space="preserve">
<value>Отмена</value> <value>Отмена</value>
@@ -577,7 +577,7 @@
<value>Адрес</value> <value>Адрес</value>
</data> </data>
<data name="TbAllowInsecure" xml:space="preserve"> <data name="TbAllowInsecure" xml:space="preserve">
<value>Разрешить небезопасные</value> <value>Пропустить проверку сертификата (allowInsecure)</value>
</data> </data>
<data name="TbAlpn" xml:space="preserve"> <data name="TbAlpn" xml:space="preserve">
<value>ALPN</value> <value>ALPN</value>
@@ -616,10 +616,10 @@
<value>TLS</value> <value>TLS</value>
</data> </data>
<data name="TipNetwork" xml:space="preserve"> <data name="TipNetwork" xml:space="preserve">
<value>*По-умолчанию raw</value> <value>*По умолчанию raw; при неверном выборе подключение будет невозможно</value>
</data> </data>
<data name="TbCoreType" xml:space="preserve"> <data name="TbCoreType" xml:space="preserve">
<value>Ядро</value> <value>Тип ядра</value>
</data> </data>
<data name="TbFlow5" xml:space="preserve"> <data name="TbFlow5" xml:space="preserve">
<value>Управление потоком (Flow)</value> <value>Управление потоком (Flow)</value>
@@ -640,7 +640,7 @@
<value>Шифрование</value> <value>Шифрование</value>
</data> </data>
<data name="TbSecurity4" xml:space="preserve"> <data name="TbSecurity4" xml:space="preserve">
<value>Пользователь (необязательно)</value> <value>Имя пользователя (необязательно)</value>
</data> </data>
<data name="TbSecurity5" xml:space="preserve"> <data name="TbSecurity5" xml:space="preserve">
<value>Шифрование</value> <value>Шифрование</value>
@@ -649,7 +649,7 @@
<value>Порт SOCKS</value> <value>Порт SOCKS</value>
</data> </data>
<data name="TipPreSocksPort" xml:space="preserve"> <data name="TipPreSocksPort" xml:space="preserve">
<value>* После установки этого значения служба SOCKS будет запущена с использованием Xray/sing-box(TUN) для обеспечения таких функций, как отображение скорости</value> <value>*Порт SOCKS пользовательской конфигурации можно не указывать. Если он задан, Xray/sing-box (TUN) дополнительно запустит предварительную службу SOCKS для разделения трафика, отображения скорости и других функций</value>
</data> </data>
<data name="TbBrowse" xml:space="preserve"> <data name="TbBrowse" xml:space="preserve">
<value>Обзор</value> <value>Обзор</value>
@@ -682,7 +682,7 @@
<value>Настройки типа ядра</value> <value>Настройки типа ядра</value>
</data> </data>
<data name="TbSettingsDefAllowInsecure" xml:space="preserve"> <data name="TbSettingsDefAllowInsecure" xml:space="preserve">
<value>Разрешить небезопасные</value> <value>По умолчанию пропускать проверку сертификата (allowInsecure)</value>
</data> </data>
<data name="TbSettingsDomainStrategy4Freedom" xml:space="preserve"> <data name="TbSettingsDomainStrategy4Freedom" xml:space="preserve">
<value>«Freedom»: стратегия обработки доменов исходящего трафика</value> <value>«Freedom»: стратегия обработки доменов исходящего трафика</value>
@@ -703,7 +703,7 @@
<value>Показывать скорость в реальном времени (требуется перезапуск)</value> <value>Показывать скорость в реальном времени (требуется перезапуск)</value>
</data> </data>
<data name="TbSettingsKeepOlderDedupl" xml:space="preserve"> <data name="TbSettingsKeepOlderDedupl" xml:space="preserve">
<value>Сохранить старые при удалении дублей</value> <value>При удалении дубликатов сохранять элементы с меньшим порядковым номером</value>
</data> </data>
<data name="TbSettingsLogEnabled" xml:space="preserve"> <data name="TbSettingsLogEnabled" xml:space="preserve">
<value>Записывать логи</value> <value>Записывать логи</value>
@@ -730,13 +730,13 @@
<value>Смешанный порт</value> <value>Смешанный порт</value>
</data> </data>
<data name="TbSettingsStartBoot" xml:space="preserve"> <data name="TbSettingsStartBoot" xml:space="preserve">
<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>URL конвертации подписок</value> <value>URL конвертации подписок (необязательно)</value>
</data> </data>
<data name="TbSettingsSystemproxy" xml:space="preserve"> <data name="TbSettingsSystemproxy" xml:space="preserve">
<value>Настройки системного прокси</value> <value>Настройки системного прокси</value>
@@ -787,13 +787,13 @@
<value>Запущено от имени администратора</value> <value>Запущено от имени администратора</value>
</data> </data>
<data name="menuMoveBottom" xml:space="preserve"> <data name="menuMoveBottom" xml:space="preserve">
<value>Спуститься вниз</value> <value>Переместить в самый низ</value>
</data> </data>
<data name="menuMoveDown" xml:space="preserve"> <data name="menuMoveDown" xml:space="preserve">
<value>Вниз</value> <value>Вниз</value>
</data> </data>
<data name="menuMoveTop" xml:space="preserve"> <data name="menuMoveTop" xml:space="preserve">
<value>Подняться наверх</value> <value>Переместить в самый верх</value>
</data> </data>
<data name="menuMoveUp" xml:space="preserve"> <data name="menuMoveUp" xml:space="preserve">
<value>Вверх</value> <value>Вверх</value>
@@ -805,7 +805,7 @@
<value>Веб-сайт {0}</value> <value>Веб-сайт {0}</value>
</data> </data>
<data name="menuRoutingAdvancedAdd" xml:space="preserve"> <data name="menuRoutingAdvancedAdd" xml:space="preserve">
<value>Добавить</value> <value>Добавить набор правил</value>
</data> </data>
<data name="menuRoutingAdvancedImportRules" xml:space="preserve"> <data name="menuRoutingAdvancedImportRules" xml:space="preserve">
<value>Импортировать правила</value> <value>Импортировать правила</value>
@@ -841,7 +841,7 @@
<value>Добавить правило</value> <value>Добавить правило</value>
</data> </data>
<data name="menuRuleExportSelected" xml:space="preserve"> <data name="menuRuleExportSelected" xml:space="preserve">
<value>Экспорт выделенных правил</value> <value>Экспортировать выбранные правила в буфер обмена</value>
</data> </data>
<data name="menuRuleList" xml:space="preserve"> <data name="menuRuleList" xml:space="preserve">
<value>Список правил</value> <value>Список правил</value>
@@ -859,7 +859,7 @@
<value>Документация RuleObject</value> <value>Документация RuleObject</value>
</data> </data>
<data name="TbDnsObjectDoc" xml:space="preserve"> <data name="TbDnsObjectDoc" xml:space="preserve">
<value>Поддерживаются DNS-объекты, нажмите для просмотра документации</value> <value>Заполните DNS-объект (формат JSON), нажмите для просмотра документации</value>
</data> </data>
<data name="SubUrlTips" xml:space="preserve"> <data name="SubUrlTips" xml:space="preserve">
<value>Для группы оставьте это поле пустым</value> <value>Для группы оставьте это поле пустым</value>
@@ -871,13 +871,13 @@
<value>Системные прокси изменены</value> <value>Системные прокси изменены</value>
</data> </data>
<data name="TbSettingsRouteOnly" xml:space="preserve"> <data name="TbSettingsRouteOnly" xml:space="preserve">
<value>Только маршрут</value> <value>Только для маршрутизации (routeOnly)</value>
</data> </data>
<data name="TbSettingsNotProxyLocalAddress" xml:space="preserve"> <data name="TbSettingsNotProxyLocalAddress" xml:space="preserve">
<value>Не использовать прокси для локальных (интранет) адресов</value> <value>Не использовать прокси для локальных (интранет) адресов</value>
</data> </data>
<data name="menuMixedTestServer" xml:space="preserve"> <data name="menuMixedTestServer" xml:space="preserve">
<value>Тест задержки и скорости всех серверов (Ctrl+E)</value> <value>Многопоточный тест задержки и скорости (Ctrl+E)</value>
</data> </data>
<data name="LvTestDelay" xml:space="preserve"> <data name="LvTestDelay" xml:space="preserve">
<value>Задержка (мс)</value> <value>Задержка (мс)</value>
@@ -886,10 +886,10 @@
<value>Скорость (МБ/с)</value> <value>Скорость (МБ/с)</value>
</data> </data>
<data name="FailedToRunCore" xml:space="preserve"> <data name="FailedToRunCore" xml:space="preserve">
<value>Не удалось запустить ядро, посмотрите логи</value> <value>Не удалось запустить ядро, см. сообщение</value>
</data> </data>
<data name="LvFilter" xml:space="preserve"> <data name="LvFilter" xml:space="preserve">
<value>Фильтр по примечаниям (регулярные выражения)</value> <value>Фильтр по псевдониму (регулярные выражения)</value>
</data> </data>
<data name="TbDisplayLog" xml:space="preserve"> <data name="TbDisplayLog" xml:space="preserve">
<value>Отображать журнал</value> <value>Отображать журнал</value>
@@ -937,7 +937,7 @@
<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-порт = +3; Xray API порт = +4; mihomo API порт = +5;</value> <value>PAC-порт = +3; Xray API порт = +4; mihomo API порт = +5;</value>
@@ -1009,7 +1009,7 @@
<value>Пользовательский DNS для sing-box</value> <value>Пользовательский DNS для sing-box</value>
</data> </data>
<data name="TbDnsSingboxObjectDoc" xml:space="preserve"> <data name="TbDnsSingboxObjectDoc" xml:space="preserve">
<value>Заполните структуру DNS, нажмите, чтобы открыть документ</value> <value>Заполните структуру DNS (формат JSON), нажмите для просмотра документации</value>
</data> </data>
<data name="TbSettingDnsImportDefConfig" xml:space="preserve"> <data name="TbSettingDnsImportDefConfig" xml:space="preserve">
<value>Нажмите, чтобы импортировать конфигурацию DNS по умолчанию</value> <value>Нажмите, чтобы импортировать конфигурацию DNS по умолчанию</value>
@@ -1033,7 +1033,7 @@
<value>Добавить сервер [Hysteria2]</value> <value>Добавить сервер [Hysteria2]</value>
</data> </data>
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve"> <data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
<value>Максимальная пропускная способность Hysteria (загрузка/отдача)</value> <value>Максимальная пропускная способность Hysteria (отдача/загрузка)</value>
</data> </data>
<data name="TbSettingsUseSystemHosts" xml:space="preserve"> <data name="TbSettingsUseSystemHosts" xml:space="preserve">
<value>Использовать системный файл hosts</value> <value>Использовать системный файл hosts</value>
@@ -1045,13 +1045,13 @@
<value>Управление перегрузками</value> <value>Управление перегрузками</value>
</data> </data>
<data name="LvPrevProfile" xml:space="preserve"> <data name="LvPrevProfile" xml:space="preserve">
<value>Примечания к предыдущему прокси</value> <value>Псевдоним предыдущего прокси</value>
</data> </data>
<data name="LvNextProfile" xml:space="preserve"> <data name="LvNextProfile" xml:space="preserve">
<value>Примечания к следующему прокси</value> <value>Псевдоним следующего прокси</value>
</data> </data>
<data name="LvPrevProfileTip" xml:space="preserve"> <data name="LvPrevProfileTip" xml:space="preserve">
<value>Убедитесь, что примечание существует и является уникальным</value> <value>Убедитесь, что псевдоним существует и является уникальным</value>
</data> </data>
<data name="TbSettingsTunAutoRoute" xml:space="preserve"> <data name="TbSettingsTunAutoRoute" xml:space="preserve">
<value>Автоматическая маршрутизация</value> <value>Автоматическая маршрутизация</value>
@@ -1177,7 +1177,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>
@@ -1210,7 +1210,7 @@
<value>Экспорт ссылок в формате Base64 в буфер обмена</value> <value>Экспорт ссылок в формате Base64 в буфер обмена</value>
</data> </data>
<data name="menuExport2ClientConfigClipboard" xml:space="preserve"> <data name="menuExport2ClientConfigClipboard" xml:space="preserve">
<value>Экспортировать полную конфигурацию в буфер обмена</value> <value>Экспортировать выбранную полную конфигурацию в буфер обмена</value>
</data> </data>
<data name="menuShowOrHideMainWindow" xml:space="preserve"> <data name="menuShowOrHideMainWindow" xml:space="preserve">
<value>Показать или скрыть главное окно</value> <value>Показать или скрыть главное окно</value>
@@ -1300,7 +1300,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>
@@ -1318,13 +1318,13 @@
<value>XHTTP-режим</value> <value>XHTTP-режим</value>
</data> </data>
<data name="TransportExtraTip" xml:space="preserve"> <data name="TransportExtraTip" xml:space="preserve">
<value>Сырой JSON, формат: { XHTTP Object }</value> <value>Сырой JSON, формат: { XHTTPObject }</value>
</data> </data>
<data name="TbSettingsHide2TrayWhenClose" xml:space="preserve"> <data name="TbSettingsHide2TrayWhenClose" xml:space="preserve">
<value>Сворачивать в трей при закрытии окна</value> <value>Сворачивать в трей при закрытии окна</value>
</data> </data>
<data name="TbSettingsMixedConcurrencyCount" xml:space="preserve"> <data name="TbSettingsMixedConcurrencyCount" xml:space="preserve">
<value>Количество одновременно выполняемых тестов при многоэтапном тестировании</value> <value>Количество параллельных задач при многопоточном тестировании</value>
</data> </data>
<data name="TbSettingsExceptionTip2" xml:space="preserve"> <data name="TbSettingsExceptionTip2" xml:space="preserve">
<value>Исключения: не использовать прокси для указанных адресов. Разделяйте запятой (,)</value> <value>Исключения: не использовать прокси для указанных адресов. Разделяйте запятой (,)</value>
@@ -1336,7 +1336,7 @@
<value>Включить второй смешанный порт</value> <value>Включить второй смешанный порт</value>
</data> </data>
<data name="TbRoutingInboundTagTips" xml:space="preserve"> <data name="TbRoutingInboundTagTips" xml:space="preserve">
<value>socks: локальный порт, socks2: второй локальный порт, socks3: LAN-порт</value> <value>tun: входящий TUN, socks: локальный порт, socks2: второй локальный порт, socks3: LAN-порт</value>
</data> </data>
<data name="TbSettingsTheme" xml:space="preserve"> <data name="TbSettingsTheme" xml:space="preserve">
<value>Тема</value> <value>Тема</value>
@@ -1357,7 +1357,7 @@
<value>Удалено {0} недействительных</value> <value>Удалено {0} недействительных</value>
</data> </data>
<data name="TbPorts7" xml:space="preserve"> <data name="TbPorts7" xml:space="preserve">
<value>Диапазон портов сервера</value> <value>Диапазон портов для переключения (Port Hopping)</value>
</data> </data>
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>Заменит указанный порт, перечисляйте через запятую (,)</value> <value>Заменит указанный порт, перечисляйте через запятую (,)</value>
@@ -1369,7 +1369,7 @@
<value>URL для тестирования текущего соединения</value> <value>URL для тестирования текущего соединения</value>
</data> </data>
<data name="TbRuleOutboundTagTip" xml:space="preserve"> <data name="TbRuleOutboundTagTip" xml:space="preserve">
<value>Можно указать название (Remarks) из конфигурации, убедитесь, что оно существует и уникально</value> <value>Можно указать псевдоним из конфигурации, убедитесь, что он существует и уникален</value>
</data> </data>
<data name="SudoIncorrectPasswordTip" xml:space="preserve"> <data name="SudoIncorrectPasswordTip" xml:space="preserve">
<value>Неверный пароль, попробуйте ещё раз.</value> <value>Неверный пароль, попробуйте ещё раз.</value>
@@ -1384,7 +1384,7 @@
<value>Удалённый DNS</value> <value>Удалённый DNS</value>
</data> </data>
<data name="TbDomesticDNS" xml:space="preserve"> <data name="TbDomesticDNS" xml:space="preserve">
<value>Внутренний DNS</value> <value>DNS для прямых подключений</value>
</data> </data>
<data name="TbDirectResolveStrategy" xml:space="preserve"> <data name="TbDirectResolveStrategy" xml:space="preserve">
<value>Стратегия разрешения прямых соединений</value> <value>Стратегия разрешения прямых соединений</value>
@@ -1489,13 +1489,13 @@
<value>Тип группы политик</value> <value>Тип группы политик</value>
</data> </data>
<data name="menuAddPolicyGroupServer" xml:space="preserve"> <data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Добавить группу политик </value> <value>Добавить группу политик</value>
</data> </data>
<data name="menuAddProxyChainServer" xml:space="preserve"> <data name="menuAddProxyChainServer" xml:space="preserve">
<value>Добавить цепочку прокси</value> <value>Добавить цепочку прокси</value>
</data> </data>
<data name="menuAddChildServer" xml:space="preserve"> <data name="menuAddChildServer" xml:space="preserve">
<value>Добавить дочернюю конфигурацию </value> <value>Добавить дочернюю конфигурацию</value>
</data> </data>
<data name="menuRemoveChildServer" xml:space="preserve"> <data name="menuRemoveChildServer" xml:space="preserve">
<value>Удалить дочернюю конфигурацию </value> <value>Удалить дочернюю конфигурацию </value>
@@ -1534,7 +1534,7 @@
<value>Bootstrap DNS</value> <value>Bootstrap DNS</value>
</data> </data>
<data name="TbBootstrapDNSTips" xml:space="preserve"> <data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Разрешает домены DNS-серверов, требуется IP-адрес</value> <value>Для разрешения доменных имён DNS-серверов необходимо указать IP-адрес</value>
</data> </data>
<data name="menuFastRealPing" xml:space="preserve"> <data name="menuFastRealPing" xml:space="preserve">
<value>Тест реальной задержки</value> <value>Тест реальной задержки</value>
@@ -1547,7 +1547,7 @@
</data> </data>
<data name="TbCertPinningTips" xml:space="preserve"> <data name="TbCertPinningTips" xml:space="preserve">
<value>Привязанный сертификат (заполните любое из полей) <value>Привязанный сертификат (заполните любое из полей)
При указании сертификат будет привязан, а «Разрешить небезопасные» отключится. При указании сертификат будет привязан, а «Пропустить проверку сертификата» отключится.
Получение сертификата может завершиться неудачей при использовании самоподписанного сертификата или при наличии ненадёжного / вредоносного ЦС в системе.</value> Получение сертификата может завершиться неудачей при использовании самоподписанного сертификата или при наличии ненадёжного / вредоносного ЦС в системе.</value>
</data> </data>
@@ -1732,12 +1732,18 @@
<value>Экспорт внутренней ссылки v2rayN в буфер обмена</value> <value>Экспорт внутренней ссылки v2rayN в буфер обмена</value>
</data> </data>
<data name="MsgCheckUpdateHasNewVersion" xml:space="preserve"> <data name="MsgCheckUpdateHasNewVersion" xml:space="preserve">
<value>{0} has a new version available: {1}</value> <value>Доступна новая версия {0}: {1}</value>
</data> </data>
<data name="menuCheckOnly" xml:space="preserve"> <data name="menuCheckOnly" xml:space="preserve">
<value>Only Check</value> <value>Проверить</value>
</data> </data>
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Не поддерживается</value>
</data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>Информация об IP</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>Доступно обновление</value>
</data> </data>
</root> </root>
+30
View File
@@ -1737,4 +1737,34 @@
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>不支持</value> <value>不支持</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP 信息</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>有更新</value>
</data>
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
<value>警告:Xray 将在 2026.8.1 禁用跳过证书验证 allowInsecure ,请尽快改用证书固定指纹 pinnedPeerCertSha256。到期后无法使用 </value>
</data>
<data name="TbVerifyPeerCertByName" xml:space="preserve">
<value>Verify Peer Cert By Name</value>
</data>
<data name="TbRouteExcludeAddress" xml:space="preserve">
<value>路由排除地址</value>
</data>
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
<value>使用逗号 (,) 分隔</value>
</data>
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
<value>TUN 路由排除列表包含无效地址:{0}</value>
</data>
<data name="MsgOptionsConflict" xml:space="preserve">
<value>{0} 与 {1} 冲突</value>
</data>
<data name="TbEnableFinalFragment" xml:space="preserve">
<value>启用末端分片</value>
</data>
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
<value>将数据包末端拆分为更小片段发送,可能影响吞吐与延迟。</value>
</data>
</root> </root>
+32 -2
View File
@@ -1333,7 +1333,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>Tun:TUN 入站,Socks:本機連接埠,Socks2:第二個本機連接埠,Socks3:區域網路連接埠</value>
</data> </data>
<data name="TbSettingsTheme" xml:space="preserve"> <data name="TbSettingsTheme" xml:space="preserve">
<value>主題</value> <value>主題</value>
@@ -1737,4 +1737,34 @@
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>不支援</value> <value>不支援</value>
</data> </data>
</root> <data name="LvTestIpInfo" xml:space="preserve">
<value>IP 資訊</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>有更新</value>
</data>
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
<value>警告:Xray 將在 2026.8.1 停用跳過憑證驗證 allowInsecure ,請盡快改用憑證固定指紋 pinnedPeerCertSha256。到期後無法使用 allowInsecure。</value>
</data>
<data name="TbVerifyPeerCertByName" xml:space="preserve">
<value>Verify Peer Cert By Name</value>
</data>
<data name="TbRouteExcludeAddress" xml:space="preserve">
<value>路由排除位址</value>
</data>
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
<value>使用逗號 (,) 分隔</value>
</data>
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
<value>TUN 路由排除清單中的位址無效: {0}</value>
</data>
<data name="MsgOptionsConflict" xml:space="preserve">
<value>{0} 與 {1} 發生衝突</value>
</data>
<data name="TbEnableFinalFragment" xml:space="preserve">
<value>啟用末端分片</value>
</data>
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
<value>將封包末端拆分為更小的片段進行傳送,可能會影響吞吐量與延遲</value>
</data>
</root>
+1 -9
View File
@@ -17,14 +17,6 @@
], ],
"routing": { "routing": {
"domainStrategy": "IPIfNonMatch", "domainStrategy": "IPIfNonMatch",
"rules": [ "rules": []
{
"inboundTag": [
"api"
],
"outboundTag": "api",
"type": "field"
}
]
} }
} }
@@ -8,6 +8,9 @@
"172.18.0.1/30", "172.18.0.1/30",
"fdfe:dcba:9876::1/126" "fdfe:dcba:9876::1/126"
], ],
"dns": [
"172.18.0.1"
],
"autoSystemRoutingTable": [ "autoSystemRoutingTable": [
"0.0.0.0/0", "0.0.0.0/0",
"::/0" "::/0"
+2 -1
View File
@@ -6,6 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Downloader" /> <PackageReference Include="Downloader" />
<PackageReference Include="IPNetwork2" />
<PackageReference Include="ReactiveUI"> <PackageReference Include="ReactiveUI">
<TreatAsUsed>true</TreatAsUsed> <TreatAsUsed>true</TreatAsUsed>
</PackageReference> </PackageReference>
@@ -56,7 +57,7 @@
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
</Compile> </Compile>
<EmbeddedResource Update="Resx\ResUI.fa-Ir.resx"> <EmbeddedResource Update="Resx\ResUI.fa.resx">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>PublicResXFileCodeGenerator</Generator> <Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource> </EmbeddedResource>
@@ -3,16 +3,10 @@ namespace ServiceLib.Services.CoreConfig;
/// <summary> /// <summary>
/// Core configuration file processing class /// Core configuration file processing class
/// </summary> /// </summary>
public class CoreConfigClashService public class CoreConfigClashService(Config config)
{ {
private Config _config;
private static readonly string _tag = "CoreConfigClashService"; private static readonly string _tag = "CoreConfigClashService";
public CoreConfigClashService(Config config)
{
_config = config;
}
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName) public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
{ {
var ret = new RetResult(); var ret = new RetResult();
@@ -56,7 +50,7 @@ public class CoreConfigClashService
var tagYamlStr1 = "!<str>"; var tagYamlStr1 = "!<str>";
var tagYamlStr2 = "__strn__"; var tagYamlStr2 = "__strn__";
var tagYamlStr3 = "!!str"; var tagYamlStr3 = "!!str";
var txtFile = File.ReadAllText(addressFileName); var txtFile = await File.ReadAllTextAsync(addressFileName);
txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2); txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2);
//YAML anchors //YAML anchors
@@ -75,13 +69,13 @@ public class CoreConfigClashService
//mixed-port //mixed-port
fileContent["mixed-port"] = AppManager.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}:{AppManager.Instance.StatePort2}"; fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
fileContent.Remove("secret"); fileContent.Remove("secret");
//allow-lan //allow-lan
if (_config.Inbound.First().AllowLANConn) if (config.Inbound.First().AllowLANConn)
{ {
fileContent["allow-lan"] = "true"; fileContent["allow-lan"] = "true";
fileContent["bind-address"] = "*"; fileContent["bind-address"] = "*";
@@ -92,23 +86,23 @@ public class CoreConfigClashService
} }
//ipv6 //ipv6
fileContent["ipv6"] = _config.ClashUIItem.EnableIPv6; fileContent["ipv6"] = config.ClashUIItem.EnableIPv6;
//mode //mode
if (!fileContent.ContainsKey("mode")) if (!fileContent.ContainsKey("mode"))
{ {
fileContent["mode"] = ERuleMode.Rule.ToString().ToLower(); fileContent["mode"] = nameof(ERuleMode.Rule).ToLower();
} }
else else
{ {
if (_config.ClashUIItem.RuleMode != ERuleMode.Unchanged) if (config.ClashUIItem.RuleMode != ERuleMode.Unchanged)
{ {
fileContent["mode"] = _config.ClashUIItem.RuleMode.ToString().ToLower(); fileContent["mode"] = config.ClashUIItem.RuleMode.ToString().ToLower();
} }
} }
//enable tun mode //enable tun mode
if (_config.TunModeItem.EnableTun) if (config.TunModeItem.EnableTun)
{ {
var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml); var tun = EmbedUtils.GetEmbedText(Global.ClashTunYaml);
if (tun.IsNotEmpty()) if (tun.IsNotEmpty())
@@ -156,7 +150,7 @@ public class CoreConfigClashService
private async Task MixinContent(Dictionary<string, object> fileContent, ProfileItem node) private async Task MixinContent(Dictionary<string, object> fileContent, ProfileItem node)
{ {
if (!_config.ClashUIItem.EnableMixinContent) if (!config.ClashUIItem.EnableMixinContent)
{ {
return; return;
} }
@@ -177,7 +171,7 @@ public class CoreConfigClashService
} }
foreach (var item in mixinContent) foreach (var item in mixinContent)
{ {
if (!_config.TunModeItem.EnableTun && item.Key == "tun") if (!config.TunModeItem.EnableTun && item.Key == "tun")
{ {
continue; continue;
} }
@@ -220,9 +214,8 @@ public class CoreConfigClashService
return; return;
} }
if (!blRemoved && !fileContent.ContainsKey(key)) if (!blRemoved && fileContent.TryAdd(key, value))
{ {
fileContent.Add(key, value);
return; return;
} }
var lstOri = (List<object>)fileContent[key]; var lstOri = (List<object>)fileContent[key];
@@ -244,7 +237,7 @@ public class CoreConfigClashService
} }
else else
{ {
lstValue.ForEach(item => lstOri.Add(item)); lstValue.ForEach(lstOri.Add);
} }
} }
@@ -153,7 +153,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
{ {
listen = Global.Loopback, listen = Global.Loopback,
listen_port = port, listen_port = port,
type = EInboundProtocol.mixed.ToString(), type = nameof(EInboundProtocol.mixed),
}; };
inbound.tag = inbound.type + inbound.listen_port.ToString(); inbound.tag = inbound.type + inbound.listen_port.ToString();
_coreConfig.inbounds.Add(inbound); _coreConfig.inbounds.Add(inbound);
@@ -229,7 +229,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
tag = $"{EInboundProtocol.mixed}{port}", tag = $"{EInboundProtocol.mixed}{port}",
listen = Global.Loopback, listen = Global.Loopback,
listen_port = port, listen_port = port,
type = EInboundProtocol.mixed.ToString(), type = nameof(EInboundProtocol.mixed),
}); });
ApplyOutboundBindInterface(); ApplyOutboundBindInterface();
ApplyOutboundSendThrough(); ApplyOutboundSendThrough();
@@ -23,7 +23,7 @@ public partial class CoreConfigSingboxService
} }
// Process outbounds // Process outbounds
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : []; var customOutboundsNode = fullConfigTemplateNode["outbounds"] as JsonArray ?? [];
foreach (var outbound in _coreConfig.outbounds) foreach (var outbound in _coreConfig.outbounds)
{ {
if (outbound.type.ToLower() is "direct" or "block") if (outbound.type.ToLower() is "direct" or "block")
@@ -42,9 +42,9 @@ public partial class CoreConfigSingboxService
fullConfigTemplateNode["outbounds"] = customOutboundsNode; fullConfigTemplateNode["outbounds"] = customOutboundsNode;
// Process endpoints // Process endpoints
if (_coreConfig.endpoints != null && _coreConfig.endpoints.Count > 0) if (_coreConfig.endpoints is { Count: > 0 })
{ {
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : []; var customEndpointsNode = fullConfigTemplateNode["endpoints"] as JsonArray ?? [];
foreach (var endpoint in _coreConfig.endpoints) foreach (var endpoint in _coreConfig.endpoints)
{ {
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
@@ -26,7 +26,7 @@ public partial class CoreConfigSingboxService
{ {
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? []; var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag) if (rules?.LastOrDefault() is { OutboundTag: Global.DirectTag } lastRule)
{ {
var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0; var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0;
var noProcess = lastRule.Process == null || lastRule.Process.Count == 0; var noProcess = lastRule.Process == null || lastRule.Process.Count == 0;
@@ -82,7 +82,7 @@ public partial class CoreConfigSingboxService
if (simpleDnsItem.UseSystemHosts == true) if (simpleDnsItem.UseSystemHosts == true)
{ {
var systemHosts = Utils.GetSystemHosts(); var systemHosts = Utils.GetSystemHosts();
if (systemHosts != null && systemHosts.Count > 0) if (systemHosts is { Count: > 0 })
{ {
foreach (var host in systemHosts) foreach (var host in systemHosts)
{ {
@@ -182,13 +182,13 @@ public partial class CoreConfigSingboxService
{ {
server = Global.SingboxRemoteDNSTag, server = Global.SingboxRemoteDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy), strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy),
clash_mode = ERuleMode.Global.ToString() clash_mode = nameof(ERuleMode.Global)
}, },
new Rule4Sbox new Rule4Sbox
{ {
server = Global.SingboxDirectDNSTag, server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom), strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
clash_mode = ERuleMode.Direct.ToString() clash_mode = nameof(ERuleMode.Direct)
} }
}); });
@@ -454,12 +454,12 @@ public partial class CoreConfigSingboxService
dns4Sbox.rules.Insert(0, new() dns4Sbox.rules.Insert(0, new()
{ {
server = tag, server = tag,
clash_mode = ERuleMode.Direct.ToString() clash_mode = nameof(ERuleMode.Direct)
}); });
dns4Sbox.rules.Insert(0, new() dns4Sbox.rules.Insert(0, new()
{ {
server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote", server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote",
clash_mode = ERuleMode.Global.ToString() clash_mode = nameof(ERuleMode.Global)
}); });
var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress; var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress;
@@ -15,8 +15,8 @@ public partial class CoreConfigSingboxService
{ {
var inbound = new Inbound4Sbox() var inbound = new Inbound4Sbox()
{ {
type = EInboundProtocol.mixed.ToString(), type = nameof(EInboundProtocol.mixed),
tag = EInboundProtocol.socks.ToString(), tag = nameof(EInboundProtocol.socks),
listen = Global.Loopback, listen = Global.Loopback,
}; };
_coreConfig.inbounds.Add(inbound); _coreConfig.inbounds.Add(inbound);
@@ -71,6 +71,7 @@ public partial class CoreConfigSingboxService
{ {
tunInbound.address = ["172.18.0.1/30"]; tunInbound.address = ["172.18.0.1/30"];
} }
tunInbound.route_exclude_address = _config.TunModeItem.RouteExcludeAddress;
_coreConfig.inbounds.Add(tunInbound); _coreConfig.inbounds.Add(tunInbound);
} }
@@ -86,7 +87,7 @@ public partial class CoreConfigSingboxService
var inbound = JsonUtils.DeepCopy(inItem); var inbound = JsonUtils.DeepCopy(inItem);
inbound.tag = protocol.ToString(); inbound.tag = protocol.ToString();
inbound.listen_port = inItem.listen_port + (int)protocol; inbound.listen_port = inItem.listen_port + (int)protocol;
inbound.type = EInboundProtocol.mixed.ToString(); inbound.type = nameof(EInboundProtocol.mixed);
return inbound; return inbound;
} }
} }
@@ -190,15 +190,16 @@ public partial class CoreConfigSingboxService
outbound.packet_encoding = "xudp"; outbound.packet_encoding = "xudp";
if (!protocolExtra.Flow.IsNullOrEmpty()) if (protocolExtra.Flow is "xtls-rprx-vision" or "xtls-rprx-vision-udp443")
{
outbound.flow = "xtls-rprx-vision";
}
else if (!protocolExtra.Flow.IsNullOrEmpty())
{ {
outbound.flow = protocolExtra.Flow; outbound.flow = protocolExtra.Flow;
} }
else
{
FillOutboundMux(outbound);
}
FillOutboundMux(outbound);
FillOutboundTransport(outbound); FillOutboundTransport(outbound);
break; break;
} }
@@ -342,7 +343,7 @@ public partial class CoreConfigSingboxService
{ {
try try
{ {
var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; var muxEnabled = _node.MuxEnabled ?? false;
if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty()) if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty())
{ {
var mux = new Multiplex4Sbox() var mux = new Multiplex4Sbox()
@@ -396,7 +397,7 @@ public partial class CoreConfigSingboxService
enabled = true, enabled = true,
record_fragment = _config.CoreBasicItem.EnableFragment ? true : null, record_fragment = _config.CoreBasicItem.EnableFragment ? true : null,
server_name = serverName, server_name = serverName,
insecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure), insecure = _node.GetAllowInsecure(),
alpn = _node.GetAlpn(), alpn = _node.GetAlpn(),
}; };
if (_node.Fingerprint.IsNotEmpty()) if (_node.Fingerprint.IsNotEmpty())
@@ -729,8 +730,8 @@ public partial class CoreConfigSingboxService
{ {
return; return;
} }
var outbounds = servers.Where(s => s is Outbound4Sbox).Cast<Outbound4Sbox>().ToList(); var outbounds = servers.OfType<Outbound4Sbox>().ToList();
var endpoints = servers.Where(s => s is Endpoints4Sbox).Cast<Endpoints4Sbox>().ToList(); var endpoints = servers.OfType<Endpoints4Sbox>().ToList();
singboxConfig.endpoints ??= []; singboxConfig.endpoints ??= [];
if (prepend) if (prepend)
{ {
@@ -96,6 +96,15 @@ public partial class CoreConfigSingboxService
new() { protocol = ["dns"] }, new() { protocol = ["dns"] },
], ],
}); });
if (_config.CoreBasicItem.EnableFinalFragment)
{
_coreConfig.route.rules.Add(new()
{
protocol = ["tls"],
action = "route-options",
tls_record_fragment = true,
});
}
} }
else else
{ {
@@ -104,6 +113,14 @@ public partial class CoreConfigSingboxService
port = [53], port = [53],
action = "hijack-dns", action = "hijack-dns",
}); });
if (_config.CoreBasicItem.EnableFinalFragment)
{
_coreConfig.route.rules.Add(new()
{
action = "route-options",
tls_record_fragment = true,
});
}
} }
var hostsDomains = new List<string>(); var hostsDomains = new List<string>();
@@ -176,12 +193,12 @@ public partial class CoreConfigSingboxService
_coreConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
outbound = Global.DirectTag, outbound = Global.DirectTag,
clash_mode = ERuleMode.Direct.ToString() clash_mode = nameof(ERuleMode.Direct)
}); });
_coreConfig.route.rules.Add(new() _coreConfig.route.rules.Add(new()
{ {
outbound = Global.ProxyTag, outbound = Global.ProxyTag,
clash_mode = ERuleMode.Global.ToString() clash_mode = nameof(ERuleMode.Global)
}); });
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty(); var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty();
@@ -18,15 +18,15 @@ public partial class CoreConfigSingboxService
//convert route geosite & geoip to ruleset //convert route geosite & geoip to ruleset
foreach (var rule in _coreConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) foreach (var rule in _coreConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
{ {
rule.rule_set ??= new List<string>(); rule.rule_set ??= [];
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList() ?? []);
rule.geosite = null; rule.geosite = null;
AddRuleSets(ruleSets, rule.rule_set); AddRuleSets(ruleSets, rule.rule_set);
} }
foreach (var rule in _coreConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) foreach (var rule in _coreConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
{ {
rule.rule_set ??= new List<string>(); rule.rule_set ??= [];
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList() ?? []);
rule.geoip = null; rule.geoip = null;
AddRuleSets(ruleSets, rule.rule_set); AddRuleSets(ruleSets, rule.rule_set);
} }
@@ -34,14 +34,14 @@ public partial class CoreConfigSingboxService
//convert dns geosite & geoip to ruleset //convert dns geosite & geoip to ruleset
foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
{ {
rule.rule_set ??= new List<string>(); rule.rule_set ??= [];
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList() ?? []);
rule.geosite = null; rule.geosite = null;
} }
foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
{ {
rule.rule_set ??= new List<string>(); rule.rule_set ??= [];
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList() ?? []);
rule.geoip = null; rule.geoip = null;
} }
foreach (var dnsRule in _coreConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) foreach (var dnsRule in _coreConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
@@ -60,6 +60,10 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{ {
ApplyOutboundFragment(); ApplyOutboundFragment();
} }
if (_config.CoreBasicItem.EnableFinalFragment)
{
ApplyFinalFragment();
}
ApplyOutboundBindInterface(); ApplyOutboundBindInterface();
ApplyOutboundSendThrough(); ApplyOutboundSendThrough();
@@ -161,7 +165,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{ {
listen = Global.Loopback, listen = Global.Loopback,
port = port, port = port,
protocol = EInboundProtocol.mixed.ToString(), protocol = nameof(EInboundProtocol.mixed),
settings = new Inboundsettings4Ray() settings = new Inboundsettings4Ray()
{ {
udp = true, udp = true,
@@ -204,6 +208,10 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{ {
ApplyOutboundFragment(); ApplyOutboundFragment();
} }
if (_config.CoreBasicItem.EnableFinalFragment)
{
ApplyFinalFragment();
}
ApplyOutboundBindInterface(); ApplyOutboundBindInterface();
ApplyOutboundSendThrough(); ApplyOutboundSendThrough();
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
@@ -262,7 +270,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
tag = $"{EInboundProtocol.socks}{port}", tag = $"{EInboundProtocol.socks}{port}",
listen = Global.Loopback, listen = Global.Loopback,
port = port, port = port,
protocol = EInboundProtocol.mixed.ToString(), protocol = nameof(EInboundProtocol.mixed),
settings = new Inboundsettings4Ray() settings = new Inboundsettings4Ray()
{ {
udp = true, udp = true,
@@ -276,6 +284,10 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{ {
ApplyOutboundFragment(); ApplyOutboundFragment();
} }
if (_config.CoreBasicItem.EnableFinalFragment)
{
ApplyFinalFragment();
}
ApplyOutboundBindInterface(); ApplyOutboundBindInterface();
ApplyOutboundSendThrough(); ApplyOutboundSendThrough();
@@ -56,7 +56,7 @@ public partial class CoreConfigV2rayService
} }
else else
{ {
_coreConfig.burstObservatory.subjectSelector ??= new(); _coreConfig.burstObservatory.subjectSelector ??= [];
_coreConfig.burstObservatory.subjectSelector.Add(baseTagName); _coreConfig.burstObservatory.subjectSelector.Add(baseTagName);
} }
} }
@@ -75,7 +75,7 @@ public partial class CoreConfigV2rayService
} }
else else
{ {
_coreConfig.observatory.subjectSelector ??= new(); _coreConfig.observatory.subjectSelector ??= [];
_coreConfig.observatory.subjectSelector.Add(baseTagName); _coreConfig.observatory.subjectSelector.Add(baseTagName);
} }
} }
@@ -104,8 +104,9 @@ public partial class CoreConfigV2rayService
}, },
}, },
tag = balancerTag, tag = balancerTag,
fallbackTag = multipleLoad == EMultipleLoad.Fallback ? Global.DirectTag : null,
}; };
_coreConfig.routing.balancers ??= new(); _coreConfig.routing.balancers ??= [];
_coreConfig.routing.balancers.Add(balancer); _coreConfig.routing.balancers.Add(balancer);
} }
} }
@@ -46,10 +46,7 @@ public partial class CoreConfigV2rayService
} }
// Ensure routing node exists // Ensure routing node exists
if (fullConfigTemplateNode["routing"] == null) fullConfigTemplateNode["routing"] ??= new JsonObject();
{
fullConfigTemplateNode["routing"] = new JsonObject();
}
// Handle balancers - append instead of override // Handle balancers - append instead of override
if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode) if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode)
@@ -34,7 +34,7 @@ public partial class CoreConfigV2rayService
return; return;
} }
var simpleDnsItem = context.SimpleDnsItem; var simpleDnsItem = context.SimpleDnsItem;
var dnsItem = _coreConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray(); var dnsItem = _coreConfig.dns as Dns4Ray ?? new Dns4Ray();
var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs; var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs;
//Outbound Freedom domainStrategy //Outbound Freedom domainStrategy
@@ -246,7 +246,7 @@ public partial class CoreConfigV2rayService
var useDirectDns = false; var useDirectDns = false;
if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag) if (rules?.LastOrDefault() is { OutboundTag: Global.DirectTag } lastRule)
{ {
var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0; var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0;
var noProcess = lastRule.Process == null || lastRule.Process.Count == 0; var noProcess = lastRule.Process == null || lastRule.Process.Count == 0;
@@ -384,9 +384,11 @@ public partial class CoreConfigV2rayService
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
if (outbound != null) if (outbound != null)
{ {
outbound.settings = new(); outbound.settings = new()
outbound.settings.domainStrategy = domainStrategy4Freedom; {
outbound.settings.userLevel = 0; domainStrategy = domainStrategy4Freedom,
userLevel = 0,
};
} }
} }
@@ -30,14 +30,19 @@ public partial class CoreConfigV2rayService
inbound3.listen = listen; inbound3.listen = listen;
_coreConfig.inbounds.Add(inbound3); _coreConfig.inbounds.Add(inbound3);
//auth // auth
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
{ {
inbound3.settings.auth = "password"; inbound3.settings.auth = "password";
inbound3.settings.accounts = new List<AccountsItem4Ray> inbound3.settings.accounts =
{ [
new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } new()
}; {
user = _config.Inbound.First().User,
pass = _config.Inbound.First().Pass,
},
];
} }
} }
else else
@@ -53,12 +58,15 @@ public partial class CoreConfigV2rayService
{ {
_config.TunModeItem.Mtu = Global.TunMtus.First(); _config.TunModeItem.Mtu = Global.TunMtus.First();
} }
var tunInbound = JsonUtils.Deserialize<Inbounds4Ray>(EmbedUtils.GetEmbedText(Global.V2raySampleTunInbound)) ?? new Inbounds4Ray { }; var tunInbound =
JsonUtils.Deserialize<Inbounds4Ray>(EmbedUtils.GetEmbedText(Global.V2raySampleTunInbound)) ??
new Inbounds4Ray();
tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun"; tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun";
tunInbound.settings.MTU = _config.TunModeItem.Mtu; tunInbound.settings.MTU = _config.TunModeItem.Mtu;
if (_config.TunModeItem.EnableIPv6Address == false) if (!_config.TunModeItem.EnableIPv6Address)
{ {
tunInbound.settings.gateway = ["172.18.0.1/30"]; tunInbound.settings.gateway = ["172.18.0.1/30"];
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
} }
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx(); var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
if (!bindInterface.IsNullOrEmpty()) if (!bindInterface.IsNullOrEmpty())
@@ -66,6 +74,53 @@ public partial class CoreConfigV2rayService
tunInbound.settings.autoOutboundsInterface = bindInterface; tunInbound.settings.autoOutboundsInterface = bindInterface;
} }
tunInbound.sniffing = inbound.sniffing; tunInbound.sniffing = inbound.sniffing;
if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
{
var wholeInternet = IPNetwork2.Parse("0.0.0.0/0");
var wholeInternetV6 = IPNetwork2.Parse("::/0");
var excludeList = _config.TunModeItem.RouteExcludeAddress.Select(IPNetwork2.Parse)
.Where(x => x != null).ToList();
var includeList = new List<IPNetwork2> { wholeInternet };
var includeListV6 = new List<IPNetwork2> { wholeInternetV6 };
foreach (var exclude in excludeList)
{
var temp = new List<IPNetwork2>();
if (exclude.AddressFamily == AddressFamily.InterNetwork)
{
foreach (var net in includeList)
{
temp.AddRange(net.Subtract(exclude));
}
includeList = temp;
}
else if (exclude.AddressFamily == AddressFamily.InterNetworkV6)
{
foreach (var net in includeListV6)
{
temp.AddRange(net.Subtract(exclude));
}
includeListV6 = temp;
}
}
includeList = IPNetwork2.Supernet(includeList.ToArray()).ToList();
includeListV6 = IPNetwork2.Supernet(includeListV6.ToArray()).ToList();
if (_config.TunModeItem.EnableIPv6Address)
{
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString())
.Concat(includeListV6.Select(x => x.ToString())).ToList();
}
else
{
tunInbound.settings.autoSystemRoutingTable = includeList.Select(x => x.ToString()).ToList();
}
}
_coreConfig.inbounds.Add(tunInbound); _coreConfig.inbounds.Add(tunInbound);
} }
} }
@@ -90,7 +145,7 @@ public partial class CoreConfigV2rayService
} }
inbound.tag = protocol.ToString(); inbound.tag = protocol.ToString();
inbound.port = inItem.LocalPort + (int)protocol; inbound.port = inItem.LocalPort + (int)protocol;
inbound.protocol = EInboundProtocol.mixed.ToString(); inbound.protocol = nameof(EInboundProtocol.mixed);
inbound.settings.udp = inItem.UdpEnabled; inbound.settings.udp = inItem.UdpEnabled;
inbound.sniffing.enabled = inItem.SniffingEnabled; inbound.sniffing.enabled = inItem.SniffingEnabled;
inbound.sniffing.destOverride = inItem.DestOverride; inbound.sniffing.destOverride = inItem.DestOverride;
@@ -62,7 +62,7 @@ public partial class CoreConfigV2rayService
try try
{ {
var protocolExtra = _node.GetProtocolExtra(); var protocolExtra = _node.GetProtocolExtra();
var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; var muxEnabled = _node.MuxEnabled ?? false;
switch (_node.ConfigType) switch (_node.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
@@ -243,9 +243,9 @@ public partial class CoreConfigV2rayService
version = 2, version = 2,
address = _node.Address, address = _node.Address,
port = _node.Port, port = _node.Port,
vnext = null,
servers = null,
}; };
outbound.settings.vnext = null;
outbound.settings.servers = null;
break; break;
} }
case EConfigType.WireGuard: case EConfigType.WireGuard:
@@ -380,10 +380,11 @@ public partial class CoreConfigV2rayService
TlsSettings4Ray tlsSettings = new() TlsSettings4Ray tlsSettings = new()
{ {
allowInsecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure), allowInsecure = _node.GetAllowInsecure(),
alpn = _node.GetAlpn(), alpn = _node.GetAlpn(),
fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint, fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint,
echConfigList = _node.EchConfigList.NullIfEmpty(), echConfigList = _node.EchConfigList.NullIfEmpty(),
verifyPeerCertByName = _node.VerifyPeerCertByName.NullIfEmpty(),
}; };
if (sni.IsNotEmpty()) if (sni.IsNotEmpty())
{ {
@@ -449,14 +450,13 @@ public partial class CoreConfigV2rayService
KcpSettings4Ray kcpSettings = new() KcpSettings4Ray kcpSettings = new()
{ {
mtu = kcpMtu, mtu = kcpMtu,
tti = _config.KcpItem.Tti tti = _config.KcpItem.Tti,
uplinkCapacity = _config.KcpItem.UplinkCapacity,
downlinkCapacity = _config.KcpItem.DownlinkCapacity,
cwndMultiplier = _config.KcpItem.CwndMultiplier,
maxSendingWindow = _config.KcpItem.MaxSendingWindow,
}; };
kcpSettings.uplinkCapacity = _config.KcpItem.UplinkCapacity;
kcpSettings.downlinkCapacity = _config.KcpItem.DownlinkCapacity;
kcpSettings.cwndMultiplier = _config.KcpItem.CwndMultiplier;
kcpSettings.maxSendingWindow = _config.KcpItem.MaxSendingWindow;
var kcpFinalmask = new Finalmask4Ray(); var kcpFinalmask = new Finalmask4Ray();
if (Global.KcpHeaderMaskMap.TryGetValue(headerType, out var header)) if (Global.KcpHeaderMaskMap.TryGetValue(headerType, out var header))
{ {
@@ -464,8 +464,8 @@ public partial class CoreConfigV2rayService
[ [
new Mask4Ray new Mask4Ray
{ {
type = header, type = "mkcp-legacy",
settings = null settings = new MaskSettings4Ray { header = header },
} }
]; ];
} }
@@ -474,15 +474,15 @@ public partial class CoreConfigV2rayService
{ {
kcpFinalmask.udp.Add(new Mask4Ray kcpFinalmask.udp.Add(new Mask4Ray
{ {
type = "mkcp-original" type = "mkcp-legacy",
}); });
} }
else else
{ {
kcpFinalmask.udp.Add(new Mask4Ray kcpFinalmask.udp.Add(new Mask4Ray
{ {
type = "mkcp-aes128gcm", type = "mkcp-legacy",
settings = new MaskSettings4Ray { password = kcpSeed } settings = new MaskSettings4Ray { value = kcpSeed },
}); });
} }
streamSettings.kcpSettings = kcpSettings; streamSettings.kcpSettings = kcpSettings;
@@ -530,7 +530,7 @@ public partial class CoreConfigV2rayService
break; break;
//xhttp //xhttp
case nameof(ETransport.xhttp): case nameof(ETransport.xhttp):
streamSettings.network = ETransport.xhttp.ToString(); streamSettings.network = nameof(ETransport.xhttp);
XhttpSettings4Ray xhttpSettings = new(); XhttpSettings4Ray xhttpSettings = new();
if (path.IsNotEmpty()) if (path.IsNotEmpty())
@@ -825,29 +825,7 @@ public partial class CoreConfigV2rayService
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)) && (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
.ToList(); .ToList();
var configPackets = _config.Fragment4RayItem?.Packets ?? "tlshello"; var (fragmentMask, noiseMask) = BuildFragmentsMasks();
var configLength = _config.Fragment4RayItem?.Length ?? "50-100";
var configDelay = _config.Fragment4RayItem?.Interval ?? "10-20";
var fragmentMask = new Mask4Ray
{
type = "fragment",
settings = new MaskSettings4Ray
{
packets = configPackets,
length = configLength,
delay = configDelay,
}
};
var noiseMask = new Mask4Ray
{
type = "noise",
settings = new MaskSettings4Ray
{
length = "10-20",
delay = "10-16",
}
};
foreach (var outbound in actOutboundWithTlsList) foreach (var outbound in actOutboundWithTlsList)
{ {
@@ -881,4 +859,67 @@ public partial class CoreConfigV2rayService
outbound.streamSettings.finalmask = finalMaskJsonObj; outbound.streamSettings.finalmask = finalMaskJsonObj;
} }
} }
private void ApplyFinalFragment()
{
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
var actOutboundList = _coreConfig.outbounds.Where(n => n.tag.StartsWith(Global.ProxyTag)).ToList();
var fragmentFreedom = new Outbounds4Ray()
{
tag = $"{Global.ProxyTag}-fragment-freedom",
protocol = "freedom",
streamSettings = new StreamSettings4Ray
{
finalmask = new Finalmask4Ray
{
tcp = [fragmentMask],
udp = [noiseMask],
}
}
};
foreach (var outbound in actOutboundList)
{
var index = _coreConfig.outbounds.IndexOf(outbound);
var originalTag = outbound.tag;
var afterTag = $"fragment-{originalTag}";
var cloneFragmentFreedom = JsonUtils.DeepCopy(fragmentFreedom);
cloneFragmentFreedom.tag = originalTag;
cloneFragmentFreedom.streamSettings.sockopt ??= new();
cloneFragmentFreedom.streamSettings.sockopt.dialerProxy = afterTag;
outbound.tag = afterTag;
_coreConfig.outbounds.Insert(index, cloneFragmentFreedom);
}
}
private (Mask4Ray tcpFragment, Mask4Ray udpNoise) BuildFragmentsMasks()
{
var configPackets = _config.Fragment4RayItem?.Packets ?? "tlshello";
var configLength = _config.Fragment4RayItem?.Length ?? "50-100";
var configDelay = _config.Fragment4RayItem?.Interval ?? "10-20";
var fragmentMask = new Mask4Ray
{
type = "fragment",
settings = new MaskSettings4Ray
{
packets = configPackets,
length = configLength,
delay = configDelay,
}
};
var noiseMask = new Mask4Ray
{
type = "noise",
settings = new MaskSettings4Ray
{
length = "10-20",
delay = "10-16",
}
};
return (fragmentMask, noiseMask);
}
} }
@@ -6,45 +6,19 @@ public partial class CoreConfigV2rayService
{ {
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
{ {
var tag = EInboundProtocol.api.ToString(); Metrics4Ray metricsObj = new();
Metrics4Ray apiObj = new();
Policy4Ray policyObj = new(); Policy4Ray policyObj = new();
SystemPolicy4Ray policySystemSetting = new(); SystemPolicy4Ray policySystemSetting = new();
_coreConfig.stats = new Stats4Ray(); _coreConfig.stats = new Stats4Ray();
apiObj.tag = tag; metricsObj.listen = $"{Global.Loopback}:{AppManager.Instance.StatePort}";
_coreConfig.metrics = apiObj; _coreConfig.metrics = metricsObj;
policySystemSetting.statsOutboundDownlink = true; policySystemSetting.statsOutboundDownlink = true;
policySystemSetting.statsOutboundUplink = true; policySystemSetting.statsOutboundUplink = true;
policyObj.system = policySystemSetting; policyObj.system = policySystemSetting;
_coreConfig.policy = policyObj; _coreConfig.policy = policyObj;
if (!_coreConfig.inbounds.Exists(item => item.tag == tag))
{
Inbounds4Ray apiInbound = new();
Inboundsettings4Ray apiInboundSettings = new();
apiInbound.tag = tag;
apiInbound.listen = Global.Loopback;
apiInbound.port = AppManager.Instance.StatePort;
apiInbound.protocol = Global.InboundAPIProtocol;
apiInboundSettings.address = Global.Loopback;
apiInbound.settings = apiInboundSettings;
_coreConfig.inbounds.Add(apiInbound);
}
if (!_coreConfig.routing.rules.Exists(item => item.outboundTag == tag))
{
RulesItem4Ray apiRoutingRule = new()
{
inboundTag = new List<string> { tag },
outboundTag = tag,
type = "field"
};
_coreConfig.routing.rules.Add(apiRoutingRule);
}
} }
} }
} }
+35 -13
View File
@@ -3,7 +3,7 @@ using System.Net.Http.Headers;
namespace ServiceLib.Services; namespace ServiceLib.Services;
/// <summary> /// <summary>
///Download /// Download
/// </summary> /// </summary>
public class DownloadService public class DownloadService
{ {
@@ -13,7 +13,10 @@ public class DownloadService
private static readonly string _tag = "DownloadService"; private static readonly string _tag = "DownloadService";
public async Task<int> DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Func<bool, string, Task> updateFunc) /// <summary>
/// Downloads data with the specified proxy and reports progress messages.
/// </summary>
public async Task<int> DownloadDataAsync(string url, IWebProxy webProxy, int downloadTimeout, Func<bool, string, Task> updateFunc)
{ {
try try
{ {
@@ -36,6 +39,9 @@ public class DownloadService
return 0; return 0;
} }
/// <summary>
/// Downloads a file and reports progress through events.
/// </summary>
public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout) public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout)
{ {
try try
@@ -64,6 +70,9 @@ public class DownloadService
} }
} }
/// <summary>
/// Gets redirect target URL without following redirects automatically.
/// </summary>
public async Task<string?> UrlRedirectAsync(string url, bool blProxy) public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
{ {
var webRequestHandler = new SocketsHttpHandler var webRequestHandler = new SocketsHttpHandler
@@ -86,11 +95,23 @@ public class DownloadService
} }
} }
/// <summary>
/// Tries to download string content using proxy switch setting.
/// </summary>
public async Task<string?> TryDownloadString(string url, bool blProxy, string userAgent) public async Task<string?> TryDownloadString(string url, bool blProxy, string userAgent)
{
var webProxy = await GetWebProxy(blProxy);
return await TryDownloadString(url, webProxy, userAgent);
}
/// <summary>
/// Tries to download string content with a specified proxy.
/// </summary>
public async Task<string?> TryDownloadString(string url, IWebProxy? webProxy, string userAgent)
{ {
try try
{ {
var result1 = await DownloadStringAsync(url, blProxy, userAgent, 15); var result1 = await DownloadStringAsync(url, webProxy, userAgent, 15);
if (result1.IsNotEmpty()) if (result1.IsNotEmpty())
{ {
return result1; return result1;
@@ -108,7 +129,7 @@ public class DownloadService
try try
{ {
var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent, 15); var result2 = await DownloadStringViaDownloader(url, webProxy, userAgent, 15);
if (result2.IsNotEmpty()) if (result2.IsNotEmpty())
{ {
return result2; return result2;
@@ -128,14 +149,12 @@ public class DownloadService
} }
/// <summary> /// <summary>
/// DownloadString /// Downloads string content via HttpClient.
/// </summary> /// </summary>
/// <param name="url"></param> private async Task<string?> DownloadStringAsync(string url, IWebProxy? webProxy, string userAgent, int timeout)
private async Task<string?> DownloadStringAsync(string url, bool blProxy, string userAgent, int timeout)
{ {
try try
{ {
var webProxy = await GetWebProxy(blProxy);
var client = new HttpClient(new SocketsHttpHandler() var client = new HttpClient(new SocketsHttpHandler()
{ {
Proxy = webProxy, Proxy = webProxy,
@@ -172,15 +191,12 @@ public class DownloadService
} }
/// <summary> /// <summary>
/// DownloadString /// Downloads string content via DownloaderHelper.
/// </summary> /// </summary>
/// <param name="url"></param> private async Task<string?> DownloadStringViaDownloader(string url, IWebProxy? webProxy, string userAgent, int timeout)
private async Task<string?> DownloadStringViaDownloader(string url, bool blProxy, string userAgent, int timeout)
{ {
try try
{ {
var webProxy = await GetWebProxy(blProxy);
if (userAgent.IsNullOrEmpty()) if (userAgent.IsNullOrEmpty())
{ {
userAgent = Utils.GetVersion(false); userAgent = Utils.GetVersion(false);
@@ -200,6 +216,9 @@ public class DownloadService
return null; return null;
} }
/// <summary>
/// Creates local SOCKS proxy when proxy switch is enabled.
/// </summary>
private async Task<WebProxy?> GetWebProxy(bool blProxy) private async Task<WebProxy?> GetWebProxy(bool blProxy)
{ {
if (!blProxy) if (!blProxy)
@@ -215,6 +234,9 @@ public class DownloadService
return new WebProxy($"socks5://{Global.Loopback}:{port}"); return new WebProxy($"socks5://{Global.Loopback}:{port}");
} }
/// <summary>
/// Checks whether the specified TCP endpoint is reachable.
/// </summary>
private async Task<bool> SocketCheck(string ip, int port) private async Task<bool> SocketCheck(string ip, int port)
{ {
try try
+56 -29
View File
@@ -7,7 +7,9 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private static readonly string _tag = "SpeedtestService"; private static readonly string _tag = "SpeedtestService";
private readonly Config? _config = config; private readonly Config? _config = config;
private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc; private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc;
private static readonly ConcurrentBag<string> _lstExitLoop = new(); private static readonly ConcurrentBag<string> _lstExitLoop = [];
private readonly int _speedTestPageSize = config.SpeedTestItem.SpeedTestPageSize ?? Global.SpeedTestPageSize;
private readonly TimeSpan _delayInterval = TimeSpan.FromSeconds(config.SpeedTestItem.SpeedTestDelayInterval ?? 1);
public void RunLoop(ESpeedActionType actionType, List<ProfileItem> selecteds) public void RunLoop(ESpeedActionType actionType, List<ProfileItem> selecteds)
{ {
@@ -31,7 +33,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private static bool ShouldStopTest(string exitLoopKey) private static bool ShouldStopTest(string exitLoopKey)
{ {
return !_lstExitLoop.Any(p => p == exitLoopKey); return _lstExitLoop.All(p => p != exitLoopKey);
} }
private async Task RunAsync(ESpeedActionType actionType, List<ProfileItem> selecteds) private async Task RunAsync(ESpeedActionType actionType, List<ProfileItem> selecteds)
@@ -135,36 +137,43 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task RunTcpingAsync(List<ServerTestItem> selecteds) private async Task RunTcpingAsync(List<ServerTestItem> selecteds)
{ {
List<Task> tasks = []; var pageSize = Math.Min(selecteds.Count, _speedTestPageSize);
foreach (var it in selecteds) var lstBatch = GetTestBatchItem(selecteds, pageSize);
{
tasks.Add(Task.Run(async () =>
{
try
{
var responseTime = await GetTcpingTime(it.Address, it.Port);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); foreach (var lst in lstBatch)
await UpdateFunc(it.IndexId, responseTime.ToString()); {
} List<Task> tasks = [];
catch (Exception ex) foreach (var it in lst)
{
tasks.Add(Task.Run(async () =>
{ {
Logging.SaveLog(_tag, ex); try
} {
})); var responseTime = await GetTcpingTime(it.Address, it.Port);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString());
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}));
}
await Task.WhenAll(tasks);
await Task.Delay(_delayInterval);
} }
await Task.WhenAll(tasks);
} }
private async Task RunRealPingBatchAsync(List<ServerTestItem> lstSelected, string exitLoopKey, int pageSize = 0) private async Task RunRealPingBatchAsync(List<ServerTestItem> lstSelected, string exitLoopKey, int pageSize = 0)
{ {
if (pageSize <= 0) if (pageSize <= 0)
{ {
pageSize = lstSelected.Count < Global.SpeedTestPageSize ? lstSelected.Count : Global.SpeedTestPageSize; pageSize = Math.Min(lstSelected.Count, _speedTestPageSize);
} }
var lstTest = GetTestBatchItem(lstSelected, pageSize); var lstTest = GetTestBatchItem(lstSelected, pageSize);
List<ServerTestItem> lstFailed = new(); List<ServerTestItem> lstFailed = [];
foreach (var lst in lstTest) foreach (var lst in lstTest)
{ {
var ret = await RunRealPingAsync(lst, exitLoopKey); var ret = await RunRealPingAsync(lst, exitLoopKey);
@@ -172,7 +181,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{ {
lstFailed.AddRange(lst); lstFailed.AddRange(lst);
} }
await Task.Delay(100); await Task.Delay(_delayInterval);
} }
//Retest the failed part //Retest the failed part
@@ -210,7 +219,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
} }
await Task.Delay(1000); await Task.Delay(1000);
List<Task> tasks = new(); List<Task> tasks = [];
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (!it.AllowTest) if (!it.AllowTest)
@@ -249,11 +258,11 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{ {
if (pageSize <= 0) if (pageSize <= 0)
{ {
pageSize = lstSelected.Count < Global.SpeedTestPageSize ? lstSelected.Count : Global.SpeedTestPageSize; pageSize = Math.Min(lstSelected.Count, _speedTestPageSize);
} }
var lstTest = GetTestBatchItem(lstSelected, pageSize); var lstTest = GetTestBatchItem(lstSelected, pageSize);
List<ServerTestItem> lstFailed = new(); List<ServerTestItem> lstFailed = [];
foreach (var lst in lstTest) foreach (var lst in lstTest)
{ {
var ret = await RunUdpTestAsync(lst, exitLoopKey); var ret = await RunUdpTestAsync(lst, exitLoopKey);
@@ -261,7 +270,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{ {
lstFailed.AddRange(lst); lstFailed.AddRange(lst);
} }
await Task.Delay(100); await Task.Delay(_delayInterval);
} }
//Retest the failed part //Retest the failed part
@@ -291,7 +300,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
} }
await Task.Delay(1000); await Task.Delay(1000);
List<Task> tasks = new(); List<Task> tasks = [];
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (!it.AllowTest) if (!it.AllowTest)
@@ -329,7 +338,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{ {
using var concurrencySemaphore = new SemaphoreSlim(concurrencyCount); using var concurrencySemaphore = new SemaphoreSlim(concurrencyCount);
var downloadHandle = new DownloadService(); var downloadHandle = new DownloadService();
List<Task> tasks = new(); List<Task> tasks = [];
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (ShouldStopTest(exitLoopKey)) if (ShouldStopTest(exitLoopKey))
@@ -392,10 +401,23 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task<int> DoRealPing(ServerTestItem it) private async Task<int> DoRealPing(ServerTestItem it)
{ {
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
var responseTime = await ConnectionHandler.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10); var responseTime = await ConnectionHandler.GetRealPingTime(webProxy, 10);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString()); await UpdateFunc(it.IndexId, responseTime.ToString());
if (responseTime > 0)
{
var ipInfo = await ConnectionHandler.GetIPInfo(webProxy);
var ipStr = ipInfo?.ToString() ?? Global.None;
ProfileExManager.Instance.SetTestIpInfo(it.IndexId, ipStr);
await UpdateIpInfoFunc(it.IndexId, ipStr);
}
else
{
await UpdateIpInfoFunc(it.IndexId, ResUI.SpeedtestingSkip);
}
return responseTime; return responseTime;
} }
@@ -467,7 +489,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize) private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
{ {
List<List<ServerTestItem>> lstTest = new(); List<List<ServerTestItem>> lstTest = [];
var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList(); var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList();
var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList(); var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList();
@@ -491,4 +513,9 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
ProfileExManager.Instance.SetTestMessage(indexId, speed); ProfileExManager.Instance.SetTestMessage(indexId, speed);
} }
} }
private async Task UpdateIpInfoFunc(string indexId, string ip)
{
await _updateFunc?.Invoke(new() { IndexId = indexId, IpInfo = ip });
}
} }
@@ -117,6 +117,11 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
var msgs = new List<string>(); var msgs = new List<string>();
foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes()) foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes())
{ {
if (!(_config.CheckUpdateItem.SelectedCoreTypes?.Contains(type.ToString()) ?? true))
{
continue;
}
var result = await CheckHasUpdateOnly(type, preRelease); var result = await CheckHasUpdateOnly(type, preRelease);
if (result.Success && result.Version != null) if (result.Success && result.Version != null)
{ {
@@ -337,6 +342,7 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
{ {
Architecture.Arm64 => coreInfo?.DownloadUrlLinuxArm64, Architecture.Arm64 => coreInfo?.DownloadUrlLinuxArm64,
Architecture.RiscV64 => coreInfo?.DownloadUrlLinuxRiscV64, Architecture.RiscV64 => coreInfo?.DownloadUrlLinuxRiscV64,
Architecture.LoongArch64 => coreInfo?.DownloadUrlLinuxLoong64,
Architecture.X64 => coreInfo?.DownloadUrlLinux64, Architecture.X64 => coreInfo?.DownloadUrlLinux64,
_ => null, _ => null,
}; };
@@ -31,8 +31,7 @@ public sealed class WindowsJobService : IDisposable
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
(uint)length)) (uint)length))
{ {
throw new Exception(string.Format("Unable to set information. Error: {0}", throw new Exception($"Unable to set information. Error: {Marshal.GetLastWin32Error()}");
Marshal.GetLastWin32Error()));
} }
} }
finally finally
@@ -216,7 +216,7 @@ public class AddGroupServerViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.PleaseAddAtLeastOneServer); NoticeManager.Instance.Enqueue(ResUI.PleaseAddAtLeastOneServer);
return; return;
} }
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? ECoreType.Xray : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? ECoreType.Xray : Enum.Parse<ECoreType>(CoreType);
if (SelectedSource.CoreType is not (ECoreType.Xray or ECoreType.sing_box) || if (SelectedSource.CoreType is not (ECoreType.Xray or ECoreType.sing_box) ||
SelectedSource.ConfigType is not (EConfigType.ProxyChain or EConfigType.PolicyGroup)) SelectedSource.ConfigType is not (EConfigType.ProxyChain or EConfigType.PolicyGroup))
{ {
@@ -50,7 +50,7 @@ public class AddServer2ViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom); NoticeManager.Instance.Enqueue(ResUI.FillServerAddressCustom);
return; return;
} }
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : Enum.Parse<ECoreType>(CoreType);
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0) if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
{ {
@@ -8,6 +8,12 @@ public class AddServerViewModel : MyReactiveObject
[Reactive] [Reactive]
public string? CoreType { get; set; } public string? CoreType { get; set; }
[Reactive]
public bool AllowInsecure { get; set; }
[Reactive]
public bool MuxEnabled { get; set; }
[Reactive] [Reactive]
public string Cert { get; set; } public string Cert { get; set; }
@@ -273,8 +279,10 @@ public class AddServerViewModel : MyReactiveObject
SelectedSource = JsonUtils.DeepCopy(profileItem); SelectedSource = JsonUtils.DeepCopy(profileItem);
} }
CoreType = SelectedSource?.CoreType?.ToString(); CoreType = SelectedSource?.CoreType?.ToString();
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty; AllowInsecure = SelectedSource?.GetAllowInsecure() == true;
CertSha = SelectedSource?.CertSha?.ToString() ?? string.Empty; MuxEnabled = SelectedSource?.MuxEnabled == true;
Cert = SelectedSource?.Cert ?? string.Empty;
CertSha = SelectedSource?.CertSha ?? string.Empty;
var protocolExtra = SelectedSource?.GetProtocolExtra() ?? new(); var protocolExtra = SelectedSource?.GetProtocolExtra() ?? new();
var transport = SelectedSource?.GetTransportExtra() ?? new(); var transport = SelectedSource?.GetTransportExtra() ?? new();
@@ -352,7 +360,9 @@ public class AddServerViewModel : MyReactiveObject
return; return;
} }
} }
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : Enum.Parse<ECoreType>(CoreType);
SelectedSource.AllowInsecure = AllowInsecure ? Global.StringTrue : Global.StringFalse;
SelectedSource.MuxEnabled = MuxEnabled;
SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert; SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert;
SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha; SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha;
if (!Global.Networks.Contains(SelectedSource.Network)) if (!Global.Networks.Contains(SelectedSource.Network))
@@ -464,7 +474,9 @@ public class AddServerViewModel : MyReactiveObject
domain += $":{SelectedSource.Port}"; domain += $":{SelectedSource.Port}";
} }
(Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName, allowInsecure: AllowInsecureCertFetch); (Cert, var certError) = await CertPemManager.Instance.GetCertPemAsync(domain, serverName,
verifyPeerCertByName: Utils.String2List(SelectedSource.VerifyPeerCertByName),
allowInsecure: AllowInsecureCertFetch);
UpdateCertTip(certError); UpdateCertTip(certError);
} }
@@ -489,7 +501,9 @@ public class AddServerViewModel : MyReactiveObject
domain += $":{SelectedSource.Port}"; domain += $":{SelectedSource.Port}";
} }
var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName, allowInsecure: AllowInsecureCertFetch); var (certs, certError) = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName,
verifyPeerCertByName: Utils.String2List(SelectedSource.VerifyPeerCertByName),
allowInsecure: AllowInsecureCertFetch);
Cert = CertPemManager.ConcatenatePemChain(certs); Cert = CertPemManager.ConcatenatePemChain(certs);
UpdateCertTip(certError); UpdateCertTip(certError);
} }
@@ -36,7 +36,7 @@ public class CheckUpdateViewModel : MyReactiveObject
this.WhenAnyValue( this.WhenAnyValue(
x => x.EnableCheckPreReleaseUpdate, x => x.EnableCheckPreReleaseUpdate,
y => y == true) y => y == true)
.Subscribe(c => _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate); .Subscribe(c => _ = OnCheckPreReleaseUpdateChanged());
RefreshCheckUpdateItems(); RefreshCheckUpdateItems();
} }
@@ -87,12 +87,23 @@ public class CheckUpdateViewModel : MyReactiveObject
}; };
} }
private async Task OnCheckPreReleaseUpdateChanged()
{
if (_config.CheckUpdateItem.CheckPreReleaseUpdate == EnableCheckPreReleaseUpdate)
{
return;
}
_config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate;
await SaveSelectedCoreTypes();
}
private async Task SaveSelectedCoreTypes() private async Task SaveSelectedCoreTypes()
{ {
_config.CheckUpdateItem.SelectedCoreTypes = CheckUpdateModels _config.CheckUpdateItem.SelectedCoreTypes =
.Where(t => t.IsSelected == true) CheckUpdateModels.Where(t => t.IsSelected == true)
.Select(t => t.CoreTypeForStorage) .Select(t => t.CoreTypeForStorage)
.ToList(); .ToList();
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
} }
@@ -69,7 +69,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
var dtNow = DateTime.Now; var dtNow = DateTime.Now;
var lstModel = new List<ClashConnectionModel>(); var lstModel = new List<ClashConnectionModel>();
foreach (var item in connections ?? new()) foreach (var item in connections ?? [])
{ {
var host = $"{(item.metadata.host.IsNullOrEmpty() ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}"; var host = $"{(item.metadata.host.IsNullOrEmpty() ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}";
if (HostFilter.IsNotEmpty() && !host.Contains(HostFilter)) if (HostFilter.IsNotEmpty() && !host.Contains(HostFilter))
@@ -85,7 +85,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
Host = host, Host = host,
Time = (dtNow - item.start).TotalSeconds < 0 ? 1 : (dtNow - item.start).TotalSeconds, Time = (dtNow - item.start).TotalSeconds < 0 ? 1 : (dtNow - item.start).TotalSeconds,
Elapsed = (dtNow - item.start).ToString(@"hh\:mm\:ss"), Elapsed = (dtNow - item.start).ToString(@"hh\:mm\:ss"),
Chain = $"{item.rule} , {string.Join("->", item.chains ?? new())}" Chain = $"{item.rule} , {string.Join("->", item.chains ?? [])}"
}; };
lstModel.Add(model); lstModel.Add(model);
@@ -67,7 +67,7 @@ public class ClashProxiesViewModel : MyReactiveObject
this.WhenAnyValue( this.WhenAnyValue(
x => x.SelectedGroup, x => x.SelectedGroup,
y => y != null && y.Name.IsNotEmpty()) y => y != null && y.Name.IsNotEmpty())
.Subscribe(c => RefreshProxyDetails(c)); .Subscribe(RefreshProxyDetails);
this.WhenAnyValue( this.WhenAnyValue(
x => x.RuleModeSelected, x => x.RuleModeSelected,
@@ -77,7 +77,7 @@ public class ClashProxiesViewModel : MyReactiveObject
this.WhenAnyValue( this.WhenAnyValue(
x => x.SortingSelected, x => x.SortingSelected,
y => y >= 0) y => y >= 0)
.Subscribe(c => DoSortingSelected(c)); .Subscribe(DoSortingSelected);
this.WhenAnyValue( this.WhenAnyValue(
x => x.AutoRefresh, x => x.AutoRefresh,
@@ -188,15 +188,14 @@ public class ClashProxiesViewModel : MyReactiveObject
ProxyGroups.Clear(); ProxyGroups.Clear();
var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups(); var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
if (proxyGroups != null && proxyGroups.Count > 0) if (proxyGroups is { Count: > 0 })
{ {
foreach (var it in proxyGroups) foreach (var it in proxyGroups)
{ {
if (it.name.IsNullOrEmpty() || !_proxies.ContainsKey(it.name)) if (it.name.IsNullOrEmpty() || !_proxies.TryGetValue(it.name, out var item))
{ {
continue; continue;
} }
var item = _proxies[it.name];
if (!Global.allowSelectType.Contains(item.type.ToLower())) if (!Global.allowSelectType.Contains(item.type.ToLower()))
{ {
continue; continue;
@@ -230,7 +229,7 @@ public class ClashProxiesViewModel : MyReactiveObject
}); });
} }
if (ProxyGroups != null && ProxyGroups.Count > 0) if (ProxyGroups is { Count: > 0 })
{ {
if (selectedName != null && ProxyGroups.Any(t => t.Name == selectedName)) if (selectedName != null && ProxyGroups.Any(t => t.Name == selectedName))
{ {
@@ -56,7 +56,7 @@ public class DNSSettingViewModel : MyReactiveObject
}); });
this.WhenAnyValue(x => x.RayCustomDNSEnableCompatible, x => x.SBCustomDNSEnableCompatible) this.WhenAnyValue(x => x.RayCustomDNSEnableCompatible, x => x.SBCustomDNSEnableCompatible)
.Select(x => !(x.Item1 && x.Item2)) .Select(x => x is not { Item1: true, Item2: true })
.ToPropertyEx(this, x => x.IsSimpleDNSEnabled); .ToPropertyEx(this, x => x.IsSimpleDNSEnabled);
_ = Init(); _ = Init();
@@ -65,6 +65,8 @@ public class MainWindowViewModel : MyReactiveObject
[Reactive] public bool BlIsWindows { get; set; } [Reactive] public bool BlIsWindows { get; set; }
[Reactive] public bool BlNewUpdate { get; set; }
#endregion Menu #endregion Menu
#region Init #region Init
@@ -251,6 +253,11 @@ public class MainWindowViewModel : MyReactiveObject
.ObserveOn(RxSchedulers.MainThreadScheduler) .ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy)); .Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
AppEvents.HasUpdateNotified
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async bl => BlNewUpdate = bl);
#endregion AppEvents #endregion AppEvents
_ = Init(); _ = Init();
@@ -297,10 +304,22 @@ public class MainWindowViewModel : MyReactiveObject
{ {
var indexIdOld = _config.IndexId; var indexIdOld = _config.IndexId;
await RefreshServers(); await RefreshServers();
if (indexIdOld != _config.IndexId)
// If indexId changed or subIndexId is empty, directly reload.
if (indexIdOld != _config.IndexId || _config.SubIndexId.IsNullOrEmpty())
{ {
await Reload(); await Reload();
} }
else
{
// The activity config belongs to the current group.
var profile = await AppManager.Instance.GetProfileItem(_config.IndexId);
if (profile != null && profile.Subid == _config.SubIndexId)
{
await Reload();
}
}
if (_config.UiItem.EnableAutoAdjustMainLvColWidth) if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
{ {
AppEvents.AdjustMainLvColWidthRequested.Publish(); AppEvents.AdjustMainLvColWidthRequested.Publish();
@@ -4,29 +4,28 @@ public class OptionSettingViewModel : MyReactiveObject
{ {
#region Core #region Core
[Reactive] public int localPort { get; set; } [Reactive] public int LocalPort { get; set; }
[Reactive] public bool SecondLocalPortEnabled { get; set; } [Reactive] public bool SecondLocalPortEnabled { get; set; }
[Reactive] public bool udpEnabled { get; set; } [Reactive] public bool UdpEnabled { get; set; }
[Reactive] public bool sniffingEnabled { get; set; } [Reactive] public bool SniffingEnabled { get; set; }
public IList<string> destOverride { get; set; } public IList<string> DestOverride { get; set; }
[Reactive] public bool routeOnly { get; set; } [Reactive] public bool RouteOnly { get; set; }
[Reactive] public bool allowLANConn { get; set; } [Reactive] public bool AllowLANConn { get; set; }
[Reactive] public bool newPort4LAN { get; set; } [Reactive] public bool NewPort4LAN { get; set; }
[Reactive] public string user { get; set; } [Reactive] public string User { get; set; }
[Reactive] public string pass { get; set; } [Reactive] public string Pass { get; set; }
[Reactive] public bool muxEnabled { get; set; } [Reactive] public bool LogEnabled { get; set; }
[Reactive] public bool logEnabled { get; set; } [Reactive] public string Loglevel { get; set; }
[Reactive] public string loglevel { get; set; } [Reactive] public string DefFingerprint { get; set; }
[Reactive] public bool defAllowInsecure { get; set; } [Reactive] public string DefUserAgent { get; set; }
[Reactive] public string defFingerprint { get; set; } [Reactive] public string SendThrough { get; set; }
[Reactive] public string defUserAgent { get; set; } [Reactive] public string BindInterface { get; set; }
[Reactive] public string sendThrough { get; set; } [Reactive] public string Mux4SboxProtocol { get; set; }
[Reactive] public string bindInterface { get; set; } [Reactive] public bool EnableCacheFile4Sbox { get; set; }
[Reactive] public string mux4SboxProtocol { get; set; } [Reactive] public int? HyUpMbps { get; set; }
[Reactive] public bool enableCacheFile4Sbox { get; set; } [Reactive] public int? HyDownMbps { get; set; }
[Reactive] public int? hyUpMbps { get; set; } [Reactive] public bool EnableFragment { get; set; }
[Reactive] public int? hyDownMbps { get; set; } [Reactive] public bool EnableFinalFragment { get; set; }
[Reactive] public bool enableFragment { get; set; }
#endregion Core #endregion Core
@@ -83,9 +82,9 @@ public class OptionSettingViewModel : MyReactiveObject
#region System proxy #region System proxy
[Reactive] public bool notProxyLocalAddress { get; set; } [Reactive] public bool NotProxyLocalAddress { get; set; }
[Reactive] public string systemProxyAdvancedProtocol { get; set; } [Reactive] public string SystemProxyAdvancedProtocol { get; set; }
[Reactive] public string systemProxyExceptions { get; set; } [Reactive] public string SystemProxyExceptions { get; set; }
[Reactive] public string CustomSystemProxyPacPath { get; set; } [Reactive] public string CustomSystemProxyPacPath { get; set; }
[Reactive] public string CustomSystemProxyScriptPath { get; set; } [Reactive] public string CustomSystemProxyScriptPath { get; set; }
@@ -100,6 +99,7 @@ public class OptionSettingViewModel : MyReactiveObject
[Reactive] public bool TunEnableIPv6Address { get; set; } [Reactive] public bool TunEnableIPv6Address { get; set; }
[Reactive] public string TunIcmpRouting { get; set; } [Reactive] public string TunIcmpRouting { get; set; }
[Reactive] public bool TunEnableLegacyProtect { get; set; } [Reactive] public bool TunEnableLegacyProtect { get; set; }
[Reactive] public string TunRouteExcludeAddress { get; set; }
#endregion Tun mode #endregion Tun mode
@@ -142,28 +142,27 @@ public class OptionSettingViewModel : MyReactiveObject
#region Core #region Core
var inbound = _config.Inbound.First(); var inbound = _config.Inbound.First();
localPort = inbound.LocalPort; LocalPort = inbound.LocalPort;
SecondLocalPortEnabled = inbound.SecondLocalPortEnabled; SecondLocalPortEnabled = inbound.SecondLocalPortEnabled;
udpEnabled = inbound.UdpEnabled; UdpEnabled = inbound.UdpEnabled;
sniffingEnabled = inbound.SniffingEnabled; SniffingEnabled = inbound.SniffingEnabled;
routeOnly = inbound.RouteOnly; RouteOnly = inbound.RouteOnly;
allowLANConn = inbound.AllowLANConn; AllowLANConn = inbound.AllowLANConn;
newPort4LAN = inbound.NewPort4LAN; NewPort4LAN = inbound.NewPort4LAN;
user = inbound.User; User = inbound.User;
pass = inbound.Pass; Pass = inbound.Pass;
muxEnabled = _config.CoreBasicItem.MuxEnabled; LogEnabled = _config.CoreBasicItem.LogEnabled;
logEnabled = _config.CoreBasicItem.LogEnabled; Loglevel = _config.CoreBasicItem.Loglevel;
loglevel = _config.CoreBasicItem.Loglevel; DefFingerprint = _config.CoreBasicItem.DefFingerprint;
defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure; DefUserAgent = _config.CoreBasicItem.DefUserAgent;
defFingerprint = _config.CoreBasicItem.DefFingerprint; SendThrough = _config.CoreBasicItem.SendThrough ?? string.Empty;
defUserAgent = _config.CoreBasicItem.DefUserAgent; BindInterface = _config.CoreBasicItem.BindInterface ?? string.Empty;
sendThrough = _config.CoreBasicItem.SendThrough ?? string.Empty; Mux4SboxProtocol = _config.Mux4SboxItem.Protocol;
bindInterface = _config.CoreBasicItem.BindInterface ?? string.Empty; EnableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox;
mux4SboxProtocol = _config.Mux4SboxItem.Protocol; HyUpMbps = _config.HysteriaItem.UpMbps;
enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox; HyDownMbps = _config.HysteriaItem.DownMbps;
hyUpMbps = _config.HysteriaItem.UpMbps; EnableFragment = _config.CoreBasicItem.EnableFragment;
hyDownMbps = _config.HysteriaItem.DownMbps; EnableFinalFragment = _config.CoreBasicItem.EnableFinalFragment;
enableFragment = _config.CoreBasicItem.EnableFragment;
#endregion Core #endregion Core
@@ -211,9 +210,9 @@ public class OptionSettingViewModel : MyReactiveObject
#region System proxy #region System proxy
notProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress; NotProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress;
systemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol; SystemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol;
systemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions; SystemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions;
CustomSystemProxyPacPath = _config.SystemProxyItem.CustomSystemProxyPacPath; CustomSystemProxyPacPath = _config.SystemProxyItem.CustomSystemProxyPacPath;
CustomSystemProxyScriptPath = _config.SystemProxyItem.CustomSystemProxyScriptPath; CustomSystemProxyScriptPath = _config.SystemProxyItem.CustomSystemProxyScriptPath;
@@ -228,6 +227,7 @@ public class OptionSettingViewModel : MyReactiveObject
TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address; TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address;
TunIcmpRouting = _config.TunModeItem.IcmpRouting; TunIcmpRouting = _config.TunModeItem.IcmpRouting;
TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect; TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect;
TunRouteExcludeAddress = Utils.List2String(_config.TunModeItem.RouteExcludeAddress, true);
#endregion Tun mode #endregion Tun mode
@@ -236,12 +236,9 @@ public class OptionSettingViewModel : MyReactiveObject
private async Task InitCoreType() private async Task InitCoreType()
{ {
if (_config.CoreTypeItem == null) _config.CoreTypeItem ??= [];
{
_config.CoreTypeItem = new List<CoreTypeItem>();
}
foreach (EConfigType it in Enum.GetValues(typeof(EConfigType))) foreach (var it in Enum.GetValues<EConfigType>())
{ {
if (_config.CoreTypeItem.FindIndex(t => t.ConfigType == it) >= 0) if (_config.CoreTypeItem.FindIndex(t => t.ConfigType == it) >= 0)
{ {
@@ -297,13 +294,13 @@ public class OptionSettingViewModel : MyReactiveObject
private async Task SaveSettingAsync() private async Task SaveSettingAsync()
{ {
if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString()) if (LocalPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(LocalPort.ToString())
|| localPort <= 0 || localPort >= Global.MaxPort) || LocalPort <= 0 || LocalPort >= Global.MaxPort)
{ {
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort); NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
return; return;
} }
var sendThroughValue = sendThrough.TrimEx(); var sendThroughValue = SendThrough.TrimEx();
if (sendThroughValue.IsNotEmpty() && !Utils.IsIpv4(sendThroughValue)) if (sendThroughValue.IsNotEmpty() && !Utils.IsIpv4(sendThroughValue))
{ {
NoticeManager.Instance.Enqueue(ResUI.FillCorrectSendThroughIPv4); NoticeManager.Instance.Enqueue(ResUI.FillCorrectSendThroughIPv4);
@@ -328,33 +325,32 @@ public class OptionSettingViewModel : MyReactiveObject
//} //}
//Core //Core
_config.Inbound.First().LocalPort = localPort; _config.Inbound.First().LocalPort = LocalPort;
_config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled; _config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled;
_config.Inbound.First().UdpEnabled = udpEnabled; _config.Inbound.First().UdpEnabled = UdpEnabled;
_config.Inbound.First().SniffingEnabled = sniffingEnabled; _config.Inbound.First().SniffingEnabled = SniffingEnabled;
_config.Inbound.First().DestOverride = destOverride?.ToList(); _config.Inbound.First().DestOverride = DestOverride?.ToList();
_config.Inbound.First().RouteOnly = routeOnly; _config.Inbound.First().RouteOnly = RouteOnly;
_config.Inbound.First().AllowLANConn = allowLANConn; _config.Inbound.First().AllowLANConn = AllowLANConn;
_config.Inbound.First().NewPort4LAN = newPort4LAN; _config.Inbound.First().NewPort4LAN = NewPort4LAN;
_config.Inbound.First().User = user; _config.Inbound.First().User = User;
_config.Inbound.First().Pass = pass; _config.Inbound.First().Pass = Pass;
if (_config.Inbound.Count > 1) if (_config.Inbound.Count > 1)
{ {
_config.Inbound.RemoveAt(1); _config.Inbound.RemoveAt(1);
} }
_config.CoreBasicItem.LogEnabled = logEnabled; _config.CoreBasicItem.LogEnabled = LogEnabled;
_config.CoreBasicItem.Loglevel = loglevel; _config.CoreBasicItem.Loglevel = Loglevel;
_config.CoreBasicItem.MuxEnabled = muxEnabled; _config.CoreBasicItem.DefFingerprint = DefFingerprint;
_config.CoreBasicItem.DefAllowInsecure = defAllowInsecure; _config.CoreBasicItem.DefUserAgent = DefUserAgent;
_config.CoreBasicItem.DefFingerprint = defFingerprint; _config.CoreBasicItem.SendThrough = SendThrough.TrimEx();
_config.CoreBasicItem.DefUserAgent = defUserAgent; _config.CoreBasicItem.BindInterface = BindInterface.TrimEx();
_config.CoreBasicItem.SendThrough = sendThrough.TrimEx(); _config.Mux4SboxItem.Protocol = Mux4SboxProtocol;
_config.CoreBasicItem.BindInterface = bindInterface.TrimEx(); _config.CoreBasicItem.EnableCacheFile4Sbox = EnableCacheFile4Sbox;
_config.Mux4SboxItem.Protocol = mux4SboxProtocol; _config.HysteriaItem.UpMbps = HyUpMbps ?? 0;
_config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox; _config.HysteriaItem.DownMbps = HyDownMbps ?? 0;
_config.HysteriaItem.UpMbps = hyUpMbps ?? 0; _config.CoreBasicItem.EnableFragment = EnableFragment;
_config.HysteriaItem.DownMbps = hyDownMbps ?? 0; _config.CoreBasicItem.EnableFinalFragment = EnableFinalFragment;
_config.CoreBasicItem.EnableFragment = enableFragment;
_config.GuiItem.AutoRun = AutoRun; _config.GuiItem.AutoRun = AutoRun;
_config.GuiItem.EnableStatistics = EnableStatistics; _config.GuiItem.EnableStatistics = EnableStatistics;
@@ -383,9 +379,9 @@ public class OptionSettingViewModel : MyReactiveObject
_config.SpeedTestItem.IPAPIUrl = IPAPIUrl; _config.SpeedTestItem.IPAPIUrl = IPAPIUrl;
//systemProxy //systemProxy
_config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions; _config.SystemProxyItem.SystemProxyExceptions = SystemProxyExceptions;
_config.SystemProxyItem.NotProxyLocalAddress = notProxyLocalAddress; _config.SystemProxyItem.NotProxyLocalAddress = NotProxyLocalAddress;
_config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol; _config.SystemProxyItem.SystemProxyAdvancedProtocol = SystemProxyAdvancedProtocol;
_config.SystemProxyItem.CustomSystemProxyPacPath = CustomSystemProxyPacPath; _config.SystemProxyItem.CustomSystemProxyPacPath = CustomSystemProxyPacPath;
_config.SystemProxyItem.CustomSystemProxyScriptPath = CustomSystemProxyScriptPath; _config.SystemProxyItem.CustomSystemProxyScriptPath = CustomSystemProxyScriptPath;
@@ -397,6 +393,7 @@ public class OptionSettingViewModel : MyReactiveObject
_config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address; _config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address;
_config.TunModeItem.IcmpRouting = TunIcmpRouting; _config.TunModeItem.IcmpRouting = TunIcmpRouting;
_config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect; _config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect;
_config.TunModeItem.RouteExcludeAddress = Utils.String2List(TunRouteExcludeAddress);
//coreType //coreType
await SaveCoreType(); await SaveCoreType();
@@ -458,7 +455,7 @@ public class OptionSettingViewModel : MyReactiveObject
default: default:
continue; continue;
} }
item.CoreType = (ECoreType)Enum.Parse(typeof(ECoreType), type); item.CoreType = Enum.Parse<ECoreType>(type);
} }
await Task.CompletedTask; await Task.CompletedTask;
} }
@@ -5,13 +5,10 @@ public class ProfilesSelectViewModel : MyReactiveObject
#region private prop #region private prop
private string _serverFilter = string.Empty; private string _serverFilter = string.Empty;
private Dictionary<string, bool> _dicHeaderSort = new(); private readonly Dictionary<string, bool> _dicHeaderSort = new();
private string _subIndexId = string.Empty; private string _subIndexId = string.Empty;
// ConfigType filter state: default include-mode with all types selected // ConfigType filter state: default include-mode with all types selected
private List<EConfigType> _filterConfigTypes = new();
private bool _filterExclude = false;
#endregion private prop #endregion private prop
@@ -33,18 +30,11 @@ public class ProfilesSelectViewModel : MyReactiveObject
public string ServerFilter { get; set; } public string ServerFilter { get; set; }
// Include/Exclude filter for ConfigType // Include/Exclude filter for ConfigType
public List<EConfigType> FilterConfigTypes [Reactive]
{ public List<EConfigType> FilterConfigTypes { get; set; }
get => _filterConfigTypes;
set => this.RaiseAndSetIfChanged(ref _filterConfigTypes, value);
}
[Reactive] [Reactive]
public bool FilterExclude public bool FilterExclude { get; set; }
{
get => _filterExclude;
set => this.RaiseAndSetIfChanged(ref _filterExclude, value);
}
#endregion ObservableCollection #endregion ObservableCollection
@@ -91,11 +81,11 @@ public class ProfilesSelectViewModel : MyReactiveObject
try try
{ {
FilterExclude = false; FilterExclude = false;
FilterConfigTypes = Enum.GetValues(typeof(EConfigType)).Cast<EConfigType>().ToList(); FilterConfigTypes = Enum.GetValues<EConfigType>().ToList();
} }
catch catch
{ {
FilterConfigTypes = new(); FilterConfigTypes = [];
} }
await RefreshSubscriptions(); await RefreshSubscriptions();
@@ -165,14 +155,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
if (lstModel.Count > 0) if (lstModel.Count > 0)
{ {
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
if (selected != null) SelectedProfile = selected ?? lstModel.First();
{
SelectedProfile = selected;
}
else
{
SelectedProfile = lstModel.First();
}
} }
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
@@ -213,7 +196,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
}).OrderBy(t => t.Sort).ToList(); }).OrderBy(t => t.Sort).ToList();
// Apply ConfigType filter (include or exclude) // Apply ConfigType filter (include or exclude)
if (FilterConfigTypes != null && FilterConfigTypes.Count > 0) if (FilterConfigTypes is { Count: > 0 })
{ {
if (FilterExclude) if (FilterExclude)
{ {
@@ -321,7 +304,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
// External setter for ConfigType filter // External setter for ConfigType filter
public void SetConfigTypeFilter(IEnumerable<EConfigType> types, bool exclude = false) public void SetConfigTypeFilter(IEnumerable<EConfigType> types, bool exclude = false)
{ {
FilterConfigTypes = types?.Distinct().ToList() ?? new List<EConfigType>(); FilterConfigTypes = types?.Distinct().ToList() ?? [];
FilterExclude = exclude; FilterExclude = exclude;
} }
@@ -6,7 +6,7 @@ public class ProfilesViewModel : MyReactiveObject
private List<ProfileItem> _lstProfile; private List<ProfileItem> _lstProfile;
private string _serverFilter = string.Empty; private string _serverFilter = string.Empty;
private Dictionary<string, bool> _dicHeaderSort = new(); private readonly Dictionary<string, bool> _dicHeaderSort = new();
private SpeedtestService? _speedtestService; private SpeedtestService? _speedtestService;
private string? _pendingSelectIndexId; private string? _pendingSelectIndexId;
@@ -190,7 +190,7 @@ public class ProfilesViewModel : MyReactiveObject
}, canEditRemove); }, canEditRemove);
SortServerResultCmd = ReactiveCommand.CreateFromTask(async () => SortServerResultCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await SortServer(EServerColName.DelayVal.ToString()); await SortServer(nameof(EServerColName.DelayVal));
}); });
RemoveInvalidServerResultCmd = ReactiveCommand.CreateFromTask(async () => RemoveInvalidServerResultCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
@@ -303,6 +303,10 @@ public class ProfilesViewModel : MyReactiveObject
{ {
item.SpeedVal = result.Speed ?? string.Empty; item.SpeedVal = result.Speed ?? string.Empty;
} }
if (result.IpInfo.IsNotEmpty())
{
item.IpInfo = result.IpInfo ?? string.Empty;
}
await Task.CompletedTask; await Task.CompletedTask;
} }
@@ -437,6 +441,7 @@ public class ProfilesViewModel : MyReactiveObject
Speed = t33?.Speed ?? 0, Speed = t33?.Speed ?? 0,
DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty, DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty,
SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? string.Empty, SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? string.Empty,
IpInfo = t33?.IpInfo ?? string.Empty,
TodayDown = t22 == null ? "" : Utils.HumanFy(t22.TodayDown), TodayDown = t22 == null ? "" : Utils.HumanFy(t22.TodayDown),
TodayUp = t22 == null ? "" : Utils.HumanFy(t22.TodayUp), TodayUp = t22 == null ? "" : Utils.HumanFy(t22.TodayUp),
TotalDown = t22 == null ? "" : Utils.HumanFy(t22.TotalDown), TotalDown = t22 == null ? "" : Utils.HumanFy(t22.TotalDown),
@@ -73,7 +73,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
} }
SelectedSource.Protocol = ProtocolItems?.ToList(); SelectedSource.Protocol = ProtocolItems?.ToList();
SelectedSource.InboundTag = InboundTagItems?.ToList(); SelectedSource.InboundTag = InboundTagItems?.ToList();
SelectedSource.RuleType = RuleType.IsNullOrEmpty() ? null : (ERuleType)Enum.Parse(typeof(ERuleType), RuleType); SelectedSource.RuleType = RuleType.IsNullOrEmpty() ? null : Enum.Parse<ERuleType>(RuleType);
var hasRule = SelectedSource.Domain?.Count > 0 var hasRule = SelectedSource.Domain?.Count > 0
|| SelectedSource.Ip?.Count > 0 || SelectedSource.Ip?.Count > 0
@@ -86,7 +86,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
SelectedSource = new(); SelectedSource = new();
SelectedRouting = routingItem; SelectedRouting = routingItem;
_rules = routingItem.Id.IsNullOrEmpty() ? new() : JsonUtils.Deserialize<List<RulesItem>>(SelectedRouting.RuleSet); _rules = routingItem.Id.IsNullOrEmpty() ? [] : JsonUtils.Deserialize<List<RulesItem>>(SelectedRouting.RuleSet);
RefreshRulesItems(); RefreshRulesItems();
} }
@@ -313,9 +313,8 @@ public class StatusBarViewModel : MyReactiveObject
} }
BlServers = true; BlServers = true;
for (var k = 0; k < lstModel.Count; k++) foreach (var it in lstModel)
{ {
var it = lstModel[k];
var name = it.GetSummary(); var name = it.GetSummary();
var item = new ComboItem() { ID = it.IndexId, Text = name }; var item = new ComboItem() { ID = it.IndexId, Text = name };
+88 -8
View File
@@ -1,3 +1,4 @@
using v2rayN.Desktop.Common;
using v2rayN.Desktop.Views; using v2rayN.Desktop.Views;
namespace v2rayN.Desktop; namespace v2rayN.Desktop;
@@ -22,13 +23,90 @@ public partial class App : Application
DataContext = StatusBarViewModel.Instance; DataContext = StatusBarViewModel.Instance;
} }
desktop.Exit += OnExit; var mainWindow = new MainWindow();
desktop.MainWindow = new MainWindow(); desktop.MainWindow = mainWindow;
if (OperatingSystem.IsMacOS())
{
Current?.TryGetFeature<IActivatableLifetime>()?.Activated += OnMacOSActivated;
}
} }
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
} }
#region MacOS Activation
private void OnMacOSActivated(object? sender, ActivatedEventArgs args)
{
if (args.Kind != ActivationKind.Reopen)
{
return;
}
if ((ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow is not MainWindow mainWindow)
{
return;
}
var isMiniaturized = MacAppUtils.IsWindowMiniaturized(mainWindow);
Dispatcher.UIThread.Post(() =>
{
if (isMiniaturized)
{
RestoreMacOSAccessoryPolicyAfterMiniaturize(mainWindow);
mainWindow.ShowHideWindow(true);
return;
}
if (!AppManager.Instance.Config.UiItem.MacOSShowInDock)
{
MacAppUtils.SetActivationPolicyAccessory();
}
mainWindow.ShowHideWindow(true);
});
}
private static void RestoreMacOSAccessoryPolicyAfterMiniaturize(MainWindow mainWindow)
{
if (AppManager.Instance.Config.UiItem.MacOSShowInDock)
{
return;
}
mainWindow
.GetObservable(Window.WindowStateProperty)
.Skip(1)
.Where(state => state != WindowState.Minimized)
.Take(1)
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(_ => QueueMacOSAccessoryPolicyRestore(mainWindow));
}
private static void QueueMacOSAccessoryPolicyRestore(MainWindow mainWindow)
{
// AppKit may keep isMiniaturized set until the Dock restore animation finishes.
DispatcherTimer.RunOnce(() => RestoreMacOSAccessoryPolicy(mainWindow), TimeSpan.FromMilliseconds(300));
}
private static void RestoreMacOSAccessoryPolicy(MainWindow mainWindow)
{
if (AppManager.Instance.Config.UiItem.MacOSShowInDock || MacAppUtils.IsWindowMiniaturized(mainWindow))
{
return;
}
MacAppUtils.SetActivationPolicyAccessory();
mainWindow.Activate();
mainWindow.Focus();
}
#endregion MacOS Activation
#region App Event
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{ {
if (e.ExceptionObject != null) if (e.ExceptionObject != null)
@@ -42,20 +120,20 @@ public partial class App : Application
Logging.SaveLog("TaskScheduler_UnobservedTaskException", e.Exception); Logging.SaveLog("TaskScheduler_UnobservedTaskException", e.Exception);
} }
private void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs e)
{
}
private async void MenuAddServerViaClipboardClick(object? sender, EventArgs e) private async void MenuAddServerViaClipboardClick(object? sender, EventArgs e)
{ {
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) try
{ {
if (desktop.MainWindow != null) if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: not null })
{ {
AppEvents.AddServerViaClipboardRequested.Publish(); AppEvents.AddServerViaClipboardRequested.Publish();
await Task.Delay(1000); await Task.Delay(1000);
} }
} }
catch (Exception ex)
{
Logging.SaveLog("MenuAddServerViaClipboardClick", ex);
}
} }
private async void MenuExit_Click(object? sender, EventArgs e) private async void MenuExit_Click(object? sender, EventArgs e)
@@ -63,4 +141,6 @@ public partial class App : Application
await AppManager.Instance.AppExitAsync(false); await AppManager.Instance.AppExitAsync(false);
AppManager.Instance.Shutdown(true); AppManager.Instance.Shutdown(true);
} }
#endregion App Event
} }
+22 -11
View File
@@ -4,7 +4,15 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
{ {
public WindowBase() public WindowBase()
{ {
Initialized += OnWindowInitialized;
Loaded += OnLoaded; Loaded += OnLoaded;
Loaded += (s, e) =>
{
if (Owner != null && !ShowInTaskbar)
{
CanMinimize = false;
}
};
} }
private void ReactiveWindowBase_Closed(object? sender, EventArgs e) private void ReactiveWindowBase_Closed(object? sender, EventArgs e)
@@ -12,30 +20,33 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
throw new NotImplementedException(); throw new NotImplementedException();
} }
protected virtual void OnLoaded(object? sender, RoutedEventArgs e) private void OnWindowInitialized(object? sender, EventArgs e)
{ {
try try
{ {
var sizeItem = ConfigHandler.GetWindowSizeItem(AppManager.Instance.Config, GetType().Name); var sizeItem = ConfigHandler.GetWindowSizeItem(AppManager.Instance.Config, GetType().Name);
if (sizeItem == null) if (sizeItem is null)
{ {
return; return;
} }
Width = sizeItem.Width; if (sizeItem.Width > 0 && !Width.Equals(sizeItem.Width))
Height = sizeItem.Height; {
Width = sizeItem.Width;
}
var workingArea = (Screens.ScreenFromWindow(this) ?? Screens.Primary).WorkingArea; if (sizeItem.Height > 0 && !Height.Equals(sizeItem.Height))
var scaling = (Utils.IsMacOS() ? null : VisualRoot?.RenderScaling) ?? 1.0; {
Height = sizeItem.Height;
var x = workingArea.X + ((workingArea.Width - (Width * scaling)) / 2); }
var y = workingArea.Y + ((workingArea.Height - (Height * scaling)) / 2);
Position = new PixelPoint((int)x, (int)y);
} }
catch { } catch { }
} }
protected virtual void OnLoaded(object? sender, RoutedEventArgs e)
{
}
protected override void OnClosed(EventArgs e) protected override void OnClosed(EventArgs e)
{ {
base.OnClosed(e); base.OnClosed(e);
@@ -2,24 +2,48 @@ namespace v2rayN.Desktop.Common;
public static class AppBuilderExtension public static class AppBuilderExtension
{ {
private static readonly string DefaultFontFamilyName =
Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC");
public static AppBuilder WithFontByDefault(this AppBuilder appBuilder) public static AppBuilder WithFontByDefault(this AppBuilder appBuilder)
{ {
var fallbacks = new List<FontFallback>(); var fallbacks = new List<FontFallback>();
var notoSansSc = new FontFamily(Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC")); var notoSansSc = new FontFamily(DefaultFontFamilyName);
fallbacks.Add(new FontFallback { FontFamily = notoSansSc });
if (OperatingSystem.IsLinux()) fallbacks.Add(new FontFallback
{ {
fallbacks.Add(new FontFallback FontFamily = notoSansSc
{ });
FontFamily = new FontFamily("Noto Color Emoji")
}); if (OperatingSystem.IsWindows())
{
AddFontFallback(fallbacks, "Segoe UI Emoji");
AddFontFallback(fallbacks, "Segoe UI Symbol");
}
else if (OperatingSystem.IsMacOS())
{
AddFontFallback(fallbacks, "Apple Color Emoji");
AddFontFallback(fallbacks, "Apple Symbols");
}
else if (OperatingSystem.IsLinux())
{
AddFontFallback(fallbacks, "Noto Color Emoji");
AddFontFallback(fallbacks, "Noto Sans Symbols");
} }
return appBuilder.With(new FontManagerOptions return appBuilder.With(new FontManagerOptions
{ {
DefaultFamilyName = DefaultFontFamilyName,
FontFallbacks = fallbacks.ToArray() FontFallbacks = fallbacks.ToArray()
}); });
} }
private static void AddFontFallback(List<FontFallback> fallbacks, string fontFamilyName)
{
fallbacks.Add(new FontFallback
{
FontFamily = new FontFamily(fontFamilyName)
});
}
} }
@@ -0,0 +1,35 @@
using System.Runtime.InteropServices;
namespace v2rayN.Desktop.Common;
internal static class MacAppUtils
{
private const string LibObjC = "/usr/lib/libobjc.dylib";
private const nint ActivationPolicyAccessory = 1;
public static void SetActivationPolicyAccessory()
=> objc_msgSend(
objc_msgSend(objc_getClass("NSApplication"), sel_registerName("sharedApplication")),
sel_registerName("setActivationPolicy:"),
ActivationPolicyAccessory);
public static bool IsWindowMiniaturized(Window window)
=> window.TryGetPlatformHandle() is IMacOSTopLevelPlatformHandle { NSWindow: not 0 } handle
&& objc_msgSend_bool(handle.NSWindow, sel_registerName("isMiniaturized"));
[DllImport(LibObjC)]
private static extern nint objc_getClass(string name);
[DllImport(LibObjC)]
private static extern nint sel_registerName(string name);
[DllImport(LibObjC, EntryPoint = "objc_msgSend")]
private static extern nint objc_msgSend(nint receiver, nint selector);
[DllImport(LibObjC, EntryPoint = "objc_msgSend")]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool objc_msgSend_bool(nint receiver, nint selector);
[DllImport(LibObjC, EntryPoint = "objc_msgSend")]
private static extern void objc_msgSend(nint receiver, nint selector, nint argument);
}
@@ -41,7 +41,7 @@ public class ThemeSettingViewModel : MyReactiveObject
{ {
_config.UiItem.CurrentTheme = CurrentTheme; _config.UiItem.CurrentTheme = CurrentTheme;
ModifyTheme(); ModifyTheme();
ConfigHandler.SaveConfig(_config); _ = ConfigHandler.SaveConfig(_config);
} }
}); });
@@ -54,7 +54,7 @@ public class ThemeSettingViewModel : MyReactiveObject
{ {
_config.UiItem.CurrentFontSize = CurrentFontSize; _config.UiItem.CurrentFontSize = CurrentFontSize;
ModifyFontSize(); ModifyFontSize();
ConfigHandler.SaveConfig(_config); _ = ConfigHandler.SaveConfig(_config);
} }
}); });
@@ -67,7 +67,7 @@ public class ThemeSettingViewModel : MyReactiveObject
{ {
_config.UiItem.CurrentLanguage = CurrentLanguage; _config.UiItem.CurrentLanguage = CurrentLanguage;
Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage); Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage);
ConfigHandler.SaveConfig(_config); _ = ConfigHandler.SaveConfig(_config);
NoticeManager.Instance.Enqueue(ResUI.NeedRebootTips); NoticeManager.Instance.Enqueue(ResUI.NeedRebootTips);
} }
}); });
@@ -18,7 +18,7 @@ public partial class AddServer2Window : WindowBase<AddServer2ViewModel>
btnCancel.Click += (s, e) => Close(); btnCancel.Click += (s, e) => Close();
ViewModel = new AddServer2ViewModel(profileItem, UpdateViewHandler); ViewModel = new AddServer2ViewModel(profileItem, UpdateViewHandler);
cmbCoreType.ItemsSource = Utils.GetEnumNames<ECoreType>().Where(t => t != ECoreType.v2rayN.ToString()).ToList().AppendEmpty(); cmbCoreType.ItemsSource = Utils.GetEnumNames<ECoreType>().Where(t => t != nameof(ECoreType.v2rayN)).ToList().AppendEmpty();
this.WhenActivated(disposables => this.WhenActivated(disposables =>
{ {
@@ -1060,7 +1060,7 @@
Grid.Row="8" Grid.Row="8"
ColumnDefinitions="300,Auto" ColumnDefinitions="300,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@@ -1108,12 +1108,12 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAllowInsecure}" /> Text="{x:Static resx:ResUI.TbAllowInsecure}" />
<ComboBox <ToggleSwitch
x:Name="cmbAllowInsecure" x:Name="togAllowInsecure"
Grid.Row="4" Grid.Row="4"
Grid.Column="1" Grid.Column="1"
Width="200" Margin="{StaticResource Margin4}"
Margin="{StaticResource Margin4}" /> HorizontalAlignment="Left" />
<TextBlock <TextBlock
Grid.Row="5" Grid.Row="5"
@@ -1129,6 +1129,20 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" /> HorizontalAlignment="Left" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbVerifyPeerCertByName}" />
<TextBox
x:Name="txtVerifyPeerCertByName"
Grid.Row="6"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock <TextBlock
Grid.Row="7" Grid.Row="7"
Grid.Column="0" Grid.Column="0"
@@ -37,7 +37,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
cmbFingerprint.ItemsSource = Global.Fingerprints; cmbFingerprint.ItemsSource = Global.Fingerprints;
cmbFingerprint2.ItemsSource = Global.Fingerprints; cmbFingerprint2.ItemsSource = Global.Fingerprints;
cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
cmbAlpn.ItemsSource = Global.Alpns; cmbAlpn.ItemsSource = Global.Alpns;
var lstStreamSecurity = new List<string> { string.Empty, Global.StreamSecurity }; var lstStreamSecurity = new List<string> { string.Empty, Global.StreamSecurity };
@@ -116,7 +115,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridFinalmask.IsVisible = false; gridFinalmask.IsVisible = false;
cmbFingerprint.IsEnabled = false; cmbFingerprint.IsEnabled = false;
cmbAlpn.IsEnabled = false; cmbAlpn.IsEnabled = false;
cmbAllowInsecure.IsEnabled = false; togAllowInsecure.IsEnabled = false;
cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls; cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls;
break; break;
@@ -138,14 +137,14 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled3.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled3.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.SOCKS: case EConfigType.SOCKS:
@@ -158,13 +157,13 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Trojan: case EConfigType.Trojan:
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
break; break;
case EConfigType.Hysteria2: case EConfigType.Hysteria2:
@@ -231,7 +230,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.StreamSecurity, v => v.cmbStreamSecurity.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.StreamSecurity, v => v.cmbStreamSecurity.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AllowInsecure, v => v.togAllowInsecure.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables);
@@ -240,6 +239,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.AllowInsecureCertFetch, v => v.togAllowInsecureCertFetch.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AllowInsecureCertFetch, v => v.togAllowInsecureCertFetch.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AllowInsecureCertFetch, v => v.txtAllowInsecureCertFetchTips.IsVisible).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AllowInsecureCertFetch, v => v.txtAllowInsecureCertFetchTips.IsVisible).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.VerifyPeerCertByName, v => v.txtVerifyPeerCertByName.Text).DisposeWith(disposables);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
@@ -112,7 +112,7 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
{ {
try try
{ {
List<ColumnItem> lvColumnItem = new(); List<ColumnItem> lvColumnItem = [];
foreach (var item2 in lstConnections.Columns) foreach (var item2 in lstConnections.Columns)
{ {
if (item2.Tag == null) if (item2.Tag == null)

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