From e179d5bc426a8d9125cf12bb95a5e0ac109884e5 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:03:23 +0800 Subject: [PATCH 01/35] Fix https://github.com/2dust/v2rayN/issues/8870 --- v2rayN/ServiceLib/Models/SingboxConfig.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index 02deaabf..03261c46 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -28,6 +28,7 @@ public class Dns4Sbox public bool? disable_cache { get; set; } public bool? disable_expire { get; set; } public bool? independent_cache { get; set; } + public int? cache_capacity { get; set; } public bool? reverse_mapping { get; set; } public string? client_subnet { get; set; } } From d9201157c8a22cde98246570483b27a6b5bb7b98 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:58:59 +0800 Subject: [PATCH 02/35] Bug fix https://github.com/2dust/v2rayN/issues/8875 --- v2rayN/v2rayN/Views/DNSSettingWindow.xaml.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/v2rayN/v2rayN/Views/DNSSettingWindow.xaml.cs b/v2rayN/v2rayN/Views/DNSSettingWindow.xaml.cs index 90999417..2710b102 100644 --- a/v2rayN/v2rayN/Views/DNSSettingWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/DNSSettingWindow.xaml.cs @@ -31,13 +31,13 @@ public partial class DNSSettingWindow this.Bind(ViewModel, vm => vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.SelectedItem).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.SelectedItem).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.SelectedItem).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Strategy4Freedom, v => v.cmbDirectDNSStrategy.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.SelectedItem).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables); From 81da72bb39035317f6415962429a549317c21ed8 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:11:31 +0800 Subject: [PATCH 03/35] Fix https://github.com/2dust/v2rayN/issues/8881 --- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 0153f6a1..de4c774d 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1332,6 +1332,7 @@ public static class ConfigHandler public static async Task RemoveInvalidServerResult(Config config, string subid) { var lstModel = await AppManager.Instance.ProfileModels(subid, ""); + lstModel.RemoveAll(t => t.ConfigType.IsComplexType()); if (lstModel is { Count: <= 0 }) { return -1; From b8f7cc0768ec2e46315887f25744bc56abb267f0 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Wed, 4 Mar 2026 12:13:06 +0000 Subject: [PATCH 04/35] Fix hosts matching (#8890) * Fix hosts matching * Fix hosts resolve rule * Fix --- .../CoreConfig/Singbox/SingboxDnsService.cs | 98 +++++++++++++------ .../Singbox/SingboxRoutingService.cs | 58 ++++++++++- 2 files changed, 123 insertions(+), 33 deletions(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index 7a5074bb..afab27d9 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -93,7 +93,23 @@ public partial class CoreConfigSingboxService foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts)) { - hostsDns.predefined[kvp.Key] = kvp.Value.Where(Utils.IsIpAddress).ToList(); + // only allow full match + // like example.com and full:example.com, + // but not domain:example.com, keyword:example.com or regex:example.com etc. + var testRule = new Rule4Sbox(); + if (!ParseV2Domain(kvp.Key, testRule)) + { + continue; + } + if (testRule.domain_keyword?.Count > 0 && !kvp.Key.Contains(':')) + { + testRule.domain = testRule.domain_keyword; + testRule.domain_keyword = null; + } + if (testRule.domain?.Count == 1) + { + hostsDns.predefined[testRule.domain.First()] = kvp.Value.Where(Utils.IsIpAddress).ToList(); + } } foreach (var host in hostsDns.predefined) @@ -179,44 +195,66 @@ public partial class CoreConfigSingboxService foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts)) { var predefined = kvp.Value.First(); - if (predefined.IsNullOrEmpty() || Utils.IsIpAddress(predefined)) + if (predefined.IsNullOrEmpty()) { continue; } - if (predefined.StartsWith('#') && int.TryParse(predefined.AsSpan(1), out var rcode)) + var rule = new Rule4Sbox() { - // xray syntactic sugar for predefined - // etc. #0 -> NOERROR - _coreConfig.dns.rules.Add(new() - { - query_type = [1, 28], - domain = [kvp.Key], - action = "predefined", - rcode = rcode switch - { - 0 => "NOERROR", - 1 => "FORMERR", - 2 => "SERVFAIL", - 3 => "NXDOMAIN", - 4 => "NOTIMP", - 5 => "REFUSED", - _ => "NOERROR", - }, - }); - continue; - } - // CNAME record - Rule4Sbox rule = new() - { - query_type = [1, 28], + query_type = [1, 5, 28], // A, CNAME and AAAA action = "predefined", rcode = "NOERROR", - answer = [$"*. IN CNAME {predefined}."], }; - if (ParseV2Domain(kvp.Key, rule)) + if (!ParseV2Domain(kvp.Key, rule)) { - _coreConfig.dns.rules.Add(rule); + continue; } + // see: https://xtls.github.io/en/config/dns.html#dnsobject + // The matching format (domain:, full:, etc.) is the same as the domain + // in the commonly used Routing System. The difference is that without a prefix, + // it defaults to using the full: prefix (similar to the common hosts file syntax). + if (rule.domain_keyword?.Count > 0 && !kvp.Key.Contains(':')) + { + rule.domain = rule.domain_keyword; + rule.domain_keyword = null; + } + // example.com #0 -> example.com with NOERROR + if (predefined.StartsWith('#') && int.TryParse(predefined.AsSpan(1), out var rcode)) + { + rule.rcode = rcode switch + { + 0 => "NOERROR", + 1 => "FORMERR", + 2 => "SERVFAIL", + 3 => "NXDOMAIN", + 4 => "NOTIMP", + 5 => "REFUSED", + _ => "NOERROR", + }; + } + else if (Utils.IsDomain(predefined)) + { + // example.com CNAME target.com -> example.com with CNAME target.com + rule.answer = new List { $"*. IN CNAME {predefined}." }; + } + else if (Utils.IsIpAddress(predefined) && (rule.domain?.Count ?? 0) == 0) + { + // not full match, but an IP address, treat it as predefined answer + if (Utils.IsIpv6(predefined)) + { + rule.answer = new List { $"*. IN AAAA {predefined}" }; + + } + else + { + rule.answer = new List { $"*. IN A {predefined}" }; + } + } + else + { + continue; + } + _coreConfig.dns.rules.Add(rule); } if (simpleDnsItem.BlockBindingQuery == true) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs index 9bcb9adf..45620c41 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs @@ -84,11 +84,58 @@ public partial class CoreConfigSingboxService } if (hostsDomains.Count > 0) { - _coreConfig.route.rules.Add(new() + var hostsResolveRule = new Rule4Sbox { action = "resolve", - domain = hostsDomains, - }); + }; + var hostsCounter = 0; + foreach (var host in hostsDomains) + { + var domainRule = new Rule4Sbox(); + if (!ParseV2Domain(host, domainRule)) + { + continue; + } + if (domainRule.domain_keyword?.Count > 0 && !host.Contains(':')) + { + domainRule.domain = domainRule.domain_keyword; + domainRule.domain_keyword = null; + } + if (domainRule.domain?.Count > 0) + { + hostsResolveRule.domain ??= []; + hostsResolveRule.domain.AddRange(domainRule.domain); + hostsCounter++; + } + else if (domainRule.domain_keyword?.Count > 0) + { + hostsResolveRule.domain_keyword ??= []; + hostsResolveRule.domain_keyword.AddRange(domainRule.domain_keyword); + hostsCounter++; + } + else if (domainRule.domain_suffix?.Count > 0) + { + hostsResolveRule.domain_suffix ??= []; + hostsResolveRule.domain_suffix.AddRange(domainRule.domain_suffix); + hostsCounter++; + } + else if (domainRule.domain_regex?.Count > 0) + { + hostsResolveRule.domain_regex ??= []; + hostsResolveRule.domain_regex.AddRange(domainRule.domain_regex); + hostsCounter++; + } + else if (domainRule.geosite?.Count > 0) + { + hostsResolveRule.geosite ??= []; + hostsResolveRule.geosite.AddRange(domainRule.geosite); + hostsCounter++; + } + } + if (hostsCounter > 0) + { + _coreConfig.route.rules.Add(hostsResolveRule); + } } _coreConfig.route.rules.Add(new() @@ -355,6 +402,11 @@ public partial class CoreConfigSingboxService rule.domain_keyword ??= []; rule.domain_keyword?.Add(domain.Substring(8)); } + else if (domain.StartsWith("dotless:")) + { + rule.domain_keyword ??= []; + rule.domain_keyword?.Add(domain.Substring(8)); + } else { rule.domain_keyword ??= []; From c0aa829abb90688dcabb66acdb5b1ef7c0fc7e91 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:49:46 +0800 Subject: [PATCH 05/35] up 7.19.2 --- v2rayN/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2rayN/Directory.Build.props b/v2rayN/Directory.Build.props index e7dffdd2..6c744bb5 100644 --- a/v2rayN/Directory.Build.props +++ b/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.19.1 + 7.19.2 From e03c22092fc62157b4c1d03182f21d552f2fb4dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:33:41 +0800 Subject: [PATCH 06/35] Bump actions/setup-dotnet from 5.0.1 to 5.2.0 (#8891) Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.0.1 to 5.2.0. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v5.0.1...v5.2.0) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-version: 5.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-linux.yml | 2 +- .github/workflows/build-osx.yml | 2 +- .github/workflows/build-windows-desktop.yml | 2 +- .github/workflows/build-windows.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index a814b8e4..88cbb388 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -37,7 +37,7 @@ jobs: fetch-depth: '0' - name: Setup .NET - uses: actions/setup-dotnet@v5.0.1 + uses: actions/setup-dotnet@v5.2.0 with: dotnet-version: '8.0.x' diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml index f16f28bf..d79295cc 100644 --- a/.github/workflows/build-osx.yml +++ b/.github/workflows/build-osx.yml @@ -32,7 +32,7 @@ jobs: fetch-depth: '0' - name: Setup - uses: actions/setup-dotnet@v5.0.1 + uses: actions/setup-dotnet@v5.2.0 with: dotnet-version: '8.0.x' diff --git a/.github/workflows/build-windows-desktop.yml b/.github/workflows/build-windows-desktop.yml index 1a85e546..99c7ef32 100644 --- a/.github/workflows/build-windows-desktop.yml +++ b/.github/workflows/build-windows-desktop.yml @@ -32,7 +32,7 @@ jobs: fetch-depth: '0' - name: Setup - uses: actions/setup-dotnet@v5.0.1 + uses: actions/setup-dotnet@v5.2.0 with: dotnet-version: '8.0.x' diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 4c1ce3ea..7d202370 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@v6.0.2 - name: Setup - uses: actions/setup-dotnet@v5.0.1 + uses: actions/setup-dotnet@v5.2.0 with: dotnet-version: '8.0.x' From 66e1aeae1fe7cc183ac7cb24982d13a3733edb60 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 7 Mar 2026 08:36:03 +0000 Subject: [PATCH 07/35] Fix speedtest core type (#8900) * Fix speedtest core type * Simplify code --- .../ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs | 2 +- v2rayN/ServiceLib/Handler/CoreConfigHandler.cs | 5 ++--- v2rayN/ServiceLib/Manager/CoreManager.cs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs index 4894b334..9a3aea84 100644 --- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs +++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs @@ -268,7 +268,7 @@ public class CoreConfigContextBuilder { IndexId = $"inner-{Utils.GetGuid(false)}", ConfigType = EConfigType.ProxyChain, - CoreType = node.CoreType ?? ECoreType.Xray, + CoreType = AppManager.Instance.GetCoreType(node, node.ConfigType), }; List childItems = [prevNode?.IndexId, node.IndexId, nextNode?.IndexId]; var chainExtraItem = chainNode.GetProtocolExtra() with diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 474234e4..99f65662 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -21,7 +21,7 @@ public static class CoreConfigHandler _ => await GenerateClientCustomConfig(node, fileName) }; } - else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) + else if (context.RunCoreType == ECoreType.sing_box) { result = new CoreConfigSingboxService(context).GenerateClientConfigContent(); } @@ -128,12 +128,11 @@ public static class CoreConfigHandler public static async Task GenerateClientSpeedtestConfig(Config config, CoreConfigContext context, ServerTestItem testItem, string fileName) { var result = new RetResult(); - var node = context.Node; var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); var port = Utils.GetFreePort(initPort + testItem.QueueNum); testItem.Port = port; - if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) + if (context.RunCoreType == ECoreType.sing_box) { result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port); } diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index a55be232..14a9ae50 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -132,7 +132,7 @@ public class CoreManager return null; } - var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); + var coreType = context.RunCoreType; var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); return await RunProcess(coreInfo, fileName, true, false); } From 679bd8afcc179d271933886e3eb3fc07f3688a62 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sun, 8 Mar 2026 17:57:09 +0800 Subject: [PATCH 08/35] Persist DataGrid column layout for ClashConnectionsView https://github.com/2dust/v2rayN/issues/8893 --- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 1 + v2rayN/ServiceLib/Models/ConfigItems.cs | 1 + v2rayN/v2rayN/Views/ClashConnectionsView.xaml | 16 ++-- .../v2rayN/Views/ClashConnectionsView.xaml.cs | 83 ++++++++++++++++++- v2rayN/v2rayN/Views/ProfilesView.xaml.cs | 68 +++++++++------ 5 files changed, 136 insertions(+), 33 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index de4c774d..ce185fb7 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -154,6 +154,7 @@ public static class ConfigHandler DownMbps = 100 }; config.ClashUIItem ??= new(); + config.ClashUIItem.ConnectionsColumnItem ??= new(); config.SystemProxyItem ??= new(); config.WebDavItem ??= new(); config.CheckUpdateItem ??= new(); diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index 7dc8c266..56e5fe47 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -208,6 +208,7 @@ public class ClashUIItem public int ProxiesAutoDelayTestInterval { get; set; } = 10; public bool ConnectionsAutoRefresh { get; set; } public int ConnectionsRefreshInterval { get; set; } = 2; + public List ConnectionsColumnItem { get; set; } } [Serializable] diff --git a/v2rayN/v2rayN/Views/ClashConnectionsView.xaml b/v2rayN/v2rayN/Views/ClashConnectionsView.xaml index 0e6ba6a7..067926ff 100644 --- a/v2rayN/v2rayN/Views/ClashConnectionsView.xaml +++ b/v2rayN/v2rayN/Views/ClashConnectionsView.xaml @@ -2,6 +2,7 @@ x:Class="v2rayN.Views.ClashConnectionsView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:base="clr-namespace:v2rayN.Base" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -78,25 +79,30 @@ - - - - - diff --git a/v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs b/v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs index 0a6245a9..47482b8e 100644 --- a/v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs +++ b/v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs @@ -1,4 +1,5 @@ using System.Windows.Controls; +using v2rayN.Base; namespace v2rayN.Views; @@ -7,9 +8,14 @@ namespace v2rayN.Views; /// public partial class ClashConnectionsView { + private static Config _config; + private static readonly string _tag = "ClashConnectionsView"; + public ClashConnectionsView() { InitializeComponent(); + _config = AppManager.Instance.Config; + ViewModel = new ClashConnectionsViewModel(UpdateViewHandler); btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click; @@ -24,7 +30,15 @@ public partial class ClashConnectionsView this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); + + AppEvents.AppExitRequested + .AsObservable() + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(_ => StorageUI()) + .DisposeWith(disposables); }); + + RestoreUI(); } private async Task UpdateViewHandler(EViewAction action, object? obj) @@ -48,7 +62,7 @@ public partial class ClashConnectionsView } catch (Exception ex) { - Logging.SaveLog("ClashConnectionsView", ex); + Logging.SaveLog(_tag, ex); } } @@ -56,4 +70,71 @@ public partial class ClashConnectionsView { ViewModel?.ClashConnectionClose(false); } + + #region UI + + private void RestoreUI() + { + try + { + var lvColumnItem = _config.ClashUIItem?.ConnectionsColumnItem?.OrderBy(t => t.Index).ToList(); + if (lvColumnItem == null) + { + return; + } + + var displayIndex = 0; + foreach (var item in lvColumnItem) + { + foreach (var col in lstConnections.Columns.Cast()) + { + if (col.ExName == item.Name) + { + if (item.Width > 0) + { + col.Width = item.Width; + } + + col.DisplayIndex = displayIndex++; + break; + } + } + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + private void StorageUI() + { + try + { + List lvColumnItem = new(); + foreach (var col in lstConnections.Columns.Cast()) + { + var name = col.ExName; + if (string.IsNullOrWhiteSpace(name)) + { + continue; + } + + lvColumnItem.Add(new() + { + Name = name, + Width = (int)col.ActualWidth, + Index = col.DisplayIndex + }); + } + + _config.ClashUIItem.ConnectionsColumnItem = lvColumnItem; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + #endregion UI } diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs index 46def3e2..1440e6ec 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs @@ -10,6 +10,7 @@ namespace v2rayN.Views; public partial class ProfilesView { private static Config _config; + private static readonly string _tag = "ProfilesView"; public ProfilesView() { @@ -339,7 +340,7 @@ public partial class ProfilesView } catch (Exception ex) { - Logging.SaveLog("ProfilesView", ex); + Logging.SaveLog(_tag, ex); } } @@ -357,46 +358,59 @@ public partial class ProfilesView private void RestoreUI() { - var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList(); - var displayIndex = 0; - foreach (var item in lvColumnItem) + try { - foreach (MyDGTextColumn item2 in lstProfiles.Columns) + var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList(); + var displayIndex = 0; + foreach (var item in lvColumnItem) { - if (item2.ExName == item.Name) + foreach (var item2 in lstProfiles.Columns.Cast()) { - if (item.Width < 0) + if (item2.ExName == item.Name) { - item2.Visibility = Visibility.Hidden; - } - else - { - item2.Width = item.Width; - item2.DisplayIndex = displayIndex++; - } - if (item.Name.ToLower().StartsWith("to")) - { - item2.Visibility = _config.GuiItem.EnableStatistics ? Visibility.Visible : Visibility.Hidden; + if (item.Width < 0) + { + item2.Visibility = Visibility.Hidden; + } + else + { + item2.Width = item.Width; + item2.DisplayIndex = displayIndex++; + } + if (item.Name.ToLower().StartsWith("to")) + { + item2.Visibility = _config.GuiItem.EnableStatistics ? Visibility.Visible : Visibility.Hidden; + } } } } } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } } private void StorageUI() { - List lvColumnItem = new(); - foreach (var t in lstProfiles.Columns) + try { - var item2 = (MyDGTextColumn)t; - lvColumnItem.Add(new() + List lvColumnItem = new(); + foreach (var item2 in lstProfiles.Columns.Cast()) { - Name = item2.ExName, - Width = (int)(item2.Visibility == Visibility.Visible ? item2.ActualWidth : -1), - Index = item2.DisplayIndex - }); + lvColumnItem.Add(new() + { + Name = item2.ExName, + Width = (int)(item2.Visibility == Visibility.Visible ? item2.ActualWidth : -1), + Index = item2.DisplayIndex + }); + } + _config.UiItem.MainColumnItem = lvColumnItem; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); } - _config.UiItem.MainColumnItem = lvColumnItem; } #endregion UI @@ -405,7 +419,7 @@ public partial class ProfilesView private Point startPoint = new(); private int startIndex = -1; - private string formatData = "ProfileItemModel"; + private readonly string formatData = "ProfileItemModel"; /// /// Helper to search up the VisualTree From df800a60c2e72e515d4d683aae9ad727116142fc Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sun, 8 Mar 2026 18:59:56 +0800 Subject: [PATCH 09/35] Persist DataGrid column layout for ClashConnectionsView --- .../Views/ClashConnectionsView.axaml | 15 ++-- .../Views/ClashConnectionsView.axaml.cs | 84 ++++++++++++++++++ .../Views/ProfilesView.axaml.cs | 87 +++++++++++-------- 3 files changed, 145 insertions(+), 41 deletions(-) diff --git a/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml index 21bad138..6a9b7088 100644 --- a/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml +++ b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml @@ -74,23 +74,28 @@ + Header="{x:Static resx:ResUI.TbSortingHost}" + Tag="Host" /> + Header="{x:Static resx:ResUI.TbSortingChain}" + Tag="Chain" /> + Header="{x:Static resx:ResUI.TbSortingNetwork}" + Tag="Network" /> + Header="{x:Static resx:ResUI.TbSortingType}" + Tag="Type" /> + Header="{x:Static resx:ResUI.TbSortingTime}" + Tag="Elapsed" /> diff --git a/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs index 1c39df72..23722ad0 100644 --- a/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs @@ -2,9 +2,15 @@ namespace v2rayN.Desktop.Views; public partial class ClashConnectionsView : ReactiveUserControl { + private static Config _config; + private static readonly string _tag = "ClashConnectionsView"; + public ClashConnectionsView() { InitializeComponent(); + + _config = AppManager.Instance.Config; + ViewModel = new ClashConnectionsViewModel(UpdateViewHandler); btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click; @@ -19,7 +25,15 @@ public partial class ClashConnectionsView : ReactiveUserControl vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); + + AppEvents.AppExitRequested + .AsObservable() + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(_ => StorageUI()) + .DisposeWith(disposables); }); + + RestoreUI(); } private async Task UpdateViewHandler(EViewAction action, object? obj) @@ -51,4 +65,74 @@ public partial class ClashConnectionsView : ReactiveUserControl t.Index).ToList(); + if (lvColumnItem == null) + { + return; + } + + var displayIndex = 0; + foreach (var item in lvColumnItem) + { + foreach (var item2 in lstConnections.Columns) + { + if (item2.Tag == null) + { + continue; + } + if (item2.Tag.Equals(item.Name)) + { + if (item.Width < 0) + { + item2.IsVisible = false; + } + else + { + item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel); + item2.DisplayIndex = displayIndex++; + } + } + } + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + private void StorageUI() + { + try + { + List lvColumnItem = new(); + foreach (var item2 in lstConnections.Columns) + { + if (item2.Tag == null) + { + continue; + } + lvColumnItem.Add(new() + { + Name = (string)item2.Tag, + Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1), + Index = item2.DisplayIndex + }); + } + _config.ClashUIItem.ConnectionsColumnItem = lvColumnItem; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + #endregion UI } diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs index 4ca8e793..40336be5 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs @@ -7,6 +7,7 @@ public partial class ProfilesView : ReactiveUserControl { private static Config _config; private Window? _window; + private static readonly string _tag = "ProfilesView"; public ProfilesView() { @@ -381,7 +382,7 @@ public partial class ProfilesView : ReactiveUserControl } catch (Exception ex) { - Logging.SaveLog("ProfilesView", ex); + Logging.SaveLog(_tag, ex); } } @@ -399,53 +400,67 @@ public partial class ProfilesView : ReactiveUserControl private void RestoreUI() { - var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList(); - var displayIndex = 0; - foreach (var item in lvColumnItem) + try { + var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList(); + var displayIndex = 0; + foreach (var item in lvColumnItem) + { + foreach (var item2 in lstProfiles.Columns) + { + if (item2.Tag == null) + { + continue; + } + if (item2.Tag.Equals(item.Name)) + { + if (item.Width < 0) + { + item2.IsVisible = false; + } + else + { + item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel); + item2.DisplayIndex = displayIndex++; + } + if (item.Name.ToLower().StartsWith("to")) + { + item2.IsVisible = _config.GuiItem.EnableStatistics; + } + } + } + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + } + + private void StorageUI() + { + try + { + List lvColumnItem = new(); foreach (var item2 in lstProfiles.Columns) { if (item2.Tag == null) { continue; } - if (item2.Tag.Equals(item.Name)) + lvColumnItem.Add(new() { - if (item.Width < 0) - { - item2.IsVisible = false; - } - else - { - item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel); - item2.DisplayIndex = displayIndex++; - } - if (item.Name.ToLower().StartsWith("to")) - { - item2.IsVisible = _config.GuiItem.EnableStatistics; - } - } + Name = (string)item2.Tag, + Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1), + Index = item2.DisplayIndex + }); } + _config.UiItem.MainColumnItem = lvColumnItem; } - } - - private void StorageUI() - { - List lvColumnItem = new(); - foreach (var item2 in lstProfiles.Columns) + catch (Exception ex) { - if (item2.Tag == null) - { - continue; - } - lvColumnItem.Add(new() - { - Name = (string)item2.Tag, - Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1), - Index = item2.DisplayIndex - }); + Logging.SaveLog(_tag, ex); } - _config.UiItem.MainColumnItem = lvColumnItem; } #endregion UI From ef5fee9975da3c6c39cdfa8b9609385e76d4cd38 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 8 Mar 2026 11:00:27 +0000 Subject: [PATCH 10/35] Fix (#8909) --- v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs index 9a3aea84..1cbfbf14 100644 --- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs +++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs @@ -269,6 +269,7 @@ public class CoreConfigContextBuilder IndexId = $"inner-{Utils.GetGuid(false)}", ConfigType = EConfigType.ProxyChain, CoreType = AppManager.Instance.GetCoreType(node, node.ConfigType), + Remarks = node.Remarks, }; List childItems = [prevNode?.IndexId, node.IndexId, nextNode?.IndexId]; var chainExtraItem = chainNode.GetProtocolExtra() with From a88396c11d722211640725404ca537fc76227340 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Tue, 10 Mar 2026 01:13:52 +0000 Subject: [PATCH 11/35] Fix custom config sub chain (#8913) --- v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs index 1cbfbf14..ddc98a1f 100644 --- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs +++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs @@ -227,7 +227,7 @@ public class CoreConfigContextBuilder { var result = NodeValidatorResult.Empty(); - if (node.Subid.IsNullOrEmpty()) + if (node.Subid.IsNullOrEmpty() || node.ConfigType == EConfigType.Custom) { return (null, result); } From 0c13488410626d3a8a251a5025f1d7579f43dcb5 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Tue, 10 Mar 2026 01:14:57 +0000 Subject: [PATCH 12/35] Fix (#8914) * Fix * Fix --- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 2 -- v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs | 12 ++++++------ .../ServiceLib/ViewModels/OptionSettingViewModel.cs | 8 ++++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index ce185fb7..eba4f4b5 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -723,8 +723,6 @@ public static class ConfigHandler profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with { SalamanderPass = profileItem.GetProtocolExtra().SalamanderPass?.TrimEx(), - UpMbps = profileItem.GetProtocolExtra().UpMbps is null or < 0 ? config.HysteriaItem.UpMbps : profileItem.GetProtocolExtra().UpMbps, - DownMbps = profileItem.GetProtocolExtra().DownMbps is null or < 0 ? config.HysteriaItem.DownMbps : profileItem.GetProtocolExtra().DownMbps, HopInterval = profileItem.GetProtocolExtra().HopInterval?.TrimEx(), }); diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index f56bb1c0..6af600f7 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -27,10 +27,10 @@ public class AddServerViewModel : MyReactiveObject public string Ports { get; set; } [Reactive] - public int UpMbps { get; set; } + public int? UpMbps { get; set; } [Reactive] - public int DownMbps { get; set; } + public int? DownMbps { get; set; } [Reactive] public string HopInterval { get; set; } @@ -113,8 +113,8 @@ public class AddServerViewModel : MyReactiveObject AlterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0; Flow = protocolExtra?.Flow ?? string.Empty; SalamanderPass = protocolExtra?.SalamanderPass ?? string.Empty; - UpMbps = protocolExtra?.UpMbps ?? _config.HysteriaItem.UpMbps; - DownMbps = protocolExtra?.DownMbps ?? _config.HysteriaItem.DownMbps; + UpMbps = protocolExtra?.UpMbps; + DownMbps = protocolExtra?.DownMbps; HopInterval = protocolExtra?.HopInterval.IsNullOrEmpty() ?? true ? Global.Hysteria2DefaultHopInt.ToString() : protocolExtra.HopInterval; VmessSecurity = protocolExtra?.VmessSecurity?.IsNullOrEmpty() == false ? protocolExtra.VmessSecurity : Global.DefaultSecurity; VlessEncryption = protocolExtra?.VlessEncryption.IsNullOrEmpty() == false ? protocolExtra.VlessEncryption : Global.None; @@ -175,8 +175,8 @@ public class AddServerViewModel : MyReactiveObject AlterId = AlterId > 0 ? AlterId.ToString() : null, Flow = Flow.NullIfEmpty(), SalamanderPass = SalamanderPass.NullIfEmpty(), - UpMbps = UpMbps >= 0 ? UpMbps : null, - DownMbps = DownMbps >= 0 ? DownMbps : null, + UpMbps = UpMbps, + DownMbps = DownMbps, HopInterval = HopInterval.NullIfEmpty(), VmessSecurity = VmessSecurity.NullIfEmpty(), VlessEncryption = VlessEncryption.NullIfEmpty(), diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 8462a2c3..b5bfe71f 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -22,8 +22,8 @@ public class OptionSettingViewModel : MyReactiveObject [Reactive] public string defUserAgent { get; set; } [Reactive] public string mux4SboxProtocol { get; set; } [Reactive] public bool enableCacheFile4Sbox { get; set; } - [Reactive] public int hyUpMbps { get; set; } - [Reactive] public int hyDownMbps { get; set; } + [Reactive] public int? hyUpMbps { get; set; } + [Reactive] public int? hyDownMbps { get; set; } [Reactive] public bool enableFragment { get; set; } #endregion Core @@ -336,8 +336,8 @@ public class OptionSettingViewModel : MyReactiveObject _config.CoreBasicItem.DefUserAgent = defUserAgent; _config.Mux4SboxItem.Protocol = mux4SboxProtocol; _config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox; - _config.HysteriaItem.UpMbps = hyUpMbps; - _config.HysteriaItem.DownMbps = hyDownMbps; + _config.HysteriaItem.UpMbps = hyUpMbps ?? 0; + _config.HysteriaItem.DownMbps = hyDownMbps ?? 0; _config.CoreBasicItem.EnableFragment = enableFragment; _config.GuiItem.AutoRun = AutoRun; From 588e82f0d940bb4c97416569decb404ec359a05d Mon Sep 17 00:00:00 2001 From: DHR60 Date: Tue, 10 Mar 2026 01:16:16 +0000 Subject: [PATCH 13/35] Tun ech protect (#8915) --- v2rayN/ServiceLib/Models/V2rayConfig.cs | 1 + .../CoreConfig/V2ray/CoreConfigV2rayService.cs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index 2393d5fb..6d4eabcb 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -360,6 +360,7 @@ public class TlsSettings4Ray public bool? disableSystemRoot { get; set; } public string? echConfigList { get; set; } public string? echForceQuery { get; set; } + public Sockopt4Ray? echSockopt { get; set; } } public class CertificateSettings4Ray diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index ca8eeb0d..ad122243 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -316,12 +316,20 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) SsMethod = Global.None, }); - foreach (var outbound in _coreConfig.outbounds.Where(outbound => outbound.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)) + foreach (var outbound in _coreConfig.outbounds + .Where(o => o.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)) { - outbound.streamSettings ??= new StreamSettings4Ray(); - outbound.streamSettings.sockopt ??= new Sockopt4Ray(); + outbound.streamSettings ??= new(); + outbound.streamSettings.sockopt ??= new(); outbound.streamSettings.sockopt.dialerProxy = "tun-project-ss"; } + // ech protected + foreach (var outbound in _coreConfig.outbounds + .Where(outbound => outbound.streamSettings?.tlsSettings?.echConfigList?.IsNullOrEmpty() == false)) + { + outbound.streamSettings!.tlsSettings!.echSockopt ??= new(); + outbound.streamSettings.tlsSettings.echSockopt.dialerProxy = "tun-project-ss"; + } _coreConfig.outbounds.Add(new CoreConfigV2rayService(context with { Node = protectNode, From 4af528f8e270eca7dcad2428db27bf74f94380e4 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Tue, 10 Mar 2026 06:10:23 +0000 Subject: [PATCH 14/35] Typo (#8917) --- .../Services/CoreConfig/V2ray/CoreConfigV2rayService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index ad122243..545e713e 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -321,19 +321,19 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) { outbound.streamSettings ??= new(); outbound.streamSettings.sockopt ??= new(); - outbound.streamSettings.sockopt.dialerProxy = "tun-project-ss"; + outbound.streamSettings.sockopt.dialerProxy = "tun-protect-ss"; } // ech protected foreach (var outbound in _coreConfig.outbounds .Where(outbound => outbound.streamSettings?.tlsSettings?.echConfigList?.IsNullOrEmpty() == false)) { outbound.streamSettings!.tlsSettings!.echSockopt ??= new(); - outbound.streamSettings.tlsSettings.echSockopt.dialerProxy = "tun-project-ss"; + outbound.streamSettings.tlsSettings.echSockopt.dialerProxy = "tun-protect-ss"; } _coreConfig.outbounds.Add(new CoreConfigV2rayService(context with { Node = protectNode, - }).BuildProxyOutbound("tun-project-ss")); + }).BuildProxyOutbound("tun-protect-ss")); _coreConfig.routing.rules ??= []; var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 }; From 0c2114d2e1a06362a38e404d607605ddfad96c44 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:29:36 +0800 Subject: [PATCH 15/35] Refactor SRS rule collection and add DNS parsing --- v2rayN/ServiceLib/Services/UpdateService.cs | 95 ++++++++++++++++----- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/v2rayN/ServiceLib/Services/UpdateService.cs b/v2rayN/ServiceLib/Services/UpdateService.cs index d368108c..f9d9a5f8 100644 --- a/v2rayN/ServiceLib/Services/UpdateService.cs +++ b/v2rayN/ServiceLib/Services/UpdateService.cs @@ -363,44 +363,36 @@ public class UpdateService(Config config, Func updateFunc) var geoipFiles = new List(); var geoSiteFiles = new List(); - //Collect used files list + // Collect from routing rules var routingItems = await AppManager.Instance.RoutingItems(); foreach (var routing in routingItems) { var rules = JsonUtils.Deserialize>(routing.RuleSet); foreach (var item in rules ?? []) { - foreach (var ip in item.Ip ?? []) - { - var prefix = "geoip:"; - if (ip.StartsWith(prefix)) - { - geoipFiles.Add(ip.Substring(prefix.Length)); - } - } - - foreach (var domain in item.Domain ?? []) - { - var prefix = "geosite:"; - if (domain.StartsWith(prefix)) - { - geoSiteFiles.Add(domain.Substring(prefix.Length)); - } - } + AddPrefixedItems(item.Ip, "geoip:", geoipFiles); + AddPrefixedItems(item.Domain, "geosite:", geoSiteFiles); } } - //append dns items TODO - geoSiteFiles.Add("google"); - geoSiteFiles.Add("cn"); - geoSiteFiles.Add("geolocation-cn"); - geoSiteFiles.Add("category-ads-all"); + // Collect from DNS configuration + var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); + if (dnsItem != null) + { + ExtractDnsRuleSets(dnsItem.NormalDNS, geoipFiles, geoSiteFiles); + ExtractDnsRuleSets(dnsItem.TunDNS, geoipFiles, geoSiteFiles); + } + // Append default items + geoSiteFiles.AddRange(["google", "cn", "geolocation-cn", "category-ads-all"]); + + // Download files var path = Utils.GetBinPath("srss"); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } + foreach (var item in geoipFiles.Distinct()) { await UpdateSrsFile("geoip", item); @@ -412,6 +404,63 @@ public class UpdateService(Config config, Func updateFunc) } } + private void AddPrefixedItems(List? items, string prefix, List output) + { + if (items == null) + { + return; + } + + foreach (var item in items) + { + if (item.StartsWith(prefix)) + { + output.Add(item.Substring(prefix.Length)); + } + } + } + + private void ExtractDnsRuleSets(string? dnsJson, List geoipFiles, List geoSiteFiles) + { + if (string.IsNullOrEmpty(dnsJson)) + { + return; + } + + try + { + var dns = JsonUtils.Deserialize(dnsJson); + if (dns?.rules != null) + { + foreach (var rule in dns.rules) + { + ExtractSrsRuleSets(rule, geoipFiles, geoSiteFiles); + } + } + } + catch { } + } + + private void ExtractSrsRuleSets(Rule4Sbox? rule, List geoipFiles, List geoSiteFiles) + { + if (rule == null) + { + return; + } + + AddPrefixedItems(rule.rule_set, "geosite-", geoSiteFiles); + AddPrefixedItems(rule.rule_set, "geoip-", geoipFiles); + + // Handle nested rules recursively + if (rule.rules != null) + { + foreach (var nestedRule in rule.rules) + { + ExtractSrsRuleSets(nestedRule, geoipFiles, geoSiteFiles); + } + } + } + private async Task UpdateSrsFile(string type, string srsName) { var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl) From 26f65dd3b2d36fee1cf792e86389baa3344f4db3 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:19:21 +0800 Subject: [PATCH 16/35] Code cleanup: pattern matching and minor fixes --- v2rayN/ServiceLib/Global.cs | 2 +- v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs | 2 ++ v2rayN/ServiceLib/Handler/ConfigHandler.cs | 4 ++-- v2rayN/ServiceLib/Models/ServerTestItem.cs | 2 +- .../Services/CoreConfig/Singbox/SingboxDnsService.cs | 1 - v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs | 6 +++--- v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml.cs | 3 +-- v2rayN/v2rayN/Views/ProfilesView.xaml.cs | 3 +-- 8 files changed, 11 insertions(+), 12 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 5478cd2a..e3ed209d 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -95,7 +95,7 @@ public class Global public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$"; - public static readonly List PolicyGroupDefaultFilterList = + public static readonly List PolicyGroupDefaultFilterList = [ // All nodes (exclude traffic/expiry info) PolicyGroupDefaultAllFilter, diff --git a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs index a6adf1c2..65110b2a 100644 --- a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs +++ b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs @@ -81,7 +81,9 @@ public class NodeValidator { var transportError = ValidateSingboxTransport(item.ConfigType, net); if (transportError != null) + { v.Error(transportError); + } if (!Global.SingboxSupportConfigType.Contains(item.ConfigType)) { diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index eba4f4b5..3c587676 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1040,8 +1040,8 @@ public static class ConfigHandler if (profileItem.StreamSecurity.IsNotEmpty()) { - if (profileItem.StreamSecurity != Global.StreamSecurity - && profileItem.StreamSecurity != Global.StreamSecurityReality) + if (profileItem.StreamSecurity is not Global.StreamSecurity + and not Global.StreamSecurityReality) { profileItem.StreamSecurity = string.Empty; } diff --git a/v2rayN/ServiceLib/Models/ServerTestItem.cs b/v2rayN/ServiceLib/Models/ServerTestItem.cs index beef2438..00e26b83 100644 --- a/v2rayN/ServiceLib/Models/ServerTestItem.cs +++ b/v2rayN/ServiceLib/Models/ServerTestItem.cs @@ -9,6 +9,6 @@ public class ServerTestItem public EConfigType ConfigType { get; set; } public bool AllowTest { get; set; } public int QueueNum { get; set; } - public required ProfileItem Profile { get; set; } + public ProfileItem Profile { get; set; } public ECoreType CoreType { get; set; } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index afab27d9..f09d3ee9 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -243,7 +243,6 @@ public partial class CoreConfigSingboxService if (Utils.IsIpv6(predefined)) { rule.answer = new List { $"*. IN AAAA {predefined}" }; - } else { diff --git a/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs index 23722ad0..4bf5237b 100644 --- a/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs @@ -8,9 +8,9 @@ public partial class ClashConnectionsView : ReactiveUserControl Date: Tue, 10 Mar 2026 17:38:03 +0800 Subject: [PATCH 17/35] up 7.19.3 --- v2rayN/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2rayN/Directory.Build.props b/v2rayN/Directory.Build.props index 6c744bb5..12693e1c 100644 --- a/v2rayN/Directory.Build.props +++ b/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.19.2 + 7.19.3 From 5e18567ce6d240420554ddb246752d6651068588 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Thu, 12 Mar 2026 14:21:21 +0800 Subject: [PATCH 18/35] Modify routing rule process name description --- v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 2 +- v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.fr.resx | 4 ++-- v2rayN/ServiceLib/Resx/ResUI.hu.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.ru.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 4 ++-- v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index b00bec2d..5edca7bf 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -3376,7 +3376,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Process (Tun mode) 的本地化字符串。 + /// 查找类似 Process name: Linux/Windows/macOS (sing-box) 的本地化字符串。 /// public static string TbRoutingRuleProcess { get { diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 16836861..b3ffa77e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1027,7 +1027,7 @@ پروتکل sing-box Mux - Process (Tun mode) + Process name: Linux/Windows/macOS (sing-box) IP or IP CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 253831ef..ad88aca5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1024,7 +1024,7 @@ Protocole de multiplexage Mux (sing-box) - Process (Tun mode) + Process name: Linux/Windows/macOS (sing-box) IP ou IP CIDR @@ -1668,4 +1668,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Group by Region - + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index a29d8987..8c734066 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1027,7 +1027,7 @@ sing-box Mux protokoll - Process (Tun mode) + Process name: Linux/Windows/macOS (sing-box) IP vagy IP CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 17416125..3fdd653a 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1027,7 +1027,7 @@ sing-box Mux Protocol - Process (Tun mode) + Process name: Linux/Windows/macOS (sing-box) IP or IP CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index d8a174d5..4aa03795 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1027,7 +1027,7 @@ Протокол Mux для sing-box - Process (Tun mode) + Process name: Linux/Windows/macOS (sing-box) IP-адрес или сеть CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 3a4e4924..6aef06ec 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1024,7 +1024,7 @@ sing-box Mux 多路复用协议 - 进程 (Tun 模式) + 进程名 Linux/Windows/macOS(sing-box) IP 或 IP CIDR @@ -1668,4 +1668,4 @@ 按地区分组 - \ No newline at end of file + diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index c4125679..3ecedfb4 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1024,7 +1024,7 @@ sing-box Mux 多路復用協定 - 行程 (Tun 模式) + 行程名 Linux/Windows/macOS(sing-box) IP 或 IP CIDR From 17ed26cd0626f2aab92342e118a73d73d86f5329 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Thu, 12 Mar 2026 14:56:01 +0800 Subject: [PATCH 19/35] Improve profile matching for Subscription, remove old option --- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 87 +++++++++++++++++-- v2rayN/ServiceLib/Models/ConfigItems.cs | 1 - v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 9 -- v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 3 - v2rayN/ServiceLib/Resx/ResUI.fr.resx | 3 - v2rayN/ServiceLib/Resx/ResUI.hu.resx | 3 - v2rayN/ServiceLib/Resx/ResUI.resx | 3 - v2rayN/ServiceLib/Resx/ResUI.ru.resx | 3 - v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 5 +- v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 3 - .../ViewModels/OptionSettingViewModel.cs | 3 - .../Views/OptionSettingWindow.axaml | 13 --- .../Views/OptionSettingWindow.axaml.cs | 1 - v2rayN/v2rayN/Views/OptionSettingWindow.xaml | 14 --- .../v2rayN/Views/OptionSettingWindow.xaml.cs | 1 - 15 files changed, 82 insertions(+), 70 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 3c587676..34758618 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -95,10 +95,7 @@ public static class ConfigHandler config.GuiItem ??= new(); config.MsgUIItem ??= new(); - config.UiItem ??= new UIItem() - { - EnableUpdateSubOnlyRemarksExist = true - }; + config.UiItem ??= new(); config.UiItem.MainColumnItem ??= new(); config.UiItem.WindowSizeItem ??= new(); @@ -1132,6 +1129,84 @@ public static class ConfigHandler } } + /// + /// Searches the specified collection for a profile item that matches the target profile item based on a series of + /// criteria. + /// + /// The method attempts to find a match by comparing the target's remarks, address, port, and + /// password in various combinations. The search is performed in order of specificity, starting with the most + /// detailed comparison. If no match is found at any stage, the method returns null. + /// An enumerable collection of profile items to search. This parameter can be null. + /// The profile item to match against items in the source collection. This parameter can be null. + /// A profile item from the source collection that matches the target item according to defined criteria; otherwise, + /// null if no match is found or if either parameter is null. + private static ProfileItem? FindMatchedProfileItem(IEnumerable? source, ProfileItem? target) + { + if (source == null || target == null) + { + return null; + } + + var matchedItem = source.FirstOrDefault(t => CompareProfileItem(t, target, true)); + if (matchedItem != null) + { + return matchedItem; + } + + if (target.Remarks.IsNotEmpty()) + { + matchedItem = source.FirstOrDefault(t => t.Remarks == target.Remarks); + if (matchedItem != null) + { + return matchedItem; + } + } + + if (target.Address.IsNotEmpty() && target.Port > 0 && target.Password.IsNotEmpty()) + { + matchedItem = source.FirstOrDefault(t => + IsSameText(t.Address, target.Address) && + t.Port == target.Port && + IsSameText(t.Password, target.Password)); + if (matchedItem != null) + { + return matchedItem; + } + } + + if (target.Address.IsNotEmpty() && target.Port > 0) + { + matchedItem = source.FirstOrDefault(t => + IsSameText(t.Address, target.Address) && + t.Port == target.Port); + if (matchedItem != null) + { + return matchedItem; + } + } + + if (target.Address.IsNotEmpty()) + { + matchedItem = source.FirstOrDefault(t => IsSameText(t.Address, target.Address)); + if (matchedItem != null) + { + return matchedItem; + } + } + + return null; + + static bool IsSameText(string? left, string? right) + { + if (left.IsNullOrEmpty() || right.IsNullOrEmpty()) + { + return false; + } + + return string.Equals(left.TrimEx(), right.TrimEx(), StringComparison.OrdinalIgnoreCase); + } + } + /// /// Remove a single server profile by its index ID /// Deletes the configuration file if it's a custom config @@ -1636,7 +1711,7 @@ public static class ConfigHandler if (activeProfile != null) { var lstSub = await AppManager.Instance.ProfileItems(subid); - var existItem = lstSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == activeProfile.Remarks : CompareProfileItem(t, activeProfile, true)); + var existItem = FindMatchedProfileItem(lstSub, activeProfile); if (existItem != null) { await ConfigHandler.SetDefaultServerIndex(config, existItem.IndexId); @@ -1649,7 +1724,7 @@ public static class ConfigHandler var lstSub = await AppManager.Instance.ProfileItems(subid); foreach (var item in lstSub) { - var existItem = lstOriSub?.FirstOrDefault(t => config.UiItem.EnableUpdateSubOnlyRemarksExist ? t.Remarks == item.Remarks : CompareProfileItem(t, item, true)); + var existItem = FindMatchedProfileItem(lstOriSub, item); if (existItem != null) { await StatisticsManager.Instance.CloneServerStatItem(existItem.IndexId, item.IndexId); diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index 56e5fe47..741c21e5 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -87,7 +87,6 @@ public class MsgUIItem public class UIItem { public bool EnableAutoAdjustMainLvColWidth { get; set; } - public bool EnableUpdateSubOnlyRemarksExist { get; set; } public int MainGirdHeight1 { get; set; } public int MainGirdHeight2 { get; set; } public EGirdOrientation MainGirdOrientation { get; set; } = EGirdOrientation.Vertical; diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 5edca7bf..2111ba0e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -3816,15 +3816,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Updating subscription, only determining if remarks exist 的本地化字符串。 - /// - public static string TbSettingsEnableUpdateSubOnlyRemarksExist { - get { - return ResourceManager.GetString("TbSettingsEnableUpdateSubOnlyRemarksExist", resourceCulture); - } - } - /// /// 查找类似 Exception 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index b3ffa77e..b6ad108e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1098,9 +1098,6 @@ آدرس اینترنتی تست پینگ سرعت - - اشتراک در حال به‌روزرسانی، فقط مشخص کنید که ملاحظاتی آیا وجود دارد! - پایان تست... diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index ad88aca5..0dfe120f 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1095,9 +1095,6 @@ Adresse de test de connexion réelle - - Ne vérifier l’existence de l’alias qu’à la maj. des abonnements - Arrêt du test en cours... diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 8c734066..94d7e48f 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1098,9 +1098,6 @@ Sebesség Ping Teszt URL - - Előfizetés frissítése, csak a megjegyzések létezésének ellenőrzése - Teszt megszakítása... diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 3fdd653a..dc24c384 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1098,9 +1098,6 @@ Speed Ping Test URL - - Updating subscription, only determining if remarks exist - Test terminating... diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 4aa03795..28dc34d0 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1098,9 +1098,6 @@ URL для быстрой проверки реальной задержки - - Обновляя подписку, проверять лишь наличие примечаний - Отмена тестирования... diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 6aef06ec..9f400c17 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1095,9 +1095,6 @@ 真连接测试地址 - - 更新订阅时只判断别名已存在否 - 测试终止中... @@ -1668,4 +1665,4 @@ 按地区分组 - + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 3ecedfb4..b35b603f 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1095,9 +1095,6 @@ 真連線測試位址 - - 更新訂閱時只判斷別名是否存在 - 測試終止中... diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index b5bfe71f..d936c5df 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -47,7 +47,6 @@ public class OptionSettingViewModel : MyReactiveObject [Reactive] public bool KeepOlderDedupl { get; set; } [Reactive] public bool DisplayRealTimeSpeed { get; set; } [Reactive] public bool EnableAutoAdjustMainLvColWidth { get; set; } - [Reactive] public bool EnableUpdateSubOnlyRemarksExist { get; set; } [Reactive] public bool AutoHideStartup { get; set; } [Reactive] public bool Hide2TrayWhenClose { get; set; } [Reactive] public bool MacOSShowInDock { get; set; } @@ -180,7 +179,6 @@ public class OptionSettingViewModel : MyReactiveObject DisplayRealTimeSpeed = _config.GuiItem.DisplayRealTimeSpeed; KeepOlderDedupl = _config.GuiItem.KeepOlderDedupl; EnableAutoAdjustMainLvColWidth = _config.UiItem.EnableAutoAdjustMainLvColWidth; - EnableUpdateSubOnlyRemarksExist = _config.UiItem.EnableUpdateSubOnlyRemarksExist; AutoHideStartup = _config.UiItem.AutoHideStartup; Hide2TrayWhenClose = _config.UiItem.Hide2TrayWhenClose; MacOSShowInDock = _config.UiItem.MacOSShowInDock; @@ -345,7 +343,6 @@ public class OptionSettingViewModel : MyReactiveObject _config.GuiItem.DisplayRealTimeSpeed = DisplayRealTimeSpeed; _config.GuiItem.KeepOlderDedupl = KeepOlderDedupl; _config.UiItem.EnableAutoAdjustMainLvColWidth = EnableAutoAdjustMainLvColWidth; - _config.UiItem.EnableUpdateSubOnlyRemarksExist = EnableUpdateSubOnlyRemarksExist; _config.UiItem.AutoHideStartup = AutoHideStartup; _config.UiItem.Hide2TrayWhenClose = Hide2TrayWhenClose; _config.UiItem.MacOSShowInDock = MacOSShowInDock; diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index cfcb48f0..8c4f1fcc 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -410,19 +410,6 @@ Margin="{StaticResource Margin4}" HorizontalAlignment="Left" /> - - - this.Bind(ViewModel, vm => vm.DisplayRealTimeSpeed, v => v.togDisplayRealTimeSpeed.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.KeepOlderDedupl, v => v.togKeepOlderDedupl.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.EnableAutoAdjustMainLvColWidth, v => v.togEnableAutoAdjustMainLvColWidth.IsChecked).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.EnableUpdateSubOnlyRemarksExist, v => v.togEnableUpdateSubOnlyRemarksExist.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoHideStartup, v => v.togAutoHideStartup.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Hide2TrayWhenClose, v => v.togHide2TrayWhenClose.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MacOSShowInDock, v => v.togMacOSShowInDock.IsChecked).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 8f0d24d4..133b4134 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -630,20 +630,6 @@ Margin="{StaticResource Margin8}" HorizontalAlignment="Left" /> - - - vm.DisplayRealTimeSpeed, v => v.togDisplayRealTimeSpeed.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.KeepOlderDedupl, v => v.togKeepOlderDedupl.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.EnableAutoAdjustMainLvColWidth, v => v.togEnableAutoAdjustMainLvColWidth.IsChecked).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.EnableUpdateSubOnlyRemarksExist, v => v.togEnableUpdateSubOnlyRemarksExist.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoHideStartup, v => v.togAutoHideStartup.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.EnableDragDropSort, v => v.togEnableDragDropSort.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.DoubleClick2Activate, v => v.togDoubleClick2Activate.IsChecked).DisposeWith(disposables); From 5cf3d6eff6c9385f60ecbb6a6b4ed14bf5386d7c Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:19:30 +0800 Subject: [PATCH 20/35] Modify routing rule process name description --- v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 2 +- v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.fr.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.hu.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.ru.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 2 +- v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 2111ba0e..5a1ea759 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -3376,7 +3376,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Process name: Linux/Windows/macOS (sing-box) 的本地化字符串。 + /// 查找类似 Process (Linux/Windows) 的本地化字符串。 /// public static string TbRoutingRuleProcess { get { diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index b6ad108e..1dd09e85 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1027,7 +1027,7 @@ پروتکل sing-box Mux - Process name: Linux/Windows/macOS (sing-box) + Process (Linux/Windows) IP or IP CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 0dfe120f..c873db43 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1024,7 +1024,7 @@ Protocole de multiplexage Mux (sing-box) - Process name: Linux/Windows/macOS (sing-box) + Process (Linux/Windows) IP ou IP CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 94d7e48f..f59a652b 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1027,7 +1027,7 @@ sing-box Mux protokoll - Process name: Linux/Windows/macOS (sing-box) + Process (Linux/Windows) IP vagy IP CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index dc24c384..f299a877 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1027,7 +1027,7 @@ sing-box Mux Protocol - Process name: Linux/Windows/macOS (sing-box) + Process (Linux/Windows) IP or IP CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 28dc34d0..957e6052 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1027,7 +1027,7 @@ Протокол Mux для sing-box - Process name: Linux/Windows/macOS (sing-box) + Process (Linux/Windows) IP-адрес или сеть CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 9f400c17..d655c30d 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1024,7 +1024,7 @@ sing-box Mux 多路复用协议 - 进程名 Linux/Windows/macOS(sing-box) + 进程 (Linux/Windows) IP 或 IP CIDR diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index b35b603f..65b1dbfb 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1024,7 +1024,7 @@ sing-box Mux 多路復用協定 - 行程名 Linux/Windows/macOS(sing-box) + 行程 (Linux/Windows) IP 或 IP CIDR From 0f4031f4451df388634ac9d856acae37cc500f2f Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 12 Mar 2026 12:45:28 +0000 Subject: [PATCH 21/35] Update dep (#8926) --- v2rayN/Directory.Packages.props | 10 +++++----- .../ServiceLib/ViewModels/CheckUpdateViewModel.cs | 4 ++-- .../ViewModels/ClashConnectionsViewModel.cs | 2 +- .../ServiceLib/ViewModels/ClashProxiesViewModel.cs | 6 +++--- .../ServiceLib/ViewModels/MainWindowViewModel.cs | 12 ++++++------ v2rayN/ServiceLib/ViewModels/MsgViewModel.cs | 2 +- v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs | 10 +++++----- v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs | 14 +++++++------- v2rayN/v2rayN.Desktop/Program.cs | 2 +- .../Views/ClashConnectionsView.axaml.cs | 2 +- v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs | 8 ++++---- v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs | 4 ++-- v2rayN/v2rayN/App.xaml.cs | 5 +++++ v2rayN/v2rayN/GlobalUsings.cs | 1 + v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs | 2 +- v2rayN/v2rayN/Views/MainWindow.xaml.cs | 8 ++++---- v2rayN/v2rayN/Views/ProfilesView.xaml.cs | 4 ++-- v2rayN/v2rayN/v2rayN.csproj | 2 +- 18 files changed, 52 insertions(+), 46 deletions(-) diff --git a/v2rayN/Directory.Packages.props b/v2rayN/Directory.Packages.props index 0921f480..839b3384 100644 --- a/v2rayN/Directory.Packages.props +++ b/v2rayN/Directory.Packages.props @@ -9,18 +9,18 @@ - + - + - + - + - + diff --git a/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs b/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs index 7bcb36ea..dc31b41b 100644 --- a/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs @@ -204,7 +204,7 @@ public class CheckUpdateViewModel : MyReactiveObject private async Task UpdateFinishedSub(bool blReload) { - RxApp.MainThreadScheduler.Schedule(blReload, (scheduler, blReload) => + RxSchedulers.MainThreadScheduler.Schedule(blReload, (scheduler, blReload) => { _ = UpdateFinishedResult(blReload); return Disposable.Empty; @@ -317,7 +317,7 @@ public class CheckUpdateViewModel : MyReactiveObject Remarks = msg, }; - RxApp.MainThreadScheduler.Schedule(item, (scheduler, model) => + RxSchedulers.MainThreadScheduler.Schedule(item, (scheduler, model) => { _ = UpdateViewResult(model); return Disposable.Empty; diff --git a/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs b/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs index 48500f3e..07de23f2 100644 --- a/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs @@ -56,7 +56,7 @@ public class ClashConnectionsViewModel : MyReactiveObject return; } - RxApp.MainThreadScheduler.Schedule(ret?.connections, (scheduler, model) => + RxSchedulers.MainThreadScheduler.Schedule(ret?.connections, (scheduler, model) => { _ = RefreshConnections(model); return Disposable.Empty; diff --git a/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs index 6628e11d..6220719b 100644 --- a/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs @@ -90,7 +90,7 @@ public class ClashProxiesViewModel : MyReactiveObject AppEvents.ProxiesReloadRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await ProxiesReload()); #endregion AppEvents @@ -173,7 +173,7 @@ public class ClashProxiesViewModel : MyReactiveObject if (refreshUI) { - RxApp.MainThreadScheduler.Schedule(() => _ = RefreshProxyGroups()); + RxSchedulers.MainThreadScheduler.Schedule(() => _ = RefreshProxyGroups()); } } @@ -387,7 +387,7 @@ public class ClashProxiesViewModel : MyReactiveObject } var model = new SpeedTestResult() { IndexId = item.Name, Delay = result }; - RxApp.MainThreadScheduler.Schedule(model, (scheduler, model) => + RxSchedulers.MainThreadScheduler.Schedule(model, (scheduler, model) => { _ = ProxiesDelayTestResult(model); return Disposable.Empty; diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 0bd0c588..ee92a0cd 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -228,22 +228,22 @@ public class MainWindowViewModel : MyReactiveObject AppEvents.ReloadRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await Reload()); AppEvents.AddServerViaScanRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await AddServerViaScanAsync()); AppEvents.AddServerViaClipboardRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await AddServerViaClipboardAsync(null)); AppEvents.SubscriptionsUpdateRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy)); #endregion AppEvents @@ -583,7 +583,7 @@ public class MainWindowViewModel : MyReactiveObject private void ReloadResult(bool showClashUI) { - RxApp.MainThreadScheduler.Schedule(() => + RxSchedulers.MainThreadScheduler.Schedule(() => { ShowClashUI = showClashUI; TabMainSelectedIndex = showClashUI ? TabMainSelectedIndex : 0; @@ -592,7 +592,7 @@ public class MainWindowViewModel : MyReactiveObject private void SetReloadEnabled(bool enabled) { - RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled); + RxSchedulers.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled); } private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext) diff --git a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs index 68bf71b5..06657a93 100644 --- a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs @@ -31,7 +31,7 @@ public class MsgViewModel : MyReactiveObject AppEvents.SendMsgViewRequested .AsObservable() - //.ObserveOn(RxApp.MainThreadScheduler) + //.ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(content => _ = AppendQueueMsg(content)); } diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index edeec49e..7700d416 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -228,22 +228,22 @@ public class ProfilesViewModel : MyReactiveObject AppEvents.ProfilesRefreshRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await RefreshServersBiz()); AppEvents.SubscriptionsRefreshRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await RefreshSubscriptions()); AppEvents.DispatcherStatisticsRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async result => await UpdateStatistics(result)); AppEvents.SetDefaultServerRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async indexId => await SetDefaultServer(indexId)); #endregion AppEvents @@ -732,7 +732,7 @@ public class ProfilesViewModel : MyReactiveObject _speedtestService ??= new SpeedtestService(_config, async (SpeedTestResult result) => { - RxApp.MainThreadScheduler.Schedule(result, (scheduler, result) => + RxSchedulers.MainThreadScheduler.Schedule(result, (scheduler, result) => { _ = SetSpeedTestResult(result); return Disposable.Empty; diff --git a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs index 9269851d..b8154c16 100644 --- a/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs @@ -200,27 +200,27 @@ public class StatusBarViewModel : MyReactiveObject AppEvents.DispatcherStatisticsRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async result => await UpdateStatistics(result)); AppEvents.RoutingsMenuRefreshRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await RefreshRoutingsMenu()); AppEvents.TestServerRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await TestServerAvailability()); AppEvents.InboundDisplayRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await InboundDisplayStatus()); AppEvents.SysProxyChangeRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async result => await SetListenerType(result)); #endregion AppEvents @@ -243,7 +243,7 @@ public class StatusBarViewModel : MyReactiveObject { AppEvents.ProfilesRefreshRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async _ => await RefreshServersBiz()); //.DisposeWith(_disposables); } } @@ -362,7 +362,7 @@ public class StatusBarViewModel : MyReactiveObject private async Task TestServerAvailabilitySub(string msg) { - RxApp.MainThreadScheduler.Schedule(msg, (scheduler, msg) => + RxSchedulers.MainThreadScheduler.Schedule(msg, (scheduler, msg) => { _ = TestServerAvailabilityResult(msg); return Disposable.Empty; diff --git a/v2rayN/v2rayN.Desktop/Program.cs b/v2rayN/v2rayN.Desktop/Program.cs index 70c43130..0ad036ec 100644 --- a/v2rayN/v2rayN.Desktop/Program.cs +++ b/v2rayN/v2rayN.Desktop/Program.cs @@ -59,7 +59,7 @@ internal class Program //.WithInterFont() .WithFontByDefault() .LogToTrace() - .UseReactiveUI(); + .UseReactiveUI(_ => { }); if (OperatingSystem.IsMacOS()) { diff --git a/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs index 4bf5237b..90690c1e 100644 --- a/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs @@ -28,7 +28,7 @@ public partial class ClashConnectionsView : ReactiveUserControl StorageUI()) .DisposeWith(disposables); }); diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs index 5c5ba9cb..7e96fad2 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs @@ -128,25 +128,25 @@ public partial class MainWindow : WindowBase AppEvents.SendSnackMsgRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async content => await DelegateSnackMsg(content)) .DisposeWith(disposables); AppEvents.AppExitRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(_ => StorageUI()) .DisposeWith(disposables); AppEvents.ShutdownRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(content => Shutdown(content)) .DisposeWith(disposables); AppEvents.ShowHideWindowRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(blShow => ShowHideWindow(blShow)) .DisposeWith(disposables); }); diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs index 40336be5..7d803f10 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs @@ -90,13 +90,13 @@ public partial class ProfilesView : ReactiveUserControl AppEvents.AppExitRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(_ => StorageUI()) .DisposeWith(disposables); AppEvents.AdjustMainLvColWidthRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(_ => AutofitColumnWidth()) .DisposeWith(disposables); }); diff --git a/v2rayN/v2rayN/App.xaml.cs b/v2rayN/v2rayN/App.xaml.cs index ca56311b..3c5f1989 100644 --- a/v2rayN/v2rayN/App.xaml.cs +++ b/v2rayN/v2rayN/App.xaml.cs @@ -39,6 +39,11 @@ public partial class App : Application } AppManager.Instance.InitComponents(); + + RxAppBuilder.CreateReactiveUIBuilder() + .WithWpf() + .BuildApp(); + base.OnStartup(e); } diff --git a/v2rayN/v2rayN/GlobalUsings.cs b/v2rayN/v2rayN/GlobalUsings.cs index 1f2d1936..fb63f7eb 100644 --- a/v2rayN/v2rayN/GlobalUsings.cs +++ b/v2rayN/v2rayN/GlobalUsings.cs @@ -20,6 +20,7 @@ global using System.Windows.Threading; global using DynamicData; global using DynamicData.Binding; global using ReactiveUI; +global using ReactiveUI.Builder; global using ReactiveUI.Fody.Helpers; global using ServiceLib; global using ServiceLib.Base; diff --git a/v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs b/v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs index 47482b8e..337b38e4 100644 --- a/v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs +++ b/v2rayN/v2rayN/Views/ClashConnectionsView.xaml.cs @@ -33,7 +33,7 @@ public partial class ClashConnectionsView AppEvents.AppExitRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(_ => StorageUI()) .DisposeWith(disposables); }); diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml.cs b/v2rayN/v2rayN/Views/MainWindow.xaml.cs index 93e62aad..42d8aabc 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/MainWindow.xaml.cs @@ -127,25 +127,25 @@ public partial class MainWindow AppEvents.SendSnackMsgRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(async content => await DelegateSnackMsg(content)) .DisposeWith(disposables); AppEvents.AppExitRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(_ => StorageUI()) .DisposeWith(disposables); AppEvents.ShutdownRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(content => Shutdown(content)) .DisposeWith(disposables); AppEvents.ShowHideWindowRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(blShow => ShowHideWindow(blShow)) .DisposeWith(disposables); }); diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs index b088fe7b..7075d358 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs @@ -84,13 +84,13 @@ public partial class ProfilesView AppEvents.AppExitRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(_ => StorageUI()) .DisposeWith(disposables); AppEvents.AdjustMainLvColWidthRequested .AsObservable() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOn(RxSchedulers.MainThreadScheduler) .Subscribe(_ => AutofitColumnWidth()) .DisposeWith(disposables); }); diff --git a/v2rayN/v2rayN/v2rayN.csproj b/v2rayN/v2rayN/v2rayN.csproj index 9654625d..45cbc376 100644 --- a/v2rayN/v2rayN/v2rayN.csproj +++ b/v2rayN/v2rayN/v2rayN.csproj @@ -2,7 +2,7 @@ WinExe - net8.0-windows10.0.17763 + net8.0-windows10.0.19041.0 true true Resources\v2rayN.ico From e6af9ab34236bd5f59c2419392341138e10e33b3 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:36:58 +0800 Subject: [PATCH 22/35] Fix https://github.com/2dust/v2rayN/issues/8916 --- .../ServiceLib/ViewModels/ProfilesSelectViewModel.cs | 11 +++-------- v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs index eb010c58..b3810b52 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs @@ -188,14 +188,9 @@ public class ProfilesSelectViewModel : MyReactiveObject { SubItems.Add(item); } - if (_subIndexId != null && SubItems.FirstOrDefault(t => t.Id == _subIndexId) != null) - { - SelectedSub = SubItems.FirstOrDefault(t => t.Id == _subIndexId); - } - else - { - SelectedSub = SubItems.First(); - } + SelectedSub = (_config.SubIndexId.IsNotEmpty() + ? SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId) + : null) ?? SubItems.LastOrDefault(); } private async Task?> GetProfileItemsEx(string subid, string filter) diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 7700d416..6e7e183c 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -391,14 +391,9 @@ public class ProfilesViewModel : MyReactiveObject { SubItems.Add(item); } - if (_config.SubIndexId != null && SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId) != null) - { - SelectedSub = SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId); - } - else - { - SelectedSub = SubItems.First(); - } + SelectedSub = (_config.SubIndexId.IsNotEmpty() + ? SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId) + : null) ?? SubItems.LastOrDefault(); } private async Task?> GetProfileItemsEx(string subid, string filter) From 214a09bc48537c62d971d97c785de326abc1b5f1 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:44:20 +0800 Subject: [PATCH 23/35] up 7.19.4 --- v2rayN/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2rayN/Directory.Build.props b/v2rayN/Directory.Build.props index 12693e1c..37a05fdc 100644 --- a/v2rayN/Directory.Build.props +++ b/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.19.3 + 7.19.4 From eb0ef90ed254ba14f637eaccd1f6e0b4039e4bcd Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sun, 15 Mar 2026 20:11:45 +0800 Subject: [PATCH 24/35] Bug fix https://github.com/2dust/v2rayN/issues/8906 --- v2rayN/ServiceLib/ViewModels/MsgViewModel.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs index 06657a93..fbe06cb0 100644 --- a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs @@ -86,15 +86,25 @@ public class MsgViewModel : MyReactiveObject } catch (Exception ex) { - _queueMsg.Enqueue(ex.Message); + EnqueueWithLimit(ex.Message); _lastMsgFilterNotAvailable = true; } } - _queueMsg.Enqueue(msg); + EnqueueWithLimit(msg); if (!msg.EndsWith(Environment.NewLine)) { - _queueMsg.Enqueue(Environment.NewLine); + EnqueueWithLimit(Environment.NewLine); + } + } + + private void EnqueueWithLimit(string item) + { + _queueMsg.Enqueue(item); + + while (_queueMsg.Count > NumMaxMsg) + { + _queueMsg.TryDequeue(out _); } } From a2929c60865de25b7d9dddd7787bde743668033c Mon Sep 17 00:00:00 2001 From: JieXu Date: Fri, 20 Mar 2026 14:25:42 +0800 Subject: [PATCH 25/35] Update package-debian.sh (#8941) * Update package-debian.sh * Update build-linux.yml * Update package-debian.sh * Update package-debian.sh * Update package-debian.sh * Update package-rhel.sh * Update package-debian.sh * Update package-debian.sh * Update package-rhel.sh * Update package-debian.sh * Update package-rhel.sh * Update package-debian.sh * Update package-rhel.sh * Update package-rhel.sh * Update package-rhel.sh * Update package-debian.sh * Update package-rhel.sh --- .github/workflows/build-linux.yml | 79 +++- package-debian.sh | 641 +++++++++++++++++++++++++++--- package-rhel.sh | 59 +-- 3 files changed, 679 insertions(+), 100 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 88cbb388..3f2fe033 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -9,9 +9,6 @@ on: push: branches: - master - tags: - - 'v*' - - 'V*' permissions: contents: write @@ -56,23 +53,6 @@ jobs: path: | ${{ github.workspace }}/v2rayN/Release/linux* - # release debian package - - name: Package debian - if: github.event.inputs.release_tag != '' - run: | - chmod 755 package-debian.sh - ./package-debian.sh "$OutputArch" "$OutputPath64" "${{ github.event.inputs.release_tag }}" - ./package-debian.sh "$OutputArchArm" "$OutputPathArm64" "${{ github.event.inputs.release_tag }}" - - - name: Upload deb to release - uses: svenstaro/upload-release-action@v2 - if: github.event.inputs.release_tag != '' - with: - file: ${{ github.workspace }}/v2rayN*.deb - tag: ${{ github.event.inputs.release_tag }} - file_glob: true - prerelease: true - # release zip archive - name: Package release zip archive if: github.event.inputs.release_tag != '' @@ -90,6 +70,65 @@ jobs: file_glob: true prerelease: true + deb: + needs: build + if: | + (github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag != '') || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) + runs-on: ubuntu-24.04 + container: + image: debian:13 + env: + RELEASE_TAG: ${{ github.event.inputs.release_tag != '' && github.event.inputs.release_tag || github.ref_name }} + + steps: + - name: Prepare tools (Debian) + shell: bash + run: | + set -euo pipefail + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y sudo git rsync findutils tar gzip unzip which curl jq wget file \ + ca-certificates desktop-file-utils xdg-utils fakeroot dpkg-dev \ + libc6 libgcc-s1 libstdc++6 zlib1g libicu-dev libssl-dev + + - name: Checkout repo (for scripts) + uses: actions/checkout@v6.0.2 + with: + submodules: 'recursive' + fetch-depth: '0' + + - name: Ensure script permissions + run: chmod 755 package-debian.sh + + - name: Package DEB (Debian-family) + run: ./package-debian.sh "${RELEASE_TAG}" --arch all + + - name: Collect DEBs into workspace + run: | + mkdir -p "$GITHUB_WORKSPACE/dist/deb" + rsync -av "$HOME/debbuild/" "$GITHUB_WORKSPACE/dist/deb/" || true + find "$GITHUB_WORKSPACE/dist/deb" -name "v2rayn_*_amd64.deb" \ + -exec mv {} "$GITHUB_WORKSPACE/dist/deb/v2rayN-linux-64.deb" \; || true + find "$GITHUB_WORKSPACE/dist/deb" -name "v2rayn_*_arm64.deb" \ + -exec mv {} "$GITHUB_WORKSPACE/dist/deb/v2rayN-linux-arm64.deb" \; || true + echo "==== Dist tree ====" + ls -R "$GITHUB_WORKSPACE/dist/deb" || true + + - name: Upload DEB artifacts + uses: actions/upload-artifact@v7.0.0 + with: + name: v2rayN-deb + path: dist/deb/**/*.deb + + - name: Upload DEBs to release + uses: svenstaro/upload-release-action@v2 + with: + file: dist/deb/**/*.deb + tag: ${{ env.RELEASE_TAG }} + file_glob: true + prerelease: true + rpm: needs: build if: | diff --git a/package-debian.sh b/package-debian.sh index 3b91e7ca..fa21461d 100644 --- a/package-debian.sh +++ b/package-debian.sh @@ -1,70 +1,607 @@ -#!/bin/bash +#!/usr/bin/env bash +set -euo pipefail -Arch="$1" -OutputPath="$2" -Version="$3" +# Require Debian base branch +. /etc/os-release -FileName="v2rayN-${Arch}.zip" -wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/$FileName" -7z x $FileName -cp -rf v2rayN-${Arch}/* $OutputPath +case "${ID:-}" in + debian) + echo "Detected supported system: ${NAME:-$ID} ${VERSION_ID:-}" + ;; + *) + echo "Unsupported system: ${NAME:-unknown} (${ID:-unknown})." + echo "This script only supports: Debian." + exit 1 + ;; +esac -PackagePath="v2rayN-Package-${Arch}" -mkdir -p "${PackagePath}/DEBIAN" -mkdir -p "${PackagePath}/opt" -cp -rf $OutputPath "${PackagePath}/opt/v2rayN" -echo "When this file exists, app will not store configs under this folder" > "${PackagePath}/opt/v2rayN/NotStoreConfigHere.txt" +# Kernel version +MIN_KERNEL="6.11" +CURRENT_KERNEL="$(uname -r)" -if [ $Arch = "linux-64" ]; then - Arch2="amd64" -else - Arch2="arm64" +lowest="$(printf '%s\n%s\n' "$MIN_KERNEL" "$CURRENT_KERNEL" | sort -V | head -n1)" + +if [[ "$lowest" != "$MIN_KERNEL" ]]; then + echo "Kernel $CURRENT_KERNEL is below $MIN_KERNEL" + exit 1 fi -echo $Arch2 -# basic -cat >"${PackagePath}/DEBIAN/control" <<-EOF -Package: v2rayN -Version: $Version -Architecture: $Arch2 -Maintainer: https://github.com/2dust/v2rayN -Depends: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1), libfreetype6 (>= 2.11) -Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others +echo "[OK] Kernel $CURRENT_KERNEL verified." + +# Config & Parse arguments +VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty +WITH_CORE="both" # Default: bundle both xray+sing-box +FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads +ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target) +BUILD_FROM="" # --buildfrom 1|2|3 to select channel non-interactively + +# If the first argument starts with --, do not treat it as a version number +if [[ "${VERSION_ARG:-}" == --* ]]; then + VERSION_ARG="" +fi +# Take the first non --* argument as version, discard it +if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi + +# Parse remaining optional arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --with-core) WITH_CORE="${2:-both}"; shift 2;; + --xray-ver) XRAY_VER="${2:-}"; shift 2;; + --singbox-ver) SING_VER="${2:-}"; shift 2;; + --netcore) FORCE_NETCORE=1; shift;; + --arch) ARCH_OVERRIDE="${2:-}"; shift 2;; + --buildfrom) BUILD_FROM="${2:-}"; shift 2;; + *) + if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi + shift;; + esac +done + +# Conflict: version number AND --buildfrom cannot be used together +if [[ -n "${VERSION_ARG:-}" && -n "${BUILD_FROM:-}" ]]; then + echo "You cannot specify both an explicit version and --buildfrom at the same time." + echo " Provide either a version (e.g. 7.14.0) OR --buildfrom 1|2|3." + exit 1 +fi + +# Check and install dependencies +host_arch="$(uname -m)" +[[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; } + +install_ok=0 + +if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get -y install \ + curl unzip tar jq rsync ca-certificates git dpkg-dev fakeroot file \ + desktop-file-utils xdg-utils wget + + if [[ "$host_arch" == "aarch64" ]]; then + sudo dpkg --add-architecture amd64 || true + sudo apt-get update + sudo apt-get -y install \ + libc6:amd64 libgcc-s1:amd64 libstdc++6:amd64 zlib1g:amd64 libfontconfig1:amd64 + elif [[ "$host_arch" == "x86_64" ]]; then + sudo dpkg --add-architecture arm64 || true + sudo apt-get update + sudo apt-get -y install \ + libc6:arm64 libgcc-s1:arm64 libstdc++6:arm64 zlib1g:arm64 libfontconfig1:arm64 + fi + + # Install .NET SDK 8 via official script + wget -q https://dot.net/v1/dotnet-install.sh + chmod +x dotnet-install.sh + ./dotnet-install.sh --channel 8.0 --install-dir "$HOME/.dotnet" + + export PATH="$HOME/.dotnet:$PATH" + export DOTNET_ROOT="$HOME/.dotnet" + + dotnet --info >/dev/null 2>&1 && install_ok=1 +fi + +if [[ "$install_ok" -ne 1 ]]; then + echo "Could not auto-install dependencies for '$ID'. Make sure these are available:" + echo "dotnet-sdk 8.x, curl, unzip, tar, rsync, git, dpkg-deb, desktop-file-utils, xdg-utils" + exit 1 +fi + +# Root directory +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Git submodules (best effort) +if [[ -f .gitmodules ]]; then + git submodule sync --recursive || true + git submodule update --init --recursive || true +fi + +# Locate project +PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj" +if [[ ! -f "$PROJECT" ]]; then + PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)" +fi +[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; } + +choose_channel() { + # If --buildfrom provided, map it directly and skip interaction. + if [[ -n "${BUILD_FROM:-}" ]]; then + case "$BUILD_FROM" in + 1) echo "latest"; return 0;; + 2) echo "prerelease"; return 0;; + 3) echo "keep"; return 0;; + *) echo "[ERROR] Invalid --buildfrom value: ${BUILD_FROM}. Use 1|2|3." >&2; exit 1;; + esac + fi + + # Print menu to stderr and read from /dev/tty so stdout only carries the token. + local ch="latest" sel="" + + if [[ -t 0 ]]; then + echo "[?] Choose v2rayN release channel:" >&2 + echo " 1) Latest (stable) [default]" >&2 + echo " 2) Pre-release (preview)" >&2 + echo " 3) Keep current (do nothing)" >&2 + printf "Enter 1, 2 or 3 [default 1]: " >&2 + + if read -r sel /dev/null 2>&1; then + git fetch --tags --force --prune --depth=1 || true + if git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then + ref="${want}" + fi + if [[ -n "$ref" ]]; then + echo "[OK] Found ref '${ref}', checking out..." + git checkout -f "${ref}" + if [[ -f .gitmodules ]]; then + git submodule sync --recursive || true + git submodule update --init --recursive || true + fi + return 0 + fi + fi + return 1 +} + +apply_channel_or_keep() { + local ch="$1" tag + + if [[ "$ch" == "keep" ]]; then + echo "[*] Keep current repository state (no checkout)." + VERSION="$(git describe --tags --abbrev=0 2>/dev/null || echo '0.0.0+git')" + VERSION="${VERSION#v}" + return 0 + fi + + echo "[*] Resolving ${ch} tag from GitHub releases..." + if [[ "$ch" == "prerelease" ]]; then + tag="$(get_latest_tag_prerelease || true)" + else + tag="$(get_latest_tag_latest || true)" + fi + + [[ -n "$tag" ]] || { echo "Failed to resolve latest tag for channel '${ch}'."; exit 1; } + echo "[*] Latest tag for '${ch}': ${tag}" + git_try_checkout "$tag" || { echo "Failed to checkout '${tag}'."; exit 1; } + VERSION="${tag#v}" +} + +if git rev-parse --git-dir >/dev/null 2>&1; then + if [[ -n "${VERSION_ARG:-}" ]]; then + clean_ver="${VERSION_ARG#v}" + if git_try_checkout "$clean_ver"; then + VERSION="$clean_ver" + else + echo "[WARN] Tag '${VERSION_ARG}' not found." + ch="$(choose_channel)" + apply_channel_or_keep "$ch" + fi + else + ch="$(choose_channel)" + apply_channel_or_keep "$ch" + fi +else + echo "Current directory is not a git repo; proceeding on current tree." + VERSION="${VERSION_ARG:-0.0.0}" +fi + +VERSION="${VERSION#v}" +echo "[*] GUI version resolved as: ${VERSION}" + +download_xray() { + local outdir="$1" rid="$2" ver="${XRAY_VER:-}" url tmp zipname="xray.zip" + mkdir -p "$outdir" + if [[ -z "$ver" ]]; then + ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \ + | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true + fi + [[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; } + if [[ "$rid" == "linux-arm64" ]]; then + url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip" + else + url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip" + fi + echo "[+] Download xray: $url" + tmp="$(mktemp -d)" + curl -fL "$url" -o "$tmp/$zipname" + unzip -q "$tmp/$zipname" -d "$tmp" + install -m 755 "$tmp/xray" "$outdir/xray" + rm -rf "$tmp" +} + +download_singbox() { + local outdir="$1" rid="$2" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin + mkdir -p "$outdir" + if [[ -z "$ver" ]]; then + ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \ + | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true + fi + [[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; } + if [[ "$rid" == "linux-arm64" ]]; then + url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz" + else + url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz" + fi + echo "[+] Download sing-box: $url" + tmp="$(mktemp -d)" + curl -fL "$url" -o "$tmp/$tarname" + tar -C "$tmp" -xzf "$tmp/$tarname" + bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)" + [[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; rm -rf "$tmp"; return 1; } + install -m 755 "$bin" "$outdir/sing-box" + rm -rf "$tmp" +} + +unify_geo_layout() { + local outroot="$1" + mkdir -p "$outroot/bin" + local names=( + "geosite.dat" + "geoip.dat" + "geoip-only-cn-private.dat" + "Country.mmdb" + "geoip.metadb" + ) + for n in "${names[@]}"; do + if [[ -f "$outroot/bin/xray/$n" ]]; then + mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n" + fi + done +} + +download_geo_assets() { + local outroot="$1" + local bin_dir="$outroot/bin" + local srss_dir="$bin_dir/srss" + mkdir -p "$bin_dir" "$srss_dir" + + echo "[+] Download Xray Geo to ${bin_dir}" + curl -fsSL -o "$bin_dir/geosite.dat" \ + "https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat" + curl -fsSL -o "$bin_dir/geoip.dat" \ + "https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat" + curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \ + "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat" + curl -fsSL -o "$bin_dir/Country.mmdb" \ + "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb" + + echo "[+] Download sing-box rule DB & rule-sets" + curl -fsSL -o "$bin_dir/geoip.metadb" \ + "https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true + + for f in \ + geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \ + geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do + curl -fsSL -o "$srss_dir/$f" \ + "https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true + done + + for f in \ + geosite-cn.srs geosite-gfw.srs geosite-google.srs geosite-greatfire.srs \ + geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do + curl -fsSL -o "$srss_dir/$f" \ + "https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true + done + + unify_geo_layout "$outroot" +} + +download_v2rayn_bundle() { + local outroot="$1" rid="$2" + local url="" + if [[ "$rid" == "linux-arm64" ]]; then + url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64.zip" + else + url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip" + fi + echo "[+] Try v2rayN bundle archive: $url" + local tmp zipname + tmp="$(mktemp -d)"; zipname="$tmp/v2rayn.zip" + curl -fL "$url" -o "$zipname" || { echo "[!] Bundle download failed"; return 1; } + unzip -q "$zipname" -d "$tmp" || { echo "[!] Bundle unzip failed"; return 1; } + + if [[ -d "$tmp/bin" ]]; then + mkdir -p "$outroot/bin" + rsync -a "$tmp/bin/" "$outroot/bin/" + else + rsync -a "$tmp/" "$outroot/" + fi + + rm -f "$outroot/v2rayn.zip" 2>/dev/null || true + find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true + + local nested_dir + nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)" + if [[ -n "$nested_dir" && -d "$nested_dir/bin" ]]; then + mkdir -p "$outroot/bin" + rsync -a "$nested_dir/bin/" "$outroot/bin/" + rm -rf "$nested_dir" + fi + + # Unify to bin/ + unify_geo_layout "$outroot" + + echo "[+] Bundle extracted to $outroot" +} + +BUILT_DEBS=() +BUILT_ALL=0 +OUTPUT_DIR="$HOME/debbuild" +mkdir -p "$OUTPUT_DIR" + +build_for_arch() { + local short="$1" + local rid deb_arch outdir_name + case "$short" in + x64) rid="linux-x64"; deb_arch="amd64"; outdir_name="amd64" ;; + arm64) rid="linux-arm64"; deb_arch="arm64"; outdir_name="arm64" ;; + *) echo "Unknown arch '$short' (use x64|arm64)"; return 1 ;; + esac + + echo "[*] Building for target: $short (RID=$rid, DEB arch=$deb_arch)" + + dotnet clean "$PROJECT" -c Release + rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true + + dotnet restore "$PROJECT" + dotnet publish "$PROJECT" \ + -c Release -r "$rid" \ + -p:PublishSingleFile=false \ + -p:SelfContained=true + + local RID_DIR="$rid" + local PUBDIR + PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish" + [[ -d "$PUBDIR" ]] || { echo "Publish directory not found: $PUBDIR"; return 1; } + + local WORKDIR PKGROOT STAGE DEBIAN_DIR + WORKDIR="$(mktemp -d)" + PKGROOT="v2rayN-publish" + STAGE="$WORKDIR/${PKGROOT}_${VERSION}_${deb_arch}" + DEBIAN_DIR="$STAGE/DEBIAN" + + mkdir -p "$STAGE/opt/v2rayN" + mkdir -p "$STAGE/usr/bin" + mkdir -p "$STAGE/usr/share/applications" + mkdir -p "$STAGE/usr/share/icons/hicolor/256x256/apps" + mkdir -p "$DEBIAN_DIR" + + # Stage publish content from source build + cp -a "$PUBDIR/." "$STAGE/opt/v2rayN/" + + local ICON_CANDIDATE + PROJECT_DIR="$(cd "$(dirname "$PROJECT")" && pwd)" + ICON_CANDIDATE="$PROJECT_DIR/v2rayN.png" + [[ -f "$ICON_CANDIDATE" ]] && cp "$ICON_CANDIDATE" "$STAGE/usr/share/icons/hicolor/256x256/apps/v2rayn.png" || true + + mkdir -p "$STAGE/opt/v2rayN/bin/xray" "$STAGE/opt/v2rayN/bin/sing_box" + + fetch_separate_cores_and_rules() { + local outroot="$1" + + if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then + download_xray "$outroot/bin/xray" "$RID_DIR" || echo "[!] xray download failed (skipped)" + fi + if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then + download_singbox "$outroot/bin/sing_box" "$RID_DIR" || echo "[!] sing-box download failed (skipped)" + fi + download_geo_assets "$outroot" || echo "[!] Geo rules download failed (skipped)" + } + + if [[ "$FORCE_NETCORE" -eq 0 ]]; then + if download_v2rayn_bundle "$STAGE/opt/v2rayN" "$RID_DIR"; then + echo "[*] Using v2rayN bundle bin assets." + else + echo "[*] Bundle failed, fallback to separate core + rules." + fetch_separate_cores_and_rules "$STAGE/opt/v2rayN" + fi + else + echo "[*] --netcore specified: use separate core + rules." + fetch_separate_cores_and_rules "$STAGE/opt/v2rayN" + fi + + # Wrapper + install -m 755 /dev/stdin "$STAGE/usr/bin/v2rayn" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +DIR="/opt/v2rayN" +cd "$DIR" + +if [[ -x "$DIR/v2rayN" ]]; then + exec "$DIR/v2rayN" "$@" +fi + +for dll in v2rayN.Desktop.dll v2rayN.dll; do + if [[ -f "$DIR/$dll" ]]; then + exec /usr/bin/dotnet "$DIR/$dll" "$@" + fi +done + +echo "v2rayN launcher: no executable found in $DIR" >&2 +ls -l "$DIR" >&2 || true +exit 1 EOF -mkdir -p "${PackagePath}/usr/share/applications" -cat >"${PackagePath}/usr/share/applications/v2rayN.desktop" <<-EOF + SHLIBS_DEPENDS="" + EXTRA_DEPENDS="libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 8.32), bash (>= 5.1), libfreetype6 (>= 2.11)" + + mkdir -p "$WORKDIR/debian" + cat > "$WORKDIR/debian/control" < +Standards-Version: 4.7.0 + +Package: v2rayn +Architecture: ${deb_arch} +Description: v2rayN +EOF + + local SYS_LIBDIR="" + local SYS_USRLIBDIR="" + multiarch="$(dpkg-architecture -a"$deb_arch" -qDEB_HOST_MULTIARCH)" + + SYS_LIBDIR="/lib/$multiarch" + SYS_USRLIBDIR="/usr/lib/$multiarch" + + : > "$DEBIAN_DIR/substvars" + mapfile -t ELF_FILES < <( + find "$STAGE/opt/v2rayN" -type f \( -name "*.so*" -o -perm -111 \) ! -name 'libcoreclrtraceptprovider.so' + ) + if [[ "${#ELF_FILES[@]}" -gt 0 ]]; then + ( + cd "$WORKDIR" + dpkg-shlibdeps \ + -l"$STAGE/opt/v2rayN" \ + -l"$SYS_LIBDIR" \ + -l"$SYS_USRLIBDIR" \ + -T"$DEBIAN_DIR/substvars" \ + "${ELF_FILES[@]}" + ) >/dev/null 2>&1 || true + fi + + SHLIBS_DEPENDS="$(sed -n 's/^shlibs:Depends=//p' "$DEBIAN_DIR/substvars" | head -n1 || true)" + + if [[ -n "$SHLIBS_DEPENDS" ]]; then + SHLIBS_DEPENDS="$(echo "$SHLIBS_DEPENDS" \ + | sed -E 's/ *\([^)]*\)//g' \ + | sed -E 's/ *, */, /g' \ + | sed -E 's/^, *//; s/, *$//')" + FINAL_DEPENDS="${SHLIBS_DEPENDS}, ${EXTRA_DEPENDS}" + else + FINAL_DEPENDS="${EXTRA_DEPENDS}" + fi + + # Desktop file + install -m 644 /dev/stdin "$STAGE/usr/share/applications/v2rayn.desktop" <<'EOF' [Desktop Entry] -Name=v2rayN -Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others -Exec=/opt/v2rayN/v2rayN -Icon=/opt/v2rayN/v2rayN.png -Terminal=false Type=Application -Categories=Network;Application; +Name=v2rayN +Comment=v2rayN for Debian GNU Linux +Exec=v2rayn +Icon=v2rayn +Terminal=false +Categories=Network; EOF -cat >"${PackagePath}/DEBIAN/postinst" <<-'EOF' + # Control file + cat > "$DEBIAN_DIR/control" < +Homepage: https://github.com/2dust/v2rayN +Section: net +Priority: optional +Depends: ${FINAL_DEPENDS} +Description: v2rayN (Avalonia) GUI client for Linux + Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / + Shadowsocks / tuic / WireGuard. +EOF + + # postinst + install -m 755 /dev/stdin "$DEBIAN_DIR/postinst" <<'EOF' +#!/bin/sh set -e -update-desktop-database || true +update-desktop-database /usr/share/applications >/dev/null 2>&1 || true +if command -v gtk-update-icon-cache >/dev/null 2>&1; then + gtk-update-icon-cache -f /usr/share/icons/hicolor >/dev/null 2>&1 || true +fi exit 0 EOF -sudo chmod 0755 "${PackagePath}/DEBIAN/postinst" -sudo chmod 0755 "${PackagePath}/opt/v2rayN/v2rayN" -sudo chmod 0755 "${PackagePath}/opt/v2rayN/AmazTool" + # postrm + install -m 755 /dev/stdin "$DEBIAN_DIR/postrm" <<'EOF' +#!/bin/sh +set -e +update-desktop-database /usr/share/applications >/dev/null 2>&1 || true +if command -v gtk-update-icon-cache >/dev/null 2>&1; then + gtk-update-icon-cache -f /usr/share/icons/hicolor >/dev/null 2>&1 || true +fi +exit 0 +EOF -# Patch -# set owner to root:root -sudo chown -R root:root "${PackagePath}" -# set all directories to 755 (readable & traversable by all users) -sudo find "${PackagePath}/opt/v2rayN" -type d -exec chmod 755 {} + -# set all regular files to 644 (readable by all users) -sudo find "${PackagePath}/opt/v2rayN" -type f -exec chmod 644 {} + -# ensure main binaries are 755 (executable by all users) -sudo chmod 755 "${PackagePath}/opt/v2rayN/v2rayN" 2>/dev/null || true -sudo chmod 755 "${PackagePath}/opt/v2rayN/AmazTool" 2>/dev/null || true + # Normalize permissions + find "$STAGE/opt/v2rayN" -type d -exec chmod 0755 {} + + find "$STAGE/opt/v2rayN" -type f -exec chmod 0644 {} + + [[ -f "$STAGE/opt/v2rayN/v2rayN" ]] && chmod 0755 "$STAGE/opt/v2rayN/v2rayN" || true + + local deb_out + deb_out="$OUTPUT_DIR/v2rayn_${VERSION}_${deb_arch}.deb" -# build deb package -sudo dpkg-deb -Zxz --build $PackagePath -sudo mv "${PackagePath}.deb" "v2rayN-${Arch}.deb" + dpkg-deb --root-owner-group --build "$STAGE" "$deb_out" + + echo "Build done for $short. DEB at:" + echo " $deb_out" + BUILT_DEBS+=("$deb_out") + + rm -rf "$WORKDIR" +} + +case "${ARCH_OVERRIDE:-}" in + all) targets=(x64 arm64); BUILT_ALL=1 ;; + x64|amd64) targets=(x64) ;; + arm64|aarch64) targets=(arm64) ;; + "") targets=($([[ "$host_arch" == "aarch64" ]] && echo arm64 || echo x64)) ;; + *) echo "Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."; exit 1 ;; +esac + +for arch in "${targets[@]}"; do + build_for_arch "$arch" +done + +echo "" +echo "================ Build Summary =================" +if [[ "${#BUILT_DEBS[@]}" -gt 0 ]]; then + echo "Output directory: $OUTPUT_DIR" + for pkg in "${BUILT_DEBS[@]}"; do + echo "$pkg" + done +else + echo "No DEBs detected in summary (check build logs above)." +fi +echo "===============================================" diff --git a/package-rhel.sh b/package-rhel.sh index 1843c850..bf295134 100644 --- a/package-rhel.sh +++ b/package-rhel.sh @@ -210,46 +210,48 @@ echo "[*] GUI version resolved as: ${VERSION}" # Helpers for core download_xray() { # Download Xray core - local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip" + local outdir="$1" rid="$2" ver="${XRAY_VER:-}" url tmp zipname="xray.zip" mkdir -p "$outdir" if [[ -z "$ver" ]]; then ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \ | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true fi [[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; } - if [[ "$RID_DIR" == "linux-arm64" ]]; then + if [[ "$rid" == "linux-arm64" ]]; then url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip" else url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip" fi echo "[+] Download xray: $url" - tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN + tmp="$(mktemp -d)" curl -fL "$url" -o "$tmp/$zipname" unzip -q "$tmp/$zipname" -d "$tmp" - install -Dm755 "$tmp/xray" "$outdir/xray" + install -m 755 "$tmp/xray" "$outdir/xray" + rm -rf "$tmp" } download_singbox() { # Download sing-box - local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin + local outdir="$1" rid="$2" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin mkdir -p "$outdir" if [[ -z "$ver" ]]; then ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \ | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true fi [[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; } - if [[ "$RID_DIR" == "linux-arm64" ]]; then + if [[ "$rid" == "linux-arm64" ]]; then url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz" else url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz" fi echo "[+] Download sing-box: $url" - tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN + tmp="$(mktemp -d)" curl -fL "$url" -o "$tmp/$tarname" tar -C "$tmp" -xzf "$tmp/$tarname" bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)" - [[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; return 1; } - install -Dm755 "$bin" "$outdir/sing-box" + [[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; rm -rf "$tmp"; return 1; } + install -m 755 "$bin" "$outdir/sing-box" + rm -rf "$tmp" } # Move geo files to outroot/bin @@ -310,9 +312,9 @@ download_geo_assets() { # Prefer the prebuilt v2rayN core bundle; then unify geo layout download_v2rayn_bundle() { - local outroot="$1" + local outroot="$1" rid="$2" local url="" - if [[ "$RID_DIR" == "linux-arm64" ]]; then + if [[ "$rid" == "linux-arm64" ]]; then url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64.zip" else url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip" @@ -378,10 +380,7 @@ build_for_arch() { local RID_DIR="$rid" local PUBDIR PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish" - [[ -d "$PUBDIR" ]] - - # Make RID_DIR visible to download helpers (they read this var) - export RID_DIR + [[ -d "$PUBDIR" ]] || { echo "Publish directory not found: $PUBDIR"; return 1; } # Per-arch working area local PKGROOT="v2rayN-publish" @@ -400,10 +399,12 @@ build_for_arch() { mkdir -p "$WORKDIR/$PKGROOT" cp -a "$PUBDIR/." "$WORKDIR/$PKGROOT/" - # Optional icon + # Required icon local ICON_CANDIDATE - ICON_CANDIDATE="$(dirname "$PROJECT")/../v2rayN.Desktop/v2rayN.png" - [[ -f "$ICON_CANDIDATE" ]] && cp "$ICON_CANDIDATE" "$WORKDIR/$PKGROOT/v2rayn.png" || true + PROJECT_DIR="$(cd "$(dirname "$PROJECT")" && pwd)" + ICON_CANDIDATE="$PROJECT_DIR/v2rayN.png" + [[ -f "$ICON_CANDIDATE" ]] || { echo "Required icon not found: $ICON_CANDIDATE"; return 1; } + cp "$ICON_CANDIDATE" "$WORKDIR/$PKGROOT/v2rayn.png" # Prepare bin structure mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box" @@ -413,16 +414,16 @@ build_for_arch() { local outroot="$1" if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then - download_xray "$outroot/bin/xray" || echo "[!] xray download failed (skipped)" + download_xray "$outroot/bin/xray" "$RID_DIR" || echo "[!] xray download failed (skipped)" fi if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then - download_singbox "$outroot/bin/sing_box" || echo "[!] sing-box download failed (skipped)" + download_singbox "$outroot/bin/sing_box" "$RID_DIR" || echo "[!] sing-box download failed (skipped)" fi download_geo_assets "$outroot" || echo "[!] Geo rules download failed (skipped)" } if [[ "$FORCE_NETCORE" -eq 0 ]]; then - if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then + if download_v2rayn_bundle "$WORKDIR/$PKGROOT" "$RID_DIR"; then echo "[*] Using v2rayN bundle archive." else echo "[*] Bundle failed, fallback to separate core + rules." @@ -484,9 +485,14 @@ https://github.com/2dust/v2rayN install -dm0755 %{buildroot}/opt/v2rayN cp -a * %{buildroot}/opt/v2rayN/ +# Normalize permissions +find %{buildroot}/opt/v2rayN -type d -exec chmod 0755 {} + +find %{buildroot}/opt/v2rayN -type f -exec chmod 0644 {} + +[ -f %{buildroot}/opt/v2rayN/v2rayN ] && chmod 0755 %{buildroot}/opt/v2rayN/v2rayN || : + # Launcher (prefer native ELF first, then DLL fallback) install -dm0755 %{buildroot}%{_bindir} -cat > %{buildroot}%{_bindir}/v2rayn << 'EOF' +install -m0755 /dev/stdin %{buildroot}%{_bindir}/v2rayn << 'EOF' #!/usr/bin/bash set -euo pipefail DIR="/opt/v2rayN" @@ -503,11 +509,10 @@ echo "v2rayN launcher: no executable found in $DIR" >&2 ls -l "$DIR" >&2 || true exit 1 EOF -chmod 0755 %{buildroot}%{_bindir}/v2rayn # Desktop file install -dm0755 %{buildroot}%{_datadir}/applications -cat > %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF' +install -m0644 /dev/stdin %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF' [Desktop Entry] Type=Application Name=v2rayN @@ -519,10 +524,8 @@ Categories=Network; EOF # Icon -if [ -f "%{_builddir}/__PKGROOT__/v2rayn.png" ]; then - install -dm0755 %{buildroot}%{_datadir}/icons/hicolor/256x256/apps - install -m0644 %{_builddir}/__PKGROOT__/v2rayn.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png -fi +install -dm0755 %{buildroot}%{_datadir}/icons/hicolor/256x256/apps +install -m0644 %{_builddir}/__PKGROOT__/v2rayn.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png %post /usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true From 0cec5986cdea9ada148ad3a5195f6e85955f5c5b Mon Sep 17 00:00:00 2001 From: Miheichev Aleksandr Sergeevich Date: Fri, 20 Mar 2026 11:19:37 +0300 Subject: [PATCH 26/35] i18n(ru): translate 68 untranslated strings in Russian localization (#8931) Complete the Russian (ru) localization by translating all remaining English strings in ResUI.ru.resx. Categories include: - Policy Group and Proxy Chain UI elements - Routing and DNS settings/tips - Certificate Pinning UI - Core error and warning messages - Server list and menu items - System proxy and miscellaneous settings 5 universal technical abbreviations (LAN, UUID, User-Agent, MTU, TLS) are intentionally kept as-is per standard Russian IT terminology. --- v2rayN/ServiceLib/Resx/ResUI.ru.resx | 142 +++++++++++++-------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 957e6052..a20fa3b1 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1027,7 +1027,7 @@ Протокол Mux для sing-box - Process (Linux/Windows) + Процесс (Linux/Windows) IP-адрес или сеть CIDR @@ -1393,10 +1393,10 @@ Внутренний DNS - Direct Target Resolution Strategy + Стратегия разрешения прямых соединений - Proxy Target Resolution Strategy + Стратегия разрешения прокси-соединений Добавить стандартные записи hosts (DNS) @@ -1429,7 +1429,7 @@ Включён пользовательский DNS — настройки на этой странице не применяются - Block ECH and HTTP/3 availability checks when enabled + При включении блокирует проверки доступности ECH и HTTP/3 Пожалуйста, заполните корректный шаблон конфигурации @@ -1462,127 +1462,127 @@ Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную. - Start parsing and processing subscription content + Начинается разбор и обработка содержимого подписки - Select Profile + Выбрать профиль - Applies globally by default, with built-in FakeIP filtering (sing-box only). + По умолчанию применяется глобально, со встроенной фильтрацией FakeIP (только sing-box). - Please Add At Least One Configuration + Добавьте хотя бы одну конфигурацию - Policy Group + Группа политик - Proxy Chain + Цепочка прокси - Lowest Latency + Наименьшая задержка - Random + Случайный - Round Robin + Циклический (Round Robin) - Most Stable + Наиболее стабильный - Policy Group Type + Тип группы политик - Add Policy Group Configuration + Добавить группу политик - Add Proxy Chain Configuration + Добавить цепочку прокси - Add Child Configuration + Добавить дочернюю конфигурацию - Remove Child Configuration + Удалить дочернюю конфигурацию - Configuration item 1, Auto add from subscription group + Конфигурация 1: автодобавление из группы подписки - Fallback + Резервный (Fallback) - Core '{0}' does not support network type '{1}' + Ядро «{0}» не поддерживает тип сети «{1}» - Core '{0}' does not support protocol '{1}' when using transport '{2}' + Ядро «{0}» не поддерживает протокол «{1}» при транспорте «{2}» - Core '{0}' does not support protocol '{1}' + Ядро «{0}» не поддерживает протокол «{1}» - The {0} property is invalid, please check + Свойство {0} недопустимо, проверьте его - Not support protocol '{0}' + Протокол «{0}» не поддерживается - If the system does not have a tray function, please do not enable it + Если в системе нет функции трея, не включайте эту опцию - You can set separate rules for Routing and DNS, or select "ALL" to apply to both + Можно задать отдельные правила для маршрутизации и DNS или выбрать «ALL» для применения к обоим - Rule Type + Тип правила Bootstrap DNS - Resolve DNS server domains, requires IP + Разрешает домены DNS-серверов, требуется IP-адрес - Test real delay + Тест реальной задержки - Auto add filtered configuration from subscription groups + Автодобавление отфильтрованных конфигураций из групп подписки - Certificate Pinning + Привязка сертификата - Pinned certificate (fill in either one) -When specified, the certificate will be pinned, and "Allow Insecure" will be disabled. + Привязанный сертификат (заполните любое из полей) +При указании сертификат будет привязан, а «Разрешить небезопасные» отключится. -The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA. +Получение сертификата может завершиться неудачей при использовании самоподписанного сертификата или при наличии ненадёжного / вредоносного ЦС в системе. - Fetch Certificate + Получить сертификат - Fetch Certificate Chain + Получить цепочку сертификатов - Please set a valid domain + Укажите корректный домен - Certificate not set + Сертификат не задан - Certificate set + Сертификат задан - Custom PAC file path + Путь к пользовательскому PAC-файлу - Custom system proxy script file path + Путь к скрипту системного прокси - macOS displays this in the Dock (requires restart) + Отображать в Dock на macOS (требуется перезапуск) - Configuration Item 2, Select and add from self-built + Конфигурация 2: выбор и добавление из собственных EchConfigList @@ -1591,81 +1591,81 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if EchForceQuery - Full certificate (chain), PEM format + Полный сертификат (цепочка) в формате PEM - Certificate fingerprint (SHA-256) + Отпечаток сертификата (SHA-256) - Serve Stale + Отдавать устаревшие записи (Serve Stale) - Parallel Query + Параллельные запросы - By default, invoked only during routing for resolution + По умолчанию используется только при разрешении имён в процессе маршрутизации - By default, invoked only during routing for resolution; ensure the remote server can reach this DNS + По умолчанию используется только при разрешении имён в процессе маршрутизации; убедитесь, что удалённый сервер может достичь этого DNS - If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used. + Если не задано или «AsIs», используется системный DNS; иначе — встроенный DNS-модуль. - If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. + Если не задано или «AsIs», разрешение DNS выполняется DNS удалённого сервера; иначе — встроенный DNS-модуль. - Port hopping interval + Интервал смены портов (Port Hopping) - Configuration item preview + Предпросмотр конфигурации Finalmask - Routing rule {0} outbound node {1} warning: {2} + Правило маршрутизации {0}, исходящий узел {1}, предупреждение: {2} - Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. + Правило маршрутизации {0}, исходящий узел {1}, ошибка: {2}. Используется только прокси-узел. - Group {0} has a cycle dependency on child node {1}. Skipping this node. + Группа {0} имеет циклическую зависимость на дочерний узел {1}. Узел пропущен. - Group {0} child node {1} warning: {2} + Группа {0}: предупреждение дочернего узла {1}: {2} - Group {0} child node {1} error: {2}. Skipping this node. + Группа {0}: ошибка дочернего узла {1}: {2}. Узел пропущен. - Group {0} child group node {1} warning: {2} + Группа {0}: предупреждение дочернего узла группы {1}: {2} - Group {0} child group node {1} error: {2}. Skipping this node. + Группа {0}: ошибка дочернего узла группы {1}: {2}. Узел пропущен. - Group {0} has no valid child node. + У группы {0} нет допустимых дочерних узлов. - Routing rule {0} has an empty outbound tag. Fallback to proxy node only. + У правила маршрутизации {0} пустой исходящий тег. Используется только прокси-узел. - Routing rule {0} outbound node {1} not found. Fallback to proxy node only. + Правило маршрутизации {0}, исходящий узел {1} не найден. Используется только прокси-узел. - Subscription previous proxy {0} not found. Skipping. + Предыдущий прокси подписки {0} не найден. Пропущено. - Subscription next proxy {0} not found. Skipping. + Следующий прокси подписки {0} не найден. Пропущено. - Generate Policy Group + Сгенерировать группу политик - All configurations + Все конфигурации - Group by Region + Группировка по регионам - \ No newline at end of file + From dd94199bbbe2269de0471fbceb264b56dd084536 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Fri, 20 Mar 2026 08:32:02 +0000 Subject: [PATCH 27/35] Json editor with syntax highlighting (#8932) * Add json editor * Replace JsonEditor with TextBox * Replace JsonEditor with TextBox * Remove TextMateSharp.Grammars * Fix two way bind * Update ResUI.ru.resx --------- Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com> --- v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 36 +++++++ v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 12 +++ v2rayN/ServiceLib/Resx/ResUI.fr.resx | 12 +++ v2rayN/ServiceLib/Resx/ResUI.hu.resx | 12 +++ v2rayN/ServiceLib/Resx/ResUI.resx | 12 +++ v2rayN/ServiceLib/Resx/ResUI.ru.resx | 12 +++ v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 12 +++ v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 12 +++ .../Views/AddServerWindow.axaml | 16 ++-- .../Views/DNSSettingWindow.axaml | 22 +---- .../Views/FullConfigTemplateWindow.axaml | 22 +---- v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml | 23 +++++ .../v2rayN.Desktop/Views/JsonEditor.axaml.cs | 96 +++++++++++++++++++ v2rayN/v2rayN.Desktop/Views/MsgView.axaml | 4 +- 14 files changed, 256 insertions(+), 47 deletions(-) create mode 100644 v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml create mode 100644 v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml.cs diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 5a1ea759..c75715ae 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -924,6 +924,42 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Copy 的本地化字符串。 + /// + public static string menuEditCopy { + get { + return ResourceManager.GetString("menuEditCopy", resourceCulture); + } + } + + /// + /// 查找类似 Format 的本地化字符串。 + /// + public static string menuEditFormat { + get { + return ResourceManager.GetString("menuEditFormat", resourceCulture); + } + } + + /// + /// 查找类似 Paste 的本地化字符串。 + /// + public static string menuEditPaste { + get { + return ResourceManager.GetString("menuEditPaste", resourceCulture); + } + } + + /// + /// 查找类似 Select all 的本地化字符串。 + /// + public static string menuEditSelectAll { + get { + return ResourceManager.GetString("menuEditSelectAll", resourceCulture); + } + } + /// /// 查找类似 Edit 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 1dd09e85..30f663e8 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1668,4 +1668,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Group by Region + + کپی + + + انتخاب همه + + + Paste + + + Format + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index c873db43..061d6dad 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1665,4 +1665,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Group by Region + + Copier + + + Tout sélect + + + Paste + + + Format + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index f59a652b..4d652587 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1668,4 +1668,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Group by Region + + Másolás + + + Összes kijelölése + + + Paste + + + Format + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index f299a877..787e7bd9 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1668,4 +1668,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Group by Region + + Copy + + + Select all + + + Paste + + + Format + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index a20fa3b1..ba2ef074 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1668,4 +1668,16 @@ Группировка по регионам + + Скопировать + + + Выбрать все + + + Paste + + + Format + diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index d655c30d..707609d0 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1665,4 +1665,16 @@ 按地区分组 + + 复制 + + + 全选 + + + 粘贴 + + + 格式化 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 65b1dbfb..4e417308 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1665,4 +1665,16 @@ 按區域分組 + + 複製 + + + 全選 + + + Paste + + + Format + \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 477eca5d..e9ded8aa 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -5,6 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" + xmlns:views="clr-namespace:v2rayN.Desktop.Views" xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib" Title="{x:Static resx:ResUI.menuServers}" Width="900" @@ -658,16 +659,13 @@ Margin="{StaticResource Margin4}" VerticalAlignment="Center" Text="{x:Static resx:ResUI.TransportExtraTip}" /> - + VerticalAlignment="Center" /> @@ -749,13 +747,13 @@ - + HorizontalAlignment="Stretch" + VerticalAlignment="Center" /> diff --git a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml index 705e79e2..3335785b 100644 --- a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml @@ -3,6 +3,7 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="using:v2rayN.Desktop.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib" @@ -399,12 +400,7 @@ BorderBrush="Gray" BorderThickness="1" Header="HTTP/SOCKS"> - + @@ -473,12 +469,7 @@ BorderBrush="Gray" BorderThickness="1" Header="HTTP/SOCKS"> - + @@ -488,12 +479,7 @@ BorderBrush="Gray" BorderThickness="1" Header="{x:Static resx:ResUI.TbSettingsTunMode}"> - + diff --git a/v2rayN/v2rayN.Desktop/Views/FullConfigTemplateWindow.axaml b/v2rayN/v2rayN.Desktop/Views/FullConfigTemplateWindow.axaml index 63f29ed0..1a2b482c 100644 --- a/v2rayN/v2rayN.Desktop/Views/FullConfigTemplateWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/FullConfigTemplateWindow.axaml @@ -5,6 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" + xmlns:views="clr-namespace:v2rayN.Desktop.Views" xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib" Title="{x:Static resx:ResUI.menuFullConfigTemplate}" Width="900" @@ -94,12 +95,7 @@ BorderBrush="Gray" BorderThickness="1" Header="xray config template json"> - + @@ -166,12 +162,7 @@ BorderBrush="Gray" BorderThickness="1" Header="sing-box config template json"> - + @@ -181,12 +172,7 @@ BorderBrush="Gray" BorderThickness="1" Header="sing-box tun config template json"> - + diff --git a/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml b/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml new file mode 100644 index 00000000..cad0021d --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml.cs b/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml.cs new file mode 100644 index 00000000..98620126 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml.cs @@ -0,0 +1,96 @@ +using System.Text.Json; +using System.Xml; +using AvaloniaEdit.Highlighting; +using AvaloniaEdit.Highlighting.Xshd; + +namespace v2rayN.Desktop.Views; + +public partial class JsonEditor : UserControl +{ + private static readonly JsonSerializerOptions SIndentedOptions = new() { WriteIndented = true }; + + private static readonly Lazy SHighlightingDark = + new(() => BuildHighlighting(dark: true), isThreadSafe: true); + + private static readonly Lazy SHighlightingLight = + new(() => BuildHighlighting(dark: false), isThreadSafe: true); + + public static readonly StyledProperty TextProperty = + AvaloniaProperty.Register(nameof(Text), defaultValue: string.Empty); + + public string Text + { + get => GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public JsonEditor() + { + InitializeComponent(); + var isDark = Application.Current?.ActualThemeVariant != ThemeVariant.Light; + Editor.SyntaxHighlighting = isDark ? SHighlightingDark.Value : SHighlightingLight.Value; + Editor.TextArea.TextView.Options.EnableHyperlinks = false; + + Editor.TextChanged += (_, _) => + { + if (Text != Editor.Text) + { + SetCurrentValue(TextProperty, Editor.Text); + } + }; + + this.GetObservable(TextProperty).Subscribe(text => + { + if (Editor.Text != text) + { + Editor.Text = text ?? string.Empty; + } + }); + } + + private static IHighlightingDefinition BuildHighlighting(bool dark) + { + var keyColor = dark ? "#9CDCFE" : "#0451A5"; + var strColor = dark ? "#CE9178" : "#A31515"; + var numColor = dark ? "#B5CEA8" : "#098658"; + var kwColor = dark ? "#569CD6" : "#0000FF"; + var xshd = $""" + + + + + + + + "([^"\\]|\\.)*"(?=\s*:) + "([^"\\]|\\.)*" + -?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)? + + true + false + null + + + + """; + using var reader = XmlReader.Create(new StringReader(xshd)); + return HighlightingLoader.Load(reader, HighlightingManager.Instance); + } + + private void FormatJson_Click(object? sender, RoutedEventArgs e) + { + try + { + var obj = JsonUtils.ParseJson(Editor.Text); + Editor.Text = JsonUtils.Serialize(obj, SIndentedOptions); + } + catch + { + // ignored + } + } + + private void Copy_Click(object? sender, RoutedEventArgs e) => Editor.Copy(); + private void Paste_Click(object? sender, RoutedEventArgs e) => Editor.Paste(); + private void SelectAll_Click(object? sender, RoutedEventArgs e) => Editor.SelectAll(); +} diff --git a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml index 90dcfeb7..aabe80fa 100644 --- a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml +++ b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml @@ -79,8 +79,8 @@ + Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" + InputGesture="Ctrl+A" /> Date: Fri, 20 Mar 2026 16:45:55 +0800 Subject: [PATCH 28/35] Update Directory.Packages.props --- v2rayN/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2rayN/Directory.Packages.props b/v2rayN/Directory.Packages.props index 839b3384..e1c29791 100644 --- a/v2rayN/Directory.Packages.props +++ b/v2rayN/Directory.Packages.props @@ -13,7 +13,7 @@ - + From 5fbcc46013b8389fc5b0be5773c65129d6daaaee Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:59:19 +0800 Subject: [PATCH 29/35] up 7.19.5 --- v2rayN/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2rayN/Directory.Build.props b/v2rayN/Directory.Build.props index 37a05fdc..b6052325 100644 --- a/v2rayN/Directory.Build.props +++ b/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.19.4 + 7.19.5 From 04783ecf449f0a797e639b020811096bb4a90faf Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 21 Mar 2026 08:54:36 +0000 Subject: [PATCH 30/35] Add NaiveProxy support (#8819) * Add UoT support * Add NaiveProxy support * Fix --- v2rayN/ServiceLib/Enums/EConfigType.cs | 1 + v2rayN/ServiceLib/Global.cs | 19 ++- .../Handler/Builder/NodeValidator.cs | 6 + v2rayN/ServiceLib/Handler/ConfigHandler.cs | 37 +++++- v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs | 7 ++ v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs | 85 ++++++++++++++ v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs | 10 +- v2rayN/ServiceLib/Manager/AppManager.cs | 8 +- v2rayN/ServiceLib/Models/ProtocolExtraItem.cs | 6 + v2rayN/ServiceLib/Models/SingboxConfig.cs | 4 + v2rayN/ServiceLib/Models/V2rayConfig.cs | 2 + v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 56 +++++++-- v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 12 ++ v2rayN/ServiceLib/Resx/ResUI.fr.resx | 12 ++ v2rayN/ServiceLib/Resx/ResUI.hu.resx | 12 ++ v2rayN/ServiceLib/Resx/ResUI.resx | 32 +++-- v2rayN/ServiceLib/Resx/ResUI.ru.resx | 12 ++ v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 30 +++-- v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 12 ++ .../Singbox/SingboxOutboundService.cs | 19 ++- .../CoreConfig/V2ray/V2rayOutboundService.cs | 1 + .../ViewModels/AddServerViewModel.cs | 15 +++ .../ViewModels/MainWindowViewModel.cs | 5 + .../Views/AddServerWindow.axaml | 87 +++++++++++++- .../Views/AddServerWindow.axaml.cs | 27 ++++- v2rayN/v2rayN.Desktop/Views/MainWindow.axaml | 1 + .../v2rayN.Desktop/Views/MainWindow.axaml.cs | 1 + v2rayN/v2rayN/Views/AddServerWindow.xaml | 109 +++++++++++++++++- v2rayN/v2rayN/Views/AddServerWindow.xaml.cs | 27 ++++- v2rayN/v2rayN/Views/MainWindow.xaml | 4 + v2rayN/v2rayN/Views/MainWindow.xaml.cs | 1 + 31 files changed, 613 insertions(+), 47 deletions(-) create mode 100644 v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs diff --git a/v2rayN/ServiceLib/Enums/EConfigType.cs b/v2rayN/ServiceLib/Enums/EConfigType.cs index 287d7b94..ae4e30ca 100644 --- a/v2rayN/ServiceLib/Enums/EConfigType.cs +++ b/v2rayN/ServiceLib/Enums/EConfigType.cs @@ -13,6 +13,7 @@ public enum EConfigType WireGuard = 9, HTTP = 10, Anytls = 11, + Naive = 12, PolicyGroup = 101, ProxyChain = 102, } diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index e3ed209d..0a52f678 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -193,6 +193,10 @@ public class Global public const string Hysteria2ProtocolShare = "hy2://"; + public const string NaiveHttpsProtocolShare = "naive+https://"; + + public const string NaiveQuicProtocolShare = "naive+quic://"; + public static readonly Dictionary ProtocolShares = new() { { EConfigType.VMess, "vmess://" }, @@ -203,7 +207,8 @@ public class Global { EConfigType.Hysteria2, "hysteria2://" }, { EConfigType.TUIC, "tuic://" }, { EConfigType.WireGuard, "wireguard://" }, - { EConfigType.Anytls, "anytls://" } + { EConfigType.Anytls, "anytls://" }, + { EConfigType.Naive, "naive://" } }; public static readonly Dictionary ProtocolTypes = new() @@ -217,7 +222,8 @@ public class Global { EConfigType.Hysteria2, "hysteria2" }, { EConfigType.TUIC, "tuic" }, { EConfigType.WireGuard, "wireguard" }, - { EConfigType.Anytls, "anytls" } + { EConfigType.Anytls, "anytls" }, + { EConfigType.Naive, "naive" } }; public static readonly List VmessSecurities = @@ -342,6 +348,7 @@ public class Global EConfigType.Hysteria2, EConfigType.TUIC, EConfigType.Anytls, + EConfigType.Naive, EConfigType.WireGuard, EConfigType.SOCKS, EConfigType.HTTP, @@ -558,6 +565,14 @@ public class Global "bbr" ]; + public static readonly List NaiveCongestionControls = + [ + "bbr", + "bbr2", + "cubic", + "reno" + ]; + public static readonly List allowSelectType = [ "selector", diff --git a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs index 65110b2a..053c51f2 100644 --- a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs +++ b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs @@ -152,6 +152,12 @@ public class NodeValidator private static string? ValidateSingboxTransport(EConfigType configType, string net) { + // Naive support tcp and quic transports + if (configType == EConfigType.Naive && net == "quic") + { + return null; + } + // sing-box does not support xhttp / kcp transports if (SingboxUnsupportedTransports.Contains(net)) { diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 34758618..bbc23f1c 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -269,6 +269,7 @@ public static class ConfigHandler EConfigType.TUIC => await AddTuicServer(config, item), EConfigType.WireGuard => await AddWireguardServer(config, item), EConfigType.Anytls => await AddAnytlsServer(config, item), + EConfigType.Naive => await AddNaiveServer(config, item), _ => -1, }; return ret; @@ -804,7 +805,7 @@ public static class ConfigHandler } /// - /// Add or edit a Anytls server + /// Add or edit an Anytls server /// Validates and processes Anytls-specific settings /// /// Current configuration @@ -831,6 +832,35 @@ public static class ConfigHandler return 0; } + /// + /// Add or edit a Naive server + /// Validates and processes Naive-specific settings + /// + /// Current configuration + /// Naive profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddNaiveServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Naive; + profileItem.CoreType = ECoreType.sing_box; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Username = profileItem.Username.TrimEx(); + profileItem.Password = profileItem.Password.TrimEx(); + profileItem.Network = profileItem.Network == "quic" ? "quic" : string.Empty; + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Password.IsNullOrEmpty()) + { + return -1; + } + await AddServerCommon(config, profileItem, toFile); + return 0; + } + /// /// Sort the server list by the specified column /// Updates the sort order in the profile extension data @@ -1077,7 +1107,8 @@ public static class ConfigHandler if (toFile) { - profileItem.SetProtocolExtra(); + //profileItem.SetProtocolExtra(); + profileItem.SetProtocolExtra(profileItem.GetProtocolExtra()); await SQLiteHelper.Instance.ReplaceAsync(profileItem); } return 0; @@ -1105,6 +1136,7 @@ public static class ConfigHandler && AreEqual(o.Address, n.Address) && o.Port == n.Port && AreEqual(o.Password, n.Password) + && AreEqual(o.Username, n.Username) && AreEqual(oProtocolExtra.VlessEncryption, nProtocolExtra.VlessEncryption) && AreEqual(oProtocolExtra.SsMethod, nProtocolExtra.SsMethod) && AreEqual(oProtocolExtra.VmessSecurity, nProtocolExtra.VmessSecurity) @@ -1496,6 +1528,7 @@ public static class ConfigHandler EConfigType.TUIC => await AddTuicServer(config, profileItem, false), EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false), + EConfigType.Naive => await AddNaiveServer(config, profileItem, false), _ => -1, }; diff --git a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs index 4fc251b7..611e5159 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs @@ -19,6 +19,7 @@ public class FmtHandler EConfigType.TUIC => TuicFmt.ToUri(item), EConfigType.WireGuard => WireguardFmt.ToUri(item), EConfigType.Anytls => AnytlsFmt.ToUri(item), + EConfigType.Naive => NaiveFmt.ToUri(item), _ => null, }; @@ -80,6 +81,12 @@ public class FmtHandler { return AnytlsFmt.Resolve(str, out msg); } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Naive]) + || str.StartsWith(Global.NaiveHttpsProtocolShare) + || str.StartsWith(Global.NaiveQuicProtocolShare)) + { + return NaiveFmt.Resolve(str, out msg); + } else { msg = ResUI.NonvmessOrssProtocol; diff --git a/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs new file mode 100644 index 00000000..aec27d57 --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs @@ -0,0 +1,85 @@ +namespace ServiceLib.Handler.Fmt; + +public class NaiveFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + var parsedUrl = Utils.TryUri(str); + if (parsedUrl == null) + { + return null; + } + + ProfileItem item = new() + { + ConfigType = EConfigType.Naive, + Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + Address = parsedUrl.IdnHost, + Port = parsedUrl.Port, + }; + if (parsedUrl.Scheme.Contains("quic")) + { + item.Network = "quic"; + } + var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); + if (rawUserInfo.Contains(':')) + { + var split = rawUserInfo.Split(':', 2); + item.Username = split[0]; + item.Password = split[1]; + } + else + { + item.Password = rawUserInfo; + } + + var query = Utils.ParseQueryString(parsedUrl.Query); + ResolveUriQuery(query, ref item); + var insecureConcurrency = int.TryParse(GetQueryValue(query, "insecure-concurrency"), out var ic) ? ic : 0; + if (insecureConcurrency > 0) + { + item.SetProtocolExtra(item.GetProtocolExtra() with + { + InsecureConcurrency = insecureConcurrency, + }); + } + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var userInfo = item.Username.IsNotEmpty() ? $"{Utils.UrlEncode(item.Username)}:{Utils.UrlEncode(item.Password)}" : Utils.UrlEncode(item.Password); + var dicQuery = new Dictionary(); + ToUriQuery(item, Global.None, ref dicQuery); + if (item.GetProtocolExtra().InsecureConcurrency > 0) + { + dicQuery.Add("insecure-concurrency", item.GetProtocolExtra()?.InsecureConcurrency.ToString()); + } + + var query = dicQuery.Count > 0 + ? ("?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray())) + : string.Empty; + var url = $"{userInfo}@{GetIpv6(item.Address)}:{item.Port}"; + + if (item.Network == "quic") + { + return $"{Global.NaiveQuicProtocolShare}{url}{query}{remark}"; + } + else + { + return $"{Global.NaiveHttpsProtocolShare}{url}{query}{remark}"; + } + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs index 56f78ea9..d1f802b0 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs @@ -30,7 +30,10 @@ public class TuicFmt : BaseFmt var query = Utils.ParseQueryString(url.Query); ResolveUriQuery(query, ref item); - item.HeaderType = GetQueryValue(query, "congestion_control"); + item.SetProtocolExtra(item.GetProtocolExtra() with + { + CongestionControl = GetQueryValue(query, "congestion_control") + }); return item; } @@ -51,7 +54,10 @@ public class TuicFmt : BaseFmt var dicQuery = new Dictionary(); ToUriQueryLite(item, ref dicQuery); - dicQuery.Add("congestion_control", item.HeaderType); + if (!item.GetProtocolExtra().CongestionControl.IsNullOrEmpty()) + { + dicQuery.Add("congestion_control", item.GetProtocolExtra().CongestionControl); + } return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Username ?? ""}:{item.Password}", dicQuery, remark); } diff --git a/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayN/ServiceLib/Manager/AppManager.cs index 43c5611d..35847d71 100644 --- a/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayN/ServiceLib/Manager/AppManager.cs @@ -305,12 +305,11 @@ public sealed class AppManager return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); } +#pragma warning disable CS0618 public async Task MigrateProfileExtra() { await MigrateProfileExtraGroup(); -#pragma warning disable CS0618 - const int pageSize = 100; var offset = 0; @@ -334,7 +333,6 @@ public sealed class AppManager } //await ProfileGroupItemManager.Instance.ClearAll(); -#pragma warning restore CS0618 } private async Task MigrateProfileExtraSub(List batch) @@ -380,6 +378,7 @@ public sealed class AppManager break; case EConfigType.TUIC: + extra = extra with { CongestionControl = item.HeaderType.NullIfEmpty(), }; item.Username = item.Id; item.Id = item.Security; item.Password = item.Security; @@ -436,7 +435,6 @@ public sealed class AppManager private async Task MigrateProfileExtraGroup() { -#pragma warning disable CS0618 var list = await SQLiteHelper.Instance.TableAsync().ToListAsync(); var groupItems = new ConcurrentDictionary(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!)); @@ -501,8 +499,8 @@ public sealed class AppManager return true; //await ProfileGroupItemManager.Instance.ClearAll(); -#pragma warning restore CS0618 } +#pragma warning restore CS0618 #endregion SqliteHelper diff --git a/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs b/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs index a768ba80..89f171ea 100644 --- a/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs +++ b/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs @@ -2,6 +2,9 @@ namespace ServiceLib.Models; public record ProtocolExtraItem { + public bool? Uot { get; init; } + public string? CongestionControl { get; init; } + // vmess public string? AlterId { get; init; } public string? VmessSecurity { get; init; } @@ -29,6 +32,9 @@ public record ProtocolExtraItem public string? Ports { get; init; } public string? HopInterval { get; init; } + // naiveproxy + public int? InsecureConcurrency { get; init; } + // group profile public string? GroupType { get; init; } public string? ChildItems { get; init; } diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index 03261c46..f2331cc4 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -134,10 +134,14 @@ public class Outbound4Sbox : BaseServer4Sbox public int? recv_window_conn { get; set; } public int? recv_window { get; set; } public bool? disable_mtu_discovery { get; set; } + public int? insecure_concurrency { get; set; } + public bool? udp_over_tcp { get; set; } public string? method { get; set; } public string? username { get; set; } public string? password { get; set; } public string? congestion_control { get; set; } + public bool? quic { get; set; } + public string? quic_congestion_control { get; set; } public string? version { get; set; } public string? network { get; set; } public string? packet_encoding { get; set; } diff --git a/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index 6d4eabcb..a6e6e8c7 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -179,6 +179,8 @@ public class ServersItem4Ray public string flow { get; set; } + public bool? uot { get; set; } + public List users { get; set; } } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index c75715ae..33fd37c5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -691,7 +691,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add Child 的本地化字符串。 + /// 查找类似 Add Child 的本地化字符串。 /// public static string menuAddChildServer { get { @@ -718,7 +718,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add [Hysteria2] 的本地化字符串。 + /// 查找类似 Add [Hysteria2] 的本地化字符串。 /// public static string menuAddHysteria2Server { get { @@ -727,7 +727,16 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add Policy Group 的本地化字符串。 + /// 查找类似 Add [NaïveProxy] 的本地化字符串。 + /// + public static string menuAddNaiveServer { + get { + return ResourceManager.GetString("menuAddNaiveServer", resourceCulture); + } + } + + /// + /// 查找类似 Add Policy Group 的本地化字符串。 /// public static string menuAddPolicyGroupServer { get { @@ -772,7 +781,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add [Shadowsocks] 的本地化字符串。 + /// 查找类似 Add [Shadowsocks] 的本地化字符串。 /// public static string menuAddShadowsocksServer { get { @@ -781,7 +790,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add [SOCKS] 的本地化字符串。 + /// 查找类似 Add [SOCKS] 的本地化字符串。 /// public static string menuAddSocksServer { get { @@ -790,7 +799,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add [Trojan] 的本地化字符串。 + /// 查找类似 Add [Trojan] 的本地化字符串。 /// public static string menuAddTrojanServer { get { @@ -799,7 +808,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add [TUIC] 的本地化字符串。 + /// 查找类似 Add [TUIC] 的本地化字符串。 /// public static string menuAddTuicServer { get { @@ -808,7 +817,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add [VLESS] 的本地化字符串。 + /// 查找类似 Add [VLESS] 的本地化字符串。 /// public static string menuAddVlessServer { get { @@ -817,7 +826,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add [VMess] 的本地化字符串。 + /// 查找类似 Add [VMess] 的本地化字符串。 /// public static string menuAddVmessServer { get { @@ -826,7 +835,7 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Add [WireGuard] 的本地化字符串。 + /// 查找类似 Add [WireGuard] 的本地化字符串。 /// public static string menuAddWireguardServer { get { @@ -3105,6 +3114,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Insecure Concurrency 的本地化字符串。 + /// + public static string TbInsecureConcurrency { + get { + return ResourceManager.GetString("TbInsecureConcurrency", resourceCulture); + } + } + /// /// 查找类似 Most Stable 的本地化字符串。 /// @@ -4491,6 +4509,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 UDP over TCP 的本地化字符串。 + /// + public static string TbUot { + get { + return ResourceManager.GetString("TbUot", resourceCulture); + } + } + + /// + /// 查找类似 Username 的本地化字符串。 + /// + public static string TbUsername { + get { + return ResourceManager.GetString("TbUsername", resourceCulture); + } + } + /// /// 查找类似 Validate Regional Domain IPs 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 30f663e8..580f3f03 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1680,4 +1680,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Format + + UDP over TCP + + + Add NaïveProxy + + + Insecure Concurrency + + + Username + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 061d6dad..79887d15 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1677,4 +1677,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Format + + UDP over TCP + + + Ajouter [NaïveProxy] + + + Insecure Concurrency + + + Username + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 4d652587..2f8305c7 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1680,4 +1680,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Format + + UDP over TCP + + + [NaïveProxy] konfiguráció hozzáadása + + + Insecure Concurrency + + + Username + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 787e7bd9..b4658076 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -514,19 +514,19 @@ Add a custom configuration - Add [Shadowsocks] + Add [Shadowsocks] - Add [SOCKS] + Add [SOCKS] - Add [Trojan] + Add [Trojan] - Add [VLESS] + Add [VLESS] - Add [VMess] + Add [VMess] Select all @@ -1036,7 +1036,7 @@ Domain - Add [Hysteria2] + Add [Hysteria2] Hysteria Max bandwidth (Up/Down) @@ -1045,7 +1045,7 @@ Use System Hosts - Add [TUIC] + Add [TUIC] Congestion control @@ -1075,7 +1075,7 @@ Enable IPv6 Address - Add [WireGuard] + Add [WireGuard] Private Key @@ -1495,13 +1495,13 @@ Policy Group Type - Add Policy Group + Add Policy Group Add Proxy Chain - Add Child + Add Child Remove Child @@ -1680,4 +1680,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Format + + UDP over TCP + + + Add [NaïveProxy] + + + Insecure Concurrency + + + Username + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index ba2ef074..0f63f5c2 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1680,4 +1680,16 @@ Format + + UDP over TCP + + + Добавить сервер [NaïveProxy] + + + Insecure Concurrency + + + Username + diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 707609d0..11dc2588 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -517,16 +517,16 @@ 添加 [Shadowsocks] - 添加 [SOCKS] + 添加 [SOCKS] - 添加 [Trojan] + 添加 [Trojan] - 添加 [VLESS] + 添加 [VLESS] - 添加 [VMess] + 添加 [VMess] 全选 @@ -1033,7 +1033,7 @@ Domain - 添加 [Hysteria2] + 添加 [Hysteria2] Hysteria 最大带宽 (Up/Dw) @@ -1042,7 +1042,7 @@ 使用系统 hosts - 添加 [TUIC] + 添加 [TUIC] 拥塞控制算法 @@ -1072,7 +1072,7 @@ 启用 IPv6 - 添加 [WireGuard] + 添加 [WireGuard] PrivateKey @@ -1102,7 +1102,7 @@ *grpc Authority - 添加 [HTTP] + 添加 [HTTP] 启用分片 (Fragment) @@ -1381,7 +1381,7 @@ Mldsa65Verify - 添加 [Anytls] + 添加 [Anytls] 远程 DNS @@ -1677,4 +1677,16 @@ 格式化 + + UDP over TCP + + + 添加 [NaïveProxy] + + + 不安全并发 + + + 用户名 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 4e417308..1f039299 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1677,4 +1677,16 @@ Format + + UDP over TCP + + + 新增 [NaïveProxy] 節點 + + + Insecure Concurrency + + + Username + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index b30c9c48..0efdc139 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -112,6 +112,7 @@ public partial class CoreConfigSingboxService outbound.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : Global.None; outbound.password = _node.Password; + outbound.udp_over_tcp = protocolExtra.Uot == true ? true : null; if (_node.Network == nameof(ETransport.tcp) && _node.HeaderType == Global.TcpHeaderHttp) { @@ -269,7 +270,7 @@ public partial class CoreConfigSingboxService { outbound.uuid = _node.Username; outbound.password = _node.Password; - outbound.congestion_control = _node.HeaderType; + outbound.congestion_control = protocolExtra.CongestionControl; break; } case EConfigType.Anytls: @@ -277,6 +278,22 @@ public partial class CoreConfigSingboxService outbound.password = _node.Password; break; } + case EConfigType.Naive: + { + outbound.username = _node.Username; + outbound.password = _node.Password; + if (outbound.network == "quic") + { + outbound.quic = true; + outbound.quic_congestion_control = protocolExtra.CongestionControl.NullIfEmpty(); + } + if (protocolExtra.InsecureConcurrency > 0) + { + outbound.insecure_concurrency = protocolExtra.InsecureConcurrency; + } + outbound.udp_over_tcp = protocolExtra.Uot == true ? true : null; + break; + } } FillOutboundTls(outbound); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index c8862648..853ada7f 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -153,6 +153,7 @@ public partial class CoreConfigV2rayService serversItem.password = _node.Password; serversItem.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : "none"; + serversItem.uot = protocolExtra.Uot == true ? true : null; serversItem.ota = false; serversItem.level = 1; diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index 6af600f7..e792e8b7 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -61,6 +61,15 @@ public class AddServerViewModel : MyReactiveObject [Reactive] public int WgMtu { get; set; } + [Reactive] + public bool Uot { get; set; } + + [Reactive] + public string CongestionControl { get; set; } + + [Reactive] + public int InsecureConcurrency { get; set; } + public ReactiveCommand FetchCertCmd { get; } public ReactiveCommand FetchCertChainCmd { get; } public ReactiveCommand SaveCmd { get; } @@ -123,6 +132,9 @@ public class AddServerViewModel : MyReactiveObject WgInterfaceAddress = protocolExtra?.WgInterfaceAddress ?? string.Empty; WgReserved = protocolExtra?.WgReserved ?? string.Empty; WgMtu = protocolExtra?.WgMtu ?? 1280; + Uot = protocolExtra?.Uot ?? false; + CongestionControl = protocolExtra?.CongestionControl ?? string.Empty; + InsecureConcurrency = protocolExtra?.InsecureConcurrency ?? 0; } private async Task SaveServerAsync() @@ -185,6 +197,9 @@ public class AddServerViewModel : MyReactiveObject WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(), WgReserved = WgReserved.NullIfEmpty(), WgMtu = WgMtu >= 576 ? WgMtu : null, + Uot = Uot ? true : null, + CongestionControl = CongestionControl.NullIfEmpty(), + InsecureConcurrency = InsecureConcurrency > 0 ? InsecureConcurrency : null }); if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index ee92a0cd..36027b79 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -18,6 +18,7 @@ public class MainWindowViewModel : MyReactiveObject public ReactiveCommand AddTuicServerCmd { get; } public ReactiveCommand AddWireguardServerCmd { get; } public ReactiveCommand AddAnytlsServerCmd { get; } + public ReactiveCommand AddNaiveServerCmd { get; } public ReactiveCommand AddCustomServerCmd { get; } public ReactiveCommand AddPolicyGroupServerCmd { get; } public ReactiveCommand AddProxyChainServerCmd { get; } @@ -117,6 +118,10 @@ public class MainWindowViewModel : MyReactiveObject { await AddServerAsync(EConfigType.Anytls); }); + AddNaiveServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(EConfigType.Naive); + }); AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(EConfigType.Custom); diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index e9ded8aa..4da1ae82 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -198,6 +198,19 @@ Width="300" Margin="{StaticResource Margin4}" /> + + + + + + + + + + + + + + + + + + + case EConfigType.Anytls: gridAnytls.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; lstStreamSecurity.Add(Global.StreamSecurityReality); cmbCoreType.IsEnabled = false; gridFinalmask.IsVisible = false; break; + + case EConfigType.Naive: + gridNaive.IsVisible = true; + cmbCoreType.IsEnabled = false; + gridFinalmask.IsVisible = false; + cmbFingerprint.IsEnabled = false; + cmbFingerprint.SelectedValue = string.Empty; + cmbAllowInsecure.IsEnabled = false; + cmbAllowInsecure.SelectedValue = string.Empty; + + cmbHeaderType12.ItemsSource = Global.NaiveCongestionControls; + break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -122,6 +136,7 @@ public partial class AddServerWindow : WindowBase case EConfigType.Shadowsocks: 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.Uot, v => v.togUotEnabled3.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); break; @@ -156,7 +171,7 @@ public partial class AddServerWindow : WindowBase case EConfigType.TUIC: this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables); break; case EConfigType.WireGuard: @@ -168,7 +183,15 @@ public partial class AddServerWindow : WindowBase break; case EConfigType.Anytls: - this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId10.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId11.Text).DisposeWith(disposables); + break; + + case EConfigType.Naive: + this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId12.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity12.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType12.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(disposables); break; } this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml index ca59c472..8d9a5f85 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml @@ -50,6 +50,7 @@ + diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs index 7e96fad2..996e7c83 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs @@ -72,6 +72,7 @@ public partial class MainWindow : WindowBase this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddPolicyGroupServerCmd, v => v.menuAddPolicyGroupServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddProxyChainServerCmd, v => v.menuAddProxyChainServer).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml index e78e7f9b..b87493e7 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -277,6 +277,20 @@ Margin="{StaticResource Margin4}" Style="{StaticResource DefComboBox}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.Text).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); break; @@ -151,7 +166,7 @@ public partial class AddServerWindow case EConfigType.TUIC: this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType8.Text).DisposeWith(disposables); break; case EConfigType.WireGuard: @@ -163,7 +178,15 @@ public partial class AddServerWindow break; case EConfigType.Anytls: - this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId10.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId11.Text).DisposeWith(disposables); + break; + + case EConfigType.Naive: + this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId12.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity12.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType12.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(disposables); break; } this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.Text).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml b/v2rayN/v2rayN/Views/MainWindow.xaml index 1c49ca24..f6dfe42b 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml +++ b/v2rayN/v2rayN/Views/MainWindow.xaml @@ -124,6 +124,10 @@ x:Name="menuAddAnytlsServer" Height="{StaticResource MenuItemHeight}" Header="{x:Static resx:ResUI.menuAddAnytlsServer}" /> + diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml.cs b/v2rayN/v2rayN/Views/MainWindow.xaml.cs index 42d8aabc..81f2f720 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/MainWindow.xaml.cs @@ -71,6 +71,7 @@ public partial class MainWindow this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddPolicyGroupServerCmd, v => v.menuAddPolicyGroupServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddProxyChainServerCmd, v => v.menuAddProxyChainServer).DisposeWith(disposables); From bbfd93f5a300ac8e02fc3a09a76f3538f935d0e9 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 21 Mar 2026 08:55:43 +0000 Subject: [PATCH 31/35] Add sing-box ech query server name (#8869) --- .../Services/CoreConfig/Singbox/SingboxOutboundService.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 0efdc139..1d3f407e 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -744,13 +744,12 @@ public partial class CoreConfigSingboxService }, null); } var idx = echConfig.IndexOf('+'); - // NOTE: query_server_name, since sing-box 1.13.0 - //var queryServerName = idx > 0 ? echConfig[..idx] : null; + var queryServerName = idx > 0 ? echConfig[..idx] : null; var echDnsServer = idx > 0 ? echConfig[(idx + 1)..] : echConfig; return (new Ech4Sbox() { enabled = true, - query_server_name = null, + query_server_name = queryServerName, }, ParseDnsAddress(echDnsServer)); } } From db9fe9c5eaed3fc0e1ff131012e969580cb2b6dd Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 21 Mar 2026 11:47:04 +0000 Subject: [PATCH 32/35] Fix (#8972) --- .../Handler/Builder/NodeValidator.cs | 6 ---- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 3 +- v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs | 18 ++++++---- v2rayN/ServiceLib/Models/ProtocolExtraItem.cs | 1 + .../Singbox/SingboxOutboundService.cs | 2 +- .../ViewModels/AddServerViewModel.cs | 11 ++++-- .../Views/AddServerWindow.axaml | 31 +++++++++++------ .../Views/AddServerWindow.axaml.cs | 14 +++++--- v2rayN/v2rayN/Views/AddServerWindow.xaml | 34 ++++++++++++------- v2rayN/v2rayN/Views/AddServerWindow.xaml.cs | 13 ++++--- 10 files changed, 85 insertions(+), 48 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs index 053c51f2..65110b2a 100644 --- a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs +++ b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs @@ -152,12 +152,6 @@ public class NodeValidator private static string? ValidateSingboxTransport(EConfigType configType, string net) { - // Naive support tcp and quic transports - if (configType == EConfigType.Naive && net == "quic") - { - return null; - } - // sing-box does not support xhttp / kcp transports if (SingboxUnsupportedTransports.Contains(net)) { diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index bbc23f1c..524d9d6a 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -848,7 +848,8 @@ public static class ConfigHandler profileItem.Address = profileItem.Address.TrimEx(); profileItem.Username = profileItem.Username.TrimEx(); profileItem.Password = profileItem.Password.TrimEx(); - profileItem.Network = profileItem.Network == "quic" ? "quic" : string.Empty; + profileItem.Alpn = string.Empty; + profileItem.Network = string.Empty; if (profileItem.StreamSecurity.IsNullOrEmpty()) { profileItem.StreamSecurity = Global.StreamSecurity; diff --git a/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs index aec27d57..5792472b 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs @@ -19,9 +19,13 @@ public class NaiveFmt : BaseFmt Address = parsedUrl.IdnHost, Port = parsedUrl.Port, }; + var protocolExtra = item.GetProtocolExtra(); if (parsedUrl.Scheme.Contains("quic")) { - item.Network = "quic"; + protocolExtra = protocolExtra with + { + NaiveQuic = true, + }; } var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); if (rawUserInfo.Contains(':')) @@ -40,12 +44,13 @@ public class NaiveFmt : BaseFmt var insecureConcurrency = int.TryParse(GetQueryValue(query, "insecure-concurrency"), out var ic) ? ic : 0; if (insecureConcurrency > 0) { - item.SetProtocolExtra(item.GetProtocolExtra() with + protocolExtra = protocolExtra with { InsecureConcurrency = insecureConcurrency, - }); + }; } + item.SetProtocolExtra(protocolExtra); return item; } @@ -63,9 +68,10 @@ public class NaiveFmt : BaseFmt var userInfo = item.Username.IsNotEmpty() ? $"{Utils.UrlEncode(item.Username)}:{Utils.UrlEncode(item.Password)}" : Utils.UrlEncode(item.Password); var dicQuery = new Dictionary(); ToUriQuery(item, Global.None, ref dicQuery); - if (item.GetProtocolExtra().InsecureConcurrency > 0) + var protocolExtra = item.GetProtocolExtra(); + if (protocolExtra.InsecureConcurrency > 0) { - dicQuery.Add("insecure-concurrency", item.GetProtocolExtra()?.InsecureConcurrency.ToString()); + dicQuery.Add("insecure-concurrency", protocolExtra?.InsecureConcurrency.ToString()); } var query = dicQuery.Count > 0 @@ -73,7 +79,7 @@ public class NaiveFmt : BaseFmt : string.Empty; var url = $"{userInfo}@{GetIpv6(item.Address)}:{item.Port}"; - if (item.Network == "quic") + if (protocolExtra.NaiveQuic == true) { return $"{Global.NaiveQuicProtocolShare}{url}{query}{remark}"; } diff --git a/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs b/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs index 89f171ea..03403f5b 100644 --- a/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs +++ b/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs @@ -34,6 +34,7 @@ public record ProtocolExtraItem // naiveproxy public int? InsecureConcurrency { get; init; } + public bool? NaiveQuic { get; init; } // group profile public string? GroupType { get; init; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 1d3f407e..4d4bc04e 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -282,7 +282,7 @@ public partial class CoreConfigSingboxService { outbound.username = _node.Username; outbound.password = _node.Password; - if (outbound.network == "quic") + if (protocolExtra.NaiveQuic == true) { outbound.quic = true; outbound.quic_congestion_control = protocolExtra.CongestionControl.NullIfEmpty(); diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index e792e8b7..e5c307c8 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -68,7 +68,10 @@ public class AddServerViewModel : MyReactiveObject public string CongestionControl { get; set; } [Reactive] - public int InsecureConcurrency { get; set; } + public int? InsecureConcurrency { get; set; } + + [Reactive] + public bool NaiveQuic { get; set; } public ReactiveCommand FetchCertCmd { get; } public ReactiveCommand FetchCertChainCmd { get; } @@ -134,7 +137,8 @@ public class AddServerViewModel : MyReactiveObject WgMtu = protocolExtra?.WgMtu ?? 1280; Uot = protocolExtra?.Uot ?? false; CongestionControl = protocolExtra?.CongestionControl ?? string.Empty; - InsecureConcurrency = protocolExtra?.InsecureConcurrency ?? 0; + InsecureConcurrency = protocolExtra?.InsecureConcurrency > 0 ? protocolExtra.InsecureConcurrency : null; + NaiveQuic = protocolExtra?.NaiveQuic ?? false; } private async Task SaveServerAsync() @@ -199,7 +203,8 @@ public class AddServerViewModel : MyReactiveObject WgMtu = WgMtu >= 576 ? WgMtu : null, Uot = Uot ? true : null, CongestionControl = CongestionControl.NullIfEmpty(), - InsecureConcurrency = InsecureConcurrency > 0 ? InsecureConcurrency : null + InsecureConcurrency = InsecureConcurrency > 0 ? InsecureConcurrency : null, + NaiveQuic = NaiveQuic ? true : null, }); if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 4da1ae82..4dc378b4 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -497,7 +497,7 @@ VerticalAlignment="Center" Text="{x:Static resx:ResUI.TbHeaderType8}" /> - + + VerticalAlignment="Center" + Orientation="Horizontal"> + + + - + + Margin="{StaticResource Margin4}" + HorizontalAlignment="Left" /> cmbFingerprint.SelectedValue = string.Empty; gridFinalmask.IsVisible = false; - cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; + cmbCongestionControl8.ItemsSource = Global.TuicCongestionControls; break; case EConfigType.WireGuard: @@ -103,14 +103,18 @@ public partial class AddServerWindow : WindowBase case EConfigType.Naive: gridNaive.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; cmbCoreType.IsEnabled = false; gridFinalmask.IsVisible = false; cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; + cmbAlpn.IsEnabled = false; + cmbAlpn.SelectedValue = string.Empty; cmbAllowInsecure.IsEnabled = false; cmbAllowInsecure.SelectedValue = string.Empty; - cmbHeaderType12.ItemsSource = Global.NaiveCongestionControls; + cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls; break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -171,7 +175,7 @@ public partial class AddServerWindow : WindowBase case EConfigType.TUIC: this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbCongestionControl8.SelectedValue).DisposeWith(disposables); break; case EConfigType.WireGuard: @@ -189,8 +193,10 @@ public partial class AddServerWindow : WindowBase case EConfigType.Naive: this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId12.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity12.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.NaiveQuic, v => v.togNaiveQuic12.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.NaiveQuic, v => v.cmbCongestionControl12.IsEnabled).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbCongestionControl12.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType12.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(disposables); break; } diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml index b87493e7..1eb28d9d 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -660,7 +660,7 @@ Style="{StaticResource ToolbarTextBlock}" Text="{x:Static resx:ResUI.TbHeaderType8}" /> - + + VerticalAlignment="Center" + Orientation="Horizontal"> + + + - + + HorizontalAlignment="Left" + Style="{StaticResource DefTextBox}" /> vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType8.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbCongestionControl8.Text).DisposeWith(disposables); break; case EConfigType.WireGuard: @@ -184,8 +188,9 @@ public partial class AddServerWindow case EConfigType.Naive: this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId12.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity12.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.NaiveQuic, v => v.togNaiveQuic12.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbCongestionControl12.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType12.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(disposables); break; } From 194c2402434ba4d81d507115312f3385fc58bf0e Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 21 Mar 2026 11:55:34 +0000 Subject: [PATCH 33/35] Add ICMP routing (#8894) --- v2rayN/ServiceLib/Global.cs | 9 ++++++ v2rayN/ServiceLib/Handler/ConfigHandler.cs | 1 + v2rayN/ServiceLib/Models/ConfigItems.cs | 1 + v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 9 ++++++ v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.fr.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.hu.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.ru.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 3 ++ v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 3 ++ .../Singbox/SingboxRoutingService.cs | 30 +++++++++++++++++++ .../ViewModels/OptionSettingViewModel.cs | 3 ++ .../Views/OptionSettingWindow.axaml | 14 +++++++++ .../Views/OptionSettingWindow.axaml.cs | 2 ++ v2rayN/v2rayN/Views/OptionSettingWindow.xaml | 16 ++++++++++ .../v2rayN/Views/OptionSettingWindow.xaml.cs | 2 ++ 17 files changed, 108 insertions(+) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 0a52f678..e9a5a41e 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -675,5 +675,14 @@ public class Global "" ]; + public static readonly List TunIcmpRoutingPolicies = + [ + "rule", + "direct", + "unreachable", + "drop", + "reply", + ]; + #endregion const } diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 524d9d6a..8f079451 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -91,6 +91,7 @@ public static class ConfigHandler { EnableTun = false, Mtu = 9000, + IcmpRouting = Global.TunIcmpRoutingPolicies.First(), }; config.GuiItem ??= new(); config.MsgUIItem ??= new(); diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index 741c21e5..8583906b 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -144,6 +144,7 @@ public class TunModeItem public string Stack { get; set; } public int Mtu { get; set; } public bool EnableIPv6Address { get; set; } + public string IcmpRouting { get; set; } } [Serializable] diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 33fd37c5..bd26cd9e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -3078,6 +3078,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 ICMP routing policy 的本地化字符串。 + /// + public static string TbIcmpRoutingPolicy { + get { + return ResourceManager.GetString("TbIcmpRoutingPolicy", resourceCulture); + } + } + /// /// 查找类似 UUID(id) 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 580f3f03..f95646a6 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1692,4 +1692,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Username + + ICMP routing policy + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 79887d15..fcdab47a 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1689,4 +1689,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Username + + ICMP routing policy + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 2f8305c7..a22a476a 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1692,4 +1692,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Username + + ICMP routing policy + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index b4658076..26d54e51 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1692,4 +1692,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Username + + ICMP routing policy + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 0f63f5c2..aa0594bd 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1692,4 +1692,7 @@ Username + + ICMP routing policy + diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 11dc2588..484e2a12 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1689,4 +1689,7 @@ 用户名 + + ICMP 路由策略 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 1f039299..8496f293 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1689,4 +1689,7 @@ Username + + ICMP routing policy + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs index 45620c41..7e1af389 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs @@ -47,6 +47,36 @@ public partial class CoreConfigSingboxService outbound = Global.DirectTag, process_name = lstDirectExe }); + + // ICMP Routing + var icmpRouting = _config.TunModeItem.IcmpRouting ?? ""; + if (!Global.TunIcmpRoutingPolicies.Contains(icmpRouting)) + { + icmpRouting = Global.TunIcmpRoutingPolicies.First(); + } + if (icmpRouting == "direct") + { + _coreConfig.route.rules.Add(new() + { + network = ["icmp"], + outbound = Global.DirectTag, + }); + } + else if (icmpRouting != "rule") + { + var rejectMethod = icmpRouting switch + { + "unreachable" => "default", + "drop" => "drop", + _ => "reply", + }; + _coreConfig.route.rules.Add(new() + { + network = ["icmp"], + action = "reject", + method = rejectMethod, + }); + } } if (_config.Inbound.First().SniffingEnabled) diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index d936c5df..5df75264 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -95,6 +95,7 @@ public class OptionSettingViewModel : MyReactiveObject [Reactive] public string TunStack { get; set; } [Reactive] public int TunMtu { get; set; } [Reactive] public bool TunEnableIPv6Address { get; set; } + [Reactive] public string TunIcmpRouting { get; set; } #endregion Tun mode @@ -218,6 +219,7 @@ public class OptionSettingViewModel : MyReactiveObject TunStack = _config.TunModeItem.Stack; TunMtu = _config.TunModeItem.Mtu; TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address; + TunIcmpRouting = _config.TunModeItem.IcmpRouting; #endregion Tun mode @@ -376,6 +378,7 @@ public class OptionSettingViewModel : MyReactiveObject _config.TunModeItem.Stack = TunStack; _config.TunModeItem.Mtu = TunMtu; _config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address; + _config.TunModeItem.IcmpRouting = TunIcmpRouting; //coreType await SaveCoreType(); diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index 8c4f1fcc..7d80088a 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -824,6 +824,20 @@ Margin="{StaticResource Margin4}" HorizontalAlignment="Left" /> + + + cmbmux4SboxProtocol.ItemsSource = Global.SingboxMuxs; cmbMtu.ItemsSource = Global.TunMtus; cmbStack.ItemsSource = Global.TunStacks; + cmbIcmpRoutingPolicy.ItemsSource = Global.TunIcmpRoutingPolicies; cmbCoreType1.ItemsSource = Global.CoreTypes; cmbCoreType2.ItemsSource = Global.CoreTypes; @@ -114,6 +115,7 @@ public partial class OptionSettingWindow : WindowBase this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType2, v => v.cmbCoreType2.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 133b4134..41d3b8d1 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -1076,6 +1076,22 @@ HorizontalAlignment="Left" Style="{StaticResource DefComboBox}" /> + + + vm.TunStack, v => v.cmbStack.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType2, v => v.cmbCoreType2.Text).DisposeWith(disposables); From 2caec729fce187fb157bad1735f4836283a207b7 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sat, 21 Mar 2026 11:59:52 +0000 Subject: [PATCH 34/35] Protect xhttp split address (#8970) --- .../Handler/Builder/CoreConfigContextBuilder.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs index ddc98a1f..a8cca654 100644 --- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs +++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs @@ -322,6 +322,7 @@ public class CoreConfigContextBuilder context.ProtectDomainList.Add(address); } + // ech query server name protect if (!node.EchConfigList.IsNullOrEmpty()) { var echQuerySni = node.Sni; @@ -338,6 +339,20 @@ public class CoreConfigContextBuilder } } + // xhttp downloadSettings address protect + if (!string.IsNullOrEmpty(node.Extra) + && JsonUtils.ParseJson(node.Extra) is JsonObject extra + && extra.TryGetPropertyValue("downloadSettings", out var dsNode) + && dsNode is JsonObject downloadSettings + && downloadSettings.TryGetPropertyValue("address", out var dAddrNode) + && dAddrNode is JsonValue dAddrValue + && dAddrValue.TryGetValue(out string? dAddr) + && !string.IsNullOrEmpty(dAddr) + && Utils.IsDomain(dAddr)) + { + context.ProtectDomainList.Add(dAddr); + } + return nodeValidatorResult; } From 74e5ead1ed149463568e9f4f04d2df416f533704 Mon Sep 17 00:00:00 2001 From: tt2563 <243264479+tt2563@users.noreply.github.com> Date: Sun, 22 Mar 2026 14:33:47 +0800 Subject: [PATCH 35/35] Update Traditional Chinese translation (#8973) * Update Traditional Chinese translation * Update confirmation message for removing nodes --- v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 8496f293..c404ebcc 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1672,10 +1672,10 @@ 全選 - Paste + 貼上 - Format + 格式化 UDP over TCP @@ -1684,12 +1684,12 @@ 新增 [NaïveProxy] 節點 - Insecure Concurrency + 不安全的並行處理 - Username + 使用者名稱 - ICMP routing policy + ICMP 路由策略 - \ No newline at end of file +